From dc85008d7fde5f341b7431f8137b595b01f6fa79 Mon Sep 17 00:00:00 2001 From: Hein Date: Sun, 15 Feb 2026 15:17:39 +0200 Subject: [PATCH] feat(api): add ResolveSpec and WebSocket client implementations - Introduced ResolveSpecClient for REST API interactions. - Added WebSocketClient for real-time communication. - Created types and utility functions for both clients. - Removed deprecated types and example files. - Configured TypeScript and Vite for building the library. --- .gitignore | 3 +- README.md | 11 + resolvespec-js/PLAN.md | 132 + resolvespec-js/README.md | 213 ++ resolvespec-js/WEBSOCKET.md | 530 --- resolvespec-js/dist/index.cjs | 1 + resolvespec-js/dist/index.d.ts | 366 ++ resolvespec-js/dist/index.js | 452 +++ resolvespec-js/package.json | 64 +- resolvespec-js/pnpm-lock.yaml | 3376 +++++++++++++++++ resolvespec-js/src/__tests__/common.test.ts | 143 + .../src/__tests__/headerspec.test.ts | 239 ++ .../src/__tests__/resolvespec.test.ts | 178 + .../src/__tests__/websocketspec.test.ts | 336 ++ resolvespec-js/src/api.ts | 132 - resolvespec-js/src/common/index.ts | 1 + resolvespec-js/src/common/types.ts | 129 + resolvespec-js/src/examples.ts | 68 - resolvespec-js/src/headerspec/client.ts | 296 ++ resolvespec-js/src/headerspec/index.ts | 7 + resolvespec-js/src/index.ts | 16 +- resolvespec-js/src/resolvespec/client.ts | 141 + resolvespec-js/src/resolvespec/index.ts | 1 + resolvespec-js/src/types.ts | 86 - resolvespec-js/src/websocket-examples.ts | 427 --- .../client.ts} | 78 +- resolvespec-js/src/websocketspec/index.ts | 2 + .../types.ts} | 21 +- resolvespec-js/tsconfig.json | 21 + resolvespec-js/vite.config.ts | 20 + 30 files changed, 6140 insertions(+), 1350 deletions(-) create mode 100644 resolvespec-js/PLAN.md create mode 100644 resolvespec-js/README.md delete mode 100644 resolvespec-js/WEBSOCKET.md create mode 100644 resolvespec-js/dist/index.cjs create mode 100644 resolvespec-js/dist/index.d.ts create mode 100644 resolvespec-js/dist/index.js create mode 100644 resolvespec-js/pnpm-lock.yaml create mode 100644 resolvespec-js/src/__tests__/common.test.ts create mode 100644 resolvespec-js/src/__tests__/headerspec.test.ts create mode 100644 resolvespec-js/src/__tests__/resolvespec.test.ts create mode 100644 resolvespec-js/src/__tests__/websocketspec.test.ts delete mode 100644 resolvespec-js/src/api.ts create mode 100644 resolvespec-js/src/common/index.ts create mode 100644 resolvespec-js/src/common/types.ts delete mode 100644 resolvespec-js/src/examples.ts create mode 100644 resolvespec-js/src/headerspec/client.ts create mode 100644 resolvespec-js/src/headerspec/index.ts create mode 100644 resolvespec-js/src/resolvespec/client.ts create mode 100644 resolvespec-js/src/resolvespec/index.ts delete mode 100644 resolvespec-js/src/types.ts delete mode 100644 resolvespec-js/src/websocket-examples.ts rename resolvespec-js/src/{websocket-client.ts => websocketspec/client.ts} (90%) create mode 100644 resolvespec-js/src/websocketspec/index.ts rename resolvespec-js/src/{websocket-types.ts => websocketspec/types.ts} (86%) create mode 100644 resolvespec-js/tsconfig.json create mode 100644 resolvespec-js/vite.config.ts diff --git a/.gitignore b/.gitignore index 3fb767e..32f5e2f 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,5 @@ go.work.sum bin/ test.db /testserver -tests/data/ \ No newline at end of file +tests/data/ +node_modules/ \ No newline at end of file diff --git a/README.md b/README.md index aee4295..dbb8152 100644 --- a/README.md +++ b/README.md @@ -357,6 +357,17 @@ Execute SQL functions and queries through a simple HTTP API with header-based pa For complete documentation, see [pkg/funcspec/](pkg/funcspec/). +#### ResolveSpec JS - TypeScript Client Library + +TypeScript/JavaScript client library supporting all three REST and WebSocket protocols. + +**Clients**: +- Body-based REST client (`read`, `create`, `update`, `deleteEntity`) +- Header-based REST client (`HeaderSpecClient`) +- WebSocket client (`WebSocketClient`) with CRUD, subscriptions, heartbeat, reconnect + +For complete documentation, see [resolvespec-js/README.md](resolvespec-js/README.md). + ### Real-Time Communication #### WebSocketSpec - WebSocket API diff --git a/resolvespec-js/PLAN.md b/resolvespec-js/PLAN.md new file mode 100644 index 0000000..62a4b9d --- /dev/null +++ b/resolvespec-js/PLAN.md @@ -0,0 +1,132 @@ +# ResolveSpec JS - Implementation Plan + +TypeScript client library for ResolveSpec, RestHeaderSpec, WebSocket and MQTT APIs. + +--- + +## Status + +| Phase | Description | Status | +|-------|-------------|--------| +| 0 | Restructure into folders | Done | +| 1 | Fix types (align with Go) | Done | +| 2 | Fix REST client | Done | +| 3 | Build config | Done | +| 4 | Tests | Done | +| 5 | HeaderSpec client | Done | +| 6 | MQTT client | Planned | +| 6.5 | Unified class pattern + singleton factories | Done | +| 7 | Response cache (TTL) | Planned | +| 8 | TanStack Query integration | Planned | +| 9 | React Hooks | Planned | + +**Build:** `dist/index.js` (ES) + `dist/index.cjs` (CJS) + `.d.ts` declarations +**Tests:** 65 passing (common: 10, resolvespec: 13, websocketspec: 15, headerspec: 27) + +--- + +## Folder Structure + +``` +src/ +├── common/ +│ ├── types.ts # Core types aligned with Go pkg/common/types.go +│ └── index.ts +├── resolvespec/ +│ ├── client.ts # ResolveSpecClient class + createResolveSpecClient singleton +│ └── index.ts +├── headerspec/ +│ ├── client.ts # HeaderSpecClient class + createHeaderSpecClient singleton + buildHeaders utility +│ └── index.ts +├── websocketspec/ +│ ├── types.ts # WS-specific types (WSMessage, WSOptions, etc.) +│ ├── client.ts # WebSocketClient class + createWebSocketClient singleton +│ └── index.ts +├── mqttspec/ # Future +│ ├── types.ts +│ ├── client.ts +│ └── index.ts +├── __tests__/ +│ ├── common.test.ts +│ ├── resolvespec.test.ts +│ ├── headerspec.test.ts +│ └── websocketspec.test.ts +└── index.ts # Root barrel export +``` + +--- + +## Type Alignment with Go + +Types in `src/common/types.ts` match `pkg/common/types.go`: + +- **Operator**: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `like`, `ilike`, `in`, `contains`, `startswith`, `endswith`, `between`, `between_inclusive`, `is_null`, `is_not_null` +- **FilterOption**: `column`, `operator`, `value`, `logic_operator` (AND/OR) +- **Options**: `columns`, `omit_columns`, `filters`, `sort`, `limit`, `offset`, `preload`, `customOperators`, `computedColumns`, `parameters`, `cursor_forward`, `cursor_backward`, `fetch_row_number` +- **PreloadOption**: `relation`, `table_name`, `columns`, `omit_columns`, `sort`, `filters`, `where`, `limit`, `offset`, `updatable`, `recursive`, `computed_ql`, `primary_key`, `related_key`, `foreign_key`, `recursive_child_key`, `sql_joins`, `join_aliases` +- **Parameter**: `name`, `value`, `sequence?` +- **Metadata**: `total`, `count`, `filtered`, `limit`, `offset`, `row_number?` +- **APIError**: `code`, `message`, `details?`, `detail?` + +--- + +## HeaderSpec Header Mapping + +Maps Options to HTTP headers per Go `restheadspec/headers.go`: + +| Header | Options field | Format | +|--------|--------------|--------| +| `X-Select-Fields` | `columns` | comma-separated | +| `X-Not-Select-Fields` | `omit_columns` | comma-separated | +| `X-FieldFilter-{col}` | `filters` (eq, AND) | value | +| `X-SearchOp-{op}-{col}` | `filters` (AND) | value | +| `X-SearchOr-{op}-{col}` | `filters` (OR) | value | +| `X-Sort` | `sort` | `+col` (asc), `-col` (desc) | +| `X-Limit` | `limit` | number | +| `X-Offset` | `offset` | number | +| `X-Cursor-Forward` | `cursor_forward` | string | +| `X-Cursor-Backward` | `cursor_backward` | string | +| `X-Preload` | `preload` | `Rel:col1,col2` pipe-separated | +| `X-Fetch-RowNumber` | `fetch_row_number` | string | +| `X-CQL-SEL-{col}` | `computedColumns` | expression | +| `X-Custom-SQL-W` | `customOperators` | SQL AND-joined | + +Complex values use `ZIP_` + base64 encoding. +HTTP methods: GET=read, POST=create, PUT=update, DELETE=delete. + +--- + +## Build & Test + +```bash +pnpm install +pnpm run build # vite library mode → dist/ +pnpm run test # vitest +pnpm run lint # eslint +``` + +**Config files:** `tsconfig.json` (ES2020, strict, bundler), `vite.config.ts` (lib mode, dts via vite-plugin-dts) +**Externals:** `uuid`, `semver` + +--- + +## Remaining Work + +- **Phase 6 — MQTT Client**: Topic-based CRUD over MQTT (optional/future) +- **Phase 7 — Cache**: In-memory response cache with TTL, key = URL + options hash, auto-invalidation on CUD, `skipCache` flag +- **Phase 8 — TanStack Query Integration**: Query/mutation hooks wrapping each client, query key factories, automatic cache invalidation +- **Phase 9 — React Hooks**: `useResolveSpec`, `useHeaderSpec`, `useWebSocket` hooks with provider context, loading/error states +- ESLint config may need updating for new folder structure + +--- + +## Reference Files + +| Purpose | Path | +|---------|------| +| Go types (source of truth) | `pkg/common/types.go` | +| Go REST handler | `pkg/resolvespec/handler.go` | +| Go HeaderSpec handler | `pkg/restheadspec/handler.go` | +| Go HeaderSpec header parsing | `pkg/restheadspec/headers.go` | +| Go test models | `pkg/testmodels/business.go` | +| Go tests | `tests/crud_test.go` | diff --git a/resolvespec-js/README.md b/resolvespec-js/README.md new file mode 100644 index 0000000..c74691f --- /dev/null +++ b/resolvespec-js/README.md @@ -0,0 +1,213 @@ +# ResolveSpec JS + +TypeScript client library for ResolveSpec APIs. Supports body-based REST, header-based REST, and WebSocket protocols. + +## Install + +```bash +pnpm add @warkypublic/resolvespec-js +``` + +## Clients + +| Client | Protocol | Singleton Factory | +| --- | --- | --- | +| `ResolveSpecClient` | REST (body-based) | `getResolveSpecClient(config)` | +| `HeaderSpecClient` | REST (header-based) | `getHeaderSpecClient(config)` | +| `WebSocketClient` | WebSocket | `getWebSocketClient(config)` | + +All clients use the class pattern. Singleton factories return cached instances keyed by URL. + +## REST Client (Body-Based) + +Options sent in JSON request body. Maps to Go `pkg/resolvespec`. + +```typescript +import { ResolveSpecClient, getResolveSpecClient } from '@warkypublic/resolvespec-js'; + +// Class instantiation +const client = new ResolveSpecClient({ baseUrl: 'http://localhost:3000', token: 'your-token' }); + +// Or singleton factory (returns cached instance per baseUrl) +const client = getResolveSpecClient({ baseUrl: 'http://localhost:3000', token: 'your-token' }); + +// Read with filters, sort, pagination +const result = await client.read('public', 'users', undefined, { + columns: ['id', 'name', 'email'], + filters: [{ column: 'status', operator: 'eq', value: 'active' }], + sort: [{ column: 'name', direction: 'asc' }], + limit: 10, + offset: 0, + preload: [{ relation: 'Posts', columns: ['id', 'title'] }], +}); + +// Read by ID +const user = await client.read('public', 'users', 42); + +// Create +const created = await client.create('public', 'users', { name: 'New User' }); + +// Update +await client.update('public', 'users', { name: 'Updated' }, 42); + +// Delete +await client.delete('public', 'users', 42); + +// Metadata +const meta = await client.getMetadata('public', 'users'); +``` + +## HeaderSpec Client (Header-Based) + +Options sent via HTTP headers. Maps to Go `pkg/restheadspec`. + +```typescript +import { HeaderSpecClient, getHeaderSpecClient } from '@warkypublic/resolvespec-js'; + +const client = new HeaderSpecClient({ baseUrl: 'http://localhost:3000', token: 'your-token' }); +// Or: const client = getHeaderSpecClient({ baseUrl: 'http://localhost:3000', token: 'your-token' }); + +// GET with options as headers +const result = await client.read('public', 'users', undefined, { + columns: ['id', 'name'], + filters: [ + { column: 'status', operator: 'eq', value: 'active' }, + { column: 'age', operator: 'gte', value: 18, logic_operator: 'AND' }, + ], + sort: [{ column: 'name', direction: 'asc' }], + limit: 50, + preload: [{ relation: 'Department', columns: ['id', 'name'] }], +}); + +// POST create +await client.create('public', 'users', { name: 'New User' }); + +// PUT update +await client.update('public', 'users', '42', { name: 'Updated' }); + +// DELETE +await client.delete('public', 'users', '42'); +``` + +### Header Mapping + +| Header | Options Field | Format | +| --- | --- | --- | +| `X-Select-Fields` | `columns` | comma-separated | +| `X-Not-Select-Fields` | `omit_columns` | comma-separated | +| `X-FieldFilter-{col}` | `filters` (eq, AND) | value | +| `X-SearchOp-{op}-{col}` | `filters` (AND) | value | +| `X-SearchOr-{op}-{col}` | `filters` (OR) | value | +| `X-Sort` | `sort` | `+col` asc, `-col` desc | +| `X-Limit` / `X-Offset` | `limit` / `offset` | number | +| `X-Cursor-Forward` | `cursor_forward` | string | +| `X-Cursor-Backward` | `cursor_backward` | string | +| `X-Preload` | `preload` | `Rel:col1,col2` pipe-separated | +| `X-Fetch-RowNumber` | `fetch_row_number` | string | +| `X-CQL-SEL-{col}` | `computedColumns` | expression | +| `X-Custom-SQL-W` | `customOperators` | SQL AND-joined | + +### Utility Functions + +```typescript +import { buildHeaders, encodeHeaderValue, decodeHeaderValue } from '@warkypublic/resolvespec-js'; + +const headers = buildHeaders({ columns: ['id', 'name'], limit: 10 }); +// => { 'X-Select-Fields': 'id,name', 'X-Limit': '10' } + +const encoded = encodeHeaderValue('complex value'); // 'ZIP_...' +const decoded = decodeHeaderValue(encoded); // 'complex value' +``` + +## WebSocket Client + +Real-time CRUD with subscriptions. Maps to Go `pkg/websocketspec`. + +```typescript +import { WebSocketClient, getWebSocketClient } from '@warkypublic/resolvespec-js'; + +const ws = new WebSocketClient({ + url: 'ws://localhost:8080/ws', + reconnect: true, + heartbeatInterval: 30000, +}); +// Or: const ws = getWebSocketClient({ url: 'ws://localhost:8080/ws' }); + +await ws.connect(); + +// CRUD +const users = await ws.read('users', { schema: 'public', limit: 10 }); +const created = await ws.create('users', { name: 'New' }, { schema: 'public' }); +await ws.update('users', '1', { name: 'Updated' }); +await ws.delete('users', '1'); + +// Subscribe to changes +const subId = await ws.subscribe('users', (notification) => { + console.log(notification.operation, notification.data); +}); + +// Unsubscribe +await ws.unsubscribe(subId); + +// Events +ws.on('connect', () => console.log('connected')); +ws.on('disconnect', () => console.log('disconnected')); +ws.on('error', (err) => console.error(err)); + +ws.disconnect(); +``` + +## Types + +All types align with Go `pkg/common/types.go`. + +### Key Types + +```typescript +interface Options { + columns?: string[]; + omit_columns?: string[]; + filters?: FilterOption[]; + sort?: SortOption[]; + limit?: number; + offset?: number; + preload?: PreloadOption[]; + customOperators?: CustomOperator[]; + computedColumns?: ComputedColumn[]; + parameters?: Parameter[]; + cursor_forward?: string; + cursor_backward?: string; + fetch_row_number?: string; +} + +interface FilterOption { + column: string; + operator: Operator | string; + value: any; + logic_operator?: 'AND' | 'OR'; +} + +// Operators: eq, neq, gt, gte, lt, lte, like, ilike, in, +// contains, startswith, endswith, between, +// between_inclusive, is_null, is_not_null + +interface APIResponse { + success: boolean; + data: T; + metadata?: Metadata; + error?: APIError; +} +``` + +## Build + +```bash +pnpm install +pnpm run build # dist/index.js (ES) + dist/index.cjs (CJS) + .d.ts +pnpm run test # vitest +pnpm run lint # eslint +``` + +## License + +MIT diff --git a/resolvespec-js/WEBSOCKET.md b/resolvespec-js/WEBSOCKET.md deleted file mode 100644 index 00e4fa0..0000000 --- a/resolvespec-js/WEBSOCKET.md +++ /dev/null @@ -1,530 +0,0 @@ -# WebSocketSpec JavaScript Client - -A TypeScript/JavaScript client for connecting to WebSocketSpec servers with full support for real-time subscriptions, CRUD operations, and automatic reconnection. - -## Installation - -```bash -npm install @warkypublic/resolvespec-js -# or -yarn add @warkypublic/resolvespec-js -# or -pnpm add @warkypublic/resolvespec-js -``` - -## Quick Start - -```typescript -import { WebSocketClient } from '@warkypublic/resolvespec-js'; - -// Create client -const client = new WebSocketClient({ - url: 'ws://localhost:8080/ws', - reconnect: true, - debug: true -}); - -// Connect -await client.connect(); - -// Read records -const users = await client.read('users', { - schema: 'public', - filters: [ - { column: 'status', operator: 'eq', value: 'active' } - ], - limit: 10 -}); - -// Subscribe to changes -const subscriptionId = await client.subscribe('users', (notification) => { - console.log('User changed:', notification.operation, notification.data); -}, { schema: 'public' }); - -// Clean up -await client.unsubscribe(subscriptionId); -client.disconnect(); -``` - -## Features - -- **Real-Time Updates**: Subscribe to entity changes and receive instant notifications -- **Full CRUD Support**: Create, read, update, and delete operations -- **TypeScript Support**: Full type definitions included -- **Auto Reconnection**: Automatic reconnection with configurable retry logic -- **Heartbeat**: Built-in keepalive mechanism -- **Event System**: Listen to connection, error, and message events -- **Promise-based API**: All async operations return promises -- **Filter & Sort**: Advanced querying with filters, sorting, and pagination -- **Preloading**: Load related entities in a single query - -## Configuration - -```typescript -const client = new WebSocketClient({ - url: 'ws://localhost:8080/ws', // WebSocket server URL - reconnect: true, // Enable auto-reconnection - reconnectInterval: 3000, // Reconnection delay (ms) - maxReconnectAttempts: 10, // Max reconnection attempts - heartbeatInterval: 30000, // Heartbeat interval (ms) - debug: false // Enable debug logging -}); -``` - -## API Reference - -### Connection Management - -#### `connect(): Promise` -Connect to the WebSocket server. - -```typescript -await client.connect(); -``` - -#### `disconnect(): void` -Disconnect from the server. - -```typescript -client.disconnect(); -``` - -#### `isConnected(): boolean` -Check if currently connected. - -```typescript -if (client.isConnected()) { - console.log('Connected!'); -} -``` - -#### `getState(): ConnectionState` -Get current connection state: `'connecting'`, `'connected'`, `'disconnecting'`, `'disconnected'`, or `'reconnecting'`. - -```typescript -const state = client.getState(); -console.log('State:', state); -``` - -### CRUD Operations - -#### `read(entity: string, options?): Promise` -Read records from an entity. - -```typescript -// Read all active users -const users = await client.read('users', { - schema: 'public', - filters: [ - { column: 'status', operator: 'eq', value: 'active' } - ], - columns: ['id', 'name', 'email'], - sort: [ - { column: 'name', direction: 'asc' } - ], - limit: 10, - offset: 0 -}); - -// Read single record by ID -const user = await client.read('users', { - schema: 'public', - record_id: '123' -}); - -// Read with preloading -const posts = await client.read('posts', { - schema: 'public', - preload: [ - { - relation: 'user', - columns: ['id', 'name', 'email'] - }, - { - relation: 'comments', - filters: [ - { column: 'status', operator: 'eq', value: 'approved' } - ] - } - ] -}); -``` - -#### `create(entity: string, data: any, options?): Promise` -Create a new record. - -```typescript -const newUser = await client.create('users', { - name: 'John Doe', - email: 'john@example.com', - status: 'active' -}, { - schema: 'public' -}); -``` - -#### `update(entity: string, id: string, data: any, options?): Promise` -Update an existing record. - -```typescript -const updatedUser = await client.update('users', '123', { - name: 'John Updated', - email: 'john.new@example.com' -}, { - schema: 'public' -}); -``` - -#### `delete(entity: string, id: string, options?): Promise` -Delete a record. - -```typescript -await client.delete('users', '123', { - schema: 'public' -}); -``` - -#### `meta(entity: string, options?): Promise` -Get metadata for an entity. - -```typescript -const metadata = await client.meta('users', { - schema: 'public' -}); -console.log('Columns:', metadata.columns); -console.log('Primary key:', metadata.primary_key); -``` - -### Subscriptions - -#### `subscribe(entity: string, callback: Function, options?): Promise` -Subscribe to entity changes. - -```typescript -const subscriptionId = await client.subscribe( - 'users', - (notification) => { - console.log('Operation:', notification.operation); // 'create', 'update', or 'delete' - console.log('Data:', notification.data); - console.log('Timestamp:', notification.timestamp); - }, - { - schema: 'public', - filters: [ - { column: 'status', operator: 'eq', value: 'active' } - ] - } -); -``` - -#### `unsubscribe(subscriptionId: string): Promise` -Unsubscribe from entity changes. - -```typescript -await client.unsubscribe(subscriptionId); -``` - -#### `getSubscriptions(): Subscription[]` -Get list of active subscriptions. - -```typescript -const subscriptions = client.getSubscriptions(); -console.log('Active subscriptions:', subscriptions.length); -``` - -### Event Handling - -#### `on(event: string, callback: Function): void` -Add event listener. - -```typescript -// Connection events -client.on('connect', () => { - console.log('Connected!'); -}); - -client.on('disconnect', (event) => { - console.log('Disconnected:', event.code, event.reason); -}); - -client.on('error', (error) => { - console.error('Error:', error); -}); - -// State changes -client.on('stateChange', (state) => { - console.log('State:', state); -}); - -// All messages -client.on('message', (message) => { - console.log('Message:', message); -}); -``` - -#### `off(event: string): void` -Remove event listener. - -```typescript -client.off('connect'); -``` - -## Filter Operators - -- `eq` - Equal (=) -- `neq` - Not Equal (!=) -- `gt` - Greater Than (>) -- `gte` - Greater Than or Equal (>=) -- `lt` - Less Than (<) -- `lte` - Less Than or Equal (<=) -- `like` - LIKE (case-sensitive) -- `ilike` - ILIKE (case-insensitive) -- `in` - IN (array of values) - -## Examples - -### Basic CRUD - -```typescript -const client = new WebSocketClient({ url: 'ws://localhost:8080/ws' }); -await client.connect(); - -// Create -const user = await client.create('users', { - name: 'Alice', - email: 'alice@example.com' -}); - -// Read -const users = await client.read('users', { - filters: [{ column: 'status', operator: 'eq', value: 'active' }] -}); - -// Update -await client.update('users', user.id, { name: 'Alice Updated' }); - -// Delete -await client.delete('users', user.id); - -client.disconnect(); -``` - -### Real-Time Subscriptions - -```typescript -const client = new WebSocketClient({ url: 'ws://localhost:8080/ws' }); -await client.connect(); - -// Subscribe to all user changes -const subId = await client.subscribe('users', (notification) => { - switch (notification.operation) { - case 'create': - console.log('New user:', notification.data); - break; - case 'update': - console.log('User updated:', notification.data); - break; - case 'delete': - console.log('User deleted:', notification.data); - break; - } -}); - -// Later: unsubscribe -await client.unsubscribe(subId); -``` - -### React Integration - -```typescript -import { useEffect, useState } from 'react'; -import { WebSocketClient } from '@warkypublic/resolvespec-js'; - -function useWebSocket(url: string) { - const [client] = useState(() => new WebSocketClient({ url })); - const [isConnected, setIsConnected] = useState(false); - - useEffect(() => { - client.on('connect', () => setIsConnected(true)); - client.on('disconnect', () => setIsConnected(false)); - client.connect(); - - return () => client.disconnect(); - }, [client]); - - return { client, isConnected }; -} - -function UsersComponent() { - const { client, isConnected } = useWebSocket('ws://localhost:8080/ws'); - const [users, setUsers] = useState([]); - - useEffect(() => { - if (!isConnected) return; - - const loadUsers = async () => { - // Subscribe to changes - await client.subscribe('users', (notification) => { - if (notification.operation === 'create') { - setUsers(prev => [...prev, notification.data]); - } else if (notification.operation === 'update') { - setUsers(prev => prev.map(u => - u.id === notification.data.id ? notification.data : u - )); - } else if (notification.operation === 'delete') { - setUsers(prev => prev.filter(u => u.id !== notification.data.id)); - } - }); - - // Load initial data - const data = await client.read('users'); - setUsers(data); - }; - - loadUsers(); - }, [client, isConnected]); - - return ( -
-

