A generic websocket connection with Zod schema validation and on message execution.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

init repo

Eric Vincent b8a94910

+3121
+9
.gitignore
··· 1 + node_modules/ 2 + dist/ 3 + *.log 4 + .DS_Store 5 + .env 6 + .env.* 7 + !.env.example 8 + coverage/ 9 + *.tsbuildinfo
+11
.prettierrc
··· 1 + { 2 + "semi": true, 3 + "trailingComma": "es5", 4 + "singleQuote": false, 5 + "printWidth": 100, 6 + "tabWidth": 2, 7 + "useTabs": false, 8 + "bracketSpacing": true, 9 + "arrowParens": "avoid", 10 + "endOfLine": "lf" 11 + }
+207
README.md
··· 1 + # wah 2 + 3 + Generic WebSocket action handler for TypeScript. Connect to any WebSocket, define message schemas with [Zod](https://zod.dev), and dispatch to typed handlers. 4 + 5 + ## Features 6 + 7 + - **Schema-matched handlers** — Register Zod schemas with handler functions. Incoming messages are validated at runtime, and all matching handlers are invoked with fully typed data. 8 + - **Multi-service failover** — Provide multiple WebSocket URLs. On connection failure, wah cycles through them with exponential backoff. 9 + - **Dynamic query parameters** — Update URL query parameters at runtime. The connection gracefully reconnects with the new URL. 10 + - **Bidirectional communication** — Handlers receive a `send()` function to reply through the same WebSocket. 11 + - **Error isolation** — Handler errors are emitted as events, never crash the connection. 12 + - **Configurable logging** — Built-in logger with log levels, or bring your own. 13 + 14 + ## Installation 15 + 16 + ```bash 17 + pnpm add wah 18 + ``` 19 + 20 + ## Quick Start 21 + 22 + ```typescript 23 + import { WebSocketClient, LogLevel } from "wah"; 24 + import { z } from "zod"; 25 + 26 + // Define message schemas 27 + const tradeSchema = z.object({ 28 + type: z.literal("trade"), 29 + symbol: z.string(), 30 + price: z.number(), 31 + volume: z.number(), 32 + }); 33 + 34 + const systemSchema = z.object({ 35 + type: z.literal("system"), 36 + code: z.number(), 37 + message: z.string(), 38 + }); 39 + 40 + // Create client 41 + const client = new WebSocketClient({ 42 + service: "wss://stream.example.com/v1", 43 + queryParams: { apiKey: "abc123", symbols: "BTC,ETH" }, 44 + logger: { enabled: true, level: LogLevel.DEBUG }, 45 + }); 46 + 47 + // Register handlers — data is fully typed via z.infer 48 + client.handle(tradeSchema, async ({ data, send }) => { 49 + console.log(`${data.symbol}: $${data.price} (vol: ${data.volume})`); 50 + send({ type: "ack", symbol: data.symbol }); 51 + }); 52 + 53 + client.handle(systemSchema, ({ data }) => { 54 + console.log(`System [${data.code}]: ${data.message}`); 55 + }); 56 + 57 + // Subscribe to events 58 + client.on("open", () => console.log("Connected")); 59 + client.on("close", info => console.log("Disconnected", info)); 60 + client.on("error", err => console.error("Error:", err)); 61 + client.on("reconnecting", info => console.log("Reconnecting:", info)); 62 + 63 + // Connect 64 + client.connect(); 65 + 66 + // Update query params (triggers reconnect with new URL) 67 + client.updateParams({ symbols: "BTC,ETH,SOL" }); 68 + 69 + // Send data 70 + client.send({ action: "subscribe", channel: "orderbook" }); 71 + 72 + // Close 73 + client.close(); 74 + ``` 75 + 76 + ## API Reference 77 + 78 + ### `WebSocketClient` 79 + 80 + #### Constructor 81 + 82 + ```typescript 83 + new WebSocketClient(options: WebSocketClientOptions) 84 + ``` 85 + 86 + **Options:** 87 + 88 + | Option | Type | Default | Description | 89 + |---|---|---|---| 90 + | `service` | `string \| string[]` | — | WebSocket URL(s). Multiple URLs enable failover. | 91 + | `queryParams` | `Record<string, string \| number \| boolean>` | `{}` | Query parameters appended to the URL. | 92 + | `reconnect.initialDelay` | `number` | `5000` | Base delay (ms) before first reconnection attempt. | 93 + | `reconnect.maxDelay` | `number` | `30000` | Maximum delay (ms) between attempts. | 94 + | `reconnect.backoffFactor` | `number` | `1.5` | Multiplier applied after each failed attempt. | 95 + | `reconnect.maxAttempts` | `number` | `3` | Max attempts per service before switching. | 96 + | `reconnect.maxServiceCycles` | `number` | `2` | Max full cycles through all services. | 97 + | `pingInterval` | `number` | `10000` | Heartbeat ping interval (ms). | 98 + | `logger.enabled` | `boolean` | `true` | Enable/disable logging. | 99 + | `logger.level` | `LogLevel` | `INFO` | Minimum log level. | 100 + | `logger.custom` | `LoggerInterface` | — | Custom logger implementation. | 101 + 102 + #### Methods 103 + 104 + **`handle<T>(schema: ZodSchema<T>, handler: MessageHandler<T>): this`** 105 + 106 + Registers a handler for messages matching the schema. Returns `this` for chaining. 107 + 108 + **`connect(): void`** 109 + 110 + Opens the WebSocket connection. 111 + 112 + **`close(): void`** 113 + 114 + Closes the connection and stops reconnection. 115 + 116 + **`send(data: unknown): boolean`** 117 + 118 + Sends data through the WebSocket. Objects are JSON-serialized. Returns `true` if sent. 119 + 120 + **`updateParams(params: Record<string, string | number | boolean>): void`** 121 + 122 + Merges new query parameters and reconnects. 123 + 124 + **`getConnectionInfo(): ConnectionInfo`** 125 + 126 + Returns a snapshot of the current connection state. 127 + 128 + #### Events 129 + 130 + | Event | Payload | Description | 131 + |---|---|---| 132 + | `"open"` | — | Connection established. | 133 + | `"close"` | `{ code, reason }` | Connection closed. | 134 + | `"error"` | `Error \| HandlerError` | Connection error or handler error. | 135 + | `"reconnecting"` | `{ attempt, maxAttempts, delay, service }` | About to reconnect. | 136 + | `"serviceSwitched"` | `{ from, to, cycle }` | Failed over to a different service URL. | 137 + 138 + ### `HandlerContext<T>` 139 + 140 + Passed to every matched handler: 141 + 142 + | Property | Type | Description | 143 + |---|---|---| 144 + | `data` | `T` | Validated, typed message data. | 145 + | `rawData` | `string` | Original raw message string. | 146 + | `send` | `(data: unknown) => boolean` | Send data back through the WebSocket. | 147 + | `connection` | `ConnectionInfo` | Read-only connection state snapshot. | 148 + 149 + ### `LogLevel` 150 + 151 + ```typescript 152 + enum LogLevel { 153 + DEBUG = 0, 154 + INFO = 1, 155 + WARN = 2, 156 + ERROR = 3, 157 + } 158 + ``` 159 + 160 + ## Multi-Service Failover 161 + 162 + When multiple service URLs are provided, the reconnection strategy works as follows: 163 + 164 + 1. Try to reconnect to the current service up to `maxAttempts` times with exponential backoff 165 + 2. Switch to the next service URL, reset attempt counter 166 + 3. Cycle through all services up to `maxServiceCycles` times 167 + 4. Give up after all cycles are exhausted 168 + 169 + ```typescript 170 + const client = new WebSocketClient({ 171 + service: [ 172 + "wss://primary.example.com/ws", 173 + "wss://secondary.example.com/ws", 174 + "wss://fallback.example.com/ws", 175 + ], 176 + reconnect: { 177 + maxAttempts: 3, 178 + initialDelay: 2000, 179 + backoffFactor: 2, 180 + maxServiceCycles: 3, 181 + }, 182 + }); 183 + ``` 184 + 185 + ## Custom Logger 186 + 187 + Replace the built-in console logger with your own implementation: 188 + 189 + ```typescript 190 + import { WebSocketClient, LoggerInterface } from "wah"; 191 + 192 + const myLogger: LoggerInterface = { 193 + debug: (msg, ctx) => myLoggingService.log("debug", msg, ctx), 194 + info: (msg, ctx) => myLoggingService.log("info", msg, ctx), 195 + warn: (msg, ctx) => myLoggingService.log("warn", msg, ctx), 196 + error: (msg, ctx) => myLoggingService.log("error", msg, ctx), 197 + }; 198 + 199 + const client = new WebSocketClient({ 200 + service: "wss://example.com/ws", 201 + logger: { custom: myLogger }, 202 + }); 203 + ``` 204 + 205 + ## License 206 + 207 + MIT
+30
eslint.config.mjs
··· 1 + import tseslint from "@typescript-eslint/eslint-plugin"; 2 + import tsparser from "@typescript-eslint/parser"; 3 + import prettier from "eslint-config-prettier"; 4 + 5 + export default [ 6 + { 7 + files: ["src/**/*.ts"], 8 + languageOptions: { 9 + parser: tsparser, 10 + parserOptions: { 11 + ecmaVersion: 2022, 12 + sourceType: "module", 13 + }, 14 + }, 15 + plugins: { 16 + "@typescript-eslint": tseslint, 17 + }, 18 + rules: { 19 + ...tseslint.configs.recommended.rules, 20 + "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], 21 + "@typescript-eslint/no-explicit-any": "warn", 22 + "prefer-const": "error", 23 + "no-var": "error", 24 + }, 25 + }, 26 + { 27 + ignores: ["dist/**", "node_modules/**"], 28 + }, 29 + prettier, 30 + ];
+39
package.json
··· 1 + { 2 + "name": "wah", 3 + "version": "0.1.0", 4 + "description": "Generic WebSocket action handler with Zod-based schema validation and typed message dispatch", 5 + "license": "MIT", 6 + "main": "./dist/index.js", 7 + "module": "./dist/index.mjs", 8 + "types": "./dist/index.d.ts", 9 + "files": [ 10 + "dist" 11 + ], 12 + "scripts": { 13 + "build": "tsup", 14 + "lint": "eslint src", 15 + "lint:fix": "eslint src --fix", 16 + "format": "prettier --write \"src/**/*.ts\"", 17 + "format:check": "prettier --check \"src/**/*.ts\"", 18 + "typecheck": "tsc --noEmit" 19 + }, 20 + "dependencies": { 21 + "ws": "^8.19.0", 22 + "zod": "^3.24.0" 23 + }, 24 + "devDependencies": { 25 + "@types/node": "^22.19.0", 26 + "@types/ws": "^8.5.13", 27 + "@typescript-eslint/eslint-plugin": "^8.55.0", 28 + "@typescript-eslint/parser": "^8.55.0", 29 + "eslint": "^9.39.0", 30 + "eslint-config-prettier": "^9.1.0", 31 + "prettier": "^3.8.0", 32 + "tsup": "^8.5.0", 33 + "typescript": "^5.9.0" 34 + }, 35 + "engines": { 36 + "node": ">=18.0.0" 37 + }, 38 + "packageManager": "pnpm@9.15.0" 39 + }
+1792
pnpm-lock.yaml
··· 1 + lockfileVersion: '9.0' 2 + 3 + settings: 4 + autoInstallPeers: true 5 + excludeLinksFromLockfile: false 6 + 7 + importers: 8 + 9 + .: 10 + dependencies: 11 + ws: 12 + specifier: ^8.19.0 13 + version: 8.19.0 14 + zod: 15 + specifier: ^3.24.0 16 + version: 3.25.76 17 + devDependencies: 18 + '@types/node': 19 + specifier: ^22.19.0 20 + version: 22.19.11 21 + '@types/ws': 22 + specifier: ^8.5.13 23 + version: 8.18.1 24 + '@typescript-eslint/eslint-plugin': 25 + specifier: ^8.55.0 26 + version: 8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3) 27 + '@typescript-eslint/parser': 28 + specifier: ^8.55.0 29 + version: 8.55.0(eslint@9.39.2)(typescript@5.9.3) 30 + eslint: 31 + specifier: ^9.39.0 32 + version: 9.39.2 33 + eslint-config-prettier: 34 + specifier: ^9.1.0 35 + version: 9.1.2(eslint@9.39.2) 36 + prettier: 37 + specifier: ^3.8.0 38 + version: 3.8.1 39 + tsup: 40 + specifier: ^8.5.0 41 + version: 8.5.1(typescript@5.9.3) 42 + typescript: 43 + specifier: ^5.9.0 44 + version: 5.9.3 45 + 46 + packages: 47 + 48 + '@esbuild/aix-ppc64@0.27.3': 49 + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} 50 + engines: {node: '>=18'} 51 + cpu: [ppc64] 52 + os: [aix] 53 + 54 + '@esbuild/android-arm64@0.27.3': 55 + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} 56 + engines: {node: '>=18'} 57 + cpu: [arm64] 58 + os: [android] 59 + 60 + '@esbuild/android-arm@0.27.3': 61 + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} 62 + engines: {node: '>=18'} 63 + cpu: [arm] 64 + os: [android] 65 + 66 + '@esbuild/android-x64@0.27.3': 67 + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} 68 + engines: {node: '>=18'} 69 + cpu: [x64] 70 + os: [android] 71 + 72 + '@esbuild/darwin-arm64@0.27.3': 73 + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} 74 + engines: {node: '>=18'} 75 + cpu: [arm64] 76 + os: [darwin] 77 + 78 + '@esbuild/darwin-x64@0.27.3': 79 + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} 80 + engines: {node: '>=18'} 81 + cpu: [x64] 82 + os: [darwin] 83 + 84 + '@esbuild/freebsd-arm64@0.27.3': 85 + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} 86 + engines: {node: '>=18'} 87 + cpu: [arm64] 88 + os: [freebsd] 89 + 90 + '@esbuild/freebsd-x64@0.27.3': 91 + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} 92 + engines: {node: '>=18'} 93 + cpu: [x64] 94 + os: [freebsd] 95 + 96 + '@esbuild/linux-arm64@0.27.3': 97 + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} 98 + engines: {node: '>=18'} 99 + cpu: [arm64] 100 + os: [linux] 101 + 102 + '@esbuild/linux-arm@0.27.3': 103 + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} 104 + engines: {node: '>=18'} 105 + cpu: [arm] 106 + os: [linux] 107 + 108 + '@esbuild/linux-ia32@0.27.3': 109 + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} 110 + engines: {node: '>=18'} 111 + cpu: [ia32] 112 + os: [linux] 113 + 114 + '@esbuild/linux-loong64@0.27.3': 115 + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} 116 + engines: {node: '>=18'} 117 + cpu: [loong64] 118 + os: [linux] 119 + 120 + '@esbuild/linux-mips64el@0.27.3': 121 + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} 122 + engines: {node: '>=18'} 123 + cpu: [mips64el] 124 + os: [linux] 125 + 126 + '@esbuild/linux-ppc64@0.27.3': 127 + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} 128 + engines: {node: '>=18'} 129 + cpu: [ppc64] 130 + os: [linux] 131 + 132 + '@esbuild/linux-riscv64@0.27.3': 133 + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} 134 + engines: {node: '>=18'} 135 + cpu: [riscv64] 136 + os: [linux] 137 + 138 + '@esbuild/linux-s390x@0.27.3': 139 + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} 140 + engines: {node: '>=18'} 141 + cpu: [s390x] 142 + os: [linux] 143 + 144 + '@esbuild/linux-x64@0.27.3': 145 + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} 146 + engines: {node: '>=18'} 147 + cpu: [x64] 148 + os: [linux] 149 + 150 + '@esbuild/netbsd-arm64@0.27.3': 151 + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} 152 + engines: {node: '>=18'} 153 + cpu: [arm64] 154 + os: [netbsd] 155 + 156 + '@esbuild/netbsd-x64@0.27.3': 157 + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} 158 + engines: {node: '>=18'} 159 + cpu: [x64] 160 + os: [netbsd] 161 + 162 + '@esbuild/openbsd-arm64@0.27.3': 163 + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} 164 + engines: {node: '>=18'} 165 + cpu: [arm64] 166 + os: [openbsd] 167 + 168 + '@esbuild/openbsd-x64@0.27.3': 169 + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} 170 + engines: {node: '>=18'} 171 + cpu: [x64] 172 + os: [openbsd] 173 + 174 + '@esbuild/openharmony-arm64@0.27.3': 175 + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} 176 + engines: {node: '>=18'} 177 + cpu: [arm64] 178 + os: [openharmony] 179 + 180 + '@esbuild/sunos-x64@0.27.3': 181 + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} 182 + engines: {node: '>=18'} 183 + cpu: [x64] 184 + os: [sunos] 185 + 186 + '@esbuild/win32-arm64@0.27.3': 187 + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} 188 + engines: {node: '>=18'} 189 + cpu: [arm64] 190 + os: [win32] 191 + 192 + '@esbuild/win32-ia32@0.27.3': 193 + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} 194 + engines: {node: '>=18'} 195 + cpu: [ia32] 196 + os: [win32] 197 + 198 + '@esbuild/win32-x64@0.27.3': 199 + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} 200 + engines: {node: '>=18'} 201 + cpu: [x64] 202 + os: [win32] 203 + 204 + '@eslint-community/eslint-utils@4.9.1': 205 + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} 206 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 207 + peerDependencies: 208 + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 209 + 210 + '@eslint-community/regexpp@4.12.2': 211 + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} 212 + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 213 + 214 + '@eslint/config-array@0.21.1': 215 + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} 216 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 217 + 218 + '@eslint/config-helpers@0.4.2': 219 + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} 220 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 221 + 222 + '@eslint/core@0.17.0': 223 + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} 224 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 225 + 226 + '@eslint/eslintrc@3.3.3': 227 + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} 228 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 229 + 230 + '@eslint/js@9.39.2': 231 + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} 232 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 233 + 234 + '@eslint/object-schema@2.1.7': 235 + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} 236 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 237 + 238 + '@eslint/plugin-kit@0.4.1': 239 + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} 240 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 241 + 242 + '@humanfs/core@0.19.1': 243 + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} 244 + engines: {node: '>=18.18.0'} 245 + 246 + '@humanfs/node@0.16.7': 247 + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} 248 + engines: {node: '>=18.18.0'} 249 + 250 + '@humanwhocodes/module-importer@1.0.1': 251 + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 252 + engines: {node: '>=12.22'} 253 + 254 + '@humanwhocodes/retry@0.4.3': 255 + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} 256 + engines: {node: '>=18.18'} 257 + 258 + '@jridgewell/gen-mapping@0.3.13': 259 + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} 260 + 261 + '@jridgewell/resolve-uri@3.1.2': 262 + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 263 + engines: {node: '>=6.0.0'} 264 + 265 + '@jridgewell/sourcemap-codec@1.5.5': 266 + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} 267 + 268 + '@jridgewell/trace-mapping@0.3.31': 269 + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} 270 + 271 + '@rollup/rollup-android-arm-eabi@4.57.1': 272 + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} 273 + cpu: [arm] 274 + os: [android] 275 + 276 + '@rollup/rollup-android-arm64@4.57.1': 277 + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} 278 + cpu: [arm64] 279 + os: [android] 280 + 281 + '@rollup/rollup-darwin-arm64@4.57.1': 282 + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} 283 + cpu: [arm64] 284 + os: [darwin] 285 + 286 + '@rollup/rollup-darwin-x64@4.57.1': 287 + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} 288 + cpu: [x64] 289 + os: [darwin] 290 + 291 + '@rollup/rollup-freebsd-arm64@4.57.1': 292 + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} 293 + cpu: [arm64] 294 + os: [freebsd] 295 + 296 + '@rollup/rollup-freebsd-x64@4.57.1': 297 + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} 298 + cpu: [x64] 299 + os: [freebsd] 300 + 301 + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': 302 + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} 303 + cpu: [arm] 304 + os: [linux] 305 + 306 + '@rollup/rollup-linux-arm-musleabihf@4.57.1': 307 + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} 308 + cpu: [arm] 309 + os: [linux] 310 + 311 + '@rollup/rollup-linux-arm64-gnu@4.57.1': 312 + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} 313 + cpu: [arm64] 314 + os: [linux] 315 + 316 + '@rollup/rollup-linux-arm64-musl@4.57.1': 317 + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} 318 + cpu: [arm64] 319 + os: [linux] 320 + 321 + '@rollup/rollup-linux-loong64-gnu@4.57.1': 322 + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} 323 + cpu: [loong64] 324 + os: [linux] 325 + 326 + '@rollup/rollup-linux-loong64-musl@4.57.1': 327 + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} 328 + cpu: [loong64] 329 + os: [linux] 330 + 331 + '@rollup/rollup-linux-ppc64-gnu@4.57.1': 332 + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} 333 + cpu: [ppc64] 334 + os: [linux] 335 + 336 + '@rollup/rollup-linux-ppc64-musl@4.57.1': 337 + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} 338 + cpu: [ppc64] 339 + os: [linux] 340 + 341 + '@rollup/rollup-linux-riscv64-gnu@4.57.1': 342 + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} 343 + cpu: [riscv64] 344 + os: [linux] 345 + 346 + '@rollup/rollup-linux-riscv64-musl@4.57.1': 347 + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} 348 + cpu: [riscv64] 349 + os: [linux] 350 + 351 + '@rollup/rollup-linux-s390x-gnu@4.57.1': 352 + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} 353 + cpu: [s390x] 354 + os: [linux] 355 + 356 + '@rollup/rollup-linux-x64-gnu@4.57.1': 357 + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} 358 + cpu: [x64] 359 + os: [linux] 360 + 361 + '@rollup/rollup-linux-x64-musl@4.57.1': 362 + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} 363 + cpu: [x64] 364 + os: [linux] 365 + 366 + '@rollup/rollup-openbsd-x64@4.57.1': 367 + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} 368 + cpu: [x64] 369 + os: [openbsd] 370 + 371 + '@rollup/rollup-openharmony-arm64@4.57.1': 372 + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} 373 + cpu: [arm64] 374 + os: [openharmony] 375 + 376 + '@rollup/rollup-win32-arm64-msvc@4.57.1': 377 + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} 378 + cpu: [arm64] 379 + os: [win32] 380 + 381 + '@rollup/rollup-win32-ia32-msvc@4.57.1': 382 + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} 383 + cpu: [ia32] 384 + os: [win32] 385 + 386 + '@rollup/rollup-win32-x64-gnu@4.57.1': 387 + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} 388 + cpu: [x64] 389 + os: [win32] 390 + 391 + '@rollup/rollup-win32-x64-msvc@4.57.1': 392 + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} 393 + cpu: [x64] 394 + os: [win32] 395 + 396 + '@types/estree@1.0.8': 397 + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} 398 + 399 + '@types/json-schema@7.0.15': 400 + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 401 + 402 + '@types/node@22.19.11': 403 + resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==} 404 + 405 + '@types/ws@8.18.1': 406 + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} 407 + 408 + '@typescript-eslint/eslint-plugin@8.55.0': 409 + resolution: {integrity: sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==} 410 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 411 + peerDependencies: 412 + '@typescript-eslint/parser': ^8.55.0 413 + eslint: ^8.57.0 || ^9.0.0 414 + typescript: '>=4.8.4 <6.0.0' 415 + 416 + '@typescript-eslint/parser@8.55.0': 417 + resolution: {integrity: sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==} 418 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 419 + peerDependencies: 420 + eslint: ^8.57.0 || ^9.0.0 421 + typescript: '>=4.8.4 <6.0.0' 422 + 423 + '@typescript-eslint/project-service@8.55.0': 424 + resolution: {integrity: sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==} 425 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 426 + peerDependencies: 427 + typescript: '>=4.8.4 <6.0.0' 428 + 429 + '@typescript-eslint/scope-manager@8.55.0': 430 + resolution: {integrity: sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==} 431 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 432 + 433 + '@typescript-eslint/tsconfig-utils@8.55.0': 434 + resolution: {integrity: sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==} 435 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 436 + peerDependencies: 437 + typescript: '>=4.8.4 <6.0.0' 438 + 439 + '@typescript-eslint/type-utils@8.55.0': 440 + resolution: {integrity: sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==} 441 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 442 + peerDependencies: 443 + eslint: ^8.57.0 || ^9.0.0 444 + typescript: '>=4.8.4 <6.0.0' 445 + 446 + '@typescript-eslint/types@8.55.0': 447 + resolution: {integrity: sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==} 448 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 449 + 450 + '@typescript-eslint/typescript-estree@8.55.0': 451 + resolution: {integrity: sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==} 452 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 453 + peerDependencies: 454 + typescript: '>=4.8.4 <6.0.0' 455 + 456 + '@typescript-eslint/utils@8.55.0': 457 + resolution: {integrity: sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==} 458 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 459 + peerDependencies: 460 + eslint: ^8.57.0 || ^9.0.0 461 + typescript: '>=4.8.4 <6.0.0' 462 + 463 + '@typescript-eslint/visitor-keys@8.55.0': 464 + resolution: {integrity: sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==} 465 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 466 + 467 + acorn-jsx@5.3.2: 468 + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 469 + peerDependencies: 470 + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 471 + 472 + acorn@8.15.0: 473 + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} 474 + engines: {node: '>=0.4.0'} 475 + hasBin: true 476 + 477 + ajv@6.12.6: 478 + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 479 + 480 + ansi-styles@4.3.0: 481 + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 482 + engines: {node: '>=8'} 483 + 484 + any-promise@1.3.0: 485 + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} 486 + 487 + argparse@2.0.1: 488 + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 489 + 490 + balanced-match@1.0.2: 491 + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 492 + 493 + brace-expansion@1.1.12: 494 + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} 495 + 496 + brace-expansion@2.0.2: 497 + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} 498 + 499 + bundle-require@5.1.0: 500 + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} 501 + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 502 + peerDependencies: 503 + esbuild: '>=0.18' 504 + 505 + cac@6.7.14: 506 + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 507 + engines: {node: '>=8'} 508 + 509 + callsites@3.1.0: 510 + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 511 + engines: {node: '>=6'} 512 + 513 + chalk@4.1.2: 514 + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 515 + engines: {node: '>=10'} 516 + 517 + chokidar@4.0.3: 518 + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} 519 + engines: {node: '>= 14.16.0'} 520 + 521 + color-convert@2.0.1: 522 + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 523 + engines: {node: '>=7.0.0'} 524 + 525 + color-name@1.1.4: 526 + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 527 + 528 + commander@4.1.1: 529 + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} 530 + engines: {node: '>= 6'} 531 + 532 + concat-map@0.0.1: 533 + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 534 + 535 + confbox@0.1.8: 536 + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} 537 + 538 + consola@3.4.2: 539 + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} 540 + engines: {node: ^14.18.0 || >=16.10.0} 541 + 542 + cross-spawn@7.0.6: 543 + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 544 + engines: {node: '>= 8'} 545 + 546 + debug@4.4.3: 547 + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} 548 + engines: {node: '>=6.0'} 549 + peerDependencies: 550 + supports-color: '*' 551 + peerDependenciesMeta: 552 + supports-color: 553 + optional: true 554 + 555 + deep-is@0.1.4: 556 + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 557 + 558 + esbuild@0.27.3: 559 + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} 560 + engines: {node: '>=18'} 561 + hasBin: true 562 + 563 + escape-string-regexp@4.0.0: 564 + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 565 + engines: {node: '>=10'} 566 + 567 + eslint-config-prettier@9.1.2: 568 + resolution: {integrity: sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==} 569 + hasBin: true 570 + peerDependencies: 571 + eslint: '>=7.0.0' 572 + 573 + eslint-scope@8.4.0: 574 + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} 575 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 576 + 577 + eslint-visitor-keys@3.4.3: 578 + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 579 + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 580 + 581 + eslint-visitor-keys@4.2.1: 582 + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} 583 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 584 + 585 + eslint@9.39.2: 586 + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} 587 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 588 + hasBin: true 589 + peerDependencies: 590 + jiti: '*' 591 + peerDependenciesMeta: 592 + jiti: 593 + optional: true 594 + 595 + espree@10.4.0: 596 + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} 597 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 598 + 599 + esquery@1.7.0: 600 + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} 601 + engines: {node: '>=0.10'} 602 + 603 + esrecurse@4.3.0: 604 + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} 605 + engines: {node: '>=4.0'} 606 + 607 + estraverse@5.3.0: 608 + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 609 + engines: {node: '>=4.0'} 610 + 611 + esutils@2.0.3: 612 + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 613 + engines: {node: '>=0.10.0'} 614 + 615 + fast-deep-equal@3.1.3: 616 + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 617 + 618 + fast-json-stable-stringify@2.1.0: 619 + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 620 + 621 + fast-levenshtein@2.0.6: 622 + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 623 + 624 + fdir@6.5.0: 625 + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} 626 + engines: {node: '>=12.0.0'} 627 + peerDependencies: 628 + picomatch: ^3 || ^4 629 + peerDependenciesMeta: 630 + picomatch: 631 + optional: true 632 + 633 + file-entry-cache@8.0.0: 634 + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} 635 + engines: {node: '>=16.0.0'} 636 + 637 + find-up@5.0.0: 638 + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 639 + engines: {node: '>=10'} 640 + 641 + fix-dts-default-cjs-exports@1.0.1: 642 + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} 643 + 644 + flat-cache@4.0.1: 645 + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} 646 + engines: {node: '>=16'} 647 + 648 + flatted@3.3.3: 649 + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} 650 + 651 + fsevents@2.3.3: 652 + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 653 + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 654 + os: [darwin] 655 + 656 + glob-parent@6.0.2: 657 + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 658 + engines: {node: '>=10.13.0'} 659 + 660 + globals@14.0.0: 661 + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} 662 + engines: {node: '>=18'} 663 + 664 + has-flag@4.0.0: 665 + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 666 + engines: {node: '>=8'} 667 + 668 + ignore@5.3.2: 669 + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} 670 + engines: {node: '>= 4'} 671 + 672 + ignore@7.0.5: 673 + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} 674 + engines: {node: '>= 4'} 675 + 676 + import-fresh@3.3.1: 677 + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} 678 + engines: {node: '>=6'} 679 + 680 + imurmurhash@0.1.4: 681 + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 682 + engines: {node: '>=0.8.19'} 683 + 684 + is-extglob@2.1.1: 685 + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 686 + engines: {node: '>=0.10.0'} 687 + 688 + is-glob@4.0.3: 689 + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 690 + engines: {node: '>=0.10.0'} 691 + 692 + isexe@2.0.0: 693 + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 694 + 695 + joycon@3.1.1: 696 + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} 697 + engines: {node: '>=10'} 698 + 699 + js-yaml@4.1.1: 700 + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} 701 + hasBin: true 702 + 703 + json-buffer@3.0.1: 704 + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} 705 + 706 + json-schema-traverse@0.4.1: 707 + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 708 + 709 + json-stable-stringify-without-jsonify@1.0.1: 710 + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} 711 + 712 + keyv@4.5.4: 713 + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} 714 + 715 + levn@0.4.1: 716 + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 717 + engines: {node: '>= 0.8.0'} 718 + 719 + lilconfig@3.1.3: 720 + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} 721 + engines: {node: '>=14'} 722 + 723 + lines-and-columns@1.2.4: 724 + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} 725 + 726 + load-tsconfig@0.2.5: 727 + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} 728 + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 729 + 730 + locate-path@6.0.0: 731 + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 732 + engines: {node: '>=10'} 733 + 734 + lodash.merge@4.6.2: 735 + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 736 + 737 + magic-string@0.30.21: 738 + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} 739 + 740 + minimatch@3.1.2: 741 + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 742 + 743 + minimatch@9.0.5: 744 + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 745 + engines: {node: '>=16 || 14 >=14.17'} 746 + 747 + mlly@1.8.0: 748 + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} 749 + 750 + ms@2.1.3: 751 + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 752 + 753 + mz@2.7.0: 754 + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} 755 + 756 + natural-compare@1.4.0: 757 + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 758 + 759 + object-assign@4.1.1: 760 + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 761 + engines: {node: '>=0.10.0'} 762 + 763 + optionator@0.9.4: 764 + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} 765 + engines: {node: '>= 0.8.0'} 766 + 767 + p-limit@3.1.0: 768 + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} 769 + engines: {node: '>=10'} 770 + 771 + p-locate@5.0.0: 772 + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 773 + engines: {node: '>=10'} 774 + 775 + parent-module@1.0.1: 776 + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 777 + engines: {node: '>=6'} 778 + 779 + path-exists@4.0.0: 780 + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 781 + engines: {node: '>=8'} 782 + 783 + path-key@3.1.1: 784 + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 785 + engines: {node: '>=8'} 786 + 787 + pathe@2.0.3: 788 + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} 789 + 790 + picocolors@1.1.1: 791 + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 792 + 793 + picomatch@4.0.3: 794 + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} 795 + engines: {node: '>=12'} 796 + 797 + pirates@4.0.7: 798 + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} 799 + engines: {node: '>= 6'} 800 + 801 + pkg-types@1.3.1: 802 + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} 803 + 804 + postcss-load-config@6.0.1: 805 + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} 806 + engines: {node: '>= 18'} 807 + peerDependencies: 808 + jiti: '>=1.21.0' 809 + postcss: '>=8.0.9' 810 + tsx: ^4.8.1 811 + yaml: ^2.4.2 812 + peerDependenciesMeta: 813 + jiti: 814 + optional: true 815 + postcss: 816 + optional: true 817 + tsx: 818 + optional: true 819 + yaml: 820 + optional: true 821 + 822 + prelude-ls@1.2.1: 823 + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 824 + engines: {node: '>= 0.8.0'} 825 + 826 + prettier@3.8.1: 827 + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} 828 + engines: {node: '>=14'} 829 + hasBin: true 830 + 831 + punycode@2.3.1: 832 + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 833 + engines: {node: '>=6'} 834 + 835 + readdirp@4.1.2: 836 + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} 837 + engines: {node: '>= 14.18.0'} 838 + 839 + resolve-from@4.0.0: 840 + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 841 + engines: {node: '>=4'} 842 + 843 + resolve-from@5.0.0: 844 + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} 845 + engines: {node: '>=8'} 846 + 847 + rollup@4.57.1: 848 + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} 849 + engines: {node: '>=18.0.0', npm: '>=8.0.0'} 850 + hasBin: true 851 + 852 + semver@7.7.4: 853 + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} 854 + engines: {node: '>=10'} 855 + hasBin: true 856 + 857 + shebang-command@2.0.0: 858 + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 859 + engines: {node: '>=8'} 860 + 861 + shebang-regex@3.0.0: 862 + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 863 + engines: {node: '>=8'} 864 + 865 + source-map@0.7.6: 866 + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} 867 + engines: {node: '>= 12'} 868 + 869 + strip-json-comments@3.1.1: 870 + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 871 + engines: {node: '>=8'} 872 + 873 + sucrase@3.35.1: 874 + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} 875 + engines: {node: '>=16 || 14 >=14.17'} 876 + hasBin: true 877 + 878 + supports-color@7.2.0: 879 + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 880 + engines: {node: '>=8'} 881 + 882 + thenify-all@1.6.0: 883 + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} 884 + engines: {node: '>=0.8'} 885 + 886 + thenify@3.3.1: 887 + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} 888 + 889 + tinyexec@0.3.2: 890 + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} 891 + 892 + tinyglobby@0.2.15: 893 + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} 894 + engines: {node: '>=12.0.0'} 895 + 896 + tree-kill@1.2.2: 897 + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} 898 + hasBin: true 899 + 900 + ts-api-utils@2.4.0: 901 + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} 902 + engines: {node: '>=18.12'} 903 + peerDependencies: 904 + typescript: '>=4.8.4' 905 + 906 + ts-interface-checker@0.1.13: 907 + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} 908 + 909 + tsup@8.5.1: 910 + resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} 911 + engines: {node: '>=18'} 912 + hasBin: true 913 + peerDependencies: 914 + '@microsoft/api-extractor': ^7.36.0 915 + '@swc/core': ^1 916 + postcss: ^8.4.12 917 + typescript: '>=4.5.0' 918 + peerDependenciesMeta: 919 + '@microsoft/api-extractor': 920 + optional: true 921 + '@swc/core': 922 + optional: true 923 + postcss: 924 + optional: true 925 + typescript: 926 + optional: true 927 + 928 + type-check@0.4.0: 929 + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 930 + engines: {node: '>= 0.8.0'} 931 + 932 + typescript@5.9.3: 933 + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} 934 + engines: {node: '>=14.17'} 935 + hasBin: true 936 + 937 + ufo@1.6.3: 938 + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} 939 + 940 + undici-types@6.21.0: 941 + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} 942 + 943 + uri-js@4.4.1: 944 + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 945 + 946 + which@2.0.2: 947 + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 948 + engines: {node: '>= 8'} 949 + hasBin: true 950 + 951 + word-wrap@1.2.5: 952 + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} 953 + engines: {node: '>=0.10.0'} 954 + 955 + ws@8.19.0: 956 + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} 957 + engines: {node: '>=10.0.0'} 958 + peerDependencies: 959 + bufferutil: ^4.0.1 960 + utf-8-validate: '>=5.0.2' 961 + peerDependenciesMeta: 962 + bufferutil: 963 + optional: true 964 + utf-8-validate: 965 + optional: true 966 + 967 + yocto-queue@0.1.0: 968 + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 969 + engines: {node: '>=10'} 970 + 971 + zod@3.25.76: 972 + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} 973 + 974 + snapshots: 975 + 976 + '@esbuild/aix-ppc64@0.27.3': 977 + optional: true 978 + 979 + '@esbuild/android-arm64@0.27.3': 980 + optional: true 981 + 982 + '@esbuild/android-arm@0.27.3': 983 + optional: true 984 + 985 + '@esbuild/android-x64@0.27.3': 986 + optional: true 987 + 988 + '@esbuild/darwin-arm64@0.27.3': 989 + optional: true 990 + 991 + '@esbuild/darwin-x64@0.27.3': 992 + optional: true 993 + 994 + '@esbuild/freebsd-arm64@0.27.3': 995 + optional: true 996 + 997 + '@esbuild/freebsd-x64@0.27.3': 998 + optional: true 999 + 1000 + '@esbuild/linux-arm64@0.27.3': 1001 + optional: true 1002 + 1003 + '@esbuild/linux-arm@0.27.3': 1004 + optional: true 1005 + 1006 + '@esbuild/linux-ia32@0.27.3': 1007 + optional: true 1008 + 1009 + '@esbuild/linux-loong64@0.27.3': 1010 + optional: true 1011 + 1012 + '@esbuild/linux-mips64el@0.27.3': 1013 + optional: true 1014 + 1015 + '@esbuild/linux-ppc64@0.27.3': 1016 + optional: true 1017 + 1018 + '@esbuild/linux-riscv64@0.27.3': 1019 + optional: true 1020 + 1021 + '@esbuild/linux-s390x@0.27.3': 1022 + optional: true 1023 + 1024 + '@esbuild/linux-x64@0.27.3': 1025 + optional: true 1026 + 1027 + '@esbuild/netbsd-arm64@0.27.3': 1028 + optional: true 1029 + 1030 + '@esbuild/netbsd-x64@0.27.3': 1031 + optional: true 1032 + 1033 + '@esbuild/openbsd-arm64@0.27.3': 1034 + optional: true 1035 + 1036 + '@esbuild/openbsd-x64@0.27.3': 1037 + optional: true 1038 + 1039 + '@esbuild/openharmony-arm64@0.27.3': 1040 + optional: true 1041 + 1042 + '@esbuild/sunos-x64@0.27.3': 1043 + optional: true 1044 + 1045 + '@esbuild/win32-arm64@0.27.3': 1046 + optional: true 1047 + 1048 + '@esbuild/win32-ia32@0.27.3': 1049 + optional: true 1050 + 1051 + '@esbuild/win32-x64@0.27.3': 1052 + optional: true 1053 + 1054 + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2)': 1055 + dependencies: 1056 + eslint: 9.39.2 1057 + eslint-visitor-keys: 3.4.3 1058 + 1059 + '@eslint-community/regexpp@4.12.2': {} 1060 + 1061 + '@eslint/config-array@0.21.1': 1062 + dependencies: 1063 + '@eslint/object-schema': 2.1.7 1064 + debug: 4.4.3 1065 + minimatch: 3.1.2 1066 + transitivePeerDependencies: 1067 + - supports-color 1068 + 1069 + '@eslint/config-helpers@0.4.2': 1070 + dependencies: 1071 + '@eslint/core': 0.17.0 1072 + 1073 + '@eslint/core@0.17.0': 1074 + dependencies: 1075 + '@types/json-schema': 7.0.15 1076 + 1077 + '@eslint/eslintrc@3.3.3': 1078 + dependencies: 1079 + ajv: 6.12.6 1080 + debug: 4.4.3 1081 + espree: 10.4.0 1082 + globals: 14.0.0 1083 + ignore: 5.3.2 1084 + import-fresh: 3.3.1 1085 + js-yaml: 4.1.1 1086 + minimatch: 3.1.2 1087 + strip-json-comments: 3.1.1 1088 + transitivePeerDependencies: 1089 + - supports-color 1090 + 1091 + '@eslint/js@9.39.2': {} 1092 + 1093 + '@eslint/object-schema@2.1.7': {} 1094 + 1095 + '@eslint/plugin-kit@0.4.1': 1096 + dependencies: 1097 + '@eslint/core': 0.17.0 1098 + levn: 0.4.1 1099 + 1100 + '@humanfs/core@0.19.1': {} 1101 + 1102 + '@humanfs/node@0.16.7': 1103 + dependencies: 1104 + '@humanfs/core': 0.19.1 1105 + '@humanwhocodes/retry': 0.4.3 1106 + 1107 + '@humanwhocodes/module-importer@1.0.1': {} 1108 + 1109 + '@humanwhocodes/retry@0.4.3': {} 1110 + 1111 + '@jridgewell/gen-mapping@0.3.13': 1112 + dependencies: 1113 + '@jridgewell/sourcemap-codec': 1.5.5 1114 + '@jridgewell/trace-mapping': 0.3.31 1115 + 1116 + '@jridgewell/resolve-uri@3.1.2': {} 1117 + 1118 + '@jridgewell/sourcemap-codec@1.5.5': {} 1119 + 1120 + '@jridgewell/trace-mapping@0.3.31': 1121 + dependencies: 1122 + '@jridgewell/resolve-uri': 3.1.2 1123 + '@jridgewell/sourcemap-codec': 1.5.5 1124 + 1125 + '@rollup/rollup-android-arm-eabi@4.57.1': 1126 + optional: true 1127 + 1128 + '@rollup/rollup-android-arm64@4.57.1': 1129 + optional: true 1130 + 1131 + '@rollup/rollup-darwin-arm64@4.57.1': 1132 + optional: true 1133 + 1134 + '@rollup/rollup-darwin-x64@4.57.1': 1135 + optional: true 1136 + 1137 + '@rollup/rollup-freebsd-arm64@4.57.1': 1138 + optional: true 1139 + 1140 + '@rollup/rollup-freebsd-x64@4.57.1': 1141 + optional: true 1142 + 1143 + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': 1144 + optional: true 1145 + 1146 + '@rollup/rollup-linux-arm-musleabihf@4.57.1': 1147 + optional: true 1148 + 1149 + '@rollup/rollup-linux-arm64-gnu@4.57.1': 1150 + optional: true 1151 + 1152 + '@rollup/rollup-linux-arm64-musl@4.57.1': 1153 + optional: true 1154 + 1155 + '@rollup/rollup-linux-loong64-gnu@4.57.1': 1156 + optional: true 1157 + 1158 + '@rollup/rollup-linux-loong64-musl@4.57.1': 1159 + optional: true 1160 + 1161 + '@rollup/rollup-linux-ppc64-gnu@4.57.1': 1162 + optional: true 1163 + 1164 + '@rollup/rollup-linux-ppc64-musl@4.57.1': 1165 + optional: true 1166 + 1167 + '@rollup/rollup-linux-riscv64-gnu@4.57.1': 1168 + optional: true 1169 + 1170 + '@rollup/rollup-linux-riscv64-musl@4.57.1': 1171 + optional: true 1172 + 1173 + '@rollup/rollup-linux-s390x-gnu@4.57.1': 1174 + optional: true 1175 + 1176 + '@rollup/rollup-linux-x64-gnu@4.57.1': 1177 + optional: true 1178 + 1179 + '@rollup/rollup-linux-x64-musl@4.57.1': 1180 + optional: true 1181 + 1182 + '@rollup/rollup-openbsd-x64@4.57.1': 1183 + optional: true 1184 + 1185 + '@rollup/rollup-openharmony-arm64@4.57.1': 1186 + optional: true 1187 + 1188 + '@rollup/rollup-win32-arm64-msvc@4.57.1': 1189 + optional: true 1190 + 1191 + '@rollup/rollup-win32-ia32-msvc@4.57.1': 1192 + optional: true 1193 + 1194 + '@rollup/rollup-win32-x64-gnu@4.57.1': 1195 + optional: true 1196 + 1197 + '@rollup/rollup-win32-x64-msvc@4.57.1': 1198 + optional: true 1199 + 1200 + '@types/estree@1.0.8': {} 1201 + 1202 + '@types/json-schema@7.0.15': {} 1203 + 1204 + '@types/node@22.19.11': 1205 + dependencies: 1206 + undici-types: 6.21.0 1207 + 1208 + '@types/ws@8.18.1': 1209 + dependencies: 1210 + '@types/node': 22.19.11 1211 + 1212 + '@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)': 1213 + dependencies: 1214 + '@eslint-community/regexpp': 4.12.2 1215 + '@typescript-eslint/parser': 8.55.0(eslint@9.39.2)(typescript@5.9.3) 1216 + '@typescript-eslint/scope-manager': 8.55.0 1217 + '@typescript-eslint/type-utils': 8.55.0(eslint@9.39.2)(typescript@5.9.3) 1218 + '@typescript-eslint/utils': 8.55.0(eslint@9.39.2)(typescript@5.9.3) 1219 + '@typescript-eslint/visitor-keys': 8.55.0 1220 + eslint: 9.39.2 1221 + ignore: 7.0.5 1222 + natural-compare: 1.4.0 1223 + ts-api-utils: 2.4.0(typescript@5.9.3) 1224 + typescript: 5.9.3 1225 + transitivePeerDependencies: 1226 + - supports-color 1227 + 1228 + '@typescript-eslint/parser@8.55.0(eslint@9.39.2)(typescript@5.9.3)': 1229 + dependencies: 1230 + '@typescript-eslint/scope-manager': 8.55.0 1231 + '@typescript-eslint/types': 8.55.0 1232 + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) 1233 + '@typescript-eslint/visitor-keys': 8.55.0 1234 + debug: 4.4.3 1235 + eslint: 9.39.2 1236 + typescript: 5.9.3 1237 + transitivePeerDependencies: 1238 + - supports-color 1239 + 1240 + '@typescript-eslint/project-service@8.55.0(typescript@5.9.3)': 1241 + dependencies: 1242 + '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) 1243 + '@typescript-eslint/types': 8.55.0 1244 + debug: 4.4.3 1245 + typescript: 5.9.3 1246 + transitivePeerDependencies: 1247 + - supports-color 1248 + 1249 + '@typescript-eslint/scope-manager@8.55.0': 1250 + dependencies: 1251 + '@typescript-eslint/types': 8.55.0 1252 + '@typescript-eslint/visitor-keys': 8.55.0 1253 + 1254 + '@typescript-eslint/tsconfig-utils@8.55.0(typescript@5.9.3)': 1255 + dependencies: 1256 + typescript: 5.9.3 1257 + 1258 + '@typescript-eslint/type-utils@8.55.0(eslint@9.39.2)(typescript@5.9.3)': 1259 + dependencies: 1260 + '@typescript-eslint/types': 8.55.0 1261 + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) 1262 + '@typescript-eslint/utils': 8.55.0(eslint@9.39.2)(typescript@5.9.3) 1263 + debug: 4.4.3 1264 + eslint: 9.39.2 1265 + ts-api-utils: 2.4.0(typescript@5.9.3) 1266 + typescript: 5.9.3 1267 + transitivePeerDependencies: 1268 + - supports-color 1269 + 1270 + '@typescript-eslint/types@8.55.0': {} 1271 + 1272 + '@typescript-eslint/typescript-estree@8.55.0(typescript@5.9.3)': 1273 + dependencies: 1274 + '@typescript-eslint/project-service': 8.55.0(typescript@5.9.3) 1275 + '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) 1276 + '@typescript-eslint/types': 8.55.0 1277 + '@typescript-eslint/visitor-keys': 8.55.0 1278 + debug: 4.4.3 1279 + minimatch: 9.0.5 1280 + semver: 7.7.4 1281 + tinyglobby: 0.2.15 1282 + ts-api-utils: 2.4.0(typescript@5.9.3) 1283 + typescript: 5.9.3 1284 + transitivePeerDependencies: 1285 + - supports-color 1286 + 1287 + '@typescript-eslint/utils@8.55.0(eslint@9.39.2)(typescript@5.9.3)': 1288 + dependencies: 1289 + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) 1290 + '@typescript-eslint/scope-manager': 8.55.0 1291 + '@typescript-eslint/types': 8.55.0 1292 + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) 1293 + eslint: 9.39.2 1294 + typescript: 5.9.3 1295 + transitivePeerDependencies: 1296 + - supports-color 1297 + 1298 + '@typescript-eslint/visitor-keys@8.55.0': 1299 + dependencies: 1300 + '@typescript-eslint/types': 8.55.0 1301 + eslint-visitor-keys: 4.2.1 1302 + 1303 + acorn-jsx@5.3.2(acorn@8.15.0): 1304 + dependencies: 1305 + acorn: 8.15.0 1306 + 1307 + acorn@8.15.0: {} 1308 + 1309 + ajv@6.12.6: 1310 + dependencies: 1311 + fast-deep-equal: 3.1.3 1312 + fast-json-stable-stringify: 2.1.0 1313 + json-schema-traverse: 0.4.1 1314 + uri-js: 4.4.1 1315 + 1316 + ansi-styles@4.3.0: 1317 + dependencies: 1318 + color-convert: 2.0.1 1319 + 1320 + any-promise@1.3.0: {} 1321 + 1322 + argparse@2.0.1: {} 1323 + 1324 + balanced-match@1.0.2: {} 1325 + 1326 + brace-expansion@1.1.12: 1327 + dependencies: 1328 + balanced-match: 1.0.2 1329 + concat-map: 0.0.1 1330 + 1331 + brace-expansion@2.0.2: 1332 + dependencies: 1333 + balanced-match: 1.0.2 1334 + 1335 + bundle-require@5.1.0(esbuild@0.27.3): 1336 + dependencies: 1337 + esbuild: 0.27.3 1338 + load-tsconfig: 0.2.5 1339 + 1340 + cac@6.7.14: {} 1341 + 1342 + callsites@3.1.0: {} 1343 + 1344 + chalk@4.1.2: 1345 + dependencies: 1346 + ansi-styles: 4.3.0 1347 + supports-color: 7.2.0 1348 + 1349 + chokidar@4.0.3: 1350 + dependencies: 1351 + readdirp: 4.1.2 1352 + 1353 + color-convert@2.0.1: 1354 + dependencies: 1355 + color-name: 1.1.4 1356 + 1357 + color-name@1.1.4: {} 1358 + 1359 + commander@4.1.1: {} 1360 + 1361 + concat-map@0.0.1: {} 1362 + 1363 + confbox@0.1.8: {} 1364 + 1365 + consola@3.4.2: {} 1366 + 1367 + cross-spawn@7.0.6: 1368 + dependencies: 1369 + path-key: 3.1.1 1370 + shebang-command: 2.0.0 1371 + which: 2.0.2 1372 + 1373 + debug@4.4.3: 1374 + dependencies: 1375 + ms: 2.1.3 1376 + 1377 + deep-is@0.1.4: {} 1378 + 1379 + esbuild@0.27.3: 1380 + optionalDependencies: 1381 + '@esbuild/aix-ppc64': 0.27.3 1382 + '@esbuild/android-arm': 0.27.3 1383 + '@esbuild/android-arm64': 0.27.3 1384 + '@esbuild/android-x64': 0.27.3 1385 + '@esbuild/darwin-arm64': 0.27.3 1386 + '@esbuild/darwin-x64': 0.27.3 1387 + '@esbuild/freebsd-arm64': 0.27.3 1388 + '@esbuild/freebsd-x64': 0.27.3 1389 + '@esbuild/linux-arm': 0.27.3 1390 + '@esbuild/linux-arm64': 0.27.3 1391 + '@esbuild/linux-ia32': 0.27.3 1392 + '@esbuild/linux-loong64': 0.27.3 1393 + '@esbuild/linux-mips64el': 0.27.3 1394 + '@esbuild/linux-ppc64': 0.27.3 1395 + '@esbuild/linux-riscv64': 0.27.3 1396 + '@esbuild/linux-s390x': 0.27.3 1397 + '@esbuild/linux-x64': 0.27.3 1398 + '@esbuild/netbsd-arm64': 0.27.3 1399 + '@esbuild/netbsd-x64': 0.27.3 1400 + '@esbuild/openbsd-arm64': 0.27.3 1401 + '@esbuild/openbsd-x64': 0.27.3 1402 + '@esbuild/openharmony-arm64': 0.27.3 1403 + '@esbuild/sunos-x64': 0.27.3 1404 + '@esbuild/win32-arm64': 0.27.3 1405 + '@esbuild/win32-ia32': 0.27.3 1406 + '@esbuild/win32-x64': 0.27.3 1407 + 1408 + escape-string-regexp@4.0.0: {} 1409 + 1410 + eslint-config-prettier@9.1.2(eslint@9.39.2): 1411 + dependencies: 1412 + eslint: 9.39.2 1413 + 1414 + eslint-scope@8.4.0: 1415 + dependencies: 1416 + esrecurse: 4.3.0 1417 + estraverse: 5.3.0 1418 + 1419 + eslint-visitor-keys@3.4.3: {} 1420 + 1421 + eslint-visitor-keys@4.2.1: {} 1422 + 1423 + eslint@9.39.2: 1424 + dependencies: 1425 + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) 1426 + '@eslint-community/regexpp': 4.12.2 1427 + '@eslint/config-array': 0.21.1 1428 + '@eslint/config-helpers': 0.4.2 1429 + '@eslint/core': 0.17.0 1430 + '@eslint/eslintrc': 3.3.3 1431 + '@eslint/js': 9.39.2 1432 + '@eslint/plugin-kit': 0.4.1 1433 + '@humanfs/node': 0.16.7 1434 + '@humanwhocodes/module-importer': 1.0.1 1435 + '@humanwhocodes/retry': 0.4.3 1436 + '@types/estree': 1.0.8 1437 + ajv: 6.12.6 1438 + chalk: 4.1.2 1439 + cross-spawn: 7.0.6 1440 + debug: 4.4.3 1441 + escape-string-regexp: 4.0.0 1442 + eslint-scope: 8.4.0 1443 + eslint-visitor-keys: 4.2.1 1444 + espree: 10.4.0 1445 + esquery: 1.7.0 1446 + esutils: 2.0.3 1447 + fast-deep-equal: 3.1.3 1448 + file-entry-cache: 8.0.0 1449 + find-up: 5.0.0 1450 + glob-parent: 6.0.2 1451 + ignore: 5.3.2 1452 + imurmurhash: 0.1.4 1453 + is-glob: 4.0.3 1454 + json-stable-stringify-without-jsonify: 1.0.1 1455 + lodash.merge: 4.6.2 1456 + minimatch: 3.1.2 1457 + natural-compare: 1.4.0 1458 + optionator: 0.9.4 1459 + transitivePeerDependencies: 1460 + - supports-color 1461 + 1462 + espree@10.4.0: 1463 + dependencies: 1464 + acorn: 8.15.0 1465 + acorn-jsx: 5.3.2(acorn@8.15.0) 1466 + eslint-visitor-keys: 4.2.1 1467 + 1468 + esquery@1.7.0: 1469 + dependencies: 1470 + estraverse: 5.3.0 1471 + 1472 + esrecurse@4.3.0: 1473 + dependencies: 1474 + estraverse: 5.3.0 1475 + 1476 + estraverse@5.3.0: {} 1477 + 1478 + esutils@2.0.3: {} 1479 + 1480 + fast-deep-equal@3.1.3: {} 1481 + 1482 + fast-json-stable-stringify@2.1.0: {} 1483 + 1484 + fast-levenshtein@2.0.6: {} 1485 + 1486 + fdir@6.5.0(picomatch@4.0.3): 1487 + optionalDependencies: 1488 + picomatch: 4.0.3 1489 + 1490 + file-entry-cache@8.0.0: 1491 + dependencies: 1492 + flat-cache: 4.0.1 1493 + 1494 + find-up@5.0.0: 1495 + dependencies: 1496 + locate-path: 6.0.0 1497 + path-exists: 4.0.0 1498 + 1499 + fix-dts-default-cjs-exports@1.0.1: 1500 + dependencies: 1501 + magic-string: 0.30.21 1502 + mlly: 1.8.0 1503 + rollup: 4.57.1 1504 + 1505 + flat-cache@4.0.1: 1506 + dependencies: 1507 + flatted: 3.3.3 1508 + keyv: 4.5.4 1509 + 1510 + flatted@3.3.3: {} 1511 + 1512 + fsevents@2.3.3: 1513 + optional: true 1514 + 1515 + glob-parent@6.0.2: 1516 + dependencies: 1517 + is-glob: 4.0.3 1518 + 1519 + globals@14.0.0: {} 1520 + 1521 + has-flag@4.0.0: {} 1522 + 1523 + ignore@5.3.2: {} 1524 + 1525 + ignore@7.0.5: {} 1526 + 1527 + import-fresh@3.3.1: 1528 + dependencies: 1529 + parent-module: 1.0.1 1530 + resolve-from: 4.0.0 1531 + 1532 + imurmurhash@0.1.4: {} 1533 + 1534 + is-extglob@2.1.1: {} 1535 + 1536 + is-glob@4.0.3: 1537 + dependencies: 1538 + is-extglob: 2.1.1 1539 + 1540 + isexe@2.0.0: {} 1541 + 1542 + joycon@3.1.1: {} 1543 + 1544 + js-yaml@4.1.1: 1545 + dependencies: 1546 + argparse: 2.0.1 1547 + 1548 + json-buffer@3.0.1: {} 1549 + 1550 + json-schema-traverse@0.4.1: {} 1551 + 1552 + json-stable-stringify-without-jsonify@1.0.1: {} 1553 + 1554 + keyv@4.5.4: 1555 + dependencies: 1556 + json-buffer: 3.0.1 1557 + 1558 + levn@0.4.1: 1559 + dependencies: 1560 + prelude-ls: 1.2.1 1561 + type-check: 0.4.0 1562 + 1563 + lilconfig@3.1.3: {} 1564 + 1565 + lines-and-columns@1.2.4: {} 1566 + 1567 + load-tsconfig@0.2.5: {} 1568 + 1569 + locate-path@6.0.0: 1570 + dependencies: 1571 + p-locate: 5.0.0 1572 + 1573 + lodash.merge@4.6.2: {} 1574 + 1575 + magic-string@0.30.21: 1576 + dependencies: 1577 + '@jridgewell/sourcemap-codec': 1.5.5 1578 + 1579 + minimatch@3.1.2: 1580 + dependencies: 1581 + brace-expansion: 1.1.12 1582 + 1583 + minimatch@9.0.5: 1584 + dependencies: 1585 + brace-expansion: 2.0.2 1586 + 1587 + mlly@1.8.0: 1588 + dependencies: 1589 + acorn: 8.15.0 1590 + pathe: 2.0.3 1591 + pkg-types: 1.3.1 1592 + ufo: 1.6.3 1593 + 1594 + ms@2.1.3: {} 1595 + 1596 + mz@2.7.0: 1597 + dependencies: 1598 + any-promise: 1.3.0 1599 + object-assign: 4.1.1 1600 + thenify-all: 1.6.0 1601 + 1602 + natural-compare@1.4.0: {} 1603 + 1604 + object-assign@4.1.1: {} 1605 + 1606 + optionator@0.9.4: 1607 + dependencies: 1608 + deep-is: 0.1.4 1609 + fast-levenshtein: 2.0.6 1610 + levn: 0.4.1 1611 + prelude-ls: 1.2.1 1612 + type-check: 0.4.0 1613 + word-wrap: 1.2.5 1614 + 1615 + p-limit@3.1.0: 1616 + dependencies: 1617 + yocto-queue: 0.1.0 1618 + 1619 + p-locate@5.0.0: 1620 + dependencies: 1621 + p-limit: 3.1.0 1622 + 1623 + parent-module@1.0.1: 1624 + dependencies: 1625 + callsites: 3.1.0 1626 + 1627 + path-exists@4.0.0: {} 1628 + 1629 + path-key@3.1.1: {} 1630 + 1631 + pathe@2.0.3: {} 1632 + 1633 + picocolors@1.1.1: {} 1634 + 1635 + picomatch@4.0.3: {} 1636 + 1637 + pirates@4.0.7: {} 1638 + 1639 + pkg-types@1.3.1: 1640 + dependencies: 1641 + confbox: 0.1.8 1642 + mlly: 1.8.0 1643 + pathe: 2.0.3 1644 + 1645 + postcss-load-config@6.0.1: 1646 + dependencies: 1647 + lilconfig: 3.1.3 1648 + 1649 + prelude-ls@1.2.1: {} 1650 + 1651 + prettier@3.8.1: {} 1652 + 1653 + punycode@2.3.1: {} 1654 + 1655 + readdirp@4.1.2: {} 1656 + 1657 + resolve-from@4.0.0: {} 1658 + 1659 + resolve-from@5.0.0: {} 1660 + 1661 + rollup@4.57.1: 1662 + dependencies: 1663 + '@types/estree': 1.0.8 1664 + optionalDependencies: 1665 + '@rollup/rollup-android-arm-eabi': 4.57.1 1666 + '@rollup/rollup-android-arm64': 4.57.1 1667 + '@rollup/rollup-darwin-arm64': 4.57.1 1668 + '@rollup/rollup-darwin-x64': 4.57.1 1669 + '@rollup/rollup-freebsd-arm64': 4.57.1 1670 + '@rollup/rollup-freebsd-x64': 4.57.1 1671 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 1672 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 1673 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 1674 + '@rollup/rollup-linux-arm64-musl': 4.57.1 1675 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 1676 + '@rollup/rollup-linux-loong64-musl': 4.57.1 1677 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 1678 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 1679 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 1680 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 1681 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 1682 + '@rollup/rollup-linux-x64-gnu': 4.57.1 1683 + '@rollup/rollup-linux-x64-musl': 4.57.1 1684 + '@rollup/rollup-openbsd-x64': 4.57.1 1685 + '@rollup/rollup-openharmony-arm64': 4.57.1 1686 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 1687 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 1688 + '@rollup/rollup-win32-x64-gnu': 4.57.1 1689 + '@rollup/rollup-win32-x64-msvc': 4.57.1 1690 + fsevents: 2.3.3 1691 + 1692 + semver@7.7.4: {} 1693 + 1694 + shebang-command@2.0.0: 1695 + dependencies: 1696 + shebang-regex: 3.0.0 1697 + 1698 + shebang-regex@3.0.0: {} 1699 + 1700 + source-map@0.7.6: {} 1701 + 1702 + strip-json-comments@3.1.1: {} 1703 + 1704 + sucrase@3.35.1: 1705 + dependencies: 1706 + '@jridgewell/gen-mapping': 0.3.13 1707 + commander: 4.1.1 1708 + lines-and-columns: 1.2.4 1709 + mz: 2.7.0 1710 + pirates: 4.0.7 1711 + tinyglobby: 0.2.15 1712 + ts-interface-checker: 0.1.13 1713 + 1714 + supports-color@7.2.0: 1715 + dependencies: 1716 + has-flag: 4.0.0 1717 + 1718 + thenify-all@1.6.0: 1719 + dependencies: 1720 + thenify: 3.3.1 1721 + 1722 + thenify@3.3.1: 1723 + dependencies: 1724 + any-promise: 1.3.0 1725 + 1726 + tinyexec@0.3.2: {} 1727 + 1728 + tinyglobby@0.2.15: 1729 + dependencies: 1730 + fdir: 6.5.0(picomatch@4.0.3) 1731 + picomatch: 4.0.3 1732 + 1733 + tree-kill@1.2.2: {} 1734 + 1735 + ts-api-utils@2.4.0(typescript@5.9.3): 1736 + dependencies: 1737 + typescript: 5.9.3 1738 + 1739 + ts-interface-checker@0.1.13: {} 1740 + 1741 + tsup@8.5.1(typescript@5.9.3): 1742 + dependencies: 1743 + bundle-require: 5.1.0(esbuild@0.27.3) 1744 + cac: 6.7.14 1745 + chokidar: 4.0.3 1746 + consola: 3.4.2 1747 + debug: 4.4.3 1748 + esbuild: 0.27.3 1749 + fix-dts-default-cjs-exports: 1.0.1 1750 + joycon: 3.1.1 1751 + picocolors: 1.1.1 1752 + postcss-load-config: 6.0.1 1753 + resolve-from: 5.0.0 1754 + rollup: 4.57.1 1755 + source-map: 0.7.6 1756 + sucrase: 3.35.1 1757 + tinyexec: 0.3.2 1758 + tinyglobby: 0.2.15 1759 + tree-kill: 1.2.2 1760 + optionalDependencies: 1761 + typescript: 5.9.3 1762 + transitivePeerDependencies: 1763 + - jiti 1764 + - supports-color 1765 + - tsx 1766 + - yaml 1767 + 1768 + type-check@0.4.0: 1769 + dependencies: 1770 + prelude-ls: 1.2.1 1771 + 1772 + typescript@5.9.3: {} 1773 + 1774 + ufo@1.6.3: {} 1775 + 1776 + undici-types@6.21.0: {} 1777 + 1778 + uri-js@4.4.1: 1779 + dependencies: 1780 + punycode: 2.3.1 1781 + 1782 + which@2.0.2: 1783 + dependencies: 1784 + isexe: 2.0.0 1785 + 1786 + word-wrap@1.2.5: {} 1787 + 1788 + ws@8.19.0: {} 1789 + 1790 + yocto-queue@0.1.0: {} 1791 + 1792 + zod@3.25.76: {}
+179
src/WebSocketClient.ts
··· 1 + import { EventEmitter } from "events"; 2 + import WebSocket from "ws"; 3 + import { z } from "zod"; 4 + import { WebSocketConnection } from "./connection/WebSocketConnection"; 5 + import { WebSocketRouter } from "./router/WebSocketRouter"; 6 + import { Logger } from "./logger/Logger"; 7 + import { WebSocketClientOptions } from "./types"; 8 + import { ConnectionInfo } from "./connection/types"; 9 + import { MessageHandler, HandlerError } from "./router/types"; 10 + 11 + /** 12 + * Main entry point for the wah library. Connects to a WebSocket, validates 13 + * incoming messages against Zod schemas, and dispatches to typed handlers. 14 + * 15 + * Composes {@link WebSocketConnection} (lifecycle, reconnect, heartbeat) with 16 + * {@link WebSocketRouter} (schema matching, handler dispatch) behind a single API. 17 + * 18 + * @example 19 + * ```typescript 20 + * import { WebSocketClient, LogLevel } from "wah"; 21 + * import { z } from "zod"; 22 + * 23 + * const tradeSchema = z.object({ 24 + * type: z.literal("trade"), 25 + * symbol: z.string(), 26 + * price: z.number(), 27 + * }); 28 + * 29 + * const client = new WebSocketClient({ 30 + * service: "wss://stream.example.com/v1", 31 + * queryParams: { apiKey: "abc" }, 32 + * logger: { enabled: true, level: LogLevel.DEBUG }, 33 + * }); 34 + * 35 + * client.handle(tradeSchema, async ({ data, send }) => { 36 + * console.log(data.symbol, data.price); 37 + * send({ ack: true }); 38 + * }); 39 + * 40 + * client.on("error", err => console.error(err)); 41 + * client.connect(); 42 + * ``` 43 + */ 44 + export class WebSocketClient extends EventEmitter { 45 + private connection: WebSocketConnection; 46 + private router: WebSocketRouter; 47 + private logger: Logger; 48 + 49 + constructor(options: WebSocketClientOptions) { 50 + super(); 51 + this.logger = new Logger(options.logger); 52 + this.connection = new WebSocketConnection(options, this.logger); 53 + this.router = new WebSocketRouter(this.logger); 54 + 55 + this.wireEvents(); 56 + } 57 + 58 + /** 59 + * Registers a message handler that is invoked when an incoming message 60 + * successfully validates against the given Zod schema. 61 + * 62 + * Multiple handlers can be registered. All handlers whose schema matches 63 + * a message will be invoked concurrently. 64 + * 65 + * @typeParam T - The type inferred from the Zod schema. 66 + * @param schema - Zod schema to validate incoming messages against. 67 + * @param handler - Callback receiving the typed data and a context object. 68 + * @returns `this` for chaining. 69 + * 70 + * @example 71 + * ```typescript 72 + * const pingSchema = z.object({ type: z.literal("ping"), ts: z.number() }); 73 + * 74 + * client.handle(pingSchema, ({ data, send }) => { 75 + * send({ type: "pong", ts: data.ts }); 76 + * }); 77 + * ``` 78 + */ 79 + handle<T>(schema: z.ZodSchema<T>, handler: MessageHandler<T>): this { 80 + this.router.register(schema, handler); 81 + return this; 82 + } 83 + 84 + /** 85 + * Opens the WebSocket connection. The client will automatically reconnect 86 + * on disconnection according to the configured reconnection strategy. 87 + */ 88 + connect(): void { 89 + this.connection.connect(); 90 + } 91 + 92 + /** 93 + * Closes the WebSocket connection and stops all reconnection attempts. 94 + */ 95 + close(): void { 96 + this.connection.close(); 97 + } 98 + 99 + /** 100 + * Sends data through the WebSocket. Objects are JSON-serialized automatically. 101 + * 102 + * @param data - Data to send. Objects/arrays are JSON.stringified; strings and Buffers are sent as-is. 103 + * @returns `true` if sent, `false` if the connection is not open. 104 + */ 105 + send(data: unknown): boolean { 106 + const serialized = 107 + typeof data === "string" || Buffer.isBuffer(data) ? data : JSON.stringify(data); 108 + return this.connection.send(serialized); 109 + } 110 + 111 + /** 112 + * Merges new query parameters into the connection URL and reconnects. 113 + * Existing parameters not present in the update are preserved. 114 + * 115 + * @param params - Key-value pairs to merge into the current query parameters. 116 + * 117 + * @example 118 + * ```typescript 119 + * // Initial connection to wss://example.com?channel=lobby 120 + * client.updateParams({ channel: "game-1" }); 121 + * // Reconnects to wss://example.com?channel=game-1 122 + * ``` 123 + */ 124 + updateParams(params: Record<string, string | number | boolean>): void { 125 + this.connection.updateParams(params); 126 + } 127 + 128 + /** 129 + * Returns a read-only snapshot of the current connection state. 130 + */ 131 + getConnectionInfo(): ConnectionInfo { 132 + return this.connection.getConnectionInfo(); 133 + } 134 + 135 + private wireEvents(): void { 136 + // Forward connection lifecycle events 137 + this.connection.on("open", () => this.emit("open")); 138 + this.connection.on("close", (info: { code: number; reason: string }) => 139 + this.emit("close", info) 140 + ); 141 + this.connection.on("reconnecting", (info: unknown) => this.emit("reconnecting", info)); 142 + this.connection.on("serviceSwitched", (info: unknown) => this.emit("serviceSwitched", info)); 143 + 144 + // Connection errors → unified "error" event 145 + this.connection.on("error", (error: Error) => this.emit("error", error)); 146 + 147 + // Router errors → unified "error" event 148 + this.router.on("error", (handlerError: HandlerError) => this.emit("error", handlerError)); 149 + 150 + // Connection messages → router 151 + this.connection.on("message", (data: WebSocket.Data) => { 152 + const raw = this.toRawString(data); 153 + if (raw !== null) { 154 + const sendFn = (payload: unknown): boolean => this.send(payload); 155 + const info = this.connection.getConnectionInfo(); 156 + // Fire and forget — errors are emitted via the router's error event 157 + this.router.route(raw, sendFn, info).catch((err: unknown) => { 158 + this.logger.error("Unexpected error in router.route", err); 159 + }); 160 + } 161 + }); 162 + } 163 + 164 + private toRawString(data: WebSocket.Data): string | null { 165 + if (typeof data === "string") { 166 + return data; 167 + } 168 + if (Buffer.isBuffer(data)) { 169 + return data.toString("utf-8"); 170 + } 171 + if (data instanceof ArrayBuffer) { 172 + return Buffer.from(data).toString("utf-8"); 173 + } 174 + if (Array.isArray(data)) { 175 + return Buffer.concat(data).toString("utf-8"); 176 + } 177 + return null; 178 + } 179 + }
+328
src/connection/WebSocketConnection.ts
··· 1 + import { EventEmitter } from "events"; 2 + import WebSocket from "ws"; 3 + import { ConnectionOptions, ConnectionInfo, ConnectionState, ReconnectOptions } from "./types"; 4 + import { Logger } from "../logger/Logger"; 5 + 6 + /** 7 + * Manages the WebSocket connection lifecycle including connecting, reconnecting 8 + * with exponential backoff, multi-service failover, heartbeat pings, and 9 + * dynamic query parameter updates. 10 + * 11 + * Events emitted: 12 + * - `"open"` — connection established 13 + * - `"close"` — connection closed (emits `{ code: number, reason: string }`) 14 + * - `"error"` — connection error (emits the `Error` object) 15 + * - `"message"` — message received (emits the raw `WebSocket.Data`) 16 + * - `"reconnecting"` — about to attempt reconnection (emits `{ attempt, maxAttempts, delay, service }`) 17 + * - `"serviceSwitched"` — failed over to a different service URL (emits `{ from, to, cycle }`) 18 + */ 19 + export class WebSocketConnection extends EventEmitter { 20 + private services: string[]; 21 + private queryParams: Record<string, string | number | boolean>; 22 + private reconnectConfig: Required<ReconnectOptions>; 23 + private pingInterval: number; 24 + 25 + private ws: WebSocket | null = null; 26 + private pingTimer: NodeJS.Timeout | null = null; 27 + private reconnectTimer: NodeJS.Timeout | null = null; 28 + 29 + private serviceIndex = 0; 30 + private reconnectAttempts = 0; 31 + private serviceCycles = 0; 32 + private isConnecting = false; 33 + private shouldReconnect = true; 34 + private messageCount = 0; 35 + private lastMessageTime = 0; 36 + 37 + private logger: Logger; 38 + 39 + constructor(options: ConnectionOptions, logger: Logger) { 40 + super(); 41 + this.services = Array.isArray(options.service) ? options.service : [options.service]; 42 + this.queryParams = { ...options.queryParams }; 43 + this.pingInterval = options.pingInterval ?? 10000; 44 + this.logger = logger; 45 + 46 + const rc = options.reconnect ?? {}; 47 + this.reconnectConfig = { 48 + initialDelay: rc.initialDelay ?? 5000, 49 + maxDelay: rc.maxDelay ?? 30000, 50 + backoffFactor: rc.backoffFactor ?? 1.5, 51 + maxAttempts: rc.maxAttempts ?? 3, 52 + maxServiceCycles: rc.maxServiceCycles ?? 2, 53 + }; 54 + } 55 + 56 + /** 57 + * Opens the WebSocket connection. Does nothing if already connecting or connected. 58 + */ 59 + connect(): void { 60 + if (this.isConnecting || this.getState() === "connected") { 61 + this.logger.debug("Connect called but already connecting/connected"); 62 + return; 63 + } 64 + this.shouldReconnect = true; 65 + this.run(); 66 + } 67 + 68 + /** 69 + * Closes the WebSocket connection gracefully and stops all reconnection attempts. 70 + */ 71 + close(): void { 72 + this.shouldReconnect = false; 73 + this.stopHeartbeat(); 74 + this.clearReconnectTimer(); 75 + 76 + if (this.ws) { 77 + try { 78 + this.ws.close(); 79 + } catch (error) { 80 + this.logger.error("Error closing WebSocket", error); 81 + } 82 + } 83 + } 84 + 85 + /** 86 + * Sends raw data through the WebSocket. 87 + * @returns `true` if the data was sent, `false` if the connection is not open. 88 + */ 89 + send(data: string | Buffer): boolean { 90 + if (this.ws && this.ws.readyState === WebSocket.OPEN) { 91 + try { 92 + this.ws.send(data); 93 + return true; 94 + } catch (error) { 95 + this.logger.error("Error sending data", error); 96 + return false; 97 + } 98 + } 99 + this.logger.debug("Cannot send: WebSocket not connected"); 100 + return false; 101 + } 102 + 103 + /** 104 + * Merges new query parameters into the current set and reconnects 105 + * so the updated URL takes effect. 106 + */ 107 + updateParams(params: Record<string, string | number | boolean>): void { 108 + this.queryParams = { ...this.queryParams, ...params }; 109 + this.logger.info("Query params updated, reconnecting", { params: this.queryParams }); 110 + 111 + // Graceful reconnect: close current, then re-run 112 + this.cleanup(); 113 + this.reconnectAttempts = 0; 114 + this.serviceCycles = 0; 115 + this.run(); 116 + } 117 + 118 + /** 119 + * Returns a read-only snapshot of the current connection state. 120 + */ 121 + getConnectionInfo(): ConnectionInfo { 122 + return { 123 + state: this.getState(), 124 + currentService: this.getCurrentServiceUrl(), 125 + serviceIndex: this.serviceIndex, 126 + allServices: [...this.services], 127 + reconnectAttempts: this.reconnectAttempts, 128 + serviceCycles: this.serviceCycles, 129 + messageCount: this.messageCount, 130 + lastMessageTime: this.lastMessageTime, 131 + }; 132 + } 133 + 134 + // --------------------------------------------------------------------------- 135 + // Private 136 + // --------------------------------------------------------------------------- 137 + 138 + private getState(): ConnectionState { 139 + if (!this.ws) return "closed"; 140 + switch (this.ws.readyState) { 141 + case WebSocket.CONNECTING: 142 + return "connecting"; 143 + case WebSocket.OPEN: 144 + return "connected"; 145 + case WebSocket.CLOSING: 146 + return "closing"; 147 + default: 148 + return "closed"; 149 + } 150 + } 151 + 152 + private getCurrentServiceUrl(): string { 153 + return this.buildUrl(this.services[this.serviceIndex]); 154 + } 155 + 156 + private buildUrl(base: string): string { 157 + if (Object.keys(this.queryParams).length === 0) { 158 + return base; 159 + } 160 + 161 + const url = new URL(base); 162 + for (const [key, value] of Object.entries(this.queryParams)) { 163 + url.searchParams.set(key, String(value)); 164 + } 165 + return url.toString(); 166 + } 167 + 168 + private run(): void { 169 + if (this.isConnecting) { 170 + return; 171 + } 172 + 173 + this.isConnecting = true; 174 + const serviceUrl = this.getCurrentServiceUrl(); 175 + 176 + try { 177 + this.logger.info("Connecting to WebSocket", { service: serviceUrl }); 178 + this.ws = new WebSocket(serviceUrl); 179 + 180 + this.ws.on("open", () => { 181 + this.isConnecting = false; 182 + this.reconnectAttempts = 0; 183 + this.serviceCycles = 0; 184 + this.startHeartbeat(); 185 + this.logger.info("WebSocket connected", { service: serviceUrl }); 186 + this.emit("open"); 187 + }); 188 + 189 + this.ws.on("message", (data: WebSocket.Data) => { 190 + this.messageCount++; 191 + this.lastMessageTime = Date.now(); 192 + this.emit("message", data); 193 + }); 194 + 195 + this.ws.on("error", (error: Error) => { 196 + this.logger.error("WebSocket error", error); 197 + this.isConnecting = false; 198 + this.emit("error", error); 199 + }); 200 + 201 + this.ws.on("close", (code: number, reason: Buffer) => { 202 + const reasonStr = reason.toString(); 203 + this.logger.info("WebSocket disconnected", { code, reason: reasonStr }); 204 + this.isConnecting = false; 205 + this.stopHeartbeat(); 206 + this.emit("close", { code, reason: reasonStr }); 207 + 208 + if (this.shouldReconnect) { 209 + this.scheduleReconnect(); 210 + } 211 + }); 212 + } catch (error) { 213 + this.logger.error("Error creating WebSocket", error); 214 + this.isConnecting = false; 215 + if (this.shouldReconnect) { 216 + this.scheduleReconnect(); 217 + } 218 + } 219 + } 220 + 221 + private scheduleReconnect(): void { 222 + this.reconnectAttempts++; 223 + 224 + if (this.reconnectAttempts >= this.reconnectConfig.maxAttempts) { 225 + if (this.canTryNextService()) { 226 + this.moveToNextService(); 227 + return; 228 + } 229 + this.logger.error("All services exhausted", { 230 + totalServices: this.services.length, 231 + maxCycles: this.reconnectConfig.maxServiceCycles, 232 + completedCycles: this.serviceCycles, 233 + }); 234 + return; 235 + } 236 + 237 + const delay = Math.min( 238 + this.reconnectConfig.initialDelay * 239 + Math.pow(this.reconnectConfig.backoffFactor, this.reconnectAttempts - 1), 240 + this.reconnectConfig.maxDelay 241 + ); 242 + 243 + this.logger.info("Scheduling reconnection", { 244 + attempt: this.reconnectAttempts, 245 + maxAttempts: this.reconnectConfig.maxAttempts, 246 + delay: `${Math.round(delay)}ms`, 247 + service: this.getCurrentServiceUrl(), 248 + }); 249 + 250 + this.emit("reconnecting", { 251 + attempt: this.reconnectAttempts, 252 + maxAttempts: this.reconnectConfig.maxAttempts, 253 + delay: Math.round(delay), 254 + service: this.getCurrentServiceUrl(), 255 + }); 256 + 257 + this.clearReconnectTimer(); 258 + this.reconnectTimer = setTimeout(() => { 259 + this.cleanup(); 260 + this.run(); 261 + }, delay); 262 + } 263 + 264 + private canTryNextService(): boolean { 265 + return this.services.length > 1 && this.serviceCycles < this.reconnectConfig.maxServiceCycles; 266 + } 267 + 268 + private moveToNextService(): void { 269 + const previousIndex = this.serviceIndex; 270 + this.serviceIndex = (this.serviceIndex + 1) % this.services.length; 271 + 272 + if (this.serviceIndex === 0) { 273 + this.serviceCycles++; 274 + } 275 + 276 + this.reconnectAttempts = 0; 277 + 278 + const from = this.services[previousIndex]; 279 + const to = this.services[this.serviceIndex]; 280 + this.logger.info("Switching service", { from, to, cycle: this.serviceCycles }); 281 + this.emit("serviceSwitched", { from, to, cycle: this.serviceCycles }); 282 + 283 + this.cleanup(); 284 + this.run(); 285 + } 286 + 287 + private cleanup(): void { 288 + if (this.ws) { 289 + this.ws.removeAllListeners(); 290 + if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) { 291 + try { 292 + this.ws.close(); 293 + } catch { 294 + // Swallow — we're cleaning up anyway 295 + } 296 + } 297 + this.ws = null; 298 + } 299 + this.clearReconnectTimer(); 300 + } 301 + 302 + private startHeartbeat(): void { 303 + this.stopHeartbeat(); 304 + this.pingTimer = setInterval(() => { 305 + if (this.ws && this.ws.readyState === WebSocket.OPEN) { 306 + try { 307 + this.ws.ping(); 308 + } catch (error) { 309 + this.logger.error("Error sending ping", error); 310 + } 311 + } 312 + }, this.pingInterval); 313 + } 314 + 315 + private stopHeartbeat(): void { 316 + if (this.pingTimer) { 317 + clearInterval(this.pingTimer); 318 + this.pingTimer = null; 319 + } 320 + } 321 + 322 + private clearReconnectTimer(): void { 323 + if (this.reconnectTimer) { 324 + clearTimeout(this.reconnectTimer); 325 + this.reconnectTimer = null; 326 + } 327 + } 328 + }
+57
src/connection/types.ts
··· 1 + /** 2 + * Configuration for reconnection behavior. 3 + */ 4 + export interface ReconnectOptions { 5 + /** Base delay in ms before the first reconnection attempt. Default: `5000`. */ 6 + initialDelay?: number; 7 + /** Maximum delay in ms between reconnection attempts (caps exponential backoff). Default: `30000`. */ 8 + maxDelay?: number; 9 + /** Multiplier applied to the delay after each failed attempt. Default: `1.5`. */ 10 + backoffFactor?: number; 11 + /** Maximum consecutive reconnection attempts per service URL before switching. Default: `3`. */ 12 + maxAttempts?: number; 13 + /** Maximum number of full cycles through all service URLs before giving up. Default: `2`. */ 14 + maxServiceCycles?: number; 15 + } 16 + 17 + /** 18 + * Configuration for the WebSocket connection layer. 19 + */ 20 + export interface ConnectionOptions { 21 + /** One or more WebSocket service URLs. When multiple are provided, the connection fails over between them. */ 22 + service: string | string[]; 23 + /** Query parameters appended to the service URL. Values are serialized via `URLSearchParams`. */ 24 + queryParams?: Record<string, string | number | boolean>; 25 + /** Reconnection behavior configuration. */ 26 + reconnect?: ReconnectOptions; 27 + /** Interval in ms for sending WebSocket ping frames to keep the connection alive. Default: `10000`. */ 28 + pingInterval?: number; 29 + } 30 + 31 + /** 32 + * Possible states of a WebSocket connection. 33 + */ 34 + export type ConnectionState = "connecting" | "connected" | "closing" | "closed"; 35 + 36 + /** 37 + * Read-only snapshot of the current connection state. 38 + * Passed to handlers so they can inspect the connection without modifying it. 39 + */ 40 + export interface ConnectionInfo { 41 + /** Current connection state. */ 42 + state: ConnectionState; 43 + /** The service URL currently in use (including query params). */ 44 + currentService: string; 45 + /** Index of the current service in the service array. */ 46 + serviceIndex: number; 47 + /** All configured service URLs. */ 48 + allServices: string[]; 49 + /** Number of consecutive reconnection attempts for the current service. */ 50 + reconnectAttempts: number; 51 + /** Number of completed full cycles through all services. */ 52 + serviceCycles: number; 53 + /** Total number of messages received since the connection was created. */ 54 + messageCount: number; 55 + /** Unix timestamp (ms) of the last received message, or `0` if none received. */ 56 + lastMessageTime: number; 57 + }
+19
src/index.ts
··· 1 + export { WebSocketClient } from "./WebSocketClient"; 2 + export { WebSocketClientOptions } from "./types"; 3 + 4 + export { Logger, LogLevel } from "./logger/Logger"; 5 + export type { LoggerInterface, LoggerOptions } from "./logger/Logger"; 6 + 7 + export type { 8 + ConnectionOptions, 9 + ConnectionState, 10 + ConnectionInfo, 11 + ReconnectOptions, 12 + } from "./connection/types"; 13 + 14 + export type { 15 + HandlerContext, 16 + MessageHandler, 17 + HandlerRegistration, 18 + HandlerError, 19 + } from "./router/types";
+102
src/logger/Logger.ts
··· 1 + /** 2 + * Log severity levels, ordered from most verbose to least verbose. 3 + * Used to filter which messages are emitted by the logger. 4 + */ 5 + export enum LogLevel { 6 + DEBUG = 0, 7 + INFO = 1, 8 + WARN = 2, 9 + ERROR = 3, 10 + } 11 + 12 + /** 13 + * Interface for custom logger implementations. 14 + * Provide an object matching this interface to replace the built-in console logger. 15 + */ 16 + export interface LoggerInterface { 17 + debug(message: string, context?: unknown): void; 18 + info(message: string, context?: unknown): void; 19 + warn(message: string, context?: unknown): void; 20 + error(message: string, context?: unknown): void; 21 + } 22 + 23 + /** 24 + * Configuration options for the logger. 25 + */ 26 + export interface LoggerOptions { 27 + /** Whether logging is enabled. Default: `true`. */ 28 + enabled?: boolean; 29 + /** Minimum log level to emit. Messages below this level are discarded. Default: `LogLevel.INFO`. */ 30 + level?: LogLevel; 31 + /** Custom logger implementation. When provided, all log calls are delegated to it. */ 32 + custom?: LoggerInterface; 33 + } 34 + 35 + /** 36 + * Internal logger used throughout the library. 37 + * Each `WebSocketClient` instance creates its own `Logger` so configuration is isolated. 38 + * 39 + * @example 40 + * ```typescript 41 + * const logger = new Logger({ enabled: true, level: LogLevel.DEBUG }); 42 + * logger.info("Connected", { service: "wss://example.com" }); 43 + * ``` 44 + */ 45 + export class Logger implements LoggerInterface { 46 + private enabled: boolean; 47 + private level: LogLevel; 48 + private custom?: LoggerInterface; 49 + 50 + constructor(options: LoggerOptions = {}) { 51 + this.enabled = options.enabled ?? true; 52 + this.level = options.level ?? LogLevel.INFO; 53 + this.custom = options.custom; 54 + } 55 + 56 + debug(message: string, context?: unknown): void { 57 + this.log(LogLevel.DEBUG, message, context); 58 + } 59 + 60 + info(message: string, context?: unknown): void { 61 + this.log(LogLevel.INFO, message, context); 62 + } 63 + 64 + warn(message: string, context?: unknown): void { 65 + this.log(LogLevel.WARN, message, context); 66 + } 67 + 68 + error(message: string, context?: unknown): void { 69 + this.log(LogLevel.ERROR, message, context); 70 + } 71 + 72 + private log(level: LogLevel, message: string, context?: unknown): void { 73 + if (!this.enabled || level < this.level) { 74 + return; 75 + } 76 + 77 + if (this.custom) { 78 + const method = LogLevel[level].toLowerCase() as keyof LoggerInterface; 79 + this.custom[method](message, context); 80 + return; 81 + } 82 + 83 + const timestamp = new Date().toISOString(); 84 + const tag = LogLevel[level]; 85 + const prefix = `[WAH ${tag}] ${timestamp}`; 86 + 87 + const logFn = 88 + level === LogLevel.ERROR 89 + ? console.error 90 + : level === LogLevel.WARN 91 + ? console.warn 92 + : level === LogLevel.DEBUG 93 + ? console.debug 94 + : console.log; 95 + 96 + if (context !== undefined) { 97 + logFn(prefix, message, context); 98 + } else { 99 + logFn(prefix, message); 100 + } 101 + } 102 + }
+106
src/router/WebSocketRouter.ts
··· 1 + import { EventEmitter } from "events"; 2 + import { z } from "zod"; 3 + import { ConnectionInfo } from "../connection/types"; 4 + import { HandlerRegistration, HandlerError, MessageHandler } from "./types"; 5 + import { Logger } from "../logger/Logger"; 6 + 7 + /** 8 + * Validates incoming WebSocket messages against registered Zod schemas 9 + * and dispatches to matching handlers. 10 + * 11 + * All handlers whose schema matches the incoming message are invoked 12 + * concurrently via `Promise.allSettled`. Handler errors are caught and 13 + * emitted as `"error"` events rather than propagating. 14 + * 15 + * Events emitted: 16 + * - `"error"` — a handler threw or JSON parsing failed (emits {@link HandlerError}) 17 + */ 18 + export class WebSocketRouter extends EventEmitter { 19 + private handlers: HandlerRegistration[] = []; 20 + private logger: Logger; 21 + 22 + constructor(logger: Logger) { 23 + super(); 24 + this.logger = logger; 25 + } 26 + 27 + /** 28 + * Registers a handler that will be invoked for every message matching the given schema. 29 + * 30 + * @typeParam T - The type inferred from the Zod schema. 31 + * @param schema - Zod schema to validate messages against. 32 + * @param handler - Function to call with the validated data. 33 + */ 34 + register<T>(schema: z.ZodSchema<T>, handler: MessageHandler<T>): void { 35 + this.handlers.push({ 36 + schema, 37 + handler: handler as MessageHandler<unknown>, 38 + }); 39 + this.logger.debug("Handler registered", { totalHandlers: this.handlers.length }); 40 + } 41 + 42 + /** 43 + * Processes a raw WebSocket message: parses JSON, validates against all 44 + * registered schemas, and invokes matching handlers. 45 + * 46 + * @param rawData - The raw message string from the WebSocket. 47 + * @param sendFn - Function to send data back through the WebSocket. 48 + * @param connectionInfo - Current connection state snapshot. 49 + */ 50 + async route( 51 + rawData: string, 52 + sendFn: (data: unknown) => boolean, 53 + connectionInfo: ConnectionInfo 54 + ): Promise<void> { 55 + let parsed: unknown; 56 + try { 57 + parsed = JSON.parse(rawData); 58 + } catch { 59 + this.logger.debug("Failed to parse message as JSON", { rawData: rawData.slice(0, 200) }); 60 + this.emit("error", { 61 + error: new Error("Failed to parse WebSocket message as JSON"), 62 + rawData, 63 + timestamp: Date.now(), 64 + } satisfies HandlerError); 65 + return; 66 + } 67 + 68 + const matched: Promise<void>[] = []; 69 + 70 + for (const registration of this.handlers) { 71 + const result = registration.schema.safeParse(parsed); 72 + if (result.success) { 73 + matched.push( 74 + this.invokeHandler(registration, result.data, rawData, sendFn, connectionInfo) 75 + ); 76 + } 77 + } 78 + 79 + if (matched.length === 0) { 80 + this.logger.debug("No handlers matched message"); 81 + return; 82 + } 83 + 84 + this.logger.debug("Dispatching to matched handlers", { count: matched.length }); 85 + await Promise.allSettled(matched); 86 + } 87 + 88 + private async invokeHandler( 89 + registration: HandlerRegistration, 90 + data: unknown, 91 + rawData: string, 92 + sendFn: (data: unknown) => boolean, 93 + connectionInfo: ConnectionInfo 94 + ): Promise<void> { 95 + try { 96 + await registration.handler({ data, rawData, send: sendFn, connection: connectionInfo }); 97 + } catch (error) { 98 + this.logger.error("Handler threw an error", error); 99 + this.emit("error", { 100 + error, 101 + rawData, 102 + timestamp: Date.now(), 103 + } satisfies HandlerError); 104 + } 105 + } 106 + }
+48
src/router/types.ts
··· 1 + import { z } from "zod"; 2 + import { ConnectionInfo } from "../connection/types"; 3 + 4 + /** 5 + * Context object passed to every matched handler. 6 + * Provides the validated message data, a send function for replies, 7 + * and read-only connection info. 8 + * 9 + * @typeParam T - The inferred type of the matched Zod schema. 10 + */ 11 + export interface HandlerContext<T> { 12 + /** The validated and typed message data. */ 13 + data: T; 14 + /** The original raw message string before parsing. */ 15 + rawData: string; 16 + /** Sends data back through the WebSocket. Returns `true` if sent, `false` if not connected. */ 17 + send: (data: unknown) => boolean; 18 + /** Read-only snapshot of the current connection state. */ 19 + connection: ConnectionInfo; 20 + } 21 + 22 + /** 23 + * A function that handles a message matching a specific schema. 24 + * Can be synchronous or asynchronous. 25 + * 26 + * @typeParam T - The inferred type of the matched Zod schema. 27 + */ 28 + export type MessageHandler<T> = (ctx: HandlerContext<T>) => void | Promise<void>; 29 + 30 + /** 31 + * Internal registration entry pairing a Zod schema with its handler. 32 + */ 33 + export interface HandlerRegistration { 34 + schema: z.ZodSchema; 35 + handler: MessageHandler<unknown>; 36 + } 37 + 38 + /** 39 + * Error information emitted when a handler throws during execution. 40 + */ 41 + export interface HandlerError { 42 + /** The error thrown by the handler. */ 43 + error: unknown; 44 + /** The raw message string that triggered the handler. */ 45 + rawData: string; 46 + /** Unix timestamp (ms) when the error occurred. */ 47 + timestamp: number; 48 + }
+11
src/types.ts
··· 1 + import { ConnectionOptions } from "./connection/types"; 2 + import { LoggerOptions } from "./logger/Logger"; 3 + 4 + /** 5 + * Configuration options for {@link WebSocketClient}. 6 + * Combines connection settings with logger configuration. 7 + */ 8 + export interface WebSocketClientOptions extends ConnectionOptions { 9 + /** Logger configuration. When omitted, logging is enabled at INFO level. */ 10 + logger?: LoggerOptions; 11 + }
+149
test-jetstream.ts
··· 1 + import { WebSocketClient, LogLevel } from "./src"; 2 + import { z } from "zod"; 3 + 4 + // --------------------------------------------------------------------------- 5 + // Jetstream message schemas 6 + // --------------------------------------------------------------------------- 7 + 8 + const commitCreateSchema = z.object({ 9 + did: z.string(), 10 + time_us: z.number(), 11 + kind: z.literal("commit"), 12 + commit: z.object({ 13 + rev: z.string(), 14 + operation: z.literal("create"), 15 + collection: z.literal("app.bsky.feed.post"), 16 + rkey: z.string(), 17 + record: z.object({ 18 + $type: z.literal("app.bsky.feed.post"), 19 + text: z.string(), 20 + createdAt: z.string(), 21 + langs: z.array(z.string()).optional(), 22 + reply: z 23 + .object({ 24 + root: z.object({ cid: z.string(), uri: z.string() }), 25 + parent: z.object({ cid: z.string(), uri: z.string() }), 26 + }) 27 + .optional(), 28 + }), 29 + cid: z.string(), 30 + }), 31 + }); 32 + 33 + const identitySchema = z.object({ 34 + did: z.string(), 35 + time_us: z.number(), 36 + kind: z.literal("identity"), 37 + identity: z.object({ 38 + did: z.string(), 39 + handle: z.string(), 40 + seq: z.number(), 41 + time: z.string(), 42 + }), 43 + }); 44 + 45 + const accountSchema = z.object({ 46 + did: z.string(), 47 + time_us: z.number(), 48 + kind: z.literal("account"), 49 + account: z.object({ 50 + active: z.boolean(), 51 + did: z.string(), 52 + seq: z.number(), 53 + time: z.string(), 54 + }), 55 + }); 56 + 57 + // --------------------------------------------------------------------------- 58 + // Counters for summary 59 + // --------------------------------------------------------------------------- 60 + 61 + let postCount = 0; 62 + let identityCount = 0; 63 + let accountCount = 0; 64 + 65 + // --------------------------------------------------------------------------- 66 + // Create client — subscribe only to posts, identities, and accounts 67 + // --------------------------------------------------------------------------- 68 + 69 + const client = new WebSocketClient({ 70 + service: [ 71 + "wss://jetstream1.us-east.bsky.network/subscribe", 72 + "wss://jetstream2.us-east.bsky.network/subscribe", 73 + "wss://jetstream1.us-west.bsky.network/subscribe", 74 + "wss://jetstream2.us-west.bsky.network/subscribe", 75 + ], 76 + queryParams: { 77 + wantedCollections: "app.bsky.feed.post", 78 + }, 79 + reconnect: { 80 + maxAttempts: 3, 81 + initialDelay: 3000, 82 + backoffFactor: 1.5, 83 + maxServiceCycles: 2, 84 + }, 85 + pingInterval: 15000, 86 + logger: { enabled: true, level: LogLevel.INFO }, 87 + }); 88 + 89 + // --------------------------------------------------------------------------- 90 + // Register handlers 91 + // --------------------------------------------------------------------------- 92 + 93 + client.handle(commitCreateSchema, ({ data }) => { 94 + postCount++; 95 + const text = data.commit.record.text.slice(0, 80).replace(/\n/g, " "); 96 + const langs = data.commit.record.langs?.join(",") ?? "?"; 97 + const isReply = data.commit.record.reply ? " [reply]" : ""; 98 + console.log(`[POST #${postCount}] (${langs})${isReply} ${text}`); 99 + }); 100 + 101 + client.handle(identitySchema, ({ data }) => { 102 + identityCount++; 103 + console.log(`[IDENTITY] ${data.identity.handle} (${data.identity.did})`); 104 + }); 105 + 106 + client.handle(accountSchema, ({ data }) => { 107 + accountCount++; 108 + const status = data.account.active ? "active" : "inactive"; 109 + console.log(`[ACCOUNT] ${data.account.did} → ${status}`); 110 + }); 111 + 112 + // --------------------------------------------------------------------------- 113 + // Connection events 114 + // --------------------------------------------------------------------------- 115 + 116 + client.on("open", () => { 117 + console.log("\n--- Connected to Jetstream ---\n"); 118 + }); 119 + 120 + client.on("close", (info) => { 121 + console.log("\n--- Disconnected ---", info); 122 + }); 123 + 124 + client.on("error", (err) => { 125 + console.error("[ERROR]", err); 126 + }); 127 + 128 + client.on("reconnecting", (info) => { 129 + console.log("[RECONNECTING]", info); 130 + }); 131 + 132 + client.on("serviceSwitched", (info) => { 133 + console.log("[SERVICE SWITCHED]", info); 134 + }); 135 + 136 + // --------------------------------------------------------------------------- 137 + // Connect and auto-close after 15 seconds 138 + // --------------------------------------------------------------------------- 139 + 140 + console.log("Starting Jetstream test — will run for 15 seconds...\n"); 141 + client.connect(); 142 + 143 + setTimeout(() => { 144 + client.close(); 145 + console.log("\n--- Test complete ---"); 146 + console.log(`Posts: ${postCount}, Identities: ${identityCount}, Accounts: ${accountCount}`); 147 + console.log(`Total messages received: ${client.getConnectionInfo().messageCount}`); 148 + process.exit(0); 149 + }, 15000);
+22
tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "lib": ["ESNext"], 4 + "outDir": "dist", 5 + "module": "CommonJS", 6 + "target": "ES2020", 7 + "esModuleInterop": true, 8 + "moduleResolution": "node", 9 + "strict": true, 10 + "alwaysStrict": true, 11 + "noUnusedLocals": true, 12 + "noUnusedParameters": true, 13 + "noImplicitReturns": true, 14 + "allowUnreachableCode": false, 15 + "skipLibCheck": true, 16 + "declaration": true, 17 + "declarationMap": true, 18 + "resolveJsonModule": true 19 + }, 20 + "include": ["src/**/*.ts"], 21 + "exclude": ["node_modules", "dist"] 22 + }
+12
tsup.config.ts
··· 1 + import { defineConfig } from "tsup"; 2 + 3 + export default defineConfig({ 4 + entry: ["src/index.ts"], 5 + format: ["cjs", "esm"], 6 + dts: true, 7 + splitting: false, 8 + sourcemap: true, 9 + clean: true, 10 + target: "es2020", 11 + outDir: "dist", 12 + });