├── .editorconfig ├── .gitattributes ├── .github ├── actions │ └── prepare-runner │ │ └── action.yml └── workflows │ ├── lint.yml │ ├── publish.yml │ ├── size-limit.yml │ ├── test.yml │ └── typecheck.yml ├── .gitignore ├── .node-version ├── .oxlintrc.json ├── .prettierignore ├── .size-limit.ts ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── LICENSE.md ├── README.md ├── demo ├── .presentation-buddy │ └── instructions.json ├── .vscode │ ├── extensions.json │ └── settings.json ├── package.json ├── pnpm-lock.yaml └── src │ ├── README.md │ ├── client.ts │ ├── types.ts │ └── worker.ts ├── examples ├── BroadcastChannel │ ├── index.html │ └── main.ts ├── DenoEventTarget │ ├── deno.json │ ├── deno.lock │ └── main.ts ├── EventTarget │ ├── index.html │ └── main.ts ├── NodeEventEmitter │ ├── main.ts │ ├── package-lock.json │ ├── package.json │ └── pnpm-lock.yaml ├── Worker │ ├── index.html │ ├── main.ts │ ├── types.ts │ └── worker.ts └── index.html ├── jsr.json ├── package.json ├── pnpm-lock.yaml ├── prettier.config.cjs ├── scripts └── bump.sh ├── src ├── createTypedChannel.test.ts ├── createTypedChannel.ts ├── index.ts ├── transports │ ├── eventTarget.test.ts │ ├── eventTarget.ts │ ├── postMessage.test.ts │ └── postMessage.ts └── types.ts ├── tsconfig.json ├── tsup.config.ts └── vitest.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/actions/prepare-runner/action.yml: -------------------------------------------------------------------------------- 1 | name: "Setup runner" 2 | description: "It setups node, install pnpm packages and caches them" 3 | runs: 4 | using: composite 5 | steps: 6 | - name: Install Node.js 7 | uses: actions/setup-node@v4 8 | with: 9 | node-version: 22 10 | - run: corepack enable 11 | shell: bash 12 | - name: Get pnpm store directory 13 | id: pnpm-cache 14 | shell: bash 15 | run: | 16 | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT 17 | 18 | - uses: actions/cache@v4 19 | name: Setup pnpm cache 20 | with: 21 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 22 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 23 | restore-keys: | 24 | ${{ runner.os }}-pnpm-store- 25 | 26 | - name: Install dependencies 27 | shell: bash 28 | run: pnpm install --frozen-lockfile 29 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint code 2 | on: 3 | push: 4 | branches: 5 | - "**" 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | lint: 12 | runs-on: ubuntu-latest 13 | env: 14 | CI_RUN: true 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: ./.github/actions/prepare-runner 18 | - run: pnpm lint 19 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish new versions to registries 2 | 3 | on: 4 | push: 5 | tags: 6 | - "[0-9]+.[0-9]+.[0-9]+" # Match semantic version tags (e.g. 1.2.3) 7 | env: 8 | CI_RUN: true 9 | jobs: 10 | publish-jsr: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | id-token: write # The OIDC ID token is used for authentication with JSR. 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: ./.github/actions/prepare-runner 18 | - run: pnpm dlx jsr publish 19 | publish-npm: 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: read 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: ./.github/actions/prepare-runner 26 | - run: pnpm build 27 | - run: | 28 | echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc 29 | env: 30 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 31 | - run: pnpm publish --no-git-checks 32 | -------------------------------------------------------------------------------- /.github/workflows/size-limit.yml: -------------------------------------------------------------------------------- 1 | name: Check bundle size 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref }} 8 | cancel-in-progress: true 9 | 10 | permissions: 11 | pull-requests: write 12 | 13 | jobs: 14 | sizecheck: 15 | runs-on: ubuntu-latest 16 | env: 17 | CI_RUN: true 18 | CI_JOB_NUMBER: 1 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: andresz1/size-limit-action@v1 22 | with: 23 | github_token: ${{ secrets.GITHUB_TOKEN }} 24 | package_manager: pnpm 25 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | on: 3 | push: 4 | branches: 5 | - "**" 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | env: 14 | CI_RUN: true 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: ./.github/actions/prepare-runner 18 | - run: pnpm test 19 | -------------------------------------------------------------------------------- /.github/workflows/typecheck.yml: -------------------------------------------------------------------------------- 1 | name: Check types 2 | on: 3 | push: 4 | branches: 5 | - "**" 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | typecheck: 12 | runs-on: ubuntu-latest 13 | env: 14 | CI_RUN: true 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: ./.github/actions/prepare-runner 18 | - run: pnpm typecheck 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | *.local 13 | 14 | # Editor directories and files 15 | .idea 16 | .DS_Store 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | 23 | # Build cache manifests 24 | *.tsbuildinfo 25 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /.oxlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/oxlint/configuration_schema.json", 3 | "plugins": ["unicorn", "typescript", "oxc"], 4 | "categories": {}, 5 | "rules": { 6 | "for-direction": "warn", 7 | "no-async-promise-executor": "warn", 8 | "no-caller": "warn", 9 | "no-class-assign": "warn", 10 | "no-compare-neg-zero": "warn", 11 | "no-cond-assign": "warn", 12 | "no-const-assign": "warn", 13 | "no-constant-binary-expression": "warn", 14 | "no-constant-condition": "warn", 15 | "no-control-regex": "warn", 16 | "no-debugger": "warn", 17 | "no-delete-var": "warn", 18 | "no-dupe-class-members": "warn", 19 | "no-dupe-else-if": "warn", 20 | "no-dupe-keys": "warn", 21 | "no-duplicate-case": "warn", 22 | "no-empty-character-class": "warn", 23 | "no-empty-pattern": "warn", 24 | "no-empty-static-block": "warn", 25 | "no-eval": "warn", 26 | "no-ex-assign": "warn", 27 | "no-extra-boolean-cast": "warn", 28 | "no-func-assign": "warn", 29 | "no-global-assign": "warn", 30 | "no-import-assign": "warn", 31 | "no-invalid-regexp": "warn", 32 | "no-irregular-whitespace": "warn", 33 | "no-loss-of-precision": "warn", 34 | "no-new-native-nonconstructor": "warn", 35 | "no-nonoctal-decimal-escape": "warn", 36 | "no-obj-calls": "warn", 37 | "no-self-assign": "warn", 38 | "no-setter-return": "warn", 39 | "no-shadow-restricted-names": "warn", 40 | "no-sparse-arrays": "warn", 41 | "no-this-before-super": "warn", 42 | "no-unsafe-finally": "warn", 43 | "no-unsafe-negation": "warn", 44 | "no-unsafe-optional-chaining": "warn", 45 | "no-unused-labels": "warn", 46 | "no-unused-private-class-members": "warn", 47 | "no-unused-vars": "warn", 48 | "no-useless-catch": "warn", 49 | "no-useless-escape": "warn", 50 | "no-useless-rename": "warn", 51 | "no-with": "warn", 52 | "require-yield": "warn", 53 | "use-isnan": "warn", 54 | "valid-typeof": "warn", 55 | "oxc/bad-array-method-on-arguments": "warn", 56 | "oxc/bad-char-at-comparison": "warn", 57 | "oxc/bad-comparison-sequence": "warn", 58 | "oxc/bad-min-max-func": "warn", 59 | "oxc/bad-object-literal-comparison": "warn", 60 | "oxc/bad-replace-all-arg": "warn", 61 | "oxc/const-comparisons": "warn", 62 | "oxc/double-comparisons": "warn", 63 | "oxc/erasing-op": "warn", 64 | "oxc/missing-throw": "warn", 65 | "oxc/number-arg-out-of-range": "warn", 66 | "oxc/only-used-in-recursion": "warn", 67 | "oxc/uninvoked-array-callback": "warn", 68 | "typescript/no-duplicate-enum-values": "warn", 69 | "typescript/no-extra-non-null-assertion": "warn", 70 | "typescript/no-misused-new": "warn", 71 | "typescript/no-non-null-asserted-optional-chain": "warn", 72 | "typescript/no-this-alias": "warn", 73 | "typescript/no-unnecessary-parameter-property-assignment": "warn", 74 | "typescript/no-unsafe-declaration-merging": "warn", 75 | "typescript/no-useless-empty-export": "warn", 76 | "typescript/no-wrapper-object-types": "warn", 77 | "typescript/prefer-as-const": "warn", 78 | "typescript/triple-slash-reference": "warn", 79 | "unicorn/no-await-in-promise-methods": "warn", 80 | "unicorn/no-empty-file": "warn", 81 | "unicorn/no-invalid-fetch-options": "warn", 82 | "unicorn/no-invalid-remove-event-listener": "warn", 83 | "unicorn/no-new-array": "warn", 84 | "unicorn/no-single-promise-in-promise-methods": "warn", 85 | "unicorn/no-thenable": "warn", 86 | "unicorn/no-unnecessary-await": "warn", 87 | "unicorn/no-useless-fallback-in-spread": "warn", 88 | "unicorn/no-useless-length-check": "warn", 89 | "unicorn/no-useless-spread": "warn", 90 | "unicorn/prefer-set-size": "warn", 91 | "unicorn/prefer-string-starts-ends-with": "warn" 92 | }, 93 | "settings": { 94 | "jsdoc": { 95 | "ignorePrivate": false, 96 | "ignoreInternal": false, 97 | "ignoreReplacesDocs": true, 98 | "overrideReplacesDocs": true, 99 | "augmentsExtendsReplacesDocs": false, 100 | "implementsReplacesDocs": false, 101 | "exemptDestructuredRootsFromChecks": false, 102 | "tagNamePreference": {} 103 | } 104 | }, 105 | "env": { 106 | "builtin": true 107 | }, 108 | "globals": {}, 109 | "ignorePatterns": ["demo"] 110 | } 111 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/.tmp 3 | **/dist 4 | 5 | pnpm-lock.yaml 6 | -------------------------------------------------------------------------------- /.size-limit.ts: -------------------------------------------------------------------------------- 1 | import type { SizeLimitConfig } from "size-limit"; 2 | 3 | module.exports = [ 4 | { 5 | name: "createTypedChannel", 6 | path: "dist/index.js", 7 | import: "{ createTypedChannel }", 8 | limit: "254 B", 9 | }, 10 | { 11 | name: "createPostMessageTransport", 12 | path: "dist/index.js", 13 | import: "{ createPostMessageTransport }", 14 | limit: "86 B", 15 | }, 16 | { 17 | name: "createEventTargetTransport", 18 | path: "dist/index.js", 19 | import: "{ createEventTargetTransport }", 20 | limit: "109 B", 21 | }, 22 | ] satisfies SizeLimitConfig; 23 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["oxc.oxc-vscode", "esbenp.prettier-vscode", "vitest.explorer"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Debug Vitest Tests", 11 | "autoAttachChildProcesses": true, 12 | "skipFiles": ["/**", "**/node_modules/**"], 13 | "program": "${workspaceFolder}/node_modules/vitest/vitest.mjs", 14 | "args": ["run", "${file}"], 15 | "smartStep": true, 16 | "console": "integratedTerminal" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.enablePaths": ["examples/DenoEventTarget"], 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll": "explicit" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2025 Pavel Grinchenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # typed-channel 2 | 3 | A type-safe communication channel for sending and receiving messages between different contexts in a TypeScript environment. 4 | 5 | [![npm version](https://img.shields.io/npm/v/typed-channel.svg)](https://www.npmjs.com/package/typed-channel) 6 | [![Bundle size](https://img.shields.io/badge/Bundle_size-from_363_B-brightgreen)](https://github.com/psd-coder/typed-channel/blob/main/.size-limit.ts) 7 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 8 | 9 | ## Features 10 | 11 | - ✅ Full TypeScript support with type checking for messages 12 | - ✅ Multiple transport implementations (EventTarget, PostMessage, your own custom implementation) 13 | - ✅ Simple, lightweight API 14 | - ✅ Zero dependencies 15 | 16 | ## Video presentation 17 | 18 | https://github.com/user-attachments/assets/d68aa980-6b95-4bff-ad12-e591842e03cb 19 | 20 | ## Installation 21 | 22 | ```bash 23 | # npm 24 | npm install typed-channel 25 | 26 | # pnpm 27 | pnpm add typed-channel 28 | 29 | # yarn 30 | yarn add typed-channel 31 | ``` 32 | 33 | ## Core Concepts 34 | 35 | `typed-channel` provides a simple API for creating type-safe communication channels between different contexts in your application. This is particularly useful for: 36 | 37 | - Communication between main thread and Web Workers 38 | - Cross-frame communication using postMessage 39 | - Communication between different parts of your application via events 40 | - Any other event-based communication 41 | 42 | ## Usage 43 | 44 | Here we are using the `EventTarget` transport to create a simple typed event emitter: 45 | 46 | ### Step 1: Define your message types 47 | 48 | ```typescript 49 | // Define the messages that can be sent through the channel 50 | type Messages = { 51 | // Message name : payload type 52 | notify: { message: string }; 53 | clear: never; // Use 'never' for messages with no payload 54 | }; 55 | ``` 56 | 57 | ### Step 2: Choose a transport 58 | 59 | ```typescript 60 | import { createTypedChannel } from "typed-channel"; 61 | import { createEventTargetTransport } from "typed-channel/transports/eventTarget"; 62 | 63 | // Create a transport appropriate for your use case 64 | const transport = createEventTargetTransport(); 65 | // Create the channel with your type definitions 66 | const channel = createTypedChannel(transport); 67 | ``` 68 | 69 | ### Step 3: Send and receive messages 70 | 71 | ```typescript 72 | // Type-safe event handling 73 | const unsubscribeNotify = eventEmitter.on("notify", ({ message }) => { 74 | console.log(`Received notification with message: ${message}`); 75 | }); 76 | const unsubscribeClear = eventEmitter.on("clear", () => { 77 | console.clear(); 78 | }); 79 | 80 | // Type-safe event emission 81 | eventEmitter.emit("notify", { message: "Application is ready" }); 82 | setTimeout(() => { 83 | eventEmitter.emit("clear"); 84 | // Remove all listeners 85 | unsubscribeNotify(); 86 | unsubscribeClear(); 87 | }, 1000); 88 | ``` 89 | 90 | ## Unidirectional vs Bidirectional Transports 91 | 92 | Transports can be configured in two ways: 93 | 94 | - **Unidirectional**: Same message types in both directions 95 | - **Bidirectional**: Different message types for incoming and outgoing communications 96 | 97 | Unidirectional transports are ideal for event buses within the same context, while bidirectional transports excel when communicating between different contexts (like main thread and worker). 98 | 99 | Here's how to use both approaches: 100 | 101 | ```typescript 102 | // Unidirectional: Same message types for both directions (default) 103 | const uniTransport = createEventTargetTransport<{ start: never; stop: never }>(); 104 | 105 | // Can receive and emit the same set of messages 106 | uniTransport.on("start", () => {}); 107 | uniTransport.on("stop", () => {}); 108 | uniTransport.emit("start"); 109 | uniTransport.emit("stop"); 110 | 111 | // Bidirectional: Different message types for inbound and outbound 112 | type InboundMessages = { 113 | status: { code: number; message: string }; 114 | data: { items: unknown[] }; 115 | }; 116 | 117 | type OutboundMessages = { 118 | fetch: { id: string }; 119 | cancel: never; 120 | }; 121 | 122 | // Explicitly defining different types for incoming and outgoing messages 123 | const biTransport = createPostMessageTransport(worker); 124 | 125 | // Can receive only inbound messages 126 | biTransport.on("status", ({ code, message }) => {}); 127 | biTransport.on("data", ({ items }) => {}); 128 | 129 | // Can emit only outbound messages 130 | biTransport.emit("fetch", { id: "123" }); 131 | biTransport.emit("cancel"); 132 | ``` 133 | 134 | ## Multiple Transports 135 | 136 | You can combine multiple transports to send messages to different targets: 137 | 138 | ```typescript 139 | import { createTypedChannel } from "typed-channel"; 140 | import { createEventTargetTransport } from "typed-channel/transports/eventTarget"; 141 | import { createPostMessageTransport } from "typed-channel/transports/postMessage"; 142 | 143 | const broadcastChannel = new BroadcastChannel("example-channel"); 144 | const broadcastTransport = createPostMessageTransport(broadcastChannel); 145 | const localTransport = createEventTargetTransport(); 146 | 147 | // Messages will be sent to all tabs, including current one 148 | const channel = createTypedChannel([localTransport, broadcastTransport]); 149 | ``` 150 | 151 | ## Available Transports 152 | 153 | ### EventTarget Transport 154 | 155 | Uses the standard DOM `EventTarget` interface for communication. 156 | 157 | ```typescript 158 | import { createEventTargetTransport } from "typed-channel/transports/eventTarget"; 159 | 160 | // Uses a new EventTarget instance by default 161 | const transport = createEventTargetTransport(); 162 | 163 | // Or provide your own EventTarget 164 | const customTarget = new EventTarget(); 165 | const transport = createEventTargetTransport(customTarget); 166 | ``` 167 | 168 | ### PostMessage Transport 169 | 170 | For communication with Workers, iframes, or other such contexts. 171 | 172 | ```typescript 173 | import { createPostMessageTransport } from "typed-channel/transports/postMessage"; 174 | 175 | // With a Worker 176 | const worker = new Worker("./worker.js"); 177 | const transport = createPostMessageTransport(worker); 178 | 179 | // With an iframe 180 | const iframe = document.querySelector("iframe"); 181 | const transport = createPostMessageTransport(iframe.contentWindow); 182 | ``` 183 | 184 | ### BroadcastChannel Transport 185 | 186 | For communication between different tabs/windows of the same origin. 187 | 188 | ```typescript 189 | import { createPostMessageTransport } from "typed-channel/transports/postMessage"; 190 | 191 | const broadcastChannel = new BroadcastChannel("example-channel"); 192 | const transport = createPostMessageTransport(broadcastChannel); 193 | ``` 194 | 195 | ## Custom Transports 196 | 197 | You can create custom transports by implementing the `TypedChannelTransport` interface. This gives you flexibility to adapt typed-channel to any messaging system. 198 | 199 | Here's a simple example of a custom transport structure: 200 | 201 | ```typescript 202 | import { type AnyMessageOf, type AnyMessages, type TypedChannelTransport } from "typed-channel"; 203 | 204 | function createNewTransport(): TypedChannelTransport { 205 | // implementation details 206 | function on(handler: (message: AnyMessageOf) => void) { 207 | handler(messageFromSomeSource); // call handler with message data coming from transport 208 | return () => {}; // return cleanup function 209 | } 210 | 211 | function emit(message: AnyMessageOf) { 212 | // pass emitted message to the transport 213 | } 214 | 215 | return { on, emit }; 216 | } 217 | ``` 218 | 219 | ### Advanced Example: Figma Plugin Communication 220 | 221 | Here's a real-world example of custom transport for Figma plugin UI communication: 222 | 223 | ```typescript 224 | import type { PluginMessages, UIMessages } from "./types"; 225 | import { 226 | type AnyMessageOf, 227 | type AnyMessages, 228 | createTypedChannel, 229 | type TypedChannelTransport, 230 | } from "typed-channel"; 231 | 232 | export type PluginMessages = { 233 | ready: never; 234 | }; 235 | 236 | export type UIMessages = { 237 | "window:resize": { width: number; height: number }; 238 | }; 239 | 240 | function createFigmaUiTransport< 241 | InboundMessages extends AnyMessages, 242 | OutboundMessages extends AnyMessages, 243 | >(): TypedChannelTransport { 244 | function on(handler: (message: AnyMessageOf) => void) { 245 | const workerMessageHandler = ( 246 | e: MessageEvent<{ pluginMessage: AnyMessageOf }>, 247 | ) => { 248 | handler(e.data.pluginMessage); 249 | }; 250 | 251 | globalThis.onmessage = workerMessageHandler; 252 | 253 | return () => (globalThis.onmessage = null); 254 | } 255 | 256 | function emit(message: AnyMessageOf) { 257 | parent.postMessage({ pluginMessage: message }, "*"); 258 | } 259 | 260 | return { on, emit }; 261 | } 262 | 263 | // Create the transport with appropriate type parameters 264 | const transport = createFigmaUiTransport(); 265 | 266 | // Create a typed communication channel using our custom transport 267 | export const pluginChannel = createTypedChannel(transport); 268 | ``` 269 | 270 | This advanced example shows how to create a custom transport for Figma plugins, where: 271 | 272 | 1. The `on` method wraps Figma's message handling convention (where messages arrive via `pluginMessage` property) 273 | 2. The `emit` method sends messages to the parent frame with the proper Figma message format 274 | 3. The transport specifies different types for inbound vs outbound messages 275 | 276 | By following these patterns, you can adapt typed-channel to work with any messaging API. 277 | 278 | ## Examples 279 | 280 | You can find more examples in the [examples directory](./examples): 281 | 282 | - [EventTarget Example](./examples/EventTarget) 283 | - [Worker Example](./examples/Worker) 284 | - [BroadcastChannel Example](./examples/BroadcastChannel) 285 | 286 | ## License 287 | 288 | [MIT](./LICENSE.md) 289 | -------------------------------------------------------------------------------- /demo/.presentation-buddy/instructions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "createFile", 4 | "path": "src/README.md" 5 | }, 6 | { 7 | "type": "typeText", 8 | "text": [ 9 | "# Typed Channel demo", 10 | "", 11 | "This is a simple example of using typed channel for communication between the main thread and the worker.", 12 | "1. We setup types for the messages that will be sent between the client and the worker.", 13 | "2. We implement the worker code", 14 | "3. We implement the main thread code" 15 | ] 16 | }, 17 | { 18 | "type": "wait", 19 | "delay": 2500 20 | }, 21 | { 22 | "type": "createFile", 23 | "path": "src/types.ts" 24 | }, 25 | { 26 | "type": "typeText", 27 | "text": [ 28 | "// Let's first define the types for the messages that will be sent between the client and the worker.", 29 | "// It is a simple ping-pong messages with a string payload.", 30 | "", 31 | "export type ClientMessages = {", 32 | " ping: string;", 33 | "};", 34 | "", 35 | "export type WorkerMessages = {", 36 | " pong: string;", 37 | "};" 38 | ] 39 | }, 40 | { 41 | "type": "wait", 42 | "delay": 2500 43 | }, 44 | { 45 | "type": "createFile", 46 | "path": "src/worker.ts" 47 | }, 48 | { 49 | "type": "typeText", 50 | "text": [ 51 | "// Now let's implement worker", 52 | "", 53 | "// First we need to create a transport instance", 54 | "// In web worker we use global object as the target for the transport", 55 | "// It takes inbound and outbound message types as generic parameters", 56 | "// In this case ClientMessages are our inbound messages and WorkerMessages are outbound ones", 57 | "const transport = createPost" 58 | ] 59 | }, 60 | { 61 | "type": "command", 62 | "command": "editor.action.triggerSuggest" 63 | }, 64 | { 65 | "type": "wait", 66 | "delay": 500 67 | }, 68 | { 69 | "type": "command", 70 | "command": "acceptSelectedSuggestion" 71 | }, 72 | { 73 | "type": "typeText", 74 | "text": ["(globalThis);", 110 | "", 111 | "// Then we are creating a channel using the transport.", 112 | "const channel = createTypedChan" 113 | ] 114 | }, 115 | { 116 | "type": "command", 117 | "command": "editor.action.triggerSuggest" 118 | }, 119 | { 120 | "type": "wait", 121 | "delay": 500 122 | }, 123 | { 124 | "type": "command", 125 | "command": "acceptSelectedSuggestion" 126 | }, 127 | { 128 | "type": "typeText", 129 | "text": [ 130 | "(transport);", 131 | "", 132 | "// And finally use it to setup the message handling", 133 | "// Notice that we have our inbound message type in the suggestions", 134 | "// And the message type is inferred from the inbound message type as well", 135 | "channel.on(\"" 136 | ] 137 | }, 138 | { 139 | "type": "command", 140 | "command": "editor.action.triggerSuggest" 141 | }, 142 | { 143 | "type": "wait", 144 | "delay": 500 145 | }, 146 | { 147 | "type": "command", 148 | "command": "acceptSelectedSuggestion" 149 | }, 150 | { 151 | "type": "typeText", 152 | "text": ["\", (message) => {", " channel.emit(\""] 153 | }, 154 | { 155 | "type": "command", 156 | "command": "editor.action.triggerSuggest" 157 | }, 158 | { 159 | "type": "wait", 160 | "delay": 500 161 | }, 162 | { 163 | "type": "command", 164 | "command": "acceptSelectedSuggestion" 165 | }, 166 | { 167 | "type": "typeText", 168 | "text": ["\", `Pong from the worker: ${message}`);", "});"] 169 | }, 170 | { 171 | "type": "wait", 172 | "delay": 2500 173 | }, 174 | { 175 | "type": "createFile", 176 | "path": "src/client.ts" 177 | }, 178 | { 179 | "type": "typeText", 180 | "text": [ 181 | "// Now let's implement main thread code", 182 | "", 183 | "// First instantiate a worker instance", 184 | "const worker = new Worker(new URL(\"./worker.ts\", import.meta.url), { type: \"module\" });", 185 | "", 186 | "// Then create a transport instance as we did before in the worker, but now", 187 | "// we are using the worker instance as the target for the transport", 188 | "// It treats WorkerMessages as inbound messages and ClientMessages as outbound ones", 189 | "const transport = createPost" 190 | ] 191 | }, 192 | { 193 | "type": "command", 194 | "command": "editor.action.triggerSuggest" 195 | }, 196 | { 197 | "type": "wait", 198 | "delay": 500 199 | }, 200 | { 201 | "type": "command", 202 | "command": "acceptSelectedSuggestion" 203 | }, 204 | { 205 | "type": "typeText", 206 | "text": ["(worker);", 242 | "", 243 | "// Create a channel using the transport.", 244 | "const channel = createTypedChan" 245 | ] 246 | }, 247 | { 248 | "type": "command", 249 | "command": "editor.action.triggerSuggest" 250 | }, 251 | { 252 | "type": "wait", 253 | "delay": 500 254 | }, 255 | { 256 | "type": "command", 257 | "command": "acceptSelectedSuggestion" 258 | }, 259 | { 260 | "type": "typeText", 261 | "text": [ 262 | "(transport);", 263 | "", 264 | "// And finally use for communicating with the worker", 265 | "// Everything is inferred properly again", 266 | "channel.on(\"" 267 | ] 268 | }, 269 | { 270 | "type": "command", 271 | "command": "editor.action.triggerSuggest" 272 | }, 273 | { 274 | "type": "wait", 275 | "delay": 500 276 | }, 277 | { 278 | "type": "command", 279 | "command": "acceptSelectedSuggestion" 280 | }, 281 | { 282 | "type": "typeText", 283 | "text": ["\", (message) => {", " console.info(message);", "});", ""] 284 | }, 285 | { 286 | "type": "typeText", 287 | "text": ["channel.emit(\""] 288 | }, 289 | { 290 | "type": "command", 291 | "command": "editor.action.triggerSuggest" 292 | }, 293 | { 294 | "type": "wait", 295 | "delay": 500 296 | }, 297 | { 298 | "type": "command", 299 | "command": "acceptSelectedSuggestion" 300 | }, 301 | { 302 | "type": "typeText", 303 | "text": [ 304 | "\", \"Hello from the main thread!\");", 305 | "", 306 | "// Voilà! Now we have typed channel for communicating between the main thread and the worker." 307 | ] 308 | }, 309 | { 310 | "type": "wait", 311 | "delay": 2500 312 | } 313 | ] 314 | -------------------------------------------------------------------------------- /demo/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["mauricedebeijer.presentation-buddy"] 3 | } 4 | -------------------------------------------------------------------------------- /demo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "github.copilot.enable": { 3 | "*": false 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typed-channel-demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "packageManager": "pnpm@10.8.1", 13 | "dependencies": { 14 | "typed-channel": "^0.9.5" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /demo/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | typed-channel: 12 | specifier: ^0.9.5 13 | version: 0.9.5 14 | 15 | packages: 16 | 17 | typed-channel@0.9.5: 18 | resolution: {integrity: sha512-taCiroVT//iljqH0ijhcyEMhG/1IgLt+FE/LKPku+oOmx1xtkAjZ8upc8KoZKezYe/sL7sMsuAC6EHnnaXt1DA==} 19 | 20 | snapshots: 21 | 22 | typed-channel@0.9.5: {} 23 | -------------------------------------------------------------------------------- /demo/src/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psd-coder/typed-channel/09c1ed2e4e0368dded6616e1ba92adb76f656d44/demo/src/README.md -------------------------------------------------------------------------------- /demo/src/client.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psd-coder/typed-channel/09c1ed2e4e0368dded6616e1ba92adb76f656d44/demo/src/client.ts -------------------------------------------------------------------------------- /demo/src/types.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psd-coder/typed-channel/09c1ed2e4e0368dded6616e1ba92adb76f656d44/demo/src/types.ts -------------------------------------------------------------------------------- /demo/src/worker.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psd-coder/typed-channel/09c1ed2e4e0368dded6616e1ba92adb76f656d44/demo/src/worker.ts -------------------------------------------------------------------------------- /examples/BroadcastChannel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | BroadcastChannel — typed-channel 7 | 8 | 9 |

