From e304c6bc00f6ece968d503bdf987d80dbd564b80 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 16 May 2026 09:23:37 +0000 Subject: [PATCH 1/8] rewrite in TypeScript with modern API, tooling, and CI - typed codec tokens (`t.bool`, `t.u32`, `t.string`, `t.le.*`, `t.i64/u64`) - `struct(schema)` factory + functional `encode` / `decode` - `Infer` derives the TS shape of decoded values from a schema - two-pass writer: measure once, allocate once, no `Buffer.concat` - `DataStructError` with codes, field path, and decode offset - dual ESM + CJS build via tsup (`.mjs` / `.cjs` / `.d.ts` / `.d.cts`) - vitest suite (wire-format goldens, roundtrip, error paths) with v8 coverage - tinybench benchmark suite - biome for lint + format - GitHub Actions CI matrix (Node 20/22/24 on linux + macOS/Windows on 22), on-demand benchmark workflow, tag-triggered release with npm provenance - dependabot, PR template, CODEOWNERS Wire format is byte-identical to 0.0.x for the corresponding new codecs; legacy DataTypes/DataReader/DataWriter exports are removed (clean break). --- .editorconfig | 12 + .github/CODEOWNERS | 1 + .github/dependabot.yml | 26 + .github/pull_request_template.md | 18 + .github/workflows/benchmark.yml | 40 + .github/workflows/ci.yml | 88 + .github/workflows/release.yml | 46 + .gitignore | 12 +- .npmrc | 2 + .nvmrc | 1 + CHANGELOG.md | 38 + README.md | 243 +- benchmark/benchmark.js | 52 - benchmark/benchmark.ts | 92 + biome.json | 52 + gruntfile.js | 36 - package-lock.json | 3927 ++++++++++++++++++++++++++++++ package.json | 74 +- src/codecs.ts | 334 +++ src/dataReader.js | 128 - src/dataTypes.js | 15 - src/dataWriter.js | 141 -- src/errors.ts | 28 + src/index.js | 3 - src/index.ts | 7 + src/struct.ts | 184 ++ src/types.ts | 45 + test/errors.test.ts | 116 + test/roundtrip.test.ts | 105 + test/tests.js | 219 -- test/wire-format.test.ts | 111 + tsconfig.build.json | 11 + tsconfig.json | 23 + tsup.config.ts | 15 + vitest.config.ts | 20 + 35 files changed, 5569 insertions(+), 696 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/CODEOWNERS create mode 100644 .github/dependabot.yml create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/benchmark.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 .npmrc create mode 100644 .nvmrc create mode 100644 CHANGELOG.md delete mode 100644 benchmark/benchmark.js create mode 100644 benchmark/benchmark.ts create mode 100644 biome.json delete mode 100644 gruntfile.js create mode 100644 package-lock.json create mode 100644 src/codecs.ts delete mode 100644 src/dataReader.js delete mode 100644 src/dataTypes.js delete mode 100644 src/dataWriter.js create mode 100644 src/errors.ts delete mode 100644 src/index.js create mode 100644 src/index.ts create mode 100644 src/struct.ts create mode 100644 src/types.ts create mode 100644 test/errors.test.ts create mode 100644 test/roundtrip.test.ts delete mode 100644 test/tests.js create mode 100644 test/wire-format.test.ts create mode 100644 tsconfig.build.json create mode 100644 tsconfig.json create mode 100644 tsup.config.ts create mode 100644 vitest.config.ts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8c52ff9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..fbeff90 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @rzcoder diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..7cf29ae --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,26 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 5 + groups: + dev-dependencies: + dependency-type: development + update-types: [minor, patch] + prod-dependencies: + dependency-type: production + update-types: [minor, patch] + ignore: + - dependency-name: '*' + update-types: [version-update:semver-major] + + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + groups: + actions: + patterns: ['*'] diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..26c97ca --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,18 @@ +## Summary + + + +## Changes + + + +## Testing + +- [ ] `npm test` passes +- [ ] `npm run lint` passes +- [ ] `npm run typecheck` passes +- [ ] (If perf-sensitive) `npm run bench` results attached + +## Notes + + diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 0000000..4e263c6 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,40 @@ +name: Benchmark + +on: + workflow_dispatch: + inputs: + ref: + description: 'Git ref to benchmark' + required: false + default: 'main' + +permissions: + contents: read + +jobs: + bench: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run build + - run: npm run bench | tee bench-results.txt + - uses: actions/upload-artifact@v4 + with: + name: bench-results-${{ inputs.ref }} + path: bench-results.txt + retention-days: 30 + - name: Append to job summary + run: | + { + echo '### Benchmark results (`${{ inputs.ref }}`)' + echo '```' + cat bench-results.txt + echo '```' + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e900381 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,88 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: + contents: read + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run format:check + - run: npm run lint + + typecheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run typecheck + + test: + name: test (Node ${{ matrix.node }} / ${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + node: [20, 22, 24] + include: + - os: windows-latest + node: 22 + - os: macos-latest + node: 22 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: npm + - run: npm ci + - run: npm run test:coverage + - name: Upload coverage + if: matrix.os == 'ubuntu-latest' && matrix.node == 22 + uses: codecov/codecov-action@v4 + with: + files: ./coverage/lcov.info + fail_ci_if_error: false + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run build + - name: Verify dual entrypoints exist + run: | + test -f dist/index.mjs + test -f dist/index.cjs + test -f dist/index.d.ts + test -f dist/index.d.cts + - uses: actions/upload-artifact@v4 + with: + name: dist + path: dist/ + retention-days: 7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1263473 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,46 @@ +name: Release + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + id-token: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + registry-url: 'https://registry.npmjs.org' + + - run: npm ci + - run: npm run lint + - run: npm run typecheck + - run: npm test + - run: npm run build + + - name: Verify tag matches package.json version + run: | + PKG_VERSION="v$(node -p "require('./package.json').version")" + TAG="${GITHUB_REF#refs/tags/}" + if [ "$PKG_VERSION" != "$TAG" ]; then + echo "Tag $TAG does not match package.json $PKG_VERSION" + exit 1 + fi + + - name: Publish to npm + run: npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true diff --git a/.gitignore b/.gitignore index 263ba4d..3746a2c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,11 @@ +node_modules/ +dist/ +coverage/ +*.tsbuildinfo +.vitest-cache/ .DS_Store -.idea -node_modules/ \ No newline at end of file +.idea/ +.vscode/ +*.log +.env +.env.local diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..15692cb --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +engine-strict=true +save-exact=false diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..25456d7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,38 @@ +# Changelog + +All notable changes to this project are documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.1.0] - 2026-05-16 + +### Added +- Full TypeScript rewrite. +- New codec-token API: `t.bool`, `t.i8/u8/i16/u16/i32/u32`, `t.f32/f64`, `t.i64/u64`, `t.string`, `t.shortBytes`, `t.bytes`. +- Little-endian variants under `t.le.*`. +- `struct(schema)` factory that compiles a schema once and reuses it across `encode`/`decode`/`sizeOf` calls. +- Functional `encode(value, schema)` and `decode(buf, schema)`. +- `Infer` type helper that derives the TypeScript shape of decoded values from a schema. +- `DataStructError` with codes (`VALUE_OUT_OF_RANGE`, `STRING_TOO_LONG`, `BYTES_TOO_LONG`, `ARRAY_TOO_LONG`, `BUFFER_UNDERFLOW`, `SCHEMA_MISMATCH`, `INVALID_SCHEMA`), `path` (e.g. `$.skills[1].description`), and `offset` (for decode errors). +- Dual ESM + CommonJS build with separate `.d.mts` / `.d.cts` type declarations. +- Vitest test suite (wire-format goldens, roundtrip, error paths) with v8 coverage. +- `tinybench`-based benchmark suite. +- GitHub Actions: CI matrix (Node 20/22/24 on Linux + Node 22 on macOS/Windows), on-demand benchmark workflow, tag-triggered release with npm provenance. +- Dependabot, PR template, CODEOWNERS. + +### Changed +- Module format: package is now `"type": "module"`; entry points are `dist/index.mjs` (ESM) and `dist/index.cjs` (CJS). +- Minimum Node.js version: `>=20.9`. +- Encoder now allocates the output `Buffer` exactly once via a two-pass measure/write strategy (no `Buffer.concat`). +- Decoded `shortBytes` / `bytes` are returned as independent copies of the source memory rather than aliased views. +- Replaced deprecated `new Buffer(...)` calls with `Buffer.allocUnsafe` (for output) and `TextEncoder`/`TextDecoder` for string codecs. + +### Removed +- Legacy exports `DataTypes`, `DataReader`, `DataWriter` (clean break — see README migration table). +- `grunt`, `grunt-simple-mocha`, `jit-grunt`, `grunt-contrib-jshint`, `chai`, `benchmark`. + +### Wire format +- **Byte-identical** with `0.0.x` for the corresponding new codecs. The previous test suite's golden buffers are preserved as goldens in `test/wire-format.test.ts`. diff --git a/README.md b/README.md index 58a27a2..6697fe5 100644 --- a/README.md +++ b/README.md @@ -1,102 +1,179 @@ -# Data structure +# data-struct -Convert between JS object and Node.js buffer with rigidly predetermined scheme. +[![CI](https://github.com/rzcoder/data-struct/actions/workflows/ci.yml/badge.svg)](https://github.com/rzcoder/data-struct/actions/workflows/ci.yml) +[![npm](https://img.shields.io/npm/v/data-struct.svg)](https://www.npmjs.com/package/data-struct) -## Installing +Schema-driven binary serialization between JavaScript values and Node `Buffer`s. -```shell - npm install data-struct +- **Tiny.** No runtime dependencies. +- **Typed.** A schema infers the exact TypeScript shape of the decoded value via `Infer`. +- **Fast.** Two-pass encoder allocates the output buffer exactly once. +- **Dual-format.** Ships ESM + CommonJS + `.d.ts` for both. +- **Wire-stable.** Big-endian by default, byte-identical with prior `0.0.x` releases. + +## Install + +```sh +npm install data-struct ``` +Requires Node `>=20.9`. ## Usage -`require("data-struct")` exports just 3 namespaces: - - * `DataTypes` – dictonary of supported types: - * **boolean** – 1 byte - * **int8** – 1 byte - * **uint8** – 1 byte - * **int16** – 2 bytes - * **uint16** – 2 bytes - * **int32** – 4 bytes - * **uint32** – 4 bytes - * **float** – 4 bytes - * **double** – 8 bytes - * **string** – 2 bytes header + string bytes length (max length: 65535 bytes, **not string length!**) - * **shortBuffer** – 2 bytes header + buffer length (max length: 65535 bytes) - * **buffer** – 4 bytes header + buffer length (max length 4294967295 bytes) - * `DataReader(buffer, scheme)` – buffer -> object function. - * `DataWriter(object, scheme)` – object -> buffer function. - -## Example - - -```javascript - -var DataTypes = require("data-struct").DataTypes; -var DataReader = require("data-struct").DataReader; -var DataWriter = require("data-struct").DataWriter; - -var hero = { - id: 9, - name: 'CirnoBaka', - hp: 146, - skills: [ - { - id: 34, - description: 'freezing frogs' - }, - { - id: 16, - description: 'perfect math' - } - ], - playable: false, - experience: 99999999, - position: { - x: 2, - y: 3 - } -}; +```ts +import { struct, t, type Infer } from 'data-struct'; + +const Hero = struct({ + id: t.u32, + name: t.string, + hp: t.i16, + skills: [{ id: t.u16, description: t.string }], + playable: t.bool, + experience: t.u32, + position: { x: t.u16, y: t.u16 }, +}); + +type Hero = Infer; + +const buf = Hero.encode({ + id: 9, + name: 'CirnoBaka', + hp: 146, + skills: [ + { id: 34, description: 'freezing frogs' }, + { id: 16, description: 'perfect math' }, + ], + playable: false, + experience: 99_999_999, + position: { x: 2, y: 3 }, +}); + +const hero = Hero.decode(buf); // typed as Hero +``` + +Functional form, when you don't want to keep a compiled `struct` around: + +```ts +import { encode, decode, t } from 'data-struct'; + +const Map2D = [[t.u8]]; +const buf = encode([[0, 1, 0], [1, 0, 1]], Map2D); +const map = decode(buf, Map2D); // number[][] +``` + +## API + +### Codec tokens — `t` + +| Token | Bytes | TS type | Notes | +| ---------------- | ---------- | ------------ | ------------------------------------------- | +| `t.bool` | 1 | `boolean` | | +| `t.i8` / `t.u8` | 1 | `number` | | +| `t.i16` / `t.u16`| 2 | `number` | big-endian | +| `t.i32` / `t.u32`| 4 | `number` | big-endian | +| `t.f32` / `t.f64`| 4 / 8 | `number` | big-endian | +| `t.i64` / `t.u64`| 8 | `bigint` | big-endian | +| `t.string` | 2 + utf-8 | `string` | uint16 length prefix, max 65535 utf-8 bytes | +| `t.shortBytes` | 2 + n | `Uint8Array` | uint16 length prefix, max 65535 bytes | +| `t.bytes` | 4 + n | `Uint8Array` | uint32 length prefix, max 4294967295 bytes | + +Little-endian variants live under `t.le.*` (e.g. `t.le.u32`, `t.le.f64`, `t.le.string`). + +### Schema shape -var heroScheme = { - id: DataTypes.uint32, - name: DataTypes.string, - hp: DataTypes.int16, - skills: [{ - id: DataTypes.uint16, - description: DataTypes.string - }], - playable: DataTypes.boolean, - experience: DataTypes.uint32, - position: { - x: DataTypes.uint16, - y: DataTypes.uint16 - } +A schema is one of: + +- A codec token (`t.u32`). +- A single-element array `[Schema]` — encodes as a uint16 length prefix followed by N child encodings. +- A plain object — fields encoded in declared key order. + +```ts +type Schema = Codec | Schema[] | { [k: string]: Schema }; +type Infer = /* recursively maps each leaf to its TS type */; +``` + +### Functions + +```ts +struct(schema: S): { + schema: S; + encode(value: Infer): Buffer; + decode(input: Uint8Array): Infer; + sizeOf(value: Infer): number; }; -var heroBuf = DataWriter(hero, heroScheme); -var heroClone = DataReader(heroBuf, heroScheme); +encode(value: Infer, schema: S): Buffer; +decode(input: Uint8Array, schema: S): Infer; +``` + +`struct(schema)` compiles the schema tree once and reuses the compiled codec across calls — prefer it in hot paths. + +### Errors + +Validation throws `DataStructError` with a `code`: +| Code | When | +| --------------------- | ------------------------------------------------- | +| `VALUE_OUT_OF_RANGE` | Numeric value outside the leaf's range or non-int | +| `STRING_TOO_LONG` | UTF-8 byte length exceeds 65535 | +| `BYTES_TOO_LONG` | Bytes value exceeds the leaf's length cap | +| `ARRAY_TOO_LONG` | Array length exceeds 65535 | +| `BUFFER_UNDERFLOW` | Decode would read past the end of the input | +| `SCHEMA_MISMATCH` | Value shape does not match the schema | +| `INVALID_SCHEMA` | Schema itself is malformed | -//nested arrays -var map = [ - [0,1,0,0,1,1,1,1], - [1,1,0,0,0,1,1,1], - [1,1,0,0,0,0,1,1], - [1,1,1,1,0,0,1,1], - [0,1,1,0,0,0,1,1], - [0,0,0,0,0,1,1,1], - [0,0,0,0,1,1,1,1] -]; +Every error carries `path` (e.g. `$.skills[1].description`) and, for decode errors, `offset`. -var mapScheme = [[DataTypes.uint8]]; +## Wire format + +``` +bool : 1 byte 0x00 = false, anything else = true +i8/u8 : 1 byte +i16/u16 : 2 bytes BE +i32/u32 : 4 bytes BE +f32/f64 : 4 / 8 bytes BE +i64/u64 : 8 bytes BE +string : uint16 BE length || utf-8 bytes +shortBytes : uint16 BE length || raw bytes +bytes : uint32 BE length || raw bytes +array [E] : uint16 BE length || N encoded elements +object {…} : fields encoded in declared key order, no header +``` -var mapBuf = DataWriter(map, mapScheme); -var mapClone = DataReader(mapBuf, mapScheme); +LE variants (`t.le.*`) swap the byte order of length prefixes and the numeric payload. + +## Migration from `0.0.x` + +The 0.1.0 release is a full rewrite with a new API surface. The wire format is preserved, so buffers produced by older versions decode correctly under the new codecs. + +| Old | New | +| ------------------------- | ------------------------- | +| `DataTypes.boolean` | `t.bool` | +| `DataTypes.int8` / `uint8`| `t.i8` / `t.u8` | +| `DataTypes.int16`/`uint16`| `t.i16` / `t.u16` | +| `DataTypes.int32`/`uint32`| `t.i32` / `t.u32` | +| `DataTypes.float` | `t.f32` | +| `DataTypes.double` | `t.f64` | +| `DataTypes.string` | `t.string` | +| `DataTypes.shortBuffer` | `t.shortBytes` | +| `DataTypes.buffer` | `t.bytes` | +| `DataWriter(obj, scheme)` | `encode(obj, schema)` | +| `DataReader(buf, scheme)` | `decode(buf, schema)` | + +The legacy `DataTypes` / `DataReader` / `DataWriter` exports are removed. + +## Development + +```sh +npm install +npm run typecheck +npm run lint +npm test +npm run bench +npm run build ``` ## License -MIT \ No newline at end of file +MIT diff --git a/benchmark/benchmark.js b/benchmark/benchmark.js deleted file mode 100644 index 2ee970f..0000000 --- a/benchmark/benchmark.js +++ /dev/null @@ -1,52 +0,0 @@ -var Benchmark = require('benchmark'); -var suite = new Benchmark.Suite; - -DataTypes = require("../src/index").DataTypes; -DataReader = require("../src/index").DataReader; -DataWriter = require("../src/index").DataWriter; - - - -var suites = { - 'int32': { - object: 0x0F00FF00, - scheme: DataTypes.int32 - }, - 'string': { - object: 'Hello I String', - scheme: DataTypes.string - }, - 'nested': { - object: { nested: { nested2: { nested3: { nested4: { nested5: 42 } } } } }, - scheme: { nested: { nested2: { nested3: { nested4: { nested5: DataTypes.uint8 } } } } } - }, - 'list of list': { - object: [ - [90,10,101], - [20,30,400], - [100,110,1] - ], - scheme: [[DataTypes.int16]] - } -}; - -for(s in suites) { - var object = suites[s].object; - var scheme = suites[s].scheme; - - (function(s, object, scheme) { - var buf = null; - var obj = null; - suite.add(s + '#writing to buffer', function () { - buf = DataWriter(object, scheme); - }) - .add(s + '#reading from buffer', function () { - obj = DataReader(buf, scheme); - }); - })(s, object, scheme); -} - - -suite.on('cycle', function(event) { - console.log(String(event.target)); -}).run(); \ No newline at end of file diff --git a/benchmark/benchmark.ts b/benchmark/benchmark.ts new file mode 100644 index 0000000..54e947f --- /dev/null +++ b/benchmark/benchmark.ts @@ -0,0 +1,92 @@ +import { Bench } from 'tinybench'; +import { decode, encode, struct, t } from '../src/index.js'; +import type { Schema } from '../src/index.js'; + +interface Scenario { + readonly name: string; + readonly schema: Schema; + readonly value: unknown; +} + +const scenarios: Scenario[] = [ + { name: 'i32', schema: t.i32, value: 0x0f00ff00 }, + { name: 'string', schema: t.string, value: 'Hello I String' }, + { + name: 'nested', + schema: { nested: { nested2: { nested3: { nested4: { nested5: t.u8 } } } } }, + value: { nested: { nested2: { nested3: { nested4: { nested5: 42 } } } } }, + }, + { + name: 'list of list', + schema: [[t.i16]], + value: [ + [90, 10, 101], + [20, 30, 400], + [100, 110, 1], + ], + }, + { + name: 'hero (struct factory)', + schema: { + id: t.u32, + name: t.string, + hp: t.i16, + skills: [{ id: t.u16, description: t.string }], + playable: t.bool, + experience: t.u32, + position: { x: t.u16, y: t.u16 }, + }, + value: { + id: 9, + name: 'CirnoBaka', + hp: 146, + skills: [ + { id: 34, description: 'freezing frogs' }, + { id: 16, description: 'perfect math' }, + ], + playable: false, + experience: 99999999, + position: { x: 2, y: 3 }, + }, + }, +]; + +async function main(): Promise { + const bench = new Bench({ time: 500 }); + + for (const scenario of scenarios) { + const codec = struct(scenario.schema); + const buffer = encode(scenario.value as never, scenario.schema); + + bench + .add(`${scenario.name} :: encode (loose)`, () => { + encode(scenario.value as never, scenario.schema); + }) + .add(`${scenario.name} :: encode (compiled struct)`, () => { + codec.encode(scenario.value as never); + }) + .add(`${scenario.name} :: decode (loose)`, () => { + decode(buffer, scenario.schema); + }) + .add(`${scenario.name} :: decode (compiled struct)`, () => { + codec.decode(buffer); + }); + } + + await bench.run(); + + console.log('\n=== data-struct benchmark ==='); + console.table( + bench.tasks.map((task) => ({ + task: task.name, + 'ops/sec': task.result?.hz.toFixed(0), + 'avg (ns)': task.result?.mean ? (task.result.mean * 1e6).toFixed(2) : '—', + samples: task.result?.samples.length, + })), + ); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..66bc5c4 --- /dev/null +++ b/biome.json @@ -0,0 +1,52 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignore": ["dist", "coverage", "node_modules", "*.tgz"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100, + "lineEnding": "lf" + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "error", + "noUnusedImports": "error" + }, + "style": { + "useImportType": "error", + "useNodejsImportProtocol": "error", + "useConsistentArrayType": { "level": "error", "options": { "syntax": "shorthand" } }, + "noNonNullAssertion": "off", + "noParameterAssign": "off" + }, + "suspicious": { + "noExplicitAny": "warn" + }, + "complexity": { + "noBannedTypes": "off" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "semicolons": "always", + "trailingCommas": "all", + "arrowParentheses": "always" + } + } +} diff --git a/gruntfile.js b/gruntfile.js deleted file mode 100644 index 1cb1aa5..0000000 --- a/gruntfile.js +++ /dev/null @@ -1,36 +0,0 @@ -module.exports = function(grunt) { - grunt.initConfig({ - jshint: { - options: { - }, - default: { - files: { - src: ['src/**/*.js', '!src/libs/**/*'] - } - }, - libs: { - files: { - src: ['src/libs/**/*'] - } - } - }, - - simplemocha: { - options: { - reporter: 'List', - ui: 'tdd' - }, - all: { src: ['test/**/*.js'] } - } - }); - - require('jit-grunt')(grunt, { - 'simplemocha': 'grunt-simple-mocha' - }); - - - grunt.registerTask('lint', ['jshint:default']); - grunt.registerTask('test', ['simplemocha']); - - grunt.registerTask('default', ['lint', 'test']); -}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..00a5e66 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3927 @@ +{ + "name": "data-struct", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "data-struct", + "version": "0.1.0", + "license": "MIT", + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@types/node": "^20.17.0", + "@vitest/coverage-v8": "^2.1.8", + "tinybench": "^3.1.0", + "tsup": "^8.3.5", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "vitest": "^2.1.8" + }, + "engines": { + "node": ">=20.9" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@biomejs/biome": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", + "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", + "dev": true, + "hasInstallScript": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "1.9.4", + "@biomejs/cli-darwin-x64": "1.9.4", + "@biomejs/cli-linux-arm64": "1.9.4", + "@biomejs/cli-linux-arm64-musl": "1.9.4", + "@biomejs/cli-linux-x64": "1.9.4", + "@biomejs/cli-linux-x64-musl": "1.9.4", + "@biomejs/cli-win32-arm64": "1.9.4", + "@biomejs/cli-win32-x64": "1.9.4" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", + "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", + "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", + "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", + "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", + "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", + "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", + "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", + "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitest/coverage-v8": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.9.tgz", + "integrity": "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.7", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", + "std-env": "^3.8.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "2.1.9", + "vitest": "2.1.9" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/rollup": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-3.1.1.tgz", + "integrity": "sha512-74pmf47HY/bHqamcCMGris+1AtGGsqTZ3Hc/UK4QvSmRuf/9PIF9753+c8XBh7JfX2r9KeZtVjOYjd6vFpc0qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tsup": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/tsx": { + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.0.tgz", + "integrity": "sha512-8ccZMPD69s1AbKXx0C5ddTNZfNjwV04iIKgjZmKfKxMynEtSYcK0Lh7iQFh53fI5Yu4pb9usgAiqyPmEONaALg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vitest/node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/package.json b/package.json index 22e36f0..97d93aa 100644 --- a/package.json +++ b/package.json @@ -1,35 +1,75 @@ { "name": "data-struct", - "version": "0.0.11", - "description": "Convert between JS object and Node.js buffer", - "main": "src/index.js", + "version": "0.1.0", + "description": "Schema-driven binary serialization between JS objects and Node Buffers", + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.cts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + }, + "./package.json": "./package.json" + }, + "files": ["dist", "README.md", "LICENSE", "CHANGELOG.md"], + "engines": { + "node": ">=20.9" + }, + "sideEffects": false, "scripts": { - "test": "grunt test" + "build": "tsup", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "bench": "tsx benchmark/benchmark.ts", + "lint": "biome check .", + "lint:fix": "biome check --write .", + "format": "biome format --write .", + "format:check": "biome format .", + "typecheck": "tsc --noEmit", + "prepublishOnly": "npm run typecheck && npm run lint && npm test && npm run build" }, "repository": { "type": "git", - "url": "https://github.com/rzcoder/data-struct.git" + "url": "git+https://github.com/rzcoder/data-struct.git" }, "keywords": [ - "node", + "binary", "buffer", - "object", + "codec", + "decode", + "encode", + "schema", "serialization", - "convert" + "struct", + "typescript" ], "author": "rzcoder", "license": "MIT", "bugs": { "url": "https://github.com/rzcoder/data-struct/issues" }, - "homepage": "https://github.com/rzcoder/data-struct", - "devDependencies": { - "grunt": "^0.4.4", - "grunt-simple-mocha": "^0.4.0", - "jit-grunt": "^0.3.2", - "chai": "^1.9.1", - "grunt-contrib-jshint": "^0.9.2", - "benchmark": "^1.0.0" + "homepage": "https://github.com/rzcoder/data-struct#readme", + "publishConfig": { + "access": "public", + "provenance": true }, - "dependencies": {} + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@types/node": "^20.17.0", + "@vitest/coverage-v8": "^2.1.8", + "tinybench": "^3.1.0", + "tsup": "^8.3.5", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "vitest": "^2.1.8" + } } diff --git a/src/codecs.ts b/src/codecs.ts new file mode 100644 index 0000000..9288291 --- /dev/null +++ b/src/codecs.ts @@ -0,0 +1,334 @@ +import { DataStructError } from './errors.js'; +import type { Codec, CodecImpl } from './types.js'; + +const textEncoder = new TextEncoder(); +const textDecoder = new TextDecoder('utf-8'); + +const U8_MAX = 0xff; +const U16_MAX = 0xffff; +const U32_MAX = 0xffffffff; +const I8_MIN = -0x80; +const I8_MAX = 0x7f; +const I16_MIN = -0x8000; +const I16_MAX = 0x7fff; +const I32_MIN = -0x80000000; +const I32_MAX = 0x7fffffff; +const I64_MIN = -(1n << 63n); +const I64_MAX = (1n << 63n) - 1n; +const U64_MAX = (1n << 64n) - 1n; + +function ensureCapacity(view: DataView, offset: number, need: number, path: string): void { + if (offset + need > view.byteLength) { + throw new DataStructError( + 'BUFFER_UNDERFLOW', + `need ${need} byte(s) at offset ${offset}, but buffer has ${view.byteLength}`, + { path, offset }, + ); + } +} + +function ensureInt(value: number, min: number, max: number, type: string, path: string): void { + if (typeof value !== 'number' || !Number.isInteger(value) || value < min || value > max) { + throw new DataStructError( + 'VALUE_OUT_OF_RANGE', + `${type} requires integer in [${min}, ${max}], got ${value}`, + { + path, + }, + ); + } +} + +function ensureFiniteNumber(value: number, type: string, path: string): void { + if (typeof value !== 'number') { + throw new DataStructError( + 'VALUE_OUT_OF_RANGE', + `${type} requires number, got ${typeof value}`, + { path }, + ); + } +} + +function ensureBigInt(value: bigint, min: bigint, max: bigint, type: string, path: string): void { + if (typeof value !== 'bigint' || value < min || value > max) { + throw new DataStructError( + 'VALUE_OUT_OF_RANGE', + `${type} requires bigint in [${min}, ${max}], got ${value}`, + { + path, + }, + ); + } +} + +function ensureUint8Array(value: unknown, type: string, path: string): asserts value is Uint8Array { + if (!(value instanceof Uint8Array)) { + throw new DataStructError( + 'SCHEMA_MISMATCH', + `${type} requires Uint8Array, got ${typeof value}`, + { path }, + ); + } +} + +function fixed( + tag: number, + size: number, + write: (view: DataView, offset: number, value: T) => void, + read: (view: DataView, offset: number) => T, + validate: (value: T, path: string) => void, +): Codec { + const impl: CodecImpl = { + measure: () => size, + write(view, _bytes, offset, value, _plan, path) { + validate(value, path); + write(view, offset, value); + return offset + size; + }, + read(view, _bytes, offset, path) { + ensureCapacity(view, offset, size, path); + return { value: read(view, offset), offset: offset + size }; + }, + }; + return { tag, impl }; +} + +const boolImpl: CodecImpl = { + measure: () => 1, + write(view, _bytes, offset, value, _plan, path) { + if (typeof value !== 'boolean') { + throw new DataStructError('SCHEMA_MISMATCH', `bool requires boolean, got ${typeof value}`, { + path, + }); + } + view.setUint8(offset, value ? 1 : 0); + return offset + 1; + }, + read(view, _bytes, offset, path) { + ensureCapacity(view, offset, 1, path); + return { value: view.getInt8(offset) !== 0, offset: offset + 1 }; + }, +}; + +function makeString(littleEndian: boolean, tag: number): Codec { + const impl: CodecImpl = { + measure(value, plan, path) { + if (typeof value !== 'string') { + throw new DataStructError( + 'SCHEMA_MISMATCH', + `string requires string, got ${typeof value}`, + { path }, + ); + } + const encoded = textEncoder.encode(value); + if (encoded.byteLength > U16_MAX) { + throw new DataStructError( + 'STRING_TOO_LONG', + `string UTF-8 byte length ${encoded.byteLength} exceeds 65535`, + { path }, + ); + } + plan.strings.push(encoded); + return 2 + encoded.byteLength; + }, + write(view, bytes, offset, _value, plan) { + const encoded = plan.strings[plan.cursor++] as Uint8Array; + view.setUint16(offset, encoded.byteLength, littleEndian); + offset += 2; + bytes.set(encoded, offset); + return offset + encoded.byteLength; + }, + read(view, bytes, offset, path) { + ensureCapacity(view, offset, 2, path); + const length = view.getUint16(offset, littleEndian); + offset += 2; + ensureCapacity(view, offset, length, path); + const value = textDecoder.decode(bytes.subarray(offset, offset + length)); + return { value, offset: offset + length }; + }, + }; + return { tag, impl }; +} + +function makeShortBytes(littleEndian: boolean, tag: number): Codec { + const impl: CodecImpl = { + measure(value, _plan, path) { + ensureUint8Array(value, 'shortBytes', path); + if (value.byteLength > U16_MAX) { + throw new DataStructError( + 'BYTES_TOO_LONG', + `shortBytes length ${value.byteLength} exceeds 65535`, + { path }, + ); + } + return 2 + value.byteLength; + }, + write(view, bytes, offset, value) { + view.setUint16(offset, value.byteLength, littleEndian); + offset += 2; + bytes.set(value, offset); + return offset + value.byteLength; + }, + read(view, bytes, offset, path) { + ensureCapacity(view, offset, 2, path); + const length = view.getUint16(offset, littleEndian); + offset += 2; + ensureCapacity(view, offset, length, path); + const value = new Uint8Array(length); + value.set(bytes.subarray(offset, offset + length)); + return { value, offset: offset + length }; + }, + }; + return { tag, impl }; +} + +function makeBytes(littleEndian: boolean, tag: number): Codec { + const impl: CodecImpl = { + measure(value, _plan, path) { + ensureUint8Array(value, 'bytes', path); + if (value.byteLength > U32_MAX) { + throw new DataStructError( + 'BYTES_TOO_LONG', + `bytes length ${value.byteLength} exceeds 4294967295`, + { path }, + ); + } + return 4 + value.byteLength; + }, + write(view, bytes, offset, value) { + view.setUint32(offset, value.byteLength, littleEndian); + offset += 4; + bytes.set(value, offset); + return offset + value.byteLength; + }, + read(view, bytes, offset, path) { + ensureCapacity(view, offset, 4, path); + const length = view.getUint32(offset, littleEndian); + offset += 4; + ensureCapacity(view, offset, length, path); + const value = new Uint8Array(length); + value.set(bytes.subarray(offset, offset + length)); + return { value, offset: offset + length }; + }, + }; + return { tag, impl }; +} + +function intCodec( + tag: number, + size: number, + signed: boolean, + min: number, + max: number, + name: string, + littleEndian: boolean, +): Codec { + const setters: Record void> = { + 'i:1': (v, o, x) => v.setInt8(o, x), + 'u:1': (v, o, x) => v.setUint8(o, x), + 'i:2': (v, o, x, le) => v.setInt16(o, x, le), + 'u:2': (v, o, x, le) => v.setUint16(o, x, le), + 'i:4': (v, o, x, le) => v.setInt32(o, x, le), + 'u:4': (v, o, x, le) => v.setUint32(o, x, le), + }; + const getters: Record number> = { + 'i:1': (v, o) => v.getInt8(o), + 'u:1': (v, o) => v.getUint8(o), + 'i:2': (v, o, le) => v.getInt16(o, le), + 'u:2': (v, o, le) => v.getUint16(o, le), + 'i:4': (v, o, le) => v.getInt32(o, le), + 'u:4': (v, o, le) => v.getUint32(o, le), + }; + const key = `${signed ? 'i' : 'u'}:${size}`; + const setter = setters[key] as (view: DataView, o: number, v: number, le: boolean) => void; + const getter = getters[key] as (view: DataView, o: number, le: boolean) => number; + return fixed( + tag, + size, + (view, offset, value) => setter(view, offset, value, littleEndian), + (view, offset) => getter(view, offset, littleEndian), + (value, path) => ensureInt(value, min, max, name, path), + ); +} + +function floatCodec(tag: number, size: 4 | 8, name: string, littleEndian: boolean): Codec { + if (size === 4) { + return fixed( + tag, + 4, + (view, offset, value) => view.setFloat32(offset, value, littleEndian), + (view, offset) => view.getFloat32(offset, littleEndian), + (value, path) => ensureFiniteNumber(value, name, path), + ); + } + return fixed( + tag, + 8, + (view, offset, value) => view.setFloat64(offset, value, littleEndian), + (view, offset) => view.getFloat64(offset, littleEndian), + (value, path) => ensureFiniteNumber(value, name, path), + ); +} + +function bigIntCodec( + tag: number, + signed: boolean, + name: string, + littleEndian: boolean, +): Codec { + const min = signed ? I64_MIN : 0n; + const max = signed ? I64_MAX : U64_MAX; + return fixed( + tag, + 8, + (view, offset, value) => { + if (signed) view.setBigInt64(offset, value, littleEndian); + else view.setBigUint64(offset, value, littleEndian); + }, + (view, offset) => + signed ? view.getBigInt64(offset, littleEndian) : view.getBigUint64(offset, littleEndian), + (value, path) => ensureBigInt(value, min, max, name, path), + ); +} + +// Wire tags preserve legacy numeric ids where applicable for debug/introspection. +const BE = false; +const LE = true; + +const beCodecs = { + bool: { tag: 0x000, impl: boolImpl } as Codec, + i8: intCodec(0x010, 1, true, I8_MIN, I8_MAX, 'i8', BE), + u8: intCodec(0x011, 1, false, 0, U8_MAX, 'u8', BE), + i16: intCodec(0x020, 2, true, I16_MIN, I16_MAX, 'i16', BE), + u16: intCodec(0x021, 2, false, 0, U16_MAX, 'u16', BE), + i32: intCodec(0x030, 4, true, I32_MIN, I32_MAX, 'i32', BE), + u32: intCodec(0x031, 4, false, 0, U32_MAX, 'u32', BE), + f32: floatCodec(0x040, 4, 'f32', BE), + f64: floatCodec(0x041, 8, 'f64', BE), + i64: bigIntCodec(0x050, true, 'i64', BE), + u64: bigIntCodec(0x051, false, 'u64', BE), + string: makeString(BE, 0x200), + shortBytes: makeShortBytes(BE, 0x210), + bytes: makeBytes(BE, 0x211), +}; + +const leCodecs = { + i16: intCodec(0x1020, 2, true, I16_MIN, I16_MAX, 'le.i16', LE), + u16: intCodec(0x1021, 2, false, 0, U16_MAX, 'le.u16', LE), + i32: intCodec(0x1030, 4, true, I32_MIN, I32_MAX, 'le.i32', LE), + u32: intCodec(0x1031, 4, false, 0, U32_MAX, 'le.u32', LE), + f32: floatCodec(0x1040, 4, 'le.f32', LE), + f64: floatCodec(0x1041, 8, 'le.f64', LE), + i64: bigIntCodec(0x1050, true, 'le.i64', LE), + u64: bigIntCodec(0x1051, false, 'le.u64', LE), + string: makeString(LE, 0x1200), + shortBytes: makeShortBytes(LE, 0x1210), + bytes: makeBytes(LE, 0x1211), +}; + +export const t = { + ...beCodecs, + le: leCodecs, +} as const; + +export type TypeRegistry = typeof t; diff --git a/src/dataReader.js b/src/dataReader.js deleted file mode 100644 index 1490d52..0000000 --- a/src/dataReader.js +++ /dev/null @@ -1,128 +0,0 @@ -var DataTypes = require('./dataTypes'); - -var typesTable = {}; - -typesTable[DataTypes.boolean] = function (pointer, buffer) { - var res = buffer.readInt8(pointer.offset); - pointer.offset += 1; - return !!res; -}; - -typesTable[DataTypes.int8] = function (pointer, buffer) { - var res = buffer.readInt8(pointer.offset); - pointer.offset += 1; - return res; -}; - -typesTable[DataTypes.uint8] = function (pointer, buffer) { - var res = buffer.readUInt8(pointer.offset); - pointer.offset += 1; - return res; -}; - -typesTable[DataTypes.int16] = function (pointer, buffer) { - var res = buffer.readInt16BE(pointer.offset); - pointer.offset += 2; - return res; -}; - -typesTable[DataTypes.uint16] = function (pointer, buffer) { - var res = buffer.readUInt16BE(pointer.offset); - pointer.offset += 2; - return res; -}; - -typesTable[DataTypes.int32] = function (pointer, buffer) { - var res = buffer.readInt32BE(pointer.offset); - pointer.offset += 4; - return res; -}; - -typesTable[DataTypes.uint32] = function (pointer, buffer) { - var res = buffer.readUInt32BE(pointer.offset); - pointer.offset += 4; - return res; -}; - -typesTable[DataTypes.float] = function (pointer, buffer) { - var res = buffer.readFloatBE(pointer.offset); - pointer.offset += 4; - return res; -}; - -typesTable[DataTypes.double] = function (pointer, buffer) { - var res = buffer.readDoubleBE(pointer.offset); - pointer.offset += 8; - return res; -}; - -typesTable[DataTypes.string] = function (pointer, buffer) { - var length; - length = buffer.readUInt16BE(pointer.offset); - pointer.offset += 2; - var res = buffer.toString('utf8', pointer.offset, pointer.offset + length); - pointer.offset += length; - return res; -}; - -typesTable[DataTypes.shortBuffer] = function (pointer, buffer) { - var length; - length = buffer.readUInt16BE(pointer.offset); - pointer.offset += 2; - var res = buffer.slice(pointer.offset, pointer.offset + length); - pointer.offset += length; - return res; -}; - -typesTable[DataTypes.buffer] = function (pointer, buffer) { - var length; - length = buffer.readUInt32BE(pointer.offset); - pointer.offset += 4; - var res = buffer.slice(pointer.offset, pointer.offset + length); - pointer.offset += length; - return res; -}; - -function list (pointer, buffer, scheme) { - var length = buffer.readUInt16BE(pointer.offset); - pointer.offset += 2; - - var res = []; - while (length--) { - res.push(structureReader(pointer, buffer, scheme)); - } - return res; -} - -var structureReader = function (pointer, buffer, scheme) { - var res = {}; - - if (arguments.length === 2) { - scheme = buffer; - buffer = pointer; - pointer = { - offset: 0 - }; - } - - if(typeof scheme === 'number') { - res = typesTable[scheme](pointer, buffer); - } else if (Array.isArray(scheme)) { - res = list(pointer, buffer, scheme[0]); - } else { - for (var el in scheme) { - var s = scheme[el]; - if (typeof s === 'number') { - res[el] = typesTable[s](pointer, buffer); - } else if (Array.isArray(s)) { - res[el] = list(pointer, buffer, s[0]); - } else { - res[el] = structureReader(pointer, buffer, s); - } - } - } - - return res; -}; - -module.exports = structureReader; \ No newline at end of file diff --git a/src/dataTypes.js b/src/dataTypes.js deleted file mode 100644 index 1f1a440..0000000 --- a/src/dataTypes.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - boolean: 0x000, - int8: 0x010, - uint8: 0x011, - int16: 0x020, - uint16: 0x021, - int32: 0x030, - uint32: 0x031, - float: 0x040, - double: 0x041, - - string: 0x200, - shortBuffer: 0x210, - buffer: 0x211 -}; \ No newline at end of file diff --git a/src/dataWriter.js b/src/dataWriter.js deleted file mode 100644 index 9961e83..0000000 --- a/src/dataWriter.js +++ /dev/null @@ -1,141 +0,0 @@ -var DataTypes = require('./dataTypes'); - -var typesTable = {}; - -typesTable[DataTypes.boolean] = function (value) { - var buffer = new Buffer(1); - buffer.writeInt8(value, 0); - return buffer; -}; - -typesTable[DataTypes.int8] = function (value) { - var buffer = new Buffer(1); - buffer.writeInt8(value, 0); - return buffer; -}; - -typesTable[DataTypes.uint8] = function (value) { - var buffer = new Buffer(1); - buffer.writeUInt8(value, 0); - return buffer; -}; - -typesTable[DataTypes.int16] = function (value) { - var buffer = new Buffer(2); - buffer.writeInt16BE(value, 0); - return buffer; -}; - -typesTable[DataTypes.uint16] = function (value) { - var buffer = new Buffer(2); - buffer.writeUInt16BE(value, 0); - return buffer; -}; - -typesTable[DataTypes.int32] = function (value) { - var buffer = new Buffer(4); - buffer.writeInt32BE(value, 0); - return buffer; -}; - -typesTable[DataTypes.uint32] = function (value) { - var buffer = new Buffer(4); - buffer.writeUInt32BE(value, 0); - return buffer; -}; - -typesTable[DataTypes.float] = function (value) { - var buffer = new Buffer(4); - buffer.writeFloatBE(value, 0); - return buffer; -}; - -typesTable[DataTypes.double] = function (value) { - var buffer = new Buffer(8); - buffer.writeDoubleBE(value, 0); - return buffer; -}; - -typesTable[DataTypes.string] = function (value) { - var buffer = new Buffer(value); - var length = buffer.length; - if (length > 0xFFFF) { - throw Error('String too long'); - } - - var res = new Buffer(2 + length); - res.writeUInt16BE(length, 0); - buffer.copy(res, 2); - - return res; -}; - -typesTable[DataTypes.shortBuffer] = function (value) { - var length = value.length; - if (length > 0xFFFF) { - throw Error('Buffer too long'); - } - - var res = new Buffer(2 + length); - res.writeUInt16BE(length, 0); - value.copy(res, 2); - - return res; -}; - -typesTable[DataTypes.buffer] = function (value) { - var length = value.length; - - if (length > 0xFFFFFFFF) { - throw Error('Buffer too long'); - } - - var res = new Buffer(4 + length); - res.writeUInt32BE(length, 0); - value.copy(res, 4); - - return res; -}; - -function list (value, scheme) { - if (!Array.isArray(value)) { - throw Error('Value is not array.'); - } - - var res = []; - var lenBuf = new Buffer(2); - var length = value.length; - lenBuf.writeUInt16BE(length,0); - res.push(lenBuf); - - for(var i = 0; i < length; i++) { - res.push(structureWriter(value[i], scheme)); - } - - return Buffer.concat(res); -} - -var structureWriter = function (object, scheme) { - var res = []; - - if(typeof scheme === 'number') { - res.push(typesTable[scheme](object)); - } else if(Array.isArray(scheme)) { - res.push(list(object, scheme[0])); - } else { - for (var el in scheme) { - var s = scheme[el]; - if (typeof s === 'number') { - res.push(typesTable[s](object[el])); - } else if (Array.isArray(s)) { - res.push(list(object[el], s[0])); - } else { - res.push(structureWriter(object[el], s)); - } - } - } - - return new Buffer.concat(res); -}; - -module.exports = structureWriter; \ No newline at end of file diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..579d591 --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,28 @@ +export type DataStructErrorCode = + | 'VALUE_OUT_OF_RANGE' + | 'STRING_TOO_LONG' + | 'BYTES_TOO_LONG' + | 'ARRAY_TOO_LONG' + | 'BUFFER_UNDERFLOW' + | 'SCHEMA_MISMATCH' + | 'INVALID_SCHEMA'; + +export interface DataStructErrorOptions { + path?: string; + offset?: number; + cause?: unknown; +} + +export class DataStructError extends Error { + readonly code: DataStructErrorCode; + readonly path: string; + readonly offset: number | undefined; + + constructor(code: DataStructErrorCode, message: string, opts: DataStructErrorOptions = {}) { + super(opts.path ? `${message} at ${opts.path}` : message, { cause: opts.cause }); + this.name = 'DataStructError'; + this.code = code; + this.path = opts.path ?? '$'; + this.offset = opts.offset; + } +} diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 2eb7b20..0000000 --- a/src/index.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports.DataTypes = require('./dataTypes'); -module.exports.DataReader = require('./dataReader'); -module.exports.DataWriter = require('./dataWriter'); \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..128a80e --- /dev/null +++ b/src/index.ts @@ -0,0 +1,7 @@ +export { t } from './codecs.js'; +export type { TypeRegistry } from './codecs.js'; +export { DataStructError } from './errors.js'; +export type { DataStructErrorCode, DataStructErrorOptions } from './errors.js'; +export { decode, encode, struct } from './struct.js'; +export type { Struct } from './struct.js'; +export type { Codec, CodecImpl, Infer, Schema } from './types.js'; diff --git a/src/struct.ts b/src/struct.ts new file mode 100644 index 0000000..74f51c4 --- /dev/null +++ b/src/struct.ts @@ -0,0 +1,184 @@ +import { Buffer } from 'node:buffer'; +import { DataStructError } from './errors.js'; +import type { AnyCodec, Codec, CodecImpl, EncodePlan, Infer, Schema } from './types.js'; + +const ARRAY_LENGTH_MAX = 0xffff; + +function isCodec(v: unknown): v is AnyCodec { + return ( + typeof v === 'object' && + v !== null && + 'impl' in v && + 'tag' in v && + typeof (v as { tag: unknown }).tag === 'number' + ); +} + +function compile(schema: Schema, path: string): AnyCodec { + if (isCodec(schema)) return schema; + if (Array.isArray(schema)) { + if (schema.length !== 1) { + throw new DataStructError( + 'INVALID_SCHEMA', + `array schema must have exactly one element descriptor, got ${schema.length}`, + { path }, + ); + } + const element = schema[0]; + if (element === undefined) { + throw new DataStructError('INVALID_SCHEMA', 'array schema element is undefined', { path }); + } + return arrayCodec(compile(element, `${path}[]`)); + } + if (typeof schema === 'object' && schema !== null) { + return structCodec(schema as Record, path); + } + throw new DataStructError('INVALID_SCHEMA', `unsupported schema node: ${String(schema)}`, { + path, + }); +} + +function arrayCodec(element: AnyCodec): Codec { + const impl: CodecImpl = { + measure(value, plan, path) { + if (!Array.isArray(value)) { + throw new DataStructError('SCHEMA_MISMATCH', `expected array, got ${typeof value}`, { + path, + }); + } + if (value.length > ARRAY_LENGTH_MAX) { + throw new DataStructError( + 'ARRAY_TOO_LONG', + `array length ${value.length} exceeds ${ARRAY_LENGTH_MAX}`, + { path }, + ); + } + let size = 2; + for (let i = 0; i < value.length; i++) { + size += element.impl.measure(value[i], plan, `${path}[${i}]`); + } + return size; + }, + write(view, bytes, offset, value, plan, path) { + view.setUint16(offset, value.length, false); + offset += 2; + for (let i = 0; i < value.length; i++) { + offset = element.impl.write(view, bytes, offset, value[i], plan, `${path}[${i}]`); + } + return offset; + }, + read(view, bytes, offset, path) { + if (offset + 2 > view.byteLength) { + throw new DataStructError( + 'BUFFER_UNDERFLOW', + `need 2 byte(s) for array length at offset ${offset}`, + { path, offset }, + ); + } + const length = view.getUint16(offset, false); + offset += 2; + const out = new Array(length); + for (let i = 0; i < length; i++) { + const r = element.impl.read(view, bytes, offset, `${path}[${i}]`); + out[i] = r.value; + offset = r.offset; + } + return { value: out, offset }; + }, + }; + return { tag: 0x100, impl }; +} + +function structCodec( + schema: Record, + basePath: string, +): Codec> { + const keys = Object.keys(schema); + const fields: [string, AnyCodec][] = keys.map((key) => { + const child = schema[key]; + if (child === undefined) { + throw new DataStructError('INVALID_SCHEMA', `field "${key}" is undefined`, { + path: `${basePath}.${key}`, + }); + } + return [key, compile(child, `${basePath}.${key}`)]; + }); + + const impl: CodecImpl> = { + measure(value, plan, path) { + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + throw new DataStructError('SCHEMA_MISMATCH', `expected object, got ${typeof value}`, { + path, + }); + } + const obj = value as Record; + let size = 0; + for (const [key, codec] of fields) { + size += codec.impl.measure(obj[key], plan, `${path}.${key}`); + } + return size; + }, + write(view, bytes, offset, value, plan, path) { + for (const [key, codec] of fields) { + offset = codec.impl.write(view, bytes, offset, value[key], plan, `${path}.${key}`); + } + return offset; + }, + read(view, bytes, offset, path) { + const out: Record = {}; + for (const [key, codec] of fields) { + const r = codec.impl.read(view, bytes, offset, `${path}.${key}`); + out[key] = r.value; + offset = r.offset; + } + return { value: out, offset }; + }, + }; + return { tag: 0x300, impl }; +} + +export interface Struct { + readonly schema: S; + encode(value: Infer): Buffer; + decode(input: Uint8Array): Infer; + sizeOf(value: Infer): number; +} + +export function struct(schema: S): Struct { + const codec = compile(schema, '$'); + + return { + schema, + encode(value): Buffer { + const plan: EncodePlan = { strings: [], cursor: 0 }; + const size = codec.impl.measure(value, plan, '$'); + const out = Buffer.allocUnsafe(size); + const view = new DataView(out.buffer, out.byteOffset, out.byteLength); + plan.cursor = 0; + codec.impl.write(view, out, 0, value, plan, '$'); + return out; + }, + decode(input): Infer { + if (!(input instanceof Uint8Array)) { + throw new DataStructError( + 'SCHEMA_MISMATCH', + `decode expects Uint8Array, got ${typeof input}`, + ); + } + const view = new DataView(input.buffer, input.byteOffset, input.byteLength); + const { value } = codec.impl.read(view, input, 0, '$'); + return value as Infer; + }, + sizeOf(value): number { + return codec.impl.measure(value, { strings: [], cursor: 0 }, '$'); + }, + }; +} + +export function encode(value: Infer, schema: S): Buffer { + return struct(schema).encode(value); +} + +export function decode(input: Uint8Array, schema: S): Infer { + return struct(schema).decode(input); +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..6b35b5c --- /dev/null +++ b/src/types.ts @@ -0,0 +1,45 @@ +declare const phantom: unique symbol; + +export interface EncodePlan { + strings: Uint8Array[]; + cursor: number; +} + +export interface ReadResult { + value: T; + offset: number; +} + +export interface CodecImpl { + measure(value: T, plan: EncodePlan, path: string): number; + write( + view: DataView, + bytes: Uint8Array, + offset: number, + value: T, + plan: EncodePlan, + path: string, + ): number; + read(view: DataView, bytes: Uint8Array, offset: number, path: string): ReadResult; +} + +export interface Codec { + readonly tag: number; + readonly impl: CodecImpl; + readonly [phantom]?: T; +} + +// biome-ignore lint/suspicious/noExplicitAny: top type for schema discriminator +export type AnyCodec = Codec; + +export type Schema = AnyCodec | readonly Schema[] | { readonly [key: string]: Schema }; + +export type Infer = S extends Codec + ? T + : S extends readonly (infer E)[] + ? E extends Schema + ? Infer[] + : never + : S extends { readonly [key: string]: Schema } + ? { -readonly [K in keyof S]: Infer } + : never; diff --git a/test/errors.test.ts b/test/errors.test.ts new file mode 100644 index 0000000..531cdfa --- /dev/null +++ b/test/errors.test.ts @@ -0,0 +1,116 @@ +import { Buffer } from 'node:buffer'; +import { describe, expect, it } from 'vitest'; +import { DataStructError, decode, encode, t } from '../src/index.js'; + +describe('errors', () => { + it('throws VALUE_OUT_OF_RANGE for negative uint', () => { + try { + encode(-1, t.u8); + throw new Error('expected throw'); + } catch (err) { + expect(err).toBeInstanceOf(DataStructError); + expect((err as DataStructError).code).toBe('VALUE_OUT_OF_RANGE'); + } + }); + + it('throws VALUE_OUT_OF_RANGE for overflow', () => { + expect(() => encode(256, t.u8)).toThrow(/u8/); + expect(() => encode(0x10000, t.u16)).toThrow(/u16/); + expect(() => encode(2.5, t.i32)).toThrow(/integer/); + }); + + it('throws STRING_TOO_LONG for >64KiB strings', () => { + const huge = 'a'.repeat(65536); + try { + encode(huge, t.string); + throw new Error('expected throw'); + } catch (err) { + expect((err as DataStructError).code).toBe('STRING_TOO_LONG'); + } + }); + + it('throws ARRAY_TOO_LONG for >64KiB arrays', () => { + const huge = new Array(65536).fill(0); + try { + encode(huge, [t.u8]); + throw new Error('expected throw'); + } catch (err) { + expect((err as DataStructError).code).toBe('ARRAY_TOO_LONG'); + } + }); + + it('throws BUFFER_UNDERFLOW when decode runs past end', () => { + try { + decode(Buffer.from([0x00]), t.u16); + throw new Error('expected throw'); + } catch (err) { + expect((err as DataStructError).code).toBe('BUFFER_UNDERFLOW'); + } + }); + + it('throws SCHEMA_MISMATCH when value shape is wrong', () => { + try { + encode('not an array' as never, [t.u8]); + throw new Error('expected throw'); + } catch (err) { + expect((err as DataStructError).code).toBe('SCHEMA_MISMATCH'); + } + }); + + it('includes the field path in error messages', () => { + try { + encode({ outer: { inner: -5 } }, { outer: { inner: t.u8 } }); + throw new Error('expected throw'); + } catch (err) { + const e = err as DataStructError; + expect(e.code).toBe('VALUE_OUT_OF_RANGE'); + expect(e.path).toBe('$.outer.inner'); + expect(e.message).toContain('$.outer.inner'); + } + }); + + it('throws INVALID_SCHEMA for malformed array schema', () => { + try { + encode([1, 2], [t.u8, t.u16]); + throw new Error('expected throw'); + } catch (err) { + expect((err as DataStructError).code).toBe('INVALID_SCHEMA'); + } + }); + + it('throws SCHEMA_MISMATCH when string codec receives a non-string', () => { + expect(() => encode(42 as never, t.string)).toThrow(/string requires string/); + }); + + it('throws SCHEMA_MISMATCH when shortBytes / bytes receive non-Uint8Array', () => { + expect(() => encode([1, 2, 3] as never, t.shortBytes)).toThrow(/Uint8Array/); + expect(() => encode('not bytes' as never, t.bytes)).toThrow(/Uint8Array/); + }); + + it('throws BYTES_TOO_LONG for shortBytes > 65535', () => { + const huge = new Uint8Array(65536); + expect(() => encode(huge, t.shortBytes)).toThrow(/shortBytes length/); + }); + + it('throws SCHEMA_MISMATCH for non-object value against struct schema', () => { + expect(() => encode(null as never, { a: t.u8 })).toThrow(/expected object/); + expect(() => encode([1] as never, { a: t.u8 })).toThrow(/expected object/); + }); + + it('throws SCHEMA_MISMATCH when decode input is not Uint8Array', () => { + expect(() => decode('not bytes' as never, t.u8)).toThrow(/Uint8Array/); + }); + + it('throws VALUE_OUT_OF_RANGE for non-bigint i64/u64', () => { + expect(() => encode(5 as never, t.i64)).toThrow(/bigint/); + expect(() => encode(-1n, t.u64)).toThrow(/bigint/); + }); + + it('throws SCHEMA_MISMATCH for bool with non-boolean input', () => { + expect(() => encode(1 as never, t.bool)).toThrow(/bool requires boolean/); + }); + + it('throws VALUE_OUT_OF_RANGE for non-number floats', () => { + expect(() => encode('nope' as never, t.f64)).toThrow(/f64/); + }); +}); diff --git a/test/roundtrip.test.ts b/test/roundtrip.test.ts new file mode 100644 index 0000000..b5bac3d --- /dev/null +++ b/test/roundtrip.test.ts @@ -0,0 +1,105 @@ +import { Buffer } from 'node:buffer'; +import { describe, expect, expectTypeOf, it } from 'vitest'; +import { decode, encode, struct, t } from '../src/index.js'; +import type { Infer } from '../src/index.js'; + +describe('roundtrip', () => { + it('deeply nested struct', () => { + const schema = { + nested: { nested2: { nested3: { nested4: { nested5: t.u8 } } } }, + }; + const value = { nested: { nested2: { nested3: { nested4: { nested5: 42 } } } } }; + expect(decode(encode(value, schema), schema)).toEqual(value); + }); + + it('list of list', () => { + const schema = [[t.i16]]; + const value = [ + [90, 10, 101], + [20, 30, 400], + [100, 110, 1], + ]; + expect(decode(encode(value, schema), schema)).toEqual(value); + }); + + it('list of list of list of list of object', () => { + const schema = { list: [[[[{ i: t.i16 }]]]] }; + const value = { + list: [[[[{ i: 1 }, { i: 2 }, { i: 3 }], [{ i: 1 }]], [[{ i: 1 }]]]], + }; + expect(decode(encode(value, schema), schema)).toEqual(value); + }); + + it('struct() factory produces same output as encode/decode', () => { + const codec = struct({ a: t.u32, b: t.string }); + const v = { a: 1234567, b: 'hello' }; + const buf = codec.encode(v); + expect(codec.decode(buf)).toEqual(v); + expect(codec.sizeOf(v)).toBe(buf.byteLength); + }); + + it('decoded shortBytes / bytes are independent copies of the source', () => { + const schema = { x: t.shortBytes }; + const src = Buffer.from([0x00, 0x02, 0xaa, 0xbb]); + const { x } = decode(src, schema); + src[2] = 0; + expect(x[0]).toBe(0xaa); + }); + + it('accepts plain Uint8Array as input', () => { + const buf = encode({ b: Buffer.from([1, 2, 3]) }, { b: t.shortBytes }); + const view = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength); + const out = decode(view, { b: t.shortBytes }); + expect(Array.from(out.b)).toEqual([1, 2, 3]); + }); + + it('all primitive number types roundtrip at boundaries', () => { + const cases: { codec: { tag: number }; values: number[] }[] = [ + { codec: t.i8, values: [-128, 0, 127] }, + { codec: t.u8, values: [0, 255] }, + { codec: t.i16, values: [-32768, 0, 32767] }, + { codec: t.u16, values: [0, 65535] }, + { codec: t.i32, values: [-2147483648, 0, 2147483647] }, + { codec: t.u32, values: [0, 4294967295] }, + ]; + for (const { codec, values } of cases) { + for (const v of values) { + const buf = encode(v as never, codec as never); + expect(decode(buf, codec as never)).toBe(v); + } + } + }); + + it('bigint i64 / u64 roundtrip', () => { + expect(decode(encode(-(1n << 63n), t.i64), t.i64)).toBe(-(1n << 63n)); + expect(decode(encode((1n << 63n) - 1n, t.i64), t.i64)).toBe((1n << 63n) - 1n); + expect(decode(encode((1n << 64n) - 1n, t.u64), t.u64)).toBe((1n << 64n) - 1n); + }); + + it('little-endian codecs produce LE bytes', () => { + expect(Array.from(encode(0x1234, t.le.u16))).toEqual([0x34, 0x12]); + expect(Array.from(encode(0x12345678, t.le.u32))).toEqual([0x78, 0x56, 0x34, 0x12]); + expect(decode(encode(-1, t.le.i32), t.le.i32)).toBe(-1); + }); + + it('empty string and empty array roundtrip', () => { + expect(decode(encode('', t.string), t.string)).toBe(''); + expect(decode(encode([], [t.u8]), [t.u8])).toEqual([]); + }); + + it('Infer derives the value type', () => { + const schema = { + id: t.u32, + name: t.string, + tags: [t.string], + active: t.bool, + } as const; + type Value = Infer; + expectTypeOf().toEqualTypeOf<{ + id: number; + name: string; + tags: string[]; + active: boolean; + }>(); + }); +}); diff --git a/test/tests.js b/test/tests.js deleted file mode 100644 index 3bab424..0000000 --- a/test/tests.js +++ /dev/null @@ -1,219 +0,0 @@ -/** - * TODO: tests for compatibility with other rsa libraries - */ - -var assert = require("chai").assert; -DataTypes = require("../src/index").DataTypes; -DataReader = require("../src/index").DataReader; -DataWriter = require("../src/index").DataWriter; - -suite("DataStruct", function(){ - - var dataBundle = { - 'basic types': { - object: 42, - scheme: DataTypes.uint16, - buffer: new Buffer([0x00, 0x2a]) - }, - - 'flat structure (basic types)': { - object: { - boolean: true, - int8: -126, - uint8: 255, - int16: -1000, - uint16: 65535, - int32: -100000, - uint32: 100000, - float: 1230000, - double: -123.456 - }, - - scheme: { - boolean: DataTypes.boolean, - int8: DataTypes.int8, - uint8: DataTypes.uint8, - int16: DataTypes.int16, - uint16: DataTypes.uint16, - int32: DataTypes.int32, - uint32: DataTypes.uint32, - float: DataTypes.float, - double: DataTypes.double - }, - - buffer: new Buffer([ - 0x01, // boolean - 0x82, // int8 - 0xff, // uint8 - 0xfc, 0x18, // int16 - 0xff, 0xff, // uint16 - 0xff, 0xfe, 0x79, 0x60, // int32 - 0x00, 0x01, 0x86, 0xa0, // uint32 - 0x49, 0x96, 0x25, 0x80, // float - 0xc0, 0x5e, 0xdd, 0x2f, 0x1a, 0x9f, 0xbe, 0x77, // double - ]) - }, - - 'flat structure (composite types)': { - object: { - string: 'Some text + юникод', - shortBuffer: new Buffer([1,2,3]), - buffer: new Buffer([0xaa,0xbb,0xcc]) - }, - - scheme: { - string: DataTypes.string, - shortBuffer: DataTypes.shortBuffer, - buffer: DataTypes.buffer - }, - - buffer: new Buffer([ - 0x00, 0x18, // string length (uint16 BE) - 0x53, 0x6f, 0x6d, 0x65, 0x20, 0x74, 0x65, 0x78, 0x74, 0x20, 0x2b, 0x20, 0xd1, 0x8e, 0xd0, 0xbd, 0xd0, 0xb8, 0xd0, 0xba, 0xd0, 0xbe, 0xd0, 0xb4, // string - 0x00, 0x03, // short buffer length (uint16 BE) - 0x01, 0x02, 0x03, // shortBuffer - 0x00, 0x00, 0x00, 0x03, // buffer length (uint16 BE) - 0xaa, 0xbb, 0xcc // buffer - ]) - }, - - 'list of strings': { - object: { - values: [ - 'string1', - 'string20', - 'string300' - ] - }, - - scheme: { - values: [DataTypes.string] - }, - - buffer: new Buffer([ - 0x00, 0x03, // list length (uint16 BE) - 0x00, 0x07, // string length (uint16 BE) - 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x31, // string1 - 0x00, 0x08, // string length (uint16 BE) - 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x32, 0x30, // string20 - 0x00, 0x09, // string length (uint16 BE) - 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x33, 0x30, 0x30 // string300 - ]) - }, - - 'list of objects': { - object: { - friends: [ - { - name: 'Alice', - age: 21 - }, - { - name: 'Bob', - age: 17 - } - ], - numbers: [ - 0x10, 0x26, 0x61, 0xff - ] - }, - - scheme: { - friends: [{ - name: DataTypes.string, - age: DataTypes.uint32 - }], - numbers:[DataTypes.uint8] - }, - - buffer: new Buffer([ - 0x00, 0x02, // friends list length (uint16 BE) - 0x00, 0x05, // string length (uint16 BE) - 0x41, 0x6c, 0x69, 0x63, 0x65, // string - 0x00, 0x00, 0x00, 0x15, //uint32 - 0x00, 0x03, // string length (uint16 BE) - 0x42, 0x6f, 0x62, // string - 0x00, 0x00, 0x00, 0x11, //uint32 - 0x00, 0x04, // numbers list length (uint16 BE) - 0x10, 0x26, 0x61, 0xff // numbers - ]) - } - - }; - - var dataBundleExtend = { - nested: { - - object: { nested: { nested2: { nested3: { nested4: { nested5: 42 } } } } }, - - scheme: { nested: { nested2: { nested3: { nested4: { nested5: DataTypes.uint8 } } } } } - - }, - - 'list of list': { - object: [ - [90,10,101], - [20,30,400], - [100,110,1] - ], - - scheme: [[DataTypes.int16]] - - }, - - 'list of list of list of list of object': { - object: { - list: [ - [[ - [{i: 1},{i: 2},{i: 3}],[{i: 1}] - ], - [ - [{i: 1}] - ]] - ] - }, - - scheme: { - list: [[[[{i: DataTypes.int16}]]]] - } - } - - }; - - suite("Buffer to Object convert", function(){ - for(var suite in dataBundle) { - var data = dataBundle[suite]; - (function(suite, data) { - test("should return buffer for '" + suite + "' suite", function () { - var res = DataReader(data.buffer, data.scheme); - assert.deepEqual(res, data.object); - }); - })(suite, data); - } - }); - - suite("Object to Buffer convert", function(){ - for(var suite in dataBundle) { - var data = dataBundle[suite]; - (function(suite, data) { - test("should return buffer for '" + suite + "' suite", function () { - var res = DataWriter(data.object, data.scheme); - assert.deepEqual(res, data.buffer); - }); - })(suite, data); - } - }); - - suite("Object to Buffer to Object convert", function() { - for(var suite in dataBundleExtend) { - var data = dataBundleExtend[suite]; - (function(suite, data) { - test("should convert to buffer and read same for '" + suite + "' suite", function () { - var buf = DataWriter(data.object, data.scheme); - var obj = DataReader(buf, data.scheme); - assert.deepEqual(obj, data.object); - }); - })(suite, data); - } - }); -}); \ No newline at end of file diff --git a/test/wire-format.test.ts b/test/wire-format.test.ts new file mode 100644 index 0000000..08060b2 --- /dev/null +++ b/test/wire-format.test.ts @@ -0,0 +1,111 @@ +import { Buffer } from 'node:buffer'; +import { describe, expect, it } from 'vitest'; +import { decode, encode, t } from '../src/index.js'; +import type { Schema } from '../src/index.js'; + +interface Fixture { + readonly name: string; + readonly schema: Schema; + readonly value: unknown; + readonly bytes: Buffer; +} + +const fixtures: Fixture[] = [ + { + name: 'leaf u16', + schema: t.u16, + value: 42, + bytes: Buffer.from([0x00, 0x2a]), + }, + { + name: 'flat numeric struct', + schema: { + boolean: t.bool, + int8: t.i8, + uint8: t.u8, + int16: t.i16, + uint16: t.u16, + int32: t.i32, + uint32: t.u32, + float: t.f32, + double: t.f64, + }, + value: { + boolean: true, + int8: -126, + uint8: 255, + int16: -1000, + uint16: 65535, + int32: -100000, + uint32: 100000, + float: 1230000, + double: -123.456, + }, + bytes: Buffer.from([ + 0x01, 0x82, 0xff, 0xfc, 0x18, 0xff, 0xff, 0xff, 0xfe, 0x79, 0x60, 0x00, 0x01, 0x86, 0xa0, + 0x49, 0x96, 0x25, 0x80, 0xc0, 0x5e, 0xdd, 0x2f, 0x1a, 0x9f, 0xbe, 0x77, + ]), + }, + { + name: 'string + bytes variants', + schema: { + str: t.string, + shortBytes: t.shortBytes, + bytes: t.bytes, + }, + value: { + str: 'Some text + юникод', + shortBytes: new Uint8Array([1, 2, 3]), + bytes: new Uint8Array([0xaa, 0xbb, 0xcc]), + }, + bytes: Buffer.from([ + 0x00, 0x18, 0x53, 0x6f, 0x6d, 0x65, 0x20, 0x74, 0x65, 0x78, 0x74, 0x20, 0x2b, 0x20, 0xd1, + 0x8e, 0xd0, 0xbd, 0xd0, 0xb8, 0xd0, 0xba, 0xd0, 0xbe, 0xd0, 0xb4, 0x00, 0x03, 0x01, 0x02, + 0x03, 0x00, 0x00, 0x00, 0x03, 0xaa, 0xbb, 0xcc, + ]), + }, + { + name: 'list of strings', + schema: { values: [t.string] }, + value: { values: ['string1', 'string20', 'string300'] }, + bytes: Buffer.from([ + 0x00, 0x03, 0x00, 0x07, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x31, 0x00, 0x08, 0x73, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x32, 0x30, 0x00, 0x09, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x33, + 0x30, 0x30, + ]), + }, + { + name: 'list of objects + list of u8', + schema: { + friends: [{ name: t.string, age: t.u32 }], + numbers: [t.u8], + }, + value: { + friends: [ + { name: 'Alice', age: 21 }, + { name: 'Bob', age: 17 }, + ], + numbers: [0x10, 0x26, 0x61, 0xff], + }, + bytes: Buffer.from([ + 0x00, 0x02, 0x00, 0x05, 0x41, 0x6c, 0x69, 0x63, 0x65, 0x00, 0x00, 0x00, 0x15, 0x00, 0x03, + 0x42, 0x6f, 0x62, 0x00, 0x00, 0x00, 0x11, 0x00, 0x04, 0x10, 0x26, 0x61, 0xff, + ]), + }, +]; + +describe('wire format (byte-identical to legacy v0.0.11)', () => { + for (const fixture of fixtures) { + describe(fixture.name, () => { + it('encode produces the golden byte sequence', () => { + const encoded = encode(fixture.value as never, fixture.schema); + expect(Buffer.from(encoded).equals(fixture.bytes)).toBe(true); + }); + + it('decode reverses the golden byte sequence', () => { + const decoded = decode(fixture.bytes, fixture.schema); + expect(decoded).toEqual(fixture.value); + }); + }); + } +}); diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..15a6c68 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "dist" + }, + "include": ["src"], + "exclude": ["test", "benchmark", "**/*.test.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..41a7bd0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "ESNext", + "moduleResolution": "Bundler", + "moduleDetection": "force", + "allowImportingTsExtensions": false, + "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noFallthroughCasesInSwitch": true, + "exactOptionalPropertyTypes": true, + "noEmit": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["src", "test", "benchmark", "*.config.ts"] +} diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..64d0edd --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['esm', 'cjs'], + outExtension: ({ format }) => ({ js: format === 'esm' ? '.mjs' : '.cjs' }), + dts: true, + sourcemap: true, + clean: true, + treeshake: true, + target: 'node20.9', + splitting: false, + minify: false, + shims: false, +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..4834d4d --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['test/**/*.test.ts'], + environment: 'node', + coverage: { + provider: 'v8', + reporter: ['text', 'html', 'lcov'], + include: ['src/**/*.ts'], + exclude: ['src/**/*.d.ts', 'src/types.ts'], + thresholds: { + lines: 90, + functions: 90, + branches: 85, + statements: 90, + }, + }, + }, +}); From 30054c535a7ed395c627d7a436ac2f6505032ced Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 16 May 2026 14:16:22 +0000 Subject: [PATCH 2/8] harden encoder/decoder and tighten CI - struct.encode: assert measure() size == write() bytes emitted; protects against allocUnsafe leaking uninitialised memory if a codec measure diverges from its write - struct decode: build output objects with Object.create(null) so a stray __proto__ key in an untrusted schema cannot pollute the prototype chain - string codec: TextDecoder(fatal:true), surfaces invalid UTF-8 as SCHEMA_MISMATCH instead of silently replacing with U+FFFD - ci: add audit job running `npm audit --audit-level=high --omit=dev` so high/critical advisories in production deps fail the build; dev-only moderate advisories (vitest dev-server chain) remain tolerated Adds tests covering invalid UTF-8 and null-prototype output. Coverage remains above threshold. --- .github/workflows/ci.yml | 13 +++++++++++++ CHANGELOG.md | 7 +++++++ src/codecs.ts | 13 +++++++++++-- src/struct.ts | 15 +++++++++++++-- test/errors.test.ts | 21 +++++++++++++++++++++ 5 files changed, 65 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e900381..f169b75 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,19 @@ jobs: - run: npm run format:check - run: npm run lint + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + # Fail only on high/critical advisories in production deps. + # Dev-only moderate advisories (vitest/vite/esbuild dev-server chain) are tolerated. + - run: npm audit --audit-level=high --omit=dev + typecheck: runs-on: ubuntu-latest steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 25456d7..472803a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Security + +- Decoded structs are now created with a `null` prototype, neutralising prototype-pollution risk if a schema is built from untrusted input that contains a `__proto__` key. +- String decoder now uses `TextDecoder` in `fatal: true` mode and throws `SCHEMA_MISMATCH` on malformed UTF-8 (previously such bytes were silently replaced with `U+FFFD`). +- `struct.encode` now asserts that `write()` emits exactly as many bytes as `measure()` reported. Defends against codec bugs leaking uninitialised memory from `Buffer.allocUnsafe`. +- CI now runs `npm audit --audit-level=high --omit=dev` and fails on high/critical advisories in production dependencies. + ## [0.1.0] - 2026-05-16 ### Added diff --git a/src/codecs.ts b/src/codecs.ts index 9288291..58162cb 100644 --- a/src/codecs.ts +++ b/src/codecs.ts @@ -2,7 +2,7 @@ import { DataStructError } from './errors.js'; import type { Codec, CodecImpl } from './types.js'; const textEncoder = new TextEncoder(); -const textDecoder = new TextDecoder('utf-8'); +const textDecoder = new TextDecoder('utf-8', { fatal: true }); const U8_MAX = 0xff; const U16_MAX = 0xffff; @@ -143,7 +143,16 @@ function makeString(littleEndian: boolean, tag: number): Codec { const length = view.getUint16(offset, littleEndian); offset += 2; ensureCapacity(view, offset, length, path); - const value = textDecoder.decode(bytes.subarray(offset, offset + length)); + let value: string; + try { + value = textDecoder.decode(bytes.subarray(offset, offset + length)); + } catch (cause) { + throw new DataStructError('SCHEMA_MISMATCH', 'invalid UTF-8 in string field', { + path, + offset, + cause, + }); + } return { value, offset: offset + length }; }, }; diff --git a/src/struct.ts b/src/struct.ts index 74f51c4..3feaef1 100644 --- a/src/struct.ts +++ b/src/struct.ts @@ -125,7 +125,9 @@ function structCodec( return offset; }, read(view, bytes, offset, path) { - const out: Record = {}; + // Null-prototype output neutralizes a __proto__ key in the schema: + // out['__proto__'] = obj would otherwise mutate the prototype chain. + const out = Object.create(null) as Record; for (const [key, codec] of fields) { const r = codec.impl.read(view, bytes, offset, `${path}.${key}`); out[key] = r.value; @@ -155,7 +157,16 @@ export function struct(schema: S): Struct { const out = Buffer.allocUnsafe(size); const view = new DataView(out.buffer, out.byteOffset, out.byteLength); plan.cursor = 0; - codec.impl.write(view, out, 0, value, plan, '$'); + const written = codec.impl.write(view, out, 0, value, plan, '$'); + // Defense in depth: allocUnsafe returned uninitialized memory; if a codec's + // measure() over-counts versus what write() actually emits, we would leak + // pool bytes. This assertion makes that class of bug visible instead of silent. + if (written !== size) { + throw new DataStructError( + 'INVALID_SCHEMA', + `internal size mismatch: measured ${size}, wrote ${written}`, + ); + } return out; }, decode(input): Infer { diff --git a/test/errors.test.ts b/test/errors.test.ts index 531cdfa..71fd274 100644 --- a/test/errors.test.ts +++ b/test/errors.test.ts @@ -113,4 +113,25 @@ describe('errors', () => { it('throws VALUE_OUT_OF_RANGE for non-number floats', () => { expect(() => encode('nope' as never, t.f64)).toThrow(/f64/); }); + + it('throws SCHEMA_MISMATCH on invalid UTF-8 in string field', () => { + // 0x00 0x02 = uint16 length 2, then 0xc3 0x28 = invalid UTF-8 sequence + const bad = new Uint8Array([0x00, 0x02, 0xc3, 0x28]); + try { + decode(bad, t.string); + throw new Error('expected throw'); + } catch (err) { + const e = err as DataStructError; + expect(e.code).toBe('SCHEMA_MISMATCH'); + expect(e.message).toMatch(/UTF-8/); + } + }); + + it('decoded structs have null prototype (proto-pollution defence)', () => { + const { decoded } = (() => { + const buf = encode({ x: 1 }, { x: t.u8 }); + return { decoded: decode(buf, { x: t.u8 }) }; + })(); + expect(Object.getPrototypeOf(decoded)).toBeNull(); + }); }); From 1bc62efbf718af02ff48006c20fb156f7c3460fd Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 17 May 2026 06:28:37 +0000 Subject: [PATCH 3/8] test: pin Infer behaviour for top-level array schemas Regression guard that the existing Schema/Infer types correctly compute the value shape for `[codec]`, `[codec] as const`, `[[codec]]` and `[{...}]` forms, including the struct() factory accepting an array schema directly. --- test/roundtrip.test.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/roundtrip.test.ts b/test/roundtrip.test.ts index b5bac3d..34f9879 100644 --- a/test/roundtrip.test.ts +++ b/test/roundtrip.test.ts @@ -102,4 +102,32 @@ describe('roundtrip', () => { active: boolean; }>(); }); + + it('Infer works for top-level array schemas (no object wrapper)', () => { + // Plain `[t.u8]` literal — must infer as number[] + expectTypeOf>().toEqualTypeOf< + number[] + >(); + + // `as const` form (readonly tuple) + const ro = [t.string] as const; + expectTypeOf>().toEqualTypeOf(); + + // Nested arrays + const matrix = [[t.i16]]; + expectTypeOf>().toEqualTypeOf(); + + // Array of structs + const list = [{ id: t.u32, name: t.string }]; + expectTypeOf>().toEqualTypeOf<{ id: number; name: string }[]>(); + + // struct() factory accepting an array schema directly + const codec = struct([t.u8]); + expectTypeOf(codec.encode).parameter(0).toEqualTypeOf(); + expectTypeOf(codec.decode).returns.toEqualTypeOf(); + + // Runtime roundtrip on the same array codec + const buf = codec.encode([1, 2, 3, 255]); + expect(codec.decode(buf)).toEqual([1, 2, 3, 255]); + }); }); From 191e7c1cd21a7c1388623c6a90d4e2d8619426c7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 17 May 2026 07:13:34 +0000 Subject: [PATCH 4/8] perf: enable tsup minify --- tsup.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsup.config.ts b/tsup.config.ts index 64d0edd..369bcf2 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -10,6 +10,6 @@ export default defineConfig({ treeshake: true, target: 'node20.9', splitting: false, - minify: false, + minify: true, shims: false, }); From b8bfab15ff663e4fc14399ff7155a1d254d527fa Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 17 May 2026 07:22:50 +0000 Subject: [PATCH 5/8] perf: lazy path tracking via try/catch+prefix instead of per-field string alloc --- src/codecs.ts | 96 ++++++++++++++++---------------------- src/struct.ts | 126 ++++++++++++++++++++++++++++++-------------------- src/types.ts | 13 ++---- 3 files changed, 118 insertions(+), 117 deletions(-) diff --git a/src/codecs.ts b/src/codecs.ts index 58162cb..9d5e178 100644 --- a/src/codecs.ts +++ b/src/codecs.ts @@ -17,56 +17,49 @@ const I64_MIN = -(1n << 63n); const I64_MAX = (1n << 63n) - 1n; const U64_MAX = (1n << 64n) - 1n; -function ensureCapacity(view: DataView, offset: number, need: number, path: string): void { +// Leaves throw without path context; parent codecs (struct/array) prefix +// the segment via prefixPath() in struct.ts. This keeps the happy path +// allocation-free for the field-iteration string concatenations. + +function ensureCapacity(view: DataView, offset: number, need: number): void { if (offset + need > view.byteLength) { throw new DataStructError( 'BUFFER_UNDERFLOW', `need ${need} byte(s) at offset ${offset}, but buffer has ${view.byteLength}`, - { path, offset }, + { offset }, ); } } -function ensureInt(value: number, min: number, max: number, type: string, path: string): void { +function ensureInt(value: number, min: number, max: number, type: string): void { if (typeof value !== 'number' || !Number.isInteger(value) || value < min || value > max) { throw new DataStructError( 'VALUE_OUT_OF_RANGE', `${type} requires integer in [${min}, ${max}], got ${value}`, - { - path, - }, ); } } -function ensureFiniteNumber(value: number, type: string, path: string): void { +function ensureFiniteNumber(value: number, type: string): void { if (typeof value !== 'number') { - throw new DataStructError( - 'VALUE_OUT_OF_RANGE', - `${type} requires number, got ${typeof value}`, - { path }, - ); + throw new DataStructError('VALUE_OUT_OF_RANGE', `${type} requires number, got ${typeof value}`); } } -function ensureBigInt(value: bigint, min: bigint, max: bigint, type: string, path: string): void { +function ensureBigInt(value: bigint, min: bigint, max: bigint, type: string): void { if (typeof value !== 'bigint' || value < min || value > max) { throw new DataStructError( 'VALUE_OUT_OF_RANGE', `${type} requires bigint in [${min}, ${max}], got ${value}`, - { - path, - }, ); } } -function ensureUint8Array(value: unknown, type: string, path: string): asserts value is Uint8Array { +function ensureUint8Array(value: unknown, type: string): asserts value is Uint8Array { if (!(value instanceof Uint8Array)) { throw new DataStructError( 'SCHEMA_MISMATCH', `${type} requires Uint8Array, got ${typeof value}`, - { path }, ); } } @@ -76,17 +69,17 @@ function fixed( size: number, write: (view: DataView, offset: number, value: T) => void, read: (view: DataView, offset: number) => T, - validate: (value: T, path: string) => void, + validate: (value: T) => void, ): Codec { const impl: CodecImpl = { measure: () => size, - write(view, _bytes, offset, value, _plan, path) { - validate(value, path); + write(view, _bytes, offset, value) { + validate(value); write(view, offset, value); return offset + size; }, - read(view, _bytes, offset, path) { - ensureCapacity(view, offset, size, path); + read(view, _bytes, offset) { + ensureCapacity(view, offset, size); return { value: read(view, offset), offset: offset + size }; }, }; @@ -95,37 +88,30 @@ function fixed( const boolImpl: CodecImpl = { measure: () => 1, - write(view, _bytes, offset, value, _plan, path) { + write(view, _bytes, offset, value) { if (typeof value !== 'boolean') { - throw new DataStructError('SCHEMA_MISMATCH', `bool requires boolean, got ${typeof value}`, { - path, - }); + throw new DataStructError('SCHEMA_MISMATCH', `bool requires boolean, got ${typeof value}`); } view.setUint8(offset, value ? 1 : 0); return offset + 1; }, - read(view, _bytes, offset, path) { - ensureCapacity(view, offset, 1, path); + read(view, _bytes, offset) { + ensureCapacity(view, offset, 1); return { value: view.getInt8(offset) !== 0, offset: offset + 1 }; }, }; function makeString(littleEndian: boolean, tag: number): Codec { const impl: CodecImpl = { - measure(value, plan, path) { + measure(value, plan) { if (typeof value !== 'string') { - throw new DataStructError( - 'SCHEMA_MISMATCH', - `string requires string, got ${typeof value}`, - { path }, - ); + throw new DataStructError('SCHEMA_MISMATCH', `string requires string, got ${typeof value}`); } const encoded = textEncoder.encode(value); if (encoded.byteLength > U16_MAX) { throw new DataStructError( 'STRING_TOO_LONG', `string UTF-8 byte length ${encoded.byteLength} exceeds 65535`, - { path }, ); } plan.strings.push(encoded); @@ -138,17 +124,16 @@ function makeString(littleEndian: boolean, tag: number): Codec { bytes.set(encoded, offset); return offset + encoded.byteLength; }, - read(view, bytes, offset, path) { - ensureCapacity(view, offset, 2, path); + read(view, bytes, offset) { + ensureCapacity(view, offset, 2); const length = view.getUint16(offset, littleEndian); offset += 2; - ensureCapacity(view, offset, length, path); + ensureCapacity(view, offset, length); let value: string; try { value = textDecoder.decode(bytes.subarray(offset, offset + length)); } catch (cause) { throw new DataStructError('SCHEMA_MISMATCH', 'invalid UTF-8 in string field', { - path, offset, cause, }); @@ -161,13 +146,12 @@ function makeString(littleEndian: boolean, tag: number): Codec { function makeShortBytes(littleEndian: boolean, tag: number): Codec { const impl: CodecImpl = { - measure(value, _plan, path) { - ensureUint8Array(value, 'shortBytes', path); + measure(value) { + ensureUint8Array(value, 'shortBytes'); if (value.byteLength > U16_MAX) { throw new DataStructError( 'BYTES_TOO_LONG', `shortBytes length ${value.byteLength} exceeds 65535`, - { path }, ); } return 2 + value.byteLength; @@ -178,11 +162,11 @@ function makeShortBytes(littleEndian: boolean, tag: number): Codec { bytes.set(value, offset); return offset + value.byteLength; }, - read(view, bytes, offset, path) { - ensureCapacity(view, offset, 2, path); + read(view, bytes, offset) { + ensureCapacity(view, offset, 2); const length = view.getUint16(offset, littleEndian); offset += 2; - ensureCapacity(view, offset, length, path); + ensureCapacity(view, offset, length); const value = new Uint8Array(length); value.set(bytes.subarray(offset, offset + length)); return { value, offset: offset + length }; @@ -193,13 +177,12 @@ function makeShortBytes(littleEndian: boolean, tag: number): Codec { function makeBytes(littleEndian: boolean, tag: number): Codec { const impl: CodecImpl = { - measure(value, _plan, path) { - ensureUint8Array(value, 'bytes', path); + measure(value) { + ensureUint8Array(value, 'bytes'); if (value.byteLength > U32_MAX) { throw new DataStructError( 'BYTES_TOO_LONG', `bytes length ${value.byteLength} exceeds 4294967295`, - { path }, ); } return 4 + value.byteLength; @@ -210,11 +193,11 @@ function makeBytes(littleEndian: boolean, tag: number): Codec { bytes.set(value, offset); return offset + value.byteLength; }, - read(view, bytes, offset, path) { - ensureCapacity(view, offset, 4, path); + read(view, bytes, offset) { + ensureCapacity(view, offset, 4); const length = view.getUint32(offset, littleEndian); offset += 4; - ensureCapacity(view, offset, length, path); + ensureCapacity(view, offset, length); const value = new Uint8Array(length); value.set(bytes.subarray(offset, offset + length)); return { value, offset: offset + length }; @@ -256,7 +239,7 @@ function intCodec( size, (view, offset, value) => setter(view, offset, value, littleEndian), (view, offset) => getter(view, offset, littleEndian), - (value, path) => ensureInt(value, min, max, name, path), + (value) => ensureInt(value, min, max, name), ); } @@ -267,7 +250,7 @@ function floatCodec(tag: number, size: 4 | 8, name: string, littleEndian: boolea 4, (view, offset, value) => view.setFloat32(offset, value, littleEndian), (view, offset) => view.getFloat32(offset, littleEndian), - (value, path) => ensureFiniteNumber(value, name, path), + (value) => ensureFiniteNumber(value, name), ); } return fixed( @@ -275,7 +258,7 @@ function floatCodec(tag: number, size: 4 | 8, name: string, littleEndian: boolea 8, (view, offset, value) => view.setFloat64(offset, value, littleEndian), (view, offset) => view.getFloat64(offset, littleEndian), - (value, path) => ensureFiniteNumber(value, name, path), + (value) => ensureFiniteNumber(value, name), ); } @@ -296,11 +279,10 @@ function bigIntCodec( }, (view, offset) => signed ? view.getBigInt64(offset, littleEndian) : view.getBigUint64(offset, littleEndian), - (value, path) => ensureBigInt(value, min, max, name, path), + (value) => ensureBigInt(value, min, max, name), ); } -// Wire tags preserve legacy numeric ids where applicable for debug/introspection. const BE = false; const LE = true; diff --git a/src/struct.ts b/src/struct.ts index 3feaef1..0eb71e9 100644 --- a/src/struct.ts +++ b/src/struct.ts @@ -3,6 +3,7 @@ import { DataStructError } from './errors.js'; import type { AnyCodec, Codec, CodecImpl, EncodePlan, Infer, Schema } from './types.js'; const ARRAY_LENGTH_MAX = 0xffff; +const PATH_AT = ' at '; function isCodec(v: unknown): v is AnyCodec { return ( @@ -14,74 +15,99 @@ function isCodec(v: unknown): v is AnyCodec { ); } -function compile(schema: Schema, path: string): AnyCodec { +// Re-throw a DataStructError after prefixing its path with a new segment. +// `segment` is either `.field` or `[index]`. Only allocated on the error path. +function rethrowWithPrefix(err: unknown, segment: string): never { + if (!(err instanceof DataStructError)) throw err; + const oldPath = err.path === '$' ? '' : err.path.slice(1); + const newPath = `$${segment}${oldPath}`; + // DataStructError's constructor appends " at " to the message when + // a path is provided; strip the previous suffix so we don't accumulate. + const oldSuffix = err.path === '$' ? '' : `${PATH_AT}${err.path}`; + const baseMessage = + oldSuffix && err.message.endsWith(oldSuffix) + ? err.message.slice(0, -oldSuffix.length) + : err.message; + const opts: { path: string; offset?: number; cause?: unknown } = { path: newPath }; + if (err.offset !== undefined) opts.offset = err.offset; + opts.cause = err.cause ?? err; + throw new DataStructError(err.code, baseMessage, opts); +} + +function compile(schema: Schema): AnyCodec { if (isCodec(schema)) return schema; if (Array.isArray(schema)) { if (schema.length !== 1) { throw new DataStructError( 'INVALID_SCHEMA', `array schema must have exactly one element descriptor, got ${schema.length}`, - { path }, ); } const element = schema[0]; if (element === undefined) { - throw new DataStructError('INVALID_SCHEMA', 'array schema element is undefined', { path }); + throw new DataStructError('INVALID_SCHEMA', 'array schema element is undefined'); } - return arrayCodec(compile(element, `${path}[]`)); + return arrayCodec(compile(element)); } if (typeof schema === 'object' && schema !== null) { - return structCodec(schema as Record, path); + return structCodec(schema as Record); } - throw new DataStructError('INVALID_SCHEMA', `unsupported schema node: ${String(schema)}`, { - path, - }); + throw new DataStructError('INVALID_SCHEMA', `unsupported schema node: ${String(schema)}`); } function arrayCodec(element: AnyCodec): Codec { const impl: CodecImpl = { - measure(value, plan, path) { + measure(value, plan) { if (!Array.isArray(value)) { - throw new DataStructError('SCHEMA_MISMATCH', `expected array, got ${typeof value}`, { - path, - }); + throw new DataStructError('SCHEMA_MISMATCH', `expected array, got ${typeof value}`); } if (value.length > ARRAY_LENGTH_MAX) { throw new DataStructError( 'ARRAY_TOO_LONG', `array length ${value.length} exceeds ${ARRAY_LENGTH_MAX}`, - { path }, ); } let size = 2; for (let i = 0; i < value.length; i++) { - size += element.impl.measure(value[i], plan, `${path}[${i}]`); + try { + size += element.impl.measure(value[i], plan); + } catch (e) { + rethrowWithPrefix(e, `[${i}]`); + } } return size; }, - write(view, bytes, offset, value, plan, path) { + write(view, bytes, offset, value, plan) { view.setUint16(offset, value.length, false); offset += 2; for (let i = 0; i < value.length; i++) { - offset = element.impl.write(view, bytes, offset, value[i], plan, `${path}[${i}]`); + try { + offset = element.impl.write(view, bytes, offset, value[i], plan); + } catch (e) { + rethrowWithPrefix(e, `[${i}]`); + } } return offset; }, - read(view, bytes, offset, path) { + read(view, bytes, offset) { if (offset + 2 > view.byteLength) { throw new DataStructError( 'BUFFER_UNDERFLOW', `need 2 byte(s) for array length at offset ${offset}`, - { path, offset }, + { offset }, ); } const length = view.getUint16(offset, false); offset += 2; const out = new Array(length); for (let i = 0; i < length; i++) { - const r = element.impl.read(view, bytes, offset, `${path}[${i}]`); - out[i] = r.value; - offset = r.offset; + try { + const r = element.impl.read(view, bytes, offset); + out[i] = r.value; + offset = r.offset; + } catch (e) { + rethrowWithPrefix(e, `[${i}]`); + } } return { value: out, offset }; }, @@ -89,49 +115,52 @@ function arrayCodec(element: AnyCodec): Codec { return { tag: 0x100, impl }; } -function structCodec( - schema: Record, - basePath: string, -): Codec> { +function structCodec(schema: Record): Codec> { const keys = Object.keys(schema); const fields: [string, AnyCodec][] = keys.map((key) => { const child = schema[key]; if (child === undefined) { - throw new DataStructError('INVALID_SCHEMA', `field "${key}" is undefined`, { - path: `${basePath}.${key}`, - }); + throw new DataStructError('INVALID_SCHEMA', `field "${key}" is undefined`); } - return [key, compile(child, `${basePath}.${key}`)]; + return [key, compile(child)]; }); const impl: CodecImpl> = { - measure(value, plan, path) { + measure(value, plan) { if (typeof value !== 'object' || value === null || Array.isArray(value)) { - throw new DataStructError('SCHEMA_MISMATCH', `expected object, got ${typeof value}`, { - path, - }); + throw new DataStructError('SCHEMA_MISMATCH', `expected object, got ${typeof value}`); } const obj = value as Record; let size = 0; for (const [key, codec] of fields) { - size += codec.impl.measure(obj[key], plan, `${path}.${key}`); + try { + size += codec.impl.measure(obj[key], plan); + } catch (e) { + rethrowWithPrefix(e, `.${key}`); + } } return size; }, - write(view, bytes, offset, value, plan, path) { + write(view, bytes, offset, value, plan) { for (const [key, codec] of fields) { - offset = codec.impl.write(view, bytes, offset, value[key], plan, `${path}.${key}`); + try { + offset = codec.impl.write(view, bytes, offset, value[key], plan); + } catch (e) { + rethrowWithPrefix(e, `.${key}`); + } } return offset; }, - read(view, bytes, offset, path) { - // Null-prototype output neutralizes a __proto__ key in the schema: - // out['__proto__'] = obj would otherwise mutate the prototype chain. + read(view, bytes, offset) { const out = Object.create(null) as Record; for (const [key, codec] of fields) { - const r = codec.impl.read(view, bytes, offset, `${path}.${key}`); - out[key] = r.value; - offset = r.offset; + try { + const r = codec.impl.read(view, bytes, offset); + out[key] = r.value; + offset = r.offset; + } catch (e) { + rethrowWithPrefix(e, `.${key}`); + } } return { value: out, offset }; }, @@ -147,20 +176,17 @@ export interface Struct { } export function struct(schema: S): Struct { - const codec = compile(schema, '$'); + const codec = compile(schema); return { schema, encode(value): Buffer { const plan: EncodePlan = { strings: [], cursor: 0 }; - const size = codec.impl.measure(value, plan, '$'); + const size = codec.impl.measure(value, plan); const out = Buffer.allocUnsafe(size); const view = new DataView(out.buffer, out.byteOffset, out.byteLength); plan.cursor = 0; - const written = codec.impl.write(view, out, 0, value, plan, '$'); - // Defense in depth: allocUnsafe returned uninitialized memory; if a codec's - // measure() over-counts versus what write() actually emits, we would leak - // pool bytes. This assertion makes that class of bug visible instead of silent. + const written = codec.impl.write(view, out, 0, value, plan); if (written !== size) { throw new DataStructError( 'INVALID_SCHEMA', @@ -177,11 +203,11 @@ export function struct(schema: S): Struct { ); } const view = new DataView(input.buffer, input.byteOffset, input.byteLength); - const { value } = codec.impl.read(view, input, 0, '$'); + const { value } = codec.impl.read(view, input, 0); return value as Infer; }, sizeOf(value): number { - return codec.impl.measure(value, { strings: [], cursor: 0 }, '$'); + return codec.impl.measure(value, { strings: [], cursor: 0 }); }, }; } diff --git a/src/types.ts b/src/types.ts index 6b35b5c..a779506 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,16 +11,9 @@ export interface ReadResult { } export interface CodecImpl { - measure(value: T, plan: EncodePlan, path: string): number; - write( - view: DataView, - bytes: Uint8Array, - offset: number, - value: T, - plan: EncodePlan, - path: string, - ): number; - read(view: DataView, bytes: Uint8Array, offset: number, path: string): ReadResult; + measure(value: T, plan: EncodePlan): number; + write(view: DataView, bytes: Uint8Array, offset: number, value: T, plan: EncodePlan): number; + read(view: DataView, bytes: Uint8Array, offset: number): ReadResult; } export interface Codec { From 7b57df1b927ca6e99c70c199f2e58027a95d2e3e Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 17 May 2026 07:26:59 +0000 Subject: [PATCH 6/8] docs: changelog entries for perf and minify changes --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 472803a..e4e8224 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Performance + +- Field-iteration path strings (`${path}.${key}`, `${path}[${i}]`) are no longer allocated on the happy path. `struct` / `array` codecs now wrap each child call in `try { ... } catch (e) { rethrowWithPrefix(e, segment) }` and build the error path only when something throws. Measured on Node 22: + - `nested` encode (compiled): -33% + - `list of list` encode (compiled): -59% + - `list of list` decode (compiled): -45% + - `hero` encode / decode (compiled): -17% each + +### Changed + +- Build: `tsup` now emits minified output. `dist/index.mjs` shrinks from ~15 KB to ~7.5 KB (~-50%); behaviour is unchanged. + ### Security - Decoded structs are now created with a `null` prototype, neutralising prototype-pollution risk if a schema is built from untrusted input that contains a `__proto__` key. From ce03ee95edf9966052afed00bf3abaceaa562f8a Mon Sep 17 00:00:00 2001 From: rzcoder Date: Sun, 17 May 2026 19:38:01 +0900 Subject: [PATCH 7/8] feat: add examples --- examples/01-basics.ts | 17 +++++++++++++++++ examples/02-hero.ts | 34 ++++++++++++++++++++++++++++++++++ examples/03-functional.ts | 19 +++++++++++++++++++ examples/04-errors.ts | 29 +++++++++++++++++++++++++++++ examples/05-little-endian.ts | 19 +++++++++++++++++++ examples/06-size-of.ts | 20 ++++++++++++++++++++ examples/README.md | 22 ++++++++++++++++++++++ tsconfig.json | 2 +- 8 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 examples/01-basics.ts create mode 100644 examples/02-hero.ts create mode 100644 examples/03-functional.ts create mode 100644 examples/04-errors.ts create mode 100644 examples/05-little-endian.ts create mode 100644 examples/06-size-of.ts create mode 100644 examples/README.md diff --git a/examples/01-basics.ts b/examples/01-basics.ts new file mode 100644 index 0000000..5ae563d --- /dev/null +++ b/examples/01-basics.ts @@ -0,0 +1,17 @@ +import { decode, encode, struct, t } from '../src/index.js'; + +// Single primitive: a 4-byte big-endian unsigned int. +const u32Buf = encode(0xdead_beef, t.u32); +console.log('u32 buffer :', u32Buf.toString('hex')); +console.log('u32 decoded:', decode(u32Buf, t.u32).toString(16)); + +// String: 2-byte length prefix followed by UTF-8 bytes. +const strBuf = encode('hello', t.string); +console.log('string buffer :', strBuf.toString('hex')); +console.log('string decoded:', decode(strBuf, t.string)); + +// Object: fields encoded in declared key order, no header. +const User = struct({ id: t.u32, name: t.string, active: t.bool }); +const userBuf = User.encode({ id: 7, name: 'ada', active: true }); +console.log('user buffer :', userBuf.toString('hex')); +console.log('user decoded:', User.decode(userBuf)); diff --git a/examples/02-hero.ts b/examples/02-hero.ts new file mode 100644 index 0000000..21d8bb1 --- /dev/null +++ b/examples/02-hero.ts @@ -0,0 +1,34 @@ +import { struct, t } from '../src/index.js'; +import type { Infer } from '../src/index.js'; + +const Hero = struct({ + id: t.u32, + name: t.string, + hp: t.i16, + skills: [{ id: t.u16, description: t.string }], + playable: t.bool, + experience: t.u32, + position: { x: t.u16, y: t.u16 }, +}); + +type Hero = Infer; + +const cirno: Hero = { + id: 9, + name: 'CirnoBaka', + hp: 146, + skills: [ + { id: 34, description: 'freezing frogs' }, + { id: 16, description: 'perfect math' }, + ], + playable: false, + experience: 99_999_999, + position: { x: 2, y: 3 }, +}; + +const buf = Hero.encode(cirno); +const decoded = Hero.decode(buf); + +console.log(`encoded ${buf.byteLength} bytes:`, buf.toString('hex')); +console.log('decoded hero :', decoded); +console.log('roundtrip eq :', JSON.stringify(cirno) === JSON.stringify(decoded)); diff --git a/examples/03-functional.ts b/examples/03-functional.ts new file mode 100644 index 0000000..a7a763e --- /dev/null +++ b/examples/03-functional.ts @@ -0,0 +1,19 @@ +import { decode, encode, t } from '../src/index.js'; + +// One-shot encoding/decoding without keeping a compiled struct around. +// Schemas are plain values, so they compose freely. + +const Map2D = [[t.u8]]; +const map: number[][] = [ + [0, 1, 0], + [1, 0, 1], +]; +const mapBuf = encode(map, Map2D); +console.log('Map2D buffer :', mapBuf.toString('hex')); +console.log('Map2D decoded:', decode(mapBuf, Map2D)); + +const Names = [t.string]; +const names = ['alice', 'bob', 'carol']; +const namesBuf = encode(names, Names); +console.log('Names buffer :', namesBuf.toString('hex')); +console.log('Names decoded:', decode(namesBuf, Names)); diff --git a/examples/04-errors.ts b/examples/04-errors.ts new file mode 100644 index 0000000..d05ba95 --- /dev/null +++ b/examples/04-errors.ts @@ -0,0 +1,29 @@ +import { DataStructError, decode, encode, t } from '../src/index.js'; + +function runCase(label: string, fn: () => unknown): void { + try { + fn(); + console.log(`[${label}] no error — unexpected`); + } catch (err) { + if (!(err instanceof DataStructError)) throw err; + const offset = err.offset !== undefined ? ` offset=${err.offset}` : ''; + console.log(`[${label}] code=${err.code} path=${err.path}${offset} :: ${err.message}`); + } +} + +// Numeric value outside the leaf's range. +runCase('VALUE_OUT_OF_RANGE', () => encode(300, t.u8)); + +// UTF-8 byte length exceeds the 65535-byte string limit. +runCase('STRING_TOO_LONG', () => encode('a'.repeat(65_536), t.string)); + +// Array length exceeds the 65535-element limit. +runCase('ARRAY_TOO_LONG', () => encode(new Array(65_536).fill(0), [t.u8])); + +// Value shape does not match the schema (name should be a string). +const Person = { id: t.u32, name: t.string }; +type PersonValue = { id: number; name: string }; +runCase('SCHEMA_MISMATCH', () => encode({ id: 1, name: 42 } as unknown as PersonValue, Person)); + +// Decode would read past the end of the input — length prefix says 5, only 2 follow. +runCase('BUFFER_UNDERFLOW', () => decode(new Uint8Array([0x00, 0x05, 0x68, 0x65]), t.string)); diff --git a/examples/05-little-endian.ts b/examples/05-little-endian.ts new file mode 100644 index 0000000..d67fc41 --- /dev/null +++ b/examples/05-little-endian.ts @@ -0,0 +1,19 @@ +import { decode, encode, t } from '../src/index.js'; + +// Big-endian (default) vs little-endian for the same value. +const value = 0x1122_3344; +const beBuf = encode(value, t.u32); +const leBuf = encode(value, t.le.u32); + +console.log('be u32:', beBuf.toString('hex')); // 11223344 +console.log('le u32:', leBuf.toString('hex')); // 44332211 + +// The length prefix on strings also flips with t.le.string. +const beStr = encode('hi', t.string); +const leStr = encode('hi', t.le.string); +console.log('be string:', beStr.toString('hex')); // 00026869 +console.log('le string:', leStr.toString('hex')); // 02006869 + +// Roundtrip via the LE codec. +console.log('le u32 decoded:', decode(leBuf, t.le.u32).toString(16)); +console.log('le string decoded:', decode(leStr, t.le.string)); diff --git a/examples/06-size-of.ts b/examples/06-size-of.ts new file mode 100644 index 0000000..7a09547 --- /dev/null +++ b/examples/06-size-of.ts @@ -0,0 +1,20 @@ +import { struct, t } from '../src/index.js'; + +const Row = struct({ id: t.u32, name: t.string }); + +// sizeOf returns the exact byte length encode() will produce for that value. +const single = { id: 42, name: 'hello' }; +console.log('single sizeOf :', Row.sizeOf(single)); // 4 (u32) + 2 (len) + 5 (utf-8) = 11 +console.log('single encoded:', Row.encode(single).byteLength); + +// Pre-compute total bytes for a batch before allocating your own output buffer. +const batch = Array.from({ length: 5 }, (_, i) => ({ id: i, name: `item-${i}` })); +let totalRowBytes = 0; +for (const item of batch) totalRowBytes += Row.sizeOf(item); +console.log(`sum of ${batch.length} row sizes:`, totalRowBytes); + +// When you encode the array as a single value, the wire format adds 2 bytes +// for the array length prefix, so the result is totalRowBytes + 2. +const Batch = struct([{ id: t.u32, name: t.string }]); +const batchBuf = Batch.encode(batch); +console.log('encoded batch :', batchBuf.byteLength, '(rows + 2-byte array length prefix)'); diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..32861e6 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,22 @@ +# examples + +Runnable demos of the `data-struct` API. Each file is self-contained — run any of them with: + +```sh +npx tsx examples/01-basics.ts +``` + +| File | Shows | +| --- | --- | +| [01-basics.ts](01-basics.ts) | Primitive codecs (`t.u32`, `t.string`, `t.bool`) and a small `struct({...})` roundtrip. | +| [02-hero.ts](02-hero.ts) | Nested object + array of objects, with the decoded value typed via `Infer`. | +| [03-functional.ts](03-functional.ts) | Top-level `encode` / `decode` for one-shot use, without keeping a compiled struct. | +| [04-errors.ts](04-errors.ts) | Each `DataStructError` kind triggered and inspected (`code`, `path`, `offset`). | +| [05-little-endian.ts](05-little-endian.ts) | `t.le.*` variants and how the bytes differ from the big-endian default. | +| [06-size-of.ts](06-size-of.ts) | `sizeOf()` for pre-budgeting buffer sizes. | + +Examples import from `../src/index.js` so they work against the in-repo source. In your own project, swap that for: + +```ts +import { struct, t } from 'data-struct'; +``` diff --git a/tsconfig.json b/tsconfig.json index 41a7bd0..1098a9e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,5 +19,5 @@ "skipLibCheck": true, "types": ["node"] }, - "include": ["src", "test", "benchmark", "*.config.ts"] + "include": ["src", "test", "benchmark", "examples", "*.config.ts"] } From 67239a34a999e8460ca2bd1e8f49c17d53ea48b5 Mon Sep 17 00:00:00 2001 From: rzcoder Date: Sun, 17 May 2026 19:55:31 +0900 Subject: [PATCH 8/8] ci: move to Trusted Publisher releases --- .github/workflows/release.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1263473..0f21cda 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,6 +20,7 @@ jobs: cache: npm registry-url: 'https://registry.npmjs.org' + - run: npm install -g npm@latest - run: npm ci - run: npm run lint - run: npm run typecheck @@ -36,9 +37,7 @@ jobs: fi - name: Publish to npm - run: npm publish --provenance --access public - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npm publish --access public - name: Create GitHub Release uses: softprops/action-gh-release@v2