├── viewer ├── scripts │ └── .gitkeep └── index.html ├── .gitignore ├── tsconfig.spec.json ├── tsconfig.lib.json ├── src ├── turbo-stream.ts ├── shared.spec.ts ├── turbo-stream.bench.mts ├── shared.ts ├── encode.ts ├── decode.ts ├── encode.spec.ts └── decode.spec.ts ├── tsconfig.json ├── biome.json ├── .github └── workflows │ ├── ci.yaml │ └── release-please.yml ├── LICENSE ├── package.json ├── README.md ├── CHANGELOG.md └── pnpm-lock.yaml /viewer/scripts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist 3 | node_modules 4 | *.tsbuildinfo 5 | *.tgz 6 | viewer/scripts -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src/**/*.ts"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "types": ["node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src/**/*.ts"], 4 | "exclude": ["**/*.bench.ts", "**/*.spec.ts"], 5 | "compilerOptions": { 6 | "composite": true, 7 | "rootDir": "src" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/turbo-stream.ts: -------------------------------------------------------------------------------- 1 | export type { DecodeOptions, DecodePlugin } from "./decode.js"; 2 | export { decode } from "./decode.js"; 3 | 4 | export type { EncodeOptions, EncodePlugin } from "./encode.js"; 5 | export { encode } from "./encode.js"; 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "Node16", 4 | "moduleResolution": "Node16", 5 | "lib": ["DOM", "DOM.Iterable", "ES2022"], 6 | "types": [], 7 | "strict": true 8 | }, 9 | "references": [ 10 | { 11 | "path": "./tsconfig.lib.json" 12 | }, 13 | { 14 | "path": "./tsconfig.spec.json" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "vcs": { 4 | "enabled": false, 5 | "clientKind": "git", 6 | "useIgnoreFile": false 7 | }, 8 | "files": { 9 | "ignoreUnknown": false, 10 | "ignore": [] 11 | }, 12 | "formatter": { 13 | "enabled": true, 14 | "indentStyle": "tab" 15 | }, 16 | "organizeImports": { 17 | "enabled": true 18 | }, 19 | "linter": { 20 | "enabled": true, 21 | "rules": { 22 | "recommended": true, 23 | "style": { 24 | "noParameterAssign": "off", 25 | "useConst": "off", 26 | "useTemplate": "off" 27 | }, 28 | "suspicious": { 29 | "noAssignInExpressions": "off", 30 | "noExplicitAny": "off" 31 | } 32 | } 33 | }, 34 | "javascript": { 35 | "formatter": { 36 | "quoteStyle": "double" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | - pull_request 3 | 4 | jobs: 5 | ci: 6 | runs-on: ubuntu-latest 7 | 8 | strategy: 9 | matrix: 10 | node-version: ['18.x', '20.x', '22.x'] 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | 16 | - name: Install Node.js 17 | uses: actions/setup-node@v3 18 | 19 | - uses: pnpm/action-setup@v2 20 | name: Install pnpm 21 | with: 22 | version: 9 23 | node-version: ${{ matrix.node }} 24 | cache: pnpm 25 | 26 | - name: Install dependencies 27 | run: pnpm i 28 | 29 | - name: Build 30 | run: pnpm build 31 | 32 | - name: Test 33 | run: pnpm test 34 | 35 | - name: Typecheck Tests 36 | run: pnpm test-typecheck 37 | 38 | - name: PR Publish 39 | if: matrix.node-version == '22.x' 40 | run: pnpx pkg-pr-new publish 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025 Jacob Ebey 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | jobs: 7 | release-please: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: google-github-actions/release-please-action@v3 12 | id: release 13 | with: 14 | token: ${{ secrets.GITHUB_TOKEN }} 15 | release-type: node 16 | package-name: turbo-stream 17 | 18 | - name: Checkout 19 | uses: actions/checkout@v3 20 | 21 | - name: Install Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: 18 25 | 26 | 27 | - uses: pnpm/action-setup@v2 28 | name: Install pnpm 29 | with: 30 | version: 9 31 | run_install: false 32 | 33 | - name: Get pnpm store directory 34 | shell: bash 35 | run: | 36 | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV 37 | 38 | - uses: actions/cache@v3 39 | name: Setup pnpm cache 40 | with: 41 | path: ${{ env.STORE_PATH }} 42 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 43 | restore-keys: | 44 | ${{ runner.os }}-pnpm-store- 45 | 46 | - name: Install dependencies 47 | run: pnpm i 48 | 49 | - name: Build 50 | run: pnpm build 51 | 52 | - uses: JS-DevTools/npm-publish@v3 53 | with: 54 | token: ${{ secrets.NPM_TOKEN }} 55 | if: ${{ steps.release.outputs.release_created }} 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "turbo-stream", 3 | "version": "3.1.0", 4 | "description": "A streaming data transport format that aims to support built-in features such as Promises, Dates, RegExps, Maps, Sets and more.", 5 | "files": [ 6 | "dist", 7 | "README.md" 8 | ], 9 | "main": "dist/turbo-stream.js", 10 | "types": "dist/turbo-stream.d.ts", 11 | "exports": { 12 | ".": { 13 | "types": "./dist/turbo-stream.d.ts", 14 | "import": "./dist/turbo-stream.mjs", 15 | "require": "./dist/turbo-stream.js", 16 | "default": "./dist/turbo-stream.js" 17 | }, 18 | "./package.json": "./package.json" 19 | }, 20 | "scripts": { 21 | "node": "node --no-warnings --loader tsm --enable-source-maps", 22 | "bench": "pnpm node ./src/turbo-stream.bench.mts", 23 | "build": "pnpm build:esm && pnpm build:cjs && cp ./dist/turbo-stream.mjs ./viewer/scripts/turbo-stream.js", 24 | "build:cjs": "tsc --outDir dist --project tsconfig.lib.json", 25 | "build:esm": "esbuild --bundle --platform=node --target=node16 --format=esm --outfile=dist/turbo-stream.mjs src/turbo-stream.ts", 26 | "test": "pnpm node --test-reporter tap --test src/*.spec.ts", 27 | "test-typecheck": "tsc --noEmit --project tsconfig.spec.json", 28 | "prepublish": "attw $(npm pack) --ignore-rules false-cjs" 29 | }, 30 | "keywords": [ 31 | "devalue", 32 | "turbo", 33 | "stream", 34 | "enhanced", 35 | "json" 36 | ], 37 | "author": "Jacob Ebey ", 38 | "license": "MIT", 39 | "repository": { 40 | "type": "git", 41 | "url": "https://github.com/jacob-ebey/turbo-stream.git" 42 | }, 43 | "sideEffects": false, 44 | "devDependencies": { 45 | "@arethetypeswrong/cli": "^0.12.2", 46 | "@biomejs/biome": "^1.9.4", 47 | "@types/node": "^20.8.7", 48 | "esbuild": "^0.19.5", 49 | "expect": "^29.7.0", 50 | "mitata": "^1.0.33", 51 | "pkg-pr-new": "^0.0.39", 52 | "tsm": "^2.3.0", 53 | "typescript": "^5.2.2" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /viewer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | turbo-stream 10 | 11 | 12 | 28 | 29 | 30 | 31 |
32 |

turbo-stream

33 | 38 |
39 | 40 |
41 |
42 | Decode 43 | 44 | 47 | 48 | 52 | 53 | 54 |
55 |
56 | 57 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Turbo Stream
[![turbo-stream's badge](https://deno.bundlejs.com/?q=turbo-stream&badge=detailed)](https://bundlejs.com/?q=turbo-stream) 2 | 3 | Streaming data transport format that supports: 4 | 5 | - Streaming promises 6 | - `undefined`, `null`, `Boolean`, `String` 7 | - `Bigint`, `Set`, `Map`, `URL`, `RegExp`, `Error` 8 | - `Number`, including `+Infinity`, `-Infinity` and `NaN`, `-0` 9 | - Circular references, repeated references 10 | - `Date`, including invalid dates 11 | - `Symbol` (`Symbol.from()`) 12 | - Objects with `toJSON` methods 13 | - Iterables, AsyncIterable 14 | - `Buffer`, `ArrayBuffer`, `DataView`, TypedArrays 15 | - `File`, `Blob`, `FormData`, `ReadableStream` 16 | 17 | Uses `ReadableStream` as the transport interface for encoding and decoding. 18 | 19 | Decode runtime size: [![turbo-stream's badge](https://deno.bundlejs.com/badge?q=turbo-stream&badge=detailed&treeshake=%5B%7B+decode+%7D%5D)](https://bundlejs.com/?q=turbo-stream&treeshake=%5B%7B+decode+%7D%5D) 20 | 21 | ## Installation 22 | 23 | ```bash 24 | npm install turbo-stream 25 | ``` 26 | 27 | ## Usage 28 | 29 | ```js 30 | import { decode, encode } from "turbo-stream"; 31 | 32 | const encodedStream = encode(Promise.resolve(42)); 33 | const decoded = await decode(encodedStream); 34 | console.log(decoded); // 42 35 | ``` 36 | 37 | ## Benchmarks 38 | 39 | Run them yourself with `pnpm bench` 40 | 41 | ``` 42 | • realistic payload 43 | ------------------------------------------- ------------------------------- 44 | JSON 2.80 µs/iter 2.71 µs █▆ 45 | (2.59 µs … 5.61 µs) 5.55 µs ██ 46 | ( 2.91 kb … 2.91 kb) 2.91 kb ██▁▂▁▁▁▁▁▂▁▁▁▁▁▁▁▁▁▁▂ 47 | turbo encode 16.71 µs/iter 16.47 µs █ 48 | (16.04 µs … 19.47 µs) 18.38 µs ███ 49 | ( 2.80 kb … 2.81 kb) 2.80 kb ██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ 50 | turbo full 35.30 µs/iter 36.33 µs █ 51 | (31.38 µs … 202.79 µs) 52.50 µs █▃ ▄ 52 | ( 2.47 kb … 454.32 kb) 104.44 kb ▂██▃▅█▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁ 53 | 54 | ┌ ┐ 55 | ┬ ╷ 56 | JSON │──┤ 57 | ┴ ╵ 58 | ┌┬╷ 59 | turbo encode ││┤ 60 | └┴╵ 61 | ╷┌─┬┐ ╷ 62 | turbo full ├┤ │├──────────────┤ 63 | ╵└─┴┘ ╵ 64 | └ ┘ 65 | 2.59 µs 27.55 µs 52.50 µs 66 | 67 | summary 68 | turbo encode 69 | 5.97x slower than JSON 70 | 2.11x faster than turbo full 71 | ``` 72 | 73 | ## Legacy 74 | 75 | Shout out to Rich Harris and his https://github.com/rich-harris/devalue project. Devalue has heavily influenced this project and portions of the original code was directly lifted from it. I highly recommend checking it out if you need something more cusomizable or without streaming support. This new version has been re-written from the ground up and no longer resembles devalue. 76 | -------------------------------------------------------------------------------- /src/shared.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from "node:test"; 2 | import { expect } from "expect"; 3 | 4 | import { 5 | Deferred, 6 | DeferredAsyncIterable, 7 | TurboBlob, 8 | TurboFile, 9 | WaitGroup, 10 | } from "./shared.js"; 11 | 12 | describe("WaitGroup", () => { 13 | test("basic", async () => { 14 | const wg = new WaitGroup(); 15 | const done = wg.wait(); 16 | 17 | wg.add(); 18 | setTimeout(() => wg.done(), 0); 19 | wg.add(); 20 | setTimeout(() => wg.done(), 0); 21 | 22 | await done; 23 | }); 24 | 25 | test("wait before done", async () => { 26 | const wg = new WaitGroup(); 27 | const doneNone = wg.wait(); 28 | 29 | wg.add(); 30 | const doneOne = wg.wait(); 31 | 32 | await doneNone; 33 | setTimeout(() => wg.done(), 0); 34 | 35 | await doneOne; 36 | }); 37 | 38 | test("double wait after done", async () => { 39 | const wg = new WaitGroup(); 40 | wg.add(); 41 | setTimeout(() => wg.done(), 0); 42 | 43 | setTimeout(() => wg.add(), 0); 44 | await wg.wait(); 45 | setTimeout(() => wg.done(), 0); 46 | 47 | await wg.wait(); 48 | await wg.wait(); 49 | }); 50 | }); 51 | 52 | describe("Deferred", () => { 53 | test("basic", async () => { 54 | const deferred = new Deferred(); 55 | deferred.resolve(42); 56 | expect(await deferred.promise).toBe(42); 57 | }); 58 | 59 | test("reject", async () => { 60 | const deferred = new Deferred(); 61 | deferred.reject(new Error("foo")); 62 | await expect(deferred.promise).rejects.toThrow("foo"); 63 | }); 64 | }); 65 | 66 | describe("DeferredAsyncIterable", () => { 67 | test("basic", async () => { 68 | const deferred = new DeferredAsyncIterable(); 69 | setTimeout(() => deferred.yield(1), 0); 70 | for await (const value of deferred.iterable) { 71 | expect(value).toBe(1); 72 | break; 73 | } 74 | setTimeout(() => deferred.yield(2), 0); 75 | for await (const value of deferred.iterable) { 76 | expect(value).toBe(2); 77 | break; 78 | } 79 | setTimeout(() => deferred.yield(3), 0); 80 | for await (const value of deferred.iterable) { 81 | expect(value).toBe(3); 82 | break; 83 | } 84 | setTimeout(() => deferred.resolve(), 0); 85 | for await (const _ of deferred.iterable) { 86 | throw new Error("should not reach here"); 87 | } 88 | }); 89 | }); 90 | 91 | test("TurboBlob", async () => { 92 | const blob = new TurboBlob(); 93 | blob.promise = Promise.resolve( 94 | new TextEncoder().encode("Hello, world!").buffer, 95 | ); 96 | blob.size = 13; 97 | blob.type = "text/plain"; 98 | 99 | expect(await blob.bytes()).toEqual( 100 | new Uint8Array([ 101 | 72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 102 | ]), 103 | ); 104 | 105 | expect(await blob.text()).toBe("Hello, world!"); 106 | 107 | expect(await blob.slice(0, 5).text()).toBe("Hello"); 108 | 109 | expect(await blob.slice(0, 5).slice(1, 3).text()).toBe("el"); 110 | 111 | expect(await blob.slice(0, 5).slice(1, 3).slice(1).text()).toBe("l"); 112 | }); 113 | 114 | test("TurboFile", async () => { 115 | const blob = new TurboFile(); 116 | blob.promise = Promise.resolve( 117 | new TextEncoder().encode("Hello, world!").buffer, 118 | ); 119 | blob.size = 13; 120 | blob.type = "text/plain"; 121 | blob.name = "file.txt"; 122 | blob.lastModified = 1000; 123 | 124 | expect(await blob.bytes()).toEqual( 125 | new Uint8Array([ 126 | 72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33, 127 | ]), 128 | ); 129 | 130 | expect(await blob.text()).toBe("Hello, world!"); 131 | 132 | expect(await blob.slice(0, 5).text()).toBe("Hello"); 133 | 134 | expect(await blob.slice(0, 5).slice(1, 3).text()).toBe("el"); 135 | 136 | expect(await blob.slice(0, 5).slice(1, 3).slice(1).text()).toBe("l"); 137 | }); 138 | -------------------------------------------------------------------------------- /src/turbo-stream.bench.mts: -------------------------------------------------------------------------------- 1 | import { bench, boxplot, group, run, summary, do_not_optimize } from "mitata"; 2 | 3 | import { decode } from "./decode.js"; 4 | import { encode } from "./encode.js"; 5 | 6 | const thousandRandomNumbers = Array.from({ length: 1000 }, () => Math.random()); 7 | 8 | const thousandRandomStrings = Array.from({ length: 1000 }, () => { 9 | return Array.from({ length: 1000 }, () => 10 | String.fromCharCode(Math.floor(Math.random() * 0x10000)), 11 | ).join(""); 12 | }); 13 | 14 | const examplePayload = { 15 | id: 12345, 16 | name: "John Doe", 17 | email: "john.doe@example.com", 18 | address: { 19 | street: "123 Main St", 20 | city: "Anytown", 21 | state: "CA", 22 | zip: "12345", 23 | }, 24 | phoneNumbers: [ 25 | { 26 | type: "home", 27 | number: "555-1234", 28 | }, 29 | { 30 | type: "work", 31 | number: "555-5678", 32 | }, 33 | ], 34 | interests: ["reading", "hiking", "coding"], 35 | friends: [ 36 | { 37 | id: 1, 38 | name: "Jane Doe", 39 | email: "jane.doe@example.com", 40 | }, 41 | { 42 | id: 2, 43 | name: "Bob Smith", 44 | email: "bob.smith@example.com", 45 | }, 46 | { 47 | id: 3, 48 | name: "Alice Johnson", 49 | email: "alice.johnson@example.com", 50 | }, 51 | ], 52 | workExperience: [ 53 | { 54 | company: "ABC Corporation", 55 | position: "Software Engineer", 56 | startDate: "2010-01-01", 57 | endDate: "2015-12-31", 58 | }, 59 | { 60 | company: "DEF Startups", 61 | position: "CTO", 62 | startDate: "2016-01-01", 63 | endDate: "2020-12-31", 64 | }, 65 | ], 66 | education: [ 67 | { 68 | school: "Anytown University", 69 | degree: "Bachelor of Science", 70 | field: "Computer Science", 71 | startDate: "2005-01-01", 72 | endDate: "2009-12-31", 73 | }, 74 | { 75 | school: "Othertown University", 76 | degree: "Master of Science", 77 | field: "Data Science", 78 | startDate: "2010-01-01", 79 | endDate: "2012-12-31", 80 | }, 81 | ], 82 | skills: [ 83 | { 84 | name: "Python", 85 | level: "expert", 86 | }, 87 | { 88 | name: "Java", 89 | level: "intermediate", 90 | }, 91 | { 92 | name: "C++", 93 | level: "beginner", 94 | }, 95 | ], 96 | projects: [ 97 | { 98 | name: "Project 1", 99 | description: "This is a sample project", 100 | startDate: "2020-01-01", 101 | endDate: "2020-12-31", 102 | }, 103 | { 104 | name: "Project 2", 105 | description: "This is another sample project", 106 | startDate: "2021-01-01", 107 | endDate: "2021-12-31", 108 | }, 109 | ], 110 | certifications: [ 111 | { 112 | name: "Certification 1", 113 | issuer: "Issuer 1", 114 | issueDate: "2015-01-01", 115 | expirationDate: "2025-12-31", 116 | }, 117 | { 118 | name: "Certification 2", 119 | issuer: "Issuer 2", 120 | issueDate: "2018-01-01", 121 | expirationDate: "2028-12-31", 122 | }, 123 | ], 124 | awards: [ 125 | { 126 | name: "Award 1", 127 | year: 2010, 128 | }, 129 | { 130 | name: "Award 2", 131 | year: 2015, 132 | }, 133 | ], 134 | publications: [ 135 | { 136 | title: "Publication 1", 137 | authors: ["John Doe", "Jane Doe"], 138 | year: 2010, 139 | }, 140 | { 141 | title: "Publication 2", 142 | authors: ["John Doe", "Bob Smith"], 143 | year: 2015, 144 | }, 145 | ], 146 | presentations: [ 147 | { 148 | title: "Presentation 1", 149 | conference: "Conference 1", 150 | year: 2010, 151 | }, 152 | { 153 | title: "Presentation 2", 154 | conference: "Conference 2", 155 | year: 2015, 156 | }, 157 | ], 158 | references: [ 159 | { 160 | name: "Reference 1", 161 | title: "Title 1", 162 | company: "Company 1", 163 | }, 164 | { 165 | name: "Reference 2", 166 | title: "Title 2", 167 | company: "Company 2", 168 | }, 169 | ], 170 | numericValues: { 171 | integer: 12345, 172 | float: 123.456, 173 | }, 174 | booleanValues: { 175 | trueValue: true, 176 | falseValue: false, 177 | }, 178 | nullValue: null, 179 | arrayOfNulls: [null, null, null], 180 | objectWithNullValues: { 181 | key1: null, 182 | key2: null, 183 | key3: null, 184 | }, 185 | }; 186 | 187 | const thousandExamplePayloads = Array.from({ length: 1000 }, () => 188 | structuredClone(examplePayload), 189 | ); 190 | 191 | async function quickEncode(value: unknown): Promise { 192 | const chunks: string[] = []; 193 | const reader = encode(value).getReader(); 194 | try { 195 | while (true) { 196 | const { done, value } = await reader.read(); 197 | if (done) { 198 | break; 199 | } 200 | chunks.push(value); 201 | } 202 | } finally { 203 | reader.releaseLock(); 204 | } 205 | return chunks; 206 | } 207 | 208 | function quickDecode(value: unknown): Promise { 209 | return decode(encode(value)); 210 | } 211 | 212 | boxplot(() => { 213 | summary(() => { 214 | group("1000 random numbers", () => { 215 | bench("JSON", async () => { 216 | do_not_optimize(await JSON.stringify(thousandRandomNumbers)); 217 | }); 218 | 219 | bench("turbo encode", async () => { 220 | do_not_optimize(await quickEncode(thousandRandomNumbers)); 221 | }).baseline(); 222 | 223 | bench("turbo full", async () => { 224 | do_not_optimize(await quickDecode(thousandRandomNumbers)); 225 | }); 226 | }); 227 | 228 | group("1000 random strings", () => { 229 | bench("JSON", async () => { 230 | do_not_optimize(await JSON.stringify(thousandRandomStrings)); 231 | }); 232 | 233 | bench("turbo encode", async () => { 234 | do_not_optimize(await quickEncode(thousandRandomStrings)); 235 | }).baseline(); 236 | 237 | bench("turbo full", async () => { 238 | do_not_optimize(await quickDecode(thousandRandomStrings)); 239 | }); 240 | }); 241 | 242 | group("realistic payload", () => { 243 | bench("JSON", async () => { 244 | do_not_optimize(await JSON.stringify(examplePayload)); 245 | }); 246 | 247 | bench("turbo encode", async () => { 248 | do_not_optimize(await quickEncode(examplePayload)); 249 | }).baseline(); 250 | 251 | bench("turbo full", async () => { 252 | do_not_optimize(await quickDecode(examplePayload)); 253 | }); 254 | }); 255 | 256 | group("1000 realistic payload", () => { 257 | bench("JSON", async () => { 258 | do_not_optimize(await JSON.stringify(thousandExamplePayloads)); 259 | }); 260 | 261 | bench("turbo encode", async () => { 262 | do_not_optimize(await quickEncode(thousandExamplePayloads)); 263 | }).baseline(); 264 | 265 | bench("turbo full", async () => { 266 | do_not_optimize(await quickDecode(thousandExamplePayloads)); 267 | }); 268 | }); 269 | }); 270 | }); 271 | 272 | await run(); 273 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [3.1.0](https://github.com/jacob-ebey/turbo-stream/compare/v3.0.1...v3.1.0) (2025-02-07) 4 | 5 | 6 | ### Features 7 | 8 | * allow for custom error redaction ([#68](https://github.com/jacob-ebey/turbo-stream/issues/68)) ([e128d83](https://github.com/jacob-ebey/turbo-stream/commit/e128d83b991865f443448f117374814f6fdc9ad5)) 9 | 10 | ## [3.0.1](https://github.com/jacob-ebey/turbo-stream/compare/v3.0.0...v3.0.1) (2025-02-05) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * reset subMode state ([#65](https://github.com/jacob-ebey/turbo-stream/issues/65)) ([7088bb7](https://github.com/jacob-ebey/turbo-stream/commit/7088bb782e9391617026e24f4901e685357b0ac0)) 16 | 17 | ## [3.0.0](https://github.com/jacob-ebey/turbo-stream/compare/v2.4.1...v3.0.0) (2025-02-04) 18 | 19 | 20 | ### ⚠ BREAKING CHANGES 21 | 22 | * new encoding format ([#59](https://github.com/jacob-ebey/turbo-stream/issues/59)) 23 | 24 | ### Features 25 | 26 | * new encoding format ([#59](https://github.com/jacob-ebey/turbo-stream/issues/59)) ([2bf9709](https://github.com/jacob-ebey/turbo-stream/commit/2bf9709532487873a2ce093ce0b7fd8e921391ae)) 27 | 28 | ## [2.4.1](https://github.com/jacob-ebey/turbo-stream/compare/v2.4.0...v2.4.1) (2024-09-11) 29 | 30 | 31 | ### Bug Fixes 32 | 33 | * address memory leak caused by too many event listeners on AbortSignal ([#49](https://github.com/jacob-ebey/turbo-stream/issues/49)) ([628504e](https://github.com/jacob-ebey/turbo-stream/commit/628504e1b344b6f6e5fdcfb0258524a518c13505)) 34 | 35 | ## [2.4.0](https://github.com/jacob-ebey/turbo-stream/compare/v2.3.0...v2.4.0) (2024-08-17) 36 | 37 | 38 | ### Features 39 | 40 | * add `postPlugins` for encode to handle any values that can not be handled natively or by other plugins ([#47](https://github.com/jacob-ebey/turbo-stream/issues/47)) ([5fc83c8](https://github.com/jacob-ebey/turbo-stream/commit/5fc83c8bb32407ec44767e55f6cecb950dc5b0e1)) 41 | 42 | ## [2.3.0](https://github.com/jacob-ebey/turbo-stream/compare/v2.2.3...v2.3.0) (2024-08-12) 43 | 44 | 45 | ### Features 46 | 47 | * maintain property order ([#45](https://github.com/jacob-ebey/turbo-stream/issues/45)) ([f0f1537](https://github.com/jacob-ebey/turbo-stream/commit/f0f1537315bd25793ad8ebc749e95c7ad2dd1fa4)) 48 | 49 | ## [2.2.3](https://github.com/jacob-ebey/turbo-stream/compare/v2.2.2...v2.2.3) (2024-08-11) 50 | 51 | 52 | ### Bug Fixes 53 | 54 | * push one value at a time to avoid stack overflows ([#43](https://github.com/jacob-ebey/turbo-stream/issues/43)) ([88c51a7](https://github.com/jacob-ebey/turbo-stream/commit/88c51a7f4a1803c12570bf325fdfc8ec9578e3f5)) 55 | 56 | ## [2.2.2](https://github.com/jacob-ebey/turbo-stream/compare/v2.2.1...v2.2.2) (2024-08-10) 57 | 58 | 59 | ### Bug Fixes 60 | 61 | * support "infinitely" large payloads ([#41](https://github.com/jacob-ebey/turbo-stream/issues/41)) ([8b602a3](https://github.com/jacob-ebey/turbo-stream/commit/8b602a33f15a914bba833123a32fcc6001ce846a)) 62 | 63 | ## [2.2.1](https://github.com/jacob-ebey/turbo-stream/compare/v2.2.0...v2.2.1) (2024-08-09) 64 | 65 | 66 | ### Bug Fixes 67 | 68 | * encoding of previously-used values ([#38](https://github.com/jacob-ebey/turbo-stream/issues/38)) ([84520be](https://github.com/jacob-ebey/turbo-stream/commit/84520be9a84223ee74dc49e32f3c5fe047c5eb78)) 69 | 70 | ## [2.2.0](https://github.com/jacob-ebey/turbo-stream/compare/v2.1.0...v2.2.0) (2024-06-04) 71 | 72 | 73 | ### Features 74 | 75 | * allow plugins to custom encode functions ([#34](https://github.com/jacob-ebey/turbo-stream/issues/34)) ([6bd197a](https://github.com/jacob-ebey/turbo-stream/commit/6bd197a258fa188c8ea4f8232531bf10f56c5d8d)) 76 | 77 | ## [2.1.0](https://github.com/jacob-ebey/turbo-stream/compare/v2.0.1...v2.1.0) (2024-05-30) 78 | 79 | 80 | ### Features 81 | 82 | * support pre resolved / rejected promises ([#32](https://github.com/jacob-ebey/turbo-stream/issues/32)) ([3f15f99](https://github.com/jacob-ebey/turbo-stream/commit/3f15f9917222878b8b8df6bcc687e2d2d63ccfd2)) 83 | 84 | 85 | ### Bug Fixes 86 | 87 | * support empty Map and Set ([#26](https://github.com/jacob-ebey/turbo-stream/issues/26)) ([#27](https://github.com/jacob-ebey/turbo-stream/issues/27)) ([fe156fe](https://github.com/jacob-ebey/turbo-stream/commit/fe156fe61612d968abe60448f0882e1490354278)) 88 | 89 | ## [2.0.1](https://github.com/jacob-ebey/turbo-stream/compare/v2.0.0...v2.0.1) (2024-04-29) 90 | 91 | 92 | ### Bug Fixes 93 | 94 | * subsequent null and undefined encoding failure ([#24](https://github.com/jacob-ebey/turbo-stream/issues/24)) ([47adfe1](https://github.com/jacob-ebey/turbo-stream/commit/47adfe1ad73b0486045bec338cc7405605bf645f)) 95 | 96 | ## [2.0.0](https://github.com/jacob-ebey/turbo-stream/compare/v1.2.1...v2.0.0) (2024-03-06) 97 | 98 | 99 | ### ⚠ BREAKING CHANGES 100 | 101 | * add abort signal ([#21](https://github.com/jacob-ebey/turbo-stream/issues/21)) 102 | 103 | ### Features 104 | 105 | * add abort signal ([#21](https://github.com/jacob-ebey/turbo-stream/issues/21)) ([34441a1](https://github.com/jacob-ebey/turbo-stream/commit/34441a1f6c405e9e27f3538764e45072e06fd6bf)) 106 | 107 | ## [1.2.1](https://github.com/jacob-ebey/turbo-stream/compare/v1.2.0...v1.2.1) (2024-02-15) 108 | 109 | 110 | ### Bug Fixes 111 | 112 | * plugin recursive loop ([#18](https://github.com/jacob-ebey/turbo-stream/issues/18)) ([dd5698b](https://github.com/jacob-ebey/turbo-stream/pull/18/commits/dd5698b15250a14cfd503d7948e26d562d7933d0)) 113 | 114 | ## [1.2.0](https://github.com/jacob-ebey/turbo-stream/compare/v1.1.1...v1.2.0) (2023-11-01) 115 | 116 | 117 | ### Features 118 | 119 | * make the module CJS + ESM ([#16](https://github.com/jacob-ebey/turbo-stream/issues/16)) ([96cb9ec](https://github.com/jacob-ebey/turbo-stream/commit/96cb9ec95a9ad62deda9117a22edf73db4408359)) 120 | 121 | ## [1.1.1](https://github.com/jacob-ebey/turbo-stream/compare/v1.1.0...v1.1.1) (2023-11-01) 122 | 123 | 124 | ### Bug Fixes 125 | 126 | * export plugin types ([#14](https://github.com/jacob-ebey/turbo-stream/issues/14)) ([f70f04a](https://github.com/jacob-ebey/turbo-stream/commit/f70f04a51e0296b70589469fdb20a1415cf00923)) 127 | * flatten objects more ([f70f04a](https://github.com/jacob-ebey/turbo-stream/commit/f70f04a51e0296b70589469fdb20a1415cf00923)) 128 | 129 | ## [1.1.0](https://github.com/jacob-ebey/turbo-stream/compare/v1.0.4...v1.1.0) (2023-10-27) 130 | 131 | 132 | ### Features 133 | 134 | * added plugin support ([#12](https://github.com/jacob-ebey/turbo-stream/issues/12)) ([49792ba](https://github.com/jacob-ebey/turbo-stream/commit/49792ba6161128f9f93bdc4237e9a0a59da1b5dd)) 135 | 136 | ## [1.0.4](https://github.com/jacob-ebey/turbo-stream/compare/v1.0.3...v1.0.4) (2023-10-27) 137 | 138 | 139 | ### Bug Fixes 140 | 141 | * add support for URL encoding and decoding ([#10](https://github.com/jacob-ebey/turbo-stream/issues/10)) ([acf9ae1](https://github.com/jacob-ebey/turbo-stream/commit/acf9ae1a2274a9289b4cf8962a9909e22abfbbe7)) 142 | 143 | ## [1.0.3](https://github.com/jacob-ebey/turbo-stream/compare/v1.0.2...v1.0.3) (2023-10-26) 144 | 145 | 146 | ### Bug Fixes 147 | 148 | * minify things a bit ([#8](https://github.com/jacob-ebey/turbo-stream/issues/8)) ([03c4861](https://github.com/jacob-ebey/turbo-stream/commit/03c4861c5713e26a5641cdd2d6db888711d3f963)) 149 | 150 | ## [1.0.2](https://github.com/jacob-ebey/turbo-stream/compare/v1.0.1...v1.0.2) (2023-10-26) 151 | 152 | 153 | ### Bug Fixes 154 | 155 | * add sideEffects: false to pkg json ([#6](https://github.com/jacob-ebey/turbo-stream/issues/6)) ([ad7b842](https://github.com/jacob-ebey/turbo-stream/commit/ad7b842fcfef7e002fec5e42124b48eeeb8113cf)) 156 | 157 | ## [1.0.1](https://github.com/jacob-ebey/turbo-stream/compare/v1.0.0...v1.0.1) (2023-10-26) 158 | 159 | 160 | ### Features 161 | 162 | * add release pipeline to the repository ([db40f74](https://github.com/jacob-ebey/turbo-stream/commit/db40f74585a5c412dbefbe97cda725b6718bb502)) 163 | * encode errors and promise rejections ([fa120e9](https://github.com/jacob-ebey/turbo-stream/commit/fa120e9e4f6828b5ff3ad909064d2ae43e95f32a)) 164 | * minify things ([1db51bf](https://github.com/jacob-ebey/turbo-stream/commit/1db51bf8ecf3e854fd5b49b1afe44627a965bdac)) 165 | * support null prototype objects ([27b3ece](https://github.com/jacob-ebey/turbo-stream/commit/27b3ece14d8f58c56b44a187851848f716de3876)) 166 | 167 | 168 | ### Bug Fixes 169 | 170 | * add repository to package.json ([#4](https://github.com/jacob-ebey/turbo-stream/issues/4)) ([9ae3518](https://github.com/jacob-ebey/turbo-stream/commit/9ae35180da01025e6dbaf1d3e16d1c10cc9ab043)) 171 | 172 | 173 | ### Miscellaneous Chores 174 | 175 | * release 1.0.0 ([ba652c6](https://github.com/jacob-ebey/turbo-stream/commit/ba652c6d91fdaa1c3cbc1c06800703c308fe77a4)) 176 | * release 1.0.1 ([053108a](https://github.com/jacob-ebey/turbo-stream/commit/053108a6e02f1263b0c580f572c082758451a9f9)) 177 | 178 | ## 1.0.0 (2023-10-26) 179 | 180 | Welcome to turbo-stream, a streaming data transport format that aims to support built-in features such as Promises, Dates, RegExps, Maps, Sets and more. 181 | 182 | Shout out to Rich Harris and his https://github.com/rich-harris/devalue project. Devalue has heavily influenced this project and portions 183 | of the code have been directly lifted from it. I highly recommend checking it out if you need something more cusomizable or without streaming support. 184 | 185 | ### Features 186 | 187 | * initial release 188 | -------------------------------------------------------------------------------- /src/shared.ts: -------------------------------------------------------------------------------- 1 | export const STR_ARRAY_BUFFER = "A"; 2 | export const STR_ASYNC_ITERABLE = "*"; 3 | export const STR_BIG_INT_64_ARRAY = "J"; 4 | export const STR_BIG_UINT_64_ARRAY = "j"; 5 | export const STR_BIGINT = "b"; 6 | export const STR_BLOB = "K"; 7 | export const STR_DATA_VIEW = "V"; 8 | export const STR_DATE = "D"; 9 | export const STR_ERROR = "E"; 10 | export const STR_FAILURE = "!"; 11 | export const STR_FALSE = "false"; 12 | export const STR_FILE = "k"; 13 | export const STR_FLOAT_32_ARRAY = "H"; 14 | export const STR_FLOAT_64_ARRAY = "h"; 15 | export const STR_FORM_DATA = "F"; 16 | export const STR_INFINITY = "I"; 17 | export const STR_INT_16_ARRAY = "L"; 18 | export const STR_INT_32_ARRAY = "G"; 19 | export const STR_INT_8_ARRAY = "O"; 20 | export const STR_MAP = "M"; 21 | export const STR_NaN = "NaN"; 22 | export const STR_NEGATIVE_INFINITY = "i"; 23 | export const STR_NEGATIVE_ZERO = "z"; 24 | export const STR_NULL = "null"; 25 | export const STR_PLUGIN = "P"; 26 | export const STR_PROMISE = "$"; 27 | export const STR_READABLE_STREAM = "R"; 28 | export const STR_REDACTED = ""; 29 | export const STR_REFERENCE_SYMBOL = "@"; 30 | export const STR_REGEXP = "r"; 31 | export const STR_SET = "S"; 32 | export const STR_SUCCESS = ":"; 33 | export const STR_SYMBOL = "s"; 34 | export const STR_TRUE = "true"; 35 | export const STR_UINT_16_ARRAY = "l"; 36 | export const STR_UINT_32_ARRAY = "g"; 37 | export const STR_UINT_8_ARRAY = "o"; 38 | export const STR_UINT_8_ARRAY_CLAMPED = "C"; 39 | export const STR_UNDEFINED = "u"; 40 | export const STR_URL = "U"; 41 | 42 | let SUPPORTS_FILE = true; 43 | try { 44 | new File([], ""); 45 | } catch { 46 | SUPPORTS_FILE = false; 47 | } 48 | 49 | export { SUPPORTS_FILE }; 50 | 51 | export class WaitGroup { 52 | p = 0; 53 | #q: (() => void)[] = []; 54 | 55 | #waitQueue(resolve: () => void) { 56 | if (this.p === 0) { 57 | resolve(); 58 | } else { 59 | this.#q.push(resolve); 60 | } 61 | } 62 | 63 | add() { 64 | this.p++; 65 | } 66 | 67 | done() { 68 | if (--this.p === 0) { 69 | let r: (() => void) | undefined; 70 | while ((r = this.#q.shift()) !== undefined) { 71 | r(); 72 | } 73 | } 74 | } 75 | 76 | wait() { 77 | return new Promise(this.#waitQueue.bind(this)); 78 | } 79 | } 80 | 81 | export class Deferred { 82 | promise: Promise; 83 | resolve!: (value: T) => void; 84 | reject!: (error: unknown) => void; 85 | 86 | constructor() { 87 | this.promise = new Promise((resolve, reject) => { 88 | this.resolve = resolve; 89 | this.reject = reject; 90 | }); 91 | } 92 | } 93 | 94 | type AsyncIterableResult = 95 | | { 96 | done: false; 97 | value: T; 98 | next: Deferred>; 99 | } 100 | | { 101 | done: true; 102 | }; 103 | 104 | export class DeferredAsyncIterable { 105 | iterable: AsyncIterable; 106 | #deferred: Deferred> = new Deferred(); 107 | #next = this.#deferred; 108 | 109 | constructor() { 110 | this.iterable = async function* (this: DeferredAsyncIterable) { 111 | let next = this.#deferred; 112 | while (true) { 113 | const res = await next.promise; 114 | if (res.done) { 115 | return; 116 | } 117 | yield res.value; 118 | next = res.next; 119 | } 120 | }.bind(this)(); 121 | } 122 | 123 | resolve() { 124 | this.#next.resolve({ done: true }); 125 | } 126 | 127 | reject(error: unknown) { 128 | // We reject before there is a chance to consume the error, so we need to catch it 129 | // to avoid an unhandled rejection. 130 | this.#next.promise.catch(() => {}); 131 | this.#next.reject(error); 132 | } 133 | 134 | yield(value: T) { 135 | const deferred = new Deferred>(); 136 | this.#next.resolve({ 137 | done: false, 138 | value, 139 | next: deferred, 140 | }); 141 | this.#next = deferred; 142 | } 143 | } 144 | 145 | export class DeferredReadableStream extends DeferredAsyncIterable { 146 | readable = new ReadableStream({ 147 | start: async (controller) => { 148 | try { 149 | for await (const value of this.iterable) { 150 | controller.enqueue(value); 151 | } 152 | controller.close(); 153 | } catch (error) { 154 | controller.error(error); 155 | } 156 | }, 157 | }); 158 | } 159 | 160 | export class TurboBlob extends Blob { 161 | promise?: Promise; 162 | #size?: number; 163 | #type?: string; 164 | #slice: { start?: number; end?: number } = {}; 165 | 166 | constructor(); 167 | constructor( 168 | from: TurboBlob, 169 | start: number | undefined, 170 | end: number | undefined, 171 | contentType: string | undefined, 172 | ); 173 | constructor( 174 | from?: TurboBlob, 175 | start?: number, 176 | end?: number, 177 | contentType?: string, 178 | ) { 179 | super(); 180 | if (typeof from !== "undefined") { 181 | this.promise = from.promise; 182 | let nextStart = from.#slice.start ?? 0; 183 | if (typeof start !== "undefined") { 184 | nextStart += start; 185 | } 186 | this.#slice.start = nextStart; 187 | let nextEnd = from.#slice.end; 188 | if (typeof end !== "undefined") { 189 | nextEnd = (from.#slice.start ?? 0) + end; 190 | } 191 | this.#slice.end = nextEnd; 192 | this.#type = contentType ?? from?.type; 193 | this.#size = (nextEnd ?? from.size) - nextStart; 194 | } 195 | } 196 | 197 | override get size(): number { 198 | if (typeof this.#size === "undefined") { 199 | throw new Error("Size is not set"); 200 | } 201 | return this.#size; 202 | } 203 | override set size(value: number) { 204 | this.#size = value; 205 | } 206 | 207 | override get type(): string { 208 | if (typeof this.#type === "undefined") { 209 | throw new Error("Type is not set"); 210 | } 211 | return this.#type; 212 | } 213 | override set type(value: string) { 214 | this.#type = value; 215 | } 216 | 217 | override async arrayBuffer(): Promise { 218 | if (!this.promise) { 219 | throw new Error("Promise is not set"); 220 | } 221 | const buffer = await this.promise; 222 | if (this.#slice) { 223 | return buffer.slice( 224 | this.#slice.start as number, 225 | this.#slice.end as number, 226 | ); 227 | } 228 | return buffer; 229 | } 230 | 231 | bytes(): Promise { 232 | return this.arrayBuffer().then((buffer) => new Uint8Array(buffer)); 233 | } 234 | 235 | override slice(start?: number, end?: number, contentType?: string): Blob { 236 | return new TurboBlob(this, start, end, contentType); 237 | } 238 | 239 | override stream(): ReadableStream { 240 | return new ReadableStream({ 241 | start: async (controller) => { 242 | try { 243 | controller.enqueue(await this.bytes()); 244 | controller.close(); 245 | } catch (err) { 246 | controller.error(err); 247 | } 248 | }, 249 | }); 250 | } 251 | 252 | override text(): Promise { 253 | return this.bytes().then((bytes) => { 254 | return new TextDecoder().decode(bytes); 255 | }); 256 | } 257 | } 258 | 259 | const FileBaseClass = SUPPORTS_FILE ? File : Blob; 260 | 261 | export class TurboFile extends FileBaseClass { 262 | promise?: Promise; 263 | #size?: number; 264 | #type?: string; 265 | #name?: string; 266 | #lastModified?: number; 267 | #slice: { start?: number; end?: number } = {}; 268 | 269 | constructor(); 270 | constructor( 271 | from: TurboFile, 272 | start: number | undefined, 273 | end: number | undefined, 274 | contentType: string | undefined, 275 | ); 276 | constructor( 277 | from?: TurboFile, 278 | start?: number, 279 | end?: number, 280 | contentType?: string, 281 | ) { 282 | if (SUPPORTS_FILE) { 283 | super([], ""); 284 | } else { 285 | super([]); 286 | } 287 | if (typeof from !== "undefined") { 288 | this.promise = from.promise; 289 | let nextStart = from.#slice.start ?? 0; 290 | if (typeof start !== "undefined") { 291 | nextStart += start; 292 | } 293 | this.#slice.start = nextStart; 294 | let nextEnd = from.#slice.end; 295 | if (typeof end !== "undefined") { 296 | nextEnd = (from.#slice.start ?? 0) + end; 297 | } 298 | this.#slice.end = nextEnd; 299 | this.#type = contentType ?? from?.type; 300 | this.#name = from.name; 301 | this.#lastModified = from.lastModified; 302 | } 303 | } 304 | 305 | get name(): string { 306 | if (typeof this.#name === "undefined") { 307 | throw new Error("Name is not set"); 308 | } 309 | return this.#name; 310 | } 311 | set name(value: string) { 312 | this.#name = value; 313 | } 314 | 315 | get lastModified(): number { 316 | if (typeof this.#lastModified === "undefined") { 317 | throw new Error("Last modified is not set"); 318 | } 319 | return this.#lastModified; 320 | } 321 | set lastModified(value: number) { 322 | this.#lastModified = value; 323 | } 324 | 325 | get size(): number { 326 | if (typeof this.#size === "undefined") { 327 | throw new Error("Size is not set"); 328 | } 329 | return this.#size; 330 | } 331 | set size(value: number) { 332 | this.#size = value; 333 | } 334 | 335 | get type(): string { 336 | if (typeof this.#type === "undefined") { 337 | throw new Error("Type is not set"); 338 | } 339 | return this.#type; 340 | } 341 | set type(value: string) { 342 | this.#type = value; 343 | } 344 | 345 | async arrayBuffer(): Promise { 346 | if (!this.promise) { 347 | throw new Error("Promise is not set"); 348 | } 349 | const buffer = await this.promise; 350 | if (this.#slice) { 351 | return buffer.slice( 352 | this.#slice.start as number, 353 | this.#slice.end as number, 354 | ); 355 | } 356 | return buffer; 357 | } 358 | 359 | bytes(): Promise { 360 | return this.arrayBuffer().then((buffer) => new Uint8Array(buffer)); 361 | } 362 | 363 | slice(start?: number, end?: number, contentType?: string): Blob { 364 | return new TurboFile(this, start, end, contentType); 365 | } 366 | 367 | stream(): ReadableStream { 368 | return new ReadableStream({ 369 | start: async (controller) => { 370 | try { 371 | controller.enqueue(await this.bytes()); 372 | controller.close(); 373 | } catch (err) { 374 | controller.error(err); 375 | } 376 | }, 377 | }); 378 | } 379 | 380 | text(): Promise { 381 | return this.bytes().then((bytes) => { 382 | return new TextDecoder().decode(bytes); 383 | }); 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /src/encode.ts: -------------------------------------------------------------------------------- 1 | import { 2 | STR_ARRAY_BUFFER, 3 | STR_ASYNC_ITERABLE, 4 | STR_BIG_INT_64_ARRAY, 5 | STR_BIG_UINT_64_ARRAY, 6 | STR_BIGINT, 7 | STR_BLOB, 8 | STR_DATA_VIEW, 9 | STR_DATE, 10 | STR_ERROR, 11 | STR_FAILURE, 12 | STR_FALSE, 13 | STR_FILE, 14 | STR_FLOAT_32_ARRAY, 15 | STR_FLOAT_64_ARRAY, 16 | STR_FORM_DATA, 17 | STR_INFINITY, 18 | STR_INT_16_ARRAY, 19 | STR_INT_32_ARRAY, 20 | STR_INT_8_ARRAY, 21 | STR_MAP, 22 | STR_NaN, 23 | STR_NEGATIVE_INFINITY, 24 | STR_NEGATIVE_ZERO, 25 | STR_NULL, 26 | STR_PLUGIN, 27 | STR_PROMISE, 28 | STR_READABLE_STREAM, 29 | STR_REDACTED, 30 | STR_REFERENCE_SYMBOL, 31 | STR_REGEXP, 32 | STR_SET, 33 | STR_SUCCESS, 34 | STR_SYMBOL, 35 | STR_TRUE, 36 | STR_UINT_16_ARRAY, 37 | STR_UINT_32_ARRAY, 38 | STR_UINT_8_ARRAY, 39 | STR_UINT_8_ARRAY_CLAMPED, 40 | STR_UNDEFINED, 41 | STR_URL, 42 | SUPPORTS_FILE, 43 | WaitGroup, 44 | } from "./shared.js"; 45 | let { NEGATIVE_INFINITY, POSITIVE_INFINITY, isNaN: nan } = Number; 46 | 47 | const ASYNC_FRAME_TYPE_PROMISE = 1; 48 | const ASYNC_FRAME_TYPE_ITERABLE = 2; 49 | 50 | type AsyncFrame = 51 | | [ 52 | type: typeof ASYNC_FRAME_TYPE_PROMISE, 53 | id: number, 54 | promise: PromiseLike, 55 | ] 56 | | [ 57 | type: typeof ASYNC_FRAME_TYPE_ITERABLE, 58 | id: number, 59 | iterable: AsyncIterable, 60 | ]; 61 | 62 | export type EncodePlugin = ( 63 | value: unknown, 64 | ) => [string, ...unknown[]] | false | null | undefined; 65 | 66 | export type EncodeOptions = { 67 | plugins?: EncodePlugin[]; 68 | redactErrors?: boolean | string; 69 | signal?: AbortSignal; 70 | }; 71 | 72 | export function encode( 73 | value: unknown, 74 | { plugins = [], redactErrors = true, signal }: EncodeOptions = {}, 75 | ) { 76 | const aborted = () => signal?.aborted ?? false; 77 | const waitForAbort = new Promise((_, reject) => { 78 | signal?.addEventListener("abort", (reason) => { 79 | reject(new DOMException("Aborted", "AbortError")); 80 | }); 81 | }); 82 | return new ReadableStream({ 83 | async start(controller) { 84 | let refCache = new WeakMap(); 85 | let asyncCache = new WeakMap(); 86 | let counters = { refId: 0, promiseId: 0 }; 87 | let wg = new WaitGroup(); 88 | let chunks: string[] = []; 89 | 90 | let encode = (value: unknown) => { 91 | encodeSync( 92 | value, 93 | chunks, 94 | refCache, 95 | asyncCache, 96 | promises, 97 | counters, 98 | plugins, 99 | redactErrors, 100 | ); 101 | 102 | controller.enqueue(chunks.join("") + "\n"); 103 | chunks.length = 0; 104 | }; 105 | 106 | let handlePromiseResolved = (id: number, value: unknown) => { 107 | wg.done(); 108 | 109 | if (aborted()) return; 110 | controller.enqueue(`${id}${STR_SUCCESS}`); 111 | encode(value); 112 | }; 113 | 114 | let handlePromiseRejected = (id: number, error: unknown) => { 115 | wg.done(); 116 | 117 | if (aborted()) return; 118 | controller.enqueue(`${id}${STR_FAILURE}`); 119 | encode(error); 120 | }; 121 | 122 | let promises = { 123 | push: (...promiseFrames: AsyncFrame[]) => { 124 | for (let [type, id, promise] of promiseFrames) { 125 | wg.add(); 126 | if (type === ASYNC_FRAME_TYPE_PROMISE) { 127 | ( 128 | Promise.race([promise, waitForAbort]) as PromiseLike 129 | ).then( 130 | handlePromiseResolved.bind(null, id), 131 | handlePromiseRejected.bind(null, id), 132 | ); 133 | } else { 134 | (async () => { 135 | let iterator = (promise as AsyncIterable)[ 136 | Symbol.asyncIterator 137 | ](); 138 | 139 | let result: IteratorResult; 140 | do { 141 | result = await iterator.next(); 142 | 143 | if (aborted()) return; 144 | 145 | if (!result.done) { 146 | controller.enqueue(`${id}${STR_SUCCESS}`); 147 | encode(result.value); 148 | } 149 | } while (!result.done); 150 | })() 151 | .then( 152 | () => { 153 | if (aborted()) return; 154 | controller.enqueue(`${id}\n`); 155 | }, 156 | (error) => { 157 | if (aborted()) return; 158 | controller.enqueue(`${id}${STR_FAILURE}`); 159 | encode(error); 160 | }, 161 | ) 162 | .finally(() => { 163 | wg.done(); 164 | }); 165 | } 166 | } 167 | }, 168 | }; 169 | 170 | try { 171 | encode(value); 172 | 173 | do { 174 | await Promise.race([wg.wait(), waitForAbort]); 175 | } while (wg.p > 0); 176 | 177 | controller.close(); 178 | } catch (error) { 179 | controller.error(error); 180 | } 181 | }, 182 | }); 183 | } 184 | 185 | const ENCODE_FRAME_TYPE_NEEDS_ENCODING = 1; 186 | const ENCODE_FRAME_TYPE_ALREADY_ENCODED = 2; 187 | 188 | type EncodeFrameObj = 189 | | { 190 | type: typeof ENCODE_FRAME_TYPE_NEEDS_ENCODING; 191 | prefix: string; 192 | value: unknown; 193 | } 194 | | { 195 | type: typeof ENCODE_FRAME_TYPE_ALREADY_ENCODED; 196 | prefix: string; 197 | value: undefined; 198 | }; 199 | 200 | class EncodeFrame { 201 | public type: number; 202 | public prefix: string; 203 | public value: unknown; 204 | constructor(type: number, prefix: string, value: unknown) { 205 | this.type = type; 206 | this.prefix = prefix; 207 | this.value = value; 208 | } 209 | } 210 | 211 | export function encodeSync( 212 | value: unknown, 213 | chunks: { push(...chunk: string[]): void }, 214 | refs: WeakMap, 215 | promises: WeakMap, 216 | asyncFrames: { push(frame: AsyncFrame): void }, 217 | counters: { refId: number; promiseId: number }, 218 | plugins: EncodePlugin[], 219 | redactErrors: boolean | string, 220 | ) { 221 | let encodeStack: EncodeFrameObj[] = [ 222 | new EncodeFrame( 223 | ENCODE_FRAME_TYPE_NEEDS_ENCODING, 224 | "", 225 | value, 226 | ) as EncodeFrameObj, 227 | ]; 228 | let frame: EncodeFrameObj | undefined; 229 | 230 | encodeLoop: while ((frame = encodeStack.pop()) !== undefined) { 231 | if (frame.type === ENCODE_FRAME_TYPE_ALREADY_ENCODED) { 232 | chunks.push(frame.prefix); 233 | continue; 234 | } 235 | 236 | let { prefix, value } = frame; 237 | chunks.push(prefix); 238 | 239 | if (value === undefined) { 240 | chunks.push(STR_UNDEFINED); 241 | continue; 242 | } 243 | if (value === null) { 244 | chunks.push(STR_NULL); 245 | continue; 246 | } 247 | if (value === true) { 248 | chunks.push(STR_TRUE); 249 | continue; 250 | } 251 | if (value === false) { 252 | chunks.push(STR_FALSE); 253 | continue; 254 | } 255 | 256 | const typeOfValue = typeof value; 257 | if (typeOfValue === "object") { 258 | if ( 259 | value instanceof Promise || 260 | typeof (value as PromiseLike).then === "function" 261 | ) { 262 | let existingId = promises.get(value); 263 | if (existingId !== undefined) { 264 | chunks.push(STR_PROMISE, existingId.toString()); 265 | continue; 266 | } 267 | 268 | let promiseId = counters.promiseId++; 269 | promises.set(value, promiseId); 270 | chunks.push(STR_PROMISE, promiseId.toString()); 271 | asyncFrames.push([ 272 | ASYNC_FRAME_TYPE_PROMISE, 273 | promiseId, 274 | value as PromiseLike, 275 | ]); 276 | continue; 277 | } 278 | 279 | if (value instanceof ReadableStream) { 280 | let existingId = promises.get(value); 281 | if (existingId !== undefined) { 282 | chunks.push(STR_READABLE_STREAM, existingId.toString()); 283 | continue; 284 | } 285 | 286 | let iterableId = counters.promiseId++; 287 | promises.set(value, iterableId); 288 | chunks.push(STR_READABLE_STREAM, iterableId.toString()); 289 | asyncFrames.push([ 290 | ASYNC_FRAME_TYPE_ITERABLE, 291 | iterableId, 292 | { 293 | [Symbol.asyncIterator]: async function* () { 294 | let reader = (value as ReadableStream).getReader(); 295 | try { 296 | while (true) { 297 | let { done, value } = await reader.read(); 298 | if (done) { 299 | return; 300 | } 301 | yield value; 302 | } 303 | } finally { 304 | reader.releaseLock(); 305 | } 306 | }, 307 | }, 308 | ]); 309 | continue; 310 | } 311 | 312 | if (typeof (value as any)[Symbol.asyncIterator] === "function") { 313 | let existingId = promises.get(value); 314 | if (existingId !== undefined) { 315 | chunks.push(STR_ASYNC_ITERABLE, existingId.toString()); 316 | continue; 317 | } 318 | 319 | let iterableId = counters.promiseId++; 320 | promises.set(value, iterableId); 321 | chunks.push(STR_ASYNC_ITERABLE, iterableId.toString()); 322 | asyncFrames.push([ 323 | ASYNC_FRAME_TYPE_ITERABLE, 324 | iterableId, 325 | value as AsyncIterable, 326 | ]); 327 | continue; 328 | } 329 | 330 | { 331 | let existingId = refs.get(value); 332 | if (existingId !== undefined) { 333 | chunks.push(STR_REFERENCE_SYMBOL, existingId.toString()); 334 | continue; 335 | } 336 | refs.set(value, counters.refId++); 337 | } 338 | 339 | if (value instanceof Date) { 340 | chunks.push(STR_DATE, '"', value.toJSON(), '"'); 341 | } else if (value instanceof RegExp) { 342 | chunks.push(STR_REGEXP, JSON.stringify([value.source, value.flags])); 343 | } else if (value instanceof URL) { 344 | chunks.push(STR_URL, JSON.stringify(value)); 345 | } else if (value instanceof ArrayBuffer) { 346 | chunks.push( 347 | STR_ARRAY_BUFFER, 348 | stringifyTypedArray(new Uint8Array(value)), 349 | ); 350 | } else if (value instanceof Int8Array) { 351 | chunks.push(STR_INT_8_ARRAY, stringifyTypedArray(value)); 352 | } else if (value instanceof Uint8Array) { 353 | chunks.push(STR_UINT_8_ARRAY, stringifyTypedArray(value)); 354 | } else if (value instanceof Uint8ClampedArray) { 355 | chunks.push(STR_UINT_8_ARRAY_CLAMPED, stringifyTypedArray(value)); 356 | } else if (value instanceof Int16Array) { 357 | chunks.push(STR_INT_16_ARRAY, stringifyTypedArray(value)); 358 | } else if (value instanceof Uint16Array) { 359 | chunks.push(STR_UINT_16_ARRAY, stringifyTypedArray(value)); 360 | } else if (value instanceof Int32Array) { 361 | chunks.push(STR_INT_32_ARRAY, stringifyTypedArray(value)); 362 | } else if (value instanceof Uint32Array) { 363 | chunks.push(STR_UINT_32_ARRAY, stringifyTypedArray(value)); 364 | } else if (value instanceof Float32Array) { 365 | chunks.push(STR_FLOAT_32_ARRAY, stringifyTypedArray(value)); 366 | } else if (value instanceof Float64Array) { 367 | chunks.push(STR_FLOAT_64_ARRAY, stringifyTypedArray(value)); 368 | } else if (value instanceof BigInt64Array) { 369 | chunks.push(STR_BIG_INT_64_ARRAY, stringifyTypedArray(value)); 370 | } else if (value instanceof BigUint64Array) { 371 | chunks.push(STR_BIG_UINT_64_ARRAY, stringifyTypedArray(value)); 372 | } else if (value instanceof DataView) { 373 | chunks.push(STR_DATA_VIEW, stringifyTypedArray(value)); 374 | } else if (value instanceof FormData) { 375 | encodeStack.push( 376 | new EncodeFrame( 377 | ENCODE_FRAME_TYPE_NEEDS_ENCODING, 378 | STR_FORM_DATA, 379 | Array.from(value.entries()), 380 | ) as EncodeFrameObj, 381 | ); 382 | } else if (SUPPORTS_FILE && value instanceof File) { 383 | encodeStack.push( 384 | new EncodeFrame(ENCODE_FRAME_TYPE_NEEDS_ENCODING, STR_FILE, { 385 | promise: (value as File).arrayBuffer(), 386 | size: value.size, 387 | type: value.type, 388 | name: value.name, 389 | lastModified: value.lastModified, 390 | }) as EncodeFrameObj, 391 | ); 392 | } else if (value instanceof Blob) { 393 | encodeStack.push( 394 | new EncodeFrame(ENCODE_FRAME_TYPE_NEEDS_ENCODING, STR_BLOB, { 395 | promise: (value as Blob).arrayBuffer(), 396 | size: value.size, 397 | type: value.type, 398 | }) as EncodeFrameObj, 399 | ); 400 | } else if (value instanceof Error) { 401 | encodeStack.push( 402 | new EncodeFrame( 403 | ENCODE_FRAME_TYPE_NEEDS_ENCODING, 404 | STR_ERROR, 405 | prepareErrorForEncoding(value, redactErrors), 406 | ) as EncodeFrameObj, 407 | ); 408 | } else if (typeof (value as any).toJSON === "function") { 409 | const newValue = (value as any).toJSON(); 410 | encodeStack.push( 411 | new EncodeFrame( 412 | ENCODE_FRAME_TYPE_NEEDS_ENCODING, 413 | "", 414 | newValue, 415 | ) as EncodeFrameObj, 416 | ); 417 | if (typeof newValue === "object") { 418 | counters.refId--; 419 | } else { 420 | refs.delete(value); 421 | } 422 | } else { 423 | { 424 | let isIterable = 425 | typeof (value as any)[Symbol.iterator] === "function"; 426 | 427 | if (isIterable) { 428 | let isArray = Array.isArray(value); 429 | let toEncode = isArray 430 | ? value 431 | : Array.from(value as Iterable); 432 | 433 | encodeStack.push( 434 | new EncodeFrame( 435 | ENCODE_FRAME_TYPE_ALREADY_ENCODED, 436 | "]", 437 | undefined, 438 | ) as EncodeFrameObj, 439 | ); 440 | for (let i = (toEncode as unknown[]).length - 1; i >= 0; i--) { 441 | encodeStack.push( 442 | new EncodeFrame( 443 | ENCODE_FRAME_TYPE_NEEDS_ENCODING, 444 | i === 0 ? "" : ",", 445 | (toEncode as unknown[])[i], 446 | ) as EncodeFrameObj, 447 | ); 448 | } 449 | chunks.push( 450 | isArray 451 | ? "[" 452 | : value instanceof Set 453 | ? `${STR_SET}[` 454 | : value instanceof Map 455 | ? `${STR_MAP}[` 456 | : "[", 457 | ); 458 | continue; 459 | } 460 | } 461 | 462 | { 463 | let pluginsLength = plugins.length; 464 | for (let i = 0; i < pluginsLength; i++) { 465 | let result = plugins[i](value); 466 | if (Array.isArray(result)) { 467 | encodeStack.push( 468 | new EncodeFrame( 469 | ENCODE_FRAME_TYPE_NEEDS_ENCODING, 470 | STR_PLUGIN, 471 | result, 472 | ) as EncodeFrameObj, 473 | ); 474 | counters.refId--; 475 | refs.delete(value); 476 | continue encodeLoop; 477 | } 478 | } 479 | } 480 | 481 | encodeStack.push( 482 | new EncodeFrame( 483 | ENCODE_FRAME_TYPE_ALREADY_ENCODED, 484 | "}", 485 | undefined, 486 | ) as EncodeFrameObj, 487 | ); 488 | { 489 | let keys = Object.keys(value as object); 490 | let end = keys.length; 491 | let encodeFrames = new Array(end); 492 | end -= 1; 493 | 494 | for (let i = keys.length - 1; i >= 0; i--) { 495 | let key = keys[i]; 496 | let prefix = i > 0 ? "," : ""; 497 | encodeFrames[end - i] = new EncodeFrame( 498 | ENCODE_FRAME_TYPE_NEEDS_ENCODING, 499 | `${prefix}${JSON.stringify(key)}:`, 500 | (value as any)[key], 501 | ); 502 | } 503 | 504 | encodeStack.push(...encodeFrames); 505 | } 506 | chunks.push("{"); 507 | } 508 | } else if (typeOfValue === "string") { 509 | chunks.push(JSON.stringify(value)); 510 | } else if (typeOfValue === "number") { 511 | if (nan(value)) { 512 | chunks.push(STR_NaN); 513 | } else if (value === POSITIVE_INFINITY) { 514 | chunks.push(STR_INFINITY); 515 | } else if (value === NEGATIVE_INFINITY) { 516 | chunks.push(STR_NEGATIVE_INFINITY); 517 | } else if (Object.is(value, -0)) { 518 | chunks.push(STR_NEGATIVE_ZERO); 519 | } else { 520 | chunks.push(value.toString()); 521 | } 522 | } else if (typeOfValue === "bigint") { 523 | chunks.push(STR_BIGINT, value.toString()); 524 | } else if (typeOfValue === "symbol") { 525 | let symbolKey = Symbol.keyFor(value as symbol); 526 | if (typeof symbolKey === "string") { 527 | chunks.push(STR_SYMBOL, JSON.stringify(symbolKey)); 528 | } else { 529 | chunks.push(STR_UNDEFINED); 530 | } 531 | } else { 532 | let pluginsLength = plugins.length; 533 | for (let i = 0; i < pluginsLength; i++) { 534 | let result = plugins[i](value); 535 | if (Array.isArray(result)) { 536 | encodeStack.push( 537 | new EncodeFrame( 538 | ENCODE_FRAME_TYPE_NEEDS_ENCODING, 539 | STR_PLUGIN, 540 | result, 541 | ) as EncodeFrameObj, 542 | ); 543 | continue encodeLoop; 544 | } 545 | } 546 | chunks.push(STR_UNDEFINED); 547 | } 548 | } 549 | } 550 | 551 | function prepareErrorForEncoding(error: Error, redactErrors: boolean | string) { 552 | const shouldRedact = 553 | redactErrors === true || 554 | typeof redactErrors === "string" || 555 | typeof redactErrors === "undefined"; 556 | const redacted = 557 | typeof redactErrors === "string" ? redactErrors : STR_REDACTED; 558 | 559 | return { 560 | name: shouldRedact ? "Error" : error.name, 561 | message: shouldRedact ? redacted : error.message, 562 | stack: shouldRedact ? undefined : error.stack, 563 | cause: error.cause, 564 | }; 565 | } 566 | 567 | function stringifyTypedArray(content: ArrayBufferView) { 568 | const view = new Uint8Array( 569 | content.buffer, 570 | content.byteOffset, 571 | content.byteLength, 572 | ); 573 | return `"${btoa(String.fromCharCode.apply(String, view as unknown as number[]))}"`; 574 | } 575 | -------------------------------------------------------------------------------- /src/decode.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Deferred, 3 | DeferredAsyncIterable, 4 | DeferredReadableStream, 5 | TurboBlob, 6 | TurboFile, 7 | } from "./shared.js"; 8 | export type DecodePlugin = ( 9 | type: string, 10 | ...data: unknown[] 11 | ) => { value: unknown } | false | null | undefined; 12 | 13 | export type DecodeOptions = { 14 | plugins?: DecodePlugin[]; 15 | }; 16 | 17 | let MODE_UNKNOWN = 0 as const; 18 | let MODE_NUMBER = 1 as const; 19 | let MODE_STRING = 2 as const; 20 | let MODE_ASYNC = 3 as const; 21 | type ParseMode = 22 | | typeof MODE_UNKNOWN 23 | | typeof MODE_NUMBER 24 | | typeof MODE_STRING 25 | | typeof MODE_ASYNC; 26 | 27 | let SUB_MODE_UNKNOWN = 0 as const; 28 | let SUB_MODE_BIGINT = 1 as const; 29 | let SUB_MODE_DATE = 2 as const; 30 | let SUB_MODE_URL = 3 as const; 31 | let SUB_MODE_SYMBOL = 4 as const; 32 | let SUB_MODE_REFERENCE = 5 as const; 33 | let SUB_MODE_OBJECT_KEY = 6 as const; 34 | let SUB_MODE_PROMISE_ID = 7 as const; 35 | let SUB_MODE_ASYNC_ITERABLE_ID = 8 as const; 36 | let SUB_MODE_READABLE_STREAM_ID = 9 as const; 37 | let SUB_MODE_ASYNC_STATUS = 10 as const; 38 | let SUB_MODE_ARRAY_BUFFER = 11 as const; 39 | let SUB_MODE_INT_8_ARRAY = 12 as const; 40 | let SUB_MODE_UINT_8_ARRAY = 13 as const; 41 | let SUB_MODE_UINT_8_ARRAY_CLAMPED = 14 as const; 42 | let SUB_MODE_INT_16_ARRAY = 15 as const; 43 | let SUB_MODE_UINT_16_ARRAY = 16 as const; 44 | let SUB_MODE_INT_32_ARRAY = 17 as const; 45 | let SUB_MODE_UINT_32_ARRAY = 18 as const; 46 | let SUB_MODE_FLOAT_32_ARRAY = 19 as const; 47 | let SUB_MODE_FLOAT_64_ARRAY = 20 as const; 48 | let SUB_MODE_BIG_INT_64_ARRAY = 21 as const; 49 | let SUB_MODE_BIG_UINT_64_ARRAY = 22 as const; 50 | let SUB_MODE_DATA_VIEW = 23 as const; 51 | 52 | type ParseSubMode = 53 | | typeof SUB_MODE_UNKNOWN 54 | | typeof SUB_MODE_BIGINT 55 | | typeof SUB_MODE_DATE 56 | | typeof SUB_MODE_URL 57 | | typeof SUB_MODE_SYMBOL 58 | | typeof SUB_MODE_REFERENCE 59 | | typeof SUB_MODE_OBJECT_KEY 60 | | typeof SUB_MODE_PROMISE_ID 61 | | typeof SUB_MODE_ASYNC_ITERABLE_ID 62 | | typeof SUB_MODE_READABLE_STREAM_ID 63 | | typeof SUB_MODE_ASYNC_STATUS 64 | | typeof SUB_MODE_ARRAY_BUFFER 65 | | typeof SUB_MODE_INT_8_ARRAY 66 | | typeof SUB_MODE_UINT_8_ARRAY 67 | | typeof SUB_MODE_UINT_8_ARRAY_CLAMPED 68 | | typeof SUB_MODE_INT_16_ARRAY 69 | | typeof SUB_MODE_UINT_16_ARRAY 70 | | typeof SUB_MODE_INT_32_ARRAY 71 | | typeof SUB_MODE_UINT_32_ARRAY 72 | | typeof SUB_MODE_FLOAT_32_ARRAY 73 | | typeof SUB_MODE_FLOAT_64_ARRAY 74 | | typeof SUB_MODE_BIG_INT_64_ARRAY 75 | | typeof SUB_MODE_BIG_UINT_64_ARRAY 76 | | typeof SUB_MODE_DATA_VIEW; 77 | 78 | let ARRAY_TYPE_SET = 0 as const; 79 | let ARRAY_TYPE_MAP = 1 as const; 80 | let ARRAY_TYPE_REGEXP = 2 as const; 81 | let ARRAY_TYPE_FORM_DATA = 3 as const; 82 | let ARRAY_TYPE_PLUGIN = 4 as const; 83 | 84 | type SetArray = unknown[] & { 85 | __type: typeof ARRAY_TYPE_SET; 86 | __ref: Set; 87 | }; 88 | 89 | type ReferenceArray = [string, ...unknown[]] & { 90 | __type: 91 | | typeof ARRAY_TYPE_PLUGIN 92 | | typeof ARRAY_TYPE_REGEXP 93 | | typeof ARRAY_TYPE_FORM_DATA; 94 | __id: number; 95 | }; 96 | 97 | type MapArray = unknown[] & { 98 | __type: typeof ARRAY_TYPE_MAP; 99 | __ref: Map; 100 | }; 101 | 102 | type TypedArray = MapArray | ReferenceArray | SetArray; 103 | 104 | let RELEASE_TYPE_VALUE = 0 as const; 105 | let RELEASE_TYPE_OBJECT = 1 as const; 106 | let RELEASE_TYPE_ARRAY = 2 as const; 107 | let RELEASE_TYPE_PROMISE = 3 as const; 108 | 109 | type ReleaseType = 110 | | typeof RELEASE_TYPE_VALUE 111 | | typeof RELEASE_TYPE_OBJECT 112 | | typeof RELEASE_TYPE_ARRAY 113 | | typeof RELEASE_TYPE_PROMISE; 114 | 115 | export async function decode( 116 | stream: ReadableStream, 117 | { plugins = [] }: DecodeOptions = {}, 118 | ): Promise { 119 | let root: Deferred | null = new Deferred(); 120 | let references: Map = new Map(); 121 | let deferredValues: Map< 122 | number, 123 | Deferred | DeferredAsyncIterable 124 | > = new Map(); 125 | let stack: unknown[] = []; 126 | let mode: ParseMode = MODE_UNKNOWN; 127 | let subMode: ParseSubMode = SUB_MODE_UNKNOWN; 128 | let buffer = ""; 129 | let shouldSkip = 0; 130 | let lastChar: number | undefined; 131 | let numSlashes = 0; 132 | let hasSlashes = false; 133 | 134 | let releaseValue = (value: unknown, type: ReleaseType) => { 135 | if (type === RELEASE_TYPE_OBJECT) { 136 | if (typeof value !== "object" || value === null) { 137 | throw new Error("Expected object"); 138 | } 139 | } else if (type === RELEASE_TYPE_ARRAY) { 140 | if (!Array.isArray(value)) { 141 | throw new Error("Expected array"); 142 | } 143 | } 144 | 145 | if ( 146 | Array.isArray(value) && 147 | typeof (value as TypedArray).__type === "number" 148 | ) { 149 | switch ((value as TypedArray).__type) { 150 | case ARRAY_TYPE_MAP: 151 | for (let [key, val] of value) { 152 | (value as MapArray).__ref.set(key, val); 153 | } 154 | value = (value as MapArray).__ref; 155 | break; 156 | case ARRAY_TYPE_SET: 157 | for (let val of value) { 158 | (value as SetArray).__ref.add(val); 159 | } 160 | value = (value as SetArray).__ref; 161 | break; 162 | case ARRAY_TYPE_REGEXP: 163 | value = new RegExp(value[0], value[1]); 164 | references.set((value as ReferenceArray).__id, value as object); 165 | break; 166 | case ARRAY_TYPE_FORM_DATA: { 167 | let formData = new FormData(); 168 | for (let [key, val] of value as [string, FormDataEntryValue][]) { 169 | formData.append(key, val); 170 | } 171 | value = formData; 172 | references.set((value as ReferenceArray).__id, value as object); 173 | break; 174 | } 175 | case ARRAY_TYPE_PLUGIN: { 176 | let pluginHandled = false; 177 | let pluginsLength = plugins.length; 178 | for (let i = 0; i < pluginsLength; i++) { 179 | let result = plugins[i](...(value as [string, ...unknown[]])); 180 | if (typeof result === "object" && result !== null) { 181 | value = result.value; 182 | pluginHandled = true; 183 | break; 184 | } 185 | } 186 | 187 | if (!pluginHandled) { 188 | // TODO: Should this throw? Should we have a way to recover from errors in the options? 189 | value = undefined; 190 | } 191 | break; 192 | } 193 | } 194 | } 195 | 196 | if (stack.length === 0) { 197 | if (root === null) { 198 | throw new Error("Unexpected root value"); 199 | } 200 | if (root !== null) { 201 | root.resolve(value as T); 202 | root = null; 203 | return; 204 | } 205 | } 206 | 207 | let parent = stack[stack.length - 1]; 208 | if (Array.isArray(parent)) { 209 | parent.push(value); 210 | } else if (typeof parent === "string") { 211 | stack.pop(); 212 | (stack[stack.length - 1] as any)[parent] = value; 213 | } else if (typeof parent === "boolean") { 214 | stack.pop(); 215 | let deferred = deferredValues.get(stack.pop() as number); 216 | if (!deferred) { 217 | throw new Error("Invalid stack state"); 218 | } 219 | if (deferred instanceof Deferred) { 220 | if (parent) { 221 | deferred.resolve(value); 222 | } else { 223 | deferred.reject(value); 224 | } 225 | } else { 226 | if (parent) { 227 | deferred.yield(value); 228 | } else { 229 | deferred.reject(value); 230 | } 231 | } 232 | } else { 233 | throw new Error("Invalid stack state"); 234 | } 235 | }; 236 | 237 | let step = (chunk: string) => { 238 | let length = chunk.length; 239 | let charCode: number; 240 | let start = shouldSkip; 241 | shouldSkip = 0; 242 | let i = start; 243 | for (; i < length; i++) { 244 | charCode = chunk.charCodeAt(i); 245 | 246 | if (mode === MODE_UNKNOWN) { 247 | if (charCode === 44) { 248 | // , 249 | mode = MODE_UNKNOWN; 250 | subMode = Array.isArray(stack[stack.length - 1]) 251 | ? SUB_MODE_UNKNOWN 252 | : SUB_MODE_OBJECT_KEY; 253 | } else if (charCode === 10) { 254 | // \n 255 | if (subMode === SUB_MODE_ASYNC_STATUS) { 256 | let id = stack.pop(); 257 | if (typeof id !== "number") { 258 | throw new Error("Invalid stack state"); 259 | } 260 | let deferred = deferredValues.get(id); 261 | (deferred as DeferredAsyncIterable).resolve(); 262 | deferredValues.delete(id); 263 | } 264 | mode = MODE_ASYNC; 265 | subMode = MODE_UNKNOWN; 266 | buffer = ""; 267 | } else if (charCode === 123) { 268 | // { 269 | let newObj = {}; 270 | stack.push(newObj); 271 | references.set(references.size, newObj); 272 | subMode = SUB_MODE_OBJECT_KEY; 273 | } else if (charCode === 125) { 274 | // } 275 | releaseValue(stack.pop(), 1); 276 | } else if (charCode === 91) { 277 | // [ 278 | let newArr: unknown[] = []; 279 | stack.push(newArr); 280 | references.set(references.size, newArr); 281 | } else if (charCode === 83) { 282 | // S 283 | let newArr = [] as unknown as SetArray; 284 | newArr.__type = ARRAY_TYPE_SET; 285 | newArr.__ref = new Set(); 286 | stack.push(newArr); 287 | references.set(references.size, newArr.__ref); 288 | i++; 289 | } else if (charCode === 77) { 290 | // M 291 | let newArr = [] as unknown as MapArray; 292 | newArr.__type = ARRAY_TYPE_MAP; 293 | newArr.__ref = new Map(); 294 | stack.push(newArr); 295 | references.set(references.size, newArr.__ref); 296 | i++; 297 | } else if (charCode === 114) { 298 | // r 299 | let newArr = [] as unknown as ReferenceArray; 300 | newArr.__type = ARRAY_TYPE_REGEXP; 301 | newArr.__id = references.size; 302 | stack.push(newArr); 303 | references.set(newArr.__id, newArr); 304 | i++; 305 | } else if (charCode === 80) { 306 | // P 307 | let newArr = [] as unknown as ReferenceArray; 308 | newArr.__type = ARRAY_TYPE_PLUGIN; 309 | newArr.__id = references.size; 310 | stack.push(newArr); 311 | references.set(newArr.__id, newArr); 312 | i++; 313 | } else if (charCode === 93) { 314 | // ] 315 | releaseValue(stack.pop(), 2); 316 | } else if (charCode === 64) { 317 | // @ 318 | subMode = SUB_MODE_REFERENCE; 319 | } else if (charCode === 68) { 320 | // D 321 | subMode = SUB_MODE_DATE; 322 | } else if (charCode === 85) { 323 | // U 324 | subMode = SUB_MODE_URL; 325 | } else if (charCode === 115) { 326 | // s 327 | subMode = SUB_MODE_SYMBOL; 328 | } else if (charCode === 34) { 329 | // " 330 | mode = MODE_STRING; 331 | buffer = ""; 332 | lastChar = undefined; 333 | numSlashes = 0; 334 | hasSlashes = false; 335 | } else if (charCode === 36) { 336 | // $ 337 | subMode = SUB_MODE_PROMISE_ID; 338 | } else if (charCode === 42) { 339 | // * 340 | subMode = SUB_MODE_ASYNC_ITERABLE_ID; 341 | } else if (charCode === 82) { 342 | // R 343 | subMode = SUB_MODE_READABLE_STREAM_ID; 344 | } else if (charCode === 58) { 345 | // : 346 | if (subMode !== SUB_MODE_ASYNC_STATUS) { 347 | throw new SyntaxError("Unexpected character: ':'"); 348 | } 349 | stack.push(true); 350 | } else if (charCode === 33) { 351 | // ! 352 | if (subMode !== SUB_MODE_ASYNC_STATUS) { 353 | throw new SyntaxError("Unexpected character: '!'"); 354 | } 355 | stack.push(false); 356 | } else if (charCode === 117) { 357 | // u 358 | releaseValue(undefined, 0); 359 | subMode = SUB_MODE_UNKNOWN; 360 | } else if (charCode === 110) { 361 | // n 362 | i += 3; 363 | releaseValue(null, 0); 364 | subMode = SUB_MODE_UNKNOWN; 365 | } else if (charCode === 116) { 366 | // t 367 | i += 3; 368 | releaseValue(true, 0); 369 | subMode = SUB_MODE_UNKNOWN; 370 | } else if (charCode === 102) { 371 | // f 372 | i += 4; 373 | releaseValue(false, 0); 374 | subMode = SUB_MODE_UNKNOWN; 375 | } else if (charCode === 78) { 376 | // N 377 | i += 2; 378 | releaseValue(Number.NaN, 0); 379 | subMode = SUB_MODE_UNKNOWN; 380 | } else if (charCode === 73) { 381 | // I 382 | releaseValue(Number.POSITIVE_INFINITY, 0); 383 | subMode = SUB_MODE_UNKNOWN; 384 | } else if (charCode === 105) { 385 | // i 386 | releaseValue(Number.NEGATIVE_INFINITY, 0); 387 | subMode = SUB_MODE_UNKNOWN; 388 | } else if (charCode === 122) { 389 | // z 390 | releaseValue(-0, 0); 391 | subMode = SUB_MODE_UNKNOWN; 392 | } else if (charCode === 98) { 393 | // b 394 | subMode = SUB_MODE_BIGINT; 395 | } else if ( 396 | charCode === 45 || // - 397 | charCode === 46 || // . 398 | (charCode >= 48 && charCode <= 57) // 0-9 399 | ) { 400 | mode = MODE_NUMBER; 401 | buffer = chunk[i]; 402 | } else if (charCode === 69) { 403 | // E 404 | let newObj = new Error(); 405 | stack.push(newObj); 406 | references.set(references.size, newObj); 407 | subMode = SUB_MODE_OBJECT_KEY; 408 | i++; 409 | } else if (charCode === 70) { 410 | // F 411 | let newArr = [] as unknown as ReferenceArray; 412 | newArr.__type = ARRAY_TYPE_FORM_DATA; 413 | newArr.__id = references.size; 414 | stack.push(newArr); 415 | references.set(newArr.__id, newArr); 416 | i++; 417 | } else if (charCode === 75) { 418 | // K 419 | let newObj = new TurboBlob(); 420 | stack.push(newObj); 421 | references.set(references.size, newObj); 422 | subMode = SUB_MODE_OBJECT_KEY; 423 | i++; 424 | } else if (charCode === 107) { 425 | // k 426 | let newObj = new TurboFile(); 427 | stack.push(newObj); 428 | references.set(references.size, newObj); 429 | subMode = SUB_MODE_OBJECT_KEY; 430 | i++; 431 | } else if (charCode === 65) { 432 | // A 433 | subMode = SUB_MODE_ARRAY_BUFFER; 434 | } else if (charCode === 79) { 435 | // O 436 | subMode = SUB_MODE_INT_8_ARRAY; 437 | } else if (charCode === 111) { 438 | // o 439 | subMode = SUB_MODE_UINT_8_ARRAY; 440 | } else if (charCode === 67) { 441 | // C 442 | subMode = SUB_MODE_UINT_8_ARRAY_CLAMPED; 443 | } else if (charCode === 76) { 444 | // L 445 | subMode = SUB_MODE_INT_16_ARRAY; 446 | } else if (charCode === 108) { 447 | // l 448 | subMode = SUB_MODE_UINT_16_ARRAY; 449 | } else if (charCode === 71) { 450 | // G 451 | subMode = SUB_MODE_INT_32_ARRAY; 452 | } else if (charCode === 103) { 453 | // g 454 | subMode = SUB_MODE_UINT_32_ARRAY; 455 | } else if (charCode === 72) { 456 | // H 457 | subMode = SUB_MODE_FLOAT_32_ARRAY; 458 | } else if (charCode === 104) { 459 | // h 460 | subMode = SUB_MODE_FLOAT_64_ARRAY; 461 | } else if (charCode === 74) { 462 | // J 463 | subMode = SUB_MODE_BIG_INT_64_ARRAY; 464 | } else if (charCode === 106) { 465 | // j 466 | subMode = SUB_MODE_BIG_UINT_64_ARRAY; 467 | } else if (charCode === 86) { 468 | // V 469 | subMode = SUB_MODE_DATA_VIEW; 470 | } else { 471 | throw new SyntaxError(`Unexpected character: '${chunk[i]}'`); 472 | } 473 | } else if (mode === MODE_NUMBER || mode === MODE_ASYNC) { 474 | if ( 475 | charCode === 45 || // - 476 | charCode === 46 || // . 477 | (charCode >= 48 && charCode <= 57) // 0-9 478 | ) { 479 | buffer += chunk[i]; 480 | } else { 481 | if (mode === MODE_ASYNC) { 482 | stack.push(Number(buffer)); 483 | mode = MODE_UNKNOWN; 484 | subMode = SUB_MODE_ASYNC_STATUS; 485 | i--; 486 | continue; 487 | } 488 | 489 | if (subMode === SUB_MODE_PROMISE_ID) { 490 | let id = Number(buffer); 491 | let existing = deferredValues.get(id) as Deferred; 492 | if (existing) { 493 | releaseValue(existing.promise, 0); 494 | } else { 495 | let deferred = new Deferred(); 496 | deferredValues.set(id, deferred); 497 | releaseValue(deferred.promise, 0); 498 | } 499 | } else if (subMode === SUB_MODE_ASYNC_ITERABLE_ID) { 500 | let id = Number(buffer); 501 | let existing = deferredValues.get( 502 | id, 503 | ) as DeferredAsyncIterable; 504 | if (existing) { 505 | releaseValue(existing.iterable, 0); 506 | } else { 507 | let deferred = new DeferredAsyncIterable(); 508 | deferredValues.set(id, deferred); 509 | releaseValue(deferred.iterable, 0); 510 | } 511 | } else if (subMode === SUB_MODE_READABLE_STREAM_ID) { 512 | let id = Number(buffer); 513 | let existing = deferredValues.get( 514 | id, 515 | ) as DeferredReadableStream; 516 | if (existing) { 517 | releaseValue(existing.readable, 0); 518 | } else { 519 | let deferred = new DeferredReadableStream(); 520 | deferredValues.set(id, deferred); 521 | releaseValue(deferred.readable, 0); 522 | } 523 | } else { 524 | releaseValue( 525 | subMode === SUB_MODE_BIGINT 526 | ? BigInt(buffer) 527 | : subMode === SUB_MODE_REFERENCE 528 | ? references.get(Number(buffer)) 529 | : Number(buffer), 530 | 0, 531 | ); 532 | } 533 | buffer = ""; 534 | mode = MODE_UNKNOWN; 535 | subMode = SUB_MODE_UNKNOWN; 536 | i--; 537 | } 538 | } else if (mode === MODE_STRING) { 539 | let stringEnd = false; 540 | for (; i < length; i++) { 541 | charCode = chunk.charCodeAt(i); 542 | if (charCode !== 34 || (lastChar === 92 && numSlashes % 2 === 1)) { 543 | buffer += chunk[i]; 544 | lastChar = charCode; 545 | if (lastChar === 92) { 546 | numSlashes++; 547 | hasSlashes = true; 548 | } else { 549 | numSlashes = 0; 550 | } 551 | } else { 552 | stringEnd = true; 553 | break; 554 | } 555 | } 556 | if (stringEnd) { 557 | let value = hasSlashes ? JSON.parse(`"${buffer}"`) : buffer; 558 | if (subMode === SUB_MODE_OBJECT_KEY) { 559 | stack.push(value); 560 | i++; 561 | } else { 562 | if (subMode === SUB_MODE_DATE) { 563 | value = new Date(value); 564 | references.set(references.size, value); 565 | } else if (subMode === SUB_MODE_SYMBOL) { 566 | value = Symbol.for(value); 567 | } else if (subMode === SUB_MODE_URL) { 568 | value = new URL(value); 569 | references.set(references.size, value); 570 | } else if (subMode === SUB_MODE_ARRAY_BUFFER) { 571 | value = decodeTypedArray(value).buffer; 572 | references.set(references.size, value); 573 | } else if (subMode === SUB_MODE_INT_8_ARRAY) { 574 | value = new Int8Array(decodeTypedArray(value).buffer); 575 | references.set(references.size, value); 576 | } else if (subMode === SUB_MODE_UINT_8_ARRAY) { 577 | value = decodeTypedArray(value); 578 | references.set(references.size, value); 579 | } else if (subMode === SUB_MODE_UINT_8_ARRAY_CLAMPED) { 580 | value = new Uint8ClampedArray(decodeTypedArray(value).buffer); 581 | references.set(references.size, value); 582 | } else if (subMode === SUB_MODE_INT_16_ARRAY) { 583 | value = new Int16Array(decodeTypedArray(value).buffer); 584 | references.set(references.size, value); 585 | } else if (subMode === SUB_MODE_UINT_16_ARRAY) { 586 | value = new Uint16Array(decodeTypedArray(value).buffer); 587 | references.set(references.size, value); 588 | } else if (subMode === SUB_MODE_INT_32_ARRAY) { 589 | value = new Int32Array(decodeTypedArray(value).buffer); 590 | references.set(references.size, value); 591 | } else if (subMode === SUB_MODE_UINT_32_ARRAY) { 592 | value = new Uint32Array(decodeTypedArray(value).buffer); 593 | references.set(references.size, value); 594 | } else if (subMode === SUB_MODE_FLOAT_32_ARRAY) { 595 | value = new Float32Array(decodeTypedArray(value).buffer); 596 | references.set(references.size, value); 597 | } else if (subMode === SUB_MODE_FLOAT_64_ARRAY) { 598 | value = new Float64Array(decodeTypedArray(value).buffer); 599 | references.set(references.size, value); 600 | } else if (subMode === SUB_MODE_BIG_INT_64_ARRAY) { 601 | value = new BigInt64Array(decodeTypedArray(value).buffer); 602 | references.set(references.size, value); 603 | } else if (subMode === SUB_MODE_BIG_UINT_64_ARRAY) { 604 | value = new BigUint64Array(decodeTypedArray(value).buffer); 605 | references.set(references.size, value); 606 | } else if (subMode === SUB_MODE_DATA_VIEW) { 607 | value = decodeTypedArray(value); 608 | value = new DataView( 609 | (value as Uint8Array).buffer, 610 | (value as Uint8Array).byteOffset, 611 | (value as Uint8Array).byteLength, 612 | ); 613 | references.set(references.size, value); 614 | } 615 | 616 | releaseValue(value, 0); 617 | } 618 | mode = MODE_UNKNOWN; 619 | subMode = SUB_MODE_UNKNOWN; 620 | } else { 621 | i--; 622 | } 623 | } 624 | } 625 | if (i > length) { 626 | shouldSkip = i - length; 627 | } 628 | }; 629 | 630 | let reader = stream.getReader(); 631 | (async () => { 632 | let read: ReadableStreamReadResult; 633 | while (!(read = await reader.read()).done) { 634 | step(read.value); 635 | } 636 | })() 637 | .catch((error) => { 638 | if (root) { 639 | root.reject(error); 640 | root = null; 641 | } 642 | for (let deferred of deferredValues.values()) { 643 | deferred.reject(error); 644 | } 645 | }) 646 | .finally(() => { 647 | reader.releaseLock(); 648 | 649 | if (root) { 650 | root.reject(new Error("Stream ended before root value was parsed")); 651 | root = null; 652 | } 653 | for (let deferred of deferredValues.values()) { 654 | deferred.reject(new Error("Stream ended before promise was resolved")); 655 | } 656 | }); 657 | 658 | return root.promise; 659 | } 660 | 661 | function decodeTypedArray(base64: string) { 662 | const decodedStr = atob(base64); 663 | const uint8Array = new Uint8Array(decodedStr.length); 664 | for (let i = 0; i < decodedStr.length; i++) { 665 | uint8Array[i] = decodedStr.charCodeAt(i); 666 | } 667 | return uint8Array; 668 | } 669 | -------------------------------------------------------------------------------- /src/encode.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from "node:test"; 2 | import { expect } from "expect"; 3 | 4 | import { encode, encodeSync, type EncodePlugin } from "./encode.js"; 5 | import { 6 | STR_ARRAY_BUFFER, 7 | STR_BIG_INT_64_ARRAY, 8 | STR_BIG_UINT_64_ARRAY, 9 | STR_BLOB, 10 | STR_DATA_VIEW, 11 | STR_DATE, 12 | STR_FILE, 13 | STR_FLOAT_32_ARRAY, 14 | STR_FLOAT_64_ARRAY, 15 | STR_FORM_DATA, 16 | STR_INFINITY, 17 | STR_INT_16_ARRAY, 18 | STR_INT_32_ARRAY, 19 | STR_INT_8_ARRAY, 20 | STR_NaN, 21 | STR_NEGATIVE_INFINITY, 22 | STR_NEGATIVE_ZERO, 23 | STR_REGEXP, 24 | STR_UINT_16_ARRAY, 25 | STR_UINT_32_ARRAY, 26 | STR_UINT_8_ARRAY, 27 | STR_UINT_8_ARRAY_CLAMPED, 28 | STR_URL, 29 | SUPPORTS_FILE, 30 | } from "./shared.js"; 31 | 32 | describe("encodeSync", () => { 33 | function quickEncode( 34 | value: unknown, 35 | expectedPromises = 0, 36 | plugins: EncodePlugin[] = [], 37 | ): string { 38 | const chunks: string[] = []; 39 | const refs = new WeakMap(); 40 | const promises = new WeakMap(); 41 | const promiseFrames: unknown[] = []; 42 | const counters = { refId: 0, promiseId: 0 }; 43 | const redactErrors = true; 44 | 45 | encodeSync( 46 | value, 47 | chunks, 48 | refs, 49 | promises, 50 | promiseFrames, 51 | counters, 52 | plugins, 53 | redactErrors, 54 | ); 55 | 56 | expect(counters.promiseId).toBe(expectedPromises); 57 | expect(promiseFrames.length).toBe(expectedPromises); 58 | 59 | return chunks.join(""); 60 | } 61 | 62 | test("undefined", () => { 63 | expect(quickEncode(undefined)).toBe("u"); 64 | }); 65 | 66 | test("null", () => { 67 | expect(quickEncode(null)).toBe("null"); 68 | }); 69 | 70 | test("true", () => { 71 | expect(quickEncode(true)).toBe("true"); 72 | }); 73 | 74 | test("false", () => { 75 | expect(quickEncode(false)).toBe("false"); 76 | }); 77 | 78 | test("NaN", () => { 79 | expect(quickEncode(Number.NaN)).toBe(STR_NaN); 80 | }); 81 | 82 | test("Infinity", () => { 83 | expect(quickEncode(Number.POSITIVE_INFINITY)).toBe(STR_INFINITY); 84 | }); 85 | 86 | test("-Infinity", () => { 87 | expect(quickEncode(Number.NEGATIVE_INFINITY)).toBe(STR_NEGATIVE_INFINITY); 88 | }); 89 | 90 | test("0", () => { 91 | expect(quickEncode(0)).toBe("0"); 92 | }); 93 | 94 | test("-0", () => { 95 | expect(quickEncode(-0)).toBe(STR_NEGATIVE_ZERO); 96 | }); 97 | 98 | test("42", () => { 99 | expect(quickEncode(42)).toBe("42"); 100 | }); 101 | 102 | test("-42", () => { 103 | expect(quickEncode(-42)).toBe("-42"); 104 | }); 105 | 106 | test("3.14", () => { 107 | expect(quickEncode(3.14)).toBe("3.14"); 108 | }); 109 | 110 | test("-3.14", () => { 111 | expect(quickEncode(-3.14)).toBe("-3.14"); 112 | }); 113 | 114 | test("bigint", () => { 115 | expect(quickEncode(42n)).toBe("b42"); 116 | }); 117 | 118 | test("symbol", () => { 119 | expect(quickEncode(Symbol.for("daSymbol"))).toBe('s"daSymbol"'); 120 | }); 121 | 122 | test("empty string", () => { 123 | expect(quickEncode("")).toBe('""'); 124 | }); 125 | 126 | test("string", () => { 127 | expect(quickEncode("Hello, world!")).toBe('"Hello, world!"'); 128 | }); 129 | 130 | test("string with special characters", () => { 131 | expect(quickEncode("\\hello\nworld\\")).toBe('"\\\\hello\\nworld\\\\"'); 132 | }); 133 | 134 | test("Date", () => { 135 | const date = new Date("2021-01-01T00:00:00Z"); 136 | expect(quickEncode(date)).toBe(`${STR_DATE}"2021-01-01T00:00:00.000Z"`); 137 | }); 138 | 139 | test("invalid Date", () => { 140 | const input = new Date("invalid"); 141 | expect(quickEncode(input)).toBe(`${STR_DATE}""`); 142 | }); 143 | 144 | test("empty object", () => { 145 | expect(quickEncode({})).toBe("{}"); 146 | }); 147 | 148 | test("empty array", () => { 149 | expect(quickEncode([])).toBe("[]"); 150 | }); 151 | 152 | test("object with one key", () => { 153 | expect(quickEncode({ a: 1 })).toBe('{"a":1}'); 154 | }); 155 | 156 | test("array with one element", () => { 157 | expect(quickEncode([1])).toBe("[1]"); 158 | }); 159 | 160 | test("object with multiple keys", () => { 161 | expect(quickEncode({ a: 1, b: 2 })).toBe('{"a":1,"b":2}'); 162 | }); 163 | 164 | test("array with multiple elements", () => { 165 | expect(quickEncode([1, 2])).toBe("[1,2]"); 166 | }); 167 | 168 | test("object with nested object", () => { 169 | expect(quickEncode({ a: { b: 1 } })).toBe('{"a":{"b":1}}'); 170 | }); 171 | 172 | test("array with nested array", () => { 173 | expect(quickEncode([[1]])).toBe("[[1]]"); 174 | }); 175 | 176 | test("object with nested array", () => { 177 | expect(quickEncode({ a: [1] })).toBe('{"a":[1]}'); 178 | }); 179 | 180 | test("array with nested object", () => { 181 | expect(quickEncode([{ a: 1 }])).toBe('[{"a":1}]'); 182 | }); 183 | 184 | test("object with circular reference", () => { 185 | const obj: Record = {}; 186 | obj.a = obj; 187 | 188 | expect(quickEncode(obj)).toBe('{"a":@0}'); 189 | }); 190 | 191 | test("object toJSON", () => { 192 | const obj = { 193 | toJSON() { 194 | return 42; 195 | }, 196 | }; 197 | expect(quickEncode(obj)).toBe("42"); 198 | }); 199 | 200 | test("object toJSON primitive multiple references", () => { 201 | const obj = { 202 | toJSON() { 203 | return 42; 204 | }, 205 | }; 206 | expect(quickEncode([obj, obj])).toBe("[42,42]"); 207 | }); 208 | 209 | test("object toJSON reference", () => { 210 | const obj = { 211 | toJSON() { 212 | return { j: 42 }; 213 | }, 214 | }; 215 | const obj2 = { a: "a", b: obj, c: obj }; 216 | expect(quickEncode(obj2)).toBe('{"a":"a","b":{"j":42},"c":@1}'); 217 | }); 218 | 219 | test("object toJSON reference multiple", () => { 220 | const obj = { 221 | toJSON() { 222 | return { j: 42 }; 223 | }, 224 | }; 225 | const obj2 = { a: "1", b: obj, c: obj }; 226 | const obj3 = { a: "2", b: obj, c: obj }; 227 | expect(quickEncode([obj2, obj3])).toBe( 228 | '[{"a":"1","b":{"j":42},"c":@2},{"a":"2","b":@2,"c":@2}]', 229 | ); 230 | }); 231 | 232 | test("array with circular reference", () => { 233 | const arr: unknown[] = []; 234 | arr.push(arr); 235 | 236 | expect(quickEncode(arr)).toBe("[@0]"); 237 | }); 238 | 239 | test("error", () => { 240 | expect(quickEncode(new Error("error"))).toBe( 241 | 'E{"name":"Error","message":"","stack":u,"cause":u}', 242 | ); 243 | }); 244 | 245 | test("promise", () => { 246 | const promise = Promise.resolve(42); 247 | expect(quickEncode(promise, 1)).toBe("$0"); 248 | }); 249 | 250 | test("promise reference", () => { 251 | const promise = Promise.resolve(42); 252 | expect(quickEncode([promise, promise], 1)).toBe("[$0,$0]"); 253 | }); 254 | 255 | test("function", () => { 256 | expect(quickEncode(() => {})).toBe("u"); 257 | }); 258 | 259 | test("plugins", () => { 260 | class A { 261 | a: string; 262 | b: B; 263 | constructor(a: string, b: B) { 264 | this.a = a; 265 | this.b = b; 266 | } 267 | } 268 | class B { 269 | b: string; 270 | constructor(b: string) { 271 | this.b = b; 272 | } 273 | } 274 | const a = new A("a", new B("b")); 275 | expect( 276 | quickEncode(a, 0, [ 277 | (value) => { 278 | if (value instanceof A) { 279 | return ["A", value.a, value.b]; 280 | } 281 | if (value instanceof B) { 282 | return ["B", value.b]; 283 | } 284 | }, 285 | ]), 286 | ).toBe('P["A","a",P["B","b"]]'); 287 | }); 288 | 289 | test("iterable", () => { 290 | const iterable = { 291 | *[Symbol.iterator]() { 292 | yield 1; 293 | yield 2; 294 | yield 3; 295 | }, 296 | }; 297 | expect(quickEncode(iterable)).toBe("[1,2,3]"); 298 | }); 299 | 300 | test("async iterable", () => { 301 | const asyncIterable = { 302 | async *[Symbol.asyncIterator]() { 303 | yield 1; 304 | yield 2; 305 | yield 3; 306 | }, 307 | }; 308 | expect(quickEncode(asyncIterable, 1)).toBe("*0"); 309 | }); 310 | 311 | test("set", () => { 312 | const set = new Set([1, 2, 3]); 313 | expect(quickEncode(set)).toBe("S[1,2,3]"); 314 | }); 315 | 316 | test("map", () => { 317 | const map = new Map([ 318 | ["a", 1], 319 | ["b", 2], 320 | ["c", 3], 321 | ]); 322 | expect(quickEncode(map)).toBe('M[["a",1],["b",2],["c",3]]'); 323 | }); 324 | 325 | test("readable stream", async () => { 326 | const readableStream = new ReadableStream({ 327 | start(controller) { 328 | controller.enqueue("a"); 329 | controller.enqueue("b"); 330 | controller.enqueue("c"); 331 | controller.close(); 332 | }, 333 | }); 334 | expect(await quickEncode(readableStream, 1)).toBe("R0"); 335 | }); 336 | }); 337 | 338 | describe("encode", () => { 339 | async function quickEncode(value: unknown, plugins: EncodePlugin[] = []) { 340 | const stream = encode(value, { plugins }); 341 | const chunks: string[] = []; 342 | 343 | const reader = stream.getReader(); 344 | try { 345 | while (true) { 346 | const { done, value } = await reader.read(); 347 | if (done) { 348 | break; 349 | } 350 | chunks.push(value); 351 | } 352 | } finally { 353 | reader.releaseLock(); 354 | } 355 | 356 | return chunks.join(""); 357 | } 358 | 359 | test("undefined", async () => { 360 | expect(await quickEncode(undefined)).toBe("u\n"); 361 | }); 362 | 363 | test("null", async () => { 364 | expect(await quickEncode(null)).toBe("null\n"); 365 | }); 366 | 367 | test("true", async () => { 368 | expect(await quickEncode(true)).toBe("true\n"); 369 | }); 370 | 371 | test("false", async () => { 372 | expect(await quickEncode(false)).toBe("false\n"); 373 | }); 374 | 375 | test("NaN", async () => { 376 | expect(await quickEncode(Number.NaN)).toBe("NaN\n"); 377 | }); 378 | 379 | test("Infinity", async () => { 380 | expect(await quickEncode(Number.POSITIVE_INFINITY)).toBe("I\n"); 381 | }); 382 | 383 | test("-Infinity", async () => { 384 | expect(await quickEncode(Number.NEGATIVE_INFINITY)).toBe("i\n"); 385 | }); 386 | 387 | test("0", async () => { 388 | expect(await quickEncode(0)).toBe("0\n"); 389 | }); 390 | 391 | test("-0", async () => { 392 | expect(await quickEncode(-0)).toBe("z\n"); 393 | }); 394 | 395 | test("42", async () => { 396 | expect(await quickEncode(42)).toBe("42\n"); 397 | }); 398 | 399 | test("-42", async () => { 400 | expect(await quickEncode(-42)).toBe("-42\n"); 401 | }); 402 | 403 | test("3.14", async () => { 404 | expect(await quickEncode(3.14)).toBe("3.14\n"); 405 | }); 406 | 407 | test("-3.14", async () => { 408 | expect(await quickEncode(-3.14)).toBe("-3.14\n"); 409 | }); 410 | 411 | test("bigint", async () => { 412 | expect(await quickEncode(42n)).toBe("b42\n"); 413 | }); 414 | 415 | test("symbol", async () => { 416 | expect(await quickEncode(Symbol.for("daSymbol"))).toBe('s"daSymbol"\n'); 417 | }); 418 | 419 | test("function", async () => { 420 | expect(await quickEncode(() => {})).toBe("u\n"); 421 | }); 422 | 423 | test("empty string", async () => { 424 | expect(await quickEncode("")).toBe('""\n'); 425 | }); 426 | 427 | test("string", async () => { 428 | expect(await quickEncode("Hello, world!")).toBe('"Hello, world!"\n'); 429 | }); 430 | 431 | test("string with special characters", async () => { 432 | expect(await quickEncode("\\hello\nworld\\")).toBe( 433 | '"\\\\hello\\nworld\\\\"\n', 434 | ); 435 | }); 436 | 437 | test("Date", async () => { 438 | const date = new Date("2021-01-01T00:00:00Z"); 439 | expect(await quickEncode(date)).toBe( 440 | `${STR_DATE}"2021-01-01T00:00:00.000Z"\n`, 441 | ); 442 | }); 443 | 444 | test("invalid Date", async () => { 445 | const input = new Date("invalid"); 446 | expect(await quickEncode(input)).toBe(`${STR_DATE}""\n`); 447 | }); 448 | 449 | test("Date reference", async () => { 450 | const date = new Date("2021-01-01T00:00:00Z"); 451 | expect(await quickEncode([date, date])).toBe( 452 | `[${STR_DATE}"2021-01-01T00:00:00.000Z",@1]\n`, 453 | ); 454 | }); 455 | 456 | test("URL", async () => { 457 | const url = new URL("https://example.com"); 458 | expect(await quickEncode(url)).toBe(`${STR_URL}"https://example.com/"\n`); 459 | }); 460 | 461 | test("URL reference", async () => { 462 | const url = new URL("https://example.com"); 463 | expect(await quickEncode([url, url])).toBe( 464 | `[${STR_URL}"https://example.com/",@1]\n`, 465 | ); 466 | }); 467 | 468 | test("empty object", async () => { 469 | expect(await quickEncode({})).toBe("{}\n"); 470 | }); 471 | 472 | test("object with one key", async () => { 473 | expect(await quickEncode({ a: 1 })).toBe('{"a":1}\n'); 474 | }); 475 | 476 | test("object with multiple keys", async () => { 477 | expect(await quickEncode({ a: 1, b: 2 })).toBe('{"a":1,"b":2}\n'); 478 | }); 479 | 480 | test("object with nested object", async () => { 481 | expect(await quickEncode({ a: { b: 1 } })).toBe('{"a":{"b":1}}\n'); 482 | }); 483 | 484 | test("object with nested array", async () => { 485 | expect(await quickEncode({ a: [1] })).toBe('{"a":[1]}\n'); 486 | }); 487 | 488 | test("object with circular reference", async () => { 489 | const obj: Record = {}; 490 | obj.a = obj; 491 | 492 | expect(await quickEncode(obj)).toBe('{"a":@0}\n'); 493 | }); 494 | 495 | test("object toJSON", async () => { 496 | const obj = { 497 | toJSON() { 498 | return 42; 499 | }, 500 | }; 501 | expect(await quickEncode(obj)).toBe("42\n"); 502 | }); 503 | 504 | test("object toJSON reference", async () => { 505 | const obj = { 506 | toJSON() { 507 | return { j: 42 }; 508 | }, 509 | }; 510 | const obj2 = { a: "a", b: obj, c: obj }; 511 | expect(await quickEncode(obj2)).toBe('{"a":"a","b":{"j":42},"c":@1}\n'); 512 | }); 513 | 514 | test("object toJSON reference multiple", async () => { 515 | const obj = { 516 | toJSON() { 517 | return { j: 42 }; 518 | }, 519 | }; 520 | const obj2 = { a: "1", b: obj, c: obj }; 521 | const obj3 = { a: "2", b: obj, c: obj }; 522 | expect(await quickEncode([obj2, obj3])).toBe( 523 | '[{"a":"1","b":{"j":42},"c":@2},{"a":"2","b":@2,"c":@2}]\n', 524 | ); 525 | }); 526 | 527 | test("empty array", async () => { 528 | expect(await quickEncode([])).toBe("[]\n"); 529 | }); 530 | 531 | test("array with one element", async () => { 532 | expect(await quickEncode([1])).toBe("[1]\n"); 533 | }); 534 | 535 | test("array with multiple elements", async () => { 536 | expect(await quickEncode([1, 2])).toBe("[1,2]\n"); 537 | }); 538 | 539 | test("array with nested array", async () => { 540 | expect(await quickEncode([[1]])).toBe("[[1]]\n"); 541 | }); 542 | 543 | test("array with nested object", async () => { 544 | expect(await quickEncode([{ a: 1 }])).toBe('[{"a":1}]\n'); 545 | }); 546 | 547 | test("array with circular reference", async () => { 548 | const arr: unknown[] = []; 549 | arr.push(arr); 550 | 551 | expect(await quickEncode(arr)).toBe("[@0]\n"); 552 | }); 553 | 554 | test("array of values", async () => { 555 | const values = [ 556 | undefined, 557 | null, 558 | true, 559 | false, 560 | Number.NaN, 561 | Number.POSITIVE_INFINITY, 562 | Number.NEGATIVE_INFINITY, 563 | -0, 564 | 0, 565 | 42, 566 | -42, 567 | 3.14, 568 | -3.14, 569 | 42n, 570 | {}, 571 | [ 572 | "1", 573 | 2, 574 | "3", 575 | Symbol.for("daSymbol"), 576 | new Date("2021-01-01"), 577 | new URL("https://example.com"), 578 | ], 579 | ]; 580 | expect(await quickEncode(values)).toBe( 581 | `[u,null,true,false,NaN,I,i,z,0,42,-42,3.14,-3.14,b42,{},[\"1\",2,\"3\",s\"daSymbol\",D\"2021-01-01T00:00:00.000Z\",U\"https://example.com/\"]]\n`, 582 | ); 583 | }); 584 | 585 | test("iterable", async () => { 586 | const iterable = { 587 | *[Symbol.iterator]() { 588 | yield 1; 589 | yield 2; 590 | yield 3; 591 | }, 592 | }; 593 | expect(await quickEncode(iterable)).toBe("[1,2,3]\n"); 594 | }); 595 | 596 | test("set", async () => { 597 | const set = new Set([1, 2, 3]); 598 | expect(await quickEncode(set)).toBe("S[1,2,3]\n"); 599 | }); 600 | 601 | test("set reference", async () => { 602 | const set = new Set([1, 2, 3]); 603 | set.add(set); 604 | expect(await quickEncode(set)).toBe("S[1,2,3,@0]\n"); 605 | }); 606 | 607 | test("map", async () => { 608 | const map = new Map([ 609 | ["a", 1], 610 | ["b", 2], 611 | ["c", 3], 612 | ]); 613 | expect(await quickEncode(map)).toBe('M[["a",1],["b",2],["c",3]]\n'); 614 | }); 615 | 616 | test("map reference", async () => { 617 | const map = new Map([ 618 | ["a", 1], 619 | ["b", 2], 620 | ["c", 3], 621 | ]); 622 | map.set(map, map); 623 | expect(await quickEncode(map)).toBe('M[["a",1],["b",2],["c",3],[@0,@0]]\n'); 624 | }); 625 | 626 | test("RegExp", async () => { 627 | const regexp = /abc/g; 628 | expect(await quickEncode(regexp)).toBe(`${STR_REGEXP}["abc","g"]\n`); 629 | }); 630 | 631 | test("RegExp reference", async () => { 632 | const regexp = /abc/g; 633 | expect(await quickEncode([regexp, regexp])).toBe( 634 | `[${STR_REGEXP}["abc","g"],@1]\n`, 635 | ); 636 | }); 637 | 638 | test("plugins", async () => { 639 | class A { 640 | a: string; 641 | b: B; 642 | constructor(a: string, b: B) { 643 | this.a = a; 644 | this.b = b; 645 | } 646 | } 647 | class B { 648 | b: string; 649 | constructor(b: string) { 650 | this.b = b; 651 | } 652 | } 653 | const a = new A("a", new B("b")); 654 | expect( 655 | await quickEncode(a, [ 656 | (value) => { 657 | if (value instanceof A) { 658 | return ["A", value.a, value.b]; 659 | } 660 | if (value instanceof B) { 661 | return ["B", value.b]; 662 | } 663 | }, 664 | ]), 665 | ).toBe('P["A","a",P["B","b"]]\n'); 666 | }); 667 | 668 | test("plugins can handle functions", async () => { 669 | const fn = () => {}; 670 | expect( 671 | await quickEncode(fn, [ 672 | (value) => { 673 | if (typeof value === "function") { 674 | return ["fn"]; 675 | } 676 | }, 677 | ]), 678 | ).toBe('P["fn"]\n'); 679 | }); 680 | 681 | test("promise", async () => { 682 | const promise = Promise.resolve(42); 683 | expect(await quickEncode(promise)).toBe("$0\n0:42\n"); 684 | }); 685 | 686 | test("rejected promise value", async () => { 687 | const promise = Promise.reject(42); 688 | expect(await quickEncode(promise)).toBe("$0\n0!42\n"); 689 | }); 690 | 691 | test("rejected promise error", async () => { 692 | const promise = Promise.reject(new Error("rejected")); 693 | expect(await quickEncode(promise)).toBe( 694 | '$0\n0!E{"name":"Error","message":"","stack":u,"cause":u}\n', 695 | ); 696 | }); 697 | 698 | test("promise reference", async () => { 699 | const promise = Promise.resolve(42); 700 | expect(await quickEncode([promise, promise])).toBe("[$0,$0]\n0:42\n"); 701 | }); 702 | 703 | test("async iterable", async () => { 704 | const asyncIterable = { 705 | async *[Symbol.asyncIterator]() { 706 | yield 1; 707 | yield 2; 708 | yield 3; 709 | }, 710 | }; 711 | expect(await quickEncode(asyncIterable)).toBe("*0\n0:1\n0:2\n0:3\n0\n"); 712 | }); 713 | 714 | test("async iterable reference", async () => { 715 | const asyncIterable = { 716 | async *[Symbol.asyncIterator]() { 717 | yield 1; 718 | yield 2; 719 | yield 3; 720 | }, 721 | }; 722 | expect(await quickEncode([asyncIterable, asyncIterable])).toBe( 723 | "[*0,*0]\n0:1\n0:2\n0:3\n0\n", 724 | ); 725 | }); 726 | 727 | test("async iterable error", async () => { 728 | const asyncIterable = { 729 | async *[Symbol.asyncIterator]() { 730 | yield 1; 731 | yield 2; 732 | throw new Error("error"); 733 | }, 734 | }; 735 | expect(await quickEncode(asyncIterable)).toBe( 736 | '*0\n0:1\n0:2\n0!E{"name":"Error","message":"","stack":u,"cause":u}\n', 737 | ); 738 | }); 739 | 740 | test("async iterable of async iterables", async () => { 741 | const asyncIterable = { 742 | async *[Symbol.asyncIterator]() { 743 | yield { 744 | async *[Symbol.asyncIterator]() { 745 | yield 1; 746 | yield 2; 747 | }, 748 | }; 749 | yield { 750 | async *[Symbol.asyncIterator]() { 751 | yield 3; 752 | yield 4; 753 | }, 754 | }; 755 | }, 756 | }; 757 | expect(await quickEncode(asyncIterable)).toBe( 758 | "*0\n0:*1\n1:1\n0:*2\n1:2\n2:3\n0\n1\n2:4\n2\n", 759 | ); 760 | }); 761 | 762 | test("readable stream", async () => { 763 | const readableStream = new ReadableStream({ 764 | start(controller) { 765 | controller.enqueue("a"); 766 | controller.enqueue("b"); 767 | controller.enqueue("c"); 768 | controller.close(); 769 | }, 770 | }); 771 | expect(await quickEncode(readableStream)).toBe( 772 | 'R0\n0:"a"\n0:"b"\n0:"c"\n0\n', 773 | ); 774 | }); 775 | 776 | test("readable stream reference", async () => { 777 | const readableStream = new ReadableStream({ 778 | start(controller) { 779 | controller.enqueue("a"); 780 | controller.enqueue("b"); 781 | controller.enqueue("c"); 782 | controller.close(); 783 | }, 784 | }); 785 | expect(await quickEncode([readableStream, readableStream])).toBe( 786 | '[R0,R0]\n0:"a"\n0:"b"\n0:"c"\n0\n', 787 | ); 788 | }); 789 | 790 | test("multiple streams", async () => { 791 | const readableStreamA = new ReadableStream({ 792 | start(controller) { 793 | controller.enqueue("a"); 794 | controller.enqueue("b"); 795 | controller.enqueue("c"); 796 | controller.close(); 797 | }, 798 | }); 799 | const readableStreamB = new ReadableStream({ 800 | start(controller) { 801 | controller.enqueue(1); 802 | controller.enqueue(2); 803 | controller.enqueue(3); 804 | controller.close(); 805 | }, 806 | }); 807 | 808 | expect(await quickEncode([readableStreamA, readableStreamB])).toBe( 809 | '[R0,R1]\n0:"a"\n1:1\n0:"b"\n1:2\n0:"c"\n1:3\n0\n1\n', 810 | ); 811 | }); 812 | 813 | test("ArrayBuffer", async () => { 814 | const arrayBuffer = new TextEncoder().encode("Hello, world!").buffer; 815 | expect(await quickEncode(arrayBuffer)).toBe( 816 | `${STR_ARRAY_BUFFER}"${btoa("Hello, world!")}"\n`, 817 | ); 818 | }); 819 | 820 | test("Int8Array", async () => { 821 | const uint8Array = new TextEncoder().encode("Hello, world!"); 822 | const int8Array = new Int8Array(uint8Array); 823 | expect(await quickEncode(int8Array)).toBe( 824 | `${STR_INT_8_ARRAY}"${btoa("Hello, world!")}"\n`, 825 | ); 826 | }); 827 | 828 | test("Uint8Array", async () => { 829 | const uint8Array = new TextEncoder().encode("Hello, world!"); 830 | expect(await quickEncode(uint8Array)).toBe( 831 | `${STR_UINT_8_ARRAY}"${btoa("Hello, world!")}"\n`, 832 | ); 833 | }); 834 | 835 | test("Uint8ClampedArray", async () => { 836 | const uint8Array = new TextEncoder().encode("Hello, world!"); 837 | const uint8ClampedArray = new Uint8ClampedArray(uint8Array); 838 | expect(await quickEncode(uint8ClampedArray)).toBe( 839 | `${STR_UINT_8_ARRAY_CLAMPED}"${btoa("Hello, world!")}"\n`, 840 | ); 841 | }); 842 | 843 | test("Int16Array", async () => { 844 | const int16Array = new Int16Array([1, 2, 3]); 845 | expect(await quickEncode(int16Array)).toBe( 846 | `${STR_INT_16_ARRAY}"AQACAAMA"\n`, 847 | ); 848 | }); 849 | 850 | test("Uint16Array", async () => { 851 | const int16Array = new Uint16Array([1, 2, 3]); 852 | expect(await quickEncode(int16Array)).toBe( 853 | `${STR_UINT_16_ARRAY}"AQACAAMA"\n`, 854 | ); 855 | }); 856 | 857 | test("Int32Array", async () => { 858 | const int32Array = new Int32Array([1, 2, 3]); 859 | expect(await quickEncode(int32Array)).toBe( 860 | `${STR_INT_32_ARRAY}"AQAAAAIAAAADAAAA"\n`, 861 | ); 862 | }); 863 | 864 | test("Uint32Array", async () => { 865 | const int32Array = new Uint32Array([1, 2, 3]); 866 | expect(await quickEncode(int32Array)).toBe( 867 | `${STR_UINT_32_ARRAY}"AQAAAAIAAAADAAAA"\n`, 868 | ); 869 | }); 870 | 871 | test("Float32Array", async () => { 872 | const float32Array = new Float32Array([1.1, 2.2, 3.3]); 873 | expect(await quickEncode(float32Array)).toBe( 874 | `${STR_FLOAT_32_ARRAY}"zcyMP83MDEAzM1NA"\n`, 875 | ); 876 | }); 877 | 878 | test("Float64Array", async () => { 879 | const float64Array = new Float64Array([1.1, 2.2, 3.3]); 880 | expect(await quickEncode(float64Array)).toBe( 881 | `${STR_FLOAT_64_ARRAY}"mpmZmZmZ8T+amZmZmZkBQGZmZmZmZgpA"\n`, 882 | ); 883 | }); 884 | 885 | test("BigInt64Array", async () => { 886 | const bigInt64Array = new BigInt64Array([1n, 2n, 3n]); 887 | expect(await quickEncode(bigInt64Array)).toBe( 888 | `${STR_BIG_INT_64_ARRAY}"AQAAAAAAAAACAAAAAAAAAAMAAAAAAAAA"\n`, 889 | ); 890 | }); 891 | 892 | test("BigUint64Array", async () => { 893 | const bigUint64Array = new BigUint64Array([1n, 2n, 3n]); 894 | expect(await quickEncode(bigUint64Array)).toBe( 895 | `${STR_BIG_UINT_64_ARRAY}"AQAAAAAAAAACAAAAAAAAAAMAAAAAAAAA"\n`, 896 | ); 897 | }); 898 | 899 | test("DataView", async () => { 900 | const arrayBuffer = new TextEncoder().encode("Hello, world!").buffer; 901 | const dataView = new DataView(arrayBuffer); 902 | expect(await quickEncode(dataView)).toBe( 903 | `${STR_DATA_VIEW}"${btoa("Hello, world!")}"\n`, 904 | ); 905 | }); 906 | 907 | test("Blob", async () => { 908 | const blob = new Blob(["Hello, world!"], { type: "text/plain" }); 909 | expect(await quickEncode(blob)).toBe( 910 | `${STR_BLOB}{"promise":$0,"size":13,"type":"text/plain"}\n0:A"SGVsbG8sIHdvcmxkIQ=="\n`, 911 | ); 912 | }); 913 | 914 | if (SUPPORTS_FILE) { 915 | test("File", async () => { 916 | const file = new File(["Hello, world!"], "hello.txt", { 917 | type: "text/plain", 918 | lastModified: 1000, 919 | }); 920 | expect(await quickEncode(file)).toBe( 921 | `${STR_FILE}{"promise":$0,"size":13,"type":"text/plain","name":"hello.txt","lastModified":1000}\n0:A"SGVsbG8sIHdvcmxkIQ=="\n`, 922 | ); 923 | }); 924 | } 925 | 926 | test("FormData", async () => { 927 | const formData = new FormData(); 928 | formData.append("a", "1"); 929 | formData.append("b", "2"); 930 | formData.append( 931 | "file", 932 | SUPPORTS_FILE 933 | ? new File(["Hello, world!"], "hello.txt", { 934 | type: "text/plain", 935 | lastModified: 1000, 936 | }) 937 | : new Blob(["Hello, world!"], { type: "text/plain" }), 938 | ); 939 | if (SUPPORTS_FILE) { 940 | expect(await quickEncode(formData)).toBe( 941 | `${STR_FORM_DATA}[["a","1"],["b","2"],["file",${STR_FILE}{"promise":$0,"size":13,"type":"text/plain","name":"hello.txt","lastModified":1000}]]\n0:A"SGVsbG8sIHdvcmxkIQ=="\n`, 942 | ); 943 | } else { 944 | expect(await quickEncode(formData)).toBe( 945 | `${STR_FORM_DATA}[["a","1"],["b","2"],["file",${STR_BLOB}{"promise":$0,"size":13,"type":"text/plain"}]]\n0:A"SGVsbG8sIHdvcmxkIQ=="\n`, 946 | ); 947 | } 948 | }); 949 | }); 950 | -------------------------------------------------------------------------------- /src/decode.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, test } from "node:test"; 2 | import { expect } from "expect"; 3 | 4 | import { decode, type DecodePlugin } from "./decode.js"; 5 | import { encode, type EncodePlugin } from "./encode.js"; 6 | import { Deferred, STR_REDACTED, SUPPORTS_FILE } from "./shared.js"; 7 | 8 | function quickDecode( 9 | value: T, 10 | encodePlugins: EncodePlugin[] = [], 11 | decodePlugins: DecodePlugin[] = [], 12 | signal?: AbortSignal, 13 | redactErrors?: boolean | string, 14 | ): Promise { 15 | const encoded = encode(value, { 16 | plugins: encodePlugins, 17 | redactErrors, 18 | signal, 19 | }); 20 | return decode( 21 | // make sure we can process streams character by character 22 | encoded.pipeThrough( 23 | new TransformStream({ 24 | transform(chunk, controller) { 25 | for (const char of chunk) { 26 | controller.enqueue(char); 27 | } 28 | }, 29 | }), 30 | ), 31 | { 32 | plugins: decodePlugins, 33 | }, 34 | ); 35 | } 36 | 37 | function getCallStackSize(): number { 38 | let counter = 0; 39 | 40 | const recurse = (): number => { 41 | counter++; 42 | try { 43 | return recurse(); 44 | } catch (error) { 45 | return counter - 1; 46 | } 47 | }; 48 | 49 | return recurse(); 50 | } 51 | 52 | describe("decode", () => { 53 | test("undefined", async () => { 54 | expect(await quickDecode(undefined)).toBe(undefined); 55 | }); 56 | 57 | test("null", async () => { 58 | expect(await quickDecode(null)).toBe(null); 59 | }); 60 | 61 | test("true", async () => { 62 | expect(await quickDecode(true)).toBe(true); 63 | }); 64 | 65 | test("false", async () => { 66 | expect(await quickDecode(false)).toBe(false); 67 | }); 68 | 69 | test("NaN", async () => { 70 | expect(await quickDecode(Number.NaN)).toBe(Number.NaN); 71 | }); 72 | 73 | test("Infinity", async () => { 74 | expect(await quickDecode(Number.POSITIVE_INFINITY)).toBe( 75 | Number.POSITIVE_INFINITY, 76 | ); 77 | }); 78 | 79 | test("-Infinity", async () => { 80 | expect(await quickDecode(Number.NEGATIVE_INFINITY)).toBe( 81 | Number.NEGATIVE_INFINITY, 82 | ); 83 | }); 84 | 85 | test("0", async () => { 86 | expect(await quickDecode(0)).toBe(0); 87 | }); 88 | 89 | test("-0", async () => { 90 | expect(await quickDecode(-0)).toBe(-0); 91 | }); 92 | 93 | test("42", async () => { 94 | expect(await quickDecode(42)).toBe(42); 95 | }); 96 | 97 | test("-42", async () => { 98 | expect(await quickDecode(-42)).toBe(-42); 99 | }); 100 | 101 | test("3.14", async () => { 102 | expect(await quickDecode(3.14)).toBe(3.14); 103 | }); 104 | 105 | test("-3.14", async () => { 106 | expect(await quickDecode(3.14)).toBe(3.14); 107 | }); 108 | 109 | test("bigint", async () => { 110 | expect(await quickDecode(42n)).toBe(42n); 111 | }); 112 | 113 | test("symbol", async () => { 114 | const symbol = Symbol.for("daSymbol"); 115 | const decoded = await quickDecode(symbol); 116 | expect(decoded).toBe(symbol); 117 | }); 118 | 119 | test("empty string", async () => { 120 | expect(await quickDecode("")).toBe(""); 121 | }); 122 | 123 | test("string", async () => { 124 | expect(await quickDecode("Hello, world!")).toBe("Hello, world!"); 125 | }); 126 | 127 | test("string with special characters", async () => { 128 | expect(await quickDecode("\\hello\nworld\\")).toBe("\\hello\nworld\\"); 129 | }); 130 | 131 | test("Date", async () => { 132 | const date = new Date(); 133 | const decoded = await quickDecode(date); 134 | expect(decoded).toEqual(date); 135 | expect(decoded).toBeInstanceOf(Date); 136 | }); 137 | 138 | test("Date", async () => { 139 | const date = new Date("invalid"); 140 | const decoded = await quickDecode(date); 141 | expect(decoded).toBeInstanceOf(Date); 142 | expect(decoded.toString()).toBe("Invalid Date"); 143 | expect(decoded.getTime()).toBeNaN(); 144 | }); 145 | 146 | test("Date reference", async () => { 147 | const date = new Date(); 148 | const decoded = await quickDecode([date, date]); 149 | expect(decoded).toEqual([date, date]); 150 | expect(decoded[0]).toBe(decoded[1]); 151 | }); 152 | 153 | test("URL", async () => { 154 | const url = new URL("https://example.com"); 155 | const decoded = await quickDecode(url); 156 | expect(decoded).toBeInstanceOf(URL); 157 | expect(decoded.href).toEqual(url.href); 158 | }); 159 | 160 | test("URL reference", async () => { 161 | const url = new URL("https://example.com"); 162 | const decoded = await quickDecode([url, url]); 163 | expect(decoded).toEqual([url, url]); 164 | expect(decoded[0]).toBe(decoded[1]); 165 | }); 166 | 167 | test("empty object", async () => { 168 | expect(await quickDecode({})).toEqual({}); 169 | }); 170 | 171 | test("object with one key", async () => { 172 | expect(await quickDecode({ key: "value" })).toEqual({ key: "value" }); 173 | }); 174 | 175 | test("object with multiple keys", async () => { 176 | expect(await quickDecode({ a: 1, b: 2 })).toEqual({ a: 1, b: 2 }); 177 | }); 178 | 179 | test("object with nested object", async () => { 180 | expect(await quickDecode({ a: { b: 1 } })).toEqual({ a: { b: 1 } }); 181 | }); 182 | 183 | test("object with nested array", async () => { 184 | expect(await quickDecode({ a: [1] })).toEqual({ a: [1] }); 185 | }); 186 | 187 | test("object with circular reference", async () => { 188 | const obj: Record = {}; 189 | obj.a = obj; 190 | const decoded = await quickDecode(obj); 191 | expect(decoded).toEqual(obj); 192 | expect(decoded.a).toBe(decoded); 193 | }); 194 | 195 | test("empty array", async () => { 196 | expect(await quickDecode([])).toEqual([]); 197 | }); 198 | 199 | test("array with one element", async () => { 200 | expect(await quickDecode([1])).toEqual([1]); 201 | }); 202 | 203 | test("array with multiple elements", async () => { 204 | expect(await quickDecode([1, 2])).toEqual([1, 2]); 205 | }); 206 | 207 | test("array with nested array", async () => { 208 | expect(await quickDecode([[1]])).toEqual([[1]]); 209 | }); 210 | 211 | test("array with nested object", async () => { 212 | expect(await quickDecode([{ a: 1 }])).toEqual([{ a: 1 }]); 213 | }); 214 | 215 | test("object toJSON", async () => { 216 | const obj = { 217 | toJSON() { 218 | return 42; 219 | }, 220 | }; 221 | expect(await quickDecode(obj)).toEqual(42); 222 | }); 223 | 224 | test("object toJSON reference", async () => { 225 | const obj = { 226 | toJSON() { 227 | return { j: 42 }; 228 | }, 229 | }; 230 | const obj2 = { a: "a", b: obj, c: obj }; 231 | 232 | const sub = { j: 42 }; 233 | const expected = { a: "a", b: sub, c: sub }; 234 | 235 | const decoded = await quickDecode(obj2); 236 | expect(decoded).toEqual(expected); 237 | expect(decoded.b).toBe(decoded.c); 238 | }); 239 | 240 | test("object toJSON reference multiple", async () => { 241 | const obj = { 242 | toJSON() { 243 | return { j: 42 }; 244 | }, 245 | }; 246 | const obj2 = { a: "1", b: obj, c: obj }; 247 | const obj3 = { a: "2", b: obj, c: obj }; 248 | 249 | const sub = { j: 42 }; 250 | const expected = [ 251 | { a: "1", b: sub, c: sub }, 252 | { a: "2", b: sub, c: sub }, 253 | ]; 254 | 255 | const decoded = await quickDecode([obj2, obj3]); 256 | expect(decoded).toEqual(expected); 257 | expect(decoded[0].b).toBe(decoded[0].c); 258 | expect(decoded[0].b).toBe(decoded[1].b); 259 | expect(decoded[0].b).toBe(decoded[1].c); 260 | }); 261 | 262 | test("array with circular references", async () => { 263 | const arr: unknown[] = []; 264 | arr.push(arr); 265 | const decoded = await quickDecode(arr); 266 | expect(decoded).toEqual(decoded); 267 | expect(decoded[0]).toBe(decoded); 268 | }); 269 | 270 | test("array of values", async () => { 271 | const values = [ 272 | undefined, 273 | null, 274 | true, 275 | false, 276 | Number.NaN, 277 | Number.POSITIVE_INFINITY, 278 | Number.NEGATIVE_INFINITY, 279 | -0, 280 | 0, 281 | 42, 282 | -42, 283 | 3.14, 284 | -3.14, 285 | 42n, 286 | {}, 287 | [ 288 | "1", 289 | 2, 290 | "3", 291 | Symbol.for("daSymbol"), 292 | new Date("2021-01-01"), 293 | new URL("https://example.com"), 294 | ], 295 | ]; 296 | expect(await quickDecode(values)).toEqual(values); 297 | }); 298 | 299 | test("iterable", async () => { 300 | const iterable = { 301 | *[Symbol.iterator]() { 302 | yield 1; 303 | yield 2; 304 | yield 3; 305 | }, 306 | }; 307 | expect(await quickDecode(iterable)).toEqual([1, 2, 3]); 308 | }); 309 | 310 | test("plugins", async () => { 311 | class A { 312 | a: string; 313 | b: B; 314 | constructor(a: string, b: B) { 315 | this.a = a; 316 | this.b = b; 317 | } 318 | } 319 | class B { 320 | b: string; 321 | constructor(b: string) { 322 | this.b = b; 323 | } 324 | } 325 | const a = new A("a", new B("b")); 326 | const decoded = await quickDecode( 327 | a, 328 | [ 329 | (value) => { 330 | if (value instanceof A) { 331 | return ["A", value.a, value.b]; 332 | } 333 | if (value instanceof B) { 334 | return ["B", value.b]; 335 | } 336 | }, 337 | ], 338 | [ 339 | (type, ...values) => { 340 | if (type === "A") { 341 | return { value: new A(values[0] as string, values[1] as B) }; 342 | } 343 | if (type === "B") { 344 | return { value: new B(values[0] as string) }; 345 | } 346 | }, 347 | ], 348 | ); 349 | expect(decoded).toBeInstanceOf(A); 350 | expect(decoded.a).toBe("a"); 351 | expect(decoded.b).toBeInstanceOf(B); 352 | expect(decoded.b.b).toBe("b"); 353 | }); 354 | 355 | test("empty set", async () => { 356 | const set = new Set(); 357 | const decoded = await quickDecode(set); 358 | expect(decoded).toBeInstanceOf(Set); 359 | expect(Array.from(decoded)).toEqual(Array.from(set)); 360 | }); 361 | 362 | test("set", async () => { 363 | const set = new Set([1, 2, 3]); 364 | const decoded = await quickDecode(set); 365 | expect(decoded).toBeInstanceOf(Set); 366 | expect(Array.from(decoded)).toEqual(Array.from(set)); 367 | }); 368 | 369 | test("set reference", async () => { 370 | const set = new Set([1, 2, 3]); 371 | const decoded = await quickDecode(set); 372 | expect(decoded).toBeInstanceOf(Set); 373 | expect(Array.from(decoded).slice(-1)).toEqual(Array.from(set).slice(-1)); 374 | expect(decoded.has(decoded)); 375 | }); 376 | 377 | test("empty map", async () => { 378 | const map = new Map(); 379 | const decoded = await quickDecode(map); 380 | expect(decoded).toBeInstanceOf(Map); 381 | expect(Array.from(decoded)).toEqual(Array.from(map)); 382 | }); 383 | 384 | test("map", async () => { 385 | const map = new Map([ 386 | ["a", 1], 387 | ["b", 2], 388 | ["c", 3], 389 | ]); 390 | const decoded = await quickDecode(map); 391 | expect(decoded).toBeInstanceOf(Map); 392 | expect(Array.from(decoded)).toEqual(Array.from(map)); 393 | }); 394 | 395 | test("map reference", async () => { 396 | const map = new Map([ 397 | ["a", 1], 398 | ["b", 2], 399 | ["c", 3], 400 | ]); 401 | map.set(map, map); 402 | const decoded = await quickDecode(map); 403 | expect(decoded).toBeInstanceOf(Map); 404 | expect(Array.from(decoded).slice(-1)).toEqual(Array.from(map).slice(-1)); 405 | expect(decoded.get(decoded)).toBe(decoded); 406 | }); 407 | 408 | test("RegExp", async () => { 409 | const regexp = /abc/g; 410 | const decoded = await quickDecode(regexp); 411 | expect(decoded).toBeInstanceOf(RegExp); 412 | expect(decoded.source).toBe("abc"); 413 | expect(decoded.flags).toBe("g"); 414 | }); 415 | 416 | test("RegExp reference", async () => { 417 | const regexp = /abc/g; 418 | const decoded = await quickDecode([regexp, regexp]); 419 | expect(decoded[0]).toBeInstanceOf(RegExp); 420 | expect(decoded[0].source).toBe("abc"); 421 | expect(decoded[0].flags).toBe("g"); 422 | expect(decoded[1]).toBeInstanceOf(RegExp); 423 | expect(decoded[1].source).toBe("abc"); 424 | expect(decoded[1].flags).toBe("g"); 425 | // TODO: Do we want to support this? 426 | // expect(decoded[0]).toBe(decoded[1]); 427 | }); 428 | 429 | test("Error", async () => { 430 | const decoded = await quickDecode(new Error("message")); 431 | expect(decoded).toBeInstanceOf(Error); 432 | expect(decoded.name).toBe("Error"); 433 | expect(decoded.message).toBe(STR_REDACTED); 434 | expect(decoded.stack).toBe(undefined); 435 | }); 436 | 437 | test("Error custom redaction", async () => { 438 | const decoded = await quickDecode( 439 | new Error("message"), 440 | undefined, 441 | undefined, 442 | undefined, 443 | "Unexpected Server Error", 444 | ); 445 | expect(decoded).toBeInstanceOf(Error); 446 | expect(decoded.name).toBe("Error"); 447 | expect(decoded.message).toBe("Unexpected Server Error"); 448 | expect(decoded.stack).toBe(undefined); 449 | }); 450 | 451 | test("promise", async () => { 452 | const promise = Promise.resolve(42); 453 | expect(await quickDecode(promise)).toBe(42); 454 | }); 455 | 456 | test("rejected promise value", async () => { 457 | const promise = Promise.reject(42); 458 | await expect(quickDecode(promise)).rejects.toBe(42); 459 | }); 460 | 461 | test("rejected promise error", async () => { 462 | const promise = Promise.reject(new Error("message")); 463 | const decodePromise = quickDecode(promise); 464 | await expect(decodePromise).rejects.toBeInstanceOf(Error); 465 | await expect(decodePromise).rejects.toThrow(STR_REDACTED); 466 | const error = await decodePromise.catch((error) => error); 467 | expect(error.name).toBe("Error"); 468 | expect(error.message).toBe(STR_REDACTED); 469 | expect(error.stack).toBe(undefined); 470 | }); 471 | 472 | test("promise reference", async () => { 473 | const promise = Promise.resolve(42); 474 | const decoded = await quickDecode([promise, promise]); 475 | expect(decoded[0]).toBeInstanceOf(Promise); 476 | expect(await decoded[0]).toBe(42); 477 | expect(decoded[1]).toBe(decoded[0]); 478 | }); 479 | 480 | test("promise can be interrupted by signal", async () => { 481 | const deferred = new Deferred(); 482 | const root = { 483 | promise: deferred.promise, 484 | }; 485 | const controller = new AbortController(); 486 | const decoded = await quickDecode( 487 | root, 488 | undefined, 489 | undefined, 490 | controller.signal, 491 | ); 492 | expect(decoded.promise).toBeInstanceOf(Promise); 493 | controller.abort(); 494 | deferred.resolve(); 495 | 496 | await expect(decoded.promise).rejects.toThrowError("Aborted"); 497 | }); 498 | 499 | test("async iterable", async () => { 500 | const asyncIterable = { 501 | async *[Symbol.asyncIterator]() { 502 | yield 1; 503 | yield 2; 504 | yield 3; 505 | }, 506 | }; 507 | const iterable = await quickDecode(asyncIterable); 508 | expect(typeof iterable[Symbol.asyncIterator]).toBe("function"); 509 | const values: unknown[] = []; 510 | for await (const value of iterable) { 511 | values.push(value); 512 | } 513 | expect(values).toEqual([1, 2, 3]); 514 | }); 515 | 516 | test("async iterable reference", async () => { 517 | const asyncIterable = { 518 | async *[Symbol.asyncIterator]() { 519 | yield 1; 520 | yield 2; 521 | yield 3; 522 | }, 523 | }; 524 | const [iterable, iterableB] = await quickDecode([ 525 | asyncIterable, 526 | asyncIterable, 527 | ]); 528 | expect(typeof iterable[Symbol.asyncIterator]).toBe("function"); 529 | const values: unknown[] = []; 530 | for await (const value of iterable) { 531 | values.push(value); 532 | } 533 | expect(values).toEqual([1, 2, 3]); 534 | expect(iterableB).toBe(iterable); 535 | }); 536 | 537 | test("async iterable error", async () => { 538 | const asyncIterable = { 539 | async *[Symbol.asyncIterator]() { 540 | yield 1; 541 | yield 2; 542 | throw new Error("error"); 543 | }, 544 | }; 545 | 546 | const iterable = await quickDecode(asyncIterable); 547 | for await (const value of iterable) { 548 | expect(value).toBe(1); 549 | break; 550 | } 551 | for await (const value of iterable) { 552 | expect(value).toBe(2); 553 | break; 554 | } 555 | try { 556 | for await (const _ of iterable) { 557 | throw new Error("should not reach here"); 558 | } 559 | } catch (error) { 560 | expect(error).toBeInstanceOf(Error); 561 | expect((error as Error).message).toBe(STR_REDACTED); 562 | expect((error as Error).stack).toBe(STR_REDACTED); 563 | } 564 | }); 565 | 566 | test("async itereable of async iterables", async () => { 567 | const deferred = new Deferred(); 568 | const asyncIterableRoot = { 569 | async *[Symbol.asyncIterator]() { 570 | yield { 571 | async *[Symbol.asyncIterator]() { 572 | yield 1; 573 | await deferred.promise; 574 | yield 2; 575 | }, 576 | }; 577 | yield { 578 | async *[Symbol.asyncIterator]() { 579 | yield 3; 580 | await deferred.promise; 581 | yield 4; 582 | }, 583 | }; 584 | }, 585 | }; 586 | const iterableRoot = await quickDecode(asyncIterableRoot); 587 | expect(typeof iterableRoot[Symbol.asyncIterator]).toBe("function"); 588 | const iterables: AsyncIterable[] = []; 589 | for await (const iterable of iterableRoot) { 590 | expect(typeof iterable[Symbol.asyncIterator]).toBe("function"); 591 | iterables.push(iterable); 592 | } 593 | expect(iterables.length).toBe(2); 594 | const [iterableA, iterableB] = iterables; 595 | for await (const value of iterableB) { 596 | expect(value).toBe(3); 597 | break; 598 | } 599 | for await (const value of iterableA) { 600 | expect(value).toBe(1); 601 | break; 602 | } 603 | deferred.resolve(); 604 | for await (const value of iterableB) { 605 | expect(value).toBe(4); 606 | break; 607 | } 608 | for await (const value of iterableA) { 609 | expect(value).toBe(2); 610 | break; 611 | } 612 | for await (const _ of iterableA) { 613 | throw new Error("should not reach here"); 614 | } 615 | for await (const _ of iterableB) { 616 | throw new Error("should not reach here"); 617 | } 618 | }); 619 | 620 | test("async iterable can be interrupted by signal", async () => { 621 | const deferred = new Deferred(); 622 | const done = new Deferred(); 623 | const asyncIterable = { 624 | async *[Symbol.asyncIterator]() { 625 | yield 1; 626 | yield 2; 627 | deferred.resolve(); 628 | await done.promise; 629 | yield 3; 630 | }, 631 | }; 632 | const root = { asyncIterable }; 633 | const controller = new AbortController(); 634 | const decoded = await quickDecode( 635 | root, 636 | undefined, 637 | undefined, 638 | controller.signal, 639 | ); 640 | expect(typeof decoded.asyncIterable[Symbol.asyncIterator]).toBe("function"); 641 | const read = async () => { 642 | for await (const value of decoded.asyncIterable) { 643 | } 644 | }; 645 | const readPromise = read(); 646 | await deferred.promise; 647 | controller.abort(); 648 | done.resolve(); 649 | await expect(readPromise).rejects.toThrowError("Aborted"); 650 | }); 651 | 652 | test("readable stream", async () => { 653 | const readableStream = new ReadableStream({ 654 | start(controller) { 655 | controller.enqueue("a"); 656 | controller.enqueue("b"); 657 | controller.enqueue("c"); 658 | controller.close(); 659 | }, 660 | }); 661 | const decoded = await quickDecode(readableStream); 662 | expect(decoded).toBeInstanceOf(ReadableStream); 663 | const reader = decoded.getReader(); 664 | const values: string[] = []; 665 | while (true) { 666 | const { done, value } = await reader.read(); 667 | if (done) { 668 | break; 669 | } 670 | values.push(value); 671 | } 672 | expect(values).toEqual(["a", "b", "c"]); 673 | }); 674 | 675 | test("readable stream reference", async () => { 676 | const readableStream = new ReadableStream({ 677 | start(controller) { 678 | controller.enqueue("a"); 679 | controller.enqueue("b"); 680 | controller.enqueue("c"); 681 | controller.close(); 682 | }, 683 | }); 684 | const [decodedA, decodedB] = await quickDecode([ 685 | readableStream, 686 | readableStream, 687 | ]); 688 | expect(decodedA).toBeInstanceOf(ReadableStream); 689 | expect(decodedB).toBe(decodedA); 690 | const reader = decodedA.getReader(); 691 | const values: string[] = []; 692 | while (true) { 693 | const { done, value } = await reader.read(); 694 | if (done) { 695 | break; 696 | } 697 | values.push(value); 698 | } 699 | expect(values).toEqual(["a", "b", "c"]); 700 | }); 701 | 702 | test("readable stream error", async () => { 703 | const deferred = new Deferred(); 704 | const readableStream = new ReadableStream({ 705 | async start(controller) { 706 | controller.enqueue("a"); 707 | controller.enqueue("b"); 708 | await deferred.promise; 709 | controller.error(new Error("error")); 710 | }, 711 | }); 712 | const decoded = await quickDecode(readableStream); 713 | expect(decoded).toBeInstanceOf(ReadableStream); 714 | const reader = decoded.getReader(); 715 | expect(await reader.read()).toEqual({ done: false, value: "a" }); 716 | expect(await reader.read()).toEqual({ done: false, value: "b" }); 717 | deferred.resolve(); 718 | await expect(reader.read()).rejects.toBeInstanceOf(Error); 719 | await expect(reader.read()).rejects.toThrow(STR_REDACTED); 720 | }); 721 | 722 | test("readable stream of readable streams", async () => { 723 | const deferred = new Deferred(); 724 | const readableStreamRoot = new ReadableStream({ 725 | start(controller) { 726 | controller.enqueue( 727 | new ReadableStream({ 728 | async start(controller) { 729 | controller.enqueue("a"); 730 | await deferred.promise; 731 | controller.enqueue("b"); 732 | controller.close(); 733 | }, 734 | }), 735 | ); 736 | controller.enqueue( 737 | new ReadableStream({ 738 | async start(controller) { 739 | controller.enqueue("c"); 740 | await deferred.promise; 741 | controller.enqueue("d"); 742 | controller.close(); 743 | }, 744 | }), 745 | ); 746 | controller.close(); 747 | }, 748 | }); 749 | const decoded = await quickDecode(readableStreamRoot); 750 | expect(decoded).toBeInstanceOf(ReadableStream); 751 | const reader = decoded.getReader(); 752 | const streams: ReadableStream[] = []; 753 | while (true) { 754 | const { done, value } = await reader.read(); 755 | if (done) { 756 | break; 757 | } 758 | streams.push(value); 759 | } 760 | expect(streams.length).toBe(2); 761 | const [streamA, streamB] = streams; 762 | const readerA = streamA.getReader(); 763 | const readerB = streamB.getReader(); 764 | expect(await readerB.read()).toEqual({ done: false, value: "c" }); 765 | expect(await readerA.read()).toEqual({ done: false, value: "a" }); 766 | deferred.resolve(); 767 | expect(await readerB.read()).toEqual({ done: false, value: "d" }); 768 | expect(await readerA.read()).toEqual({ done: false, value: "b" }); 769 | expect(await readerB.read()).toEqual({ done: true }); 770 | expect(await readerA.read()).toEqual({ done: true }); 771 | }); 772 | 773 | test("readable stream can be interrupted by signal", async () => { 774 | const deferred = new Deferred(); 775 | const done = new Deferred(); 776 | const controller = new AbortController(); 777 | const readableStream = new ReadableStream({ 778 | async start(controller) { 779 | controller.enqueue("a"); 780 | controller.enqueue("b"); 781 | deferred.resolve(); 782 | await done.promise; 783 | controller.enqueue("c"); 784 | controller.close(); 785 | }, 786 | }); 787 | const root = { readableStream }; 788 | const decoded = await quickDecode( 789 | root, 790 | undefined, 791 | undefined, 792 | controller.signal, 793 | ); 794 | expect(decoded.readableStream).toBeInstanceOf(ReadableStream); 795 | await deferred.promise; 796 | controller.abort(); 797 | done.resolve(); 798 | const read = async () => { 799 | const reader = decoded.readableStream.getReader(); 800 | while (true) { 801 | const { done } = await reader.read(); 802 | if (done) { 803 | break; 804 | } 805 | } 806 | }; 807 | await expect(read()).rejects.toThrowError("Aborted"); 808 | }); 809 | 810 | test("can encode and decode recursive promises beyond the maximum call stack size", async () => { 811 | let doubleMaxCallStackSize = Math.floor(getCallStackSize() * 2); 812 | expect(doubleMaxCallStackSize).toBeGreaterThan(0); 813 | 814 | type Nested = { i: number; next: Promise | null }; 815 | const input: Nested = { i: 0, next: null }; 816 | let current: Nested = input; 817 | for (let i = 1; i < doubleMaxCallStackSize; i++) { 818 | const next = { i, next: null }; 819 | current.next = Promise.resolve(next); 820 | current = next; 821 | } 822 | 823 | let decoded = await quickDecode(input); 824 | let last: Nested = decoded; 825 | for (let i = 1; i < doubleMaxCallStackSize; i++) { 826 | expect(last.i).toBe(i - 1); 827 | expect(last.next).not.toBeNull(); 828 | last = (await last.next) as Nested; 829 | } 830 | expect(last.i).toBe(doubleMaxCallStackSize - 1); 831 | expect(last.next).toBeNull(); 832 | }); 833 | 834 | test("can encode and decode async iterables beyond the maximum call stack size", async () => { 835 | let doubleMaxCallStackSize = Math.floor(getCallStackSize() * 2); 836 | expect(doubleMaxCallStackSize).toBeGreaterThan(0); 837 | async function* asyncIterable() { 838 | for (let i = 0; i < doubleMaxCallStackSize; i++) { 839 | yield i; 840 | } 841 | } 842 | 843 | let decoded = await quickDecode(asyncIterable()); 844 | let i = 0; 845 | for await (const value of decoded) { 846 | expect(value).toBe(i++); 847 | } 848 | expect(i).toBe(doubleMaxCallStackSize); 849 | }); 850 | 851 | test("ArrayBuffer", async () => { 852 | const arrayBuffer = new TextEncoder().encode("Hello, world!").buffer; 853 | const decoded = await quickDecode(arrayBuffer); 854 | expect(decoded).toBeInstanceOf(ArrayBuffer); 855 | expect(decoded.byteLength).toBe(arrayBuffer.byteLength); 856 | const decodedText = new TextDecoder().decode(decoded); 857 | expect(decodedText).toBe("Hello, world!"); 858 | }); 859 | 860 | test("Int8Array", async () => { 861 | const int8Array = new Int8Array([-1, 2, 3]); 862 | const decoded = await quickDecode(int8Array); 863 | expect(decoded).toBeInstanceOf(Int8Array); 864 | expect(Array.from(decoded)).toEqual(Array.from(int8Array)); 865 | }); 866 | 867 | test("Uint8Array", async () => { 868 | const uint8Array = new TextEncoder().encode("Hello, world!"); 869 | const decoded = await quickDecode(uint8Array); 870 | expect(decoded).toBeInstanceOf(Uint8Array); 871 | expect(Array.from(decoded)).toEqual(Array.from(uint8Array)); 872 | }); 873 | 874 | test("Uint8ClampedArray", async () => { 875 | const uint8Array = new TextEncoder().encode("Hello, world!"); 876 | const uint8ClampedArray = new Uint8ClampedArray(uint8Array); 877 | const decoded = await quickDecode(uint8ClampedArray); 878 | expect(decoded).toBeInstanceOf(Uint8ClampedArray); 879 | expect(Array.from(decoded)).toEqual(Array.from(uint8ClampedArray)); 880 | }); 881 | 882 | test("Int16Array", async () => { 883 | const int16Array = new Int16Array([-1, 2, 3]); 884 | const decoded = await quickDecode(int16Array); 885 | expect(decoded).toBeInstanceOf(Int16Array); 886 | expect(Array.from(decoded)).toEqual(Array.from(int16Array)); 887 | }); 888 | 889 | test("Uint16Array", async () => { 890 | const int16Array = new Uint16Array([1, 2, 3]); 891 | const decoded = await quickDecode(int16Array); 892 | expect(decoded).toBeInstanceOf(Uint16Array); 893 | expect(Array.from(decoded)).toEqual(Array.from(int16Array)); 894 | }); 895 | 896 | test("Int32Array", async () => { 897 | const int32Array = new Int32Array([-1, 2, 3]); 898 | const decoded = await quickDecode(int32Array); 899 | expect(decoded).toBeInstanceOf(Int32Array); 900 | expect(Array.from(decoded)).toEqual(Array.from(int32Array)); 901 | }); 902 | 903 | test("Uint32Array", async () => { 904 | const int32Array = new Uint32Array([1, 2, 3]); 905 | const decoded = await quickDecode(int32Array); 906 | expect(decoded).toBeInstanceOf(Uint32Array); 907 | expect(Array.from(decoded)).toEqual(Array.from(int32Array)); 908 | }); 909 | 910 | test("Float32Array", async () => { 911 | const float32Array = new Float32Array([-1.1, 2.2, 3.3]); 912 | const decoded = await quickDecode(float32Array); 913 | expect(decoded).toBeInstanceOf(Float32Array); 914 | expect(Array.from(decoded)).toEqual(Array.from(float32Array)); 915 | }); 916 | 917 | test("Float64Array", async () => { 918 | const float64Array = new Float64Array([-1.1, 2.2, 3.3]); 919 | const decoded = await quickDecode(float64Array); 920 | expect(decoded).toBeInstanceOf(Float64Array); 921 | expect(Array.from(decoded)).toEqual(Array.from(float64Array)); 922 | }); 923 | 924 | test("BigInt64Array", async () => { 925 | const bigInt64Array = new BigInt64Array([-1n, 2n, 3n]); 926 | const decoded = await quickDecode(bigInt64Array); 927 | expect(decoded).toBeInstanceOf(BigInt64Array); 928 | expect(Array.from(decoded)).toEqual(Array.from(bigInt64Array)); 929 | }); 930 | 931 | test("BigUint64Array", async () => { 932 | const bigUint64Array = new BigUint64Array([1n, 2n, 3n]); 933 | const decoded = await quickDecode(bigUint64Array); 934 | expect(decoded).toBeInstanceOf(BigUint64Array); 935 | expect(Array.from(decoded)).toEqual(Array.from(bigUint64Array)); 936 | }); 937 | 938 | test("DataView", async () => { 939 | const arrayBuffer = new TextEncoder().encode("Hello, world!").buffer; 940 | const dataView = new DataView(arrayBuffer); 941 | const decoded = await quickDecode(dataView); 942 | expect(decoded).toBeInstanceOf(DataView); 943 | expect(decoded.byteLength).toBe(dataView.byteLength); 944 | expect(decoded.byteOffset).toBe(dataView.byteOffset); 945 | expect(decoded.buffer).toBeInstanceOf(ArrayBuffer); 946 | expect(decoded.buffer.byteLength).toBe(arrayBuffer.byteLength); 947 | expect(decoded.buffer.byteLength).toBe(arrayBuffer.byteLength); 948 | const decodedText = new TextDecoder().decode(decoded.buffer); 949 | expect(decodedText).toBe("Hello, world!"); 950 | }); 951 | 952 | test("Blob", async () => { 953 | const blob = new Blob(["Hello, world!"]); 954 | const decoded = await quickDecode(blob); 955 | expect(decoded).toBeInstanceOf(Blob); 956 | expect(decoded.size).toBe(blob.size); 957 | expect(decoded.type).toBe(blob.type); 958 | expect(await blob.text()).toBe("Hello, world!"); 959 | }); 960 | 961 | if (SUPPORTS_FILE) { 962 | test("File", async () => { 963 | const file = new File(["Hello, world!"], "file.txt", { 964 | type: "text/plain", 965 | lastModified: 1000, 966 | }); 967 | const decoded = await quickDecode(file); 968 | expect(decoded).toBeInstanceOf(File); 969 | expect(decoded.size).toBe(file.size); 970 | expect(decoded.type).toBe(file.type); 971 | expect(decoded.name).toBe(file.name); 972 | expect(decoded.lastModified).toBe(file.lastModified); 973 | expect(await file.text()).toBe("Hello, world!"); 974 | }); 975 | } 976 | 977 | test("FormData", async () => { 978 | const formData = new FormData(); 979 | formData.append("key", "value"); 980 | formData.append("key", "value2"); 981 | if (SUPPORTS_FILE) { 982 | formData.append( 983 | "file", 984 | new File(["Hello, world!"], "file.txt", { 985 | type: "text/plain", 986 | lastModified: 1000, 987 | }), 988 | ); 989 | } 990 | const decoded = await quickDecode(formData); 991 | expect(decoded).toBeInstanceOf(FormData); 992 | expect(decoded.getAll("key")).toEqual(["value", "value2"]); 993 | if (SUPPORTS_FILE) { 994 | const file = decoded.get("file") as File; 995 | expect(file).toBeInstanceOf(File); 996 | expect(file.size).toBe(13); 997 | expect(file.type).toBe("text/plain"); 998 | expect(file.name).toBe("file.txt"); 999 | expect(file.lastModified).toBe(1000); 1000 | expect(await file.text()).toBe("Hello, world!"); 1001 | } 1002 | }); 1003 | }); 1004 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | devDependencies: 11 | '@arethetypeswrong/cli': 12 | specifier: ^0.12.2 13 | version: 0.12.2 14 | '@biomejs/biome': 15 | specifier: ^1.9.4 16 | version: 1.9.4 17 | '@types/node': 18 | specifier: ^20.8.7 19 | version: 20.8.7 20 | esbuild: 21 | specifier: ^0.19.5 22 | version: 0.19.5 23 | expect: 24 | specifier: ^29.7.0 25 | version: 29.7.0 26 | mitata: 27 | specifier: ^1.0.33 28 | version: 1.0.33 29 | pkg-pr-new: 30 | specifier: ^0.0.39 31 | version: 0.0.39 32 | tsm: 33 | specifier: ^2.3.0 34 | version: 2.3.0 35 | typescript: 36 | specifier: ^5.2.2 37 | version: 5.2.2 38 | 39 | packages: 40 | 41 | '@andrewbranch/untar.js@1.0.3': 42 | resolution: {integrity: sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw==} 43 | 44 | '@arethetypeswrong/cli@0.12.2': 45 | resolution: {integrity: sha512-SE4Rqy8LM8zgRLeVXZqFIOg4w4TCDG2AMguuZDDRcrUmVQj7phW0tWJnKwsZtyJ6SdqXTIzWvGYiUJiHg2hb9w==} 46 | hasBin: true 47 | 48 | '@arethetypeswrong/core@0.12.2': 49 | resolution: {integrity: sha512-ez/quGfC6RVg7VrwCgMVreJ01jbkfJQRNxOG7Bpl4YGcPRs45ZE1AzpHiIdzqfwFg9EBVSaewaffrsK5MVbALw==} 50 | 51 | '@babel/code-frame@7.22.13': 52 | resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==} 53 | engines: {node: '>=6.9.0'} 54 | 55 | '@babel/helper-validator-identifier@7.22.20': 56 | resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} 57 | engines: {node: '>=6.9.0'} 58 | 59 | '@babel/highlight@7.22.20': 60 | resolution: {integrity: sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==} 61 | engines: {node: '>=6.9.0'} 62 | 63 | '@biomejs/biome@1.9.4': 64 | resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} 65 | engines: {node: '>=14.21.3'} 66 | hasBin: true 67 | 68 | '@biomejs/cli-darwin-arm64@1.9.4': 69 | resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} 70 | engines: {node: '>=14.21.3'} 71 | cpu: [arm64] 72 | os: [darwin] 73 | 74 | '@biomejs/cli-darwin-x64@1.9.4': 75 | resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} 76 | engines: {node: '>=14.21.3'} 77 | cpu: [x64] 78 | os: [darwin] 79 | 80 | '@biomejs/cli-linux-arm64-musl@1.9.4': 81 | resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} 82 | engines: {node: '>=14.21.3'} 83 | cpu: [arm64] 84 | os: [linux] 85 | 86 | '@biomejs/cli-linux-arm64@1.9.4': 87 | resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} 88 | engines: {node: '>=14.21.3'} 89 | cpu: [arm64] 90 | os: [linux] 91 | 92 | '@biomejs/cli-linux-x64-musl@1.9.4': 93 | resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} 94 | engines: {node: '>=14.21.3'} 95 | cpu: [x64] 96 | os: [linux] 97 | 98 | '@biomejs/cli-linux-x64@1.9.4': 99 | resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} 100 | engines: {node: '>=14.21.3'} 101 | cpu: [x64] 102 | os: [linux] 103 | 104 | '@biomejs/cli-win32-arm64@1.9.4': 105 | resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} 106 | engines: {node: '>=14.21.3'} 107 | cpu: [arm64] 108 | os: [win32] 109 | 110 | '@biomejs/cli-win32-x64@1.9.4': 111 | resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} 112 | engines: {node: '>=14.21.3'} 113 | cpu: [x64] 114 | os: [win32] 115 | 116 | '@colors/colors@1.5.0': 117 | resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} 118 | engines: {node: '>=0.1.90'} 119 | 120 | '@esbuild/android-arm64@0.19.5': 121 | resolution: {integrity: sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==} 122 | engines: {node: '>=12'} 123 | cpu: [arm64] 124 | os: [android] 125 | 126 | '@esbuild/android-arm@0.15.18': 127 | resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} 128 | engines: {node: '>=12'} 129 | cpu: [arm] 130 | os: [android] 131 | 132 | '@esbuild/android-arm@0.19.5': 133 | resolution: {integrity: sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==} 134 | engines: {node: '>=12'} 135 | cpu: [arm] 136 | os: [android] 137 | 138 | '@esbuild/android-x64@0.19.5': 139 | resolution: {integrity: sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==} 140 | engines: {node: '>=12'} 141 | cpu: [x64] 142 | os: [android] 143 | 144 | '@esbuild/darwin-arm64@0.19.5': 145 | resolution: {integrity: sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==} 146 | engines: {node: '>=12'} 147 | cpu: [arm64] 148 | os: [darwin] 149 | 150 | '@esbuild/darwin-x64@0.19.5': 151 | resolution: {integrity: sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==} 152 | engines: {node: '>=12'} 153 | cpu: [x64] 154 | os: [darwin] 155 | 156 | '@esbuild/freebsd-arm64@0.19.5': 157 | resolution: {integrity: sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==} 158 | engines: {node: '>=12'} 159 | cpu: [arm64] 160 | os: [freebsd] 161 | 162 | '@esbuild/freebsd-x64@0.19.5': 163 | resolution: {integrity: sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==} 164 | engines: {node: '>=12'} 165 | cpu: [x64] 166 | os: [freebsd] 167 | 168 | '@esbuild/linux-arm64@0.19.5': 169 | resolution: {integrity: sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==} 170 | engines: {node: '>=12'} 171 | cpu: [arm64] 172 | os: [linux] 173 | 174 | '@esbuild/linux-arm@0.19.5': 175 | resolution: {integrity: sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==} 176 | engines: {node: '>=12'} 177 | cpu: [arm] 178 | os: [linux] 179 | 180 | '@esbuild/linux-ia32@0.19.5': 181 | resolution: {integrity: sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==} 182 | engines: {node: '>=12'} 183 | cpu: [ia32] 184 | os: [linux] 185 | 186 | '@esbuild/linux-loong64@0.15.18': 187 | resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} 188 | engines: {node: '>=12'} 189 | cpu: [loong64] 190 | os: [linux] 191 | 192 | '@esbuild/linux-loong64@0.19.5': 193 | resolution: {integrity: sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==} 194 | engines: {node: '>=12'} 195 | cpu: [loong64] 196 | os: [linux] 197 | 198 | '@esbuild/linux-mips64el@0.19.5': 199 | resolution: {integrity: sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==} 200 | engines: {node: '>=12'} 201 | cpu: [mips64el] 202 | os: [linux] 203 | 204 | '@esbuild/linux-ppc64@0.19.5': 205 | resolution: {integrity: sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==} 206 | engines: {node: '>=12'} 207 | cpu: [ppc64] 208 | os: [linux] 209 | 210 | '@esbuild/linux-riscv64@0.19.5': 211 | resolution: {integrity: sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==} 212 | engines: {node: '>=12'} 213 | cpu: [riscv64] 214 | os: [linux] 215 | 216 | '@esbuild/linux-s390x@0.19.5': 217 | resolution: {integrity: sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==} 218 | engines: {node: '>=12'} 219 | cpu: [s390x] 220 | os: [linux] 221 | 222 | '@esbuild/linux-x64@0.19.5': 223 | resolution: {integrity: sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==} 224 | engines: {node: '>=12'} 225 | cpu: [x64] 226 | os: [linux] 227 | 228 | '@esbuild/netbsd-x64@0.19.5': 229 | resolution: {integrity: sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==} 230 | engines: {node: '>=12'} 231 | cpu: [x64] 232 | os: [netbsd] 233 | 234 | '@esbuild/openbsd-x64@0.19.5': 235 | resolution: {integrity: sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==} 236 | engines: {node: '>=12'} 237 | cpu: [x64] 238 | os: [openbsd] 239 | 240 | '@esbuild/sunos-x64@0.19.5': 241 | resolution: {integrity: sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==} 242 | engines: {node: '>=12'} 243 | cpu: [x64] 244 | os: [sunos] 245 | 246 | '@esbuild/win32-arm64@0.19.5': 247 | resolution: {integrity: sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==} 248 | engines: {node: '>=12'} 249 | cpu: [arm64] 250 | os: [win32] 251 | 252 | '@esbuild/win32-ia32@0.19.5': 253 | resolution: {integrity: sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==} 254 | engines: {node: '>=12'} 255 | cpu: [ia32] 256 | os: [win32] 257 | 258 | '@esbuild/win32-x64@0.19.5': 259 | resolution: {integrity: sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==} 260 | engines: {node: '>=12'} 261 | cpu: [x64] 262 | os: [win32] 263 | 264 | '@jest/expect-utils@29.7.0': 265 | resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} 266 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 267 | 268 | '@jest/schemas@29.6.3': 269 | resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} 270 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 271 | 272 | '@jest/types@29.6.3': 273 | resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} 274 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 275 | 276 | '@jsdevtools/ez-spawn@3.0.4': 277 | resolution: {integrity: sha512-f5DRIOZf7wxogefH03RjMPMdBF7ADTWUMoOs9kaJo06EfwF+aFhMZMDZxHg/Xe12hptN9xoZjGso2fdjapBRIA==} 278 | engines: {node: '>=10'} 279 | 280 | '@octokit/action@6.1.0': 281 | resolution: {integrity: sha512-lo+nHx8kAV86bxvOVOI3vFjX3gXPd/L7guAUbvs3pUvnR2KC+R7yjBkA1uACt4gYhs4LcWP3AXSGQzsbeN2XXw==} 282 | engines: {node: '>= 18'} 283 | 284 | '@octokit/auth-action@4.1.0': 285 | resolution: {integrity: sha512-m+3t7K46IYyMk7Bl6/lF4Rv09GqDZjYmNg8IWycJ2Fa3YE3DE7vQcV6G2hUPmR9NDqenefNJwVtlisMjzymPiQ==} 286 | engines: {node: '>= 18'} 287 | 288 | '@octokit/auth-token@4.0.0': 289 | resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} 290 | engines: {node: '>= 18'} 291 | 292 | '@octokit/core@5.2.0': 293 | resolution: {integrity: sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==} 294 | engines: {node: '>= 18'} 295 | 296 | '@octokit/endpoint@9.0.5': 297 | resolution: {integrity: sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==} 298 | engines: {node: '>= 18'} 299 | 300 | '@octokit/graphql@7.1.0': 301 | resolution: {integrity: sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==} 302 | engines: {node: '>= 18'} 303 | 304 | '@octokit/openapi-types@20.0.0': 305 | resolution: {integrity: sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==} 306 | 307 | '@octokit/openapi-types@23.0.1': 308 | resolution: {integrity: sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==} 309 | 310 | '@octokit/plugin-paginate-rest@9.2.1': 311 | resolution: {integrity: sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw==} 312 | engines: {node: '>= 18'} 313 | peerDependencies: 314 | '@octokit/core': '5' 315 | 316 | '@octokit/plugin-rest-endpoint-methods@10.4.1': 317 | resolution: {integrity: sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==} 318 | engines: {node: '>= 18'} 319 | peerDependencies: 320 | '@octokit/core': '5' 321 | 322 | '@octokit/request-error@5.1.0': 323 | resolution: {integrity: sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==} 324 | engines: {node: '>= 18'} 325 | 326 | '@octokit/request@8.4.0': 327 | resolution: {integrity: sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==} 328 | engines: {node: '>= 18'} 329 | 330 | '@octokit/types@12.6.0': 331 | resolution: {integrity: sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==} 332 | 333 | '@octokit/types@13.8.0': 334 | resolution: {integrity: sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==} 335 | 336 | '@sinclair/typebox@0.27.8': 337 | resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} 338 | 339 | '@types/istanbul-lib-coverage@2.0.5': 340 | resolution: {integrity: sha512-zONci81DZYCZjiLe0r6equvZut0b+dBRPBN5kBDjsONnutYNtJMoWQ9uR2RkL1gLG9NMTzvf+29e5RFfPbeKhQ==} 341 | 342 | '@types/istanbul-lib-report@3.0.2': 343 | resolution: {integrity: sha512-8toY6FgdltSdONav1XtUHl4LN1yTmLza+EuDazb/fEmRNCwjyqNVIQWs2IfC74IqjHkREs/nQ2FWq5kZU9IC0w==} 344 | 345 | '@types/istanbul-reports@3.0.3': 346 | resolution: {integrity: sha512-1nESsePMBlf0RPRffLZi5ujYh7IH1BWL4y9pr+Bn3cJBdxz+RTP8bUFljLz9HvzhhOSWKdyBZ4DIivdL6rvgZg==} 347 | 348 | '@types/node@20.8.7': 349 | resolution: {integrity: sha512-21TKHHh3eUHIi2MloeptJWALuCu5H7HQTdTrWIFReA8ad+aggoX+lRes3ex7/FtpC+sVUpFMQ+QTfYr74mruiQ==} 350 | 351 | '@types/stack-utils@2.0.2': 352 | resolution: {integrity: sha512-g7CK9nHdwjK2n0ymT2CW698FuWJRIx+RP6embAzZ2Qi8/ilIrA1Imt2LVSeHUzKvpoi7BhmmQcXz95eS0f2JXw==} 353 | 354 | '@types/yargs-parser@21.0.2': 355 | resolution: {integrity: sha512-5qcvofLPbfjmBfKaLfj/+f+Sbd6pN4zl7w7VSVI5uz7m9QZTuB2aZAa2uo1wHFBNN2x6g/SoTkXmd8mQnQF2Cw==} 356 | 357 | '@types/yargs@17.0.29': 358 | resolution: {integrity: sha512-nacjqA3ee9zRF/++a3FUY1suHTFKZeHba2n8WeDw9cCVdmzmHpIxyzOJBcpHvvEmS8E9KqWlSnWHUkOrkhWcvA==} 359 | 360 | acorn@8.14.0: 361 | resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} 362 | engines: {node: '>=0.4.0'} 363 | hasBin: true 364 | 365 | ansi-escapes@6.2.0: 366 | resolution: {integrity: sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==} 367 | engines: {node: '>=14.16'} 368 | 369 | ansi-regex@5.0.1: 370 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 371 | engines: {node: '>=8'} 372 | 373 | ansi-styles@3.2.1: 374 | resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} 375 | engines: {node: '>=4'} 376 | 377 | ansi-styles@4.3.0: 378 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 379 | engines: {node: '>=8'} 380 | 381 | ansi-styles@5.2.0: 382 | resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} 383 | engines: {node: '>=10'} 384 | 385 | ansicolors@0.3.2: 386 | resolution: {integrity: sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==} 387 | 388 | before-after-hook@2.2.3: 389 | resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} 390 | 391 | braces@3.0.2: 392 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 393 | engines: {node: '>=8'} 394 | 395 | builtins@5.0.1: 396 | resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} 397 | 398 | call-me-maybe@1.0.2: 399 | resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} 400 | 401 | cardinal@2.1.1: 402 | resolution: {integrity: sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==} 403 | hasBin: true 404 | 405 | chalk@2.4.2: 406 | resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} 407 | engines: {node: '>=4'} 408 | 409 | chalk@4.1.2: 410 | resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} 411 | engines: {node: '>=10'} 412 | 413 | chalk@5.3.0: 414 | resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} 415 | engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} 416 | 417 | ci-info@3.9.0: 418 | resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} 419 | engines: {node: '>=8'} 420 | 421 | cli-table3@0.6.3: 422 | resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==} 423 | engines: {node: 10.* || >= 12.*} 424 | 425 | color-convert@1.9.3: 426 | resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} 427 | 428 | color-convert@2.0.1: 429 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 430 | engines: {node: '>=7.0.0'} 431 | 432 | color-name@1.1.3: 433 | resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} 434 | 435 | color-name@1.1.4: 436 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 437 | 438 | commander@10.0.1: 439 | resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} 440 | engines: {node: '>=14'} 441 | 442 | confbox@0.1.8: 443 | resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} 444 | 445 | cross-spawn@7.0.6: 446 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 447 | engines: {node: '>= 8'} 448 | 449 | decode-uri-component@0.4.1: 450 | resolution: {integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==} 451 | engines: {node: '>=14.16'} 452 | 453 | deprecation@2.3.1: 454 | resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} 455 | 456 | diff-sequences@29.6.3: 457 | resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} 458 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 459 | 460 | emoji-regex@8.0.0: 461 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 462 | 463 | esbuild-android-64@0.15.18: 464 | resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==} 465 | engines: {node: '>=12'} 466 | cpu: [x64] 467 | os: [android] 468 | 469 | esbuild-android-arm64@0.15.18: 470 | resolution: {integrity: sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==} 471 | engines: {node: '>=12'} 472 | cpu: [arm64] 473 | os: [android] 474 | 475 | esbuild-darwin-64@0.15.18: 476 | resolution: {integrity: sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==} 477 | engines: {node: '>=12'} 478 | cpu: [x64] 479 | os: [darwin] 480 | 481 | esbuild-darwin-arm64@0.15.18: 482 | resolution: {integrity: sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==} 483 | engines: {node: '>=12'} 484 | cpu: [arm64] 485 | os: [darwin] 486 | 487 | esbuild-freebsd-64@0.15.18: 488 | resolution: {integrity: sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==} 489 | engines: {node: '>=12'} 490 | cpu: [x64] 491 | os: [freebsd] 492 | 493 | esbuild-freebsd-arm64@0.15.18: 494 | resolution: {integrity: sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==} 495 | engines: {node: '>=12'} 496 | cpu: [arm64] 497 | os: [freebsd] 498 | 499 | esbuild-linux-32@0.15.18: 500 | resolution: {integrity: sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==} 501 | engines: {node: '>=12'} 502 | cpu: [ia32] 503 | os: [linux] 504 | 505 | esbuild-linux-64@0.15.18: 506 | resolution: {integrity: sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==} 507 | engines: {node: '>=12'} 508 | cpu: [x64] 509 | os: [linux] 510 | 511 | esbuild-linux-arm64@0.15.18: 512 | resolution: {integrity: sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==} 513 | engines: {node: '>=12'} 514 | cpu: [arm64] 515 | os: [linux] 516 | 517 | esbuild-linux-arm@0.15.18: 518 | resolution: {integrity: sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==} 519 | engines: {node: '>=12'} 520 | cpu: [arm] 521 | os: [linux] 522 | 523 | esbuild-linux-mips64le@0.15.18: 524 | resolution: {integrity: sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==} 525 | engines: {node: '>=12'} 526 | cpu: [mips64el] 527 | os: [linux] 528 | 529 | esbuild-linux-ppc64le@0.15.18: 530 | resolution: {integrity: sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==} 531 | engines: {node: '>=12'} 532 | cpu: [ppc64] 533 | os: [linux] 534 | 535 | esbuild-linux-riscv64@0.15.18: 536 | resolution: {integrity: sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==} 537 | engines: {node: '>=12'} 538 | cpu: [riscv64] 539 | os: [linux] 540 | 541 | esbuild-linux-s390x@0.15.18: 542 | resolution: {integrity: sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==} 543 | engines: {node: '>=12'} 544 | cpu: [s390x] 545 | os: [linux] 546 | 547 | esbuild-netbsd-64@0.15.18: 548 | resolution: {integrity: sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==} 549 | engines: {node: '>=12'} 550 | cpu: [x64] 551 | os: [netbsd] 552 | 553 | esbuild-openbsd-64@0.15.18: 554 | resolution: {integrity: sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==} 555 | engines: {node: '>=12'} 556 | cpu: [x64] 557 | os: [openbsd] 558 | 559 | esbuild-sunos-64@0.15.18: 560 | resolution: {integrity: sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==} 561 | engines: {node: '>=12'} 562 | cpu: [x64] 563 | os: [sunos] 564 | 565 | esbuild-windows-32@0.15.18: 566 | resolution: {integrity: sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==} 567 | engines: {node: '>=12'} 568 | cpu: [ia32] 569 | os: [win32] 570 | 571 | esbuild-windows-64@0.15.18: 572 | resolution: {integrity: sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==} 573 | engines: {node: '>=12'} 574 | cpu: [x64] 575 | os: [win32] 576 | 577 | esbuild-windows-arm64@0.15.18: 578 | resolution: {integrity: sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==} 579 | engines: {node: '>=12'} 580 | cpu: [arm64] 581 | os: [win32] 582 | 583 | esbuild@0.15.18: 584 | resolution: {integrity: sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==} 585 | engines: {node: '>=12'} 586 | hasBin: true 587 | 588 | esbuild@0.19.5: 589 | resolution: {integrity: sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==} 590 | engines: {node: '>=12'} 591 | hasBin: true 592 | 593 | escape-string-regexp@1.0.5: 594 | resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} 595 | engines: {node: '>=0.8.0'} 596 | 597 | escape-string-regexp@2.0.0: 598 | resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} 599 | engines: {node: '>=8'} 600 | 601 | esprima@4.0.1: 602 | resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} 603 | engines: {node: '>=4'} 604 | hasBin: true 605 | 606 | expect@29.7.0: 607 | resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} 608 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 609 | 610 | fdir@6.4.3: 611 | resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} 612 | peerDependencies: 613 | picomatch: ^3 || ^4 614 | peerDependenciesMeta: 615 | picomatch: 616 | optional: true 617 | 618 | fetch-ponyfill@7.1.0: 619 | resolution: {integrity: sha512-FhbbL55dj/qdVO3YNK7ZEkshvj3eQ7EuIGV2I6ic/2YiocvyWv+7jg2s4AyS0wdRU75s3tA8ZxI/xPigb0v5Aw==} 620 | 621 | fflate@0.7.4: 622 | resolution: {integrity: sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==} 623 | 624 | fill-range@7.0.1: 625 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 626 | engines: {node: '>=8'} 627 | 628 | filter-obj@5.1.0: 629 | resolution: {integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==} 630 | engines: {node: '>=14.16'} 631 | 632 | graceful-fs@4.2.11: 633 | resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 634 | 635 | has-flag@3.0.0: 636 | resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} 637 | engines: {node: '>=4'} 638 | 639 | has-flag@4.0.0: 640 | resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 641 | engines: {node: '>=8'} 642 | 643 | ignore@5.3.2: 644 | resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} 645 | engines: {node: '>= 4'} 646 | 647 | is-fullwidth-code-point@3.0.0: 648 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 649 | engines: {node: '>=8'} 650 | 651 | is-number@7.0.0: 652 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 653 | engines: {node: '>=0.12.0'} 654 | 655 | isbinaryfile@5.0.4: 656 | resolution: {integrity: sha512-YKBKVkKhty7s8rxddb40oOkuP0NbaeXrQvLin6QMHL7Ypiy2RW9LwOVrVgZRyOrhQlayMd9t+D8yDy8MKFTSDQ==} 657 | engines: {node: '>= 18.0.0'} 658 | 659 | isexe@2.0.0: 660 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 661 | 662 | jest-diff@29.7.0: 663 | resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} 664 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 665 | 666 | jest-get-type@29.6.3: 667 | resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} 668 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 669 | 670 | jest-matcher-utils@29.7.0: 671 | resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} 672 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 673 | 674 | jest-message-util@29.7.0: 675 | resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} 676 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 677 | 678 | jest-util@29.7.0: 679 | resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} 680 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 681 | 682 | js-tokens@4.0.0: 683 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 684 | 685 | lodash@4.17.21: 686 | resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 687 | 688 | lru-cache@6.0.0: 689 | resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 690 | engines: {node: '>=10'} 691 | 692 | marked-terminal@5.2.0: 693 | resolution: {integrity: sha512-Piv6yNwAQXGFjZSaiNljyNFw7jKDdGrw70FSbtxEyldLsyeuV5ZHm/1wW++kWbrOF1VPnUgYOhB2oLL0ZpnekA==} 694 | engines: {node: '>=14.13.1 || >=16.0.0'} 695 | peerDependencies: 696 | marked: ^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 697 | 698 | marked@5.1.2: 699 | resolution: {integrity: sha512-ahRPGXJpjMjwSOlBoTMZAK7ATXkli5qCPxZ21TG44rx1KEo44bii4ekgTDQPNRQ4Kh7JMb9Ub1PVk1NxRSsorg==} 700 | engines: {node: '>= 16'} 701 | hasBin: true 702 | 703 | micromatch@4.0.5: 704 | resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} 705 | engines: {node: '>=8.6'} 706 | 707 | mitata@1.0.33: 708 | resolution: {integrity: sha512-ZRbHD4ZGAbC1B9SYCZXjLox2scPCauhTPkXGk2o7CGj/wNeBNjagwFutphDCgJNbEF80fyMBcPdkfr+WFC9cHw==} 709 | 710 | mlly@1.7.4: 711 | resolution: {integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==} 712 | 713 | node-emoji@1.11.0: 714 | resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} 715 | 716 | node-fetch@2.6.13: 717 | resolution: {integrity: sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==} 718 | engines: {node: 4.x || >=6.0.0} 719 | peerDependencies: 720 | encoding: ^0.1.0 721 | peerDependenciesMeta: 722 | encoding: 723 | optional: true 724 | 725 | node-fetch@2.7.0: 726 | resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} 727 | engines: {node: 4.x || >=6.0.0} 728 | peerDependencies: 729 | encoding: ^0.1.0 730 | peerDependenciesMeta: 731 | encoding: 732 | optional: true 733 | 734 | once@1.4.0: 735 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 736 | 737 | path-key@3.1.1: 738 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 739 | engines: {node: '>=8'} 740 | 741 | pathe@2.0.2: 742 | resolution: {integrity: sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==} 743 | 744 | picomatch@2.3.1: 745 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 746 | engines: {node: '>=8.6'} 747 | 748 | picomatch@4.0.2: 749 | resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} 750 | engines: {node: '>=12'} 751 | 752 | pkg-pr-new@0.0.39: 753 | resolution: {integrity: sha512-ItcsHK+4uO0qmjK4RNs6vTWv3xFhbPZd5U6RoYbxRURWNZfH7KvpyqRzaw4GR7de/IjkdHVZHCzQkjnp3VOVdg==} 754 | hasBin: true 755 | 756 | pkg-types@1.3.1: 757 | resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} 758 | 759 | pretty-format@29.7.0: 760 | resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} 761 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 762 | 763 | query-registry@3.0.1: 764 | resolution: {integrity: sha512-M9RxRITi2mHMVPU5zysNjctUT8bAPx6ltEXo/ir9+qmiM47Y7f0Ir3+OxUO5OjYAWdicBQRew7RtHtqUXydqlg==} 765 | engines: {node: '>=20'} 766 | 767 | query-string@9.1.1: 768 | resolution: {integrity: sha512-MWkCOVIcJP9QSKU52Ngow6bsAWAPlPK2MludXvcrS2bGZSl+T1qX9MZvRIkqUIkGLJquMJHWfsT6eRqUpp4aWg==} 769 | engines: {node: '>=18'} 770 | 771 | quick-lru@7.0.0: 772 | resolution: {integrity: sha512-MX8gB7cVYTrYcFfAnfLlhRd0+Toyl8yX8uBx1MrX7K0jegiz9TumwOK27ldXrgDlHRdVi+MqU9Ssw6dr4BNreg==} 773 | engines: {node: '>=18'} 774 | 775 | react-is@18.2.0: 776 | resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} 777 | 778 | redeyed@2.1.1: 779 | resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==} 780 | 781 | semver@7.5.4: 782 | resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} 783 | engines: {node: '>=10'} 784 | hasBin: true 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 | slash@3.0.0: 795 | resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 796 | engines: {node: '>=8'} 797 | 798 | split-on-first@3.0.0: 799 | resolution: {integrity: sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==} 800 | engines: {node: '>=12'} 801 | 802 | stack-utils@2.0.6: 803 | resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} 804 | engines: {node: '>=10'} 805 | 806 | string-argv@0.3.2: 807 | resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} 808 | engines: {node: '>=0.6.19'} 809 | 810 | string-width@4.2.3: 811 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 812 | engines: {node: '>=8'} 813 | 814 | strip-ansi@6.0.1: 815 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 816 | engines: {node: '>=8'} 817 | 818 | supports-color@5.5.0: 819 | resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} 820 | engines: {node: '>=4'} 821 | 822 | supports-color@7.2.0: 823 | resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 824 | engines: {node: '>=8'} 825 | 826 | supports-hyperlinks@2.3.0: 827 | resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} 828 | engines: {node: '>=8'} 829 | 830 | tinyglobby@0.2.10: 831 | resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} 832 | engines: {node: '>=12.0.0'} 833 | 834 | to-regex-range@5.0.1: 835 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 836 | engines: {node: '>=8.0'} 837 | 838 | tr46@0.0.3: 839 | resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} 840 | 841 | tsm@2.3.0: 842 | resolution: {integrity: sha512-++0HFnmmR+gMpDtKTnW3XJ4yv9kVGi20n+NfyQWB9qwJvTaIWY9kBmzek2YUQK5APTQ/1DTrXmm4QtFPmW9Rzw==} 843 | engines: {node: '>=12'} 844 | hasBin: true 845 | 846 | type-detect@4.1.0: 847 | resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} 848 | engines: {node: '>=4'} 849 | 850 | type-fest@3.13.1: 851 | resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} 852 | engines: {node: '>=14.16'} 853 | 854 | typescript@5.2.2: 855 | resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} 856 | engines: {node: '>=14.17'} 857 | hasBin: true 858 | 859 | ufo@1.5.4: 860 | resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} 861 | 862 | undici-types@5.25.3: 863 | resolution: {integrity: sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==} 864 | 865 | undici@6.21.1: 866 | resolution: {integrity: sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==} 867 | engines: {node: '>=18.17'} 868 | 869 | universal-user-agent@6.0.1: 870 | resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} 871 | 872 | url-join@5.0.0: 873 | resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==} 874 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 875 | 876 | validate-npm-package-name@5.0.0: 877 | resolution: {integrity: sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==} 878 | engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} 879 | 880 | validate-npm-package-name@5.0.1: 881 | resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} 882 | engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} 883 | 884 | webidl-conversions@3.0.1: 885 | resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} 886 | 887 | whatwg-url@5.0.0: 888 | resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} 889 | 890 | which@2.0.2: 891 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 892 | engines: {node: '>= 8'} 893 | hasBin: true 894 | 895 | wrappy@1.0.2: 896 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 897 | 898 | yallist@4.0.0: 899 | resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 900 | 901 | zod-package-json@1.1.0: 902 | resolution: {integrity: sha512-RvEsa3W/NCqEBMtnoE09GRVelA3IqRcKaijEiM6CEGsD19qLurf0HjrYMHwOqImOszlLL0ja63DPJeeU4pm7oQ==} 903 | engines: {node: '>=20'} 904 | 905 | zod@3.24.1: 906 | resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} 907 | 908 | snapshots: 909 | 910 | '@andrewbranch/untar.js@1.0.3': {} 911 | 912 | '@arethetypeswrong/cli@0.12.2': 913 | dependencies: 914 | '@arethetypeswrong/core': 0.12.2 915 | chalk: 4.1.2 916 | cli-table3: 0.6.3 917 | commander: 10.0.1 918 | marked: 5.1.2 919 | marked-terminal: 5.2.0(marked@5.1.2) 920 | node-fetch: 2.7.0 921 | semver: 7.5.4 922 | transitivePeerDependencies: 923 | - encoding 924 | 925 | '@arethetypeswrong/core@0.12.2': 926 | dependencies: 927 | '@andrewbranch/untar.js': 1.0.3 928 | fetch-ponyfill: 7.1.0 929 | fflate: 0.7.4 930 | semver: 7.5.4 931 | typescript: 5.2.2 932 | validate-npm-package-name: 5.0.0 933 | transitivePeerDependencies: 934 | - encoding 935 | 936 | '@babel/code-frame@7.22.13': 937 | dependencies: 938 | '@babel/highlight': 7.22.20 939 | chalk: 2.4.2 940 | 941 | '@babel/helper-validator-identifier@7.22.20': {} 942 | 943 | '@babel/highlight@7.22.20': 944 | dependencies: 945 | '@babel/helper-validator-identifier': 7.22.20 946 | chalk: 2.4.2 947 | js-tokens: 4.0.0 948 | 949 | '@biomejs/biome@1.9.4': 950 | optionalDependencies: 951 | '@biomejs/cli-darwin-arm64': 1.9.4 952 | '@biomejs/cli-darwin-x64': 1.9.4 953 | '@biomejs/cli-linux-arm64': 1.9.4 954 | '@biomejs/cli-linux-arm64-musl': 1.9.4 955 | '@biomejs/cli-linux-x64': 1.9.4 956 | '@biomejs/cli-linux-x64-musl': 1.9.4 957 | '@biomejs/cli-win32-arm64': 1.9.4 958 | '@biomejs/cli-win32-x64': 1.9.4 959 | 960 | '@biomejs/cli-darwin-arm64@1.9.4': 961 | optional: true 962 | 963 | '@biomejs/cli-darwin-x64@1.9.4': 964 | optional: true 965 | 966 | '@biomejs/cli-linux-arm64-musl@1.9.4': 967 | optional: true 968 | 969 | '@biomejs/cli-linux-arm64@1.9.4': 970 | optional: true 971 | 972 | '@biomejs/cli-linux-x64-musl@1.9.4': 973 | optional: true 974 | 975 | '@biomejs/cli-linux-x64@1.9.4': 976 | optional: true 977 | 978 | '@biomejs/cli-win32-arm64@1.9.4': 979 | optional: true 980 | 981 | '@biomejs/cli-win32-x64@1.9.4': 982 | optional: true 983 | 984 | '@colors/colors@1.5.0': 985 | optional: true 986 | 987 | '@esbuild/android-arm64@0.19.5': 988 | optional: true 989 | 990 | '@esbuild/android-arm@0.15.18': 991 | optional: true 992 | 993 | '@esbuild/android-arm@0.19.5': 994 | optional: true 995 | 996 | '@esbuild/android-x64@0.19.5': 997 | optional: true 998 | 999 | '@esbuild/darwin-arm64@0.19.5': 1000 | optional: true 1001 | 1002 | '@esbuild/darwin-x64@0.19.5': 1003 | optional: true 1004 | 1005 | '@esbuild/freebsd-arm64@0.19.5': 1006 | optional: true 1007 | 1008 | '@esbuild/freebsd-x64@0.19.5': 1009 | optional: true 1010 | 1011 | '@esbuild/linux-arm64@0.19.5': 1012 | optional: true 1013 | 1014 | '@esbuild/linux-arm@0.19.5': 1015 | optional: true 1016 | 1017 | '@esbuild/linux-ia32@0.19.5': 1018 | optional: true 1019 | 1020 | '@esbuild/linux-loong64@0.15.18': 1021 | optional: true 1022 | 1023 | '@esbuild/linux-loong64@0.19.5': 1024 | optional: true 1025 | 1026 | '@esbuild/linux-mips64el@0.19.5': 1027 | optional: true 1028 | 1029 | '@esbuild/linux-ppc64@0.19.5': 1030 | optional: true 1031 | 1032 | '@esbuild/linux-riscv64@0.19.5': 1033 | optional: true 1034 | 1035 | '@esbuild/linux-s390x@0.19.5': 1036 | optional: true 1037 | 1038 | '@esbuild/linux-x64@0.19.5': 1039 | optional: true 1040 | 1041 | '@esbuild/netbsd-x64@0.19.5': 1042 | optional: true 1043 | 1044 | '@esbuild/openbsd-x64@0.19.5': 1045 | optional: true 1046 | 1047 | '@esbuild/sunos-x64@0.19.5': 1048 | optional: true 1049 | 1050 | '@esbuild/win32-arm64@0.19.5': 1051 | optional: true 1052 | 1053 | '@esbuild/win32-ia32@0.19.5': 1054 | optional: true 1055 | 1056 | '@esbuild/win32-x64@0.19.5': 1057 | optional: true 1058 | 1059 | '@jest/expect-utils@29.7.0': 1060 | dependencies: 1061 | jest-get-type: 29.6.3 1062 | 1063 | '@jest/schemas@29.6.3': 1064 | dependencies: 1065 | '@sinclair/typebox': 0.27.8 1066 | 1067 | '@jest/types@29.6.3': 1068 | dependencies: 1069 | '@jest/schemas': 29.6.3 1070 | '@types/istanbul-lib-coverage': 2.0.5 1071 | '@types/istanbul-reports': 3.0.3 1072 | '@types/node': 20.8.7 1073 | '@types/yargs': 17.0.29 1074 | chalk: 4.1.2 1075 | 1076 | '@jsdevtools/ez-spawn@3.0.4': 1077 | dependencies: 1078 | call-me-maybe: 1.0.2 1079 | cross-spawn: 7.0.6 1080 | string-argv: 0.3.2 1081 | type-detect: 4.1.0 1082 | 1083 | '@octokit/action@6.1.0': 1084 | dependencies: 1085 | '@octokit/auth-action': 4.1.0 1086 | '@octokit/core': 5.2.0 1087 | '@octokit/plugin-paginate-rest': 9.2.1(@octokit/core@5.2.0) 1088 | '@octokit/plugin-rest-endpoint-methods': 10.4.1(@octokit/core@5.2.0) 1089 | '@octokit/types': 12.6.0 1090 | undici: 6.21.1 1091 | 1092 | '@octokit/auth-action@4.1.0': 1093 | dependencies: 1094 | '@octokit/auth-token': 4.0.0 1095 | '@octokit/types': 13.8.0 1096 | 1097 | '@octokit/auth-token@4.0.0': {} 1098 | 1099 | '@octokit/core@5.2.0': 1100 | dependencies: 1101 | '@octokit/auth-token': 4.0.0 1102 | '@octokit/graphql': 7.1.0 1103 | '@octokit/request': 8.4.0 1104 | '@octokit/request-error': 5.1.0 1105 | '@octokit/types': 13.8.0 1106 | before-after-hook: 2.2.3 1107 | universal-user-agent: 6.0.1 1108 | 1109 | '@octokit/endpoint@9.0.5': 1110 | dependencies: 1111 | '@octokit/types': 13.8.0 1112 | universal-user-agent: 6.0.1 1113 | 1114 | '@octokit/graphql@7.1.0': 1115 | dependencies: 1116 | '@octokit/request': 8.4.0 1117 | '@octokit/types': 13.8.0 1118 | universal-user-agent: 6.0.1 1119 | 1120 | '@octokit/openapi-types@20.0.0': {} 1121 | 1122 | '@octokit/openapi-types@23.0.1': {} 1123 | 1124 | '@octokit/plugin-paginate-rest@9.2.1(@octokit/core@5.2.0)': 1125 | dependencies: 1126 | '@octokit/core': 5.2.0 1127 | '@octokit/types': 12.6.0 1128 | 1129 | '@octokit/plugin-rest-endpoint-methods@10.4.1(@octokit/core@5.2.0)': 1130 | dependencies: 1131 | '@octokit/core': 5.2.0 1132 | '@octokit/types': 12.6.0 1133 | 1134 | '@octokit/request-error@5.1.0': 1135 | dependencies: 1136 | '@octokit/types': 13.8.0 1137 | deprecation: 2.3.1 1138 | once: 1.4.0 1139 | 1140 | '@octokit/request@8.4.0': 1141 | dependencies: 1142 | '@octokit/endpoint': 9.0.5 1143 | '@octokit/request-error': 5.1.0 1144 | '@octokit/types': 13.8.0 1145 | universal-user-agent: 6.0.1 1146 | 1147 | '@octokit/types@12.6.0': 1148 | dependencies: 1149 | '@octokit/openapi-types': 20.0.0 1150 | 1151 | '@octokit/types@13.8.0': 1152 | dependencies: 1153 | '@octokit/openapi-types': 23.0.1 1154 | 1155 | '@sinclair/typebox@0.27.8': {} 1156 | 1157 | '@types/istanbul-lib-coverage@2.0.5': {} 1158 | 1159 | '@types/istanbul-lib-report@3.0.2': 1160 | dependencies: 1161 | '@types/istanbul-lib-coverage': 2.0.5 1162 | 1163 | '@types/istanbul-reports@3.0.3': 1164 | dependencies: 1165 | '@types/istanbul-lib-report': 3.0.2 1166 | 1167 | '@types/node@20.8.7': 1168 | dependencies: 1169 | undici-types: 5.25.3 1170 | 1171 | '@types/stack-utils@2.0.2': {} 1172 | 1173 | '@types/yargs-parser@21.0.2': {} 1174 | 1175 | '@types/yargs@17.0.29': 1176 | dependencies: 1177 | '@types/yargs-parser': 21.0.2 1178 | 1179 | acorn@8.14.0: {} 1180 | 1181 | ansi-escapes@6.2.0: 1182 | dependencies: 1183 | type-fest: 3.13.1 1184 | 1185 | ansi-regex@5.0.1: {} 1186 | 1187 | ansi-styles@3.2.1: 1188 | dependencies: 1189 | color-convert: 1.9.3 1190 | 1191 | ansi-styles@4.3.0: 1192 | dependencies: 1193 | color-convert: 2.0.1 1194 | 1195 | ansi-styles@5.2.0: {} 1196 | 1197 | ansicolors@0.3.2: {} 1198 | 1199 | before-after-hook@2.2.3: {} 1200 | 1201 | braces@3.0.2: 1202 | dependencies: 1203 | fill-range: 7.0.1 1204 | 1205 | builtins@5.0.1: 1206 | dependencies: 1207 | semver: 7.5.4 1208 | 1209 | call-me-maybe@1.0.2: {} 1210 | 1211 | cardinal@2.1.1: 1212 | dependencies: 1213 | ansicolors: 0.3.2 1214 | redeyed: 2.1.1 1215 | 1216 | chalk@2.4.2: 1217 | dependencies: 1218 | ansi-styles: 3.2.1 1219 | escape-string-regexp: 1.0.5 1220 | supports-color: 5.5.0 1221 | 1222 | chalk@4.1.2: 1223 | dependencies: 1224 | ansi-styles: 4.3.0 1225 | supports-color: 7.2.0 1226 | 1227 | chalk@5.3.0: {} 1228 | 1229 | ci-info@3.9.0: {} 1230 | 1231 | cli-table3@0.6.3: 1232 | dependencies: 1233 | string-width: 4.2.3 1234 | optionalDependencies: 1235 | '@colors/colors': 1.5.0 1236 | 1237 | color-convert@1.9.3: 1238 | dependencies: 1239 | color-name: 1.1.3 1240 | 1241 | color-convert@2.0.1: 1242 | dependencies: 1243 | color-name: 1.1.4 1244 | 1245 | color-name@1.1.3: {} 1246 | 1247 | color-name@1.1.4: {} 1248 | 1249 | commander@10.0.1: {} 1250 | 1251 | confbox@0.1.8: {} 1252 | 1253 | cross-spawn@7.0.6: 1254 | dependencies: 1255 | path-key: 3.1.1 1256 | shebang-command: 2.0.0 1257 | which: 2.0.2 1258 | 1259 | decode-uri-component@0.4.1: {} 1260 | 1261 | deprecation@2.3.1: {} 1262 | 1263 | diff-sequences@29.6.3: {} 1264 | 1265 | emoji-regex@8.0.0: {} 1266 | 1267 | esbuild-android-64@0.15.18: 1268 | optional: true 1269 | 1270 | esbuild-android-arm64@0.15.18: 1271 | optional: true 1272 | 1273 | esbuild-darwin-64@0.15.18: 1274 | optional: true 1275 | 1276 | esbuild-darwin-arm64@0.15.18: 1277 | optional: true 1278 | 1279 | esbuild-freebsd-64@0.15.18: 1280 | optional: true 1281 | 1282 | esbuild-freebsd-arm64@0.15.18: 1283 | optional: true 1284 | 1285 | esbuild-linux-32@0.15.18: 1286 | optional: true 1287 | 1288 | esbuild-linux-64@0.15.18: 1289 | optional: true 1290 | 1291 | esbuild-linux-arm64@0.15.18: 1292 | optional: true 1293 | 1294 | esbuild-linux-arm@0.15.18: 1295 | optional: true 1296 | 1297 | esbuild-linux-mips64le@0.15.18: 1298 | optional: true 1299 | 1300 | esbuild-linux-ppc64le@0.15.18: 1301 | optional: true 1302 | 1303 | esbuild-linux-riscv64@0.15.18: 1304 | optional: true 1305 | 1306 | esbuild-linux-s390x@0.15.18: 1307 | optional: true 1308 | 1309 | esbuild-netbsd-64@0.15.18: 1310 | optional: true 1311 | 1312 | esbuild-openbsd-64@0.15.18: 1313 | optional: true 1314 | 1315 | esbuild-sunos-64@0.15.18: 1316 | optional: true 1317 | 1318 | esbuild-windows-32@0.15.18: 1319 | optional: true 1320 | 1321 | esbuild-windows-64@0.15.18: 1322 | optional: true 1323 | 1324 | esbuild-windows-arm64@0.15.18: 1325 | optional: true 1326 | 1327 | esbuild@0.15.18: 1328 | optionalDependencies: 1329 | '@esbuild/android-arm': 0.15.18 1330 | '@esbuild/linux-loong64': 0.15.18 1331 | esbuild-android-64: 0.15.18 1332 | esbuild-android-arm64: 0.15.18 1333 | esbuild-darwin-64: 0.15.18 1334 | esbuild-darwin-arm64: 0.15.18 1335 | esbuild-freebsd-64: 0.15.18 1336 | esbuild-freebsd-arm64: 0.15.18 1337 | esbuild-linux-32: 0.15.18 1338 | esbuild-linux-64: 0.15.18 1339 | esbuild-linux-arm: 0.15.18 1340 | esbuild-linux-arm64: 0.15.18 1341 | esbuild-linux-mips64le: 0.15.18 1342 | esbuild-linux-ppc64le: 0.15.18 1343 | esbuild-linux-riscv64: 0.15.18 1344 | esbuild-linux-s390x: 0.15.18 1345 | esbuild-netbsd-64: 0.15.18 1346 | esbuild-openbsd-64: 0.15.18 1347 | esbuild-sunos-64: 0.15.18 1348 | esbuild-windows-32: 0.15.18 1349 | esbuild-windows-64: 0.15.18 1350 | esbuild-windows-arm64: 0.15.18 1351 | 1352 | esbuild@0.19.5: 1353 | optionalDependencies: 1354 | '@esbuild/android-arm': 0.19.5 1355 | '@esbuild/android-arm64': 0.19.5 1356 | '@esbuild/android-x64': 0.19.5 1357 | '@esbuild/darwin-arm64': 0.19.5 1358 | '@esbuild/darwin-x64': 0.19.5 1359 | '@esbuild/freebsd-arm64': 0.19.5 1360 | '@esbuild/freebsd-x64': 0.19.5 1361 | '@esbuild/linux-arm': 0.19.5 1362 | '@esbuild/linux-arm64': 0.19.5 1363 | '@esbuild/linux-ia32': 0.19.5 1364 | '@esbuild/linux-loong64': 0.19.5 1365 | '@esbuild/linux-mips64el': 0.19.5 1366 | '@esbuild/linux-ppc64': 0.19.5 1367 | '@esbuild/linux-riscv64': 0.19.5 1368 | '@esbuild/linux-s390x': 0.19.5 1369 | '@esbuild/linux-x64': 0.19.5 1370 | '@esbuild/netbsd-x64': 0.19.5 1371 | '@esbuild/openbsd-x64': 0.19.5 1372 | '@esbuild/sunos-x64': 0.19.5 1373 | '@esbuild/win32-arm64': 0.19.5 1374 | '@esbuild/win32-ia32': 0.19.5 1375 | '@esbuild/win32-x64': 0.19.5 1376 | 1377 | escape-string-regexp@1.0.5: {} 1378 | 1379 | escape-string-regexp@2.0.0: {} 1380 | 1381 | esprima@4.0.1: {} 1382 | 1383 | expect@29.7.0: 1384 | dependencies: 1385 | '@jest/expect-utils': 29.7.0 1386 | jest-get-type: 29.6.3 1387 | jest-matcher-utils: 29.7.0 1388 | jest-message-util: 29.7.0 1389 | jest-util: 29.7.0 1390 | 1391 | fdir@6.4.3(picomatch@4.0.2): 1392 | optionalDependencies: 1393 | picomatch: 4.0.2 1394 | 1395 | fetch-ponyfill@7.1.0: 1396 | dependencies: 1397 | node-fetch: 2.6.13 1398 | transitivePeerDependencies: 1399 | - encoding 1400 | 1401 | fflate@0.7.4: {} 1402 | 1403 | fill-range@7.0.1: 1404 | dependencies: 1405 | to-regex-range: 5.0.1 1406 | 1407 | filter-obj@5.1.0: {} 1408 | 1409 | graceful-fs@4.2.11: {} 1410 | 1411 | has-flag@3.0.0: {} 1412 | 1413 | has-flag@4.0.0: {} 1414 | 1415 | ignore@5.3.2: {} 1416 | 1417 | is-fullwidth-code-point@3.0.0: {} 1418 | 1419 | is-number@7.0.0: {} 1420 | 1421 | isbinaryfile@5.0.4: {} 1422 | 1423 | isexe@2.0.0: {} 1424 | 1425 | jest-diff@29.7.0: 1426 | dependencies: 1427 | chalk: 4.1.2 1428 | diff-sequences: 29.6.3 1429 | jest-get-type: 29.6.3 1430 | pretty-format: 29.7.0 1431 | 1432 | jest-get-type@29.6.3: {} 1433 | 1434 | jest-matcher-utils@29.7.0: 1435 | dependencies: 1436 | chalk: 4.1.2 1437 | jest-diff: 29.7.0 1438 | jest-get-type: 29.6.3 1439 | pretty-format: 29.7.0 1440 | 1441 | jest-message-util@29.7.0: 1442 | dependencies: 1443 | '@babel/code-frame': 7.22.13 1444 | '@jest/types': 29.6.3 1445 | '@types/stack-utils': 2.0.2 1446 | chalk: 4.1.2 1447 | graceful-fs: 4.2.11 1448 | micromatch: 4.0.5 1449 | pretty-format: 29.7.0 1450 | slash: 3.0.0 1451 | stack-utils: 2.0.6 1452 | 1453 | jest-util@29.7.0: 1454 | dependencies: 1455 | '@jest/types': 29.6.3 1456 | '@types/node': 20.8.7 1457 | chalk: 4.1.2 1458 | ci-info: 3.9.0 1459 | graceful-fs: 4.2.11 1460 | picomatch: 2.3.1 1461 | 1462 | js-tokens@4.0.0: {} 1463 | 1464 | lodash@4.17.21: {} 1465 | 1466 | lru-cache@6.0.0: 1467 | dependencies: 1468 | yallist: 4.0.0 1469 | 1470 | marked-terminal@5.2.0(marked@5.1.2): 1471 | dependencies: 1472 | ansi-escapes: 6.2.0 1473 | cardinal: 2.1.1 1474 | chalk: 5.3.0 1475 | cli-table3: 0.6.3 1476 | marked: 5.1.2 1477 | node-emoji: 1.11.0 1478 | supports-hyperlinks: 2.3.0 1479 | 1480 | marked@5.1.2: {} 1481 | 1482 | micromatch@4.0.5: 1483 | dependencies: 1484 | braces: 3.0.2 1485 | picomatch: 2.3.1 1486 | 1487 | mitata@1.0.33: {} 1488 | 1489 | mlly@1.7.4: 1490 | dependencies: 1491 | acorn: 8.14.0 1492 | pathe: 2.0.2 1493 | pkg-types: 1.3.1 1494 | ufo: 1.5.4 1495 | 1496 | node-emoji@1.11.0: 1497 | dependencies: 1498 | lodash: 4.17.21 1499 | 1500 | node-fetch@2.6.13: 1501 | dependencies: 1502 | whatwg-url: 5.0.0 1503 | 1504 | node-fetch@2.7.0: 1505 | dependencies: 1506 | whatwg-url: 5.0.0 1507 | 1508 | once@1.4.0: 1509 | dependencies: 1510 | wrappy: 1.0.2 1511 | 1512 | path-key@3.1.1: {} 1513 | 1514 | pathe@2.0.2: {} 1515 | 1516 | picomatch@2.3.1: {} 1517 | 1518 | picomatch@4.0.2: {} 1519 | 1520 | pkg-pr-new@0.0.39: 1521 | dependencies: 1522 | '@jsdevtools/ez-spawn': 3.0.4 1523 | '@octokit/action': 6.1.0 1524 | ignore: 5.3.2 1525 | isbinaryfile: 5.0.4 1526 | pkg-types: 1.3.1 1527 | query-registry: 3.0.1 1528 | tinyglobby: 0.2.10 1529 | 1530 | pkg-types@1.3.1: 1531 | dependencies: 1532 | confbox: 0.1.8 1533 | mlly: 1.7.4 1534 | pathe: 2.0.2 1535 | 1536 | pretty-format@29.7.0: 1537 | dependencies: 1538 | '@jest/schemas': 29.6.3 1539 | ansi-styles: 5.2.0 1540 | react-is: 18.2.0 1541 | 1542 | query-registry@3.0.1: 1543 | dependencies: 1544 | query-string: 9.1.1 1545 | quick-lru: 7.0.0 1546 | url-join: 5.0.0 1547 | validate-npm-package-name: 5.0.1 1548 | zod: 3.24.1 1549 | zod-package-json: 1.1.0 1550 | 1551 | query-string@9.1.1: 1552 | dependencies: 1553 | decode-uri-component: 0.4.1 1554 | filter-obj: 5.1.0 1555 | split-on-first: 3.0.0 1556 | 1557 | quick-lru@7.0.0: {} 1558 | 1559 | react-is@18.2.0: {} 1560 | 1561 | redeyed@2.1.1: 1562 | dependencies: 1563 | esprima: 4.0.1 1564 | 1565 | semver@7.5.4: 1566 | dependencies: 1567 | lru-cache: 6.0.0 1568 | 1569 | shebang-command@2.0.0: 1570 | dependencies: 1571 | shebang-regex: 3.0.0 1572 | 1573 | shebang-regex@3.0.0: {} 1574 | 1575 | slash@3.0.0: {} 1576 | 1577 | split-on-first@3.0.0: {} 1578 | 1579 | stack-utils@2.0.6: 1580 | dependencies: 1581 | escape-string-regexp: 2.0.0 1582 | 1583 | string-argv@0.3.2: {} 1584 | 1585 | string-width@4.2.3: 1586 | dependencies: 1587 | emoji-regex: 8.0.0 1588 | is-fullwidth-code-point: 3.0.0 1589 | strip-ansi: 6.0.1 1590 | 1591 | strip-ansi@6.0.1: 1592 | dependencies: 1593 | ansi-regex: 5.0.1 1594 | 1595 | supports-color@5.5.0: 1596 | dependencies: 1597 | has-flag: 3.0.0 1598 | 1599 | supports-color@7.2.0: 1600 | dependencies: 1601 | has-flag: 4.0.0 1602 | 1603 | supports-hyperlinks@2.3.0: 1604 | dependencies: 1605 | has-flag: 4.0.0 1606 | supports-color: 7.2.0 1607 | 1608 | tinyglobby@0.2.10: 1609 | dependencies: 1610 | fdir: 6.4.3(picomatch@4.0.2) 1611 | picomatch: 4.0.2 1612 | 1613 | to-regex-range@5.0.1: 1614 | dependencies: 1615 | is-number: 7.0.0 1616 | 1617 | tr46@0.0.3: {} 1618 | 1619 | tsm@2.3.0: 1620 | dependencies: 1621 | esbuild: 0.15.18 1622 | 1623 | type-detect@4.1.0: {} 1624 | 1625 | type-fest@3.13.1: {} 1626 | 1627 | typescript@5.2.2: {} 1628 | 1629 | ufo@1.5.4: {} 1630 | 1631 | undici-types@5.25.3: {} 1632 | 1633 | undici@6.21.1: {} 1634 | 1635 | universal-user-agent@6.0.1: {} 1636 | 1637 | url-join@5.0.0: {} 1638 | 1639 | validate-npm-package-name@5.0.0: 1640 | dependencies: 1641 | builtins: 5.0.1 1642 | 1643 | validate-npm-package-name@5.0.1: {} 1644 | 1645 | webidl-conversions@3.0.1: {} 1646 | 1647 | whatwg-url@5.0.0: 1648 | dependencies: 1649 | tr46: 0.0.3 1650 | webidl-conversions: 3.0.1 1651 | 1652 | which@2.0.2: 1653 | dependencies: 1654 | isexe: 2.0.0 1655 | 1656 | wrappy@1.0.2: {} 1657 | 1658 | yallist@4.0.0: {} 1659 | 1660 | zod-package-json@1.1.0: 1661 | dependencies: 1662 | zod: 3.24.1 1663 | 1664 | zod@3.24.1: {} 1665 | --------------------------------------------------------------------------------