BroadcastChannel example

10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/BroadcastChannel/main.ts: -------------------------------------------------------------------------------- 1 | import { createTypedChannel } from "../../src/index"; 2 | import { createPostMessageTransport } from "../../src/transports/postMessage"; 3 | import { createEventTargetTransport } from "../../src/transports/eventTarget"; 4 | 5 | // Setup channel 6 | type Events = { 7 | "new-leader-elected": { id: string }; 8 | message: { message: string }; 9 | }; 10 | 11 | const broadcastChannel = new BroadcastChannel("example-channel"); 12 | // We setup both EventTarget and BroadcastChannel transports to receive messages in all tabs, including the current one. 13 | // This is important because BroadcastChannel does not trigger event handlers in the current tab. 14 | // The EventTarget transport is used to receive messages in the current tab. 15 | // The BroadcastChannel transport is used to receive messages in other tabs. 16 | const eventTargetTransport = createEventTargetTransport(); 17 | const broadcastChannelTransport = createPostMessageTransport(broadcastChannel); 18 | const typedChannel = createTypedChannel([eventTargetTransport, broadcastChannelTransport]); 19 | 20 | const tabId = crypto.randomUUID(); 21 | 22 | log(`Tab ID: ${tabId}`); 23 | 24 | awaitLeadership().then(() => { 25 | typedChannel.emit("new-leader-elected", { id: tabId }); 26 | }); 27 | 28 | typedChannel.on("new-leader-elected", ({ id }) => { 29 | log(`New leader elected: ${id}`); 30 | 31 | if (id === tabId) { 32 | log("Mama, I am a leader!"); 33 | } 34 | }); 35 | 36 | typedChannel.on("message", (event) => { 37 | log(`Broadcast message: ${event.message}`); 38 | }); 39 | 40 | document 41 | .getElementById("broadcast")! 42 | .addEventListener("click", () => 43 | typedChannel.emit("message", { message: `Hello from tab: ${tabId}` }), 44 | ); 45 | 46 | // Utils 47 | async function awaitLeadership() { 48 | const NEVER = new Promise(() => {}); 49 | 50 | return new Promise((resolve) => { 51 | window.navigator.locks.request("leader-tab", () => { 52 | resolve(); 53 | return NEVER; 54 | }); 55 | }); 56 | } 57 | 58 | function log(message: string) { 59 | const messageElement = document.createElement("p"); 60 | messageElement.textContent = message; 61 | 62 | document.getElementById("log")!.appendChild(messageElement); 63 | } 64 | -------------------------------------------------------------------------------- /examples/DenoEventTarget/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "demo": "deno run main.ts" 4 | }, 5 | "imports": { 6 | "typed-channel": "jsr:@psdcoder/typed-channel@0.9.3" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/DenoEventTarget/deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5", 3 | "specifiers": { 4 | "jsr:@psdcoder/typed-channel@0.9.3": "0.9.3" 5 | }, 6 | "jsr": { 7 | "@psdcoder/typed-channel@0.9.3": { 8 | "integrity": "cbac84109e5523860a9a6ea6aff11a9d95faa5ba0e3ae0d20bcec3958cb04631" 9 | } 10 | }, 11 | "workspace": { 12 | "dependencies": [ 13 | "jsr:@psdcoder/typed-channel@0.9.3" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/DenoEventTarget/main.ts: -------------------------------------------------------------------------------- 1 | import { createTypedChannel, createEventTargetTransport } from "typed-channel"; 2 | 3 | // Setup channel 4 | type Events = { 5 | ready: never; 6 | notify: { message: string }; 7 | }; 8 | 9 | const transport = createEventTargetTransport(); 10 | const channel = createTypedChannel(transport); 11 | 12 | // Listen for events 13 | channel.on("ready", () => console.log("Ready event received")); 14 | channel.on("notify", ({ message }) => console.log(`Notify event received: ${message}`)); 15 | 16 | // Emit events 17 | channel.emit("ready"); 18 | let n = 1; 19 | const interval = setInterval(() => { 20 | channel.emit("notify", { message: `Hello, world! ${n++}` }); 21 | if (n > 5) { 22 | clearInterval(interval); 23 | } 24 | }, 1000); 25 | -------------------------------------------------------------------------------- /examples/EventTarget/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | BroadcastChannel — typed-channel 7 | 8 | 9 |

BroadcastChannel example