Users {isConnected ? '🟢' : '🔴'}

- {users.map(user => ( -
{user.name}
- ))} -
- ); -} -``` - -### TypeScript with Typed Models - -```typescript -interface User { - id: number; - name: string; - email: string; - status: 'active' | 'inactive'; -} - -interface Post { - id: number; - title: string; - content: string; - user_id: number; - user?: User; -} - -const client = new WebSocketClient({ url: 'ws://localhost:8080/ws' }); -await client.connect(); - -// Type-safe operations -const users = await client.read('users', { - filters: [{ column: 'status', operator: 'eq', value: 'active' }] -}); - -const newUser = await client.create('users', { - name: 'Bob', - email: 'bob@example.com', - status: 'active' -}); - -// Type-safe subscriptions -await client.subscribe( - 'posts', - (notification) => { - const post = notification.data as Post; - console.log('Post:', post.title); - } -); -``` - -### Error Handling - -```typescript -const client = new WebSocketClient({ - url: 'ws://localhost:8080/ws', - reconnect: true, - maxReconnectAttempts: 5 -}); - -client.on('error', (error) => { - console.error('Connection error:', error); -}); - -client.on('stateChange', (state) => { - console.log('State:', state); - if (state === 'reconnecting') { - console.log('Attempting to reconnect...'); - } -}); - -try { - await client.connect(); - - try { - const user = await client.read('users', { record_id: '999' }); - } catch (error) { - console.error('Record not found:', error); - } - - try { - await client.create('users', { /* invalid data */ }); - } catch (error) { - console.error('Validation failed:', error); - } - -} catch (error) { - console.error('Connection failed:', error); -} -``` - -### Multiple Subscriptions - -```typescript -const client = new WebSocketClient({ url: 'ws://localhost:8080/ws' }); -await client.connect(); - -// Subscribe to multiple entities -const userSub = await client.subscribe('users', (n) => { - console.log('[Users]', n.operation, n.data); -}); - -const postSub = await client.subscribe('posts', (n) => { - console.log('[Posts]', n.operation, n.data); -}, { - filters: [{ column: 'status', operator: 'eq', value: 'published' }] -}); - -const commentSub = await client.subscribe('comments', (n) => { - console.log('[Comments]', n.operation, n.data); -}); - -// Check active subscriptions -console.log('Active:', client.getSubscriptions().length); - -// Clean up -await client.unsubscribe(userSub); -await client.unsubscribe(postSub); -await client.unsubscribe(commentSub); -``` - -## Best Practices - -1. **Always Clean Up**: Call `disconnect()` when done to close the connection properly -2. **Use TypeScript**: Leverage type definitions for better type safety -3. **Handle Errors**: Always wrap operations in try-catch blocks -4. **Limit Subscriptions**: Don't create too many subscriptions per connection -5. **Use Filters**: Apply filters to subscriptions to reduce unnecessary notifications -6. **Connection State**: Check `isConnected()` before operations -7. **Event Listeners**: Remove event listeners when no longer needed with `off()` -8. **Reconnection**: Enable auto-reconnection for production apps - -## Browser Support - -- Chrome/Edge 88+ -- Firefox 85+ -- Safari 14+ -- Node.js 14.16+ - -## License - -MIT diff --git a/resolvespec-js/dist/index.cjs b/resolvespec-js/dist/index.cjs new file mode 100644 index 0000000..7f63fa7 --- /dev/null +++ b/resolvespec-js/dist/index.cjs @@ -0,0 +1 @@ +"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l=require("uuid"),d=new Map;function w(n){const e=n.baseUrl;let t=d.get(e);return t||(t=new g(n),d.set(e,t)),t}class g{constructor(e){this.config=e}buildUrl(e,t,s){let r=`${this.config.baseUrl}/${e}/${t}`;return s&&(r+=`/${s}`),r}baseHeaders(){const e={"Content-Type":"application/json"};return this.config.token&&(e.Authorization=`Bearer ${this.config.token}`),e}async fetchWithError(e,t){const s=await fetch(e,t),r=await s.json();if(!s.ok)throw new Error(r.error?.message||"An error occurred");return r}async getMetadata(e,t){const s=this.buildUrl(e,t);return this.fetchWithError(s,{method:"GET",headers:this.baseHeaders()})}async read(e,t,s,r){const i=typeof s=="number"||typeof s=="string"?String(s):void 0,a=this.buildUrl(e,t,i),c={operation:"read",id:Array.isArray(s)?s:void 0,options:r};return this.fetchWithError(a,{method:"POST",headers:this.baseHeaders(),body:JSON.stringify(c)})}async create(e,t,s,r){const i=this.buildUrl(e,t),a={operation:"create",data:s,options:r};return this.fetchWithError(i,{method:"POST",headers:this.baseHeaders(),body:JSON.stringify(a)})}async update(e,t,s,r,i){const a=typeof r=="number"||typeof r=="string"?String(r):void 0,c=this.buildUrl(e,t,a),o={operation:"update",id:Array.isArray(r)?r:void 0,data:s,options:i};return this.fetchWithError(c,{method:"POST",headers:this.baseHeaders(),body:JSON.stringify(o)})}async delete(e,t,s){const r=this.buildUrl(e,t,String(s)),i={operation:"delete"};return this.fetchWithError(r,{method:"POST",headers:this.baseHeaders(),body:JSON.stringify(i)})}}const f=new Map;function H(n){const e=n.url;let t=f.get(e);return t||(t=new S(n),f.set(e,t)),t}class S{constructor(e){this.ws=null,this.messageHandlers=new Map,this.subscriptions=new Map,this.eventListeners={},this.state="disconnected",this.reconnectAttempts=0,this.reconnectTimer=null,this.heartbeatTimer=null,this.isManualClose=!1,this.config={url:e.url,reconnect:e.reconnect??!0,reconnectInterval:e.reconnectInterval??3e3,maxReconnectAttempts:e.maxReconnectAttempts??10,heartbeatInterval:e.heartbeatInterval??3e4,debug:e.debug??!1}}async connect(){if(this.ws?.readyState===WebSocket.OPEN){this.log("Already connected");return}return this.isManualClose=!1,this.setState("connecting"),new Promise((e,t)=>{try{this.ws=new WebSocket(this.config.url),this.ws.onopen=()=>{this.log("Connected to WebSocket server"),this.setState("connected"),this.reconnectAttempts=0,this.startHeartbeat(),this.emit("connect"),e()},this.ws.onmessage=s=>{this.handleMessage(s.data)},this.ws.onerror=s=>{this.log("WebSocket error:",s);const r=new Error("WebSocket connection error");this.emit("error",r),t(r)},this.ws.onclose=s=>{this.log("WebSocket closed:",s.code,s.reason),this.stopHeartbeat(),this.setState("disconnected"),this.emit("disconnect",s),this.config.reconnect&&!this.isManualClose&&this.reconnectAttempts{this.connect().catch(r=>{this.log("Reconnection failed:",r)})},this.config.reconnectInterval))}}catch(s){t(s)}})}disconnect(){this.isManualClose=!0,this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.stopHeartbeat(),this.ws&&(this.setState("disconnecting"),this.ws.close(),this.ws=null),this.setState("disconnected"),this.messageHandlers.clear()}async request(e,t,s){this.ensureConnected();const r=l.v4(),i={id:r,type:"request",operation:e,entity:t,schema:s?.schema,record_id:s?.record_id,data:s?.data,options:s?.options};return new Promise((a,c)=>{this.messageHandlers.set(r,o=>{o.success?a(o.data):c(new Error(o.error?.message||"Request failed"))}),this.send(i),setTimeout(()=>{this.messageHandlers.has(r)&&(this.messageHandlers.delete(r),c(new Error("Request timeout")))},3e4)})}async read(e,t){return this.request("read",e,{schema:t?.schema,record_id:t?.record_id,options:{filters:t?.filters,columns:t?.columns,sort:t?.sort,preload:t?.preload,limit:t?.limit,offset:t?.offset}})}async create(e,t,s){return this.request("create",e,{schema:s?.schema,data:t})}async update(e,t,s,r){return this.request("update",e,{schema:r?.schema,record_id:t,data:s})}async delete(e,t,s){await this.request("delete",e,{schema:s?.schema,record_id:t})}async meta(e,t){return this.request("meta",e,{schema:t?.schema})}async subscribe(e,t,s){this.ensureConnected();const r=l.v4(),i={id:r,type:"subscription",operation:"subscribe",entity:e,schema:s?.schema,options:{filters:s?.filters}};return new Promise((a,c)=>{this.messageHandlers.set(r,o=>{if(o.success&&o.data?.subscription_id){const h=o.data.subscription_id;this.subscriptions.set(h,{id:h,entity:e,schema:s?.schema,options:{filters:s?.filters},callback:t}),this.log(`Subscribed to ${e} with ID: ${h}`),a(h)}else c(new Error(o.error?.message||"Subscription failed"))}),this.send(i),setTimeout(()=>{this.messageHandlers.has(r)&&(this.messageHandlers.delete(r),c(new Error("Subscription timeout")))},1e4)})}async unsubscribe(e){this.ensureConnected();const t=l.v4(),s={id:t,type:"subscription",operation:"unsubscribe",subscription_id:e};return new Promise((r,i)=>{this.messageHandlers.set(t,a=>{a.success?(this.subscriptions.delete(e),this.log(`Unsubscribed from ${e}`),r()):i(new Error(a.error?.message||"Unsubscribe failed"))}),this.send(s),setTimeout(()=>{this.messageHandlers.has(t)&&(this.messageHandlers.delete(t),i(new Error("Unsubscribe timeout")))},1e4)})}getSubscriptions(){return Array.from(this.subscriptions.values())}getState(){return this.state}isConnected(){return this.ws?.readyState===WebSocket.OPEN}on(e,t){this.eventListeners[e]=t}off(e){delete this.eventListeners[e]}handleMessage(e){try{const t=JSON.parse(e);switch(this.log("Received message:",t),this.emit("message",t),t.type){case"response":this.handleResponse(t);break;case"notification":this.handleNotification(t);break;case"pong":break;default:this.log("Unknown message type:",t.type)}}catch(t){this.log("Error parsing message:",t)}}handleResponse(e){const t=this.messageHandlers.get(e.id);t&&(t(e),this.messageHandlers.delete(e.id))}handleNotification(e){const t=this.subscriptions.get(e.subscription_id);t?.callback&&t.callback(e)}send(e){if(!this.ws||this.ws.readyState!==WebSocket.OPEN)throw new Error("WebSocket is not connected");const t=JSON.stringify(e);this.log("Sending message:",e),this.ws.send(t)}startHeartbeat(){this.heartbeatTimer||(this.heartbeatTimer=setInterval(()=>{if(this.isConnected()){const e={id:l.v4(),type:"ping"};this.send(e)}},this.config.heartbeatInterval))}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}setState(e){this.state!==e&&(this.state=e,this.emit("stateChange",e))}ensureConnected(){if(!this.isConnected())throw new Error("WebSocket is not connected. Call connect() first.")}emit(e,...t){const s=this.eventListeners[e];s&&s(...t)}log(...e){this.config.debug&&console.log("[WebSocketClient]",...e)}}function C(n){return typeof btoa=="function"?"ZIP_"+btoa(n):"ZIP_"+Buffer.from(n,"utf-8").toString("base64")}function y(n){let e=n;return e.startsWith("ZIP_")?(e=e.slice(4).replace(/[\n\r ]/g,""),e=m(e)):e.startsWith("__")&&(e=e.slice(2).replace(/[\n\r ]/g,""),e=m(e)),(e.startsWith("ZIP_")||e.startsWith("__"))&&(e=y(e)),e}function m(n){return typeof atob=="function"?atob(n):Buffer.from(n,"base64").toString("utf-8")}function u(n){const e={};if(n.columns?.length&&(e["X-Select-Fields"]=n.columns.join(",")),n.omit_columns?.length&&(e["X-Not-Select-Fields"]=n.omit_columns.join(",")),n.filters?.length)for(const t of n.filters){const s=t.logic_operator??"AND",r=v(t.operator),i=k(t);t.operator==="eq"&&s==="AND"?e[`X-FieldFilter-${t.column}`]=i:s==="OR"?e[`X-SearchOr-${r}-${t.column}`]=i:e[`X-SearchOp-${r}-${t.column}`]=i}if(n.sort?.length){const t=n.sort.map(s=>s.direction.toUpperCase()==="DESC"?`-${s.column}`:`+${s.column}`);e["X-Sort"]=t.join(",")}if(n.limit!==void 0&&(e["X-Limit"]=String(n.limit)),n.offset!==void 0&&(e["X-Offset"]=String(n.offset)),n.cursor_forward&&(e["X-Cursor-Forward"]=n.cursor_forward),n.cursor_backward&&(e["X-Cursor-Backward"]=n.cursor_backward),n.preload?.length){const t=n.preload.map(s=>s.columns?.length?`${s.relation}:${s.columns.join(",")}`:s.relation);e["X-Preload"]=t.join("|")}if(n.fetch_row_number&&(e["X-Fetch-RowNumber"]=n.fetch_row_number),n.computedColumns?.length)for(const t of n.computedColumns)e[`X-CQL-SEL-${t.name}`]=t.expression;if(n.customOperators?.length){const t=n.customOperators.map(s=>s.sql);e["X-Custom-SQL-W"]=t.join(" AND ")}return e}function v(n){switch(n){case"eq":return"equals";case"neq":return"notequals";case"gt":return"greaterthan";case"gte":return"greaterthanorequal";case"lt":return"lessthan";case"lte":return"lessthanorequal";case"like":case"ilike":case"contains":return"contains";case"startswith":return"beginswith";case"endswith":return"endswith";case"in":return"in";case"between":return"between";case"between_inclusive":return"betweeninclusive";case"is_null":return"empty";case"is_not_null":return"notempty";default:return n}}function k(n){return n.value===null||n.value===void 0?"":Array.isArray(n.value)?n.value.join(","):String(n.value)}const b=new Map;function E(n){const e=n.baseUrl;let t=b.get(e);return t||(t=new p(n),b.set(e,t)),t}class p{constructor(e){this.config=e}buildUrl(e,t,s){let r=`${this.config.baseUrl}/${e}/${t}`;return s&&(r+=`/${s}`),r}baseHeaders(){const e={"Content-Type":"application/json"};return this.config.token&&(e.Authorization=`Bearer ${this.config.token}`),e}async fetchWithError(e,t){const s=await fetch(e,t),r=await s.json();if(!s.ok)throw new Error(r.error?.message||"An error occurred");return r}async read(e,t,s,r){const i=this.buildUrl(e,t,s),a=r?u(r):{};return this.fetchWithError(i,{method:"GET",headers:{...this.baseHeaders(),...a}})}async create(e,t,s,r){const i=this.buildUrl(e,t),a=r?u(r):{};return this.fetchWithError(i,{method:"POST",headers:{...this.baseHeaders(),...a},body:JSON.stringify(s)})}async update(e,t,s,r,i){const a=this.buildUrl(e,t,s),c=i?u(i):{};return this.fetchWithError(a,{method:"PUT",headers:{...this.baseHeaders(),...c},body:JSON.stringify(r)})}async delete(e,t,s){const r=this.buildUrl(e,t,s);return this.fetchWithError(r,{method:"DELETE",headers:this.baseHeaders()})}}exports.HeaderSpecClient=p;exports.ResolveSpecClient=g;exports.WebSocketClient=S;exports.buildHeaders=u;exports.decodeHeaderValue=y;exports.encodeHeaderValue=C;exports.getHeaderSpecClient=E;exports.getResolveSpecClient=w;exports.getWebSocketClient=H; diff --git a/resolvespec-js/dist/index.d.ts b/resolvespec-js/dist/index.d.ts new file mode 100644 index 0000000..52dc330 --- /dev/null +++ b/resolvespec-js/dist/index.d.ts @@ -0,0 +1,366 @@ +export declare interface APIError { + code: string; + message: string; + details?: any; + detail?: string; +} + +export declare interface APIResponse { + success: boolean; + data: T; + metadata?: Metadata; + error?: APIError; +} + +/** + * Build HTTP headers from Options, matching Go's restheadspec handler conventions. + * + * Header mapping: + * - X-Select-Fields: comma-separated columns + * - X-Not-Select-Fields: comma-separated omit_columns + * - X-FieldFilter-{col}: exact match (eq) + * - X-SearchOp-{operator}-{col}: AND filter + * - X-SearchOr-{operator}-{col}: OR filter + * - X-Sort: +col (asc), -col (desc) + * - X-Limit, X-Offset: pagination + * - X-Cursor-Forward, X-Cursor-Backward: cursor pagination + * - X-Preload: RelationName:field1,field2 pipe-separated + * - X-Fetch-RowNumber: row number fetch + * - X-CQL-SEL-{col}: computed columns + * - X-Custom-SQL-W: custom operators (AND) + */ +export declare function buildHeaders(options: Options): Record; + +export declare interface ClientConfig { + baseUrl: string; + token?: string; +} + +export declare interface Column { + name: string; + type: string; + is_nullable: boolean; + is_primary: boolean; + is_unique: boolean; + has_index: boolean; +} + +export declare interface ComputedColumn { + name: string; + expression: string; +} + +export declare type ConnectionState = 'connecting' | 'connected' | 'disconnecting' | 'disconnected' | 'reconnecting'; + +export declare interface CustomOperator { + name: string; + sql: string; +} + +/** + * Decode a header value that may be base64 encoded with ZIP_ or __ prefix. + */ +export declare function decodeHeaderValue(value: string): string; + +/** + * Encode a value with base64 and ZIP_ prefix for complex header values. + */ +export declare function encodeHeaderValue(value: string): string; + +export declare interface FilterOption { + column: string; + operator: Operator | string; + value: any; + logic_operator?: 'AND' | 'OR'; +} + +export declare function getHeaderSpecClient(config: ClientConfig): HeaderSpecClient; + +export declare function getResolveSpecClient(config: ClientConfig): ResolveSpecClient; + +export declare function getWebSocketClient(config: WebSocketClientConfig): WebSocketClient; + +/** + * HeaderSpec REST client. + * Sends query options via HTTP headers instead of request body, matching the Go restheadspec handler. + * + * HTTP methods: GET=read, POST=create, PUT=update, DELETE=delete + */ +export declare class HeaderSpecClient { + private config; + constructor(config: ClientConfig); + private buildUrl; + private baseHeaders; + private fetchWithError; + read(schema: string, entity: string, id?: string, options?: Options): Promise>; + create(schema: string, entity: string, data: any, options?: Options): Promise>; + update(schema: string, entity: string, id: string, data: any, options?: Options): Promise>; + delete(schema: string, entity: string, id: string): Promise>; +} + +export declare type MessageType = 'request' | 'response' | 'notification' | 'subscription' | 'error' | 'ping' | 'pong'; + +export declare interface Metadata { + total: number; + count: number; + filtered: number; + limit: number; + offset: number; + row_number?: number; +} + +export declare type Operation = 'read' | 'create' | 'update' | 'delete'; + +export declare type Operator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ilike' | 'in' | 'contains' | 'startswith' | 'endswith' | 'between' | 'between_inclusive' | 'is_null' | 'is_not_null'; + +export declare interface Options { + preload?: PreloadOption[]; + columns?: string[]; + omit_columns?: string[]; + filters?: FilterOption[]; + sort?: SortOption[]; + limit?: number; + offset?: number; + customOperators?: CustomOperator[]; + computedColumns?: ComputedColumn[]; + parameters?: Parameter[]; + cursor_forward?: string; + cursor_backward?: string; + fetch_row_number?: string; +} + +export declare interface Parameter { + name: string; + value: string; + sequence?: number; +} + +export declare interface PreloadOption { + relation: string; + table_name?: string; + columns?: string[]; + omit_columns?: string[]; + sort?: SortOption[]; + filters?: FilterOption[]; + where?: string; + limit?: number; + offset?: number; + updatable?: boolean; + computed_ql?: Record; + recursive?: boolean; + primary_key?: string; + related_key?: string; + foreign_key?: string; + recursive_child_key?: string; + sql_joins?: string[]; + join_aliases?: string[]; +} + +export declare interface RequestBody { + operation: Operation; + id?: number | string | string[]; + data?: any | any[]; + options?: Options; +} + +export declare class ResolveSpecClient { + private config; + constructor(config: ClientConfig); + private buildUrl; + private baseHeaders; + private fetchWithError; + getMetadata(schema: string, entity: string): Promise>; + read(schema: string, entity: string, id?: number | string | string[], options?: Options): Promise>; + create(schema: string, entity: string, data: any | any[], options?: Options): Promise>; + update(schema: string, entity: string, data: any | any[], id?: number | string | string[], options?: Options): Promise>; + delete(schema: string, entity: string, id: number | string): Promise>; +} + +export declare type SortDirection = 'asc' | 'desc' | 'ASC' | 'DESC'; + +export declare interface SortOption { + column: string; + direction: SortDirection; +} + +export declare interface Subscription { + id: string; + entity: string; + schema?: string; + options?: WSOptions; + callback?: (notification: WSNotificationMessage) => void; +} + +export declare interface SubscriptionOptions { + filters?: FilterOption[]; + onNotification?: (notification: WSNotificationMessage) => void; +} + +export declare interface TableMetadata { + schema: string; + table: string; + columns: Column[]; + relations: string[]; +} + +export declare class WebSocketClient { + private ws; + private config; + private messageHandlers; + private subscriptions; + private eventListeners; + private state; + private reconnectAttempts; + private reconnectTimer; + private heartbeatTimer; + private isManualClose; + constructor(config: WebSocketClientConfig); + connect(): Promise; + disconnect(): void; + request(operation: WSOperation, entity: string, options?: { + schema?: string; + record_id?: string; + data?: any; + options?: WSOptions; + }): Promise; + read(entity: string, options?: { + schema?: string; + record_id?: string; + filters?: FilterOption[]; + columns?: string[]; + sort?: SortOption[]; + preload?: PreloadOption[]; + limit?: number; + offset?: number; + }): Promise; + create(entity: string, data: any, options?: { + schema?: string; + }): Promise; + update(entity: string, id: string, data: any, options?: { + schema?: string; + }): Promise; + delete(entity: string, id: string, options?: { + schema?: string; + }): Promise; + meta(entity: string, options?: { + schema?: string; + }): Promise; + subscribe(entity: string, callback: (notification: WSNotificationMessage) => void, options?: { + schema?: string; + filters?: FilterOption[]; + }): Promise; + unsubscribe(subscriptionId: string): Promise; + getSubscriptions(): Subscription[]; + getState(): ConnectionState; + isConnected(): boolean; + on(event: K, callback: WebSocketClientEvents[K]): void; + off(event: K): void; + private handleMessage; + private handleResponse; + private handleNotification; + private send; + private startHeartbeat; + private stopHeartbeat; + private setState; + private ensureConnected; + private emit; + private log; +} + +export declare interface WebSocketClientConfig { + url: string; + reconnect?: boolean; + reconnectInterval?: number; + maxReconnectAttempts?: number; + heartbeatInterval?: number; + debug?: boolean; +} + +export declare interface WebSocketClientEvents { + connect: () => void; + disconnect: (event: CloseEvent) => void; + error: (error: Error) => void; + message: (message: WSMessage) => void; + stateChange: (state: ConnectionState) => void; +} + +export declare interface WSErrorInfo { + code: string; + message: string; + details?: Record; +} + +export declare interface WSMessage { + id?: string; + type: MessageType; + operation?: WSOperation; + schema?: string; + entity?: string; + record_id?: string; + data?: any; + options?: WSOptions; + subscription_id?: string; + success?: boolean; + error?: WSErrorInfo; + metadata?: Record; + timestamp?: string; +} + +export declare interface WSNotificationMessage { + type: 'notification'; + operation: WSOperation; + subscription_id: string; + schema?: string; + entity: string; + data: any; + timestamp: string; +} + +export declare type WSOperation = 'read' | 'create' | 'update' | 'delete' | 'subscribe' | 'unsubscribe' | 'meta'; + +export declare interface WSOptions { + filters?: FilterOption[]; + columns?: string[]; + omit_columns?: string[]; + preload?: PreloadOption[]; + sort?: SortOption[]; + limit?: number; + offset?: number; + parameters?: Parameter[]; + cursor_forward?: string; + cursor_backward?: string; + fetch_row_number?: string; +} + +export declare interface WSRequestMessage { + id: string; + type: 'request'; + operation: WSOperation; + schema?: string; + entity: string; + record_id?: string; + data?: any; + options?: WSOptions; +} + +export declare interface WSResponseMessage { + id: string; + type: 'response'; + success: boolean; + data?: any; + error?: WSErrorInfo; + metadata?: Record; + timestamp: string; +} + +export declare interface WSSubscriptionMessage { + id: string; + type: 'subscription'; + operation: 'subscribe' | 'unsubscribe'; + schema?: string; + entity: string; + options?: WSOptions; + subscription_id?: string; +} + +export { } diff --git a/resolvespec-js/dist/index.js b/resolvespec-js/dist/index.js new file mode 100644 index 0000000..20d1c57 --- /dev/null +++ b/resolvespec-js/dist/index.js @@ -0,0 +1,452 @@ +import { v4 as l } from "uuid"; +const d = /* @__PURE__ */ new Map(); +function E(n) { + const e = n.baseUrl; + let t = d.get(e); + return t || (t = new g(n), d.set(e, t)), t; +} +class g { + constructor(e) { + this.config = e; + } + buildUrl(e, t, s) { + let r = `${this.config.baseUrl}/${e}/${t}`; + return s && (r += `/${s}`), r; + } + baseHeaders() { + const e = { + "Content-Type": "application/json" + }; + return this.config.token && (e.Authorization = `Bearer ${this.config.token}`), e; + } + async fetchWithError(e, t) { + const s = await fetch(e, t), r = await s.json(); + if (!s.ok) + throw new Error(r.error?.message || "An error occurred"); + return r; + } + async getMetadata(e, t) { + const s = this.buildUrl(e, t); + return this.fetchWithError(s, { + method: "GET", + headers: this.baseHeaders() + }); + } + async read(e, t, s, r) { + const i = typeof s == "number" || typeof s == "string" ? String(s) : void 0, a = this.buildUrl(e, t, i), c = { + operation: "read", + id: Array.isArray(s) ? s : void 0, + options: r + }; + return this.fetchWithError(a, { + method: "POST", + headers: this.baseHeaders(), + body: JSON.stringify(c) + }); + } + async create(e, t, s, r) { + const i = this.buildUrl(e, t), a = { + operation: "create", + data: s, + options: r + }; + return this.fetchWithError(i, { + method: "POST", + headers: this.baseHeaders(), + body: JSON.stringify(a) + }); + } + async update(e, t, s, r, i) { + const a = typeof r == "number" || typeof r == "string" ? String(r) : void 0, c = this.buildUrl(e, t, a), o = { + operation: "update", + id: Array.isArray(r) ? r : void 0, + data: s, + options: i + }; + return this.fetchWithError(c, { + method: "POST", + headers: this.baseHeaders(), + body: JSON.stringify(o) + }); + } + async delete(e, t, s) { + const r = this.buildUrl(e, t, String(s)), i = { + operation: "delete" + }; + return this.fetchWithError(r, { + method: "POST", + headers: this.baseHeaders(), + body: JSON.stringify(i) + }); + } +} +const f = /* @__PURE__ */ new Map(); +function _(n) { + const e = n.url; + let t = f.get(e); + return t || (t = new w(n), f.set(e, t)), t; +} +class w { + constructor(e) { + this.ws = null, this.messageHandlers = /* @__PURE__ */ new Map(), this.subscriptions = /* @__PURE__ */ new Map(), this.eventListeners = {}, this.state = "disconnected", this.reconnectAttempts = 0, this.reconnectTimer = null, this.heartbeatTimer = null, this.isManualClose = !1, this.config = { + url: e.url, + reconnect: e.reconnect ?? !0, + reconnectInterval: e.reconnectInterval ?? 3e3, + maxReconnectAttempts: e.maxReconnectAttempts ?? 10, + heartbeatInterval: e.heartbeatInterval ?? 3e4, + debug: e.debug ?? !1 + }; + } + async connect() { + if (this.ws?.readyState === WebSocket.OPEN) { + this.log("Already connected"); + return; + } + return this.isManualClose = !1, this.setState("connecting"), new Promise((e, t) => { + try { + this.ws = new WebSocket(this.config.url), this.ws.onopen = () => { + this.log("Connected to WebSocket server"), this.setState("connected"), this.reconnectAttempts = 0, this.startHeartbeat(), this.emit("connect"), e(); + }, this.ws.onmessage = (s) => { + this.handleMessage(s.data); + }, this.ws.onerror = (s) => { + this.log("WebSocket error:", s); + const r = new Error("WebSocket connection error"); + this.emit("error", r), t(r); + }, this.ws.onclose = (s) => { + this.log("WebSocket closed:", s.code, s.reason), this.stopHeartbeat(), this.setState("disconnected"), this.emit("disconnect", s), this.config.reconnect && !this.isManualClose && this.reconnectAttempts < this.config.maxReconnectAttempts && (this.reconnectAttempts++, this.log(`Reconnection attempt ${this.reconnectAttempts}/${this.config.maxReconnectAttempts}`), this.setState("reconnecting"), this.reconnectTimer = setTimeout(() => { + this.connect().catch((r) => { + this.log("Reconnection failed:", r); + }); + }, this.config.reconnectInterval)); + }; + } catch (s) { + t(s); + } + }); + } + disconnect() { + this.isManualClose = !0, this.reconnectTimer && (clearTimeout(this.reconnectTimer), this.reconnectTimer = null), this.stopHeartbeat(), this.ws && (this.setState("disconnecting"), this.ws.close(), this.ws = null), this.setState("disconnected"), this.messageHandlers.clear(); + } + async request(e, t, s) { + this.ensureConnected(); + const r = l(), i = { + id: r, + type: "request", + operation: e, + entity: t, + schema: s?.schema, + record_id: s?.record_id, + data: s?.data, + options: s?.options + }; + return new Promise((a, c) => { + this.messageHandlers.set(r, (o) => { + o.success ? a(o.data) : c(new Error(o.error?.message || "Request failed")); + }), this.send(i), setTimeout(() => { + this.messageHandlers.has(r) && (this.messageHandlers.delete(r), c(new Error("Request timeout"))); + }, 3e4); + }); + } + async read(e, t) { + return this.request("read", e, { + schema: t?.schema, + record_id: t?.record_id, + options: { + filters: t?.filters, + columns: t?.columns, + sort: t?.sort, + preload: t?.preload, + limit: t?.limit, + offset: t?.offset + } + }); + } + async create(e, t, s) { + return this.request("create", e, { + schema: s?.schema, + data: t + }); + } + async update(e, t, s, r) { + return this.request("update", e, { + schema: r?.schema, + record_id: t, + data: s + }); + } + async delete(e, t, s) { + await this.request("delete", e, { + schema: s?.schema, + record_id: t + }); + } + async meta(e, t) { + return this.request("meta", e, { + schema: t?.schema + }); + } + async subscribe(e, t, s) { + this.ensureConnected(); + const r = l(), i = { + id: r, + type: "subscription", + operation: "subscribe", + entity: e, + schema: s?.schema, + options: { + filters: s?.filters + } + }; + return new Promise((a, c) => { + this.messageHandlers.set(r, (o) => { + if (o.success && o.data?.subscription_id) { + const h = o.data.subscription_id; + this.subscriptions.set(h, { + id: h, + entity: e, + schema: s?.schema, + options: { filters: s?.filters }, + callback: t + }), this.log(`Subscribed to ${e} with ID: ${h}`), a(h); + } else + c(new Error(o.error?.message || "Subscription failed")); + }), this.send(i), setTimeout(() => { + this.messageHandlers.has(r) && (this.messageHandlers.delete(r), c(new Error("Subscription timeout"))); + }, 1e4); + }); + } + async unsubscribe(e) { + this.ensureConnected(); + const t = l(), s = { + id: t, + type: "subscription", + operation: "unsubscribe", + subscription_id: e + }; + return new Promise((r, i) => { + this.messageHandlers.set(t, (a) => { + a.success ? (this.subscriptions.delete(e), this.log(`Unsubscribed from ${e}`), r()) : i(new Error(a.error?.message || "Unsubscribe failed")); + }), this.send(s), setTimeout(() => { + this.messageHandlers.has(t) && (this.messageHandlers.delete(t), i(new Error("Unsubscribe timeout"))); + }, 1e4); + }); + } + getSubscriptions() { + return Array.from(this.subscriptions.values()); + } + getState() { + return this.state; + } + isConnected() { + return this.ws?.readyState === WebSocket.OPEN; + } + on(e, t) { + this.eventListeners[e] = t; + } + off(e) { + delete this.eventListeners[e]; + } + // Private methods + handleMessage(e) { + try { + const t = JSON.parse(e); + switch (this.log("Received message:", t), this.emit("message", t), t.type) { + case "response": + this.handleResponse(t); + break; + case "notification": + this.handleNotification(t); + break; + case "pong": + break; + default: + this.log("Unknown message type:", t.type); + } + } catch (t) { + this.log("Error parsing message:", t); + } + } + handleResponse(e) { + const t = this.messageHandlers.get(e.id); + t && (t(e), this.messageHandlers.delete(e.id)); + } + handleNotification(e) { + const t = this.subscriptions.get(e.subscription_id); + t?.callback && t.callback(e); + } + send(e) { + if (!this.ws || this.ws.readyState !== WebSocket.OPEN) + throw new Error("WebSocket is not connected"); + const t = JSON.stringify(e); + this.log("Sending message:", e), this.ws.send(t); + } + startHeartbeat() { + this.heartbeatTimer || (this.heartbeatTimer = setInterval(() => { + if (this.isConnected()) { + const e = { + id: l(), + type: "ping" + }; + this.send(e); + } + }, this.config.heartbeatInterval)); + } + stopHeartbeat() { + this.heartbeatTimer && (clearInterval(this.heartbeatTimer), this.heartbeatTimer = null); + } + setState(e) { + this.state !== e && (this.state = e, this.emit("stateChange", e)); + } + ensureConnected() { + if (!this.isConnected()) + throw new Error("WebSocket is not connected. Call connect() first."); + } + emit(e, ...t) { + const s = this.eventListeners[e]; + s && s(...t); + } + log(...e) { + this.config.debug && console.log("[WebSocketClient]", ...e); + } +} +function C(n) { + return typeof btoa == "function" ? "ZIP_" + btoa(n) : "ZIP_" + Buffer.from(n, "utf-8").toString("base64"); +} +function y(n) { + let e = n; + return e.startsWith("ZIP_") ? (e = e.slice(4).replace(/[\n\r ]/g, ""), e = m(e)) : e.startsWith("__") && (e = e.slice(2).replace(/[\n\r ]/g, ""), e = m(e)), (e.startsWith("ZIP_") || e.startsWith("__")) && (e = y(e)), e; +} +function m(n) { + return typeof atob == "function" ? atob(n) : Buffer.from(n, "base64").toString("utf-8"); +} +function u(n) { + const e = {}; + if (n.columns?.length && (e["X-Select-Fields"] = n.columns.join(",")), n.omit_columns?.length && (e["X-Not-Select-Fields"] = n.omit_columns.join(",")), n.filters?.length) + for (const t of n.filters) { + const s = t.logic_operator ?? "AND", r = p(t.operator), i = S(t); + t.operator === "eq" && s === "AND" ? e[`X-FieldFilter-${t.column}`] = i : s === "OR" ? e[`X-SearchOr-${r}-${t.column}`] = i : e[`X-SearchOp-${r}-${t.column}`] = i; + } + if (n.sort?.length) { + const t = n.sort.map((s) => s.direction.toUpperCase() === "DESC" ? `-${s.column}` : `+${s.column}`); + e["X-Sort"] = t.join(","); + } + if (n.limit !== void 0 && (e["X-Limit"] = String(n.limit)), n.offset !== void 0 && (e["X-Offset"] = String(n.offset)), n.cursor_forward && (e["X-Cursor-Forward"] = n.cursor_forward), n.cursor_backward && (e["X-Cursor-Backward"] = n.cursor_backward), n.preload?.length) { + const t = n.preload.map((s) => s.columns?.length ? `${s.relation}:${s.columns.join(",")}` : s.relation); + e["X-Preload"] = t.join("|"); + } + if (n.fetch_row_number && (e["X-Fetch-RowNumber"] = n.fetch_row_number), n.computedColumns?.length) + for (const t of n.computedColumns) + e[`X-CQL-SEL-${t.name}`] = t.expression; + if (n.customOperators?.length) { + const t = n.customOperators.map((s) => s.sql); + e["X-Custom-SQL-W"] = t.join(" AND "); + } + return e; +} +function p(n) { + switch (n) { + case "eq": + return "equals"; + case "neq": + return "notequals"; + case "gt": + return "greaterthan"; + case "gte": + return "greaterthanorequal"; + case "lt": + return "lessthan"; + case "lte": + return "lessthanorequal"; + case "like": + case "ilike": + case "contains": + return "contains"; + case "startswith": + return "beginswith"; + case "endswith": + return "endswith"; + case "in": + return "in"; + case "between": + return "between"; + case "between_inclusive": + return "betweeninclusive"; + case "is_null": + return "empty"; + case "is_not_null": + return "notempty"; + default: + return n; + } +} +function S(n) { + return n.value === null || n.value === void 0 ? "" : Array.isArray(n.value) ? n.value.join(",") : String(n.value); +} +const b = /* @__PURE__ */ new Map(); +function v(n) { + const e = n.baseUrl; + let t = b.get(e); + return t || (t = new H(n), b.set(e, t)), t; +} +class H { + constructor(e) { + this.config = e; + } + buildUrl(e, t, s) { + let r = `${this.config.baseUrl}/${e}/${t}`; + return s && (r += `/${s}`), r; + } + baseHeaders() { + const e = { + "Content-Type": "application/json" + }; + return this.config.token && (e.Authorization = `Bearer ${this.config.token}`), e; + } + async fetchWithError(e, t) { + const s = await fetch(e, t), r = await s.json(); + if (!s.ok) + throw new Error(r.error?.message || "An error occurred"); + return r; + } + async read(e, t, s, r) { + const i = this.buildUrl(e, t, s), a = r ? u(r) : {}; + return this.fetchWithError(i, { + method: "GET", + headers: { ...this.baseHeaders(), ...a } + }); + } + async create(e, t, s, r) { + const i = this.buildUrl(e, t), a = r ? u(r) : {}; + return this.fetchWithError(i, { + method: "POST", + headers: { ...this.baseHeaders(), ...a }, + body: JSON.stringify(s) + }); + } + async update(e, t, s, r, i) { + const a = this.buildUrl(e, t, s), c = i ? u(i) : {}; + return this.fetchWithError(a, { + method: "PUT", + headers: { ...this.baseHeaders(), ...c }, + body: JSON.stringify(r) + }); + } + async delete(e, t, s) { + const r = this.buildUrl(e, t, s); + return this.fetchWithError(r, { + method: "DELETE", + headers: this.baseHeaders() + }); + } +} +export { + H as HeaderSpecClient, + g as ResolveSpecClient, + w as WebSocketClient, + u as buildHeaders, + y as decodeHeaderValue, + C as encodeHeaderValue, + v as getHeaderSpecClient, + E as getResolveSpecClient, + _ as getWebSocketClient +}; diff --git a/resolvespec-js/package.json b/resolvespec-js/package.json index c53c6dc..3d715ad 100644 --- a/resolvespec-js/package.json +++ b/resolvespec-js/package.json @@ -1,20 +1,23 @@ { "name": "@warkypublic/resolvespec-js", "version": "1.0.0", - "description": "Client side library for the ResolveSpec API", + "description": "TypeScript client library for ResolveSpec REST, HeaderSpec, and WebSocket APIs", "type": "module", - "main": "./src/index.ts", - "module": "./src/index.ts", - "types": "./src/index.ts", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, "publishConfig": { - "access": "public", - "main": "./dist/index.js", - "module": "./dist/index.js", - "types": "./dist/index.d.ts" + "access": "public" }, "files": [ "dist", - "bin", "README.md" ], "scripts": { @@ -25,38 +28,33 @@ "lint": "eslint src" }, "keywords": [ - "string", - "blob", - "dependencies", - "workspace", - "package", - "cli", - "tools", - "npm", - "yarn", - "pnpm" + "resolvespec", + "headerspec", + "websocket", + "rest-client", + "typescript", + "api-client" ], "author": "Hein (Warkanum) Puth", "license": "MIT", "dependencies": { - "semver": "^7.6.3", - "uuid": "^11.0.3" + "uuid": "^13.0.0" }, "devDependencies": { - "@changesets/cli": "^2.27.10", - "@eslint/js": "^9.16.0", - "@types/jsdom": "^21.1.7", - "eslint": "^9.16.0", - "globals": "^15.13.0", - "jsdom": "^25.0.1", - "typescript": "^5.7.2", - "typescript-eslint": "^8.17.0", - "vite": "^6.0.2", - "vite-plugin-dts": "^4.3.0", - "vitest": "^2.1.8" + "@changesets/cli": "^2.29.8", + "@eslint/js": "^10.0.1", + "@types/jsdom": "^27.0.0", + "eslint": "^10.0.0", + "globals": "^17.3.0", + "jsdom": "^28.1.0", + "typescript": "^5.9.3", + "typescript-eslint": "^8.55.0", + "vite": "^7.3.1", + "vite-plugin-dts": "^4.5.4", + "vitest": "^4.0.18" }, "engines": { - "node": ">=14.16" + "node": ">=18" }, "repository": { "type": "git", diff --git a/resolvespec-js/pnpm-lock.yaml b/resolvespec-js/pnpm-lock.yaml new file mode 100644 index 0000000..8ba7b53 --- /dev/null +++ b/resolvespec-js/pnpm-lock.yaml @@ -0,0 +1,3376 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + uuid: + specifier: ^13.0.0 + version: 13.0.0 + devDependencies: + '@changesets/cli': + specifier: ^2.29.8 + version: 2.29.8(@types/node@25.2.3) + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@10.0.0) + '@types/jsdom': + specifier: ^27.0.0 + version: 27.0.0 + eslint: + specifier: ^10.0.0 + version: 10.0.0 + globals: + specifier: ^17.3.0 + version: 17.3.0 + jsdom: + specifier: ^28.1.0 + version: 28.1.0 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + typescript-eslint: + specifier: ^8.55.0 + version: 8.55.0(eslint@10.0.0)(typescript@5.9.3) + vite: + specifier: ^7.3.1 + version: 7.3.1(@types/node@25.2.3) + vite-plugin-dts: + specifier: ^4.5.4 + version: 4.5.4(@types/node@25.2.3)(rollup@4.57.1)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)) + vitest: + specifier: ^4.0.18 + version: 4.0.18(@types/node@25.2.3)(jsdom@28.1.0) + +packages: + + '@acemir/cssom@0.9.31': + resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} + + '@asamuzakjp/css-color@4.1.2': + resolution: {integrity: sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==} + + '@asamuzakjp/dom-selector@6.8.1': + resolution: {integrity: sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@changesets/apply-release-plan@7.0.14': + resolution: {integrity: sha512-ddBvf9PHdy2YY0OUiEl3TV78mH9sckndJR14QAt87KLEbIov81XO0q0QAmvooBxXlqRRP8I9B7XOzZwQG7JkWA==} + + '@changesets/assemble-release-plan@6.0.9': + resolution: {integrity: sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ==} + + '@changesets/changelog-git@0.2.1': + resolution: {integrity: sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q==} + + '@changesets/cli@2.29.8': + resolution: {integrity: sha512-1weuGZpP63YWUYjay/E84qqwcnt5yJMM0tep10Up7Q5cS/DGe2IZ0Uj3HNMxGhCINZuR7aO9WBMdKnPit5ZDPA==} + hasBin: true + + '@changesets/config@3.1.2': + resolution: {integrity: sha512-CYiRhA4bWKemdYi/uwImjPxqWNpqGPNbEBdX1BdONALFIDK7MCUj6FPkzD+z9gJcvDFUQJn9aDVf4UG7OT6Kog==} + + '@changesets/errors@0.2.0': + resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} + + '@changesets/get-dependents-graph@2.1.3': + resolution: {integrity: sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ==} + + '@changesets/get-release-plan@4.0.14': + resolution: {integrity: sha512-yjZMHpUHgl4Xl5gRlolVuxDkm4HgSJqT93Ri1Uz8kGrQb+5iJ8dkXJ20M2j/Y4iV5QzS2c5SeTxVSKX+2eMI0g==} + + '@changesets/get-version-range-type@0.4.0': + resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} + + '@changesets/git@3.0.4': + resolution: {integrity: sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw==} + + '@changesets/logger@0.1.1': + resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} + + '@changesets/parse@0.4.2': + resolution: {integrity: sha512-Uo5MC5mfg4OM0jU3up66fmSn6/NE9INK+8/Vn/7sMVcdWg46zfbvvUSjD9EMonVqPi9fbrJH9SXHn48Tr1f2yA==} + + '@changesets/pre@2.0.2': + resolution: {integrity: sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug==} + + '@changesets/read@0.6.6': + resolution: {integrity: sha512-P5QaN9hJSQQKJShzzpBT13FzOSPyHbqdoIBUd2DJdgvnECCyO6LmAOWSV+O8se2TaZJVwSXjL+v9yhb+a9JeJg==} + + '@changesets/should-skip-package@0.1.2': + resolution: {integrity: sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw==} + + '@changesets/types@4.1.0': + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + + '@changesets/types@6.1.0': + resolution: {integrity: sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA==} + + '@changesets/write@0.4.0': + resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} + + '@csstools/color-helpers@6.0.1': + resolution: {integrity: sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.1.1': + resolution: {integrity: sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.0.1': + resolution: {integrity: sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.0.27': + resolution: {integrity: sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==} + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.3': + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.3': + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.3': + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.3': + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.3': + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.3': + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.3': + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.3': + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.3': + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.3': + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.3': + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.3': + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.3': + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.3': + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.3': + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.3': + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.3': + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.1': + resolution: {integrity: sha512-uVSdg/V4dfQmTjJzR0szNczjOH/J+FyUMMjYtr07xFRXR7EDf9i1qdxrD0VusZH9knj1/ecxzCQQxyic5NzAiA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.5.2': + resolution: {integrity: sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.1.0': + resolution: {integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/object-schema@3.0.1': + resolution: {integrity: sha512-P9cq2dpr+LU8j3qbLygLcSZrl2/ds/pUpfnHNNuk5HW7mnngHs+6WSq5C9mO3rqRX8A1poxqLTC9cu0KOyJlBg==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.6.0': + resolution: {integrity: sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@exodus/bytes@1.14.1': + resolution: {integrity: sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.1': + resolution: {integrity: sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==} + engines: {node: 20 || >=22} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@manypkg/find-root@1.1.0': + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + + '@manypkg/get-packages@1.1.3': + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + + '@microsoft/api-extractor-model@7.32.2': + resolution: {integrity: sha512-Ussc25rAalc+4JJs9HNQE7TuO9y6jpYQX9nWD1DhqUzYPBr3Lr7O9intf+ZY8kD5HnIqeIRJX7ccCT0QyBy2Ww==} + + '@microsoft/api-extractor@7.56.3': + resolution: {integrity: sha512-fRqok4aRNq5GpgGBv2fKlSSKbirPKTJ75vQefthB5x9dwt4Zz+AezUzdc1p/AG4wUBIgmhjcEwn/Rj+N4Wh4Mw==} + hasBin: true + + '@microsoft/tsdoc-config@0.18.0': + resolution: {integrity: sha512-8N/vClYyfOH+l4fLkkr9+myAoR6M7akc8ntBJ4DJdWH2b09uVfr71+LTMpNyG19fNqWDg8KEDZhx5wxuqHyGjw==} + + '@microsoft/tsdoc@0.16.0': + resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.57.1': + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.57.1': + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.57.1': + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.57.1': + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.57.1': + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.57.1': + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.57.1': + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.57.1': + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.57.1': + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.57.1': + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.57.1': + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.57.1': + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.57.1': + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.57.1': + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} + cpu: [x64] + os: [win32] + + '@rushstack/node-core-library@5.19.1': + resolution: {integrity: sha512-ESpb2Tajlatgbmzzukg6zyAhH+sICqJR2CNXNhXcEbz6UGCQfrKCtkxOpJTftWc8RGouroHG0Nud1SJAszvpmA==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/problem-matcher@0.1.1': + resolution: {integrity: sha512-Fm5XtS7+G8HLcJHCWpES5VmeMyjAKaWeyZU5qPzZC+22mPlJzAsOxymHiWIfuirtPckX3aptWws+K2d0BzniJA==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/rig-package@0.6.0': + resolution: {integrity: sha512-ZQmfzsLE2+Y91GF15c65L/slMRVhF6Hycq04D4TwtdGaUAbIXXg9c5pKA5KFU7M4QMaihoobp9JJYpYcaY3zOw==} + + '@rushstack/terminal@0.21.0': + resolution: {integrity: sha512-cLaI4HwCNYmknM5ns4G+drqdEB6q3dCPV423+d3TZeBusYSSm09+nR7CnhzJMjJqeRcdMAaLnrA4M/3xDz4R3w==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/ts-command-line@5.2.0': + resolution: {integrity: sha512-lYxCX0nDdkDtCkVpvF0m25ymf66SaMWuppbD6b7MdkIzvGXKBXNIVZlwBH/C0YfkanrupnICWf2n4z3AKSfaHw==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@types/argparse@1.0.38': + resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/jsdom@27.0.0': + resolution: {integrity: sha512-NZyFl/PViwKzdEkQg96gtnB8wm+1ljhdDay9ahn4hgb+SfVtPCbm3TlmDUFXTA+MGN3CijicnMhG18SI5H3rFw==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@12.20.55': + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + + '@types/node@25.2.3': + resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + + '@typescript-eslint/eslint-plugin@8.55.0': + resolution: {integrity: sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.55.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.55.0': + resolution: {integrity: sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.55.0': + resolution: {integrity: sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.55.0': + resolution: {integrity: sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.55.0': + resolution: {integrity: sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.55.0': + resolution: {integrity: sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.55.0': + resolution: {integrity: sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.55.0': + resolution: {integrity: sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.55.0': + resolution: {integrity: sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.55.0': + resolution: {integrity: sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitest/expect@4.0.18': + resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} + + '@vitest/mocker@4.0.18': + resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.0.18': + resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} + + '@vitest/runner@4.0.18': + resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} + + '@vitest/snapshot@4.0.18': + resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} + + '@vitest/spy@4.0.18': + resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} + + '@vitest/utils@4.0.18': + resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} + + '@volar/language-core@2.4.28': + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} + + '@volar/source-map@2.4.28': + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} + + '@volar/typescript@2.4.28': + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + + '@vue/compiler-core@3.5.28': + resolution: {integrity: sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ==} + + '@vue/compiler-dom@3.5.28': + resolution: {integrity: sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA==} + + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + + '@vue/language-core@2.2.0': + resolution: {integrity: sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@vue/shared@3.5.28': + resolution: {integrity: sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + + ajv@8.13.0: + resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==} + + alien-signals@0.4.14: + resolution: {integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + better-path-resolve@1.0.0: + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} + engines: {node: '>=4'} + + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + compare-versions@6.1.1: + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + cssstyle@6.0.1: + resolution: {integrity: sha512-IoJs7La+oFp/AB033wBStxNOJt4+9hHMxsXUPANcoXL2b3W4DZKghlJ2cI/eyeRZIQ9ysvYEorVhjrcYctWbog==} + engines: {node: '>=20'} + + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + de-indent@1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + + diff@8.0.3: + resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} + engines: {node: '>=0.3.1'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@9.1.0: + resolution: {integrity: sha512-CkWE42hOJsNj9FJRaoMX9waUFYhqY4jmyLFdAdzZr6VaCg3ynLYx4WnOdkaIifGfH4gsUcBTn4OZbHXkpLD0FQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.0: + resolution: {integrity: sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.0.0: + resolution: {integrity: sha512-O0piBKY36YSJhlFSG8p9VUdPV/SxxS4FYDWVpr/9GJuMaepzwlf4J8I4ov1b+ySQfDTPhc3DtLaxcT1fN0yqCg==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.1.0: + resolution: {integrity: sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + + extendable-error@0.1.7: + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + fs-extra@11.3.3: + resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} + engines: {node: '>=14.14'} + + fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@17.3.0: + resolution: {integrity: sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==} + engines: {node: '>=18'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + human-id@4.1.3: + resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} + hasBin: true + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-lazy@4.0.0: + resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} + engines: {node: '>=8'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + is-subdir@1.2.0: + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} + engines: {node: '>=4'} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jju@1.4.0: + resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} + + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsdom@28.1.0: + resolution: {integrity: sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + local-pkg@1.1.2: + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + engines: {node: '>=14'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + + lru-cache@11.2.6: + resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} + engines: {node: 20 || >=22} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + minimatch@10.1.2: + resolution: {integrity: sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==} + engines: {node: 20 || >=22} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + outdent@0.5.0: + resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + + p-filter@2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-map@2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-manager-detector@0.2.11: + resolution: {integrity: sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + parse5@8.0.0: + resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + read-yaml-file@1.1.0: + resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} + engines: {node: '>=6'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.57.1: + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + spawndamnit@3.0.1: + resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + term-size@2.2.1: + resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} + engines: {node: '>=8'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + + tldts-core@7.0.23: + resolution: {integrity: sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==} + + tldts@7.0.23: + resolution: {integrity: sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==} + hasBin: true + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tough-cookie@6.0.0: + resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.55.0: + resolution: {integrity: sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.8.2: + resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} + engines: {node: '>=14.17'} + hasBin: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + undici@7.22.0: + resolution: {integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==} + engines: {node: '>=20.18.1'} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + uuid@13.0.0: + resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} + hasBin: true + + vite-plugin-dts@4.5.4: + resolution: {integrity: sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==} + peerDependencies: + typescript: '*' + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.0.18: + resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.18 + '@vitest/browser-preview': 4.0.18 + '@vitest/browser-webdriverio': 4.0.18 + '@vitest/ui': 4.0.18 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.0: + resolution: {integrity: sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@acemir/cssom@0.9.31': {} + + '@asamuzakjp/css-color@4.1.2': + dependencies: + '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + lru-cache: 11.2.6 + + '@asamuzakjp/dom-selector@6.8.1': + dependencies: + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.1.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.2.6 + + '@asamuzakjp/nwsapi@2.3.9': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + + '@babel/runtime@7.28.6': {} + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.1.0 + + '@changesets/apply-release-plan@7.0.14': + dependencies: + '@changesets/config': 3.1.2 + '@changesets/get-version-range-type': 0.4.0 + '@changesets/git': 3.0.4 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.8.8 + resolve-from: 5.0.0 + semver: 7.7.4 + + '@changesets/assemble-release-plan@6.0.9': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + semver: 7.7.4 + + '@changesets/changelog-git@0.2.1': + dependencies: + '@changesets/types': 6.1.0 + + '@changesets/cli@2.29.8(@types/node@25.2.3)': + dependencies: + '@changesets/apply-release-plan': 7.0.14 + '@changesets/assemble-release-plan': 6.0.9 + '@changesets/changelog-git': 0.2.1 + '@changesets/config': 3.1.2 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/get-release-plan': 4.0.14 + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.6 + '@changesets/should-skip-package': 0.1.2 + '@changesets/types': 6.1.0 + '@changesets/write': 0.4.0 + '@inquirer/external-editor': 1.0.3(@types/node@25.2.3) + '@manypkg/get-packages': 1.1.3 + ansi-colors: 4.1.3 + ci-info: 3.9.0 + enquirer: 2.4.1 + fs-extra: 7.0.1 + mri: 1.2.0 + p-limit: 2.3.0 + package-manager-detector: 0.2.11 + picocolors: 1.1.1 + resolve-from: 5.0.0 + semver: 7.7.4 + spawndamnit: 3.0.1 + term-size: 2.2.1 + transitivePeerDependencies: + - '@types/node' + + '@changesets/config@3.1.2': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.3 + '@changesets/logger': 0.1.1 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.8 + + '@changesets/errors@0.2.0': + dependencies: + extendable-error: 0.1.7 + + '@changesets/get-dependents-graph@2.1.3': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + picocolors: 1.1.1 + semver: 7.7.4 + + '@changesets/get-release-plan@4.0.14': + dependencies: + '@changesets/assemble-release-plan': 6.0.9 + '@changesets/config': 3.1.2 + '@changesets/pre': 2.0.2 + '@changesets/read': 0.6.6 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/get-version-range-type@0.4.0': {} + + '@changesets/git@3.0.4': + dependencies: + '@changesets/errors': 0.2.0 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.8 + spawndamnit: 3.0.1 + + '@changesets/logger@0.1.1': + dependencies: + picocolors: 1.1.1 + + '@changesets/parse@0.4.2': + dependencies: + '@changesets/types': 6.1.0 + js-yaml: 4.1.1 + + '@changesets/pre@2.0.2': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + + '@changesets/read@0.6.6': + dependencies: + '@changesets/git': 3.0.4 + '@changesets/logger': 0.1.1 + '@changesets/parse': 0.4.2 + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + p-filter: 2.1.0 + picocolors: 1.1.1 + + '@changesets/should-skip-package@0.1.2': + dependencies: + '@changesets/types': 6.1.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/types@4.1.0': {} + + '@changesets/types@6.1.0': {} + + '@changesets/write@0.4.0': + dependencies: + '@changesets/types': 6.1.0 + fs-extra: 7.0.1 + human-id: 4.1.3 + prettier: 2.8.8 + + '@csstools/color-helpers@6.0.1': {} + + '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.0.1 + '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.0.27': {} + + '@csstools/css-tokenizer@4.0.0': {} + + '@esbuild/aix-ppc64@0.27.3': + optional: true + + '@esbuild/android-arm64@0.27.3': + optional: true + + '@esbuild/android-arm@0.27.3': + optional: true + + '@esbuild/android-x64@0.27.3': + optional: true + + '@esbuild/darwin-arm64@0.27.3': + optional: true + + '@esbuild/darwin-x64@0.27.3': + optional: true + + '@esbuild/freebsd-arm64@0.27.3': + optional: true + + '@esbuild/freebsd-x64@0.27.3': + optional: true + + '@esbuild/linux-arm64@0.27.3': + optional: true + + '@esbuild/linux-arm@0.27.3': + optional: true + + '@esbuild/linux-ia32@0.27.3': + optional: true + + '@esbuild/linux-loong64@0.27.3': + optional: true + + '@esbuild/linux-mips64el@0.27.3': + optional: true + + '@esbuild/linux-ppc64@0.27.3': + optional: true + + '@esbuild/linux-riscv64@0.27.3': + optional: true + + '@esbuild/linux-s390x@0.27.3': + optional: true + + '@esbuild/linux-x64@0.27.3': + optional: true + + '@esbuild/netbsd-arm64@0.27.3': + optional: true + + '@esbuild/netbsd-x64@0.27.3': + optional: true + + '@esbuild/openbsd-arm64@0.27.3': + optional: true + + '@esbuild/openbsd-x64@0.27.3': + optional: true + + '@esbuild/openharmony-arm64@0.27.3': + optional: true + + '@esbuild/sunos-x64@0.27.3': + optional: true + + '@esbuild/win32-arm64@0.27.3': + optional: true + + '@esbuild/win32-ia32@0.27.3': + optional: true + + '@esbuild/win32-x64@0.27.3': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@10.0.0)': + dependencies: + eslint: 10.0.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.1': + dependencies: + '@eslint/object-schema': 3.0.1 + debug: 4.4.3 + minimatch: 10.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.5.2': + dependencies: + '@eslint/core': 1.1.0 + + '@eslint/core@1.1.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/js@10.0.1(eslint@10.0.0)': + optionalDependencies: + eslint: 10.0.0 + + '@eslint/object-schema@3.0.1': {} + + '@eslint/plugin-kit@0.6.0': + dependencies: + '@eslint/core': 1.1.0 + levn: 0.4.1 + + '@exodus/bytes@1.14.1': {} + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@inquirer/external-editor@1.0.3(@types/node@25.2.3)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 25.2.3 + + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.1': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@manypkg/find-root@1.1.0': + dependencies: + '@babel/runtime': 7.28.6 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + + '@manypkg/get-packages@1.1.3': + dependencies: + '@babel/runtime': 7.28.6 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + + '@microsoft/api-extractor-model@7.32.2(@types/node@25.2.3)': + dependencies: + '@microsoft/tsdoc': 0.16.0 + '@microsoft/tsdoc-config': 0.18.0 + '@rushstack/node-core-library': 5.19.1(@types/node@25.2.3) + transitivePeerDependencies: + - '@types/node' + + '@microsoft/api-extractor@7.56.3(@types/node@25.2.3)': + dependencies: + '@microsoft/api-extractor-model': 7.32.2(@types/node@25.2.3) + '@microsoft/tsdoc': 0.16.0 + '@microsoft/tsdoc-config': 0.18.0 + '@rushstack/node-core-library': 5.19.1(@types/node@25.2.3) + '@rushstack/rig-package': 0.6.0 + '@rushstack/terminal': 0.21.0(@types/node@25.2.3) + '@rushstack/ts-command-line': 5.2.0(@types/node@25.2.3) + diff: 8.0.3 + lodash: 4.17.23 + minimatch: 10.1.2 + resolve: 1.22.11 + semver: 7.5.4 + source-map: 0.6.1 + typescript: 5.8.2 + transitivePeerDependencies: + - '@types/node' + + '@microsoft/tsdoc-config@0.18.0': + dependencies: + '@microsoft/tsdoc': 0.16.0 + ajv: 8.12.0 + jju: 1.4.0 + resolve: 1.22.11 + + '@microsoft/tsdoc@0.16.0': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@rollup/pluginutils@5.3.0(rollup@4.57.1)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.57.1 + + '@rollup/rollup-android-arm-eabi@4.57.1': + optional: true + + '@rollup/rollup-android-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-x64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.57.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.57.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.57.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.57.1': + optional: true + + '@rushstack/node-core-library@5.19.1(@types/node@25.2.3)': + dependencies: + ajv: 8.13.0 + ajv-draft-04: 1.0.0(ajv@8.13.0) + ajv-formats: 3.0.1(ajv@8.13.0) + fs-extra: 11.3.3 + import-lazy: 4.0.0 + jju: 1.4.0 + resolve: 1.22.11 + semver: 7.5.4 + optionalDependencies: + '@types/node': 25.2.3 + + '@rushstack/problem-matcher@0.1.1(@types/node@25.2.3)': + optionalDependencies: + '@types/node': 25.2.3 + + '@rushstack/rig-package@0.6.0': + dependencies: + resolve: 1.22.11 + strip-json-comments: 3.1.1 + + '@rushstack/terminal@0.21.0(@types/node@25.2.3)': + dependencies: + '@rushstack/node-core-library': 5.19.1(@types/node@25.2.3) + '@rushstack/problem-matcher': 0.1.1(@types/node@25.2.3) + supports-color: 8.1.1 + optionalDependencies: + '@types/node': 25.2.3 + + '@rushstack/ts-command-line@5.2.0(@types/node@25.2.3)': + dependencies: + '@rushstack/terminal': 0.21.0(@types/node@25.2.3) + '@types/argparse': 1.0.38 + argparse: 1.0.10 + string-argv: 0.3.2 + transitivePeerDependencies: + - '@types/node' + + '@standard-schema/spec@1.1.0': {} + + '@types/argparse@1.0.38': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/esrecurse@4.3.1': {} + + '@types/estree@1.0.8': {} + + '@types/jsdom@27.0.0': + dependencies: + '@types/node': 25.2.3 + '@types/tough-cookie': 4.0.5 + parse5: 7.3.0 + + '@types/json-schema@7.0.15': {} + + '@types/node@12.20.55': {} + + '@types/node@25.2.3': + dependencies: + undici-types: 7.16.0 + + '@types/tough-cookie@4.0.5': {} + + '@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@10.0.0)(typescript@5.9.3))(eslint@10.0.0)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.55.0(eslint@10.0.0)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.55.0 + '@typescript-eslint/type-utils': 8.55.0(eslint@10.0.0)(typescript@5.9.3) + '@typescript-eslint/utils': 8.55.0(eslint@10.0.0)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.55.0 + eslint: 10.0.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.55.0(eslint@10.0.0)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.55.0 + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.55.0 + debug: 4.4.3 + eslint: 10.0.0 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.55.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) + '@typescript-eslint/types': 8.55.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.55.0': + dependencies: + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/visitor-keys': 8.55.0 + + '@typescript-eslint/tsconfig-utils@8.55.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.55.0(eslint@10.0.0)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.55.0(eslint@10.0.0)(typescript@5.9.3) + debug: 4.4.3 + eslint: 10.0.0 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.55.0': {} + + '@typescript-eslint/typescript-estree@8.55.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.55.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/visitor-keys': 8.55.0 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.55.0(eslint@10.0.0)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0) + '@typescript-eslint/scope-manager': 8.55.0 + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) + eslint: 10.0.0 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.55.0': + dependencies: + '@typescript-eslint/types': 8.55.0 + eslint-visitor-keys: 4.2.1 + + '@vitest/expect@4.0.18': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + chai: 6.2.2 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.2.3))': + dependencies: + '@vitest/spy': 4.0.18 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.1(@types/node@25.2.3) + + '@vitest/pretty-format@4.0.18': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.18': + dependencies: + '@vitest/utils': 4.0.18 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.0.18': {} + + '@vitest/utils@4.0.18': + dependencies: + '@vitest/pretty-format': 4.0.18 + tinyrainbow: 3.0.3 + + '@volar/language-core@2.4.28': + dependencies: + '@volar/source-map': 2.4.28 + + '@volar/source-map@2.4.28': {} + + '@volar/typescript@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue/compiler-core@3.5.28': + dependencies: + '@babel/parser': 7.29.0 + '@vue/shared': 3.5.28 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.28': + dependencies: + '@vue/compiler-core': 3.5.28 + '@vue/shared': 3.5.28 + + '@vue/compiler-vue2@2.7.16': + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/language-core@2.2.0(typescript@5.9.3)': + dependencies: + '@volar/language-core': 2.4.28 + '@vue/compiler-dom': 3.5.28 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.28 + alien-signals: 0.4.14 + minimatch: 9.0.5 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + optionalDependencies: + typescript: 5.9.3 + + '@vue/shared@3.5.28': {} + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + agent-base@7.1.4: {} + + ajv-draft-04@1.0.0(ajv@8.13.0): + optionalDependencies: + ajv: 8.13.0 + + ajv-formats@3.0.1(ajv@8.13.0): + optionalDependencies: + ajv: 8.13.0 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.12.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + ajv@8.13.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + alien-signals@0.4.14: {} + + ansi-colors@4.1.3: {} + + ansi-regex@5.0.1: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + array-union@2.1.0: {} + + assertion-error@2.0.1: {} + + balanced-match@1.0.2: {} + + better-path-resolve@1.0.0: + dependencies: + is-windows: 1.0.2 + + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + chai@6.2.2: {} + + chardet@2.1.1: {} + + ci-info@3.9.0: {} + + compare-versions@6.1.1: {} + + confbox@0.1.8: {} + + confbox@0.2.4: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + cssstyle@6.0.1: + dependencies: + '@asamuzakjp/css-color': 4.1.2 + '@csstools/css-syntax-patches-for-csstree': 1.0.27 + css-tree: 3.1.0 + lru-cache: 11.2.6 + + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.0 + transitivePeerDependencies: + - '@noble/hashes' + + de-indent@1.0.2: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + deep-is@0.1.4: {} + + detect-indent@6.1.0: {} + + diff@8.0.3: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + + entities@6.0.1: {} + + entities@7.0.1: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.27.3: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 + + escape-string-regexp@4.0.0: {} + + eslint-scope@9.1.0: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.0: {} + + eslint@10.0.0: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.1 + '@eslint/config-helpers': 0.5.2 + '@eslint/core': 1.1.0 + '@eslint/plugin-kit': 0.6.0 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.0 + eslint-visitor-keys: 5.0.0 + espree: 11.1.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@11.1.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 5.0.0 + + esprima@4.0.1: {} + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + expect-type@1.3.0: {} + + exsolve@1.0.8: {} + + extendable-error@0.1.7: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + fs-extra@11.3.3: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-extra@7.0.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@17.3.0: {} + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.14.1 + transitivePeerDependencies: + - '@noble/hashes' + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + human-id@4.1.3: {} + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-lazy@4.0.0: {} + + imurmurhash@0.1.4: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + is-subdir@1.2.0: + dependencies: + better-path-resolve: 1.0.0 + + is-windows@1.0.2: {} + + isexe@2.0.0: {} + + jju@1.4.0: {} + + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsdom@28.1.0: + dependencies: + '@acemir/cssom': 0.9.31 + '@asamuzakjp/dom-selector': 6.8.1 + '@bramus/specificity': 2.4.2 + '@exodus/bytes': 1.14.1 + cssstyle: 6.0.1 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + parse5: 8.0.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.0 + undici: 7.22.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + - supports-color + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kolorist@1.8.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + local-pkg@1.1.2: + dependencies: + mlly: 1.8.0 + pkg-types: 2.3.0 + quansync: 0.2.11 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.startcase@4.4.0: {} + + lodash@4.17.23: {} + + lru-cache@11.2.6: {} + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + mdn-data@2.12.2: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + minimatch@10.1.2: + dependencies: + '@isaacs/brace-expansion': 5.0.1 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + + mri@1.2.0: {} + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + obug@2.1.1: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + outdent@0.5.0: {} + + p-filter@2.1.0: + dependencies: + p-map: 2.1.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-map@2.1.0: {} + + p-try@2.2.0: {} + + package-manager-detector@0.2.11: + dependencies: + quansync: 0.2.11 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + parse5@8.0.0: + dependencies: + entities: 6.0.1 + + path-browserify@1.0.1: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-type@4.0.0: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pify@4.0.1: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.4 + exsolve: 1.0.8 + pathe: 2.0.3 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier@2.8.8: {} + + punycode@2.3.1: {} + + quansync@0.2.11: {} + + queue-microtask@1.2.3: {} + + read-yaml-file@1.1.0: + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.14.2 + pify: 4.0.1 + strip-bom: 3.0.0 + + require-from-string@2.0.2: {} + + resolve-from@5.0.0: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rollup@4.57.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.57.1 + '@rollup/rollup-android-arm64': 4.57.1 + '@rollup/rollup-darwin-arm64': 4.57.1 + '@rollup/rollup-darwin-x64': 4.57.1 + '@rollup/rollup-freebsd-arm64': 4.57.1 + '@rollup/rollup-freebsd-x64': 4.57.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 + '@rollup/rollup-linux-arm64-musl': 4.57.1 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 + '@rollup/rollup-linux-loong64-musl': 4.57.1 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 + '@rollup/rollup-linux-x64-gnu': 4.57.1 + '@rollup/rollup-linux-x64-musl': 4.57.1 + '@rollup/rollup-openbsd-x64': 4.57.1 + '@rollup/rollup-openharmony-arm64': 4.57.1 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 + '@rollup/rollup-win32-x64-gnu': 4.57.1 + '@rollup/rollup-win32-x64-msvc': 4.57.1 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + semver@7.5.4: + dependencies: + lru-cache: 6.0.0 + + semver@7.7.4: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + source-map-js@1.2.1: {} + + source-map@0.6.1: {} + + spawndamnit@3.0.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + sprintf-js@1.0.3: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + string-argv@0.3.2: {} + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-bom@3.0.0: {} + + strip-json-comments@3.1.1: {} + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + symbol-tree@3.2.4: {} + + term-size@2.2.1: {} + + tinybench@2.9.0: {} + + tinyexec@1.0.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinyrainbow@3.0.3: {} + + tldts-core@7.0.23: {} + + tldts@7.0.23: + dependencies: + tldts-core: 7.0.23 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tough-cookie@6.0.0: + dependencies: + tldts: 7.0.23 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + + ts-api-utils@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.55.0(eslint@10.0.0)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0(eslint@10.0.0)(typescript@5.9.3))(eslint@10.0.0)(typescript@5.9.3) + '@typescript-eslint/parser': 8.55.0(eslint@10.0.0)(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.55.0(eslint@10.0.0)(typescript@5.9.3) + eslint: 10.0.0 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.8.2: {} + + typescript@5.9.3: {} + + ufo@1.6.3: {} + + undici-types@7.16.0: {} + + undici@7.22.0: {} + + universalify@0.1.2: {} + + universalify@2.0.1: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + uuid@13.0.0: {} + + vite-plugin-dts@4.5.4(@types/node@25.2.3)(rollup@4.57.1)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.3)): + dependencies: + '@microsoft/api-extractor': 7.56.3(@types/node@25.2.3) + '@rollup/pluginutils': 5.3.0(rollup@4.57.1) + '@volar/typescript': 2.4.28 + '@vue/language-core': 2.2.0(typescript@5.9.3) + compare-versions: 6.1.1 + debug: 4.4.3 + kolorist: 1.8.0 + local-pkg: 1.1.2 + magic-string: 0.30.21 + typescript: 5.9.3 + optionalDependencies: + vite: 7.3.1(@types/node@25.2.3) + transitivePeerDependencies: + - '@types/node' + - rollup + - supports-color + + vite@7.3.1(@types/node@25.2.3): + dependencies: + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.57.1 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 25.2.3 + fsevents: 2.3.3 + + vitest@4.0.18(@types/node@25.2.3)(jsdom@28.1.0): + dependencies: + '@vitest/expect': 4.0.18 + '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.2.3)) + '@vitest/pretty-format': 4.0.18 + '@vitest/runner': 4.0.18 + '@vitest/snapshot': 4.0.18 + '@vitest/spy': 4.0.18 + '@vitest/utils': 4.0.18 + es-module-lexer: 1.7.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.3.1(@types/node@25.2.3) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.2.3 + jsdom: 28.1.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + + vscode-uri@3.1.0: {} + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.0: + dependencies: + '@exodus/bytes': 1.14.1 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + yallist@4.0.0: {} + + yocto-queue@0.1.0: {} diff --git a/resolvespec-js/src/__tests__/common.test.ts b/resolvespec-js/src/__tests__/common.test.ts new file mode 100644 index 0000000..977256a --- /dev/null +++ b/resolvespec-js/src/__tests__/common.test.ts @@ -0,0 +1,143 @@ +import { describe, it, expect } from 'vitest'; +import type { + Options, + FilterOption, + SortOption, + PreloadOption, + RequestBody, + APIResponse, + Metadata, + APIError, + Parameter, + ComputedColumn, + CustomOperator, +} from '../common/types'; + +describe('Common Types', () => { + it('should construct a valid FilterOption with logic_operator', () => { + const filter: FilterOption = { + column: 'name', + operator: 'eq', + value: 'test', + logic_operator: 'OR', + }; + expect(filter.logic_operator).toBe('OR'); + expect(filter.operator).toBe('eq'); + }); + + it('should construct Options with all new fields', () => { + const opts: Options = { + columns: ['id', 'name'], + omit_columns: ['secret'], + filters: [{ column: 'age', operator: 'gte', value: 18 }], + sort: [{ column: 'name', direction: 'asc' }], + limit: 10, + offset: 0, + cursor_forward: 'abc123', + cursor_backward: 'xyz789', + fetch_row_number: '42', + parameters: [{ name: 'param1', value: 'val1', sequence: 1 }], + computedColumns: [{ name: 'full_name', expression: "first || ' ' || last" }], + customOperators: [{ name: 'custom', sql: "status = 'active'" }], + preload: [{ + relation: 'Items', + columns: ['id', 'title'], + omit_columns: ['internal'], + sort: [{ column: 'id', direction: 'ASC' }], + recursive: true, + primary_key: 'id', + related_key: 'parent_id', + sql_joins: ['LEFT JOIN other ON other.id = items.other_id'], + join_aliases: ['other'], + }], + }; + expect(opts.omit_columns).toEqual(['secret']); + expect(opts.cursor_forward).toBe('abc123'); + expect(opts.fetch_row_number).toBe('42'); + expect(opts.parameters![0].sequence).toBe(1); + expect(opts.preload![0].recursive).toBe(true); + }); + + it('should construct a RequestBody with numeric id', () => { + const body: RequestBody = { + operation: 'read', + id: 42, + options: { limit: 10 }, + }; + expect(body.id).toBe(42); + }); + + it('should construct a RequestBody with string array id', () => { + const body: RequestBody = { + operation: 'delete', + id: ['1', '2', '3'], + }; + expect(Array.isArray(body.id)).toBe(true); + }); + + it('should construct Metadata with count and row_number', () => { + const meta: Metadata = { + total: 100, + count: 10, + filtered: 50, + limit: 10, + offset: 0, + row_number: 5, + }; + expect(meta.count).toBe(10); + expect(meta.row_number).toBe(5); + }); + + it('should construct APIError with detail field', () => { + const err: APIError = { + code: 'not_found', + message: 'Record not found', + detail: 'The record with id 42 does not exist', + }; + expect(err.detail).toBeDefined(); + }); + + it('should construct APIResponse with metadata', () => { + const resp: APIResponse = { + success: true, + data: ['a', 'b'], + metadata: { total: 2, count: 2, filtered: 2, limit: 10, offset: 0 }, + }; + expect(resp.metadata?.count).toBe(2); + }); + + it('should support all operator types', () => { + const operators: FilterOption['operator'][] = [ + 'eq', 'neq', 'gt', 'gte', 'lt', 'lte', + 'like', 'ilike', 'in', + 'contains', 'startswith', 'endswith', + 'between', 'between_inclusive', + 'is_null', 'is_not_null', + ]; + for (const op of operators) { + const f: FilterOption = { column: 'x', operator: op, value: 'v' }; + expect(f.operator).toBe(op); + } + }); + + it('should support PreloadOption with computed_ql and where', () => { + const preload: PreloadOption = { + relation: 'Details', + where: "status = 'active'", + computed_ql: { cql1: 'SUM(amount)' }, + table_name: 'detail_table', + updatable: true, + foreign_key: 'detail_id', + recursive_child_key: 'parent_detail_id', + }; + expect(preload.computed_ql?.cql1).toBe('SUM(amount)'); + expect(preload.updatable).toBe(true); + }); + + it('should support Parameter interface', () => { + const p: Parameter = { name: 'key', value: 'val' }; + expect(p.name).toBe('key'); + const p2: Parameter = { name: 'key2', value: 'val2', sequence: 5 }; + expect(p2.sequence).toBe(5); + }); +}); diff --git a/resolvespec-js/src/__tests__/headerspec.test.ts b/resolvespec-js/src/__tests__/headerspec.test.ts new file mode 100644 index 0000000..54ac730 --- /dev/null +++ b/resolvespec-js/src/__tests__/headerspec.test.ts @@ -0,0 +1,239 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { buildHeaders, encodeHeaderValue, decodeHeaderValue, HeaderSpecClient, getHeaderSpecClient } from '../headerspec/client'; +import type { Options, ClientConfig, APIResponse } from '../common/types'; + +describe('buildHeaders', () => { + it('should set X-Select-Fields for columns', () => { + const h = buildHeaders({ columns: ['id', 'name', 'email'] }); + expect(h['X-Select-Fields']).toBe('id,name,email'); + }); + + it('should set X-Not-Select-Fields for omit_columns', () => { + const h = buildHeaders({ omit_columns: ['secret', 'internal'] }); + expect(h['X-Not-Select-Fields']).toBe('secret,internal'); + }); + + it('should set X-FieldFilter for eq AND filters', () => { + const h = buildHeaders({ + filters: [{ column: 'status', operator: 'eq', value: 'active' }], + }); + expect(h['X-FieldFilter-status']).toBe('active'); + }); + + it('should set X-SearchOp for non-eq AND filters', () => { + const h = buildHeaders({ + filters: [{ column: 'age', operator: 'gte', value: 18 }], + }); + expect(h['X-SearchOp-greaterthanorequal-age']).toBe('18'); + }); + + it('should set X-SearchOr for OR filters', () => { + const h = buildHeaders({ + filters: [{ column: 'name', operator: 'contains', value: 'test', logic_operator: 'OR' }], + }); + expect(h['X-SearchOr-contains-name']).toBe('test'); + }); + + it('should set X-Sort with direction prefixes', () => { + const h = buildHeaders({ + sort: [ + { column: 'name', direction: 'asc' }, + { column: 'created_at', direction: 'DESC' }, + ], + }); + expect(h['X-Sort']).toBe('+name,-created_at'); + }); + + it('should set X-Limit and X-Offset', () => { + const h = buildHeaders({ limit: 25, offset: 50 }); + expect(h['X-Limit']).toBe('25'); + expect(h['X-Offset']).toBe('50'); + }); + + it('should set cursor pagination headers', () => { + const h = buildHeaders({ cursor_forward: 'abc', cursor_backward: 'xyz' }); + expect(h['X-Cursor-Forward']).toBe('abc'); + expect(h['X-Cursor-Backward']).toBe('xyz'); + }); + + it('should set X-Preload with pipe-separated relations', () => { + const h = buildHeaders({ + preload: [ + { relation: 'Items', columns: ['id', 'name'] }, + { relation: 'Category' }, + ], + }); + expect(h['X-Preload']).toBe('Items:id,name|Category'); + }); + + it('should set X-Fetch-RowNumber', () => { + const h = buildHeaders({ fetch_row_number: '42' }); + expect(h['X-Fetch-RowNumber']).toBe('42'); + }); + + it('should set X-CQL-SEL for computed columns', () => { + const h = buildHeaders({ + computedColumns: [ + { name: 'total', expression: 'price * qty' }, + ], + }); + expect(h['X-CQL-SEL-total']).toBe('price * qty'); + }); + + it('should set X-Custom-SQL-W for custom operators', () => { + const h = buildHeaders({ + customOperators: [ + { name: 'active', sql: "status = 'active'" }, + { name: 'verified', sql: "verified = true" }, + ], + }); + expect(h['X-Custom-SQL-W']).toBe("status = 'active' AND verified = true"); + }); + + it('should return empty object for empty options', () => { + const h = buildHeaders({}); + expect(Object.keys(h)).toHaveLength(0); + }); + + it('should handle between filter with array value', () => { + const h = buildHeaders({ + filters: [{ column: 'price', operator: 'between', value: [10, 100] }], + }); + expect(h['X-SearchOp-between-price']).toBe('10,100'); + }); + + it('should handle is_null filter with null value', () => { + const h = buildHeaders({ + filters: [{ column: 'deleted_at', operator: 'is_null', value: null }], + }); + expect(h['X-SearchOp-empty-deleted_at']).toBe(''); + }); + + it('should handle in filter with array value', () => { + const h = buildHeaders({ + filters: [{ column: 'id', operator: 'in', value: [1, 2, 3] }], + }); + expect(h['X-SearchOp-in-id']).toBe('1,2,3'); + }); +}); + +describe('encodeHeaderValue / decodeHeaderValue', () => { + it('should round-trip encode/decode', () => { + const original = 'some complex value with spaces & symbols!'; + const encoded = encodeHeaderValue(original); + expect(encoded.startsWith('ZIP_')).toBe(true); + const decoded = decodeHeaderValue(encoded); + expect(decoded).toBe(original); + }); + + it('should decode __ prefixed values', () => { + const encoded = '__' + btoa('hello'); + expect(decodeHeaderValue(encoded)).toBe('hello'); + }); + + it('should return plain values as-is', () => { + expect(decodeHeaderValue('plain')).toBe('plain'); + }); +}); + +describe('HeaderSpecClient', () => { + const config: ClientConfig = { baseUrl: 'http://localhost:3000', token: 'tok' }; + + function mockFetch(data: APIResponse, ok = true) { + return vi.fn().mockResolvedValue({ + ok, + json: () => Promise.resolve(data), + }); + } + + beforeEach(() => { + vi.restoreAllMocks(); + }); + + it('read() sends GET with headers from options', async () => { + globalThis.fetch = mockFetch({ success: true, data: [{ id: 1 }] }); + const client = new HeaderSpecClient(config); + + await client.read('public', 'users', undefined, { + columns: ['id', 'name'], + limit: 10, + }); + + const [url, opts] = (globalThis.fetch as any).mock.calls[0]; + expect(url).toBe('http://localhost:3000/public/users'); + expect(opts.method).toBe('GET'); + expect(opts.headers['X-Select-Fields']).toBe('id,name'); + expect(opts.headers['X-Limit']).toBe('10'); + expect(opts.headers['Authorization']).toBe('Bearer tok'); + }); + + it('read() with id appends to URL', async () => { + globalThis.fetch = mockFetch({ success: true, data: {} }); + const client = new HeaderSpecClient(config); + + await client.read('public', 'users', '42'); + + const [url] = (globalThis.fetch as any).mock.calls[0]; + expect(url).toBe('http://localhost:3000/public/users/42'); + }); + + it('create() sends POST with body and headers', async () => { + globalThis.fetch = mockFetch({ success: true, data: { id: 1 } }); + const client = new HeaderSpecClient(config); + + await client.create('public', 'users', { name: 'Test' }); + + const [url, opts] = (globalThis.fetch as any).mock.calls[0]; + expect(opts.method).toBe('POST'); + expect(JSON.parse(opts.body)).toEqual({ name: 'Test' }); + }); + + it('update() sends PUT with id in URL', async () => { + globalThis.fetch = mockFetch({ success: true, data: {} }); + const client = new HeaderSpecClient(config); + + await client.update('public', 'users', '1', { name: 'Updated' }, { + filters: [{ column: 'active', operator: 'eq', value: true }], + }); + + const [url, opts] = (globalThis.fetch as any).mock.calls[0]; + expect(url).toBe('http://localhost:3000/public/users/1'); + expect(opts.method).toBe('PUT'); + expect(opts.headers['X-FieldFilter-active']).toBe('true'); + }); + + it('delete() sends DELETE', async () => { + globalThis.fetch = mockFetch({ success: true, data: undefined as any }); + const client = new HeaderSpecClient(config); + + await client.delete('public', 'users', '1'); + + const [url, opts] = (globalThis.fetch as any).mock.calls[0]; + expect(url).toBe('http://localhost:3000/public/users/1'); + expect(opts.method).toBe('DELETE'); + }); + + it('throws on non-ok response', async () => { + globalThis.fetch = mockFetch( + { success: false, data: null as any, error: { code: 'err', message: 'fail' } }, + false + ); + const client = new HeaderSpecClient(config); + + await expect(client.read('public', 'users')).rejects.toThrow('fail'); + }); +}); + +describe('getHeaderSpecClient singleton', () => { + it('returns same instance for same baseUrl', () => { + const a = getHeaderSpecClient({ baseUrl: 'http://hs-singleton:3000' }); + const b = getHeaderSpecClient({ baseUrl: 'http://hs-singleton:3000' }); + expect(a).toBe(b); + }); + + it('returns different instances for different baseUrls', () => { + const a = getHeaderSpecClient({ baseUrl: 'http://hs-singleton-a:3000' }); + const b = getHeaderSpecClient({ baseUrl: 'http://hs-singleton-b:3000' }); + expect(a).not.toBe(b); + }); +}); diff --git a/resolvespec-js/src/__tests__/resolvespec.test.ts b/resolvespec-js/src/__tests__/resolvespec.test.ts new file mode 100644 index 0000000..7034237 --- /dev/null +++ b/resolvespec-js/src/__tests__/resolvespec.test.ts @@ -0,0 +1,178 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { ResolveSpecClient, getResolveSpecClient } from '../resolvespec/client'; +import type { ClientConfig, APIResponse } from '../common/types'; + +const config: ClientConfig = { baseUrl: 'http://localhost:3000', token: 'test-token' }; + +function mockFetchResponse(data: APIResponse, ok = true, status = 200) { + return vi.fn().mockResolvedValue({ + ok, + status, + json: () => Promise.resolve(data), + }); +} + +beforeEach(() => { + vi.restoreAllMocks(); +}); + +describe('ResolveSpecClient', () => { + it('read() sends POST with operation read', async () => { + const response: APIResponse = { success: true, data: [{ id: 1 }] }; + globalThis.fetch = mockFetchResponse(response); + + const client = new ResolveSpecClient(config); + const result = await client.read('public', 'users', 1); + expect(result.success).toBe(true); + + const [url, opts] = (globalThis.fetch as any).mock.calls[0]; + expect(url).toBe('http://localhost:3000/public/users/1'); + expect(opts.method).toBe('POST'); + expect(opts.headers['Authorization']).toBe('Bearer test-token'); + + const body = JSON.parse(opts.body); + expect(body.operation).toBe('read'); + }); + + it('read() with string array id puts id in body', async () => { + const response: APIResponse = { success: true, data: [] }; + globalThis.fetch = mockFetchResponse(response); + + const client = new ResolveSpecClient(config); + await client.read('public', 'users', ['1', '2']); + const body = JSON.parse((globalThis.fetch as any).mock.calls[0][1].body); + expect(body.id).toEqual(['1', '2']); + }); + + it('read() passes options through', async () => { + const response: APIResponse = { success: true, data: [] }; + globalThis.fetch = mockFetchResponse(response); + + const client = new ResolveSpecClient(config); + await client.read('public', 'users', undefined, { + columns: ['id', 'name'], + omit_columns: ['secret'], + filters: [{ column: 'active', operator: 'eq', value: true }], + sort: [{ column: 'name', direction: 'asc' }], + limit: 10, + offset: 0, + cursor_forward: 'cursor1', + fetch_row_number: '5', + }); + + const body = JSON.parse((globalThis.fetch as any).mock.calls[0][1].body); + expect(body.options.columns).toEqual(['id', 'name']); + expect(body.options.omit_columns).toEqual(['secret']); + expect(body.options.cursor_forward).toBe('cursor1'); + expect(body.options.fetch_row_number).toBe('5'); + }); + + it('create() sends POST with operation create and data', async () => { + const response: APIResponse = { success: true, data: { id: 1, name: 'Test' } }; + globalThis.fetch = mockFetchResponse(response); + + const client = new ResolveSpecClient(config); + const result = await client.create('public', 'users', { name: 'Test' }); + expect(result.data.name).toBe('Test'); + + const body = JSON.parse((globalThis.fetch as any).mock.calls[0][1].body); + expect(body.operation).toBe('create'); + expect(body.data.name).toBe('Test'); + }); + + it('update() with single id puts id in URL', async () => { + const response: APIResponse = { success: true, data: { id: 1 } }; + globalThis.fetch = mockFetchResponse(response); + + const client = new ResolveSpecClient(config); + await client.update('public', 'users', { name: 'Updated' }, 1); + const [url] = (globalThis.fetch as any).mock.calls[0]; + expect(url).toBe('http://localhost:3000/public/users/1'); + }); + + it('update() with string array id puts id in body', async () => { + const response: APIResponse = { success: true, data: {} }; + globalThis.fetch = mockFetchResponse(response); + + const client = new ResolveSpecClient(config); + await client.update('public', 'users', { active: false }, ['1', '2']); + const body = JSON.parse((globalThis.fetch as any).mock.calls[0][1].body); + expect(body.id).toEqual(['1', '2']); + }); + + it('delete() sends POST with operation delete', async () => { + const response: APIResponse = { success: true, data: undefined as any }; + globalThis.fetch = mockFetchResponse(response); + + const client = new ResolveSpecClient(config); + await client.delete('public', 'users', 1); + const [url, opts] = (globalThis.fetch as any).mock.calls[0]; + expect(url).toBe('http://localhost:3000/public/users/1'); + + const body = JSON.parse(opts.body); + expect(body.operation).toBe('delete'); + }); + + it('getMetadata() sends GET request', async () => { + const response: APIResponse = { + success: true, + data: { schema: 'public', table: 'users', columns: [], relations: [] }, + }; + globalThis.fetch = mockFetchResponse(response); + + const client = new ResolveSpecClient(config); + const result = await client.getMetadata('public', 'users'); + expect(result.data.table).toBe('users'); + + const opts = (globalThis.fetch as any).mock.calls[0][1]; + expect(opts.method).toBe('GET'); + }); + + it('throws on non-ok response', async () => { + const errorResp = { + success: false, + data: null, + error: { code: 'not_found', message: 'Not found' }, + }; + globalThis.fetch = mockFetchResponse(errorResp as any, false, 404); + + const client = new ResolveSpecClient(config); + await expect(client.read('public', 'users', 999)).rejects.toThrow('Not found'); + }); + + it('throws generic error when no error message', async () => { + globalThis.fetch = vi.fn().mockResolvedValue({ + ok: false, + status: 500, + json: () => Promise.resolve({ success: false, data: null }), + }); + + const client = new ResolveSpecClient(config); + await expect(client.read('public', 'users')).rejects.toThrow('An error occurred'); + }); + + it('config without token omits Authorization header', async () => { + const noAuthConfig: ClientConfig = { baseUrl: 'http://localhost:3000' }; + const response: APIResponse = { success: true, data: [] }; + globalThis.fetch = mockFetchResponse(response); + + const client = new ResolveSpecClient(noAuthConfig); + await client.read('public', 'users'); + const opts = (globalThis.fetch as any).mock.calls[0][1]; + expect(opts.headers['Authorization']).toBeUndefined(); + }); +}); + +describe('getResolveSpecClient singleton', () => { + it('returns same instance for same baseUrl', () => { + const a = getResolveSpecClient({ baseUrl: 'http://singleton-test:3000' }); + const b = getResolveSpecClient({ baseUrl: 'http://singleton-test:3000' }); + expect(a).toBe(b); + }); + + it('returns different instances for different baseUrls', () => { + const a = getResolveSpecClient({ baseUrl: 'http://singleton-a:3000' }); + const b = getResolveSpecClient({ baseUrl: 'http://singleton-b:3000' }); + expect(a).not.toBe(b); + }); +}); diff --git a/resolvespec-js/src/__tests__/websocketspec.test.ts b/resolvespec-js/src/__tests__/websocketspec.test.ts new file mode 100644 index 0000000..7850b45 --- /dev/null +++ b/resolvespec-js/src/__tests__/websocketspec.test.ts @@ -0,0 +1,336 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { WebSocketClient, getWebSocketClient } from '../websocketspec/client'; +import type { WebSocketClientConfig } from '../websocketspec/types'; + +// Mock uuid +vi.mock('uuid', () => ({ + v4: vi.fn(() => 'mock-uuid-1234'), +})); + +// Mock WebSocket +class MockWebSocket { + static OPEN = 1; + static CLOSED = 3; + + url: string; + readyState = MockWebSocket.OPEN; + onopen: ((ev: any) => void) | null = null; + onclose: ((ev: any) => void) | null = null; + onmessage: ((ev: any) => void) | null = null; + onerror: ((ev: any) => void) | null = null; + + private sentMessages: string[] = []; + + constructor(url: string) { + this.url = url; + // Simulate async open + setTimeout(() => { + this.onopen?.({}); + }, 0); + } + + send(data: string) { + this.sentMessages.push(data); + } + + close() { + this.readyState = MockWebSocket.CLOSED; + this.onclose?.({ code: 1000, reason: 'Normal closure' } as any); + } + + getSentMessages(): any[] { + return this.sentMessages.map((m) => JSON.parse(m)); + } + + simulateMessage(data: any) { + this.onmessage?.({ data: JSON.stringify(data) }); + } +} + +let mockWsInstance: MockWebSocket | null = null; + +beforeEach(() => { + mockWsInstance = null; + (globalThis as any).WebSocket = class extends MockWebSocket { + constructor(url: string) { + super(url); + mockWsInstance = this; + } + }; + (globalThis as any).WebSocket.OPEN = MockWebSocket.OPEN; + (globalThis as any).WebSocket.CLOSED = MockWebSocket.CLOSED; +}); + +afterEach(() => { + vi.restoreAllMocks(); +}); + +describe('WebSocketClient', () => { + const wsConfig: WebSocketClientConfig = { + url: 'ws://localhost:8080', + reconnect: false, + heartbeatInterval: 60000, + }; + + it('should connect and set state to connected', async () => { + const client = new WebSocketClient(wsConfig); + await client.connect(); + expect(client.getState()).toBe('connected'); + expect(client.isConnected()).toBe(true); + client.disconnect(); + }); + + it('should disconnect and set state to disconnected', async () => { + const client = new WebSocketClient(wsConfig); + await client.connect(); + client.disconnect(); + expect(client.getState()).toBe('disconnected'); + expect(client.isConnected()).toBe(false); + }); + + it('should send read request', async () => { + const client = new WebSocketClient(wsConfig); + await client.connect(); + + const readPromise = client.read('users', { + schema: 'public', + filters: [{ column: 'active', operator: 'eq', value: true }], + limit: 10, + }); + + // Simulate server response + const sent = mockWsInstance!.getSentMessages(); + expect(sent.length).toBe(1); + expect(sent[0].operation).toBe('read'); + expect(sent[0].entity).toBe('users'); + expect(sent[0].options.filters[0].column).toBe('active'); + + mockWsInstance!.simulateMessage({ + id: sent[0].id, + type: 'response', + success: true, + data: [{ id: 1 }], + timestamp: new Date().toISOString(), + }); + + const result = await readPromise; + expect(result).toEqual([{ id: 1 }]); + + client.disconnect(); + }); + + it('should send create request', async () => { + const client = new WebSocketClient(wsConfig); + await client.connect(); + + const createPromise = client.create('users', { name: 'Test' }, { schema: 'public' }); + + const sent = mockWsInstance!.getSentMessages(); + expect(sent[0].operation).toBe('create'); + expect(sent[0].data.name).toBe('Test'); + + mockWsInstance!.simulateMessage({ + id: sent[0].id, + type: 'response', + success: true, + data: { id: 1, name: 'Test' }, + timestamp: new Date().toISOString(), + }); + + const result = await createPromise; + expect(result.name).toBe('Test'); + + client.disconnect(); + }); + + it('should send update request with record_id', async () => { + const client = new WebSocketClient(wsConfig); + await client.connect(); + + const updatePromise = client.update('users', '1', { name: 'Updated' }); + + const sent = mockWsInstance!.getSentMessages(); + expect(sent[0].operation).toBe('update'); + expect(sent[0].record_id).toBe('1'); + + mockWsInstance!.simulateMessage({ + id: sent[0].id, + type: 'response', + success: true, + data: { id: 1, name: 'Updated' }, + timestamp: new Date().toISOString(), + }); + + await updatePromise; + client.disconnect(); + }); + + it('should send delete request', async () => { + const client = new WebSocketClient(wsConfig); + await client.connect(); + + const deletePromise = client.delete('users', '1'); + + const sent = mockWsInstance!.getSentMessages(); + expect(sent[0].operation).toBe('delete'); + expect(sent[0].record_id).toBe('1'); + + mockWsInstance!.simulateMessage({ + id: sent[0].id, + type: 'response', + success: true, + timestamp: new Date().toISOString(), + }); + + await deletePromise; + client.disconnect(); + }); + + it('should reject on failed request', async () => { + const client = new WebSocketClient(wsConfig); + await client.connect(); + + const readPromise = client.read('users'); + + const sent = mockWsInstance!.getSentMessages(); + mockWsInstance!.simulateMessage({ + id: sent[0].id, + type: 'response', + success: false, + error: { code: 'not_found', message: 'Not found' }, + timestamp: new Date().toISOString(), + }); + + await expect(readPromise).rejects.toThrow('Not found'); + client.disconnect(); + }); + + it('should handle subscriptions', async () => { + const client = new WebSocketClient(wsConfig); + await client.connect(); + + const callback = vi.fn(); + const subPromise = client.subscribe('users', callback, { + schema: 'public', + }); + + const sent = mockWsInstance!.getSentMessages(); + expect(sent[0].type).toBe('subscription'); + expect(sent[0].operation).toBe('subscribe'); + + mockWsInstance!.simulateMessage({ + id: sent[0].id, + type: 'response', + success: true, + data: { subscription_id: 'sub-1' }, + timestamp: new Date().toISOString(), + }); + + const subId = await subPromise; + expect(subId).toBe('sub-1'); + expect(client.getSubscriptions()).toHaveLength(1); + + // Simulate notification + mockWsInstance!.simulateMessage({ + type: 'notification', + operation: 'create', + subscription_id: 'sub-1', + entity: 'users', + data: { id: 2, name: 'New' }, + timestamp: new Date().toISOString(), + }); + + expect(callback).toHaveBeenCalledTimes(1); + expect(callback.mock.calls[0][0].data.id).toBe(2); + + client.disconnect(); + }); + + it('should handle unsubscribe', async () => { + const client = new WebSocketClient(wsConfig); + await client.connect(); + + // Subscribe first + const subPromise = client.subscribe('users', vi.fn()); + let sent = mockWsInstance!.getSentMessages(); + mockWsInstance!.simulateMessage({ + id: sent[0].id, + type: 'response', + success: true, + data: { subscription_id: 'sub-1' }, + timestamp: new Date().toISOString(), + }); + await subPromise; + + // Unsubscribe + const unsubPromise = client.unsubscribe('sub-1'); + sent = mockWsInstance!.getSentMessages(); + mockWsInstance!.simulateMessage({ + id: sent[sent.length - 1].id, + type: 'response', + success: true, + timestamp: new Date().toISOString(), + }); + + await unsubPromise; + expect(client.getSubscriptions()).toHaveLength(0); + + client.disconnect(); + }); + + it('should emit events', async () => { + const client = new WebSocketClient(wsConfig); + const connectCb = vi.fn(); + const stateChangeCb = vi.fn(); + + client.on('connect', connectCb); + client.on('stateChange', stateChangeCb); + + await client.connect(); + + expect(connectCb).toHaveBeenCalledTimes(1); + expect(stateChangeCb).toHaveBeenCalled(); + + client.off('connect'); + client.disconnect(); + }); + + it('should reject when sending without connection', async () => { + const client = new WebSocketClient(wsConfig); + await expect(client.read('users')).rejects.toThrow('WebSocket is not connected'); + }); + + it('should handle pong messages without error', async () => { + const client = new WebSocketClient(wsConfig); + await client.connect(); + + // Should not throw + mockWsInstance!.simulateMessage({ type: 'pong' }); + + client.disconnect(); + }); + + it('should handle malformed messages gracefully', async () => { + const client = new WebSocketClient({ ...wsConfig, debug: false }); + await client.connect(); + + // Simulate non-JSON message + mockWsInstance!.onmessage?.({ data: 'not-json' } as any); + + client.disconnect(); + }); +}); + +describe('getWebSocketClient singleton', () => { + it('returns same instance for same url', () => { + const a = getWebSocketClient({ url: 'ws://ws-singleton:8080' }); + const b = getWebSocketClient({ url: 'ws://ws-singleton:8080' }); + expect(a).toBe(b); + }); + + it('returns different instances for different urls', () => { + const a = getWebSocketClient({ url: 'ws://ws-singleton-a:8080' }); + const b = getWebSocketClient({ url: 'ws://ws-singleton-b:8080' }); + expect(a).not.toBe(b); + }); +}); diff --git a/resolvespec-js/src/api.ts b/resolvespec-js/src/api.ts deleted file mode 100644 index 9c548bd..0000000 --- a/resolvespec-js/src/api.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { ClientConfig, APIResponse, TableMetadata, Options, RequestBody } from "./types"; - -// Helper functions -const getHeaders = (options?: Record): HeadersInit => { - const headers: HeadersInit = { - 'Content-Type': 'application/json', - }; - - if (options?.token) { - headers['Authorization'] = `Bearer ${options.token}`; - } - - return headers; -}; - -const buildUrl = (config: ClientConfig, schema: string, entity: string, id?: string): string => { - let url = `${config.baseUrl}/${schema}/${entity}`; - if (id) { - url += `/${id}`; - } - return url; -}; - -const fetchWithError = async (url: string, options: RequestInit): Promise> => { - try { - const response = await fetch(url, options); - const data = await response.json(); - - if (!response.ok) { - throw new Error(data.error?.message || 'An error occurred'); - } - - return data; - } catch (error) { - throw error; - } -}; - -// API Functions -export const getMetadata = async ( - config: ClientConfig, - schema: string, - entity: string -): Promise> => { - const url = buildUrl(config, schema, entity); - return fetchWithError(url, { - method: 'GET', - headers: getHeaders(config), - }); -}; - -export const read = async ( - config: ClientConfig, - schema: string, - entity: string, - id?: string, - options?: Options -): Promise> => { - const url = buildUrl(config, schema, entity, id); - const body: RequestBody = { - operation: 'read', - options, - }; - - return fetchWithError(url, { - method: 'POST', - headers: getHeaders(config), - body: JSON.stringify(body), - }); -}; - -export const create = async ( - config: ClientConfig, - schema: string, - entity: string, - data: any | any[], - options?: Options -): Promise> => { - const url = buildUrl(config, schema, entity); - const body: RequestBody = { - operation: 'create', - data, - options, - }; - - return fetchWithError(url, { - method: 'POST', - headers: getHeaders(config), - body: JSON.stringify(body), - }); -}; - -export const update = async ( - config: ClientConfig, - schema: string, - entity: string, - data: any | any[], - id?: string | string[], - options?: Options -): Promise> => { - const url = buildUrl(config, schema, entity, typeof id === 'string' ? id : undefined); - const body: RequestBody = { - operation: 'update', - id: typeof id === 'string' ? undefined : id, - data, - options, - }; - - return fetchWithError(url, { - method: 'POST', - headers: getHeaders(config), - body: JSON.stringify(body), - }); -}; - -export const deleteEntity = async ( - config: ClientConfig, - schema: string, - entity: string, - id: string -): Promise> => { - const url = buildUrl(config, schema, entity, id); - const body: RequestBody = { - operation: 'delete', - }; - - return fetchWithError(url, { - method: 'POST', - headers: getHeaders(config), - body: JSON.stringify(body), - }); -}; diff --git a/resolvespec-js/src/common/index.ts b/resolvespec-js/src/common/index.ts new file mode 100644 index 0000000..fcb073f --- /dev/null +++ b/resolvespec-js/src/common/index.ts @@ -0,0 +1 @@ +export * from './types'; diff --git a/resolvespec-js/src/common/types.ts b/resolvespec-js/src/common/types.ts new file mode 100644 index 0000000..83628c7 --- /dev/null +++ b/resolvespec-js/src/common/types.ts @@ -0,0 +1,129 @@ +// Types aligned with Go pkg/common/types.go + +export type Operator = + | 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' + | 'like' | 'ilike' | 'in' + | 'contains' | 'startswith' | 'endswith' + | 'between' | 'between_inclusive' + | 'is_null' | 'is_not_null'; + +export type Operation = 'read' | 'create' | 'update' | 'delete'; +export type SortDirection = 'asc' | 'desc' | 'ASC' | 'DESC'; + +export interface Parameter { + name: string; + value: string; + sequence?: number; +} + +export interface PreloadOption { + relation: string; + table_name?: string; + columns?: string[]; + omit_columns?: string[]; + sort?: SortOption[]; + filters?: FilterOption[]; + where?: string; + limit?: number; + offset?: number; + updatable?: boolean; + computed_ql?: Record; + recursive?: boolean; + // Relationship keys + primary_key?: string; + related_key?: string; + foreign_key?: string; + recursive_child_key?: string; + // Custom SQL JOINs + sql_joins?: string[]; + join_aliases?: string[]; +} + +export interface FilterOption { + column: string; + operator: Operator | string; + value: any; + logic_operator?: 'AND' | 'OR'; +} + +export interface SortOption { + column: string; + direction: SortDirection; +} + +export interface CustomOperator { + name: string; + sql: string; +} + +export interface ComputedColumn { + name: string; + expression: string; +} + +export interface Options { + preload?: PreloadOption[]; + columns?: string[]; + omit_columns?: string[]; + filters?: FilterOption[]; + sort?: SortOption[]; + limit?: number; + offset?: number; + customOperators?: CustomOperator[]; + computedColumns?: ComputedColumn[]; + parameters?: Parameter[]; + cursor_forward?: string; + cursor_backward?: string; + fetch_row_number?: string; +} + +export interface RequestBody { + operation: Operation; + id?: number | string | string[]; + data?: any | any[]; + options?: Options; +} + +export interface Metadata { + total: number; + count: number; + filtered: number; + limit: number; + offset: number; + row_number?: number; +} + +export interface APIError { + code: string; + message: string; + details?: any; + detail?: string; +} + +export interface APIResponse { + success: boolean; + data: T; + metadata?: Metadata; + error?: APIError; +} + +export interface Column { + name: string; + type: string; + is_nullable: boolean; + is_primary: boolean; + is_unique: boolean; + has_index: boolean; +} + +export interface TableMetadata { + schema: string; + table: string; + columns: Column[]; + relations: string[]; +} + +export interface ClientConfig { + baseUrl: string; + token?: string; +} diff --git a/resolvespec-js/src/examples.ts b/resolvespec-js/src/examples.ts deleted file mode 100644 index 3c1e014..0000000 --- a/resolvespec-js/src/examples.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { getMetadata, read, create, update, deleteEntity } from "./api"; -import { ClientConfig } from "./types"; - -// Usage Examples -const config: ClientConfig = { - baseUrl: 'http://api.example.com/v1', - token: 'your-token-here' -}; - -// Example usage -const examples = async () => { - // Get metadata - const metadata = await getMetadata(config, 'test', 'employees'); - - - // Read with relations - const employees = await read(config, 'test', 'employees', undefined, { - preload: [ - { - relation: 'department', - columns: ['id', 'name'] - } - ], - filters: [ - { - column: 'status', - operator: 'eq', - value: 'active' - } - ] - }); - - // Create single record - const newEmployee = await create(config, 'test', 'employees', { - first_name: 'John', - last_name: 'Doe', - email: 'john@example.com' - }); - - // Bulk create - const newEmployees = await create(config, 'test', 'employees', [ - { - first_name: 'Jane', - last_name: 'Smith', - email: 'jane@example.com' - }, - { - first_name: 'Bob', - last_name: 'Johnson', - email: 'bob@example.com' - } - ]); - - // Update single record - const updatedEmployee = await update(config, 'test', 'employees', - { status: 'inactive' }, - 'emp123' - ); - - // Bulk update - const updatedEmployees = await update(config, 'test', 'employees', - { department_id: 'dept2' }, - ['emp1', 'emp2', 'emp3'] - ); - - // Delete - await deleteEntity(config, 'test', 'employees', 'emp123'); -}; \ No newline at end of file diff --git a/resolvespec-js/src/headerspec/client.ts b/resolvespec-js/src/headerspec/client.ts new file mode 100644 index 0000000..a1b4289 --- /dev/null +++ b/resolvespec-js/src/headerspec/client.ts @@ -0,0 +1,296 @@ +import type { + ClientConfig, + APIResponse, + Options, + FilterOption, + SortOption, + PreloadOption, + CustomOperator, +} from '../common/types'; + +/** + * Encode a value with base64 and ZIP_ prefix for complex header values. + */ +export function encodeHeaderValue(value: string): string { + if (typeof btoa === 'function') { + return 'ZIP_' + btoa(value); + } + return 'ZIP_' + Buffer.from(value, 'utf-8').toString('base64'); +} + +/** + * Decode a header value that may be base64 encoded with ZIP_ or __ prefix. + */ +export function decodeHeaderValue(value: string): string { + let code = value; + + if (code.startsWith('ZIP_')) { + code = code.slice(4).replace(/[\n\r ]/g, ''); + code = decodeBase64(code); + } else if (code.startsWith('__')) { + code = code.slice(2).replace(/[\n\r ]/g, ''); + code = decodeBase64(code); + } + + // Handle nested encoding + if (code.startsWith('ZIP_') || code.startsWith('__')) { + code = decodeHeaderValue(code); + } + + return code; +} + +function decodeBase64(str: string): string { + if (typeof atob === 'function') { + return atob(str); + } + return Buffer.from(str, 'base64').toString('utf-8'); +} + +/** + * Build HTTP headers from Options, matching Go's restheadspec handler conventions. + * + * Header mapping: + * - X-Select-Fields: comma-separated columns + * - X-Not-Select-Fields: comma-separated omit_columns + * - X-FieldFilter-{col}: exact match (eq) + * - X-SearchOp-{operator}-{col}: AND filter + * - X-SearchOr-{operator}-{col}: OR filter + * - X-Sort: +col (asc), -col (desc) + * - X-Limit, X-Offset: pagination + * - X-Cursor-Forward, X-Cursor-Backward: cursor pagination + * - X-Preload: RelationName:field1,field2 pipe-separated + * - X-Fetch-RowNumber: row number fetch + * - X-CQL-SEL-{col}: computed columns + * - X-Custom-SQL-W: custom operators (AND) + */ +export function buildHeaders(options: Options): Record { + const headers: Record = {}; + + // Column selection + if (options.columns?.length) { + headers['X-Select-Fields'] = options.columns.join(','); + } + + if (options.omit_columns?.length) { + headers['X-Not-Select-Fields'] = options.omit_columns.join(','); + } + + // Filters + if (options.filters?.length) { + for (const filter of options.filters) { + const logicOp = filter.logic_operator ?? 'AND'; + const op = mapOperatorToHeaderOp(filter.operator); + const valueStr = formatFilterValue(filter); + + if (filter.operator === 'eq' && logicOp === 'AND') { + // Simple field filter shorthand + headers[`X-FieldFilter-${filter.column}`] = valueStr; + } else if (logicOp === 'OR') { + headers[`X-SearchOr-${op}-${filter.column}`] = valueStr; + } else { + headers[`X-SearchOp-${op}-${filter.column}`] = valueStr; + } + } + } + + // Sort + if (options.sort?.length) { + const sortParts = options.sort.map((s: SortOption) => { + const dir = s.direction.toUpperCase(); + return dir === 'DESC' ? `-${s.column}` : `+${s.column}`; + }); + headers['X-Sort'] = sortParts.join(','); + } + + // Pagination + if (options.limit !== undefined) { + headers['X-Limit'] = String(options.limit); + } + if (options.offset !== undefined) { + headers['X-Offset'] = String(options.offset); + } + + // Cursor pagination + if (options.cursor_forward) { + headers['X-Cursor-Forward'] = options.cursor_forward; + } + if (options.cursor_backward) { + headers['X-Cursor-Backward'] = options.cursor_backward; + } + + // Preload + if (options.preload?.length) { + const parts = options.preload.map((p: PreloadOption) => { + if (p.columns?.length) { + return `${p.relation}:${p.columns.join(',')}`; + } + return p.relation; + }); + headers['X-Preload'] = parts.join('|'); + } + + // Fetch row number + if (options.fetch_row_number) { + headers['X-Fetch-RowNumber'] = options.fetch_row_number; + } + + // Computed columns + if (options.computedColumns?.length) { + for (const cc of options.computedColumns) { + headers[`X-CQL-SEL-${cc.name}`] = cc.expression; + } + } + + // Custom operators -> X-Custom-SQL-W + if (options.customOperators?.length) { + const sqlParts = options.customOperators.map((co: CustomOperator) => co.sql); + headers['X-Custom-SQL-W'] = sqlParts.join(' AND '); + } + + return headers; +} + +function mapOperatorToHeaderOp(operator: string): string { + switch (operator) { + case 'eq': return 'equals'; + case 'neq': return 'notequals'; + case 'gt': return 'greaterthan'; + case 'gte': return 'greaterthanorequal'; + case 'lt': return 'lessthan'; + case 'lte': return 'lessthanorequal'; + case 'like': + case 'ilike': + case 'contains': return 'contains'; + case 'startswith': return 'beginswith'; + case 'endswith': return 'endswith'; + case 'in': return 'in'; + case 'between': return 'between'; + case 'between_inclusive': return 'betweeninclusive'; + case 'is_null': return 'empty'; + case 'is_not_null': return 'notempty'; + default: return operator; + } +} + +function formatFilterValue(filter: FilterOption): string { + if (filter.value === null || filter.value === undefined) { + return ''; + } + if (Array.isArray(filter.value)) { + return filter.value.join(','); + } + return String(filter.value); +} + +const instances = new Map(); + +export function getHeaderSpecClient(config: ClientConfig): HeaderSpecClient { + const key = config.baseUrl; + let instance = instances.get(key); + if (!instance) { + instance = new HeaderSpecClient(config); + instances.set(key, instance); + } + return instance; +} + +/** + * HeaderSpec REST client. + * Sends query options via HTTP headers instead of request body, matching the Go restheadspec handler. + * + * HTTP methods: GET=read, POST=create, PUT=update, DELETE=delete + */ +export class HeaderSpecClient { + private config: ClientConfig; + + constructor(config: ClientConfig) { + this.config = config; + } + + private buildUrl(schema: string, entity: string, id?: string): string { + let url = `${this.config.baseUrl}/${schema}/${entity}`; + if (id) { + url += `/${id}`; + } + return url; + } + + private baseHeaders(): Record { + const headers: Record = { + 'Content-Type': 'application/json', + }; + if (this.config.token) { + headers['Authorization'] = `Bearer ${this.config.token}`; + } + return headers; + } + + private async fetchWithError(url: string, init: RequestInit): Promise> { + const response = await fetch(url, init); + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error?.message || 'An error occurred'); + } + + return data; + } + + async read( + schema: string, + entity: string, + id?: string, + options?: Options + ): Promise> { + const url = this.buildUrl(schema, entity, id); + const optHeaders = options ? buildHeaders(options) : {}; + return this.fetchWithError(url, { + method: 'GET', + headers: { ...this.baseHeaders(), ...optHeaders }, + }); + } + + async create( + schema: string, + entity: string, + data: any, + options?: Options + ): Promise> { + const url = this.buildUrl(schema, entity); + const optHeaders = options ? buildHeaders(options) : {}; + return this.fetchWithError(url, { + method: 'POST', + headers: { ...this.baseHeaders(), ...optHeaders }, + body: JSON.stringify(data), + }); + } + + async update( + schema: string, + entity: string, + id: string, + data: any, + options?: Options + ): Promise> { + const url = this.buildUrl(schema, entity, id); + const optHeaders = options ? buildHeaders(options) : {}; + return this.fetchWithError(url, { + method: 'PUT', + headers: { ...this.baseHeaders(), ...optHeaders }, + body: JSON.stringify(data), + }); + } + + async delete( + schema: string, + entity: string, + id: string + ): Promise> { + const url = this.buildUrl(schema, entity, id); + return this.fetchWithError(url, { + method: 'DELETE', + headers: this.baseHeaders(), + }); + } +} diff --git a/resolvespec-js/src/headerspec/index.ts b/resolvespec-js/src/headerspec/index.ts new file mode 100644 index 0000000..0a3c4f8 --- /dev/null +++ b/resolvespec-js/src/headerspec/index.ts @@ -0,0 +1,7 @@ +export { + HeaderSpecClient, + getHeaderSpecClient, + buildHeaders, + encodeHeaderValue, + decodeHeaderValue, +} from './client'; diff --git a/resolvespec-js/src/index.ts b/resolvespec-js/src/index.ts index 1a9aa90..bd9906d 100644 --- a/resolvespec-js/src/index.ts +++ b/resolvespec-js/src/index.ts @@ -1,7 +1,11 @@ -// Types -export * from './types'; -export * from './websocket-types'; +// Common types +export * from './common'; -// WebSocket Client -export { WebSocketClient } from './websocket-client'; -export type { WebSocketClient as default } from './websocket-client'; +// REST client (ResolveSpec) +export * from './resolvespec'; + +// WebSocket client +export * from './websocketspec'; + +// HeaderSpec client +export * from './headerspec'; diff --git a/resolvespec-js/src/resolvespec/client.ts b/resolvespec-js/src/resolvespec/client.ts new file mode 100644 index 0000000..4dcbe5c --- /dev/null +++ b/resolvespec-js/src/resolvespec/client.ts @@ -0,0 +1,141 @@ +import type { ClientConfig, APIResponse, TableMetadata, Options, RequestBody } from '../common/types'; + +const instances = new Map(); + +export function getResolveSpecClient(config: ClientConfig): ResolveSpecClient { + const key = config.baseUrl; + let instance = instances.get(key); + if (!instance) { + instance = new ResolveSpecClient(config); + instances.set(key, instance); + } + return instance; +} + +export class ResolveSpecClient { + private config: ClientConfig; + + constructor(config: ClientConfig) { + this.config = config; + } + + private buildUrl(schema: string, entity: string, id?: string): string { + let url = `${this.config.baseUrl}/${schema}/${entity}`; + if (id) { + url += `/${id}`; + } + return url; + } + + private baseHeaders(): HeadersInit { + const headers: Record = { + 'Content-Type': 'application/json', + }; + + if (this.config.token) { + headers['Authorization'] = `Bearer ${this.config.token}`; + } + + return headers; + } + + private async fetchWithError(url: string, options: RequestInit): Promise> { + const response = await fetch(url, options); + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error?.message || 'An error occurred'); + } + + return data; + } + + async getMetadata(schema: string, entity: string): Promise> { + const url = this.buildUrl(schema, entity); + return this.fetchWithError(url, { + method: 'GET', + headers: this.baseHeaders(), + }); + } + + async read( + schema: string, + entity: string, + id?: number | string | string[], + options?: Options + ): Promise> { + const urlId = typeof id === 'number' || typeof id === 'string' ? String(id) : undefined; + const url = this.buildUrl(schema, entity, urlId); + const body: RequestBody = { + operation: 'read', + id: Array.isArray(id) ? id : undefined, + options, + }; + + return this.fetchWithError(url, { + method: 'POST', + headers: this.baseHeaders(), + body: JSON.stringify(body), + }); + } + + async create( + schema: string, + entity: string, + data: any | any[], + options?: Options + ): Promise> { + const url = this.buildUrl(schema, entity); + const body: RequestBody = { + operation: 'create', + data, + options, + }; + + return this.fetchWithError(url, { + method: 'POST', + headers: this.baseHeaders(), + body: JSON.stringify(body), + }); + } + + async update( + schema: string, + entity: string, + data: any | any[], + id?: number | string | string[], + options?: Options + ): Promise> { + const urlId = typeof id === 'number' || typeof id === 'string' ? String(id) : undefined; + const url = this.buildUrl(schema, entity, urlId); + const body: RequestBody = { + operation: 'update', + id: Array.isArray(id) ? id : undefined, + data, + options, + }; + + return this.fetchWithError(url, { + method: 'POST', + headers: this.baseHeaders(), + body: JSON.stringify(body), + }); + } + + async delete( + schema: string, + entity: string, + id: number | string + ): Promise> { + const url = this.buildUrl(schema, entity, String(id)); + const body: RequestBody = { + operation: 'delete', + }; + + return this.fetchWithError(url, { + method: 'POST', + headers: this.baseHeaders(), + body: JSON.stringify(body), + }); + } +} diff --git a/resolvespec-js/src/resolvespec/index.ts b/resolvespec-js/src/resolvespec/index.ts new file mode 100644 index 0000000..75a8922 --- /dev/null +++ b/resolvespec-js/src/resolvespec/index.ts @@ -0,0 +1 @@ +export { ResolveSpecClient, getResolveSpecClient } from './client'; diff --git a/resolvespec-js/src/types.ts b/resolvespec-js/src/types.ts deleted file mode 100644 index 18ae382..0000000 --- a/resolvespec-js/src/types.ts +++ /dev/null @@ -1,86 +0,0 @@ -// Types -export type Operator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ilike' | 'in'; -export type Operation = 'read' | 'create' | 'update' | 'delete'; -export type SortDirection = 'asc' | 'desc'; - -export interface PreloadOption { - relation: string; - columns?: string[]; - filters?: FilterOption[]; -} - -export interface FilterOption { - column: string; - operator: Operator; - value: any; -} - -export interface SortOption { - column: string; - direction: SortDirection; -} - -export interface CustomOperator { - name: string; - sql: string; -} - -export interface ComputedColumn { - name: string; - expression: string; -} - -export interface Options { - preload?: PreloadOption[]; - columns?: string[]; - filters?: FilterOption[]; - sort?: SortOption[]; - limit?: number; - offset?: number; - customOperators?: CustomOperator[]; - computedColumns?: ComputedColumn[]; -} - -export interface RequestBody { - operation: Operation; - id?: string | string[]; - data?: any | any[]; - options?: Options; -} - -export interface APIResponse { - success: boolean; - data: T; - metadata?: { - total: number; - filtered: number; - limit: number; - offset: number; - }; - error?: { - code: string; - message: string; - details?: any; - }; -} - -export interface Column { - name: string; - type: string; - is_nullable: boolean; - is_primary: boolean; - is_unique: boolean; - has_index: boolean; -} - -export interface TableMetadata { - schema: string; - table: string; - columns: Column[]; - relations: string[]; -} - -export interface ClientConfig { - baseUrl: string; - token?: string; -} diff --git a/resolvespec-js/src/websocket-examples.ts b/resolvespec-js/src/websocket-examples.ts deleted file mode 100644 index 576603d..0000000 --- a/resolvespec-js/src/websocket-examples.ts +++ /dev/null @@ -1,427 +0,0 @@ -import { WebSocketClient } from './websocket-client'; -import type { WSNotificationMessage } from './websocket-types'; - -/** - * Example 1: Basic Usage - */ -export async function basicUsageExample() { - // Create client - const client = new WebSocketClient({ - url: 'ws://localhost:8080/ws', - reconnect: true, - debug: true - }); - - // Connect - await client.connect(); - - // Read users - const users = await client.read('users', { - schema: 'public', - filters: [ - { column: 'status', operator: 'eq', value: 'active' } - ], - limit: 10, - sort: [ - { column: 'name', direction: 'asc' } - ] - }); - - console.log('Users:', users); - - // Create a user - const newUser = await client.create('users', { - name: 'John Doe', - email: 'john@example.com', - status: 'active' - }, { schema: 'public' }); - - console.log('Created user:', newUser); - - // Update user - const updatedUser = await client.update('users', '123', { - name: 'John Updated' - }, { schema: 'public' }); - - console.log('Updated user:', updatedUser); - - // Delete user - await client.delete('users', '123', { schema: 'public' }); - - // Disconnect - client.disconnect(); -} - -/** - * Example 2: Real-time Subscriptions - */ -export async function subscriptionExample() { - const client = new WebSocketClient({ - url: 'ws://localhost:8080/ws', - debug: true - }); - - await client.connect(); - - // Subscribe to user changes - const subscriptionId = await client.subscribe( - 'users', - (notification: WSNotificationMessage) => { - console.log('User changed:', notification.operation, notification.data); - - switch (notification.operation) { - case 'create': - console.log('New user created:', notification.data); - break; - case 'update': - console.log('User updated:', notification.data); - break; - case 'delete': - console.log('User deleted:', notification.data); - break; - } - }, - { - schema: 'public', - filters: [ - { column: 'status', operator: 'eq', value: 'active' } - ] - } - ); - - console.log('Subscribed with ID:', subscriptionId); - - // Later: unsubscribe - setTimeout(async () => { - await client.unsubscribe(subscriptionId); - console.log('Unsubscribed'); - client.disconnect(); - }, 60000); -} - -/** - * Example 3: Event Handling - */ -export async function eventHandlingExample() { - const client = new WebSocketClient({ - url: 'ws://localhost:8080/ws' - }); - - // Listen to connection events - client.on('connect', () => { - console.log('Connected!'); - }); - - client.on('disconnect', (event) => { - console.log('Disconnected:', event.code, event.reason); - }); - - client.on('error', (error) => { - console.error('WebSocket error:', error); - }); - - client.on('stateChange', (state) => { - console.log('State changed to:', state); - }); - - client.on('message', (message) => { - console.log('Received message:', message); - }); - - await client.connect(); - - // Your operations here... -} - -/** - * Example 4: Multiple Subscriptions - */ -export async function multipleSubscriptionsExample() { - const client = new WebSocketClient({ - url: 'ws://localhost:8080/ws', - debug: true - }); - - await client.connect(); - - // Subscribe to users - const userSubId = await client.subscribe( - 'users', - (notification) => { - console.log('[Users]', notification.operation, notification.data); - }, - { schema: 'public' } - ); - - // Subscribe to posts - const postSubId = await client.subscribe( - 'posts', - (notification) => { - console.log('[Posts]', notification.operation, notification.data); - }, - { - schema: 'public', - filters: [ - { column: 'status', operator: 'eq', value: 'published' } - ] - } - ); - - // Subscribe to comments - const commentSubId = await client.subscribe( - 'comments', - (notification) => { - console.log('[Comments]', notification.operation, notification.data); - }, - { schema: 'public' } - ); - - console.log('Active subscriptions:', client.getSubscriptions()); - - // Clean up after 60 seconds - setTimeout(async () => { - await client.unsubscribe(userSubId); - await client.unsubscribe(postSubId); - await client.unsubscribe(commentSubId); - client.disconnect(); - }, 60000); -} - -/** - * Example 5: Advanced Queries - */ -export async function advancedQueriesExample() { - const client = new WebSocketClient({ - url: 'ws://localhost:8080/ws' - }); - - await client.connect(); - - // Complex query with filters, sorting, pagination, and preloading - const posts = await client.read('posts', { - schema: 'public', - filters: [ - { column: 'status', operator: 'eq', value: 'published' }, - { column: 'views', operator: 'gte', value: 100 } - ], - columns: ['id', 'title', 'content', 'user_id', 'created_at'], - sort: [ - { column: 'created_at', direction: 'desc' }, - { column: 'views', direction: 'desc' } - ], - preload: [ - { - relation: 'user', - columns: ['id', 'name', 'email'] - }, - { - relation: 'comments', - columns: ['id', 'content', 'user_id'], - filters: [ - { column: 'status', operator: 'eq', value: 'approved' } - ] - } - ], - limit: 20, - offset: 0 - }); - - console.log('Posts:', posts); - - // Get single record by ID - const post = await client.read('posts', { - schema: 'public', - record_id: '123' - }); - - console.log('Single post:', post); - - client.disconnect(); -} - -/** - * Example 6: Error Handling - */ -export async function errorHandlingExample() { - const client = new WebSocketClient({ - url: 'ws://localhost:8080/ws', - reconnect: true, - maxReconnectAttempts: 5 - }); - - client.on('error', (error) => { - console.error('Connection error:', error); - }); - - client.on('stateChange', (state) => { - console.log('Connection state:', state); - }); - - try { - await client.connect(); - - try { - // Try to read non-existent entity - await client.read('nonexistent', { schema: 'public' }); - } catch (error) { - console.error('Read error:', error); - } - - try { - // Try to create invalid record - await client.create('users', { - // Missing required fields - }, { schema: 'public' }); - } catch (error) { - console.error('Create error:', error); - } - - } catch (error) { - console.error('Connection failed:', error); - } finally { - client.disconnect(); - } -} - -/** - * Example 7: React Integration - */ -export function reactIntegrationExample() { - const exampleCode = ` -import { useEffect, useState } from 'react'; -import { WebSocketClient } from '@warkypublic/resolvespec-js'; - -export function useWebSocket(url: string) { - const [client] = useState(() => new WebSocketClient({ url })); - const [isConnected, setIsConnected] = useState(false); - - useEffect(() => { - client.on('connect', () => setIsConnected(true)); - client.on('disconnect', () => setIsConnected(false)); - - client.connect(); - - return () => { - client.disconnect(); - }; - }, [client]); - - return { client, isConnected }; -} - -export function UsersComponent() { - const { client, isConnected } = useWebSocket('ws://localhost:8080/ws'); - const [users, setUsers] = useState([]); - - useEffect(() => { - if (!isConnected) return; - - // Subscribe to user changes - const subscribeToUsers = async () => { - const subId = await client.subscribe('users', (notification) => { - if (notification.operation === 'create') { - setUsers(prev => [...prev, notification.data]); - } else if (notification.operation === 'update') { - setUsers(prev => prev.map(u => - u.id === notification.data.id ? notification.data : u - )); - } else if (notification.operation === 'delete') { - setUsers(prev => prev.filter(u => u.id !== notification.data.id)); - } - }, { schema: 'public' }); - - // Load initial users - const initialUsers = await client.read('users', { - schema: 'public', - filters: [{ column: 'status', operator: 'eq', value: 'active' }] - }); - setUsers(initialUsers); - - return () => client.unsubscribe(subId); - }; - - subscribeToUsers(); - }, [client, isConnected]); - - const createUser = async (name: string, email: string) => { - await client.create('users', { name, email, status: 'active' }, { - schema: 'public' - }); - }; - - return ( -
-

