From 783709e6c7366b24f8767f5712ad6d79db58f64b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 31 May 2026 01:27:02 +0000 Subject: [PATCH 1/9] feat: add Date / ArrayBuffer / TypedArray tags, golden vectors, conformance docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - format.ts: add Tag.Date(40), Tag.Bytes(41), Tag.TypedArray(42) and ElementType enum, in sync with spec/FORMAT.md §5.3/§5.1/§5.4 - encode.ts: encode Date (svarint ms + 0 nanos), every TypedArray subtype (concrete-before-abstract instanceof order), and ArrayBuffer as Bytes - decode.ts: decode Date / Bytes / TypedArray, error on unknown element type - index.ts: export ElementType - test/roundtrip.ts: +14 cases (Date incl. identity, all TypedArrays, ArrayBuffer, special floats, empty arrays) — 42 passed, 0 failed - scripts/gen-golden.ts: generate spec/golden/*.bin vectors - spec/golden/: 9 golden binary vectors - conformance/README.md: harness contract, vector table, per-language layout --- conformance/README.md | 101 +++++++++++++++++++++++++++++++ js/scripts/gen-golden.ts | 119 +++++++++++++++++++++++++++++++++++++ js/src/decode.ts | 34 ++++++++++- js/src/encode.ts | 41 ++++++++++++- js/src/format.ts | 20 +++++++ js/src/index.ts | 2 +- js/test/roundtrip.ts | 65 ++++++++++++++++++++ spec/golden/bigint.bin | Bin 0 -> 74 bytes spec/golden/bytes.bin | Bin 0 -> 34 bytes spec/golden/cycles.bin | Bin 0 -> 86 bytes spec/golden/date.bin | Bin 0 -> 73 bytes spec/golden/map_set.bin | Bin 0 -> 109 bytes spec/golden/primitive.bin | Bin 0 -> 178 bytes spec/golden/string.bin | Bin 0 -> 97 bytes spec/golden/symbol.bin | Bin 0 -> 99 bytes spec/golden/typedarray.bin | Bin 0 -> 298 bytes 16 files changed, 379 insertions(+), 3 deletions(-) create mode 100644 conformance/README.md create mode 100644 js/scripts/gen-golden.ts create mode 100644 spec/golden/bigint.bin create mode 100644 spec/golden/bytes.bin create mode 100644 spec/golden/cycles.bin create mode 100644 spec/golden/date.bin create mode 100644 spec/golden/map_set.bin create mode 100644 spec/golden/primitive.bin create mode 100644 spec/golden/string.bin create mode 100644 spec/golden/symbol.bin create mode 100644 spec/golden/typedarray.bin diff --git a/conformance/README.md b/conformance/README.md new file mode 100644 index 0000000..abccffb --- /dev/null +++ b/conformance/README.md @@ -0,0 +1,101 @@ +# Graft Conformance Harness + +This directory defines how language implementations prove they conform to the +Graft binary format. The format itself is specified in +[`../spec/FORMAT.md`](../spec/FORMAT.md) — that document is the single source of +truth. This README only describes the *testing* contract. + +There is no implementation code here yet; this is the skeleton that future +language ports build against. + +--- + +## 1. Golden vectors + +The canonical test inputs live in [`../spec/golden/`](../spec/golden/). Each +`.bin` file is a complete Graft stream (`MAGIC VERSION ROOT COUNT NODE{COUNT}`, +see FORMAT.md §3) produced by the reference JS encoder. + +Every file's root is a single `Object` node whose keys name the individual +cases. Decode the whole file once, then assert on each key. + +| File | Covers (FORMAT.md §) | Cases | +|------------------|----------------------|-------| +| `primitive.bin` | §5.1 | null, undefined, bool, int, float, NaN, -0, ±Infinity | +| `bigint.bin` | §5.1 | zero, large positive, large negative | +| `string.bin` | §2.4 | empty, ASCII, multibyte UTF-8, emoji (surrogate pairs) | +| `date.bin` | §5.3 | epoch, pre-epoch (negative ms), normal, far future | +| `bytes.bin` | §5.1 `Bytes` | ArrayBuffer (non-empty + empty) | +| `typedarray.bin` | §5.4 | every `ElementType` (0–10) + an empty array | +| `map_set.bin` | §5.5 | Map & Set, including an object-identity key shared between them | +| `symbol.bin` | §5.2 | Registered, Unique (with file-internal identity), WellKnown | +| `cycles.bin` | §4 | self-cycle, cross-references, shared identity | + +### Regenerating + +The vectors are generated from the reference implementation: + +```bash +cd js && npx tsx scripts/gen-golden.ts +``` + +Regenerate only when FORMAT.md changes. The committed `.bin` files are the +authority that other languages test against — treat a diff in these files +during review as a deliberate format change, not an incidental one. + +--- + +## 2. How a new implementation passes conformance + +A conforming implementation must satisfy FORMAT.md §8. Concretely, against the +golden vectors: + +1. **Read** each `.bin` file as raw bytes. +2. **Verify the header**: `MAGIC == "GRF1"`, `VERSION == 1`. Reject otherwise. +3. **Decode** the full heap using the two-pass algorithm (FORMAT.md §4): + allocate `COUNT` placeholder slots, parse every node, then resolve all + references. This is mandatory — cycles and shared identity cannot be + restored with a single recursive pass. +4. **Assert** the decoded values match the expected structure for each key. + Use the documented fallback (FORMAT.md §5) for any type the target language + cannot represent natively, and assert against the fallback shape instead. +5. **Error** on unknown tags (the reserved ranges in FORMAT.md §6) rather than + skipping them. + +A round-trip test (decode → re-encode → byte-compare) is **not** required and +generally won't hold across languages: encoders may legitimately order heap +nodes differently. Conformance is defined by *decoded value equality*, not +byte equality. The one stable guarantee is the JS reference encoder +reproducing its own golden bytes. + +### Expected-value notation + +FORMAT.md §8 references a `.meta.json` sidecar describing each vector's expected +decoded structure in a language-neutral notation. Those files are not yet +generated. Until they exist, the expectations are defined by the case names in +the table above plus the inputs in `js/scripts/gen-golden.ts`, which is the +human-readable description of what each vector contains. + +--- + +## 3. Directory convention for language implementations + +Each language port gets its own subdirectory under `conformance/`: + +``` +conformance/ + README.md ← this file + / ← e.g. go/, rust/, python/, ruby/, cpp/ + README.md ← how to build & run this port's conformance suite + ... ← decoder + a test runner over ../../spec/golden/ +``` + +Requirements for each `/`: + +- A test runner that loads every `.bin` from `spec/golden/`, decodes it, and + asserts the documented expectations. +- A short `README.md` with the exact build/run command (e.g. `go test ./...`, + `cargo test`, `pytest`). +- No modification of `spec/golden/` or `spec/FORMAT.md` from within a language + directory. Format changes flow the other way: edit FORMAT.md first, then the + reference encoder, regenerate golden, then update each port. diff --git a/js/scripts/gen-golden.ts b/js/scripts/gen-golden.ts new file mode 100644 index 0000000..af82eac --- /dev/null +++ b/js/scripts/gen-golden.ts @@ -0,0 +1,119 @@ +// Generates the golden binary vectors in spec/golden/. +// Each .bin file is a single Object node whose keys are the named test cases. +// Run with: cd js && npx tsx scripts/gen-golden.ts +// +// Paths are resolved relative to this file so the output location is stable +// no matter where the script is launched from. + +import { writeFileSync, mkdirSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; +import { encode } from "../src/index.js"; + +const here = dirname(fileURLToPath(import.meta.url)); +const goldenDir = join(here, "..", "..", "spec", "golden"); +mkdirSync(goldenDir, { recursive: true }); + +function emit(name: string, value: unknown): void { + const path = join(goldenDir, name); + writeFileSync(path, encode(value)); + console.log("wrote " + path); +} + +// --- primitives / special numbers (FORMAT.md §5.1) --- +emit("primitive.bin", { + null: null, + undefined: undefined, + true: true, + false: false, + int: 42, + negative_int: -7, + float: 3.14, + nan: NaN, + negative_zero: -0, + infinity: Infinity, + negative_infinity: -Infinity, +}); + +// --- bigint (FORMAT.md §5.1) --- +emit("bigint.bin", { + zero: 0n, + positive: 123456789012345678901234567890n, + negative: -987654321098765432109876543210n, +}); + +// --- strings (FORMAT.md §2.4) --- +emit("string.bin", { + empty: "", + ascii: "hello world", + multibyte: "héllo こんにちは", + emoji: "🌊🚀✨", +}); + +// --- Date (FORMAT.md §5.3) --- +emit("date.bin", { + epoch: new Date(0), + negative: new Date(-1), + normal: new Date("2024-01-15T12:00:00.000Z"), + far_future: new Date(253402300799999), +}); + +// --- ArrayBuffer / Bytes (FORMAT.md §5.1) --- +{ + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const empty = new ArrayBuffer(0); + emit("bytes.bin", { buffer: ab, empty }); +} + +// --- TypedArray, every ElementType (FORMAT.md §5.4) --- +emit("typedarray.bin", { + uint8: new Uint8Array([0, 127, 255]), + uint8_clamped: new Uint8ClampedArray([0, 128, 255]), + uint16: new Uint16Array([0, 256, 65535]), + uint32: new Uint32Array([0, 65536, 4294967295]), + int8: new Int8Array([-128, 0, 127]), + int16: new Int16Array([-32768, 0, 32767]), + int32: new Int32Array([-2147483648, 0, 2147483647]), + float32: new Float32Array([1.5, -0, Infinity]), + float64: new Float64Array([1.1, NaN, -0, Infinity]), + bigint64: new BigInt64Array([0n, -1n, 9223372036854775807n]), + biguint64: new BigUint64Array([0n, 1n, 18446744073709551615n]), + empty: new Uint8Array([]), +}); + +// --- Map / Set, including object keys (FORMAT.md §5.5) --- +{ + const objKey = { k: 1 }; + const map = new Map([ + ["string_key", 99], + [objKey, "object-keyed value"], + [42, "number key"], + ]); + const set = new Set([1, "two", objKey]); + emit("map_set.bin", { map, set, shared_key: objKey }); +} + +// --- Symbols: Registered / Unique / WellKnown (FORMAT.md §5.2) --- +{ + const registered = Symbol.for("app.id"); + const unique = Symbol("desc"); + emit("symbol.bin", { + [registered]: "registered value", + [unique]: "unique value", + well_known: Symbol.iterator, + unique_again: unique, // file-internal identity for the unique symbol + }); +} + +// --- cycles & shared identity (FORMAT.md §4) --- +{ + const a: Record = { name: "a" }; + const b: Record = { name: "b", peer: a }; + a.peer = b; + a.self = a; + const shared = { tag: "shared" }; + emit("cycles.bin", { a, b, x: shared, y: shared }); +} + +console.log("\ngolden vectors written to " + goldenDir); diff --git a/js/src/decode.ts b/js/src/decode.ts index 8234905..5d34237 100644 --- a/js/src/decode.ts +++ b/js/src/decode.ts @@ -1,5 +1,5 @@ import { ByteReader } from "./buffer.js"; -import { MAGIC, VERSION, Tag, KeyKind } from "./format.js"; +import { MAGIC, VERSION, Tag, KeyKind, ElementType } from "./format.js"; type Filler = (resolve: (idx: number) => unknown) => void; @@ -156,6 +156,38 @@ function readNode(r: ByteReader): DecodedNode { }, }; } + case Tag.Date: { + const ms = r.svarint(); + r.svarint(); // sub_ms_nanos: JS では無視 + return { value: new Date(Number(ms)) }; + } + case Tag.Bytes: { + const len = r.uvarintNum(); + const raw = r.bytes(len); + return { value: raw.buffer.slice(raw.byteOffset, raw.byteOffset + len) }; + } + case Tag.TypedArray: { + const et = r.u8(); + const len = r.uvarintNum(); + const raw = r.bytes(len); + const buf = raw.buffer.slice(raw.byteOffset, raw.byteOffset + len) as ArrayBuffer; + const ctors: Record ArrayBufferView> = { + [ElementType.Uint8]: Uint8Array, + [ElementType.Uint8Clamped]: Uint8ClampedArray, + [ElementType.Uint16]: Uint16Array, + [ElementType.Uint32]: Uint32Array, + [ElementType.Int8]: Int8Array, + [ElementType.Int16]: Int16Array, + [ElementType.Int32]: Int32Array, + [ElementType.Float32]: Float32Array, + [ElementType.Float64]: Float64Array, + [ElementType.BigInt64]: BigInt64Array, + [ElementType.BigUint64]: BigUint64Array, + }; + const Ctor = ctors[et]; + if (!Ctor) throw new Error("unknown element type: " + et); + return { value: new Ctor(buf) }; + } default: throw new Error("unknown tag: " + tag); } diff --git a/js/src/encode.ts b/js/src/encode.ts index a00ed9f..328baa7 100644 --- a/js/src/encode.ts +++ b/js/src/encode.ts @@ -1,5 +1,5 @@ import { ByteWriter } from "./buffer.js"; -import { MAGIC, VERSION, Tag, KeyKind } from "./format.js"; +import { MAGIC, VERSION, Tag, KeyKind, ElementType } from "./format.js"; // Explicit contents for weak collections, since WeakMap/WeakSet are not // enumerable per spec. The caller supplies the entries they are holding. @@ -104,7 +104,46 @@ export function encode(root: unknown, provider: WeakProvider = {}): Uint8Array { }); } + function encodeTypedArray(et: ElementType, arr: ArrayBufferView): Node { + const bytes = new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength); + return leaf((w) => { + w.u8(Tag.TypedArray); + w.u8(et); + w.uvarint(bytes.length); + w.bytes(bytes); + }); + } + function buildObject(obj: object): Node { + // Date — leaf, checked before structural types. + if (obj instanceof Date) { + const ms = obj.getTime(); + return leaf((w) => { + w.u8(Tag.Date); + w.svarint(BigInt(ms)); + w.svarint(0n); // sub_ms_nanos: JS は常に 0 + }); + } + // TypedArrays — concrete subtype first, then ArrayBuffer, all before Array. + if (obj instanceof BigInt64Array) return encodeTypedArray(ElementType.BigInt64, obj); + if (obj instanceof BigUint64Array) return encodeTypedArray(ElementType.BigUint64, obj); + if (obj instanceof Float64Array) return encodeTypedArray(ElementType.Float64, obj); + if (obj instanceof Float32Array) return encodeTypedArray(ElementType.Float32, obj); + if (obj instanceof Int32Array) return encodeTypedArray(ElementType.Int32, obj); + if (obj instanceof Uint32Array) return encodeTypedArray(ElementType.Uint32, obj); + if (obj instanceof Int16Array) return encodeTypedArray(ElementType.Int16, obj); + if (obj instanceof Uint16Array) return encodeTypedArray(ElementType.Uint16, obj); + if (obj instanceof Int8Array) return encodeTypedArray(ElementType.Int8, obj); + if (obj instanceof Uint8ClampedArray) return encodeTypedArray(ElementType.Uint8Clamped, obj); + if (obj instanceof Uint8Array) return encodeTypedArray(ElementType.Uint8, obj); + if (obj instanceof ArrayBuffer) { + const bytes = new Uint8Array(obj); + return leaf((w) => { + w.u8(Tag.Bytes); + w.uvarint(bytes.length); + w.bytes(bytes); + }); + } if (Array.isArray(obj)) { // Iterate by index so holes in sparse arrays become explicit undefined. const refs: number[] = new Array(obj.length); diff --git a/js/src/format.ts b/js/src/format.ts index 67631c1..29759ea 100644 --- a/js/src/format.ts +++ b/js/src/format.ts @@ -31,6 +31,26 @@ export enum Tag { // ---- weak collections (children supplied via explicit input) ---- WeakMap = 30, // count + count*(keyRef,valueRef) [only resolvable entries] WeakSet = 31, // count + count*ref + + // ---- extended leaves ---- + Date = 40, // svarint(unix_ms) + svarint(sub_ms_nanos) + Bytes = 41, // uvarint(byte_length) + raw_bytes (ArrayBuffer) + TypedArray = 42, // u8(element_type) + uvarint(byte_length) + raw_bytes (LE) +} + +// TypedArray element type codes (see FORMAT.md §5.4). +export enum ElementType { + Uint8 = 0, + Uint8Clamped = 1, + Uint16 = 2, + Uint32 = 3, + Int8 = 4, + Int16 = 5, + Int32 = 6, + Float32 = 7, + Float64 = 8, + BigInt64 = 9, + BigUint64 = 10, } // Object key kinds, since JS object keys may be strings or symbols. diff --git a/js/src/index.ts b/js/src/index.ts index 33371e4..2e3ae1a 100644 --- a/js/src/index.ts +++ b/js/src/index.ts @@ -1,4 +1,4 @@ export { encode, type WeakProvider } from "./encode.js"; export { decode } from "./decode.js"; -export { Tag, KeyKind, MAGIC, VERSION } from "./format.js"; +export { Tag, KeyKind, ElementType, MAGIC, VERSION } from "./format.js"; export { ByteReader, ByteWriter } from "./buffer.js"; diff --git a/js/test/roundtrip.ts b/js/test/roundtrip.ts index 91ec5af..1816d5e 100644 --- a/js/test/roundtrip.ts +++ b/js/test/roundtrip.ts @@ -129,5 +129,70 @@ function check(name: string, cond: boolean): void { check("array hole -> undefined", out[1] === undefined); } +// ---- Date ---- +{ + const root = { + epoch: new Date(0), + negative: new Date(-1), + normal: new Date("2024-01-15T12:00:00.000Z"), + far_future: new Date(253402300799999), + }; + const out = decode(encode(root)) as typeof root; + check("date epoch", out.epoch instanceof Date && out.epoch.getTime() === 0); + check("date negative (pre-epoch)", out.negative instanceof Date && out.negative.getTime() === -1); + check( + "date normal", + out.normal instanceof Date && out.normal.getTime() === Date.parse("2024-01-15T12:00:00.000Z") + ); + check("date far future", out.far_future instanceof Date && out.far_future.getTime() === 253402300799999); + + const d = new Date(); + const idRoot = { a: d, b: d }; + const idOut = decode(encode(idRoot)) as typeof idRoot; + check("date shared identity a === b", idOut.a === idOut.b); + check("date shared identity value", idOut.a.getTime() === d.getTime()); +} + +// ---- ArrayBuffer / TypedArray ---- +{ + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const out = decode(encode({ ab })) as { ab: ArrayBuffer }; + check("arraybuffer is ArrayBuffer", out.ab instanceof ArrayBuffer && out.ab.byteLength === 4); + check( + "arraybuffer bytes", + [...new Uint8Array(out.ab)].join(",") === "1,2,3,4" + ); + + const root = { + u8: new Uint8Array([0, 127, 255]), + i16: new Int16Array([-32768, 0, 32767]), + f64: new Float64Array([1.1, NaN, -0, Infinity]), + bi64: new BigInt64Array([0n, -1n, 9223372036854775807n]), + clamped: new Uint8ClampedArray([0, 128, 255]), + empty: new Uint8Array([]), + }; + const o = decode(encode(root)) as typeof root; + check("Uint8Array type & values", o.u8 instanceof Uint8Array && [...o.u8].join(",") === "0,127,255"); + check("Int16Array type & values", o.i16 instanceof Int16Array && [...o.i16].join(",") === "-32768,0,32767"); + check( + "Float64Array type & special values", + o.f64 instanceof Float64Array && + o.f64[0] === 1.1 && + Number.isNaN(o.f64[1]) && + Object.is(o.f64[2], -0) && + o.f64[3] === Infinity + ); + check( + "BigInt64Array type & values", + o.bi64 instanceof BigInt64Array && o.bi64[0] === 0n && o.bi64[1] === -1n && o.bi64[2] === 9223372036854775807n + ); + check( + "Uint8ClampedArray type & values", + o.clamped instanceof Uint8ClampedArray && [...o.clamped].join(",") === "0,128,255" + ); + check("empty Uint8Array", o.empty instanceof Uint8Array && o.empty.length === 0); +} + console.log(`\n${pass} passed, ${fail} failed`); if (fail > 0) process.exit(1); diff --git a/spec/golden/bigint.bin b/spec/golden/bigint.bin new file mode 100644 index 0000000000000000000000000000000000000000..2275d93f7f21a48d4e1061cac58385e25f9b3f2f GIT binary patch literal 74 zcmZ<{ax-LPU=d|zV5v$i%4cNYD9A6)EXgcOWn$pSOHEG%F`3yI7}yvtP5txn$=n}1 d55L`i;+-KI0R!U`r}ZOG_57nlG5 literal 0 HcmV?d00001 diff --git a/spec/golden/map_set.bin b/spec/golden/map_set.bin new file mode 100644 index 0000000000000000000000000000000000000000..826e7b1e60811fc9afaf23d6e14c57c476713983 GIT binary patch literal 109 zcmZ<{ax-LP;1*?OV9re}U}Rt}PA%bN;402YEJ{s@&rYpm5o2azW?^IJVCO0>Day=C z2MZo!6lG*!%w}a_Vi(F!%1TWx(FLkaO;IRI%qdM}31R2TE6q(xEm8oAiZiovv9L3j Hl;;BgT_YOP literal 0 HcmV?d00001 diff --git a/spec/golden/primitive.bin b/spec/golden/primitive.bin new file mode 100644 index 0000000000000000000000000000000000000000..b2eab623af0dcb7d824206fb6f42646dd6a56a82 GIT binary patch literal 178 zcmZ<{ax-LP;1T6!V96`Z$zf#REX_+vP0P$nO<`hSDJd#VWoBSaOUx-wWno~>%qwAK z;K@r(Pb|qSON|F|*?^Kc`H3a$49t0nc^nM9aLKCFqI^yUj?6ruS(zo3TnvJ6DVP{H m10ypNO9%@ut9}D5(Up6N{5GGnp7Tb4zndGLtGxQkg+=`B|AP>+Oko!&N=?r!2Fj(TD3m4Ul%}#nwL^FSI*c80 literal 0 HcmV?d00001 diff --git a/spec/golden/typedarray.bin b/spec/golden/typedarray.bin new file mode 100644 index 0000000000000000000000000000000000000000..26336bf22de5ed993ef87e1278e6e5e808518881 GIT binary patch literal 298 zcmX|)xemfG5JV?9b`C;`L`l6}4n=fy(DMT*oI>JA5eh2jH{fgdB)~8Ym>XZNkHx9nZhBBQ$GK=BO Date: Sun, 31 May 2026 01:30:36 +0000 Subject: [PATCH 2/9] chore: pin devDependencies to exact versions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - package.json: drop caret ranges (@types/node, tsx, typescript) → exact pins - pnpm-lock.yaml: update specifier fields to match (lockfile format kept at 6.0) - add js/.npmrc with save-exact=true so future installs pin by default --- js/.npmrc | 1 + js/package.json | 6 +++--- js/pnpm-lock.yaml | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 js/.npmrc diff --git a/js/.npmrc b/js/.npmrc new file mode 100644 index 0000000..cffe8cd --- /dev/null +++ b/js/.npmrc @@ -0,0 +1 @@ +save-exact=true diff --git a/js/package.json b/js/package.json index 5f7ca29..8bd2e2b 100644 --- a/js/package.json +++ b/js/package.json @@ -11,8 +11,8 @@ "license": "ISC", "type": "module", "devDependencies": { - "@types/node": "^25.9.1", - "tsx": "^4.22.3", - "typescript": "^6.0.3" + "@types/node": "25.9.1", + "tsx": "4.22.3", + "typescript": "6.0.3" } } diff --git a/js/pnpm-lock.yaml b/js/pnpm-lock.yaml index 0d262d3..0bc9f13 100644 --- a/js/pnpm-lock.yaml +++ b/js/pnpm-lock.yaml @@ -6,13 +6,13 @@ settings: devDependencies: '@types/node': - specifier: ^25.9.1 + specifier: 25.9.1 version: 25.9.1 tsx: - specifier: ^4.22.3 + specifier: 4.22.3 version: 4.22.3 typescript: - specifier: ^6.0.3 + specifier: 6.0.3 version: 6.0.3 packages: From 26a5fac6544322ecf06f8c8a68c773e0250d4dca Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 31 May 2026 01:33:03 +0000 Subject: [PATCH 3/9] chore: pin pnpm to 11.5.0 via packageManager field - package.json: add packageManager "pnpm@11.5.0+sha512..." (Corepack pin) - pnpm-lock.yaml: regenerate with pnpm 11.5.0 (lockfile format 6.0 -> 9.0) Corepack now enforces pnpm 11.5.0 for everyone using this repo. --- js/package.json | 3 +- js/pnpm-lock.yaml | 291 +++++++++++++++++++++-------------------- js/pnpm-workspace.yaml | 2 + 3 files changed, 154 insertions(+), 142 deletions(-) create mode 100644 js/pnpm-workspace.yaml diff --git a/js/package.json b/js/package.json index 8bd2e2b..5fac29e 100644 --- a/js/package.json +++ b/js/package.json @@ -14,5 +14,6 @@ "@types/node": "25.9.1", "tsx": "4.22.3", "typescript": "6.0.3" - } + }, + "packageManager": "pnpm@11.5.0+sha512.dbfcc4f81cf48597afd4bc391ffdf12c11f1a9fb83a395bfa6b0a2d9cc2fd8ffebafdb1ccbd529632153f793904c2615b7f09fe1a345473fd1c35845172a8eb1" } diff --git a/js/pnpm-lock.yaml b/js/pnpm-lock.yaml index 0bc9f13..9429a40 100644 --- a/js/pnpm-lock.yaml +++ b/js/pnpm-lock.yaml @@ -1,267 +1,292 @@ -lockfileVersion: '6.0' +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -devDependencies: - '@types/node': - specifier: 25.9.1 - version: 25.9.1 - tsx: - specifier: 4.22.3 - version: 4.22.3 - typescript: - specifier: 6.0.3 - version: 6.0.3 +importers: + + .: + devDependencies: + '@types/node': + specifier: 25.9.1 + version: 25.9.1 + tsx: + specifier: 4.22.3 + version: 4.22.3 + typescript: + specifier: 6.0.3 + version: 6.0.3 packages: - /@esbuild/aix-ppc64@0.28.0: + '@esbuild/aix-ppc64@0.28.0': resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm64@0.28.0: + '@esbuild/android-arm64@0.28.0': resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} engines: {node: '>=18'} cpu: [arm64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-arm@0.28.0: + '@esbuild/android-arm@0.28.0': resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/android-x64@0.28.0: + '@esbuild/android-x64@0.28.0': resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} engines: {node: '>=18'} cpu: [x64] os: [android] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-arm64@0.28.0: + '@esbuild/darwin-arm64@0.28.0': resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/darwin-x64@0.28.0: + '@esbuild/darwin-x64@0.28.0': resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-arm64@0.28.0: + '@esbuild/freebsd-arm64@0.28.0': resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/freebsd-x64@0.28.0: + '@esbuild/freebsd-x64@0.28.0': resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm64@0.28.0: + '@esbuild/linux-arm64@0.28.0': resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-arm@0.28.0: + '@esbuild/linux-arm@0.28.0': resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ia32@0.28.0: + '@esbuild/linux-ia32@0.28.0': resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-loong64@0.28.0: + '@esbuild/linux-loong64@0.28.0': resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-mips64el@0.28.0: + '@esbuild/linux-mips64el@0.28.0': resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-ppc64@0.28.0: + '@esbuild/linux-ppc64@0.28.0': resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-riscv64@0.28.0: + '@esbuild/linux-riscv64@0.28.0': resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-s390x@0.28.0: + '@esbuild/linux-s390x@0.28.0': resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/linux-x64@0.28.0: + '@esbuild/linux-x64@0.28.0': resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] - requiresBuild: true - dev: true - optional: true - /@esbuild/netbsd-arm64@0.28.0: + '@esbuild/netbsd-arm64@0.28.0': resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/netbsd-x64@0.28.0: + '@esbuild/netbsd-x64@0.28.0': resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/openbsd-arm64@0.28.0: + '@esbuild/openbsd-arm64@0.28.0': resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/openbsd-x64@0.28.0: + '@esbuild/openbsd-x64@0.28.0': resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - requiresBuild: true - dev: true - optional: true - /@esbuild/openharmony-arm64@0.28.0: + '@esbuild/openharmony-arm64@0.28.0': resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - requiresBuild: true - dev: true - optional: true - /@esbuild/sunos-x64@0.28.0: + '@esbuild/sunos-x64@0.28.0': resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-arm64@0.28.0: + '@esbuild/win32-arm64@0.28.0': resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-ia32@0.28.0: + '@esbuild/win32-ia32@0.28.0': resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - requiresBuild: true - dev: true - optional: true - /@esbuild/win32-x64@0.28.0: + '@esbuild/win32-x64@0.28.0': resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} engines: {node: '>=18'} cpu: [x64] os: [win32] - requiresBuild: true - dev: true - optional: true - /@types/node@25.9.1: + '@types/node@25.9.1': resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} - dependencies: - undici-types: 7.24.6 - dev: true - /esbuild@0.28.0: + esbuild@0.28.0: resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} engines: {node: '>=18'} hasBin: true - requiresBuild: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + tsx@4.22.3: + resolution: {integrity: sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==} + engines: {node: '>=18.0.0'} + hasBin: true + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + +snapshots: + + '@esbuild/aix-ppc64@0.28.0': + optional: true + + '@esbuild/android-arm64@0.28.0': + optional: true + + '@esbuild/android-arm@0.28.0': + optional: true + + '@esbuild/android-x64@0.28.0': + optional: true + + '@esbuild/darwin-arm64@0.28.0': + optional: true + + '@esbuild/darwin-x64@0.28.0': + optional: true + + '@esbuild/freebsd-arm64@0.28.0': + optional: true + + '@esbuild/freebsd-x64@0.28.0': + optional: true + + '@esbuild/linux-arm64@0.28.0': + optional: true + + '@esbuild/linux-arm@0.28.0': + optional: true + + '@esbuild/linux-ia32@0.28.0': + optional: true + + '@esbuild/linux-loong64@0.28.0': + optional: true + + '@esbuild/linux-mips64el@0.28.0': + optional: true + + '@esbuild/linux-ppc64@0.28.0': + optional: true + + '@esbuild/linux-riscv64@0.28.0': + optional: true + + '@esbuild/linux-s390x@0.28.0': + optional: true + + '@esbuild/linux-x64@0.28.0': + optional: true + + '@esbuild/netbsd-arm64@0.28.0': + optional: true + + '@esbuild/netbsd-x64@0.28.0': + optional: true + + '@esbuild/openbsd-arm64@0.28.0': + optional: true + + '@esbuild/openbsd-x64@0.28.0': + optional: true + + '@esbuild/openharmony-arm64@0.28.0': + optional: true + + '@esbuild/sunos-x64@0.28.0': + optional: true + + '@esbuild/win32-arm64@0.28.0': + optional: true + + '@esbuild/win32-ia32@0.28.0': + optional: true + + '@esbuild/win32-x64@0.28.0': + optional: true + + '@types/node@25.9.1': + dependencies: + undici-types: 7.24.6 + + esbuild@0.28.0: optionalDependencies: '@esbuild/aix-ppc64': 0.28.0 '@esbuild/android-arm': 0.28.0 @@ -289,32 +314,16 @@ packages: '@esbuild/win32-arm64': 0.28.0 '@esbuild/win32-ia32': 0.28.0 '@esbuild/win32-x64': 0.28.0 - dev: true - /fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: true + fsevents@2.3.3: optional: true - /tsx@4.22.3: - resolution: {integrity: sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==} - engines: {node: '>=18.0.0'} - hasBin: true + tsx@4.22.3: dependencies: esbuild: 0.28.0 optionalDependencies: fsevents: 2.3.3 - dev: true - /typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} - hasBin: true - dev: true + typescript@6.0.3: {} - /undici-types@7.24.6: - resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - dev: true + undici-types@7.24.6: {} diff --git a/js/pnpm-workspace.yaml b/js/pnpm-workspace.yaml new file mode 100644 index 0000000..49c0ad7 --- /dev/null +++ b/js/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +allowBuilds: + esbuild: false From 6f58ed1f4b35eea2f1964f22625f2176be544d1a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 31 May 2026 01:36:47 +0000 Subject: [PATCH 4/9] chore: add vitest test runner and tsdown build - devDeps: vitest@4.1.7, tsdown@0.22.1 (exact pins) - migrate test/roundtrip.ts -> test/roundtrip.test.ts (describe/it/expect), all assertions preserved; run via `pnpm test` (vitest run) - vitest.config.ts: include test/**/*.test.ts, node environment - tsdown.config.ts: bundle src/index.ts -> dist (ESM + d.ts), `pnpm build` - package.json: proper ESM exports/main/module/types pointing at dist, files: [dist], scripts: build / test / test:watch / typecheck --- js/package.json | 24 +- js/pnpm-lock.yaml | 1192 +++++++++++++++++++++++++++++++++++++ js/test/roundtrip.test.ts | 184 ++++++ js/test/roundtrip.ts | 198 ------ js/tsdown.config.ts | 9 + js/vitest.config.ts | 8 + 6 files changed, 1413 insertions(+), 202 deletions(-) create mode 100644 js/test/roundtrip.test.ts delete mode 100644 js/test/roundtrip.ts create mode 100644 js/tsdown.config.ts create mode 100644 js/vitest.config.ts diff --git a/js/package.json b/js/package.json index 5fac29e..35d1e36 100644 --- a/js/package.json +++ b/js/package.json @@ -2,18 +2,34 @@ "name": "greft", "version": "1.0.0", "description": "", - "main": "index.js", + "type": "module", + "main": "./dist/index.mjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.mts", + "exports": { + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs" + } + }, + "files": [ + "dist" + ], "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "build": "tsdown", + "test": "vitest run", + "test:watch": "vitest", + "typecheck": "tsc --noEmit" }, "keywords": [], "author": "", "license": "ISC", - "type": "module", "devDependencies": { "@types/node": "25.9.1", + "tsdown": "0.22.1", "tsx": "4.22.3", - "typescript": "6.0.3" + "typescript": "6.0.3", + "vitest": "4.1.7" }, "packageManager": "pnpm@11.5.0+sha512.dbfcc4f81cf48597afd4bc391ffdf12c11f1a9fb83a395bfa6b0a2d9cc2fd8ffebafdb1ccbd529632153f793904c2615b7f09fe1a345473fd1c35845172a8eb1" } diff --git a/js/pnpm-lock.yaml b/js/pnpm-lock.yaml index 9429a40..c73f87f 100644 --- a/js/pnpm-lock.yaml +++ b/js/pnpm-lock.yaml @@ -11,15 +11,51 @@ importers: '@types/node': specifier: 25.9.1 version: 25.9.1 + tsdown: + specifier: 0.22.1 + version: 0.22.1(tsx@4.22.3)(typescript@6.0.3) tsx: specifier: 4.22.3 version: 4.22.3 typescript: specifier: 6.0.3 version: 6.0.3 + vitest: + specifier: 4.1.7 + version: 4.1.7(@types/node@25.9.1)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)) packages: + '@babel/generator@8.0.0-rc.6': + resolution: {integrity: sha512-6mIzgVK8DgEzvIapoQwhXTMnnkuE4STQmVv9H03i/tZ2ml8oev3TRvZJgTenK2Bsq0YWNtzOrFdTyNzCMFtjJQ==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-string-parser@8.0.0-rc.6': + resolution: {integrity: sha512-BCkFy+zN6kXQed3YOT7aJl93NfDSzQc3pBfsvTVPs9gU9X3V0aefEF5kwBT0E+mDWH9QgKaZstYUQN9VdQZT4g==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-validator-identifier@8.0.0-rc.6': + resolution: {integrity: sha512-nVJ+1JcCgntv8d78rRo++o2wuODT0Irknx2BF8Np4Ft2CRgjLqIs4qzSZ8b66yGbBdMWGmZBO9WEZv1hhNiSpg==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/parser@8.0.0-rc.6': + resolution: {integrity: sha512-rOS8IpdO7mQELkTPlCsTgPejO0bFuZdEDCGQJouYbYf9e1FLTym7Fei2pEjq8q7MWbX0ravcd7QQYKs1TxOuog==} + engines: {node: ^22.18.0 || >=24.11.0} + hasBin: true + + '@babel/types@8.0.0-rc.6': + resolution: {integrity: sha512-p7/ABylAYlexb31wtRdIfH9L9A0Z2T/9H6zAqzqndkY2PLkvNNc580wGhp/gGKN4Sp9sQvSkhc6Oga8/O+wTyw==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@esbuild/aix-ppc64@0.28.0': resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} engines: {node: '>=18'} @@ -176,19 +212,576 @@ packages: cpu: [x64] os: [win32] + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@oxc-project/types@0.132.0': + resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==} + + '@oxc-project/types@0.133.0': + resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + + '@quansync/fs@1.0.0': + resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + + '@rolldown/binding-android-arm64@1.0.2': + resolution: {integrity: sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-android-arm64@1.0.3': + resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.2': + resolution: {integrity: sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-arm64@1.0.3': + resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.2': + resolution: {integrity: sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.3': + resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.2': + resolution: {integrity: sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-freebsd-x64@1.0.3': + resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.2': + resolution: {integrity: sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.2': + resolution: {integrity: sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.2': + resolution: {integrity: sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-arm64-musl@1.0.3': + resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.2': + resolution: {integrity: sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.2': + resolution: {integrity: sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.2': + resolution: {integrity: sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.3': + resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.2': + resolution: {integrity: sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-x64-musl@1.0.3': + resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.2': + resolution: {integrity: sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-openharmony-arm64@1.0.3': + resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.2': + resolution: {integrity: sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-wasm32-wasi@1.0.3': + resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.2': + resolution: {integrity: sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.2': + resolution: {integrity: sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.3': + resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/jsesc@2.5.1': + resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + '@types/node@25.9.1': resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} + '@vitest/expect@4.1.7': + resolution: {integrity: sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==} + + '@vitest/mocker@4.1.7': + resolution: {integrity: sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.7': + resolution: {integrity: sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==} + + '@vitest/runner@4.1.7': + resolution: {integrity: sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==} + + '@vitest/snapshot@4.1.7': + resolution: {integrity: sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==} + + '@vitest/spy@4.1.7': + resolution: {integrity: sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==} + + '@vitest/utils@4.1.7': + resolution: {integrity: sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==} + + ansis@4.3.0: + resolution: {integrity: sha512-44mvgtPvohuU/70DdY5Oz2AIrLJ9k6/5x4KmoSvPwO+5Moijo0+N9D0fKbbYZQWP1hNm5CpOf+E01jhxG/r8xg==} + engines: {node: '>=14'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-kit@3.0.0-beta.1: + resolution: {integrity: sha512-trmleAnZ2PxN/loHWVhhx1qeOHSRXq4TDsBBxq3GqeJitfk3+jTQ+v/C1km/KYq9M7wKqCewMh+/NAvVH7m+bw==} + engines: {node: '>=20.19.0'} + + birpc@4.0.0: + resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} + + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dts-resolver@3.0.0: + resolution: {integrity: sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==} + engines: {node: ^22.18.0 || >=24.0.0} + peerDependencies: + oxc-resolver: '>=11.0.0' + peerDependenciesMeta: + oxc-resolver: + optional: true + + empathic@2.0.1: + resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} + engines: {node: '>=14'} + + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + esbuild@0.28.0: resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} engines: {node: '>=18'} hasBin: true + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + get-tsconfig@5.0.0-beta.5: + resolution: {integrity: sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==} + engines: {node: '>=20.20.0'} + + hookable@6.1.1: + resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} + + import-without-cache@0.4.0: + resolution: {integrity: sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==} + engines: {node: ^22.18.0 || >=24.0.0} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + quansync@1.0.0: + resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + rolldown-plugin-dts@0.25.2: + resolution: {integrity: sha512-nMhN/R+vmR8GM45ZW1FWMSjRTSDDn/6w4GTf8RNrEFCBdl8B1kySWrU1ixPtbwzXoRlcO+R/S88VgXuJQwfdDg==} + engines: {node: ^22.18.0 || >=24.0.0} + peerDependencies: + '@ts-macro/tsc': ^0.3.6 + '@typescript/native-preview': '>=7.0.0-dev.20260325.1' + rolldown: ^1.0.0 + typescript: ^5.0.0 || ^6.0.0 + vue-tsc: ~3.2.0 + peerDependenciesMeta: + '@ts-macro/tsc': + optional: true + '@typescript/native-preview': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + + rolldown@1.0.2: + resolution: {integrity: sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + rolldown@1.0.3: + resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} + engines: {node: '>=10'} + hasBin: true + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.2.3: + resolution: {integrity: sha512-g62dB+w1/OEFnPvmX0yd/HnetYITOL+1nJW7kitOycOeAvmbWC/nu0fwmmQ/kupNojqExzyC/T++pST/jRJ2mQ==} + engines: {node: '>=18'} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + tsdown@0.22.1: + resolution: {integrity: sha512-Ldx1jLyDFEzsN/fMBi2TBVaZe4fuEJhIiHjQhX0pV7oa5uYz5Imdivs5mNzEXOrMEtFRR6C9BQ2YqLoroffB+Q==} + engines: {node: ^22.18.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@tsdown/css': 0.22.1 + '@tsdown/exe': 0.22.1 + '@vitejs/devtools': '*' + publint: ^0.3.8 + tsx: '*' + typescript: ^5.0.0 || ^6.0.0 + unplugin-unused: ^0.5.0 + unrun: '*' + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@tsdown/css': + optional: true + '@tsdown/exe': + optional: true + '@vitejs/devtools': + optional: true + publint: + optional: true + tsx: + optional: true + typescript: + optional: true + unplugin-unused: + optional: true + unrun: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.22.3: resolution: {integrity: sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==} engines: {node: '>=18.0.0'} @@ -199,11 +792,141 @@ packages: engines: {node: '>=14.17'} hasBin: true + unconfig-core@7.5.0: + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} + undici-types@7.24.6: resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + vite@8.0.14: + resolution: {integrity: sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.7: + resolution: {integrity: sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.7 + '@vitest/browser-preview': 4.1.7 + '@vitest/browser-webdriverio': 4.1.7 + '@vitest/coverage-istanbul': 4.1.7 + '@vitest/coverage-v8': 4.1.7 + '@vitest/ui': 4.1.7 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + snapshots: + '@babel/generator@8.0.0-rc.6': + dependencies: + '@babel/parser': 8.0.0-rc.6 + '@babel/types': 8.0.0-rc.6 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@types/jsesc': 2.5.1 + jsesc: 3.1.0 + + '@babel/helper-string-parser@8.0.0-rc.6': {} + + '@babel/helper-validator-identifier@8.0.0-rc.6': {} + + '@babel/parser@8.0.0-rc.6': + dependencies: + '@babel/types': 8.0.0-rc.6 + + '@babel/types@8.0.0-rc.6': + dependencies: + '@babel/helper-string-parser': 8.0.0-rc.6 + '@babel/helper-validator-identifier': 8.0.0-rc.6 + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.28.0': optional: true @@ -282,10 +1005,226 @@ snapshots: '@esbuild/win32-x64@0.28.0': optional: true + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@oxc-project/types@0.132.0': {} + + '@oxc-project/types@0.133.0': {} + + '@quansync/fs@1.0.0': + dependencies: + quansync: 1.0.0 + + '@rolldown/binding-android-arm64@1.0.2': + optional: true + + '@rolldown/binding-android-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.2': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-x64@1.0.2': + optional: true + + '@rolldown/binding-darwin-x64@1.0.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.2': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.2': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.2': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.2': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.2': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.2': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.2': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.2': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.2': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.2': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.3': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.2': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.2': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@standard-schema/spec@1.1.0': {} + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.9': {} + + '@types/jsesc@2.5.1': {} + '@types/node@25.9.1': dependencies: undici-types: 7.24.6 + '@vitest/expect@4.1.7': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.7 + '@vitest/utils': 4.1.7 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.7(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3))': + dependencies: + '@vitest/spy': 4.1.7 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3) + + '@vitest/pretty-format@4.1.7': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.7': + dependencies: + '@vitest/utils': 4.1.7 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.7': + dependencies: + '@vitest/pretty-format': 4.1.7 + '@vitest/utils': 4.1.7 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.7': {} + + '@vitest/utils@4.1.7': + dependencies: + '@vitest/pretty-format': 4.1.7 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + ansis@4.3.0: {} + + assertion-error@2.0.1: {} + + ast-kit@3.0.0-beta.1: + dependencies: + '@babel/parser': 8.0.0-rc.6 + estree-walker: 3.0.3 + pathe: 2.0.3 + + birpc@4.0.0: {} + + cac@7.0.0: {} + + chai@6.2.2: {} + + convert-source-map@2.0.0: {} + + defu@6.1.7: {} + + detect-libc@2.1.2: {} + + dts-resolver@3.0.0: {} + + empathic@2.0.1: {} + + es-module-lexer@2.1.0: {} + esbuild@0.28.0: optionalDependencies: '@esbuild/aix-ppc64': 0.28.0 @@ -315,9 +1254,212 @@ snapshots: '@esbuild/win32-ia32': 0.28.0 '@esbuild/win32-x64': 0.28.0 + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + expect-type@1.3.0: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + fsevents@2.3.3: optional: true + get-tsconfig@5.0.0-beta.5: + dependencies: + resolve-pkg-maps: 1.0.0 + + hookable@6.1.1: {} + + import-without-cache@0.4.0: {} + + jsesc@3.1.0: {} + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + nanoid@3.3.12: {} + + obug@2.1.1: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + quansync@1.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + rolldown-plugin-dts@0.25.2(rolldown@1.0.3)(typescript@6.0.3): + dependencies: + '@babel/generator': 8.0.0-rc.6 + '@babel/helper-validator-identifier': 8.0.0-rc.6 + '@babel/parser': 8.0.0-rc.6 + ast-kit: 3.0.0-beta.1 + birpc: 4.0.0 + dts-resolver: 3.0.0 + get-tsconfig: 5.0.0-beta.5 + obug: 2.1.1 + rolldown: 1.0.3 + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - oxc-resolver + + rolldown@1.0.2: + dependencies: + '@oxc-project/types': 0.132.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.2 + '@rolldown/binding-darwin-arm64': 1.0.2 + '@rolldown/binding-darwin-x64': 1.0.2 + '@rolldown/binding-freebsd-x64': 1.0.2 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.2 + '@rolldown/binding-linux-arm64-gnu': 1.0.2 + '@rolldown/binding-linux-arm64-musl': 1.0.2 + '@rolldown/binding-linux-ppc64-gnu': 1.0.2 + '@rolldown/binding-linux-s390x-gnu': 1.0.2 + '@rolldown/binding-linux-x64-gnu': 1.0.2 + '@rolldown/binding-linux-x64-musl': 1.0.2 + '@rolldown/binding-openharmony-arm64': 1.0.2 + '@rolldown/binding-wasm32-wasi': 1.0.2 + '@rolldown/binding-win32-arm64-msvc': 1.0.2 + '@rolldown/binding-win32-x64-msvc': 1.0.2 + + rolldown@1.0.3: + dependencies: + '@oxc-project/types': 0.133.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.3 + '@rolldown/binding-darwin-arm64': 1.0.3 + '@rolldown/binding-darwin-x64': 1.0.3 + '@rolldown/binding-freebsd-x64': 1.0.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.3 + '@rolldown/binding-linux-arm64-musl': 1.0.3 + '@rolldown/binding-linux-ppc64-gnu': 1.0.3 + '@rolldown/binding-linux-s390x-gnu': 1.0.3 + '@rolldown/binding-linux-x64-gnu': 1.0.3 + '@rolldown/binding-linux-x64-musl': 1.0.3 + '@rolldown/binding-openharmony-arm64': 1.0.3 + '@rolldown/binding-wasm32-wasi': 1.0.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.3 + '@rolldown/binding-win32-x64-msvc': 1.0.3 + + semver@7.8.1: {} + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@4.1.0: {} + + tinybench@2.9.0: {} + + tinyexec@1.2.3: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinyrainbow@3.1.0: {} + + tree-kill@1.2.2: {} + + tsdown@0.22.1(tsx@4.22.3)(typescript@6.0.3): + dependencies: + ansis: 4.3.0 + cac: 7.0.0 + defu: 6.1.7 + empathic: 2.0.1 + hookable: 6.1.1 + import-without-cache: 0.4.0 + obug: 2.1.1 + picomatch: 4.0.4 + rolldown: 1.0.3 + rolldown-plugin-dts: 0.25.2(rolldown@1.0.3)(typescript@6.0.3) + semver: 7.8.1 + tinyexec: 1.2.3 + tinyglobby: 0.2.16 + tree-kill: 1.2.2 + unconfig-core: 7.5.0 + optionalDependencies: + tsx: 4.22.3 + typescript: 6.0.3 + transitivePeerDependencies: + - '@ts-macro/tsc' + - '@typescript/native-preview' + - oxc-resolver + - vue-tsc + + tslib@2.8.1: + optional: true + tsx@4.22.3: dependencies: esbuild: 0.28.0 @@ -326,4 +1468,54 @@ snapshots: typescript@6.0.3: {} + unconfig-core@7.5.0: + dependencies: + '@quansync/fs': 1.0.0 + quansync: 1.0.0 + undici-types@7.24.6: {} + + vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.0.2 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 25.9.1 + esbuild: 0.28.0 + fsevents: 2.3.3 + tsx: 4.22.3 + + vitest@4.1.7(@types/node@25.9.1)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)): + dependencies: + '@vitest/expect': 4.1.7 + '@vitest/mocker': 4.1.7(vite@8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3)) + '@vitest/pretty-format': 4.1.7 + '@vitest/runner': 4.1.7 + '@vitest/snapshot': 4.1.7 + '@vitest/spy': 4.1.7 + '@vitest/utils': 4.1.7 + es-module-lexer: 2.1.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.2.3 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 8.0.14(@types/node@25.9.1)(esbuild@0.28.0)(tsx@4.22.3) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.9.1 + transitivePeerDependencies: + - msw + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 diff --git a/js/test/roundtrip.test.ts b/js/test/roundtrip.test.ts new file mode 100644 index 0000000..ed5df5a --- /dev/null +++ b/js/test/roundtrip.test.ts @@ -0,0 +1,184 @@ +import { describe, it, expect } from "vitest"; +import { encode, decode, type WeakProvider } from "../src/index.js"; + +describe("primitives & special numbers", () => { + const v = { + n: null, + u: undefined, + t: true, + f: false, + i: 42, + neg: -7, + big: 9007199254740993, // > MAX_SAFE as float still exact-int? -> 9007199254740992 region + fl: 3.14, + nan: NaN, + negZero: -0, + inf: Infinity, + ninf: -Infinity, + bi: 123456789012345678901234567890n, + nbi: -42n, + s: "héllo 🌊", + }; + const out = decode(encode(v)) as typeof v; + + it("null", () => expect(out.n).toBeNull()); + it("undefined preserved", () => expect(out.u === undefined && "u" in out).toBe(true)); + it("bool", () => expect(out.t === true && out.f === false).toBe(true)); + it("int", () => expect(out.i === 42 && out.neg === -7).toBe(true)); + it("float", () => expect(out.fl).toBe(3.14)); + it("NaN", () => expect(Number.isNaN(out.nan)).toBe(true)); + it("-0 preserved", () => expect(Object.is(out.negZero, -0)).toBe(true)); + it("Infinity", () => expect(out.inf === Infinity && out.ninf === -Infinity).toBe(true)); + it("bigint", () => expect(out.bi === 123456789012345678901234567890n && out.nbi === -42n).toBe(true)); + it("unicode string", () => expect(out.s).toBe("héllo 🌊")); +}); + +describe("cycles & shared identity", () => { + const a: any = { name: "a" }; + const b: any = { name: "b", peer: a }; + a.peer = b; + a.self = a; + const shared = { tag: "shared" }; + const root = { a, b, x: shared, y: shared }; + const out = decode(encode(root)) as any; + + it("cycle a.self === a", () => expect(out.a.self).toBe(out.a)); + it("cross ref a.peer === b", () => expect(out.a.peer).toBe(out.b)); + it("cross ref b.peer === a", () => expect(out.b.peer).toBe(out.a)); + it("shared identity preserved", () => expect(out.x).toBe(out.y)); +}); + +describe("Map / Set with object keys", () => { + const key = { k: 1 }; + const m = new Map([[key, "v"], ["str", 99]]); + const s = new Set([1, key, "z"]); + const root = { m, s, key }; + const out = decode(encode(root)) as any; + + it("map size", () => expect(out.m.size).toBe(2)); + it("map object-key identity", () => expect(out.m.get(out.key)).toBe("v")); + it("map string key", () => expect(out.m.get("str")).toBe(99)); + it("set has shared key", () => expect(out.s.has(out.key)).toBe(true)); + it("set primitive", () => expect(out.s.has(1) && out.s.has("z")).toBe(true)); +}); + +describe("Symbols", () => { + const reg = Symbol.for("app.id"); + const uniq = Symbol("desc"); + const root = { + [reg]: 1, + [uniq]: 2, + iter: Symbol.iterator, + pairA: uniq, + pairB: uniq, // same unique symbol used twice -> file-internal identity + }; + const out = decode(encode(root)) as any; + + it("registered symbol restored & identical", () => expect(out[Symbol.for("app.id")]).toBe(1)); + it("well-known Symbol.iterator", () => expect(out.iter).toBe(Symbol.iterator)); + it("unique symbol file-internal identity", () => expect(out.pairA).toBe(out.pairB)); + it("unique symbol key present with desc", () => { + const symKeys = Object.getOwnPropertySymbols(out); + const uniqKey = symKeys.find((k) => k.description === "desc" && k !== Symbol.for("app.id")); + expect(uniqKey !== undefined && out[uniqKey!] === 2).toBe(true); + }); +}); + +describe("WeakMap / WeakSet via explicit provider", () => { + const k1 = { id: 1 }; + const k2 = { id: 2 }; + const wm = new WeakMap(); + wm.set(k1, "one"); + wm.set(k2, "two"); + const ws = new WeakSet(); + ws.add(k1); + + // The keys must also be reachable in the graph for identity-based restore. + const root = { wm, ws, k1, k2 }; + + const provider: WeakProvider = { + weakMapEntries: () => [ + [k1, "one"], + [k2, "two"], + ], + weakSetValues: () => [k1], + }; + + const out = decode(encode(root, provider)) as any; + + it("weakmap restores via reachable key identity", () => expect(out.wm.get(out.k1)).toBe("one")); + it("weakmap second entry", () => expect(out.wm.get(out.k2)).toBe("two")); + it("weakset restores", () => expect(out.ws.has(out.k1) && !out.ws.has(out.k2)).toBe(true)); +}); + +describe("arrays with holes treated as undefined", () => { + const arr = [1, , 3]; // eslint-disable-line no-sparse-arrays + const out = decode(encode(arr)) as unknown[]; + + it("array length preserved", () => expect(out.length).toBe(3)); + it("array hole -> undefined", () => expect(out[1]).toBeUndefined()); +}); + +describe("Date", () => { + const root = { + epoch: new Date(0), + negative: new Date(-1), + normal: new Date("2024-01-15T12:00:00.000Z"), + far_future: new Date(253402300799999), + }; + const out = decode(encode(root)) as typeof root; + + it("date epoch", () => expect(out.epoch instanceof Date && out.epoch.getTime() === 0).toBe(true)); + it("date negative (pre-epoch)", () => + expect(out.negative instanceof Date && out.negative.getTime() === -1).toBe(true)); + it("date normal", () => + expect(out.normal instanceof Date && out.normal.getTime() === Date.parse("2024-01-15T12:00:00.000Z")).toBe(true)); + it("date far future", () => + expect(out.far_future instanceof Date && out.far_future.getTime() === 253402300799999).toBe(true)); + + it("date shared identity & value", () => { + const d = new Date(); + const idOut = decode(encode({ a: d, b: d })) as { a: Date; b: Date }; + expect(idOut.a === idOut.b && idOut.a.getTime() === d.getTime()).toBe(true); + }); +}); + +describe("ArrayBuffer / TypedArray", () => { + it("ArrayBuffer roundtrips type & bytes", () => { + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const out = decode(encode({ ab })) as { ab: ArrayBuffer }; + expect(out.ab instanceof ArrayBuffer && out.ab.byteLength === 4).toBe(true); + expect([...new Uint8Array(out.ab)].join(",")).toBe("1,2,3,4"); + }); + + const root = { + u8: new Uint8Array([0, 127, 255]), + i16: new Int16Array([-32768, 0, 32767]), + f64: new Float64Array([1.1, NaN, -0, Infinity]), + bi64: new BigInt64Array([0n, -1n, 9223372036854775807n]), + clamped: new Uint8ClampedArray([0, 128, 255]), + empty: new Uint8Array([]), + }; + const o = decode(encode(root)) as typeof root; + + it("Uint8Array type & values", () => + expect(o.u8 instanceof Uint8Array && [...o.u8].join(",") === "0,127,255").toBe(true)); + it("Int16Array type & values", () => + expect(o.i16 instanceof Int16Array && [...o.i16].join(",") === "-32768,0,32767").toBe(true)); + it("Float64Array type & special values", () => + expect( + o.f64 instanceof Float64Array && + o.f64[0] === 1.1 && + Number.isNaN(o.f64[1]) && + Object.is(o.f64[2], -0) && + o.f64[3] === Infinity + ).toBe(true)); + it("BigInt64Array type & values", () => + expect( + o.bi64 instanceof BigInt64Array && o.bi64[0] === 0n && o.bi64[1] === -1n && o.bi64[2] === 9223372036854775807n + ).toBe(true)); + it("Uint8ClampedArray type & values", () => + expect(o.clamped instanceof Uint8ClampedArray && [...o.clamped].join(",") === "0,128,255").toBe(true)); + it("empty Uint8Array", () => expect(o.empty instanceof Uint8Array && o.empty.length === 0).toBe(true)); +}); diff --git a/js/test/roundtrip.ts b/js/test/roundtrip.ts deleted file mode 100644 index 1816d5e..0000000 --- a/js/test/roundtrip.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { encode, decode, type WeakProvider } from "../src/index.js"; - -let pass = 0; -let fail = 0; -function check(name: string, cond: boolean): void { - if (cond) { - pass++; - console.log(" ok " + name); - } else { - fail++; - console.log("FAIL " + name); - } -} - -// ---- primitives & special numbers ---- -{ - const v = { - n: null, - u: undefined, - t: true, - f: false, - i: 42, - neg: -7, - big: 9007199254740993, // > MAX_SAFE as float still exact-int? -> 9007199254740992 region - fl: 3.14, - nan: NaN, - negZero: -0, - inf: Infinity, - ninf: -Infinity, - bi: 123456789012345678901234567890n, - nbi: -42n, - s: "héllo 🌊", - }; - const out = decode(encode(v)) as typeof v; - check("null", out.n === null); - check("undefined preserved", out.u === undefined && "u" in out); - check("bool", out.t === true && out.f === false); - check("int", out.i === 42 && out.neg === -7); - check("float", out.fl === 3.14); - check("NaN", Number.isNaN(out.nan)); - check("-0 preserved", Object.is(out.negZero, -0)); - check("Infinity", out.inf === Infinity && out.ninf === -Infinity); - check("bigint", out.bi === 123456789012345678901234567890n && out.nbi === -42n); - check("unicode string", out.s === "héllo 🌊"); -} - -// ---- cycles & shared identity ---- -{ - const a: any = { name: "a" }; - const b: any = { name: "b", peer: a }; - a.peer = b; - a.self = a; - const shared = { tag: "shared" }; - const root = { a, b, x: shared, y: shared }; - const out = decode(encode(root)) as any; - check("cycle a.self === a", out.a.self === out.a); - check("cross ref a.peer === b", out.a.peer === out.b); - check("cross ref b.peer === a", out.b.peer === out.a); - check("shared identity preserved", out.x === out.y); -} - -// ---- Map / Set with object keys ---- -{ - const key = { k: 1 }; - const m = new Map([[key, "v"], ["str", 99]]); - const s = new Set([1, key, "z"]); - const root = { m, s, key }; - const out = decode(encode(root)) as any; - check("map size", out.m.size === 2); - check("map object-key identity", out.m.get(out.key) === "v"); - check("map string key", out.m.get("str") === 99); - check("set has shared key", out.s.has(out.key)); - check("set primitive", out.s.has(1) && out.s.has("z")); -} - -// ---- Symbols ---- -{ - const reg = Symbol.for("app.id"); - const uniq = Symbol("desc"); - const root = { - [reg]: 1, - [uniq]: 2, - iter: Symbol.iterator, - pairA: uniq, - pairB: uniq, // same unique symbol used twice -> file-internal identity - }; - const out = decode(encode(root)) as any; - check("registered symbol restored & identical", out[Symbol.for("app.id")] === 1); - check("well-known Symbol.iterator", out.iter === Symbol.iterator); - check("unique symbol file-internal identity", out.pairA === out.pairB); - // unique symbol value is present under *some* symbol key - const symKeys = Object.getOwnPropertySymbols(out); - const uniqKey = symKeys.find((k) => k.description === "desc" && k !== Symbol.for("app.id")); - check("unique symbol key present with desc", uniqKey !== undefined && out[uniqKey!] === 2); -} - -// ---- WeakMap / WeakSet via explicit provider ---- -{ - const k1 = { id: 1 }; - const k2 = { id: 2 }; - const wm = new WeakMap(); - wm.set(k1, "one"); - wm.set(k2, "two"); - const ws = new WeakSet(); - ws.add(k1); - - // The keys must also be reachable in the graph for identity-based restore. - const root = { wm, ws, k1, k2 }; - - const provider: WeakProvider = { - weakMapEntries: () => [ - [k1, "one"], - [k2, "two"], - ], - weakSetValues: () => [k1], - }; - - const out = decode(encode(root, provider)) as any; - check("weakmap restores via reachable key identity", out.wm.get(out.k1) === "one"); - check("weakmap second entry", out.wm.get(out.k2) === "two"); - check("weakset restores", out.ws.has(out.k1) && !out.ws.has(out.k2)); -} - -// ---- arrays with holes treated as undefined (documented behavior) ---- -{ - const arr = [1, , 3]; // eslint-disable-line no-sparse-arrays - const out = decode(encode(arr)) as unknown[]; - check("array length preserved", out.length === 3); - check("array hole -> undefined", out[1] === undefined); -} - -// ---- Date ---- -{ - const root = { - epoch: new Date(0), - negative: new Date(-1), - normal: new Date("2024-01-15T12:00:00.000Z"), - far_future: new Date(253402300799999), - }; - const out = decode(encode(root)) as typeof root; - check("date epoch", out.epoch instanceof Date && out.epoch.getTime() === 0); - check("date negative (pre-epoch)", out.negative instanceof Date && out.negative.getTime() === -1); - check( - "date normal", - out.normal instanceof Date && out.normal.getTime() === Date.parse("2024-01-15T12:00:00.000Z") - ); - check("date far future", out.far_future instanceof Date && out.far_future.getTime() === 253402300799999); - - const d = new Date(); - const idRoot = { a: d, b: d }; - const idOut = decode(encode(idRoot)) as typeof idRoot; - check("date shared identity a === b", idOut.a === idOut.b); - check("date shared identity value", idOut.a.getTime() === d.getTime()); -} - -// ---- ArrayBuffer / TypedArray ---- -{ - const ab = new ArrayBuffer(4); - new Uint8Array(ab).set([1, 2, 3, 4]); - const out = decode(encode({ ab })) as { ab: ArrayBuffer }; - check("arraybuffer is ArrayBuffer", out.ab instanceof ArrayBuffer && out.ab.byteLength === 4); - check( - "arraybuffer bytes", - [...new Uint8Array(out.ab)].join(",") === "1,2,3,4" - ); - - const root = { - u8: new Uint8Array([0, 127, 255]), - i16: new Int16Array([-32768, 0, 32767]), - f64: new Float64Array([1.1, NaN, -0, Infinity]), - bi64: new BigInt64Array([0n, -1n, 9223372036854775807n]), - clamped: new Uint8ClampedArray([0, 128, 255]), - empty: new Uint8Array([]), - }; - const o = decode(encode(root)) as typeof root; - check("Uint8Array type & values", o.u8 instanceof Uint8Array && [...o.u8].join(",") === "0,127,255"); - check("Int16Array type & values", o.i16 instanceof Int16Array && [...o.i16].join(",") === "-32768,0,32767"); - check( - "Float64Array type & special values", - o.f64 instanceof Float64Array && - o.f64[0] === 1.1 && - Number.isNaN(o.f64[1]) && - Object.is(o.f64[2], -0) && - o.f64[3] === Infinity - ); - check( - "BigInt64Array type & values", - o.bi64 instanceof BigInt64Array && o.bi64[0] === 0n && o.bi64[1] === -1n && o.bi64[2] === 9223372036854775807n - ); - check( - "Uint8ClampedArray type & values", - o.clamped instanceof Uint8ClampedArray && [...o.clamped].join(",") === "0,128,255" - ); - check("empty Uint8Array", o.empty instanceof Uint8Array && o.empty.length === 0); -} - -console.log(`\n${pass} passed, ${fail} failed`); -if (fail > 0) process.exit(1); diff --git a/js/tsdown.config.ts b/js/tsdown.config.ts new file mode 100644 index 0000000..06c277a --- /dev/null +++ b/js/tsdown.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "tsdown"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: ["esm"], + dts: true, + clean: true, + // Zero runtime deps — nothing to externalize. +}); diff --git a/js/vitest.config.ts b/js/vitest.config.ts new file mode 100644 index 0000000..d9ebefd --- /dev/null +++ b/js/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["test/**/*.test.ts"], + environment: "node", + }, +}); From 8321459f5054bd2e7ca74d0833819f1f93c19d89 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 31 May 2026 01:40:58 +0000 Subject: [PATCH 5/9] chore: emit .js from tsdown; add oxlint, oxfmt, fast-check - tsdown: outExtensions -> .js / .d.ts (was .mjs / .d.mts); package.json main/module/types/exports realigned to dist/index.js + dist/index.d.ts - devDeps (exact pins): oxlint@1.67.0, oxfmt@0.52.0, fast-check@4.8.0 - scripts: lint, lint:fix, format, format:check - .oxlintrc.json: correctness category; disable unicorn/no-new-array (intentional length-preallocation in encode/decode) - .oxfmtrc.json: defaults; formatted all sources (cosmetic only: trailing commas / wrapping) - test/property.test.ts: fast-check property roundtrips (nested values, Int32Array, Date) - drop unused loss-of-precision literal from roundtrip test Verified: typecheck + lint + format:check clean, 43 tests pass, build ok. --- js/.oxfmtrc.json | 4 + js/.oxlintrc.json | 13 ++ js/package.json | 31 ++- js/pnpm-lock.yaml | 458 ++++++++++++++++++++++++++++++++++++++ js/src/decode.ts | 5 +- js/src/encode.ts | 13 +- js/test/property.test.ts | 51 +++++ js/test/roundtrip.test.ts | 31 ++- js/tsdown.config.ts | 2 + 9 files changed, 579 insertions(+), 29 deletions(-) create mode 100644 js/.oxfmtrc.json create mode 100644 js/.oxlintrc.json create mode 100644 js/test/property.test.ts diff --git a/js/.oxfmtrc.json b/js/.oxfmtrc.json new file mode 100644 index 0000000..55c15df --- /dev/null +++ b/js/.oxfmtrc.json @@ -0,0 +1,4 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "ignorePatterns": [] +} diff --git a/js/.oxlintrc.json b/js/.oxlintrc.json new file mode 100644 index 0000000..1d09893 --- /dev/null +++ b/js/.oxlintrc.json @@ -0,0 +1,13 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["typescript", "unicorn", "oxc"], + "categories": { + "correctness": "error" + }, + "rules": { + "unicorn/no-new-array": "off" + }, + "env": { + "builtin": true + } +} diff --git a/js/package.json b/js/package.json index 35d1e36..5e1f542 100644 --- a/js/package.json +++ b/js/package.json @@ -2,30 +2,37 @@ "name": "greft", "version": "1.0.0", "description": "", + "keywords": [], + "license": "ISC", + "author": "", + "files": [ + "dist" + ], "type": "module", - "main": "./dist/index.mjs", - "module": "./dist/index.mjs", - "types": "./dist/index.d.mts", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { - "types": "./dist/index.d.mts", - "import": "./dist/index.mjs" + "types": "./dist/index.d.ts", + "import": "./dist/index.js" } }, - "files": [ - "dist" - ], "scripts": { "build": "tsdown", "test": "vitest run", "test:watch": "vitest", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "lint": "oxlint", + "lint:fix": "oxlint --fix", + "format": "oxfmt", + "format:check": "oxfmt --check" }, - "keywords": [], - "author": "", - "license": "ISC", "devDependencies": { "@types/node": "25.9.1", + "fast-check": "4.8.0", + "oxfmt": "0.52.0", + "oxlint": "1.67.0", "tsdown": "0.22.1", "tsx": "4.22.3", "typescript": "6.0.3", diff --git a/js/pnpm-lock.yaml b/js/pnpm-lock.yaml index c73f87f..17cdbdd 100644 --- a/js/pnpm-lock.yaml +++ b/js/pnpm-lock.yaml @@ -11,6 +11,15 @@ importers: '@types/node': specifier: 25.9.1 version: 25.9.1 + fast-check: + specifier: 4.8.0 + version: 4.8.0 + oxfmt: + specifier: 0.52.0 + version: 0.52.0 + oxlint: + specifier: 1.67.0 + version: 1.67.0 tsdown: specifier: 0.22.1 version: 0.22.1(tsx@4.22.3)(typescript@6.0.3) @@ -237,6 +246,250 @@ packages: '@oxc-project/types@0.133.0': resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + '@oxfmt/binding-android-arm-eabi@0.52.0': + resolution: {integrity: sha512-17EMSJnQ9g+upVHrAUYDMfH5lvRKQ9Nvg8WtEoH72oDr1VpWz+7/o3tD97U1EToen2YAQ/68JmtDYkQUi20dfQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxfmt/binding-android-arm64@0.52.0': + resolution: {integrity: sha512-A2G1IdwGEW2lLJkIxcvuirRH1CzSl/e0NX11zTlW1gvxJThfwbI/BEoaKrTNpm7M2FchvIf6guvIQU7d5iz+OQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxfmt/binding-darwin-arm64@0.52.0': + resolution: {integrity: sha512-f9+bLvOYxy7NttCLFTvQ7afmqDOWY4wIP9xdvfj5trQ1qj6f2UFAGwZESlfsMjvJNTyRpXfIlOanCI9FOvoeQA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxfmt/binding-darwin-x64@0.52.0': + resolution: {integrity: sha512-YSTB9sJ5nnQd/Q0ddHkgof0ZCHPAnWZT1IW2SJ8omz7CP7KluJhO1fNHrpqdxCtpztJwSs4hY1uAee35wKxxaw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxfmt/binding-freebsd-x64@0.52.0': + resolution: {integrity: sha512-NIrRNTTPCs4UbmVs0bxLSCDlLCtIRMJIXklNKaXa5Oj2/K1UIMBvgE8+uPVo01Io3N9HF0+GAX+aAHjUgZS7vA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxfmt/binding-linux-arm-gnueabihf@0.52.0': + resolution: {integrity: sha512-JXUCde8mn3GpgQouz2PXUokgy/uT1QrRJBL2s983VWcSQp62wTFYiNXgTKdeo1Jgbr0IgUnKKvzIk/YBlj/nVQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.52.0': + resolution: {integrity: sha512-psbUXaRZ+V8DaXz10Qf7LSHtdtdKAmC8fxXgeU608jjzrmWK4quamZMOpl6sf+dikoFHA85uE93Q0BqxrCdQrQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm64-gnu@0.52.0': + resolution: {integrity: sha512-Jw7MgWUU9lcLCcy82updISP3EthTlfvAwR6gWNxPzqly7+fLvOi2gHQE9xXQjpqaVLm/8P+gOzlv9ODuoVlaaw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-arm64-musl@0.52.0': + resolution: {integrity: sha512-wZg6bLjDvh2KibyI3QFUYo8GTXneIFsd0JvehtvJiUmQ8WRPERgxd/VM4ctWb86U5FT1FkqgS8/wZKVB+AZScg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-ppc64-gnu@0.52.0': + resolution: {integrity: sha512-IngE8uxhNvxcMrLjZNDo9xNLY7rEK33AKnaMd2B46he1e/mz2CfcW6If/U1wUjdRZddm1QzQaciqZkuMkdh1FA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-gnu@0.52.0': + resolution: {integrity: sha512-H3+DdFMv/efN3Efmhsv18jDrpiWWqKG7wsfAlQBqAt6z/E2Bx+TwEj2Nowe51CPOWB8/mFBC2dAMSgVFLvvowA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.52.0': + resolution: {integrity: sha512-zji+1kb7lJKohSDjzC1IsS+K/cKRs1hdVf0ZH0VbdbiakmtLvN9twBoXo/k8VdjFax7kfo+DyPxS7vv52br1aw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.52.0': + resolution: {integrity: sha512-hcLBYedpCy7ToUvvBidWk7+11Yhg1oAZ4+6hKPic/mQI6NaqXJSXMps5nFlwUuX2ewhtLZZDPg63TI042qGKBg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-gnu@0.52.0': + resolution: {integrity: sha512-IDO2loXK2OtTOhSPchU9MW25mWL2QCDGdJbjN8MXKZVS80qXe5gMTwQWu/gMJ3juoBHbkuUZNB2N1LHzNT7DoA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-musl@0.52.0': + resolution: {integrity: sha512-mAV2Hjn0SatJ+KoAzKUC3eJhdJ8wv+3m1KyuS0dTsbF0c5weq+QrCt/DRZZM+uj/XiKzCDEUKYsBF30e2qkcyw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-openharmony-arm64@0.52.0': + resolution: {integrity: sha512-vd4npaUIwChxp7XzkqmepBWTT9YMcSe/NBApVGPC30/lLyOVaV3dvma1SKo03t8O73BPRAG7EyJzGlN5cJM5hQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxfmt/binding-win32-arm64-msvc@0.52.0': + resolution: {integrity: sha512-k2sz6gWQdMfh5HPpIS+Bw/0UEV/kaK2xuqJRrWL233sEHx9WLlsmvlPFM4HUNThkYbSN0U0vPW7LVKZWDS8hPQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxfmt/binding-win32-ia32-msvc@0.52.0': + resolution: {integrity: sha512-rhke69GTcArodLHpjMTfNnvjTEBryDeZcUCKK/VjXDMtfTULl6QRh0ymX5/hbCUv2WjYm9h/QbW++q2vE15gWQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxfmt/binding-win32-x64-msvc@0.52.0': + resolution: {integrity: sha512-q5xL7oeXkZdEtNZWBdvehJcmt+GRu9l2bK40yJs1jJXlqq+r0Hygb1rTjq+FM2o/2xyt4cufH6KRplHp3Jjsvw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint/binding-android-arm-eabi@1.67.0': + resolution: {integrity: sha512-VrSi571rDv1N8HaEDM+DEX8nmT0y9jJo8tzzW13vsOWTx59xQczCIJx68n2zWOXRT5YKZsOZXp4qkHN/10x4mw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.67.0': + resolution: {integrity: sha512-l6+NdYxMoRohix5r5bbigW16LPicceCwGcQ6LKKuE1kUdjgFfQolJjrJsQYPFetIs78Gxj/G/f5TEGoTCwj9nQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.67.0': + resolution: {integrity: sha512-jOzXxS1AxFxhImLIRbtGIMrEwaXcgMw3gR57WB1cRk8ai+vpr6726kxXqVvlNsrXtJ/FrmOm8RxlC0m8SW24Qg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.67.0': + resolution: {integrity: sha512-3DFAVY94OqjIZHXIPz37yGRSWwOFTAqChQ64/M69GYLawzP0KiwdhDNfqdKKYT0bTR/DNxmMnQsj3ns+8+X/Lg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.67.0': + resolution: {integrity: sha512-e4dDKZuLu8TR9DEBssWSDahlPgZBwojTTHZUvnjBRJfJJbpxYCjfjKfi0Z1+CSLMiJBwI2yCDtRM1XJQaARjmg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.67.0': + resolution: {integrity: sha512-BKytFdcQzbITV3xlnzDUDTEDtbUMCCiC4EaNTDZ4FyT8gdNvBC4gfiLucXp/sQl0XU3p7syTlorUWVVVBZab2g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.67.0': + resolution: {integrity: sha512-XYAv0esBDX7BpTzRDjVX2Vdj+zndd8ll2dFQiaeQ6zTZr7A8GRDTN7fH3FP3jU+O0vCDx85oH/EtG7BzPgAXuw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.67.0': + resolution: {integrity: sha512-zizRMjA0i6u/2B0evgda04iycu+MoNuf1pBy6Eh+1CjC5wMEG7qN5zdDKTCvFc0KSYSDM9QTG3gjZHirgtQuKg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.67.0': + resolution: {integrity: sha512-zB/Tf6sUjmmvvbva9Gj3JTJ8rJ9t4I8/U0o6vSRtd0DRIsIuyegBwJAzhSUFQHdMijIRJkW0exs/yBhpw2S20w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.67.0': + resolution: {integrity: sha512-kgU40Gt74CK0TCsF51KZymkIwN9U0BajKsMijB52zPqOeZU9NAHkA/NSQkZDHEaCakx42DxhXkODiAqf2b4Gug==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.67.0': + resolution: {integrity: sha512-tOYhkk/iaG9aD3FvGpBFd1Lrw0x0RaVoJBxjUkfNzS50rC5NS5BteNCwgr8A2zCdADrIIoze6D7u6U5Ic++/iQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.67.0': + resolution: {integrity: sha512-sEtywrPb+0b+tHYl1SDCrw903fiC4eyKoNqzP3v+f2JT3Xcv4NEYG+P8rj+eEnX7IWhqV/xj8/JmcmVj21CXaA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.67.0': + resolution: {integrity: sha512-BvR8Moa0zCLxroOx4vZaZN9nUfwAUpSTwjZdxZyKy4bv3PrzrXrxKR/ZQ0L9wNSvlPhnMJeZfa3q5w6ZCTuN6Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.67.0': + resolution: {integrity: sha512-mm2cxM6fksOpq6l0uFws8BUGKAR4dNa/cZCn37Npq7PFbhD5HDJqWfnoIvTaeRKMy5XdS2tO0MA0qbHDrnXAAA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.67.0': + resolution: {integrity: sha512-WmbMuLapKyDlobMkXAaAL0Y+Uczh4LETfIfQsUpbId4Ip8Ai82/jqeYTOoUCkuuhBFapgqP253+d83tLKOksJg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.67.0': + resolution: {integrity: sha512-9g/PqxYJelzzTAOR5Y+RiRqdeydhEuXv2KxNeFcAKQ7UsvnWSY1OP4MsuPMbTO2Pf70tz7mFhl1j13H3fyh+8g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.67.0': + resolution: {integrity: sha512-2VhwE6Gatb0vJGnN0TBuQMbKCOiZlSQ/zJvVWYLK4a9d4iDiJOen/yVQkGpmsJ90MuH66fzi0kEKI0jRQMDxGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.67.0': + resolution: {integrity: sha512-EQ3VExXfeM1InbE5+JjufhZZTWy+kHUwgt3yZR7gQ47Je/mE0WspQPan0OJznh493L5anM210YNJtH1PXjTSFg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.67.0': + resolution: {integrity: sha512-bw24y+/1MHS4QDkons3YyHkPT9uCMoLHHgQhb+mb8NOjTYwub1CZ+K9Ngr8aO5DMrDrkqHwTzlTwFP2vS8Y/ZQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@quansync/fs@1.0.0': resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} @@ -544,6 +797,10 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + fast-check@4.8.0: + resolution: {integrity: sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg==} + engines: {node: '>=12.17.0'} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -659,6 +916,32 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + oxfmt@0.52.0: + resolution: {integrity: sha512-nJlYM35F64zTDMecCNhoHNkf+D/eHv7xcjj9XDSj+bFAVtN93m7v8DQMdHd6nDG6Akf/kEYYHmDUBs2Dz27Sug==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + svelte: ^5.0.0 + vite-plus: '*' + peerDependenciesMeta: + svelte: + optional: true + vite-plus: + optional: true + + oxlint@1.67.0: + resolution: {integrity: sha512-blwwaHPdoH8piQ5/z0KHeoHFR7FZgl12WluKJfu4qFLPkZl6mK04PkLE45Fw1NxfBRSlh40Gu7MkxHUw++ociQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=0.22.1' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -673,6 +956,9 @@ packages: resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} + pure-rand@8.4.0: + resolution: {integrity: sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==} + quansync@1.0.0: resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} @@ -737,6 +1023,10 @@ packages: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tinypool@2.1.0: + resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} + engines: {node: ^20.0.0 || >=22.0.0} + tinyrainbow@3.1.0: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} @@ -1030,6 +1320,120 @@ snapshots: '@oxc-project/types@0.133.0': {} + '@oxfmt/binding-android-arm-eabi@0.52.0': + optional: true + + '@oxfmt/binding-android-arm64@0.52.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.52.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.52.0': + optional: true + + '@oxfmt/binding-freebsd-x64@0.52.0': + optional: true + + '@oxfmt/binding-linux-arm-gnueabihf@0.52.0': + optional: true + + '@oxfmt/binding-linux-arm-musleabihf@0.52.0': + optional: true + + '@oxfmt/binding-linux-arm64-gnu@0.52.0': + optional: true + + '@oxfmt/binding-linux-arm64-musl@0.52.0': + optional: true + + '@oxfmt/binding-linux-ppc64-gnu@0.52.0': + optional: true + + '@oxfmt/binding-linux-riscv64-gnu@0.52.0': + optional: true + + '@oxfmt/binding-linux-riscv64-musl@0.52.0': + optional: true + + '@oxfmt/binding-linux-s390x-gnu@0.52.0': + optional: true + + '@oxfmt/binding-linux-x64-gnu@0.52.0': + optional: true + + '@oxfmt/binding-linux-x64-musl@0.52.0': + optional: true + + '@oxfmt/binding-openharmony-arm64@0.52.0': + optional: true + + '@oxfmt/binding-win32-arm64-msvc@0.52.0': + optional: true + + '@oxfmt/binding-win32-ia32-msvc@0.52.0': + optional: true + + '@oxfmt/binding-win32-x64-msvc@0.52.0': + optional: true + + '@oxlint/binding-android-arm-eabi@1.67.0': + optional: true + + '@oxlint/binding-android-arm64@1.67.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.67.0': + optional: true + + '@oxlint/binding-darwin-x64@1.67.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.67.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.67.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.67.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.67.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.67.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.67.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.67.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.67.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.67.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.67.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.67.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.67.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.67.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.67.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.67.0': + optional: true + '@quansync/fs@1.0.0': dependencies: quansync: 1.0.0 @@ -1260,6 +1664,10 @@ snapshots: expect-type@1.3.0: {} + fast-check@4.8.0: + dependencies: + pure-rand: 8.4.0 + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 @@ -1334,6 +1742,52 @@ snapshots: obug@2.1.1: {} + oxfmt@0.52.0: + dependencies: + tinypool: 2.1.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.52.0 + '@oxfmt/binding-android-arm64': 0.52.0 + '@oxfmt/binding-darwin-arm64': 0.52.0 + '@oxfmt/binding-darwin-x64': 0.52.0 + '@oxfmt/binding-freebsd-x64': 0.52.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.52.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.52.0 + '@oxfmt/binding-linux-arm64-gnu': 0.52.0 + '@oxfmt/binding-linux-arm64-musl': 0.52.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.52.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.52.0 + '@oxfmt/binding-linux-riscv64-musl': 0.52.0 + '@oxfmt/binding-linux-s390x-gnu': 0.52.0 + '@oxfmt/binding-linux-x64-gnu': 0.52.0 + '@oxfmt/binding-linux-x64-musl': 0.52.0 + '@oxfmt/binding-openharmony-arm64': 0.52.0 + '@oxfmt/binding-win32-arm64-msvc': 0.52.0 + '@oxfmt/binding-win32-ia32-msvc': 0.52.0 + '@oxfmt/binding-win32-x64-msvc': 0.52.0 + + oxlint@1.67.0: + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.67.0 + '@oxlint/binding-android-arm64': 1.67.0 + '@oxlint/binding-darwin-arm64': 1.67.0 + '@oxlint/binding-darwin-x64': 1.67.0 + '@oxlint/binding-freebsd-x64': 1.67.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.67.0 + '@oxlint/binding-linux-arm-musleabihf': 1.67.0 + '@oxlint/binding-linux-arm64-gnu': 1.67.0 + '@oxlint/binding-linux-arm64-musl': 1.67.0 + '@oxlint/binding-linux-ppc64-gnu': 1.67.0 + '@oxlint/binding-linux-riscv64-gnu': 1.67.0 + '@oxlint/binding-linux-riscv64-musl': 1.67.0 + '@oxlint/binding-linux-s390x-gnu': 1.67.0 + '@oxlint/binding-linux-x64-gnu': 1.67.0 + '@oxlint/binding-linux-x64-musl': 1.67.0 + '@oxlint/binding-openharmony-arm64': 1.67.0 + '@oxlint/binding-win32-arm64-msvc': 1.67.0 + '@oxlint/binding-win32-ia32-msvc': 1.67.0 + '@oxlint/binding-win32-x64-msvc': 1.67.0 + pathe@2.0.3: {} picocolors@1.1.1: {} @@ -1346,6 +1800,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + pure-rand@8.4.0: {} + quansync@1.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -1427,6 +1883,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinypool@2.1.0: {} + tinyrainbow@3.1.0: {} tree-kill@1.2.2: {} diff --git a/js/src/decode.ts b/js/src/decode.ts index 5d34237..ebf2003 100644 --- a/js/src/decode.ts +++ b/js/src/decode.ts @@ -11,7 +11,7 @@ interface DecodedNode { const wellKnownByName = new Map( (Object.getOwnPropertyNames(Symbol) as Array) .filter((n) => typeof (Symbol as any)[n] === "symbol") - .map((n) => [String(n), (Symbol as any)[n] as symbol]) + .map((n) => [String(n), (Symbol as any)[n] as symbol]), ); export function decode(bytes: Uint8Array): unknown { @@ -96,7 +96,8 @@ function readNode(r: ByteReader): DecodedNode { value: obj, fill: (resolve) => { for (const p of props) { - const k = p.kind === KeyKind.String ? (p.key as string) : (resolve(p.key as number) as symbol); + const k = + p.kind === KeyKind.String ? (p.key as string) : (resolve(p.key as number) as symbol); obj[k] = resolve(p.val); } }, diff --git a/js/src/encode.ts b/js/src/encode.ts index 328baa7..9a9b358 100644 --- a/js/src/encode.ts +++ b/js/src/encode.ts @@ -15,7 +15,7 @@ interface Node { const wellKnownSymbols = new Map( (Object.getOwnPropertyNames(Symbol) as Array) .filter((n) => typeof (Symbol as any)[n] === "symbol") - .map((n) => [(Symbol as any)[n] as symbol, String(n)]) + .map((n) => [(Symbol as any)[n] as symbol, String(n)]), ); export function encode(root: unknown, provider: WeakProvider = {}): Uint8Array { @@ -86,10 +86,11 @@ export function encode(root: unknown, provider: WeakProvider = {}): Uint8Array { function buildSymbol(sym: symbol): Node { const wk = wellKnownSymbols.get(sym); - if (wk) return leaf((w) => { - w.u8(Tag.SymbolWellKnown); - w.str(wk); - }); + if (wk) + return leaf((w) => { + w.u8(Tag.SymbolWellKnown); + w.str(wk); + }); const key = Symbol.keyFor(sym); if (key !== undefined) { return leaf((w) => { @@ -197,7 +198,7 @@ export function encode(root: unknown, provider: WeakProvider = {}): Uint8Array { // Plain object: own enumerable string + symbol keys. const stringKeys = Object.keys(obj); const symKeys = Object.getOwnPropertySymbols(obj).filter( - (s) => Object.getOwnPropertyDescriptor(obj, s)?.enumerable + (s) => Object.getOwnPropertyDescriptor(obj, s)?.enumerable, ); const props: Array<{ kind: KeyKind; key: string | number; val: number }> = []; for (const k of stringKeys) { diff --git a/js/test/property.test.ts b/js/test/property.test.ts new file mode 100644 index 0000000..403c26c --- /dev/null +++ b/js/test/property.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect } from "vitest"; +import fc from "fast-check"; +import { encode, decode } from "../src/index.js"; + +// Property: decode(encode(x)) is structurally equal to x for any JSON-like +// value graph. fast-check shrinks counter-examples to a minimal failing case. +describe("roundtrip properties", () => { + // Leaf values graft can represent losslessly and that compare with toEqual. + const leaf = fc.oneof( + fc.constant(null), + fc.boolean(), + fc.integer(), + fc.double({ noDefaultInfinity: false, noNaN: true }), + fc.string(), + fc.bigInt(), + ); + + const anyValue = fc.letrec((tie) => ({ + value: fc.oneof({ depthSize: "small" }, leaf, tie("array"), tie("object")), + array: fc.array(tie("value"), { maxLength: 8 }), + object: fc.dictionary(fc.string(), tie("value"), { maxKeys: 8 }), + })).value; + + it("any nested value survives encode -> decode", () => { + fc.assert( + fc.property(anyValue, (v) => { + expect(decode(encode(v))).toEqual(v); + }), + ); + }); + + it("typed arrays survive encode -> decode", () => { + fc.assert( + fc.property(fc.array(fc.integer({ min: -2147483648, max: 2147483647 })), (xs) => { + const arr = Int32Array.from(xs); + const out = decode(encode(arr)) as Int32Array; + expect(out instanceof Int32Array).toBe(true); + expect([...out]).toEqual([...arr]); + }), + ); + }); + + it("dates survive encode -> decode", () => { + fc.assert( + fc.property(fc.date({ noInvalidDate: true }), (d) => { + const out = decode(encode(d)) as Date; + expect(out instanceof Date && out.getTime() === d.getTime()).toBe(true); + }), + ); + }); +}); diff --git a/js/test/roundtrip.test.ts b/js/test/roundtrip.test.ts index ed5df5a..6b1a59f 100644 --- a/js/test/roundtrip.test.ts +++ b/js/test/roundtrip.test.ts @@ -9,7 +9,6 @@ describe("primitives & special numbers", () => { f: false, i: 42, neg: -7, - big: 9007199254740993, // > MAX_SAFE as float still exact-int? -> 9007199254740992 region fl: 3.14, nan: NaN, negZero: -0, @@ -29,7 +28,8 @@ describe("primitives & special numbers", () => { it("NaN", () => expect(Number.isNaN(out.nan)).toBe(true)); it("-0 preserved", () => expect(Object.is(out.negZero, -0)).toBe(true)); it("Infinity", () => expect(out.inf === Infinity && out.ninf === -Infinity).toBe(true)); - it("bigint", () => expect(out.bi === 123456789012345678901234567890n && out.nbi === -42n).toBe(true)); + it("bigint", () => + expect(out.bi === 123456789012345678901234567890n && out.nbi === -42n).toBe(true)); it("unicode string", () => expect(out.s).toBe("héllo 🌊")); }); @@ -50,7 +50,10 @@ describe("cycles & shared identity", () => { describe("Map / Set with object keys", () => { const key = { k: 1 }; - const m = new Map([[key, "v"], ["str", 99]]); + const m = new Map([ + [key, "v"], + ["str", 99], + ]); const s = new Set([1, key, "z"]); const root = { m, s, key }; const out = decode(encode(root)) as any; @@ -132,9 +135,13 @@ describe("Date", () => { it("date negative (pre-epoch)", () => expect(out.negative instanceof Date && out.negative.getTime() === -1).toBe(true)); it("date normal", () => - expect(out.normal instanceof Date && out.normal.getTime() === Date.parse("2024-01-15T12:00:00.000Z")).toBe(true)); + expect( + out.normal instanceof Date && out.normal.getTime() === Date.parse("2024-01-15T12:00:00.000Z"), + ).toBe(true)); it("date far future", () => - expect(out.far_future instanceof Date && out.far_future.getTime() === 253402300799999).toBe(true)); + expect(out.far_future instanceof Date && out.far_future.getTime() === 253402300799999).toBe( + true, + )); it("date shared identity & value", () => { const d = new Date(); @@ -172,13 +179,19 @@ describe("ArrayBuffer / TypedArray", () => { o.f64[0] === 1.1 && Number.isNaN(o.f64[1]) && Object.is(o.f64[2], -0) && - o.f64[3] === Infinity + o.f64[3] === Infinity, ).toBe(true)); it("BigInt64Array type & values", () => expect( - o.bi64 instanceof BigInt64Array && o.bi64[0] === 0n && o.bi64[1] === -1n && o.bi64[2] === 9223372036854775807n + o.bi64 instanceof BigInt64Array && + o.bi64[0] === 0n && + o.bi64[1] === -1n && + o.bi64[2] === 9223372036854775807n, ).toBe(true)); it("Uint8ClampedArray type & values", () => - expect(o.clamped instanceof Uint8ClampedArray && [...o.clamped].join(",") === "0,128,255").toBe(true)); - it("empty Uint8Array", () => expect(o.empty instanceof Uint8Array && o.empty.length === 0).toBe(true)); + expect(o.clamped instanceof Uint8ClampedArray && [...o.clamped].join(",") === "0,128,255").toBe( + true, + )); + it("empty Uint8Array", () => + expect(o.empty instanceof Uint8Array && o.empty.length === 0).toBe(true)); }); diff --git a/js/tsdown.config.ts b/js/tsdown.config.ts index 06c277a..d1d7f04 100644 --- a/js/tsdown.config.ts +++ b/js/tsdown.config.ts @@ -5,5 +5,7 @@ export default defineConfig({ format: ["esm"], dts: true, clean: true, + // Emit .js / .d.ts instead of the default .mjs / .d.mts (package is ESM). + outExtensions: () => ({ js: ".js", dts: ".d.ts" }), // Zero runtime deps — nothing to externalize. }); From 9df044f7cc1f70a80b6840e2d1226fbd1f3bf61d Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 31 May 2026 01:45:10 +0000 Subject: [PATCH 6/9] refactor: drop .js extension from relative imports moduleResolution is "bundler" and the lib is bundled by tsdown (run via tsx/vitest), so explicit .js extensions are unnecessary. Extensionless specifiers across src/test/scripts; behavior unchanged. Verified: typecheck + lint + format:check clean, 43 tests pass, build ok, golden vectors byte-identical. --- js/scripts/gen-golden.ts | 2 +- js/src/decode.ts | 4 ++-- js/src/encode.ts | 4 ++-- js/src/index.ts | 8 ++++---- js/test/property.test.ts | 2 +- js/test/roundtrip.test.ts | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/js/scripts/gen-golden.ts b/js/scripts/gen-golden.ts index af82eac..8a52084 100644 --- a/js/scripts/gen-golden.ts +++ b/js/scripts/gen-golden.ts @@ -8,7 +8,7 @@ import { writeFileSync, mkdirSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; -import { encode } from "../src/index.js"; +import { encode } from "../src/index"; const here = dirname(fileURLToPath(import.meta.url)); const goldenDir = join(here, "..", "..", "spec", "golden"); diff --git a/js/src/decode.ts b/js/src/decode.ts index ebf2003..6588652 100644 --- a/js/src/decode.ts +++ b/js/src/decode.ts @@ -1,5 +1,5 @@ -import { ByteReader } from "./buffer.js"; -import { MAGIC, VERSION, Tag, KeyKind, ElementType } from "./format.js"; +import { ByteReader } from "./buffer"; +import { MAGIC, VERSION, Tag, KeyKind, ElementType } from "./format"; type Filler = (resolve: (idx: number) => unknown) => void; diff --git a/js/src/encode.ts b/js/src/encode.ts index 9a9b358..8c6e991 100644 --- a/js/src/encode.ts +++ b/js/src/encode.ts @@ -1,5 +1,5 @@ -import { ByteWriter } from "./buffer.js"; -import { MAGIC, VERSION, Tag, KeyKind, ElementType } from "./format.js"; +import { ByteWriter } from "./buffer"; +import { MAGIC, VERSION, Tag, KeyKind, ElementType } from "./format"; // Explicit contents for weak collections, since WeakMap/WeakSet are not // enumerable per spec. The caller supplies the entries they are holding. diff --git a/js/src/index.ts b/js/src/index.ts index 2e3ae1a..ebd4a8e 100644 --- a/js/src/index.ts +++ b/js/src/index.ts @@ -1,4 +1,4 @@ -export { encode, type WeakProvider } from "./encode.js"; -export { decode } from "./decode.js"; -export { Tag, KeyKind, ElementType, MAGIC, VERSION } from "./format.js"; -export { ByteReader, ByteWriter } from "./buffer.js"; +export { encode, type WeakProvider } from "./encode"; +export { decode } from "./decode"; +export { Tag, KeyKind, ElementType, MAGIC, VERSION } from "./format"; +export { ByteReader, ByteWriter } from "./buffer"; diff --git a/js/test/property.test.ts b/js/test/property.test.ts index 403c26c..23e1bed 100644 --- a/js/test/property.test.ts +++ b/js/test/property.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from "vitest"; import fc from "fast-check"; -import { encode, decode } from "../src/index.js"; +import { encode, decode } from "../src/index"; // Property: decode(encode(x)) is structurally equal to x for any JSON-like // value graph. fast-check shrinks counter-examples to a minimal failing case. diff --git a/js/test/roundtrip.test.ts b/js/test/roundtrip.test.ts index 6b1a59f..c68071d 100644 --- a/js/test/roundtrip.test.ts +++ b/js/test/roundtrip.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { encode, decode, type WeakProvider } from "../src/index.js"; +import { encode, decode, type WeakProvider } from "../src/index"; describe("primitives & special numbers", () => { const v = { From af86d27230410ca099b72db599b45e55bbcbafca Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 31 May 2026 01:47:30 +0000 Subject: [PATCH 7/9] docs: drop "do not touch" constraint from CLAUDE.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Next session will extend existing modules, so the "完了済み(触らないこと)" prohibition no longer applies. Heading is now just "完了済み". --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 660ba61..5f1c8e1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,7 +52,7 @@ graft/ ## 現在の実装状態 -### 完了済み(触らないこと) +### 完了済み - `buffer.ts`: ByteWriter / ByteReader(uvarint, svarint, f64, str) - `format.ts`: Tag 0〜31(Null〜WeakSet)、KeyKind、MAGIC(`GRF1`)、VERSION From 9f10cb9c57de899f5385f5ecdca085a4b7aa09e0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 31 May 2026 01:48:22 +0000 Subject: [PATCH 8/9] ci: add GitHub Actions workflow Runs on push to main and all PRs. Node 22 + Corepack-pinned pnpm 11.5.0, working-directory js/. Steps: install --frozen-lockfile, typecheck, lint, format:check, test, build. Caches the pnpm store keyed on the lockfile. --- .github/workflows/ci.yml | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..686e377 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,55 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + + defaults: + run: + working-directory: js + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + # pnpm version is pinned via the "packageManager" field in js/package.json. + - name: Enable Corepack + run: corepack enable + + - name: Resolve pnpm store path + run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_ENV" + + - uses: actions/cache@v4 + with: + path: ${{ env.STORE_PATH }} + key: pnpm-store-${{ runner.os }}-${{ hashFiles('js/pnpm-lock.yaml') }} + restore-keys: pnpm-store-${{ runner.os }}- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Typecheck + run: pnpm run typecheck + + - name: Lint + run: pnpm run lint + + - name: Format check + run: pnpm run format:check + + - name: Test + run: pnpm test + + - name: Build + run: pnpm run build From 65c3ac5b9b943b130cd869efd62e6fd2144ba6ca Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 31 May 2026 01:49:12 +0000 Subject: [PATCH 9/9] ci: test across Node 22, 24, 26 Add a build matrix over Node 22/24/26 (fail-fast disabled). pnpm store cache key now includes the node version to avoid cross-job collisions. --- .github/workflows/ci.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 686e377..4a43e20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,6 +12,11 @@ jobs: build: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node-version: [22, 24, 26] + defaults: run: working-directory: js @@ -21,7 +26,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: ${{ matrix.node-version }} # pnpm version is pinned via the "packageManager" field in js/package.json. - name: Enable Corepack @@ -33,8 +38,8 @@ jobs: - uses: actions/cache@v4 with: path: ${{ env.STORE_PATH }} - key: pnpm-store-${{ runner.os }}-${{ hashFiles('js/pnpm-lock.yaml') }} - restore-keys: pnpm-store-${{ runner.os }}- + key: pnpm-store-${{ runner.os }}-node${{ matrix.node-version }}-${{ hashFiles('js/pnpm-lock.yaml') }} + restore-keys: pnpm-store-${{ runner.os }}-node${{ matrix.node-version }}- - name: Install dependencies run: pnpm install --frozen-lockfile