10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/EventTarget/main.ts: -------------------------------------------------------------------------------- 1 | import { createTypedChannel } from "../../src/index"; 2 | import { createEventTargetTransport } from "../../src/transports/eventTarget"; 3 | 4 | // Setup channel 5 | type Events = { 6 | ready: never; 7 | notify: { message: string }; 8 | reset: never; 9 | }; 10 | 11 | const transport = createEventTargetTransport(); 12 | const channel = createTypedChannel(transport); 13 | 14 | // Listen for events 15 | channel.on("ready", () => log("Ready event received")); 16 | channel.on("notify", ({ message }) => log(`Notify event received: ${message}`)); 17 | channel.on("reset", () => (document.getElementById("log")!.innerHTML = "")); 18 | 19 | // Emit events 20 | channel.emit("ready"); 21 | setTimeout(() => { 22 | channel.emit("notify", { message: "Hello, world!" }); 23 | }, 1000); 24 | document 25 | .getElementById("trigger")! 26 | .addEventListener("click", () => channel.emit("notify", { message: "Hello, again!" })); 27 | document.getElementById("reset")!.addEventListener("click", () => channel.emit("reset")); 28 | 29 | // Utils 30 | function log(message: string) { 31 | const messageElement = document.createElement("p"); 32 | messageElement.textContent = message; 33 | 34 | document.getElementById("log")!.appendChild(messageElement); 35 | } 36 | -------------------------------------------------------------------------------- /examples/NodeEventEmitter/main.ts: -------------------------------------------------------------------------------- 1 | import EventEmitter from "node:events"; 2 | import { 3 | createTypedChannel, 4 | type AnyMessageOf, 5 | type AnyMessages, 6 | type TypedChannelTransport, 7 | } from "typed-channel"; 8 | 9 | // This is just a DEMO of ability working with Node.js EventEmitter. 10 | // EventEmitter supports specifying types out of the box, so there isn't much sense in using 11 | // TypedChannel for this. But it is possible to use typed-channel with EventEmitter if you want 12 | // to have the same API across your application 13 | 14 | // Create custom Node EventEmitter transport 15 | function createEventEmitterTransport( 16 | ee: EventEmitter = new EventEmitter(), 17 | ): TypedChannelTransport { 18 | function on(handler: (message: AnyMessageOf) => void) { 19 | const workerMessageHandler = (message: AnyMessageOf) => { 20 | handler(message); 21 | }; 22 | 23 | ee.addListener("message", workerMessageHandler); 24 | return () => ee.removeListener("message", workerMessageHandler); 25 | } 26 | 27 | function emit(message: AnyMessageOf) { 28 | ee.emit("message", message); 29 | } 30 | 31 | return { on, emit }; 32 | } 33 | 34 | // Setup channel 35 | type Events = { 36 | ready: never; 37 | notify: { message: string }; 38 | }; 39 | 40 | const eventEmitterTransport = createEventEmitterTransport(); 41 | const channel = createTypedChannel(eventEmitterTransport); 42 | 43 | // Listen for events 44 | channel.on("ready", () => console.log("Ready event received")); 45 | channel.on("notify", ({ message }) => console.log(`Notify event received: ${message}`)); 46 | 47 | // Emit events 48 | channel.emit("ready"); 49 | let n = 1; 50 | const interval = setInterval(() => { 51 | channel.emit("notify", { message: `Hello, world! ${n++}` }); 52 | if (n > 5) { 53 | clearInterval(interval); 54 | } 55 | }, 1000); 56 | -------------------------------------------------------------------------------- /examples/NodeEventEmitter/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typed-channel-node-demo", 3 | "version": "0.0.1", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "typed-channel-node-demo", 9 | "version": "0.0.1", 10 | "dependencies": { 11 | "typed-channel": "^0.9.3" 12 | }, 13 | "devDependencies": { 14 | "tsx": "^4.19.3" 15 | } 16 | }, 17 | "node_modules/@esbuild/darwin-arm64": { 18 | "version": "0.25.2", 19 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", 20 | "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", 21 | "cpu": [ 22 | "arm64" 23 | ], 24 | "dev": true, 25 | "license": "MIT", 26 | "optional": true, 27 | "os": [ 28 | "darwin" 29 | ], 30 | "engines": { 31 | "node": ">=18" 32 | } 33 | }, 34 | "node_modules/esbuild": { 35 | "version": "0.25.2", 36 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", 37 | "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", 38 | "dev": true, 39 | "hasInstallScript": true, 40 | "license": "MIT", 41 | "bin": { 42 | "esbuild": "bin/esbuild" 43 | }, 44 | "engines": { 45 | "node": ">=18" 46 | }, 47 | "optionalDependencies": { 48 | "@esbuild/aix-ppc64": "0.25.2", 49 | "@esbuild/android-arm": "0.25.2", 50 | "@esbuild/android-arm64": "0.25.2", 51 | "@esbuild/android-x64": "0.25.2", 52 | "@esbuild/darwin-arm64": "0.25.2", 53 | "@esbuild/darwin-x64": "0.25.2", 54 | "@esbuild/freebsd-arm64": "0.25.2", 55 | "@esbuild/freebsd-x64": "0.25.2", 56 | "@esbuild/linux-arm": "0.25.2", 57 | "@esbuild/linux-arm64": "0.25.2", 58 | "@esbuild/linux-ia32": "0.25.2", 59 | "@esbuild/linux-loong64": "0.25.2", 60 | "@esbuild/linux-mips64el": "0.25.2", 61 | "@esbuild/linux-ppc64": "0.25.2", 62 | "@esbuild/linux-riscv64": "0.25.2", 63 | "@esbuild/linux-s390x": "0.25.2", 64 | "@esbuild/linux-x64": "0.25.2", 65 | "@esbuild/netbsd-arm64": "0.25.2", 66 | "@esbuild/netbsd-x64": "0.25.2", 67 | "@esbuild/openbsd-arm64": "0.25.2", 68 | "@esbuild/openbsd-x64": "0.25.2", 69 | "@esbuild/sunos-x64": "0.25.2", 70 | "@esbuild/win32-arm64": "0.25.2", 71 | "@esbuild/win32-ia32": "0.25.2", 72 | "@esbuild/win32-x64": "0.25.2" 73 | } 74 | }, 75 | "node_modules/fsevents": { 76 | "version": "2.3.3", 77 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 78 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 79 | "dev": true, 80 | "hasInstallScript": true, 81 | "license": "MIT", 82 | "optional": true, 83 | "os": [ 84 | "darwin" 85 | ], 86 | "engines": { 87 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 88 | } 89 | }, 90 | "node_modules/get-tsconfig": { 91 | "version": "4.10.0", 92 | "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", 93 | "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", 94 | "dev": true, 95 | "license": "MIT", 96 | "dependencies": { 97 | "resolve-pkg-maps": "^1.0.0" 98 | }, 99 | "funding": { 100 | "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" 101 | } 102 | }, 103 | "node_modules/resolve-pkg-maps": { 104 | "version": "1.0.0", 105 | "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", 106 | "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", 107 | "dev": true, 108 | "license": "MIT", 109 | "funding": { 110 | "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" 111 | } 112 | }, 113 | "node_modules/tsx": { 114 | "version": "4.19.3", 115 | "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz", 116 | "integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==", 117 | "dev": true, 118 | "license": "MIT", 119 | "dependencies": { 120 | "esbuild": "~0.25.0", 121 | "get-tsconfig": "^4.7.5" 122 | }, 123 | "bin": { 124 | "tsx": "dist/cli.mjs" 125 | }, 126 | "engines": { 127 | "node": ">=18.0.0" 128 | }, 129 | "optionalDependencies": { 130 | "fsevents": "~2.3.3" 131 | } 132 | }, 133 | "node_modules/typed-channel": { 134 | "version": "0.9.3", 135 | "resolved": "https://registry.npmjs.org/typed-channel/-/typed-channel-0.9.3.tgz", 136 | "integrity": "sha512-my9has41FFXWVArI17Y2s3qOAwqNvNz/n3K7/tMgzmYElXw3MBTvM3JRmDXguUJOrS/ID2OIlcNK51bjnCO9XQ==", 137 | "license": "MIT" 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /examples/NodeEventEmitter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typed-channel-node-demo", 3 | "private": true, 4 | "version": "0.0.1", 5 | "scripts": { 6 | "demo": "tsx main.ts" 7 | }, 8 | "dependencies": { 9 | "typed-channel": "^0.9.3" 10 | }, 11 | "devDependencies": { 12 | "tsx": "^4.19.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/NodeEventEmitter/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | typed-channel: 12 | specifier: ^0.9.3 13 | version: 0.9.3 14 | devDependencies: 15 | '@types/node': 16 | specifier: '22' 17 | version: 22.14.1 18 | ts-node: 19 | specifier: ^10.9.2 20 | version: 10.9.2(@types/node@22.14.1)(typescript@5.8.3) 21 | 22 | packages: 23 | 24 | '@cspotcode/source-map-support@0.8.1': 25 | resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} 26 | engines: {node: '>=12'} 27 | 28 | '@jridgewell/resolve-uri@3.1.2': 29 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 30 | engines: {node: '>=6.0.0'} 31 | 32 | '@jridgewell/sourcemap-codec@1.5.0': 33 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 34 | 35 | '@jridgewell/trace-mapping@0.3.9': 36 | resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} 37 | 38 | '@tsconfig/node10@1.0.11': 39 | resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} 40 | 41 | '@tsconfig/node12@1.0.11': 42 | resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} 43 | 44 | '@tsconfig/node14@1.0.3': 45 | resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} 46 | 47 | '@tsconfig/node16@1.0.4': 48 | resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} 49 | 50 | '@types/node@22.14.1': 51 | resolution: {integrity: sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==} 52 | 53 | acorn-walk@8.3.4: 54 | resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} 55 | engines: {node: '>=0.4.0'} 56 | 57 | acorn@8.14.1: 58 | resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} 59 | engines: {node: '>=0.4.0'} 60 | hasBin: true 61 | 62 | arg@4.1.3: 63 | resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} 64 | 65 | create-require@1.1.1: 66 | resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} 67 | 68 | diff@4.0.2: 69 | resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} 70 | engines: {node: '>=0.3.1'} 71 | 72 | make-error@1.3.6: 73 | resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} 74 | 75 | ts-node@10.9.2: 76 | resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} 77 | hasBin: true 78 | peerDependencies: 79 | '@swc/core': '>=1.2.50' 80 | '@swc/wasm': '>=1.2.50' 81 | '@types/node': '*' 82 | typescript: '>=2.7' 83 | peerDependenciesMeta: 84 | '@swc/core': 85 | optional: true 86 | '@swc/wasm': 87 | optional: true 88 | 89 | typed-channel@0.9.3: 90 | resolution: {integrity: sha512-my9has41FFXWVArI17Y2s3qOAwqNvNz/n3K7/tMgzmYElXw3MBTvM3JRmDXguUJOrS/ID2OIlcNK51bjnCO9XQ==} 91 | 92 | typescript@5.8.3: 93 | resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} 94 | engines: {node: '>=14.17'} 95 | hasBin: true 96 | 97 | undici-types@6.21.0: 98 | resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} 99 | 100 | v8-compile-cache-lib@3.0.1: 101 | resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} 102 | 103 | yn@3.1.1: 104 | resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} 105 | engines: {node: '>=6'} 106 | 107 | snapshots: 108 | 109 | '@cspotcode/source-map-support@0.8.1': 110 | dependencies: 111 | '@jridgewell/trace-mapping': 0.3.9 112 | 113 | '@jridgewell/resolve-uri@3.1.2': {} 114 | 115 | '@jridgewell/sourcemap-codec@1.5.0': {} 116 | 117 | '@jridgewell/trace-mapping@0.3.9': 118 | dependencies: 119 | '@jridgewell/resolve-uri': 3.1.2 120 | '@jridgewell/sourcemap-codec': 1.5.0 121 | 122 | '@tsconfig/node10@1.0.11': {} 123 | 124 | '@tsconfig/node12@1.0.11': {} 125 | 126 | '@tsconfig/node14@1.0.3': {} 127 | 128 | '@tsconfig/node16@1.0.4': {} 129 | 130 | '@types/node@22.14.1': 131 | dependencies: 132 | undici-types: 6.21.0 133 | 134 | acorn-walk@8.3.4: 135 | dependencies: 136 | acorn: 8.14.1 137 | 138 | acorn@8.14.1: {} 139 | 140 | arg@4.1.3: {} 141 | 142 | create-require@1.1.1: {} 143 | 144 | diff@4.0.2: {} 145 | 146 | make-error@1.3.6: {} 147 | 148 | ts-node@10.9.2(@types/node@22.14.1)(typescript@5.8.3): 149 | dependencies: 150 | '@cspotcode/source-map-support': 0.8.1 151 | '@tsconfig/node10': 1.0.11 152 | '@tsconfig/node12': 1.0.11 153 | '@tsconfig/node14': 1.0.3 154 | '@tsconfig/node16': 1.0.4 155 | '@types/node': 22.14.1 156 | acorn: 8.14.1 157 | acorn-walk: 8.3.4 158 | arg: 4.1.3 159 | create-require: 1.1.1 160 | diff: 4.0.2 161 | make-error: 1.3.6 162 | typescript: 5.8.3 163 | v8-compile-cache-lib: 3.0.1 164 | yn: 3.1.1 165 | 166 | typed-channel@0.9.3: {} 167 | 168 | typescript@5.8.3: {} 169 | 170 | undici-types@6.21.0: {} 171 | 172 | v8-compile-cache-lib@3.0.1: {} 173 | 174 | yn@3.1.1: {} 175 | -------------------------------------------------------------------------------- /examples/Worker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Worker — typed-channel 7 | 8 | 9 |
10 |

Worker example

11 | 12 | 13 |
14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/Worker/main.ts: -------------------------------------------------------------------------------- 1 | import { createTypedChannel } from "../../src/index"; 2 | import { createPostMessageTransport } from "../../src/transports/postMessage"; 3 | import type { ClientMessages, WorkerMessages } from "./types"; 4 | 5 | // Setup channel 6 | const worker = new Worker(new URL("./worker.ts", import.meta.url), { 7 | type: "module", 8 | }); 9 | 10 | const workerTransport = createPostMessageTransport(worker); 11 | const channel = createTypedChannel(workerTransport); 12 | 13 | // Listen for or trigger events 14 | document.getElementById("start")!.addEventListener("click", () => channel.emit("startTimer")); 15 | document.getElementById("stop")!.addEventListener("click", () => channel.emit("stopTimer")); 16 | channel.on("notify", ({ message }) => log("Notification received: " + message)); 17 | 18 | // Utils 19 | function log(message: string) { 20 | const messageElement = document.createElement("p"); 21 | messageElement.textContent = message; 22 | 23 | document.getElementById("log")!.appendChild(messageElement); 24 | } 25 | -------------------------------------------------------------------------------- /examples/Worker/types.ts: -------------------------------------------------------------------------------- 1 | export type ClientMessages = { 2 | startTimer: never; 3 | stopTimer: never; 4 | }; 5 | 6 | export type WorkerMessages = { 7 | notify: { message: string }; 8 | }; 9 | -------------------------------------------------------------------------------- /examples/Worker/worker.ts: -------------------------------------------------------------------------------- 1 | import { createTypedChannel } from "../../src/index"; 2 | import { createPostMessageTransport } from "../../src/transports/postMessage"; 3 | import type { ClientMessages, WorkerMessages } from "./types"; 4 | 5 | // Setup channel 6 | const postMessageTransport = createPostMessageTransport(globalThis); 7 | const channel = createTypedChannel(postMessageTransport); 8 | 9 | // Listen for or trigger events 10 | let timer: ReturnType | null = null; 11 | channel.on("startTimer", () => { 12 | if (timer) { 13 | return; 14 | } 15 | 16 | timer = setInterval(() => { 17 | channel.emit("notify", { message: `Timer tick: ${Date.now()}` }); 18 | }, 1000); 19 | }); 20 | channel.on("stopTimer", () => { 21 | if (timer) { 22 | clearInterval(timer); 23 | timer = null; 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Examples index — typed-channel 7 | 8 | 9 |

All examples