Users ({users.length})

- {isConnected ? '🟢 Connected' : '🔴 Disconnected'} - {/* Render users... */} -
- ); -} -`; - - console.log(exampleCode); -} - -/** - * Example 8: TypeScript with Typed Models - */ -export async function typedModelsExample() { - // Define your models - interface User { - id: number; - name: string; - email: string; - status: 'active' | 'inactive'; - created_at: string; - } - - interface Post { - id: number; - title: string; - content: string; - user_id: number; - status: 'draft' | 'published'; - views: number; - user?: User; - } - - const client = new WebSocketClient({ - url: 'ws://localhost:8080/ws' - }); - - await client.connect(); - - // Type-safe operations - const users = await client.read('users', { - schema: 'public', - filters: [{ column: 'status', operator: 'eq', value: 'active' }] - }); - - const newUser = await client.create('users', { - name: 'Alice', - email: 'alice@example.com', - status: 'active' - }, { schema: 'public' }); - - const posts = await client.read('posts', { - schema: 'public', - preload: [ - { - relation: 'user', - columns: ['id', 'name', 'email'] - } - ] - }); - - // Type-safe subscriptions - await client.subscribe( - 'users', - (notification) => { - const user = notification.data as User; - console.log('User changed:', user.name, user.email); - }, - { schema: 'public' } - ); - - client.disconnect(); -} diff --git a/resolvespec-js/src/websocket-client.ts b/resolvespec-js/src/websocketspec/client.ts similarity index 90% rename from resolvespec-js/src/websocket-client.ts rename to resolvespec-js/src/websocketspec/client.ts index 6482cc3..8a52af5 100644 --- a/resolvespec-js/src/websocket-client.ts +++ b/resolvespec-js/src/websocketspec/client.ts @@ -8,10 +8,22 @@ import type { WSOperation, WSOptions, Subscription, - SubscriptionOptions, ConnectionState, WebSocketClientEvents -} from './websocket-types'; +} from './types'; +import type { FilterOption, SortOption, PreloadOption } from '../common/types'; + +const instances = new Map(); + +export function getWebSocketClient(config: WebSocketClientConfig): WebSocketClient { + const key = config.url; + let instance = instances.get(key); + if (!instance) { + instance = new WebSocketClient(config); + instances.set(key, instance); + } + return instance; +} export class WebSocketClient { private ws: WebSocket | null = null; @@ -36,9 +48,6 @@ export class WebSocketClient { }; } - /** - * Connect to WebSocket server - */ async connect(): Promise { if (this.ws?.readyState === WebSocket.OPEN) { this.log('Already connected'); @@ -78,7 +87,6 @@ export class WebSocketClient { this.setState('disconnected'); this.emit('disconnect', event); - // Attempt reconnection if enabled and not manually closed if (this.config.reconnect && !this.isManualClose && this.reconnectAttempts < this.config.maxReconnectAttempts) { this.reconnectAttempts++; this.log(`Reconnection attempt ${this.reconnectAttempts}/${this.config.maxReconnectAttempts}`); @@ -97,9 +105,6 @@ export class WebSocketClient { }); } - /** - * Disconnect from WebSocket server - */ disconnect(): void { this.isManualClose = true; @@ -120,9 +125,6 @@ export class WebSocketClient { this.messageHandlers.clear(); } - /** - * Send a CRUD request and wait for response - */ async request( operation: WSOperation, entity: string, @@ -148,7 +150,6 @@ export class WebSocketClient { }; return new Promise((resolve, reject) => { - // Set up response handler this.messageHandlers.set(id, (response: WSResponseMessage) => { if (response.success) { resolve(response.data); @@ -157,10 +158,8 @@ export class WebSocketClient { } }); - // Send message this.send(message); - // Timeout after 30 seconds setTimeout(() => { if (this.messageHandlers.has(id)) { this.messageHandlers.delete(id); @@ -170,16 +169,13 @@ export class WebSocketClient { }); } - /** - * Read records - */ async read(entity: string, options?: { schema?: string; record_id?: string; - filters?: import('./types').FilterOption[]; + filters?: FilterOption[]; columns?: string[]; - sort?: import('./types').SortOption[]; - preload?: import('./types').PreloadOption[]; + sort?: SortOption[]; + preload?: PreloadOption[]; limit?: number; offset?: number; }): Promise { @@ -197,9 +193,6 @@ export class WebSocketClient { }); } - /** - * Create a record - */ async create(entity: string, data: any, options?: { schema?: string; }): Promise { @@ -209,9 +202,6 @@ export class WebSocketClient { }); } - /** - * Update a record - */ async update(entity: string, id: string, data: any, options?: { schema?: string; }): Promise { @@ -222,9 +212,6 @@ export class WebSocketClient { }); } - /** - * Delete a record - */ async delete(entity: string, id: string, options?: { schema?: string; }): Promise { @@ -234,9 +221,6 @@ export class WebSocketClient { }); } - /** - * Get metadata for an entity - */ async meta(entity: string, options?: { schema?: string; }): Promise { @@ -245,15 +229,12 @@ export class WebSocketClient { }); } - /** - * Subscribe to entity changes - */ async subscribe( entity: string, callback: (notification: WSNotificationMessage) => void, options?: { schema?: string; - filters?: import('./types').FilterOption[]; + filters?: FilterOption[]; } ): Promise { this.ensureConnected(); @@ -275,7 +256,6 @@ export class WebSocketClient { if (response.success && response.data?.subscription_id) { const subscriptionId = response.data.subscription_id; - // Store subscription this.subscriptions.set(subscriptionId, { id: subscriptionId, entity, @@ -293,7 +273,6 @@ export class WebSocketClient { this.send(message); - // Timeout setTimeout(() => { if (this.messageHandlers.has(id)) { this.messageHandlers.delete(id); @@ -303,9 +282,6 @@ export class WebSocketClient { }); } - /** - * Unsubscribe from entity changes - */ async unsubscribe(subscriptionId: string): Promise { this.ensureConnected(); @@ -330,7 +306,6 @@ export class WebSocketClient { this.send(message); - // Timeout setTimeout(() => { if (this.messageHandlers.has(id)) { this.messageHandlers.delete(id); @@ -340,37 +315,22 @@ export class WebSocketClient { }); } - /** - * Get list of active subscriptions - */ getSubscriptions(): Subscription[] { return Array.from(this.subscriptions.values()); } - /** - * Get connection state - */ getState(): ConnectionState { return this.state; } - /** - * Check if connected - */ isConnected(): boolean { return this.ws?.readyState === WebSocket.OPEN; } - /** - * Add event listener - */ on(event: K, callback: WebSocketClientEvents[K]): void { this.eventListeners[event] = callback as any; } - /** - * Remove event listener - */ off(event: K): void { delete this.eventListeners[event]; } @@ -384,7 +344,6 @@ export class WebSocketClient { this.emit('message', message); - // Handle different message types switch (message.type) { case 'response': this.handleResponse(message as WSResponseMessage); @@ -395,7 +354,6 @@ export class WebSocketClient { break; case 'pong': - // Heartbeat response break; default: diff --git a/resolvespec-js/src/websocketspec/index.ts b/resolvespec-js/src/websocketspec/index.ts new file mode 100644 index 0000000..9bff184 --- /dev/null +++ b/resolvespec-js/src/websocketspec/index.ts @@ -0,0 +1,2 @@ +export * from './types'; +export { WebSocketClient, getWebSocketClient } from './client'; diff --git a/resolvespec-js/src/websocket-types.ts b/resolvespec-js/src/websocketspec/types.ts similarity index 86% rename from resolvespec-js/src/websocket-types.ts rename to resolvespec-js/src/websocketspec/types.ts index 29fc34d..a8dba7a 100644 --- a/resolvespec-js/src/websocket-types.ts +++ b/resolvespec-js/src/websocketspec/types.ts @@ -1,17 +1,24 @@ +import type { FilterOption, SortOption, PreloadOption, Parameter } from '../common/types'; + +// Re-export common types +export type { FilterOption, SortOption, PreloadOption, Operator, SortDirection } from '../common/types'; + // WebSocket Message Types export type MessageType = 'request' | 'response' | 'notification' | 'subscription' | 'error' | 'ping' | 'pong'; export type WSOperation = 'read' | 'create' | 'update' | 'delete' | 'subscribe' | 'unsubscribe' | 'meta'; -// Re-export common types -export type { FilterOption, SortOption, PreloadOption, Operator, SortDirection } from './types'; - export interface WSOptions { - filters?: import('./types').FilterOption[]; + filters?: FilterOption[]; columns?: string[]; - preload?: import('./types').PreloadOption[]; - sort?: import('./types').SortOption[]; + omit_columns?: string[]; + preload?: PreloadOption[]; + sort?: SortOption[]; limit?: number; offset?: number; + parameters?: Parameter[]; + cursor_forward?: string; + cursor_backward?: string; + fetch_row_number?: string; } export interface WSMessage { @@ -78,7 +85,7 @@ export interface WSSubscriptionMessage { } export interface SubscriptionOptions { - filters?: import('./types').FilterOption[]; + filters?: FilterOption[]; onNotification?: (notification: WSNotificationMessage) => void; } diff --git a/resolvespec-js/tsconfig.json b/resolvespec-js/tsconfig.json new file mode 100644 index 0000000..d62033b --- /dev/null +++ b/resolvespec-js/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "dist", + "rootDir": "src", + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "lib": ["ES2020", "DOM"] + }, + "include": ["src"], + "exclude": ["node_modules", "dist", "src/__tests__"] +} diff --git a/resolvespec-js/vite.config.ts b/resolvespec-js/vite.config.ts new file mode 100644 index 0000000..63802f1 --- /dev/null +++ b/resolvespec-js/vite.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from 'vite'; +import dts from 'vite-plugin-dts'; +import { resolve } from 'path'; + +export default defineConfig({ + plugins: [ + dts({ rollupTypes: true }), + ], + build: { + lib: { + entry: resolve(__dirname, 'src/index.ts'), + name: 'ResolveSpec', + formats: ['es', 'cjs'], + fileName: (format) => `index.${format === 'es' ? 'js' : 'cjs'}`, + }, + rollupOptions: { + external: ['uuid', 'semver'], + }, + }, +});