10 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /jsr.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://jsr.io/schema/config-file.v1.json", 3 | "name": "@psdcoder/typed-channel", 4 | "version": "0.9.5", 5 | "license": "MIT", 6 | "exports": { 7 | ".": "./src/index.ts" 8 | }, 9 | "publish": { 10 | "include": ["src/**/*.ts", "package.json", "README.md", "LICENSE"], 11 | "exclude": ["src/**/*.test.ts"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typed-channel", 3 | "version": "0.9.5", 4 | "description": "A type-safe communication channel for sending and receiving messages between different contexts in a TypeScript environment", 5 | "keywords": [ 6 | "typed", 7 | "types", 8 | "typescript", 9 | "channel", 10 | "transport", 11 | "communication", 12 | "events", 13 | "worker", 14 | "postmessage" 15 | ], 16 | "repository": "psd-coder/typed-channel", 17 | "author": "Pavel Grinchenko ", 18 | "license": "MIT", 19 | "files": [ 20 | "dist/*", 21 | "package.json", 22 | "README.md", 23 | "LICENSE" 24 | ], 25 | "type": "module", 26 | "sideEffects": false, 27 | "main": "./dist/index.js", 28 | "scripts": { 29 | "build": "tsup", 30 | "examples": "vite ./examples --open", 31 | "lint": "pnpm --parallel /^lint:*/", 32 | "lint:oxlint": "oxlint", 33 | "lint:prettier": "prettier --check .", 34 | "lint:size": "pnpm build && size-limit", 35 | "format": "prettier . --write", 36 | "typecheck": "tsc --noEmit", 37 | "test": "vitest run", 38 | "test:watch": "vitest", 39 | "bump": "./scripts/bump.sh" 40 | }, 41 | "packageManager": "pnpm@10.8.1", 42 | "devDependencies": { 43 | "@size-limit/preset-small-lib": "^11.2.0", 44 | "oxlint": "^0.16.6", 45 | "prettier": "^3.5.3", 46 | "size-limit": "^11.2.0", 47 | "tsup": "^8.4.0", 48 | "typescript": "^5.8.3", 49 | "vite": "^6.3.1", 50 | "vitest": "^3.1.1" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | devDependencies: 11 | '@size-limit/preset-small-lib': 12 | specifier: ^11.2.0 13 | version: 11.2.0(size-limit@11.2.0) 14 | oxlint: 15 | specifier: ^0.16.6 16 | version: 0.16.6 17 | prettier: 18 | specifier: ^3.5.3 19 | version: 3.5.3 20 | size-limit: 21 | specifier: ^11.2.0 22 | version: 11.2.0 23 | tsup: 24 | specifier: ^8.4.0 25 | version: 8.4.0(jiti@2.4.2)(postcss@8.5.3)(typescript@5.8.3) 26 | typescript: 27 | specifier: ^5.8.3 28 | version: 5.8.3 29 | vite: 30 | specifier: ^6.3.1 31 | version: 6.3.2(@types/node@22.14.1)(jiti@2.4.2) 32 | vitest: 33 | specifier: ^3.1.1 34 | version: 3.1.1(@types/node@22.14.1)(happy-dom@17.4.4)(jiti@2.4.2)(jsdom@26.1.0) 35 | 36 | packages: 37 | 38 | '@asamuzakjp/css-color@3.1.2': 39 | resolution: {integrity: sha512-nwgc7jPn3LpZ4JWsoHtuwBsad1qSSLDDX634DdG0PBJofIuIEtSWk4KkRmuXyu178tjuHAbwiMNNzwqIyLYxZw==} 40 | 41 | '@csstools/color-helpers@5.0.2': 42 | resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==} 43 | engines: {node: '>=18'} 44 | 45 | '@csstools/css-calc@2.1.2': 46 | resolution: {integrity: sha512-TklMyb3uBB28b5uQdxjReG4L80NxAqgrECqLZFQbyLekwwlcDDS8r3f07DKqeo8C4926Br0gf/ZDe17Zv4wIuw==} 47 | engines: {node: '>=18'} 48 | peerDependencies: 49 | '@csstools/css-parser-algorithms': ^3.0.4 50 | '@csstools/css-tokenizer': ^3.0.3 51 | 52 | '@csstools/css-color-parser@3.0.8': 53 | resolution: {integrity: sha512-pdwotQjCCnRPuNi06jFuP68cykU1f3ZWExLe/8MQ1LOs8Xq+fTkYgd+2V8mWUWMrOn9iS2HftPVaMZDaXzGbhQ==} 54 | engines: {node: '>=18'} 55 | peerDependencies: 56 | '@csstools/css-parser-algorithms': ^3.0.4 57 | '@csstools/css-tokenizer': ^3.0.3 58 | 59 | '@csstools/css-parser-algorithms@3.0.4': 60 | resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==} 61 | engines: {node: '>=18'} 62 | peerDependencies: 63 | '@csstools/css-tokenizer': ^3.0.3 64 | 65 | '@csstools/css-tokenizer@3.0.3': 66 | resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==} 67 | engines: {node: '>=18'} 68 | 69 | '@esbuild/aix-ppc64@0.25.2': 70 | resolution: {integrity: sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==} 71 | engines: {node: '>=18'} 72 | cpu: [ppc64] 73 | os: [aix] 74 | 75 | '@esbuild/android-arm64@0.25.2': 76 | resolution: {integrity: sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==} 77 | engines: {node: '>=18'} 78 | cpu: [arm64] 79 | os: [android] 80 | 81 | '@esbuild/android-arm@0.25.2': 82 | resolution: {integrity: sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==} 83 | engines: {node: '>=18'} 84 | cpu: [arm] 85 | os: [android] 86 | 87 | '@esbuild/android-x64@0.25.2': 88 | resolution: {integrity: sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==} 89 | engines: {node: '>=18'} 90 | cpu: [x64] 91 | os: [android] 92 | 93 | '@esbuild/darwin-arm64@0.25.2': 94 | resolution: {integrity: sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==} 95 | engines: {node: '>=18'} 96 | cpu: [arm64] 97 | os: [darwin] 98 | 99 | '@esbuild/darwin-x64@0.25.2': 100 | resolution: {integrity: sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==} 101 | engines: {node: '>=18'} 102 | cpu: [x64] 103 | os: [darwin] 104 | 105 | '@esbuild/freebsd-arm64@0.25.2': 106 | resolution: {integrity: sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==} 107 | engines: {node: '>=18'} 108 | cpu: [arm64] 109 | os: [freebsd] 110 | 111 | '@esbuild/freebsd-x64@0.25.2': 112 | resolution: {integrity: sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==} 113 | engines: {node: '>=18'} 114 | cpu: [x64] 115 | os: [freebsd] 116 | 117 | '@esbuild/linux-arm64@0.25.2': 118 | resolution: {integrity: sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==} 119 | engines: {node: '>=18'} 120 | cpu: [arm64] 121 | os: [linux] 122 | 123 | '@esbuild/linux-arm@0.25.2': 124 | resolution: {integrity: sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==} 125 | engines: {node: '>=18'} 126 | cpu: [arm] 127 | os: [linux] 128 | 129 | '@esbuild/linux-ia32@0.25.2': 130 | resolution: {integrity: sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==} 131 | engines: {node: '>=18'} 132 | cpu: [ia32] 133 | os: [linux] 134 | 135 | '@esbuild/linux-loong64@0.25.2': 136 | resolution: {integrity: sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==} 137 | engines: {node: '>=18'} 138 | cpu: [loong64] 139 | os: [linux] 140 | 141 | '@esbuild/linux-mips64el@0.25.2': 142 | resolution: {integrity: sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==} 143 | engines: {node: '>=18'} 144 | cpu: [mips64el] 145 | os: [linux] 146 | 147 | '@esbuild/linux-ppc64@0.25.2': 148 | resolution: {integrity: sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==} 149 | engines: {node: '>=18'} 150 | cpu: [ppc64] 151 | os: [linux] 152 | 153 | '@esbuild/linux-riscv64@0.25.2': 154 | resolution: {integrity: sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==} 155 | engines: {node: '>=18'} 156 | cpu: [riscv64] 157 | os: [linux] 158 | 159 | '@esbuild/linux-s390x@0.25.2': 160 | resolution: {integrity: sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==} 161 | engines: {node: '>=18'} 162 | cpu: [s390x] 163 | os: [linux] 164 | 165 | '@esbuild/linux-x64@0.25.2': 166 | resolution: {integrity: sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==} 167 | engines: {node: '>=18'} 168 | cpu: [x64] 169 | os: [linux] 170 | 171 | '@esbuild/netbsd-arm64@0.25.2': 172 | resolution: {integrity: sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==} 173 | engines: {node: '>=18'} 174 | cpu: [arm64] 175 | os: [netbsd] 176 | 177 | '@esbuild/netbsd-x64@0.25.2': 178 | resolution: {integrity: sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==} 179 | engines: {node: '>=18'} 180 | cpu: [x64] 181 | os: [netbsd] 182 | 183 | '@esbuild/openbsd-arm64@0.25.2': 184 | resolution: {integrity: sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==} 185 | engines: {node: '>=18'} 186 | cpu: [arm64] 187 | os: [openbsd] 188 | 189 | '@esbuild/openbsd-x64@0.25.2': 190 | resolution: {integrity: sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==} 191 | engines: {node: '>=18'} 192 | cpu: [x64] 193 | os: [openbsd] 194 | 195 | '@esbuild/sunos-x64@0.25.2': 196 | resolution: {integrity: sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==} 197 | engines: {node: '>=18'} 198 | cpu: [x64] 199 | os: [sunos] 200 | 201 | '@esbuild/win32-arm64@0.25.2': 202 | resolution: {integrity: sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==} 203 | engines: {node: '>=18'} 204 | cpu: [arm64] 205 | os: [win32] 206 | 207 | '@esbuild/win32-ia32@0.25.2': 208 | resolution: {integrity: sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==} 209 | engines: {node: '>=18'} 210 | cpu: [ia32] 211 | os: [win32] 212 | 213 | '@esbuild/win32-x64@0.25.2': 214 | resolution: {integrity: sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==} 215 | engines: {node: '>=18'} 216 | cpu: [x64] 217 | os: [win32] 218 | 219 | '@isaacs/cliui@8.0.2': 220 | resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} 221 | engines: {node: '>=12'} 222 | 223 | '@jridgewell/gen-mapping@0.3.8': 224 | resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} 225 | engines: {node: '>=6.0.0'} 226 | 227 | '@jridgewell/resolve-uri@3.1.2': 228 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 229 | engines: {node: '>=6.0.0'} 230 | 231 | '@jridgewell/set-array@1.2.1': 232 | resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} 233 | engines: {node: '>=6.0.0'} 234 | 235 | '@jridgewell/sourcemap-codec@1.5.0': 236 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 237 | 238 | '@jridgewell/trace-mapping@0.3.25': 239 | resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 240 | 241 | '@oxlint/darwin-arm64@0.16.6': 242 | resolution: {integrity: sha512-wvW55Br6o08JEmiezMqvo0byZNH9eunCkbouV8rM2gQP6ROv8lbeQdPZLpAeFz0QA4Ca2b2pVo5S3N2fS78d+Q==} 243 | cpu: [arm64] 244 | os: [darwin] 245 | 246 | '@oxlint/darwin-x64@0.16.6': 247 | resolution: {integrity: sha512-VezC8yep+1TxVtBsTQz2OHJs9aTuIQ7ISyl5rn1QVQXeG7wdFIIFln3ilu2TtaMjnswEdEsCDqBjyoF1euqQow==} 248 | cpu: [x64] 249 | os: [darwin] 250 | 251 | '@oxlint/linux-arm64-gnu@0.16.6': 252 | resolution: {integrity: sha512-hvpBsP5/bERq8ft4KidszGifWV4ZcXeaJrfNI8CqIbfd4AqGJmnc5d6M2Op/sYdEMjRGdpPqftfzw4D6jDHPIQ==} 253 | cpu: [arm64] 254 | os: [linux] 255 | 256 | '@oxlint/linux-arm64-musl@0.16.6': 257 | resolution: {integrity: sha512-PolYYEhYELXaQ0ht0g6Z827rRVDgbi/PQcHFpctiDHbSruW3udIOy9nEOAUt0agSHYrdZcC0NWzISE+CPrM0Yw==} 258 | cpu: [arm64] 259 | os: [linux] 260 | 261 | '@oxlint/linux-x64-gnu@0.16.6': 262 | resolution: {integrity: sha512-y4Lq4mcheXYzyLiS2TG1CaNDfgK+yVmmyJlms010Gs6nd1ejF6cObMuY5g6GLPGRJMJxG4fhbE955I2y50+Ltg==} 263 | cpu: [x64] 264 | os: [linux] 265 | 266 | '@oxlint/linux-x64-musl@0.16.6': 267 | resolution: {integrity: sha512-p3Njn7MzBsIJr+23HtxItA86UP01xhcWfwU35RGWVyTNbXIdNoAkaD+DjXQj2KSEauO7rRDAZbrTA+40NWNNkQ==} 268 | cpu: [x64] 269 | os: [linux] 270 | 271 | '@oxlint/win32-arm64@0.16.6': 272 | resolution: {integrity: sha512-IdySuXzslSnZEk9F2mRx1cjyPsHM8ny2xQd+FEbWhDhfwxVZCK+m9hXoEnqVQ6FLXQsOjWVutGtYb+EpDiZxlQ==} 273 | cpu: [arm64] 274 | os: [win32] 275 | 276 | '@oxlint/win32-x64@0.16.6': 277 | resolution: {integrity: sha512-DqkFdDX1ULoizFBg21TMIe6B5L2a59KljqpN1S7H4+IXhxmRcc71bpZ7FRwrxjrlRhtCD4SAPTZadBI9qRhViw==} 278 | cpu: [x64] 279 | os: [win32] 280 | 281 | '@pkgjs/parseargs@0.11.0': 282 | resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 283 | engines: {node: '>=14'} 284 | 285 | '@rollup/rollup-android-arm-eabi@4.40.0': 286 | resolution: {integrity: sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==} 287 | cpu: [arm] 288 | os: [android] 289 | 290 | '@rollup/rollup-android-arm64@4.40.0': 291 | resolution: {integrity: sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==} 292 | cpu: [arm64] 293 | os: [android] 294 | 295 | '@rollup/rollup-darwin-arm64@4.40.0': 296 | resolution: {integrity: sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==} 297 | cpu: [arm64] 298 | os: [darwin] 299 | 300 | '@rollup/rollup-darwin-x64@4.40.0': 301 | resolution: {integrity: sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==} 302 | cpu: [x64] 303 | os: [darwin] 304 | 305 | '@rollup/rollup-freebsd-arm64@4.40.0': 306 | resolution: {integrity: sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==} 307 | cpu: [arm64] 308 | os: [freebsd] 309 | 310 | '@rollup/rollup-freebsd-x64@4.40.0': 311 | resolution: {integrity: sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==} 312 | cpu: [x64] 313 | os: [freebsd] 314 | 315 | '@rollup/rollup-linux-arm-gnueabihf@4.40.0': 316 | resolution: {integrity: sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==} 317 | cpu: [arm] 318 | os: [linux] 319 | 320 | '@rollup/rollup-linux-arm-musleabihf@4.40.0': 321 | resolution: {integrity: sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==} 322 | cpu: [arm] 323 | os: [linux] 324 | 325 | '@rollup/rollup-linux-arm64-gnu@4.40.0': 326 | resolution: {integrity: sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==} 327 | cpu: [arm64] 328 | os: [linux] 329 | 330 | '@rollup/rollup-linux-arm64-musl@4.40.0': 331 | resolution: {integrity: sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==} 332 | cpu: [arm64] 333 | os: [linux] 334 | 335 | '@rollup/rollup-linux-loongarch64-gnu@4.40.0': 336 | resolution: {integrity: sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==} 337 | cpu: [loong64] 338 | os: [linux] 339 | 340 | '@rollup/rollup-linux-powerpc64le-gnu@4.40.0': 341 | resolution: {integrity: sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==} 342 | cpu: [ppc64] 343 | os: [linux] 344 | 345 | '@rollup/rollup-linux-riscv64-gnu@4.40.0': 346 | resolution: {integrity: sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==} 347 | cpu: [riscv64] 348 | os: [linux] 349 | 350 | '@rollup/rollup-linux-riscv64-musl@4.40.0': 351 | resolution: {integrity: sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==} 352 | cpu: [riscv64] 353 | os: [linux] 354 | 355 | '@rollup/rollup-linux-s390x-gnu@4.40.0': 356 | resolution: {integrity: sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==} 357 | cpu: [s390x] 358 | os: [linux] 359 | 360 | '@rollup/rollup-linux-x64-gnu@4.40.0': 361 | resolution: {integrity: sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==} 362 | cpu: [x64] 363 | os: [linux] 364 | 365 | '@rollup/rollup-linux-x64-musl@4.40.0': 366 | resolution: {integrity: sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==} 367 | cpu: [x64] 368 | os: [linux] 369 | 370 | '@rollup/rollup-win32-arm64-msvc@4.40.0': 371 | resolution: {integrity: sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==} 372 | cpu: [arm64] 373 | os: [win32] 374 | 375 | '@rollup/rollup-win32-ia32-msvc@4.40.0': 376 | resolution: {integrity: sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==} 377 | cpu: [ia32] 378 | os: [win32] 379 | 380 | '@rollup/rollup-win32-x64-msvc@4.40.0': 381 | resolution: {integrity: sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==} 382 | cpu: [x64] 383 | os: [win32] 384 | 385 | '@size-limit/esbuild@11.2.0': 386 | resolution: {integrity: sha512-vSg9H0WxGQPRzDnBzeDyD9XT0Zdq0L+AI3+77/JhxznbSCMJMMr8ndaWVQRhOsixl97N0oD4pRFw2+R1Lcvi6A==} 387 | engines: {node: ^18.0.0 || >=20.0.0} 388 | peerDependencies: 389 | size-limit: 11.2.0 390 | 391 | '@size-limit/file@11.2.0': 392 | resolution: {integrity: sha512-OZHE3putEkQ/fgzz3Tp/0hSmfVo3wyTpOJSRNm6AmcwX4Nm9YtTfbQQ/hZRwbBFR23S7x2Sd9EbqYzngKwbRoA==} 393 | engines: {node: ^18.0.0 || >=20.0.0} 394 | peerDependencies: 395 | size-limit: 11.2.0 396 | 397 | '@size-limit/preset-small-lib@11.2.0': 398 | resolution: {integrity: sha512-RFbbIVfv8/QDgTPyXzjo5NKO6CYyK5Uq5xtNLHLbw5RgSKrgo8WpiB/fNivZuNd/5Wk0s91PtaJ9ThNcnFuI3g==} 399 | peerDependencies: 400 | size-limit: 11.2.0 401 | 402 | '@types/estree@1.0.7': 403 | resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} 404 | 405 | '@types/node@22.14.1': 406 | resolution: {integrity: sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==} 407 | 408 | '@vitest/expect@3.1.1': 409 | resolution: {integrity: sha512-q/zjrW9lgynctNbwvFtQkGK9+vvHA5UzVi2V8APrp1C6fG6/MuYYkmlx4FubuqLycCeSdHD5aadWfua/Vr0EUA==} 410 | 411 | '@vitest/mocker@3.1.1': 412 | resolution: {integrity: sha512-bmpJJm7Y7i9BBELlLuuM1J1Q6EQ6K5Ye4wcyOpOMXMcePYKSIYlpcrCm4l/O6ja4VJA5G2aMJiuZkZdnxlC3SA==} 413 | peerDependencies: 414 | msw: ^2.4.9 415 | vite: ^5.0.0 || ^6.0.0 416 | peerDependenciesMeta: 417 | msw: 418 | optional: true 419 | vite: 420 | optional: true 421 | 422 | '@vitest/pretty-format@3.1.1': 423 | resolution: {integrity: sha512-dg0CIzNx+hMMYfNmSqJlLSXEmnNhMswcn3sXO7Tpldr0LiGmg3eXdLLhwkv2ZqgHb/d5xg5F7ezNFRA1fA13yA==} 424 | 425 | '@vitest/runner@3.1.1': 426 | resolution: {integrity: sha512-X/d46qzJuEDO8ueyjtKfxffiXraPRfmYasoC4i5+mlLEJ10UvPb0XH5M9C3gWuxd7BAQhpK42cJgJtq53YnWVA==} 427 | 428 | '@vitest/snapshot@3.1.1': 429 | resolution: {integrity: sha512-bByMwaVWe/+1WDf9exFxWWgAixelSdiwo2p33tpqIlM14vW7PRV5ppayVXtfycqze4Qhtwag5sVhX400MLBOOw==} 430 | 431 | '@vitest/spy@3.1.1': 432 | resolution: {integrity: sha512-+EmrUOOXbKzLkTDwlsc/xrwOlPDXyVk3Z6P6K4oiCndxz7YLpp/0R0UsWVOKT0IXWjjBJuSMk6D27qipaupcvQ==} 433 | 434 | '@vitest/utils@3.1.1': 435 | resolution: {integrity: sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg==} 436 | 437 | agent-base@7.1.3: 438 | resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} 439 | engines: {node: '>= 14'} 440 | 441 | ansi-regex@5.0.1: 442 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 443 | engines: {node: '>=8'} 444 | 445 | ansi-regex@6.1.0: 446 | resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} 447 | engines: {node: '>=12'} 448 | 449 | ansi-styles@4.3.0: 450 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 451 | engines: {node: '>=8'} 452 | 453 | ansi-styles@6.2.1: 454 | resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} 455 | engines: {node: '>=12'} 456 | 457 | any-promise@1.3.0: 458 | resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} 459 | 460 | assertion-error@2.0.1: 461 | resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} 462 | engines: {node: '>=12'} 463 | 464 | balanced-match@1.0.2: 465 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 466 | 467 | brace-expansion@2.0.1: 468 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 469 | 470 | bundle-require@5.1.0: 471 | resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} 472 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 473 | peerDependencies: 474 | esbuild: '>=0.18' 475 | 476 | bytes-iec@3.1.1: 477 | resolution: {integrity: sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA==} 478 | engines: {node: '>= 0.8'} 479 | 480 | cac@6.7.14: 481 | resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 482 | engines: {node: '>=8'} 483 | 484 | chai@5.2.0: 485 | resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} 486 | engines: {node: '>=12'} 487 | 488 | check-error@2.1.1: 489 | resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} 490 | engines: {node: '>= 16'} 491 | 492 | chokidar@4.0.3: 493 | resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} 494 | engines: {node: '>= 14.16.0'} 495 | 496 | color-convert@2.0.1: 497 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 498 | engines: {node: '>=7.0.0'} 499 | 500 | color-name@1.1.4: 501 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 502 | 503 | commander@4.1.1: 504 | resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} 505 | engines: {node: '>= 6'} 506 | 507 | consola@3.4.2: 508 | resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} 509 | engines: {node: ^14.18.0 || >=16.10.0} 510 | 511 | cross-spawn@7.0.6: 512 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 513 | engines: {node: '>= 8'} 514 | 515 | cssstyle@4.3.0: 516 | resolution: {integrity: sha512-6r0NiY0xizYqfBvWp1G7WXJ06/bZyrk7Dc6PHql82C/pKGUTKu4yAX4Y8JPamb1ob9nBKuxWzCGTRuGwU3yxJQ==} 517 | engines: {node: '>=18'} 518 | 519 | data-urls@5.0.0: 520 | resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} 521 | engines: {node: '>=18'} 522 | 523 | debug@4.4.0: 524 | resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} 525 | engines: {node: '>=6.0'} 526 | peerDependencies: 527 | supports-color: '*' 528 | peerDependenciesMeta: 529 | supports-color: 530 | optional: true 531 | 532 | decimal.js@10.5.0: 533 | resolution: {integrity: sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==} 534 | 535 | deep-eql@5.0.2: 536 | resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} 537 | engines: {node: '>=6'} 538 | 539 | eastasianwidth@0.2.0: 540 | resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 541 | 542 | emoji-regex@8.0.0: 543 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 544 | 545 | emoji-regex@9.2.2: 546 | resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} 547 | 548 | entities@4.5.0: 549 | resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} 550 | engines: {node: '>=0.12'} 551 | 552 | es-module-lexer@1.6.0: 553 | resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} 554 | 555 | esbuild@0.25.2: 556 | resolution: {integrity: sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==} 557 | engines: {node: '>=18'} 558 | hasBin: true 559 | 560 | estree-walker@3.0.3: 561 | resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} 562 | 563 | expect-type@1.2.1: 564 | resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} 565 | engines: {node: '>=12.0.0'} 566 | 567 | fdir@6.4.3: 568 | resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} 569 | peerDependencies: 570 | picomatch: ^3 || ^4 571 | peerDependenciesMeta: 572 | picomatch: 573 | optional: true 574 | 575 | foreground-child@3.3.1: 576 | resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} 577 | engines: {node: '>=14'} 578 | 579 | fsevents@2.3.3: 580 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 581 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 582 | os: [darwin] 583 | 584 | glob@10.4.5: 585 | resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} 586 | hasBin: true 587 | 588 | happy-dom@17.4.4: 589 | resolution: {integrity: sha512-/Pb0ctk3HTZ5xEL3BZ0hK1AqDSAUuRQitOmROPHhfUYEWpmTImwfD8vFDGADmMAX0JYgbcgxWoLFKtsWhcpuVA==} 590 | engines: {node: '>=18.0.0'} 591 | 592 | html-encoding-sniffer@4.0.0: 593 | resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} 594 | engines: {node: '>=18'} 595 | 596 | http-proxy-agent@7.0.2: 597 | resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} 598 | engines: {node: '>= 14'} 599 | 600 | https-proxy-agent@7.0.6: 601 | resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} 602 | engines: {node: '>= 14'} 603 | 604 | iconv-lite@0.6.3: 605 | resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} 606 | engines: {node: '>=0.10.0'} 607 | 608 | is-fullwidth-code-point@3.0.0: 609 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 610 | engines: {node: '>=8'} 611 | 612 | is-potential-custom-element-name@1.0.1: 613 | resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} 614 | 615 | isexe@2.0.0: 616 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 617 | 618 | jackspeak@3.4.3: 619 | resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} 620 | 621 | jiti@2.4.2: 622 | resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} 623 | hasBin: true 624 | 625 | joycon@3.1.1: 626 | resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} 627 | engines: {node: '>=10'} 628 | 629 | jsdom@26.1.0: 630 | resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} 631 | engines: {node: '>=18'} 632 | peerDependencies: 633 | canvas: ^3.0.0 634 | peerDependenciesMeta: 635 | canvas: 636 | optional: true 637 | 638 | lilconfig@3.1.3: 639 | resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} 640 | engines: {node: '>=14'} 641 | 642 | lines-and-columns@1.2.4: 643 | resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} 644 | 645 | load-tsconfig@0.2.5: 646 | resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} 647 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 648 | 649 | lodash.sortby@4.7.0: 650 | resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} 651 | 652 | loupe@3.1.3: 653 | resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} 654 | 655 | lru-cache@10.4.3: 656 | resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} 657 | 658 | magic-string@0.30.17: 659 | resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} 660 | 661 | minimatch@9.0.5: 662 | resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 663 | engines: {node: '>=16 || 14 >=14.17'} 664 | 665 | minipass@7.1.2: 666 | resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} 667 | engines: {node: '>=16 || 14 >=14.17'} 668 | 669 | ms@2.1.3: 670 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 671 | 672 | mz@2.7.0: 673 | resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} 674 | 675 | nanoid@3.3.11: 676 | resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} 677 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 678 | hasBin: true 679 | 680 | nanoid@5.1.5: 681 | resolution: {integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==} 682 | engines: {node: ^18 || >=20} 683 | hasBin: true 684 | 685 | nanospinner@1.2.2: 686 | resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==} 687 | 688 | nwsapi@2.2.20: 689 | resolution: {integrity: sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==} 690 | 691 | object-assign@4.1.1: 692 | resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 693 | engines: {node: '>=0.10.0'} 694 | 695 | oxlint@0.16.6: 696 | resolution: {integrity: sha512-pesehI0loV2h2k95mFRsUg6uNgGw2RPs1pcuAfPRJUwGehkfraMVCQofwqsMUeufmXygweH734vhKzQ24r3djA==} 697 | engines: {node: '>=8.*'} 698 | hasBin: true 699 | 700 | package-json-from-dist@1.0.1: 701 | resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} 702 | 703 | parse5@7.2.1: 704 | resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} 705 | 706 | path-key@3.1.1: 707 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 708 | engines: {node: '>=8'} 709 | 710 | path-scurry@1.11.1: 711 | resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} 712 | engines: {node: '>=16 || 14 >=14.18'} 713 | 714 | pathe@2.0.3: 715 | resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} 716 | 717 | pathval@2.0.0: 718 | resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} 719 | engines: {node: '>= 14.16'} 720 | 721 | picocolors@1.1.1: 722 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 723 | 724 | picomatch@4.0.2: 725 | resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} 726 | engines: {node: '>=12'} 727 | 728 | pirates@4.0.7: 729 | resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} 730 | engines: {node: '>= 6'} 731 | 732 | postcss-load-config@6.0.1: 733 | resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} 734 | engines: {node: '>= 18'} 735 | peerDependencies: 736 | jiti: '>=1.21.0' 737 | postcss: '>=8.0.9' 738 | tsx: ^4.8.1 739 | yaml: ^2.4.2 740 | peerDependenciesMeta: 741 | jiti: 742 | optional: true 743 | postcss: 744 | optional: true 745 | tsx: 746 | optional: true 747 | yaml: 748 | optional: true 749 | 750 | postcss@8.5.3: 751 | resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} 752 | engines: {node: ^10 || ^12 || >=14} 753 | 754 | prettier@3.5.3: 755 | resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} 756 | engines: {node: '>=14'} 757 | hasBin: true 758 | 759 | punycode@2.3.1: 760 | resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 761 | engines: {node: '>=6'} 762 | 763 | readdirp@4.1.2: 764 | resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} 765 | engines: {node: '>= 14.18.0'} 766 | 767 | resolve-from@5.0.0: 768 | resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} 769 | engines: {node: '>=8'} 770 | 771 | rollup@4.40.0: 772 | resolution: {integrity: sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==} 773 | engines: {node: '>=18.0.0', npm: '>=8.0.0'} 774 | hasBin: true 775 | 776 | rrweb-cssom@0.8.0: 777 | resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} 778 | 779 | safer-buffer@2.1.2: 780 | resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} 781 | 782 | saxes@6.0.0: 783 | resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} 784 | engines: {node: '>=v12.22.7'} 785 | 786 | shebang-command@2.0.0: 787 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 788 | engines: {node: '>=8'} 789 | 790 | shebang-regex@3.0.0: 791 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 792 | engines: {node: '>=8'} 793 | 794 | siginfo@2.0.0: 795 | resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} 796 | 797 | signal-exit@4.1.0: 798 | resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 799 | engines: {node: '>=14'} 800 | 801 | size-limit@11.2.0: 802 | resolution: {integrity: sha512-2kpQq2DD/pRpx3Tal/qRW1SYwcIeQ0iq8li5CJHQgOC+FtPn2BVmuDtzUCgNnpCrbgtfEHqh+iWzxK+Tq6C+RQ==} 803 | engines: {node: ^18.0.0 || >=20.0.0} 804 | hasBin: true 805 | 806 | source-map-js@1.2.1: 807 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 808 | engines: {node: '>=0.10.0'} 809 | 810 | source-map@0.8.0-beta.0: 811 | resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} 812 | engines: {node: '>= 8'} 813 | 814 | stackback@0.0.2: 815 | resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} 816 | 817 | std-env@3.9.0: 818 | resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} 819 | 820 | string-width@4.2.3: 821 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 822 | engines: {node: '>=8'} 823 | 824 | string-width@5.1.2: 825 | resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} 826 | engines: {node: '>=12'} 827 | 828 | strip-ansi@6.0.1: 829 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 830 | engines: {node: '>=8'} 831 | 832 | strip-ansi@7.1.0: 833 | resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 834 | engines: {node: '>=12'} 835 | 836 | sucrase@3.35.0: 837 | resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} 838 | engines: {node: '>=16 || 14 >=14.17'} 839 | hasBin: true 840 | 841 | symbol-tree@3.2.4: 842 | resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} 843 | 844 | thenify-all@1.6.0: 845 | resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} 846 | engines: {node: '>=0.8'} 847 | 848 | thenify@3.3.1: 849 | resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} 850 | 851 | tinybench@2.9.0: 852 | resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} 853 | 854 | tinyexec@0.3.2: 855 | resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} 856 | 857 | tinyglobby@0.2.12: 858 | resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} 859 | engines: {node: '>=12.0.0'} 860 | 861 | tinypool@1.0.2: 862 | resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} 863 | engines: {node: ^18.0.0 || >=20.0.0} 864 | 865 | tinyrainbow@2.0.0: 866 | resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} 867 | engines: {node: '>=14.0.0'} 868 | 869 | tinyspy@3.0.2: 870 | resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} 871 | engines: {node: '>=14.0.0'} 872 | 873 | tldts-core@6.1.86: 874 | resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} 875 | 876 | tldts@6.1.86: 877 | resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} 878 | hasBin: true 879 | 880 | tough-cookie@5.1.2: 881 | resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} 882 | engines: {node: '>=16'} 883 | 884 | tr46@1.0.1: 885 | resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} 886 | 887 | tr46@5.1.1: 888 | resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} 889 | engines: {node: '>=18'} 890 | 891 | tree-kill@1.2.2: 892 | resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} 893 | hasBin: true 894 | 895 | ts-interface-checker@0.1.13: 896 | resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} 897 | 898 | tsup@8.4.0: 899 | resolution: {integrity: sha512-b+eZbPCjz10fRryaAA7C8xlIHnf8VnsaRqydheLIqwG/Mcpfk8Z5zp3HayX7GaTygkigHl5cBUs+IhcySiIexQ==} 900 | engines: {node: '>=18'} 901 | hasBin: true 902 | peerDependencies: 903 | '@microsoft/api-extractor': ^7.36.0 904 | '@swc/core': ^1 905 | postcss: ^8.4.12 906 | typescript: '>=4.5.0' 907 | peerDependenciesMeta: 908 | '@microsoft/api-extractor': 909 | optional: true 910 | '@swc/core': 911 | optional: true 912 | postcss: 913 | optional: true 914 | typescript: 915 | optional: true 916 | 917 | typescript@5.8.3: 918 | resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} 919 | engines: {node: '>=14.17'} 920 | hasBin: true 921 | 922 | undici-types@6.21.0: 923 | resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} 924 | 925 | vite-node@3.1.1: 926 | resolution: {integrity: sha512-V+IxPAE2FvXpTCHXyNem0M+gWm6J7eRyWPR6vYoG/Gl+IscNOjXzztUhimQgTxaAoUoj40Qqimaa0NLIOOAH4w==} 927 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 928 | hasBin: true 929 | 930 | vite@6.3.2: 931 | resolution: {integrity: sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==} 932 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 933 | hasBin: true 934 | peerDependencies: 935 | '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 936 | jiti: '>=1.21.0' 937 | less: '*' 938 | lightningcss: ^1.21.0 939 | sass: '*' 940 | sass-embedded: '*' 941 | stylus: '*' 942 | sugarss: '*' 943 | terser: ^5.16.0 944 | tsx: ^4.8.1 945 | yaml: ^2.4.2 946 | peerDependenciesMeta: 947 | '@types/node': 948 | optional: true 949 | jiti: 950 | optional: true 951 | less: 952 | optional: true 953 | lightningcss: 954 | optional: true 955 | sass: 956 | optional: true 957 | sass-embedded: 958 | optional: true 959 | stylus: 960 | optional: true 961 | sugarss: 962 | optional: true 963 | terser: 964 | optional: true 965 | tsx: 966 | optional: true 967 | yaml: 968 | optional: true 969 | 970 | vitest@3.1.1: 971 | resolution: {integrity: sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q==} 972 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 973 | hasBin: true 974 | peerDependencies: 975 | '@edge-runtime/vm': '*' 976 | '@types/debug': ^4.1.12 977 | '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 978 | '@vitest/browser': 3.1.1 979 | '@vitest/ui': 3.1.1 980 | happy-dom: '*' 981 | jsdom: '*' 982 | peerDependenciesMeta: 983 | '@edge-runtime/vm': 984 | optional: true 985 | '@types/debug': 986 | optional: true 987 | '@types/node': 988 | optional: true 989 | '@vitest/browser': 990 | optional: true 991 | '@vitest/ui': 992 | optional: true 993 | happy-dom: 994 | optional: true 995 | jsdom: 996 | optional: true 997 | 998 | w3c-xmlserializer@5.0.0: 999 | resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} 1000 | engines: {node: '>=18'} 1001 | 1002 | webidl-conversions@4.0.2: 1003 | resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} 1004 | 1005 | webidl-conversions@7.0.0: 1006 | resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} 1007 | engines: {node: '>=12'} 1008 | 1009 | whatwg-encoding@3.1.1: 1010 | resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} 1011 | engines: {node: '>=18'} 1012 | 1013 | whatwg-mimetype@3.0.0: 1014 | resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} 1015 | engines: {node: '>=12'} 1016 | 1017 | whatwg-mimetype@4.0.0: 1018 | resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} 1019 | engines: {node: '>=18'} 1020 | 1021 | whatwg-url@14.2.0: 1022 | resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} 1023 | engines: {node: '>=18'} 1024 | 1025 | whatwg-url@7.1.0: 1026 | resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} 1027 | 1028 | which@2.0.2: 1029 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1030 | engines: {node: '>= 8'} 1031 | hasBin: true 1032 | 1033 | why-is-node-running@2.3.0: 1034 | resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} 1035 | engines: {node: '>=8'} 1036 | hasBin: true 1037 | 1038 | wrap-ansi@7.0.0: 1039 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 1040 | engines: {node: '>=10'} 1041 | 1042 | wrap-ansi@8.1.0: 1043 | resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} 1044 | engines: {node: '>=12'} 1045 | 1046 | ws@8.18.1: 1047 | resolution: {integrity: sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==} 1048 | engines: {node: '>=10.0.0'} 1049 | peerDependencies: 1050 | bufferutil: ^4.0.1 1051 | utf-8-validate: '>=5.0.2' 1052 | peerDependenciesMeta: 1053 | bufferutil: 1054 | optional: true 1055 | utf-8-validate: 1056 | optional: true 1057 | 1058 | xml-name-validator@5.0.0: 1059 | resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} 1060 | engines: {node: '>=18'} 1061 | 1062 | xmlchars@2.2.0: 1063 | resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} 1064 | 1065 | snapshots: 1066 | 1067 | '@asamuzakjp/css-color@3.1.2': 1068 | dependencies: 1069 | '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) 1070 | '@csstools/css-color-parser': 3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) 1071 | '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) 1072 | '@csstools/css-tokenizer': 3.0.3 1073 | lru-cache: 10.4.3 1074 | optional: true 1075 | 1076 | '@csstools/color-helpers@5.0.2': 1077 | optional: true 1078 | 1079 | '@csstools/css-calc@2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': 1080 | dependencies: 1081 | '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) 1082 | '@csstools/css-tokenizer': 3.0.3 1083 | optional: true 1084 | 1085 | '@csstools/css-color-parser@3.0.8(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': 1086 | dependencies: 1087 | '@csstools/color-helpers': 5.0.2 1088 | '@csstools/css-calc': 2.1.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) 1089 | '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) 1090 | '@csstools/css-tokenizer': 3.0.3 1091 | optional: true 1092 | 1093 | '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)': 1094 | dependencies: 1095 | '@csstools/css-tokenizer': 3.0.3 1096 | optional: true 1097 | 1098 | '@csstools/css-tokenizer@3.0.3': 1099 | optional: true 1100 | 1101 | '@esbuild/aix-ppc64@0.25.2': 1102 | optional: true 1103 | 1104 | '@esbuild/android-arm64@0.25.2': 1105 | optional: true 1106 | 1107 | '@esbuild/android-arm@0.25.2': 1108 | optional: true 1109 | 1110 | '@esbuild/android-x64@0.25.2': 1111 | optional: true 1112 | 1113 | '@esbuild/darwin-arm64@0.25.2': 1114 | optional: true 1115 | 1116 | '@esbuild/darwin-x64@0.25.2': 1117 | optional: true 1118 | 1119 | '@esbuild/freebsd-arm64@0.25.2': 1120 | optional: true 1121 | 1122 | '@esbuild/freebsd-x64@0.25.2': 1123 | optional: true 1124 | 1125 | '@esbuild/linux-arm64@0.25.2': 1126 | optional: true 1127 | 1128 | '@esbuild/linux-arm@0.25.2': 1129 | optional: true 1130 | 1131 | '@esbuild/linux-ia32@0.25.2': 1132 | optional: true 1133 | 1134 | '@esbuild/linux-loong64@0.25.2': 1135 | optional: true 1136 | 1137 | '@esbuild/linux-mips64el@0.25.2': 1138 | optional: true 1139 | 1140 | '@esbuild/linux-ppc64@0.25.2': 1141 | optional: true 1142 | 1143 | '@esbuild/linux-riscv64@0.25.2': 1144 | optional: true 1145 | 1146 | '@esbuild/linux-s390x@0.25.2': 1147 | optional: true 1148 | 1149 | '@esbuild/linux-x64@0.25.2': 1150 | optional: true 1151 | 1152 | '@esbuild/netbsd-arm64@0.25.2': 1153 | optional: true 1154 | 1155 | '@esbuild/netbsd-x64@0.25.2': 1156 | optional: true 1157 | 1158 | '@esbuild/openbsd-arm64@0.25.2': 1159 | optional: true 1160 | 1161 | '@esbuild/openbsd-x64@0.25.2': 1162 | optional: true 1163 | 1164 | '@esbuild/sunos-x64@0.25.2': 1165 | optional: true 1166 | 1167 | '@esbuild/win32-arm64@0.25.2': 1168 | optional: true 1169 | 1170 | '@esbuild/win32-ia32@0.25.2': 1171 | optional: true 1172 | 1173 | '@esbuild/win32-x64@0.25.2': 1174 | optional: true 1175 | 1176 | '@isaacs/cliui@8.0.2': 1177 | dependencies: 1178 | string-width: 5.1.2 1179 | string-width-cjs: string-width@4.2.3 1180 | strip-ansi: 7.1.0 1181 | strip-ansi-cjs: strip-ansi@6.0.1 1182 | wrap-ansi: 8.1.0 1183 | wrap-ansi-cjs: wrap-ansi@7.0.0 1184 | 1185 | '@jridgewell/gen-mapping@0.3.8': 1186 | dependencies: 1187 | '@jridgewell/set-array': 1.2.1 1188 | '@jridgewell/sourcemap-codec': 1.5.0 1189 | '@jridgewell/trace-mapping': 0.3.25 1190 | 1191 | '@jridgewell/resolve-uri@3.1.2': {} 1192 | 1193 | '@jridgewell/set-array@1.2.1': {} 1194 | 1195 | '@jridgewell/sourcemap-codec@1.5.0': {} 1196 | 1197 | '@jridgewell/trace-mapping@0.3.25': 1198 | dependencies: 1199 | '@jridgewell/resolve-uri': 3.1.2 1200 | '@jridgewell/sourcemap-codec': 1.5.0 1201 | 1202 | '@oxlint/darwin-arm64@0.16.6': 1203 | optional: true 1204 | 1205 | '@oxlint/darwin-x64@0.16.6': 1206 | optional: true 1207 | 1208 | '@oxlint/linux-arm64-gnu@0.16.6': 1209 | optional: true 1210 | 1211 | '@oxlint/linux-arm64-musl@0.16.6': 1212 | optional: true 1213 | 1214 | '@oxlint/linux-x64-gnu@0.16.6': 1215 | optional: true 1216 | 1217 | '@oxlint/linux-x64-musl@0.16.6': 1218 | optional: true 1219 | 1220 | '@oxlint/win32-arm64@0.16.6': 1221 | optional: true 1222 | 1223 | '@oxlint/win32-x64@0.16.6': 1224 | optional: true 1225 | 1226 | '@pkgjs/parseargs@0.11.0': 1227 | optional: true 1228 | 1229 | '@rollup/rollup-android-arm-eabi@4.40.0': 1230 | optional: true 1231 | 1232 | '@rollup/rollup-android-arm64@4.40.0': 1233 | optional: true 1234 | 1235 | '@rollup/rollup-darwin-arm64@4.40.0': 1236 | optional: true 1237 | 1238 | '@rollup/rollup-darwin-x64@4.40.0': 1239 | optional: true 1240 | 1241 | '@rollup/rollup-freebsd-arm64@4.40.0': 1242 | optional: true 1243 | 1244 | '@rollup/rollup-freebsd-x64@4.40.0': 1245 | optional: true 1246 | 1247 | '@rollup/rollup-linux-arm-gnueabihf@4.40.0': 1248 | optional: true 1249 | 1250 | '@rollup/rollup-linux-arm-musleabihf@4.40.0': 1251 | optional: true 1252 | 1253 | '@rollup/rollup-linux-arm64-gnu@4.40.0': 1254 | optional: true 1255 | 1256 | '@rollup/rollup-linux-arm64-musl@4.40.0': 1257 | optional: true 1258 | 1259 | '@rollup/rollup-linux-loongarch64-gnu@4.40.0': 1260 | optional: true 1261 | 1262 | '@rollup/rollup-linux-powerpc64le-gnu@4.40.0': 1263 | optional: true 1264 | 1265 | '@rollup/rollup-linux-riscv64-gnu@4.40.0': 1266 | optional: true 1267 | 1268 | '@rollup/rollup-linux-riscv64-musl@4.40.0': 1269 | optional: true 1270 | 1271 | '@rollup/rollup-linux-s390x-gnu@4.40.0': 1272 | optional: true 1273 | 1274 | '@rollup/rollup-linux-x64-gnu@4.40.0': 1275 | optional: true 1276 | 1277 | '@rollup/rollup-linux-x64-musl@4.40.0': 1278 | optional: true 1279 | 1280 | '@rollup/rollup-win32-arm64-msvc@4.40.0': 1281 | optional: true 1282 | 1283 | '@rollup/rollup-win32-ia32-msvc@4.40.0': 1284 | optional: true 1285 | 1286 | '@rollup/rollup-win32-x64-msvc@4.40.0': 1287 | optional: true 1288 | 1289 | '@size-limit/esbuild@11.2.0(size-limit@11.2.0)': 1290 | dependencies: 1291 | esbuild: 0.25.2 1292 | nanoid: 5.1.5 1293 | size-limit: 11.2.0 1294 | 1295 | '@size-limit/file@11.2.0(size-limit@11.2.0)': 1296 | dependencies: 1297 | size-limit: 11.2.0 1298 | 1299 | '@size-limit/preset-small-lib@11.2.0(size-limit@11.2.0)': 1300 | dependencies: 1301 | '@size-limit/esbuild': 11.2.0(size-limit@11.2.0) 1302 | '@size-limit/file': 11.2.0(size-limit@11.2.0) 1303 | size-limit: 11.2.0 1304 | 1305 | '@types/estree@1.0.7': {} 1306 | 1307 | '@types/node@22.14.1': 1308 | dependencies: 1309 | undici-types: 6.21.0 1310 | optional: true 1311 | 1312 | '@vitest/expect@3.1.1': 1313 | dependencies: 1314 | '@vitest/spy': 3.1.1 1315 | '@vitest/utils': 3.1.1 1316 | chai: 5.2.0 1317 | tinyrainbow: 2.0.0 1318 | 1319 | '@vitest/mocker@3.1.1(vite@6.3.2(@types/node@22.14.1)(jiti@2.4.2))': 1320 | dependencies: 1321 | '@vitest/spy': 3.1.1 1322 | estree-walker: 3.0.3 1323 | magic-string: 0.30.17 1324 | optionalDependencies: 1325 | vite: 6.3.2(@types/node@22.14.1)(jiti@2.4.2) 1326 | 1327 | '@vitest/pretty-format@3.1.1': 1328 | dependencies: 1329 | tinyrainbow: 2.0.0 1330 | 1331 | '@vitest/runner@3.1.1': 1332 | dependencies: 1333 | '@vitest/utils': 3.1.1 1334 | pathe: 2.0.3 1335 | 1336 | '@vitest/snapshot@3.1.1': 1337 | dependencies: 1338 | '@vitest/pretty-format': 3.1.1 1339 | magic-string: 0.30.17 1340 | pathe: 2.0.3 1341 | 1342 | '@vitest/spy@3.1.1': 1343 | dependencies: 1344 | tinyspy: 3.0.2 1345 | 1346 | '@vitest/utils@3.1.1': 1347 | dependencies: 1348 | '@vitest/pretty-format': 3.1.1 1349 | loupe: 3.1.3 1350 | tinyrainbow: 2.0.0 1351 | 1352 | agent-base@7.1.3: 1353 | optional: true 1354 | 1355 | ansi-regex@5.0.1: {} 1356 | 1357 | ansi-regex@6.1.0: {} 1358 | 1359 | ansi-styles@4.3.0: 1360 | dependencies: 1361 | color-convert: 2.0.1 1362 | 1363 | ansi-styles@6.2.1: {} 1364 | 1365 | any-promise@1.3.0: {} 1366 | 1367 | assertion-error@2.0.1: {} 1368 | 1369 | balanced-match@1.0.2: {} 1370 | 1371 | brace-expansion@2.0.1: 1372 | dependencies: 1373 | balanced-match: 1.0.2 1374 | 1375 | bundle-require@5.1.0(esbuild@0.25.2): 1376 | dependencies: 1377 | esbuild: 0.25.2 1378 | load-tsconfig: 0.2.5 1379 | 1380 | bytes-iec@3.1.1: {} 1381 | 1382 | cac@6.7.14: {} 1383 | 1384 | chai@5.2.0: 1385 | dependencies: 1386 | assertion-error: 2.0.1 1387 | check-error: 2.1.1 1388 | deep-eql: 5.0.2 1389 | loupe: 3.1.3 1390 | pathval: 2.0.0 1391 | 1392 | check-error@2.1.1: {} 1393 | 1394 | chokidar@4.0.3: 1395 | dependencies: 1396 | readdirp: 4.1.2 1397 | 1398 | color-convert@2.0.1: 1399 | dependencies: 1400 | color-name: 1.1.4 1401 | 1402 | color-name@1.1.4: {} 1403 | 1404 | commander@4.1.1: {} 1405 | 1406 | consola@3.4.2: {} 1407 | 1408 | cross-spawn@7.0.6: 1409 | dependencies: 1410 | path-key: 3.1.1 1411 | shebang-command: 2.0.0 1412 | which: 2.0.2 1413 | 1414 | cssstyle@4.3.0: 1415 | dependencies: 1416 | '@asamuzakjp/css-color': 3.1.2 1417 | rrweb-cssom: 0.8.0 1418 | optional: true 1419 | 1420 | data-urls@5.0.0: 1421 | dependencies: 1422 | whatwg-mimetype: 4.0.0 1423 | whatwg-url: 14.2.0 1424 | optional: true 1425 | 1426 | debug@4.4.0: 1427 | dependencies: 1428 | ms: 2.1.3 1429 | 1430 | decimal.js@10.5.0: 1431 | optional: true 1432 | 1433 | deep-eql@5.0.2: {} 1434 | 1435 | eastasianwidth@0.2.0: {} 1436 | 1437 | emoji-regex@8.0.0: {} 1438 | 1439 | emoji-regex@9.2.2: {} 1440 | 1441 | entities@4.5.0: 1442 | optional: true 1443 | 1444 | es-module-lexer@1.6.0: {} 1445 | 1446 | esbuild@0.25.2: 1447 | optionalDependencies: 1448 | '@esbuild/aix-ppc64': 0.25.2 1449 | '@esbuild/android-arm': 0.25.2 1450 | '@esbuild/android-arm64': 0.25.2 1451 | '@esbuild/android-x64': 0.25.2 1452 | '@esbuild/darwin-arm64': 0.25.2 1453 | '@esbuild/darwin-x64': 0.25.2 1454 | '@esbuild/freebsd-arm64': 0.25.2 1455 | '@esbuild/freebsd-x64': 0.25.2 1456 | '@esbuild/linux-arm': 0.25.2 1457 | '@esbuild/linux-arm64': 0.25.2 1458 | '@esbuild/linux-ia32': 0.25.2 1459 | '@esbuild/linux-loong64': 0.25.2 1460 | '@esbuild/linux-mips64el': 0.25.2 1461 | '@esbuild/linux-ppc64': 0.25.2 1462 | '@esbuild/linux-riscv64': 0.25.2 1463 | '@esbuild/linux-s390x': 0.25.2 1464 | '@esbuild/linux-x64': 0.25.2 1465 | '@esbuild/netbsd-arm64': 0.25.2 1466 | '@esbuild/netbsd-x64': 0.25.2 1467 | '@esbuild/openbsd-arm64': 0.25.2 1468 | '@esbuild/openbsd-x64': 0.25.2 1469 | '@esbuild/sunos-x64': 0.25.2 1470 | '@esbuild/win32-arm64': 0.25.2 1471 | '@esbuild/win32-ia32': 0.25.2 1472 | '@esbuild/win32-x64': 0.25.2 1473 | 1474 | estree-walker@3.0.3: 1475 | dependencies: 1476 | '@types/estree': 1.0.7 1477 | 1478 | expect-type@1.2.1: {} 1479 | 1480 | fdir@6.4.3(picomatch@4.0.2): 1481 | optionalDependencies: 1482 | picomatch: 4.0.2 1483 | 1484 | foreground-child@3.3.1: 1485 | dependencies: 1486 | cross-spawn: 7.0.6 1487 | signal-exit: 4.1.0 1488 | 1489 | fsevents@2.3.3: 1490 | optional: true 1491 | 1492 | glob@10.4.5: 1493 | dependencies: 1494 | foreground-child: 3.3.1 1495 | jackspeak: 3.4.3 1496 | minimatch: 9.0.5 1497 | minipass: 7.1.2 1498 | package-json-from-dist: 1.0.1 1499 | path-scurry: 1.11.1 1500 | 1501 | happy-dom@17.4.4: 1502 | dependencies: 1503 | webidl-conversions: 7.0.0 1504 | whatwg-mimetype: 3.0.0 1505 | optional: true 1506 | 1507 | html-encoding-sniffer@4.0.0: 1508 | dependencies: 1509 | whatwg-encoding: 3.1.1 1510 | optional: true 1511 | 1512 | http-proxy-agent@7.0.2: 1513 | dependencies: 1514 | agent-base: 7.1.3 1515 | debug: 4.4.0 1516 | transitivePeerDependencies: 1517 | - supports-color 1518 | optional: true 1519 | 1520 | https-proxy-agent@7.0.6: 1521 | dependencies: 1522 | agent-base: 7.1.3 1523 | debug: 4.4.0 1524 | transitivePeerDependencies: 1525 | - supports-color 1526 | optional: true 1527 | 1528 | iconv-lite@0.6.3: 1529 | dependencies: 1530 | safer-buffer: 2.1.2 1531 | optional: true 1532 | 1533 | is-fullwidth-code-point@3.0.0: {} 1534 | 1535 | is-potential-custom-element-name@1.0.1: 1536 | optional: true 1537 | 1538 | isexe@2.0.0: {} 1539 | 1540 | jackspeak@3.4.3: 1541 | dependencies: 1542 | '@isaacs/cliui': 8.0.2 1543 | optionalDependencies: 1544 | '@pkgjs/parseargs': 0.11.0 1545 | 1546 | jiti@2.4.2: {} 1547 | 1548 | joycon@3.1.1: {} 1549 | 1550 | jsdom@26.1.0: 1551 | dependencies: 1552 | cssstyle: 4.3.0 1553 | data-urls: 5.0.0 1554 | decimal.js: 10.5.0 1555 | html-encoding-sniffer: 4.0.0 1556 | http-proxy-agent: 7.0.2 1557 | https-proxy-agent: 7.0.6 1558 | is-potential-custom-element-name: 1.0.1 1559 | nwsapi: 2.2.20 1560 | parse5: 7.2.1 1561 | rrweb-cssom: 0.8.0 1562 | saxes: 6.0.0 1563 | symbol-tree: 3.2.4 1564 | tough-cookie: 5.1.2 1565 | w3c-xmlserializer: 5.0.0 1566 | webidl-conversions: 7.0.0 1567 | whatwg-encoding: 3.1.1 1568 | whatwg-mimetype: 4.0.0 1569 | whatwg-url: 14.2.0 1570 | ws: 8.18.1 1571 | xml-name-validator: 5.0.0 1572 | transitivePeerDependencies: 1573 | - bufferutil 1574 | - supports-color 1575 | - utf-8-validate 1576 | optional: true 1577 | 1578 | lilconfig@3.1.3: {} 1579 | 1580 | lines-and-columns@1.2.4: {} 1581 | 1582 | load-tsconfig@0.2.5: {} 1583 | 1584 | lodash.sortby@4.7.0: {} 1585 | 1586 | loupe@3.1.3: {} 1587 | 1588 | lru-cache@10.4.3: {} 1589 | 1590 | magic-string@0.30.17: 1591 | dependencies: 1592 | '@jridgewell/sourcemap-codec': 1.5.0 1593 | 1594 | minimatch@9.0.5: 1595 | dependencies: 1596 | brace-expansion: 2.0.1 1597 | 1598 | minipass@7.1.2: {} 1599 | 1600 | ms@2.1.3: {} 1601 | 1602 | mz@2.7.0: 1603 | dependencies: 1604 | any-promise: 1.3.0 1605 | object-assign: 4.1.1 1606 | thenify-all: 1.6.0 1607 | 1608 | nanoid@3.3.11: {} 1609 | 1610 | nanoid@5.1.5: {} 1611 | 1612 | nanospinner@1.2.2: 1613 | dependencies: 1614 | picocolors: 1.1.1 1615 | 1616 | nwsapi@2.2.20: 1617 | optional: true 1618 | 1619 | object-assign@4.1.1: {} 1620 | 1621 | oxlint@0.16.6: 1622 | optionalDependencies: 1623 | '@oxlint/darwin-arm64': 0.16.6 1624 | '@oxlint/darwin-x64': 0.16.6 1625 | '@oxlint/linux-arm64-gnu': 0.16.6 1626 | '@oxlint/linux-arm64-musl': 0.16.6 1627 | '@oxlint/linux-x64-gnu': 0.16.6 1628 | '@oxlint/linux-x64-musl': 0.16.6 1629 | '@oxlint/win32-arm64': 0.16.6 1630 | '@oxlint/win32-x64': 0.16.6 1631 | 1632 | package-json-from-dist@1.0.1: {} 1633 | 1634 | parse5@7.2.1: 1635 | dependencies: 1636 | entities: 4.5.0 1637 | optional: true 1638 | 1639 | path-key@3.1.1: {} 1640 | 1641 | path-scurry@1.11.1: 1642 | dependencies: 1643 | lru-cache: 10.4.3 1644 | minipass: 7.1.2 1645 | 1646 | pathe@2.0.3: {} 1647 | 1648 | pathval@2.0.0: {} 1649 | 1650 | picocolors@1.1.1: {} 1651 | 1652 | picomatch@4.0.2: {} 1653 | 1654 | pirates@4.0.7: {} 1655 | 1656 | postcss-load-config@6.0.1(jiti@2.4.2)(postcss@8.5.3): 1657 | dependencies: 1658 | lilconfig: 3.1.3 1659 | optionalDependencies: 1660 | jiti: 2.4.2 1661 | postcss: 8.5.3 1662 | 1663 | postcss@8.5.3: 1664 | dependencies: 1665 | nanoid: 3.3.11 1666 | picocolors: 1.1.1 1667 | source-map-js: 1.2.1 1668 | 1669 | prettier@3.5.3: {} 1670 | 1671 | punycode@2.3.1: {} 1672 | 1673 | readdirp@4.1.2: {} 1674 | 1675 | resolve-from@5.0.0: {} 1676 | 1677 | rollup@4.40.0: 1678 | dependencies: 1679 | '@types/estree': 1.0.7 1680 | optionalDependencies: 1681 | '@rollup/rollup-android-arm-eabi': 4.40.0 1682 | '@rollup/rollup-android-arm64': 4.40.0 1683 | '@rollup/rollup-darwin-arm64': 4.40.0 1684 | '@rollup/rollup-darwin-x64': 4.40.0 1685 | '@rollup/rollup-freebsd-arm64': 4.40.0 1686 | '@rollup/rollup-freebsd-x64': 4.40.0 1687 | '@rollup/rollup-linux-arm-gnueabihf': 4.40.0 1688 | '@rollup/rollup-linux-arm-musleabihf': 4.40.0 1689 | '@rollup/rollup-linux-arm64-gnu': 4.40.0 1690 | '@rollup/rollup-linux-arm64-musl': 4.40.0 1691 | '@rollup/rollup-linux-loongarch64-gnu': 4.40.0 1692 | '@rollup/rollup-linux-powerpc64le-gnu': 4.40.0 1693 | '@rollup/rollup-linux-riscv64-gnu': 4.40.0 1694 | '@rollup/rollup-linux-riscv64-musl': 4.40.0 1695 | '@rollup/rollup-linux-s390x-gnu': 4.40.0 1696 | '@rollup/rollup-linux-x64-gnu': 4.40.0 1697 | '@rollup/rollup-linux-x64-musl': 4.40.0 1698 | '@rollup/rollup-win32-arm64-msvc': 4.40.0 1699 | '@rollup/rollup-win32-ia32-msvc': 4.40.0 1700 | '@rollup/rollup-win32-x64-msvc': 4.40.0 1701 | fsevents: 2.3.3 1702 | 1703 | rrweb-cssom@0.8.0: 1704 | optional: true 1705 | 1706 | safer-buffer@2.1.2: 1707 | optional: true 1708 | 1709 | saxes@6.0.0: 1710 | dependencies: 1711 | xmlchars: 2.2.0 1712 | optional: true 1713 | 1714 | shebang-command@2.0.0: 1715 | dependencies: 1716 | shebang-regex: 3.0.0 1717 | 1718 | shebang-regex@3.0.0: {} 1719 | 1720 | siginfo@2.0.0: {} 1721 | 1722 | signal-exit@4.1.0: {} 1723 | 1724 | size-limit@11.2.0: 1725 | dependencies: 1726 | bytes-iec: 3.1.1 1727 | chokidar: 4.0.3 1728 | jiti: 2.4.2 1729 | lilconfig: 3.1.3 1730 | nanospinner: 1.2.2 1731 | picocolors: 1.1.1 1732 | tinyglobby: 0.2.12 1733 | 1734 | source-map-js@1.2.1: {} 1735 | 1736 | source-map@0.8.0-beta.0: 1737 | dependencies: 1738 | whatwg-url: 7.1.0 1739 | 1740 | stackback@0.0.2: {} 1741 | 1742 | std-env@3.9.0: {} 1743 | 1744 | string-width@4.2.3: 1745 | dependencies: 1746 | emoji-regex: 8.0.0 1747 | is-fullwidth-code-point: 3.0.0 1748 | strip-ansi: 6.0.1 1749 | 1750 | string-width@5.1.2: 1751 | dependencies: 1752 | eastasianwidth: 0.2.0 1753 | emoji-regex: 9.2.2 1754 | strip-ansi: 7.1.0 1755 | 1756 | strip-ansi@6.0.1: 1757 | dependencies: 1758 | ansi-regex: 5.0.1 1759 | 1760 | strip-ansi@7.1.0: 1761 | dependencies: 1762 | ansi-regex: 6.1.0 1763 | 1764 | sucrase@3.35.0: 1765 | dependencies: 1766 | '@jridgewell/gen-mapping': 0.3.8 1767 | commander: 4.1.1 1768 | glob: 10.4.5 1769 | lines-and-columns: 1.2.4 1770 | mz: 2.7.0 1771 | pirates: 4.0.7 1772 | ts-interface-checker: 0.1.13 1773 | 1774 | symbol-tree@3.2.4: 1775 | optional: true 1776 | 1777 | thenify-all@1.6.0: 1778 | dependencies: 1779 | thenify: 3.3.1 1780 | 1781 | thenify@3.3.1: 1782 | dependencies: 1783 | any-promise: 1.3.0 1784 | 1785 | tinybench@2.9.0: {} 1786 | 1787 | tinyexec@0.3.2: {} 1788 | 1789 | tinyglobby@0.2.12: 1790 | dependencies: 1791 | fdir: 6.4.3(picomatch@4.0.2) 1792 | picomatch: 4.0.2 1793 | 1794 | tinypool@1.0.2: {} 1795 | 1796 | tinyrainbow@2.0.0: {} 1797 | 1798 | tinyspy@3.0.2: {} 1799 | 1800 | tldts-core@6.1.86: 1801 | optional: true 1802 | 1803 | tldts@6.1.86: 1804 | dependencies: 1805 | tldts-core: 6.1.86 1806 | optional: true 1807 | 1808 | tough-cookie@5.1.2: 1809 | dependencies: 1810 | tldts: 6.1.86 1811 | optional: true 1812 | 1813 | tr46@1.0.1: 1814 | dependencies: 1815 | punycode: 2.3.1 1816 | 1817 | tr46@5.1.1: 1818 | dependencies: 1819 | punycode: 2.3.1 1820 | optional: true 1821 | 1822 | tree-kill@1.2.2: {} 1823 | 1824 | ts-interface-checker@0.1.13: {} 1825 | 1826 | tsup@8.4.0(jiti@2.4.2)(postcss@8.5.3)(typescript@5.8.3): 1827 | dependencies: 1828 | bundle-require: 5.1.0(esbuild@0.25.2) 1829 | cac: 6.7.14 1830 | chokidar: 4.0.3 1831 | consola: 3.4.2 1832 | debug: 4.4.0 1833 | esbuild: 0.25.2 1834 | joycon: 3.1.1 1835 | picocolors: 1.1.1 1836 | postcss-load-config: 6.0.1(jiti@2.4.2)(postcss@8.5.3) 1837 | resolve-from: 5.0.0 1838 | rollup: 4.40.0 1839 | source-map: 0.8.0-beta.0 1840 | sucrase: 3.35.0 1841 | tinyexec: 0.3.2 1842 | tinyglobby: 0.2.12 1843 | tree-kill: 1.2.2 1844 | optionalDependencies: 1845 | postcss: 8.5.3 1846 | typescript: 5.8.3 1847 | transitivePeerDependencies: 1848 | - jiti 1849 | - supports-color 1850 | - tsx 1851 | - yaml 1852 | 1853 | typescript@5.8.3: {} 1854 | 1855 | undici-types@6.21.0: 1856 | optional: true 1857 | 1858 | vite-node@3.1.1(@types/node@22.14.1)(jiti@2.4.2): 1859 | dependencies: 1860 | cac: 6.7.14 1861 | debug: 4.4.0 1862 | es-module-lexer: 1.6.0 1863 | pathe: 2.0.3 1864 | vite: 6.3.2(@types/node@22.14.1)(jiti@2.4.2) 1865 | transitivePeerDependencies: 1866 | - '@types/node' 1867 | - jiti 1868 | - less 1869 | - lightningcss 1870 | - sass 1871 | - sass-embedded 1872 | - stylus 1873 | - sugarss 1874 | - supports-color 1875 | - terser 1876 | - tsx 1877 | - yaml 1878 | 1879 | vite@6.3.2(@types/node@22.14.1)(jiti@2.4.2): 1880 | dependencies: 1881 | esbuild: 0.25.2 1882 | fdir: 6.4.3(picomatch@4.0.2) 1883 | picomatch: 4.0.2 1884 | postcss: 8.5.3 1885 | rollup: 4.40.0 1886 | tinyglobby: 0.2.12 1887 | optionalDependencies: 1888 | '@types/node': 22.14.1 1889 | fsevents: 2.3.3 1890 | jiti: 2.4.2 1891 | 1892 | vitest@3.1.1(@types/node@22.14.1)(happy-dom@17.4.4)(jiti@2.4.2)(jsdom@26.1.0): 1893 | dependencies: 1894 | '@vitest/expect': 3.1.1 1895 | '@vitest/mocker': 3.1.1(vite@6.3.2(@types/node@22.14.1)(jiti@2.4.2)) 1896 | '@vitest/pretty-format': 3.1.1 1897 | '@vitest/runner': 3.1.1 1898 | '@vitest/snapshot': 3.1.1 1899 | '@vitest/spy': 3.1.1 1900 | '@vitest/utils': 3.1.1 1901 | chai: 5.2.0 1902 | debug: 4.4.0 1903 | expect-type: 1.2.1 1904 | magic-string: 0.30.17 1905 | pathe: 2.0.3 1906 | std-env: 3.9.0 1907 | tinybench: 2.9.0 1908 | tinyexec: 0.3.2 1909 | tinypool: 1.0.2 1910 | tinyrainbow: 2.0.0 1911 | vite: 6.3.2(@types/node@22.14.1)(jiti@2.4.2) 1912 | vite-node: 3.1.1(@types/node@22.14.1)(jiti@2.4.2) 1913 | why-is-node-running: 2.3.0 1914 | optionalDependencies: 1915 | '@types/node': 22.14.1 1916 | happy-dom: 17.4.4 1917 | jsdom: 26.1.0 1918 | transitivePeerDependencies: 1919 | - jiti 1920 | - less 1921 | - lightningcss 1922 | - msw 1923 | - sass 1924 | - sass-embedded 1925 | - stylus 1926 | - sugarss 1927 | - supports-color 1928 | - terser 1929 | - tsx 1930 | - yaml 1931 | 1932 | w3c-xmlserializer@5.0.0: 1933 | dependencies: 1934 | xml-name-validator: 5.0.0 1935 | optional: true 1936 | 1937 | webidl-conversions@4.0.2: {} 1938 | 1939 | webidl-conversions@7.0.0: 1940 | optional: true 1941 | 1942 | whatwg-encoding@3.1.1: 1943 | dependencies: 1944 | iconv-lite: 0.6.3 1945 | optional: true 1946 | 1947 | whatwg-mimetype@3.0.0: 1948 | optional: true 1949 | 1950 | whatwg-mimetype@4.0.0: 1951 | optional: true 1952 | 1953 | whatwg-url@14.2.0: 1954 | dependencies: 1955 | tr46: 5.1.1 1956 | webidl-conversions: 7.0.0 1957 | optional: true 1958 | 1959 | whatwg-url@7.1.0: 1960 | dependencies: 1961 | lodash.sortby: 4.7.0 1962 | tr46: 1.0.1 1963 | webidl-conversions: 4.0.2 1964 | 1965 | which@2.0.2: 1966 | dependencies: 1967 | isexe: 2.0.0 1968 | 1969 | why-is-node-running@2.3.0: 1970 | dependencies: 1971 | siginfo: 2.0.0 1972 | stackback: 0.0.2 1973 | 1974 | wrap-ansi@7.0.0: 1975 | dependencies: 1976 | ansi-styles: 4.3.0 1977 | string-width: 4.2.3 1978 | strip-ansi: 6.0.1 1979 | 1980 | wrap-ansi@8.1.0: 1981 | dependencies: 1982 | ansi-styles: 6.2.1 1983 | string-width: 5.1.2 1984 | strip-ansi: 7.1.0 1985 | 1986 | ws@8.18.1: 1987 | optional: true 1988 | 1989 | xml-name-validator@5.0.0: 1990 | optional: true 1991 | 1992 | xmlchars@2.2.0: 1993 | optional: true 1994 | -------------------------------------------------------------------------------- /prettier.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import("prettier").Config} */ 2 | module.exports = { 3 | singleQuote: false, 4 | trailingComma: "all", 5 | printWidth: 100, 6 | }; 7 | -------------------------------------------------------------------------------- /scripts/bump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if version type is provided 4 | if [ $# -lt 1 ]; then 5 | echo "Usage: $0 " 6 | exit 1 7 | fi 8 | 9 | VERSION_TYPE=$1 10 | 11 | # Use npm to bump the version in package.json (without git commit and tag) 12 | npm --no-git-tag-version version $VERSION_TYPE 13 | 14 | # Get the new version from package.json 15 | NEW_VERSION=$(node -p "require('./package.json').version") 16 | 17 | # Update jsr.json if it exists 18 | if [ -f "jsr.json" ]; then 19 | jq ".version = \"$NEW_VERSION\"" jsr.json > jsr.json.tmp && mv jsr.json.tmp jsr.json 20 | prettier --write jsr.json 21 | echo "Updated version in jsr.json" 22 | fi 23 | 24 | # Stage the changes 25 | git add package.json 26 | if [ -f "jsr.json" ]; then 27 | git add jsr.json 28 | fi 29 | 30 | # Commit the changes 31 | git commit -m "Bump version to: $NEW_VERSION" 32 | 33 | # Create a tag without 'v' prefix 34 | git tag -a "$NEW_VERSION" -m "Release $NEW_VERSION" 35 | 36 | echo "Version bump complete. New version: $NEW_VERSION" 37 | echo "Remember to push both the commit and the tag:" 38 | echo " git push origin main" 39 | echo " git push --tags" 40 | -------------------------------------------------------------------------------- /src/createTypedChannel.test.ts: -------------------------------------------------------------------------------- 1 | import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; 2 | import { createTypedChannel } from "./createTypedChannel"; 3 | import type { Message, TypedChannelTransport } from "./types"; 4 | 5 | type TestInboundMessages = { 6 | ready: never; 7 | configLoading: Record; 8 | }; 9 | 10 | type TestOutboundMessages = { 11 | "window:resize": { width: number; height: number }; 12 | "ctaButton:click": never; 13 | }; 14 | 15 | function createMockTransport() { 16 | const unlisten = vi.fn(); 17 | let messageHandler: ((message: Message) => void) | null = null; 18 | 19 | return { 20 | on: vi.fn((handler) => { 21 | messageHandler = handler; 22 | return unlisten; 23 | }), 24 | emit: vi.fn(), 25 | simulateMessageFromTransport: (message: Message) => { 26 | messageHandler?.(message); 27 | }, 28 | unlisten, 29 | } as TypedChannelTransport & { 30 | simulateMessageFromTransport: (message: Message) => void; 31 | unlisten: ReturnType; 32 | }; 33 | } 34 | 35 | describe("createTypedChannel", () => { 36 | let mockTransport: ReturnType; 37 | 38 | beforeEach(() => { 39 | mockTransport = createMockTransport(); 40 | }); 41 | 42 | afterEach(() => { 43 | vi.resetAllMocks(); 44 | }); 45 | 46 | describe("listen()", () => { 47 | test("Should start listening on creation", () => { 48 | const mockTransport = createMockTransport(); 49 | createTypedChannel(mockTransport); 50 | expect(mockTransport.on).toHaveBeenCalledTimes(1); 51 | }); 52 | 53 | test("Should not register multiple handlers if already listening", () => { 54 | const mockTransport = createMockTransport(); 55 | const channel = createTypedChannel(mockTransport); 56 | 57 | channel.listen(); 58 | channel.listen(); 59 | channel.listen(); 60 | 61 | expect(mockTransport.on).toHaveBeenCalledTimes(1); 62 | }); 63 | 64 | test("Should register handler with transport if not already listening", () => { 65 | const channel = createTypedChannel(mockTransport); 66 | 67 | channel.unlisten(); 68 | channel.listen(); 69 | 70 | expect(mockTransport.on).toHaveBeenCalledTimes(2); 71 | }); 72 | }); 73 | 74 | describe("unlisten()", () => { 75 | test("Should call cleanup function when unlistening", () => { 76 | const channel = createTypedChannel(mockTransport); 77 | channel.unlisten(); 78 | expect(mockTransport.unlisten).toHaveBeenCalledTimes(1); 79 | }); 80 | 81 | test("Should not call cleanup function if already unlistened", () => { 82 | const channel = createTypedChannel(mockTransport); 83 | channel.unlisten(); 84 | channel.unlisten(); 85 | expect(mockTransport.unlisten).toHaveBeenCalledTimes(1); 86 | }); 87 | }); 88 | 89 | describe("emit()", () => { 90 | test("Should send message through transport with correct format", () => { 91 | const channel = createTypedChannel(mockTransport); 92 | 93 | channel.emit("window:resize", { width: 800, height: 600 }); 94 | expect(mockTransport.emit).toHaveBeenCalledWith({ 95 | type: "window:resize", 96 | payload: { width: 800, height: 600 }, 97 | }); 98 | }); 99 | 100 | test("Should send message through transport with no payload", () => { 101 | const channel = createTypedChannel(mockTransport); 102 | 103 | channel.emit("ctaButton:click"); 104 | expect(mockTransport.emit).toHaveBeenCalledWith({ type: "ctaButton:click" }); 105 | }); 106 | 107 | test("Should not allow sending unknown message types", () => { 108 | const channel = createTypedChannel(mockTransport); 109 | 110 | // @ts-expect-error 111 | channel.emit("unknownMessageType", { some: "data" }); 112 | }); 113 | }); 114 | 115 | describe("on()", () => { 116 | test("Should register handler for specific message type", () => { 117 | const channel = createTypedChannel(mockTransport); 118 | const handler = vi.fn(); 119 | 120 | channel.on("ready", handler); 121 | mockTransport.simulateMessageFromTransport({ 122 | type: "ready", 123 | payload: undefined, 124 | }); 125 | 126 | expect(handler).toHaveBeenCalledTimes(1); 127 | expect(handler).toHaveBeenCalledWith(undefined); 128 | }); 129 | 130 | test("Should call handler only for the specified message type", () => { 131 | const channel = createTypedChannel(mockTransport); 132 | const handler = vi.fn(); 133 | 134 | channel.on("configLoading", handler); 135 | mockTransport.simulateMessageFromTransport({ type: "ready", payload: undefined }); 136 | 137 | expect(handler).not.toHaveBeenCalled(); 138 | }); 139 | 140 | test("Should call multiple handlers for the same message type", () => { 141 | const channel = createTypedChannel(mockTransport); 142 | const handler1 = vi.fn(); 143 | const handler2 = vi.fn(); 144 | const mockConfig = {}; 145 | 146 | channel.on("configLoading", handler1); 147 | channel.on("configLoading", handler2); 148 | mockTransport.simulateMessageFromTransport({ type: "configLoading", payload: mockConfig }); 149 | 150 | expect(handler1).toHaveBeenCalledTimes(1); 151 | expect(handler1).toHaveBeenCalledWith(mockConfig); 152 | expect(handler2).toHaveBeenCalledTimes(1); 153 | expect(handler2).toHaveBeenCalledWith(mockConfig); 154 | }); 155 | 156 | test("Should return cleanup function that removes handler", () => { 157 | const channel = createTypedChannel(mockTransport); 158 | const handler = vi.fn(); 159 | 160 | const cleanup = channel.on("ready", handler); 161 | cleanup(); 162 | mockTransport.simulateMessageFromTransport({ type: "ready", payload: undefined }); 163 | 164 | expect(handler).not.toHaveBeenCalled(); 165 | }); 166 | }); 167 | 168 | describe("Multiple transports support", () => { 169 | test("Should receive messages from all transports", () => { 170 | const mockTransport2 = createMockTransport(); 171 | const channel = createTypedChannel([mockTransport, mockTransport2]); 172 | const handler = vi.fn(); 173 | 174 | channel.on("configLoading", handler); 175 | mockTransport.simulateMessageFromTransport({ 176 | type: "configLoading", 177 | payload: { data: "from transport 1" }, 178 | }); 179 | mockTransport2.simulateMessageFromTransport({ 180 | type: "configLoading", 181 | payload: { data: "from transport 2" }, 182 | }); 183 | 184 | expect(handler).toHaveBeenCalledTimes(2); 185 | expect(handler).toHaveBeenCalledWith({ data: "from transport 1" }); 186 | expect(handler).toHaveBeenCalledWith({ data: "from transport 2" }); 187 | }); 188 | 189 | test("Should emit messages to all transports", () => { 190 | const mockTransport2 = createMockTransport(); 191 | const channel = createTypedChannel([mockTransport, mockTransport2]); 192 | channel.emit("window:resize", { width: 1024, height: 768 }); 193 | 194 | expect(mockTransport.emit).toHaveBeenCalledWith({ 195 | type: "window:resize", 196 | payload: { width: 1024, height: 768 }, 197 | }); 198 | expect(mockTransport2.emit).toHaveBeenCalledWith({ 199 | type: "window:resize", 200 | payload: { width: 1024, height: 768 }, 201 | }); 202 | }); 203 | 204 | test("Should unlisten from all transports", () => { 205 | const mockTransport2 = createMockTransport(); 206 | const channel = createTypedChannel([mockTransport, mockTransport2]); 207 | 208 | channel.unlisten(); 209 | expect(mockTransport.unlisten).toHaveBeenCalled(); 210 | expect(mockTransport2.unlisten).toHaveBeenCalled(); 211 | }); 212 | }); 213 | }); 214 | -------------------------------------------------------------------------------- /src/createTypedChannel.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | CleanupFunction, 3 | AnyMessages, 4 | TypedChannelTransport, 5 | Message, 6 | TypedChannel, 7 | } from "./types"; 8 | 9 | /** 10 | * Creates a typed communication channel that can send and receive strongly-typed messages. 11 | * This is useful for creating a strongly-typed API for communication between different parts of an application, such as between a main thread and a web worker, or between different components in a framework. 12 | * Can be unidirectional or bidirectional, depending on the transport interfaces used. To make it unidirectional, just pass the same message types for both inbound and outbound messages. 13 | * Can work either one or multiple transports. If multiple transports are passed, the channel will send messages to all of them and listen for incoming messages from all of them. 14 | * 15 | * @template InboundMessages - The message types this channel can receive 16 | * @template OutboundMessages - The message types this channel can send 17 | * @param transport - One or more transport interfaces to use for message passing 18 | * @returns A typed channel instance with methods to send and receive messages 19 | */ 20 | export function createTypedChannel< 21 | InboundMessages extends AnyMessages, 22 | OutboundMessages extends AnyMessages, 23 | >( 24 | transport: 25 | | TypedChannelTransport 26 | | TypedChannelTransport[], 27 | ): TypedChannel { 28 | const transports = Array.isArray(transport) ? transport : [transport]; 29 | const handlersByMessageType = {} as { 30 | [K in keyof InboundMessages]?: ((payload: InboundMessages[K]) => void)[]; 31 | }; 32 | let unsubscribeIncomingMessages: CleanupFunction[] = []; 33 | 34 | function handleIncomingMessage( 35 | message: Message, 36 | ) { 37 | if (handlersByMessageType[message.type]) { 38 | const typeHandlers = handlersByMessageType[message.type]; 39 | 40 | if (!typeHandlers) { 41 | return; 42 | } 43 | 44 | for (const handler of typeHandlers) { 45 | handler(message.payload as InboundMessages[Type]); 46 | } 47 | } 48 | } 49 | 50 | function listen() { 51 | if (unsubscribeIncomingMessages.length === 0) { 52 | unsubscribeIncomingMessages = transports.map((t) => t.on(handleIncomingMessage)); 53 | } 54 | } 55 | 56 | function unlisten() { 57 | unsubscribeIncomingMessages.forEach((unsubscribe) => unsubscribe?.()); 58 | unsubscribeIncomingMessages = []; 59 | } 60 | 61 | function emit( 62 | ...[type, payload]: OutboundMessages[Type] extends never 63 | ? [Type] 64 | : [Type, OutboundMessages[Type]] 65 | ) { 66 | const message = { type, payload } as Message; 67 | transports.forEach((t) => t.emit(message)); 68 | } 69 | 70 | function on( 71 | type: Type, 72 | handler: (payload: InboundMessages[Type]) => void, 73 | ): CleanupFunction { 74 | handlersByMessageType[type] ??= []; 75 | handlersByMessageType[type].push(handler); 76 | 77 | return () => { 78 | if (!handlersByMessageType[type]) { 79 | return; 80 | } 81 | 82 | const handlerIndex = handlersByMessageType[type].indexOf(handler); 83 | 84 | if (handlerIndex !== -1) { 85 | handlersByMessageType[type].splice(handlerIndex, 1); 86 | } 87 | }; 88 | } 89 | 90 | listen(); 91 | 92 | return { 93 | listen, 94 | unlisten, 95 | emit, 96 | on, 97 | }; 98 | } 99 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * # typed-channel 3 | * 4 | * A type-safe communication channel for sending and receiving messages between different contexts in TypeScript. 5 | * 6 | * ## Features 7 | * 8 | * - Full TypeScript support with type checking for messages 9 | * - Multiple transport implementations (EventTarget, PostMessage, your custom transport) 10 | * - Simple, lightweight API 11 | * - Zero dependencies 12 | * 13 | * ## Core Concepts 14 | * 15 | * This library provides strongly-typed message passing for: 16 | * - Communication between main thread and Web Workers 17 | * - Cross-frame communication using postMessage 18 | * - Communication between different parts of your application via events 19 | * - Any other event-based communication 20 | * 21 | * ## Basic Usage 22 | * 23 | * ```typescript 24 | * // 1. Define your message types 25 | * type Messages = { 26 | * notify: { message: string }; 27 | * clear: never; // Use 'never' for messages with no payload 28 | * }; 29 | * 30 | * // 2. Choose a transport 31 | * const transport = createEventTargetTransport(); 32 | * const channel = createTypedChannel(transport); 33 | * 34 | * // 3. Send and receive messages 35 | * const unsubscribe = channel.on("notify", ({ message }) => { 36 | * console.log(`Notification: ${message}`); 37 | * }); 38 | * 39 | * channel.emit("notify", { message: "Hello world" }); 40 | * ``` 41 | * 42 | * ## Transport Types 43 | * 44 | * - **EventTarget**: Uses the standard DOM `EventTarget` interface for communication. 45 | * - **PostMessage**: For communication with Workers, iframes, or other such contexts. 46 | * 47 | * ## Bidirectional Communication 48 | * 49 | * You can specify different message types for inbound and outbound communication: 50 | * 51 | * ```typescript 52 | * type InboundMessages = { status: { code: number } }; 53 | * type OutboundMessages = { fetch: { id: string } }; 54 | * 55 | * // Different types for incoming and outgoing messages 56 | * const transport = createPostMessageTransport(worker); 57 | * const channel = createTypedChannel(transport); 58 | * ``` 59 | * 60 | * ## Multiple Transports 61 | * 62 | * You can combine multiple transports to send messages to different targets: 63 | * 64 | * ```typescript 65 | * import { createTypedChannel } from "typed-channel"; 66 | * import { createEventTargetTransport } from "typed-channel/transports/eventTarget"; 67 | * import { createPostMessageTransport } from "typed-channel/transports/postMessage"; 68 | * 69 | * const broadcastChannel = new BroadcastChannel("example-channel"); 70 | * const broadcastTransport = createPostMessageTransport(broadcastChannel); 71 | * const localTransport = createEventTargetTransport(); 72 | * 73 | * // Messages will be sent to all tabs, including current one 74 | * const channel = createTypedChannel([localTransport, broadcastTransport]); 75 | * ``` 76 | * 77 | * @module typed-channel 78 | */ 79 | 80 | export { createTypedChannel } from "./createTypedChannel"; 81 | export { createEventTargetTransport } from "./transports/eventTarget"; 82 | export { createPostMessageTransport } from "./transports/postMessage"; 83 | export * from "./types"; 84 | -------------------------------------------------------------------------------- /src/transports/eventTarget.test.ts: -------------------------------------------------------------------------------- 1 | import type { MockInstance } from "vitest"; 2 | import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; 3 | import type { AnyMessageOf } from "../types"; 4 | import { createEventTargetTransport } from "./eventTarget"; 5 | 6 | type TestMessages = { 7 | notify: { message: string }; 8 | update: { id: number; value: string }; 9 | clear: never; 10 | }; 11 | 12 | describe("createEventTargetTransport", () => { 13 | let mockTarget: EventTarget; 14 | let dispatchSpy: MockInstance; 15 | let addListenerSpy: MockInstance; 16 | let removeListenerSpy: MockInstance; 17 | 18 | beforeEach(() => { 19 | mockTarget = new EventTarget(); 20 | dispatchSpy = vi.spyOn(mockTarget, "dispatchEvent"); 21 | addListenerSpy = vi.spyOn(mockTarget, "addEventListener"); 22 | removeListenerSpy = vi.spyOn(mockTarget, "removeEventListener"); 23 | }); 24 | 25 | afterEach(() => { 26 | vi.resetAllMocks(); 27 | }); 28 | 29 | test("Should create transport with default target when none provided", () => { 30 | const transport = createEventTargetTransport(); 31 | expect(transport).toHaveProperty("on"); 32 | expect(transport).toHaveProperty("emit"); 33 | }); 34 | 35 | test("Should create transport with provided target", () => { 36 | const transport = createEventTargetTransport(mockTarget); 37 | expect(transport).toHaveProperty("on"); 38 | expect(transport).toHaveProperty("emit"); 39 | }); 40 | 41 | describe("on()", () => { 42 | test("Should register message handler", () => { 43 | const transport = createEventTargetTransport(mockTarget); 44 | const handler = vi.fn(); 45 | 46 | transport.on(handler); 47 | 48 | expect(addListenerSpy).toHaveBeenCalledTimes(1); 49 | expect(addListenerSpy).toHaveBeenCalledWith("message", expect.any(Function)); 50 | }); 51 | 52 | test("Should call handler with message detail when event is dispatched", () => { 53 | const transport = createEventTargetTransport(mockTarget); 54 | const handler = vi.fn(); 55 | const message: AnyMessageOf = { 56 | type: "notify", 57 | payload: { message: "Hello" }, 58 | }; 59 | 60 | transport.on(handler); 61 | mockTarget.dispatchEvent(new CustomEvent("message", { detail: message })); 62 | expect(handler).toHaveBeenCalledTimes(1); 63 | expect(handler).toHaveBeenCalledWith(message); 64 | }); 65 | 66 | test("Should return cleanup function that removes event listener", () => { 67 | const transport = createEventTargetTransport(mockTarget); 68 | const handler = vi.fn(); 69 | 70 | const cleanup = transport.on(handler); 71 | expect(addListenerSpy).toHaveBeenCalledTimes(1); 72 | 73 | cleanup(); 74 | expect(removeListenerSpy).toHaveBeenCalledTimes(1); 75 | expect(removeListenerSpy).toHaveBeenCalledWith("message", expect.any(Function)); 76 | }); 77 | }); 78 | 79 | describe("emit()", () => { 80 | test("Should dispatch custom event with message as detail", () => { 81 | const transport = createEventTargetTransport(mockTarget); 82 | const message: AnyMessageOf = { 83 | type: "update", 84 | payload: { id: 123, value: "test" }, 85 | }; 86 | 87 | transport.emit(message); 88 | 89 | expect(dispatchSpy).toHaveBeenCalledTimes(1); 90 | expect(dispatchSpy).toHaveBeenCalledWith(expect.any(CustomEvent)); 91 | 92 | const eventArg = dispatchSpy.mock.calls[0]?.[0] as CustomEvent; 93 | expect(eventArg.type).toBe("message"); 94 | expect(eventArg.detail).toEqual(message); 95 | }); 96 | 97 | test("Should handle messages with no payload", () => { 98 | const transport = createEventTargetTransport(mockTarget); 99 | const message: AnyMessageOf = { 100 | type: "clear", 101 | payload: undefined, 102 | }; 103 | 104 | transport.emit(message); 105 | 106 | expect(dispatchSpy).toHaveBeenCalledTimes(1); 107 | 108 | const eventArg = dispatchSpy.mock.calls[0]?.[0] as CustomEvent; 109 | expect(eventArg.detail).toEqual(message); 110 | }); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /src/transports/eventTarget.ts: -------------------------------------------------------------------------------- 1 | import type { AnyMessageOf, AnyMessages, TypedChannelTransport } from "../types"; 2 | 3 | /** 4 | * Creates a transport that uses the DOM EventTarget interface for communication. 5 | * This transport can be used with any object that implements the EventTarget interface. 6 | * The most common use case is creating event channels in a web application (event emitters) 7 | * 8 | * @template InboundMessages - The message types this transport can receive 9 | * @template OutboundMessages - The message types this transport can send (defaults to InboundMessages) 10 | * @param target - The EventTarget instance to use for event dispatching (defaults to a new EventTarget) 11 | * @returns A typed channel transport for use with createTypedChannel 12 | */ 13 | export function createEventTargetTransport< 14 | InboundMessages extends AnyMessages, 15 | OutboundMessages extends AnyMessages = InboundMessages, 16 | >( 17 | target: EventTarget = new EventTarget(), 18 | ): TypedChannelTransport { 19 | /** 20 | * Registers a handler for incoming messages 21 | * 22 | * @param handler - The function to call when a message is received 23 | * @returns A function that, when called, will unregister the handler 24 | */ 25 | function on(handler: (message: AnyMessageOf) => void) { 26 | const handleMessage = (e: Event) => { 27 | handler((e as CustomEvent>).detail); 28 | }; 29 | 30 | target.addEventListener("message", handleMessage); 31 | 32 | return () => { 33 | target.removeEventListener("message", handleMessage); 34 | }; 35 | } 36 | 37 | /** 38 | * Sends a message through the event target 39 | * 40 | * @param message - The message to send 41 | */ 42 | function emit(message: AnyMessageOf) { 43 | const event = new CustomEvent("message", { detail: message }); 44 | target.dispatchEvent(event); 45 | } 46 | 47 | return { on, emit }; 48 | } 49 | -------------------------------------------------------------------------------- /src/transports/postMessage.test.ts: -------------------------------------------------------------------------------- 1 | import type { MockInstance } from "vitest"; 2 | import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; 3 | import type { AnyMessageOf } from "../types"; 4 | import { createPostMessageTransport } from "./postMessage"; 5 | import type { PostMessageTarget } from "./postMessage"; 6 | 7 | type TestMessages = { 8 | notify: { message: string }; 9 | update: { id: number; value: string }; 10 | clear: never; 11 | }; 12 | 13 | describe("createPostMessageTransport", () => { 14 | let mockTarget: PostMessageTarget; 15 | let postMessageSpy: MockInstance; 16 | let addListenerSpy: MockInstance; 17 | let removeListenerSpy: MockInstance; 18 | 19 | beforeEach(() => { 20 | mockTarget = { 21 | postMessage: vi.fn(), 22 | addEventListener: vi.fn(), 23 | removeEventListener: vi.fn(), 24 | }; 25 | postMessageSpy = vi.spyOn(mockTarget, "postMessage"); 26 | addListenerSpy = vi.spyOn(mockTarget, "addEventListener"); 27 | removeListenerSpy = vi.spyOn(mockTarget, "removeEventListener"); 28 | }); 29 | 30 | afterEach(() => { 31 | vi.resetAllMocks(); 32 | }); 33 | 34 | test("Should create transport with provided target", () => { 35 | const transport = createPostMessageTransport(mockTarget); 36 | expect(transport).toHaveProperty("on"); 37 | expect(transport).toHaveProperty("emit"); 38 | }); 39 | 40 | describe("on()", () => { 41 | test("Should register message handler", () => { 42 | const transport = createPostMessageTransport(mockTarget); 43 | const handler = vi.fn(); 44 | 45 | transport.on(handler); 46 | 47 | expect(addListenerSpy).toHaveBeenCalledTimes(1); 48 | expect(addListenerSpy).toHaveBeenCalledWith("message", expect.any(Function)); 49 | }); 50 | 51 | test("Should call handler with message data when message event is received", () => { 52 | const transport = createPostMessageTransport(mockTarget); 53 | const handler = vi.fn(); 54 | const message: AnyMessageOf = { 55 | type: "notify", 56 | payload: { message: "Hello" }, 57 | }; 58 | 59 | transport.on(handler); 60 | 61 | // Simulate message event 62 | const listener = addListenerSpy.mock.calls[0]?.[1]; 63 | listener?.({ data: message } as MessageEvent); 64 | 65 | expect(handler).toHaveBeenCalledTimes(1); 66 | expect(handler).toHaveBeenCalledWith(message); 67 | }); 68 | 69 | test("Should return cleanup function that removes event listener", () => { 70 | const transport = createPostMessageTransport(mockTarget); 71 | const handler = vi.fn(); 72 | 73 | const cleanup = transport.on(handler); 74 | expect(addListenerSpy).toHaveBeenCalledTimes(1); 75 | 76 | cleanup(); 77 | expect(removeListenerSpy).toHaveBeenCalledTimes(1); 78 | expect(removeListenerSpy).toHaveBeenCalledWith("message", expect.any(Function)); 79 | }); 80 | }); 81 | 82 | describe("emit()", () => { 83 | test("Should call postMessage with message", () => { 84 | const transport = createPostMessageTransport(mockTarget); 85 | const message: AnyMessageOf = { 86 | type: "update", 87 | payload: { id: 123, value: "test" }, 88 | }; 89 | 90 | transport.emit(message); 91 | 92 | expect(postMessageSpy).toHaveBeenCalledTimes(1); 93 | expect(postMessageSpy).toHaveBeenCalledWith(message); 94 | }); 95 | 96 | test("Should handle messages with no payload", () => { 97 | const transport = createPostMessageTransport(mockTarget); 98 | const message: AnyMessageOf = { 99 | type: "clear", 100 | payload: undefined, 101 | }; 102 | 103 | transport.emit(message); 104 | 105 | expect(postMessageSpy).toHaveBeenCalledTimes(1); 106 | expect(postMessageSpy).toHaveBeenCalledWith(message); 107 | }); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /src/transports/postMessage.ts: -------------------------------------------------------------------------------- 1 | import type { AnyMessageOf, AnyMessages, TypedChannelTransport } from "../types"; 2 | 3 | /** 4 | * Interface for objects that support PostMessage interface. 5 | * This includes Web Workers, iframe contentWindow objects, and other objects that implement 6 | * postMessage, addEventListener, and removeEventListener methods. 7 | */ 8 | export type PostMessageTarget = { 9 | postMessage: (message: any) => void; 10 | addEventListener: (type: "message", listener: (event: MessageEvent) => void) => void; 11 | removeEventListener: (type: "message", listener: (event: MessageEvent) => void) => void; 12 | }; 13 | 14 | /** 15 | * Creates a transport that uses the PostMessage interface for communication. 16 | * This transport can be used with Web Workers, iframes, or any object that supports 17 | * the PostMessage interface. 18 | * 19 | * @template InboundMessages - The message types this transport can receive 20 | * @template OutboundMessages - The message types this transport can send (defaults to InboundMessages) 21 | * @param target - The object implementing the PostMessageTarget interface 22 | * @returns A typed channel transport for use with createTypedChannel 23 | */ 24 | export function createPostMessageTransport< 25 | InboundMessages extends AnyMessages, 26 | OutboundMessages extends AnyMessages = InboundMessages, 27 | >(target: PostMessageTarget): TypedChannelTransport { 28 | /** 29 | * Registers a handler for incoming messages 30 | * 31 | * @param handler - The function to call when a message is received 32 | * @returns A function that, when called, will unregister the handler 33 | */ 34 | function on(handler: (message: AnyMessageOf) => void) { 35 | const handleMessage = (e: MessageEvent>) => { 36 | handler(e.data); 37 | }; 38 | 39 | target.addEventListener("message", handleMessage); 40 | 41 | return () => { 42 | target.removeEventListener("message", handleMessage); 43 | }; 44 | } 45 | 46 | /** 47 | * Sends a message through the postMessage API 48 | * 49 | * @param message - The message to send 50 | */ 51 | function emit(message: AnyMessageOf) { 52 | target.postMessage(message); 53 | } 54 | 55 | return { on, emit }; 56 | } 57 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Function that will be called on unsubscribing from a message type. 3 | */ 4 | export type CleanupFunction = VoidFunction; 5 | 6 | /** 7 | * Represents a typed message with a payload. 8 | * @template Type - The string literal type of the message. 9 | * @template Payload - The type of the payload data. 10 | */ 11 | export type Message = { 12 | type: Type; 13 | payload: [Payload] extends [never] ? undefined : Payload; 14 | }; 15 | 16 | /** 17 | * Utility type for extending real messages map from it in generics. 18 | */ 19 | export type AnyMessages = Record; 20 | 21 | /** 22 | * Utility type that extracts all possible message types from a message map. 23 | * @template T - The message map type. 24 | */ 25 | export type AnyMessageOf = { 26 | [K in keyof T & string]: Message; 27 | }[keyof T & string]; 28 | 29 | /** 30 | * The main TypedChannel interface for communication between components. 31 | * @template InboundMessages - The message types this channel can receive. 32 | * @template OutboundMessages - The message types this channel can send. 33 | */ 34 | export type TypedChannel< 35 | InboundMessages extends AnyMessages, 36 | OutboundMessages extends AnyMessages, 37 | > = { 38 | /** 39 | * Starts listening for incoming messages on all transports 40 | */ 41 | listen: () => void; 42 | /** 43 | * Stops listening for incoming messages and cleans up all transport subscriptions 44 | */ 45 | unlisten: () => void; 46 | /** 47 | * Sends a message through all transports 48 | * 49 | * @param type - The message type identifier 50 | * @param payload - The optional payload (omitted if the message type has no payload) 51 | */ 52 | emit: ( 53 | ...[type, payload]: OutboundMessages[Type] extends never 54 | ? [Type] 55 | : [Type, OutboundMessages[Type]] 56 | ) => void; 57 | /** 58 | * Registers a handler for a specific message type 59 | * 60 | * @param type - The message type identifier 61 | * @param handler - The function to call when a message of this type is received 62 | * @returns A cleanup function that, when called, will unregister the handler 63 | */ 64 | on: ( 65 | type: Type, 66 | handler: (payload: InboundMessages[Type]) => void, 67 | ) => CleanupFunction; 68 | }; 69 | 70 | /** 71 | * Interface for a transport mechanism that can send and receive messages. 72 | * @template InboundMessages - The message types this transport can receive. 73 | * @template OutboundMessages - The message types this transport can send. 74 | */ 75 | export type TypedChannelTransport< 76 | InboundMessages extends AnyMessages, 77 | OutboundMessages extends AnyMessages, 78 | > = { 79 | /** 80 | * Register a handler for incoming messages. 81 | * @param handler - The function to call when a message is received. 82 | * @returns A function that, when called, will unregister the handler. 83 | */ 84 | on: (handler: (message: AnyMessageOf) => void) => CleanupFunction; 85 | /** 86 | * Send a message through the transport. 87 | * @param message - The message to send. 88 | */ 89 | emit: (message: AnyMessageOf) => void; 90 | }; 91 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "target": "ES2024", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "resolveJsonModule": true, 15 | "verbatimModuleSyntax": true, 16 | "noEmit": true, 17 | "declaration": true, 18 | "declarationMap": true, 19 | 20 | /* Linting */ 21 | "strict": true, 22 | "exactOptionalPropertyTypes": true, 23 | "noImplicitOverride": true, 24 | "noUnusedLocals": true, 25 | "noUnusedParameters": true, 26 | "noFallthroughCasesInSwitch": true, 27 | "noImplicitReturns": true, 28 | "noUncheckedIndexedAccess": true, 29 | "noPropertyAccessFromIndexSignature": true, 30 | "noUncheckedSideEffectImports": true 31 | }, 32 | "include": ["src", "examples"], 33 | "exclude": ["examples/DenoEventTarget", "examples/NodeEventEmitter"] 34 | } 35 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | export default defineConfig({ 4 | clean: true, 5 | dts: true, 6 | entry: ["src/**/!(*.test).ts"], 7 | format: ["esm"], 8 | outDir: "dist", 9 | treeshake: true, 10 | bundle: false, 11 | splitting: false, 12 | }); 13 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitest/config"; 2 | 3 | export default defineConfig({ 4 | test: { 5 | include: ["src/**/*.test.[jt]s?(x)"], 6 | environment: "jsdom", 7 | typecheck: { 8 | tsconfig: "./tsconfig.json", 9 | }, 10 | }, 11 | }); 12 | --------------------------------------------------------------------------------