diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index c1bd204..0000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = { - "env": { - "es6": true, - "node": true, - "mocha": true - }, - "parserOptions": { - "ecmaVersion": 2017, - "sourceType": "module" - }, - "extends": "eslint:recommended", - "rules": { - "indent": [ - "error", - 4 - ], - "linebreak-style": [ - "warn", - "unix" - ], - "quotes": [ - "error", - "double" - ], - "semi": [ - "error", - "always" - ] - } -}; diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6df4591 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# Normalize all text to LF in the repository and in every checkout, +# regardless of the local core.autocrlf setting. +* text=auto eol=lf + +# Binary test fixtures and artifacts: never subject to EOL conversion. +*.zkey -text +*.ptau -text +*.wtns -text +*.r1cs -text +*.wasm -text +*.png -text diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0706d5..6f3d14e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,12 +2,13 @@ name: Continuous Integration on: push: branches: - - main + - master pull_request: jobs: test: name: Test + timeout-minutes: 15 runs-on: ${{ matrix.os }} strategy: @@ -18,10 +19,10 @@ jobs: steps: - name: Checkout project - uses: actions/checkout@v3 + uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} check-latest: true @@ -30,5 +31,8 @@ jobs: - name: Install dependencies run: npm ci + - name: Install Playwright dependencies + run: npx playwright install + - name: Run tests run: npm test diff --git a/.gitignore b/.gitignore index 9e7d574..03111aa 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,7 @@ tmp # Workspace files are user-specific *.sublime-workspace + +# vitest browser-mode artifacts (failure screenshots, attachments) +.vitest-attachments/ +test/__screenshots__/ diff --git a/README.md b/README.md index 4efaf3d..e1b2e3d 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,176 @@ # fastfile -fastfile is a package to read/write binary files with a transparent cache. +fastfile is a package to read/write binary files through one small async API +backed by a transparent page cache. The same `FastFile` interface works over +several backends, so code written against it (for example the snarkjs +zkey/ptau readers) runs unchanged whether the data lives on disk, in memory, +behind an HTTP URL, or inside a browser `Blob`. + +## Capabilities + +- **Page-cached reads and writes** — small reads (headers, section tables, + `readULE32`-style scans) are served from cached pages; writes are cached and + flushed asynchronously, so sequential access to big files stays fast. +- **Multiple backends behind one API**: + + | Backend | Type tag | Where | What it is for | + |---|---|---|---| + | OS file | `file` | Node | Regular files, with direct-I/O fast paths for large aligned reads/writes | + | Memory | `mem` | Node + browser | A `Uint8Array` you already have, or a scratch file that never touches disk | + | Big memory | `bigMem` | Node + browser | Like `mem`, but paged so it can hold buffers past the V8 single-allocation limits | + | HTTP(S) | `http` | Node + browser | Read-only streaming over `Range` requests; falls back to buffering the whole body when the server does not support ranges | + | Blob | `blob` | Browser (and Node ≥ 18) | Read-only zero-copy reads from a `Blob`/`File` (e.g. ``), so multi-GB files stream chunk-by-chunk | + +- **Bounded memory on huge remote files** — with the `http` and `blob` + backends, large reads stream straight into the caller's buffer and only the + small-read page cache is retained; the whole file is never resident. +- **Node/browser split at package level** — the `exports` map gives Node the + filesystem implementation and gives bundlers a browser build with the + file backend removed (`mem`, `bigMem`, `http` and `blob` remain). + +## Install -This library maintains a read and write cache that allows to write asynchronously -improving the speed of the reads and writes of big files. +``` +npm install fastfile +``` + +## API + +All entry points return a promise of a `FastFile` handle: -## Usage +- `createOverride(o, cacheSize?, pageSize?)` — create a file, truncating any + existing one. +- `createNoOverride(o, cacheSize?, pageSize?)` — create a file, failing if it + already exists (Node only distinction; alias of `createOverride` in the + browser). +- `readExisting(o, cacheSize?, pageSize?)` — open read-only. Accepts the most + input shapes (see the examples). +- `readWriteExisting(o, cacheSize?, pageSize?)` — open an existing file + read/write. +- `readWriteExistingOrCreate(o, cacheSize?, pageSize?)` — like the above but + creates the file when missing. + +`o` is a file name/URL string, a `Uint8Array`, a `Blob`, or an options object +with a `type` tag from the table above. `cacheSize` (default 64 KiB) and +`pageSize` (default 8 KiB) tune the page cache. They apply to every source +form: for an options object they fill only the fields the object leaves +undefined, so explicit fields on the object always win. + +The returned handle offers, among others: ``` -npm install fastfile +read(len, pos) readToBuffer(buff, offset, len, pos) +write(buff, pos) readString(pos) +readULE32/readULE64/readUBE32(pos) +writeULE32/writeULE64/writeUBE32(v, pos) +totalSize close() discard() ``` -```javascript +Reads return `Uint8Array`s. `close()` flushes pending writes and is +idempotent (repeat calls return the same promise instead of throwing). -const fastFile = require("fastfile"); +## Examples -async function writeFile() { - const f = await fastFile.createOverride("pattern.bin"); +### OS files (Node) - const buff = Buffer.from("0001020304050607", "hex"); - for (let i=0; i<1000; i++) { - await f.write(buff, i*8); - } +```javascript +import * as fastFile from "fastfile"; - await f.close(); +const f = await fastFile.createOverride("pattern.bin"); +const buff = Buffer.from("0001020304050607", "hex"); +for (let i = 0; i < 1000; i++) { + await f.write(buff, i * 8); } +await f.close(); -async function readFile() { - const f = await fastFile.readExisting("pattern.bin"); +const r = await fastFile.readExisting("pattern.bin"); +const bytes = await r.read(16, 8); // 16 bytes from offset 8 +await r.close(); +``` - const buff = await f.read(16, 8); +From CommonJS use `const fastFile = require("fastfile");` — the `require` +condition resolves to the CJS build. - await f.close(); +### Memory - return buff; +```javascript +// Wrap bytes you already have (zero-copy): +const f1 = await fastFile.readExisting(new Uint8Array([1, 2, 3, 4])); -} +// Scratch file that never touches disk: +const f2 = await fastFile.createOverride({ type: "mem" }); +await f2.writeULE32(42, 0); + +// Beyond the single-allocation limit (paged): +const f3 = await fastFile.createOverride({ type: "bigMem" }); +``` -writeFile().then( () => { - readFile().then( (buff) => { - console.log(buff.toString("hex")); - }); +### HTTP(S) with Range streaming + +```javascript +// Works in Node and in the browser. Probes the server with +// `Range: bytes=0-0`: when ranges are supported, every read becomes a range +// request and large reads stream directly into your buffer -- the file is +// never fully resident. When the server ignores Range, the body is buffered +// once (the historical behavior). +const f = await fastFile.readExisting("https://example.com/circuit_final.zkey"); +console.log(f.totalSize); +const header = await f.read(32, 0); +await f.close(); + +// Explicit form with cache tuning: +const g = await fastFile.readExisting({ + type: "http", + url: "https://example.com/powersOfTau28_hez_final_10.ptau", + cacheSize: 1 << 20, + pageSize: 1 << 16, }); +``` + +#### Persistent warm start (browsers, experimental) +```javascript +// Opt-in: fetched blocks persist in IndexedDB, so a later session against +// the same URL serves its reads locally instead of re-downloading. +const f = await fastFile.readExisting({ + type: "http", + url: "https://example.com/circuit_final.zkey", + cache: true, // or tune it: + // cache: { blockSize: 1 << 21, maxBytes: 1 << 29, dbName: "fastfile-http-cache" }, +}); ``` -## License +Entries are keyed by URL plus the server's strong validator (ETag / +Last-Modified) and are dropped when the remote file changes; without a +strong validator nothing is cached. Works for Range-less servers too: the +first session persists the fully-downloaded body, and later sessions probe +conditionally (If-None-Match / If-Modified-Since) -- an unchanged file +answers a bodyless 304 and every read is served locally. Total storage is bounded (`maxBytes`, +default 512 MiB) with least-recently-opened files evicted first. Where +IndexedDB is unavailable (Node, private windows, blocked storage) the option +is a silent no-op and reads stream as usual. + +### Blob / File (browser) -fastfile is part of the iden3 project copyright 2018 0KIMS association and published with GPL-3 license. Please check the COPYING file for more details. +```javascript +// e.g. a multi-GB zkey selected by the user; blob.slice() is zero-copy, so +// bytes reach memory only as they are read. +document.querySelector("input[type=file]").addEventListener("change", async (e) => { + const f = await fastFile.readExisting(e.target.files[0]); + const magic = await f.read(4, 0); + await f.close(); +}); +``` + +### In the browser + +Bundlers pick the browser build automatically via the package `exports`. +String inputs to `readExisting` are treated as URLs (there is no filesystem); +writable files must be `mem`/`bigMem`. The file backend throws +`"File I/O is not supported in the browser"`. + +## License +fastfile is part of the iden3 project copyright 2018-2026 0KIMS association +and published with GPL-3 license. Please check the COPYING file for more +details. diff --git a/build/browser/browser.esm.js b/build/browser/browser.esm.js new file mode 100644 index 0000000..93fcf17 --- /dev/null +++ b/build/browser/browser.esm.js @@ -0,0 +1,674 @@ +//#region src/memfile.js +function e(e) { + let t = e.initialSize || 1 << 20, n = new s(); + return n.o = e, n.o.data = new Uint8Array(t), n.allocSize = t, n.totalSize = 0, n.readOnly = !1, n.pos = 0, n; +} +function t(e) { + let t = new s(); + return t.o = e, t.allocSize = e.data.byteLength, t.totalSize = e.data.byteLength, t.readOnly = !0, t.pos = 0, t; +} +function n(e) { + let t = new s(); + return t.o = e, t.allocSize = e.data.byteLength, t.totalSize = e.data.byteLength, t.readOnly = !1, t.pos = 0, t; +} +var r = /* @__PURE__ */ new Uint8Array(4), i = new DataView(r.buffer), a = /* @__PURE__ */ new Uint8Array(8), o = new DataView(a.buffer), s = class { + constructor() { + this.pageSize = 16384; + } + _resizeIfNeeded(e) { + if (e > this.allocSize) { + let t = Math.max(this.allocSize + (1 << 20), Math.floor(this.allocSize * 1.1), e), n = new Uint8Array(t); + n.set(this.o.data), this.o.data = n, this.allocSize = t; + } + } + async write(e, t) { + let n = this; + if (t === void 0 && (t = n.pos), this.readOnly) throw Error("Writing a read only file"); + this._resizeIfNeeded(t + e.byteLength), this.o.data.set(e.slice(), t), t + e.byteLength > this.totalSize && (this.totalSize = t + e.byteLength), this.pos = t + e.byteLength; + } + async readToBuffer(e, t, n, r) { + let i = this; + if (r === void 0 && (r = i.pos), this.readOnly && r + n > this.totalSize) throw Error("Reading out of bounds"); + this._resizeIfNeeded(r + n); + let a = new Uint8Array(this.o.data.buffer, this.o.data.byteOffset + r, n); + e.set(a, t), this.pos = r + n; + } + async read(e, t) { + let n = this, r = new Uint8Array(e); + return await n.readToBuffer(r, 0, e, t), r; + } + close() { + this.o.data.byteLength != this.totalSize && (this.o.data = this.o.data.slice(0, this.totalSize)); + } + async discard() {} + async writeULE32(e, t) { + let n = this; + i.setUint32(0, e, !0), await n.write(r, t); + } + async writeUBE32(e, t) { + let n = this; + i.setUint32(0, e, !1), await n.write(r, t); + } + async writeULE64(e, t) { + let n = this; + o.setUint32(0, e & 4294967295, !0), o.setUint32(4, Math.floor(e / 4294967296), !0), await n.write(a, t); + } + async readULE32(e) { + let t = await this.read(4, e); + return new Uint32Array(t.buffer)[0]; + } + async readUBE32(e) { + let t = await this.read(4, e); + return new DataView(t.buffer).getUint32(0, !1); + } + async readULE64(e) { + let t = await this.read(8, e), n = new Uint32Array(t.buffer); + return n[1] * 4294967296 + n[0]; + } + async readString(e) { + let t = this, n = e === void 0 ? t.pos : e; + if (n >= this.totalSize) { + if (this.readOnly) throw Error("Reading out of bounds"); + return ""; + } + let r = new Uint8Array(t.o.data.buffer, n, this.totalSize - n), i = r.findIndex((e) => e === 0), a = i !== -1, o = ""; + return a ? (o = new TextDecoder().decode(r.slice(0, i)), t.pos = n + i + 1) : t.pos = n, o; + } +}, c = 1 << 22; +function l(e) { + let t = e.initialSize || 0, n = new g(); + n.o = e; + let r = t ? Math.floor((t - 1) / c) + 1 : 0; + n.o.data = []; + for (let e = 0; e < r - 1; e++) n.o.data.push(new Uint8Array(c)); + return r && n.o.data.push(new Uint8Array(t - c * (r - 1))), n.totalSize = 0, n.readOnly = !1, n.pos = 0, n; +} +function u(e) { + let t = new g(); + return t.o = e, t.totalSize = (e.data.length - 1) * c + e.data[e.data.length - 1].byteLength, t.readOnly = !0, t.pos = 0, t; +} +function d(e) { + let t = new g(); + return t.o = e, t.totalSize = (e.data.length - 1) * c + e.data[e.data.length - 1].byteLength, t.readOnly = !1, t.pos = 0, t; +} +var f = /* @__PURE__ */ new Uint8Array(4), p = new DataView(f.buffer), m = /* @__PURE__ */ new Uint8Array(8), h = new DataView(m.buffer), g = class { + constructor() { + this.pageSize = 16384; + } + _resizeIfNeeded(e) { + if (e <= this.totalSize) return; + if (this.readOnly) throw Error("Reading out of file bounds"); + let t = Math.floor((e - 1) / c) + 1; + for (let n = Math.max(this.o.data.length - 1, 0); n < t; n++) { + let r = n < t - 1 ? c : e - (t - 1) * c, i = new Uint8Array(r); + n == this.o.data.length - 1 && i.set(this.o.data[n]), this.o.data[n] = i; + } + this.totalSize = e; + } + async write(e, t) { + let n = this; + if (t === void 0 && (t = n.pos), this.readOnly) throw Error("Writing a read only file"); + this._resizeIfNeeded(t + e.byteLength); + let r = Math.floor(t / c), i = t % c, a = e.byteLength; + for (; a > 0;) { + let t = i + a > c ? c - i : a, o = e.slice(e.byteLength - a, e.byteLength - a + t); + new Uint8Array(n.o.data[r].buffer, i, t).set(o), a -= t, r++, i = 0; + } + this.pos = t + e.byteLength; + } + async readToBuffer(e, t, n, r) { + let i = this; + if (r === void 0 && (r = i.pos), this.readOnly && r + n > this.totalSize) throw Error("Reading out of bounds"); + this._resizeIfNeeded(r + n); + let a = Math.floor(r / c), o = r % c, s = n; + for (; s > 0;) { + let r = o + s > c ? c - o : s, l = new Uint8Array(i.o.data[a].buffer, o, r); + e.set(l, t + n - s), s -= r, a++, o = 0; + } + this.pos = r + n; + } + async read(e, t) { + let n = this, r = new Uint8Array(e); + return await n.readToBuffer(r, 0, e, t), r; + } + close() {} + async discard() {} + async writeULE32(e, t) { + let n = this; + p.setUint32(0, e, !0), await n.write(f, t); + } + async writeUBE32(e, t) { + let n = this; + p.setUint32(0, e, !1), await n.write(f, t); + } + async writeULE64(e, t) { + let n = this; + h.setUint32(0, e & 4294967295, !0), h.setUint32(4, Math.floor(e / 4294967296), !0), await n.write(m, t); + } + async readULE32(e) { + let t = await this.read(4, e); + return new Uint32Array(t.buffer)[0]; + } + async readUBE32(e) { + let t = await this.read(4, e); + return new DataView(t.buffer).getUint32(0, !1); + } + async readULE64(e) { + let t = await this.read(8, e), n = new Uint32Array(t.buffer); + return n[1] * 4294967296 + n[0]; + } + async readString(e) { + let t = this, n = e === void 0 ? t.pos : e; + if (n > this.totalSize) { + if (this.readOnly) throw Error("Reading out of bounds"); + this._resizeIfNeeded(e); + } + let r = !1, i = ""; + for (; !r;) { + let e = Math.floor(n / c), a = n % c; + if (t.o.data[e] === void 0) throw Error("ERROR"); + let o = Math.min(2048, t.o.data[e].length - a); + if (o <= 0) return t.pos = n, i; + let s = new Uint8Array(t.o.data[e].buffer, a, o), l = s.findIndex((e) => e === 0); + r = l !== -1, r ? (i += new TextDecoder().decode(s.slice(0, l)), t.pos = e * c + a + l + 1) : (i += new TextDecoder().decode(s), t.pos = e * c + a + s.length), n = t.pos; + } + return i; + } +}, _ = 1 << 20, v = 8192, y = class { + constructor(e, t, n, r) { + this.readRangeInto = e, this.totalSize = t, this.pos = 0, this.pageSize = r || v, this.maxPagesLoaded = Math.floor((n || _) / this.pageSize) + 1, this.pages = /* @__PURE__ */ new Map(), this.readOnly = !0; + } + _pageLen(e) { + let t = e * this.pageSize; + return Math.min(t + this.pageSize, this.totalSize) - t; + } + _loadPage(e) { + let t = this, n = t.pages.get(e); + if (n) return t.pages.delete(e), t.pages.set(e, n), n.promise; + let r = new Uint8Array(t._pageLen(e)); + return n = { + buff: null, + promise: null + }, n.promise = t.readRangeInto(r, 0, e * t.pageSize, r.byteLength).then(function() { + return n.buff = r, r; + }, function(n) { + throw t.pages.delete(e), n; + }), t.pages.set(e, n), t._trimCache(), n.promise; + } + _trimCache() { + let e = this; + if (!(e.pages.size <= e.maxPagesLoaded)) for (let t of e.pages) { + if (e.pages.size <= e.maxPagesLoaded) return; + t[1].buff && e.pages.delete(t[0]); + } + } + async readToBuffer(e, t, n, r) { + let i = this; + if (n === 0) return; + if (i.pendingClose) throw Error("Reading a closing file"); + if (r === void 0 && (r = i.pos), r + n > i.totalSize) throw Error("Reading out of bounds"); + if (i.pos = r + n, n >= i.pageSize) { + await i.readRangeInto(e, t, r, n); + return; + } + let a = Math.floor(r / i.pageSize), o = Math.floor((r + n - 1) / i.pageSize), s = r % i.pageSize, c = 0; + for (let r = a; r <= o; r++) { + let a = await i._loadPage(r), o = Math.min(n - c, i.pageSize - s); + e.set(a.subarray(s, s + o), t + c), c += o, s = 0; + } + } + async read(e, t) { + let n = new Uint8Array(e); + return await this.readToBuffer(n, 0, e, t), n; + } + async readULE32(e) { + let t = await this.read(4, e); + return new Uint32Array(t.buffer)[0]; + } + async readUBE32(e) { + let t = await this.read(4, e); + return new DataView(t.buffer).getUint32(0, !1); + } + async readULE64(e) { + let t = await this.read(8, e), n = new Uint32Array(t.buffer); + return n[1] * 4294967296 + n[0]; + } + async readString(e) { + let t = this; + if (t.pendingClose) throw Error("Reading a closing file"); + let n = e === void 0 ? t.pos : e, r = []; + for (; n < t.totalSize;) { + let e = Math.min(t.pageSize, t.totalSize - n), i = await t.read(e, n), a = i.indexOf(0); + if (a >= 0) return r.push(i.subarray(0, a)), t.pos = n + a + 1, b(r); + r.push(i), n += e; + } + return t.pos = n, b(r); + } + async write() { + throw Error("Writing a read only file"); + } + async writeULE32() { + throw Error("Writing a read only file"); + } + async writeUBE32() { + throw Error("Writing a read only file"); + } + async writeULE64() { + throw Error("Writing a read only file"); + } + async close() { + this.pendingClose || (this.pendingClose = !0, this.pages.clear()); + } + async discard() { + await this.close(); + } +}; +function b(e) { + let t = 0; + for (let n = 0; n < e.length; n++) t += e[n].byteLength; + let n = new Uint8Array(t), r = 0; + for (let t = 0; t < e.length; t++) n.set(e[t], r), r += e[t].byteLength; + return new TextDecoder().decode(n); +} +//#endregion +//#region src/idbcache.js +var x = 1 << 21, S = 1 << 29, C = "fastfile-http-cache", w = /* @__PURE__ */ new Map(); +function T(e) { + if (w.has(e)) return w.get(e); + let t = new Promise((t, n) => { + let r = indexedDB.open(e, 1); + r.onupgradeneeded = () => { + let e = r.result; + e.createObjectStore("files"), e.createObjectStore("blocks"); + }, r.onsuccess = () => t(r.result), r.onerror = () => n(r.error), r.onblocked = () => n(/* @__PURE__ */ Error("IndexedDB open blocked")); + }); + return w.set(e, t), t.catch(() => w.delete(e)), t; +} +function E(e) { + return new Promise((t, n) => { + e.onsuccess = () => t(e.result), e.onerror = () => n(e.error); + }); +} +function D(e) { + return new Promise((t, n) => { + e.oncomplete = () => t(), e.onerror = () => n(e.error), e.onabort = () => n(e.error || /* @__PURE__ */ Error("IndexedDB transaction aborted")); + }); +} +function O(e, t, n) { + return IDBKeyRange.bound([e, t], [e, n]); +} +async function ee(e, t) { + let n = e.transaction(["files", "blocks"], "readwrite"); + n.objectStore("files").delete(t), n.objectStore("blocks").delete(O(t, 0, Infinity)), await D(n); +} +async function k(e, t, n, r, i, a) { + let o = e.transaction(["files", "blocks"], "readwrite"), s = o.objectStore("files"), c = await E(s.get(t)), l = 0; + c && c.validator === n && c.totalSize === r && c.blockSize === i ? l = c.bytes : c && o.objectStore("blocks").delete(O(t, 0, Infinity)), s.put({ + validator: n, + totalSize: r, + blockSize: i, + bytes: l, + lastUsed: Date.now() + }, t), await D(o); + let u = e.transaction("files", "readonly"), d = u.objectStore("files"), [f, p] = await Promise.all([E(d.getAllKeys()), E(d.getAll())]); + await D(u); + let m = p.reduce((e, t) => e + t.bytes, 0); + if (m <= a) return; + let h = f.map((e, t) => ({ + key: e, + meta: p[t] + })).filter((e) => e.key !== t).sort((e, t) => e.meta.lastUsed - t.meta.lastUsed); + for (let t of h) { + if (m <= a) break; + await ee(e, t.key), m -= t.meta.bytes; + } +} +async function A(e, t) { + let { fileKey: n, validator: r, totalSize: i } = t, a = typeof t.options == "object" && t.options || {}, o = a.blockSize || x, s = a.maxBytes || S, c = a.dbName || C; + if (typeof indexedDB > "u" || !r) return e; + let l; + try { + l = await T(c), await k(l, n, r, i, o, s); + } catch { + return e; + } + let u = !1; + async function d(e, t) { + let r = l.transaction("blocks", "readonly"), i = r.objectStore("blocks"), a = O(n, e, t), [o, s] = await Promise.all([E(i.getAllKeys(a)), E(i.getAll(a))]); + await D(r); + let c = /* @__PURE__ */ new Map(); + for (let e = 0; e < o.length; e++) c.set(o[e][1], s[e]); + return c; + } + async function f(e) { + if (!(u || e.length === 0)) try { + let t = l.transaction(["files", "blocks"], "readwrite"), r = t.objectStore("blocks"), i = t.objectStore("files"); + for (let t of e) r.put(t.data, [n, t.index]); + let a = await E(i.get(n)); + a && (a.bytes += e.reduce((e, t) => e + t.data.byteLength, 0), a.lastUsed = Date.now(), i.put(a, n)), await D(t); + } catch { + u = !0; + } + } + let p = (e) => Math.min(o, i - e * o), m = /* @__PURE__ */ new Map(); + function h(e) { + let t, n, r = new Promise((e, r) => { + t = e, n = r; + }); + return r.catch(() => {}), m.set(e, r), { + resolve: t, + reject: n, + promise: r + }; + } + return async function(t, n, r, i) { + if (i === 0) return; + let a = Math.floor(r / o), s = Math.floor((r + i - 1) / o), c = await d(a, s), l = [], u = (e, a) => { + let s = a * o, c = Math.max(r, s), l = Math.min(r + i, s + p(a)); + t.set(e.subarray(c - s, l - s), n + (c - r)); + }, g = a; + for (; g <= s;) { + let a = g * o, d = a + p(g), f = c.get(g); + if (f) { + u(f, g), g++; + continue; + } + let _ = m.get(g); + if (_) { + u(await _, g), g++; + continue; + } + if (a >= r && d <= r + i) { + let u = g; + for (; u + 1 <= s && !c.get(u + 1) && !m.get(u + 1) && (u + 1) * o + p(u + 1) <= r + i;) u++; + let d = a, f = u * o + p(u), _ = []; + for (let e = g; e <= u; e++) _.push(h(e)); + try { + await e(t, n + (d - r), d, f - d); + } catch (e) { + for (let t = g; t <= u; t++) _[t - g].reject(e), m.delete(t); + throw e; + } + for (let e = g; e <= u; e++) { + let i = e * o, a = t.slice(n + (i - r), n + (i - r) + p(e)); + _[e - g].resolve(a), l.push({ + index: e, + data: a + }); + } + g = u + 1; + } else { + let t = h(g), n = new Uint8Array(p(g)); + try { + await e(n, 0, a, n.length); + } catch (e) { + throw t.reject(e), m.delete(g), e; + } + t.resolve(n), u(n, g), l.push({ + index: g, + data: n + }), g++; + } + } + await f(l); + for (let e of l) m.delete(e.index); + }; +} +async function j(e) { + let t = (typeof e.options == "object" && e.options || {}).dbName || C; + if (typeof indexedDB > "u") return null; + try { + let n = (await T(t)).transaction("files", "readonly"), r = await E(n.objectStore("files").get(e.fileKey)); + return await D(n), r ? { + validator: r.validator, + totalSize: r.totalSize, + blockSize: r.blockSize, + bytes: r.bytes + } : null; + } catch { + return null; + } +} +async function M(e) { + let { fileKey: t, validator: n, totalSize: r, data: i } = e, a = typeof e.options == "object" && e.options || {}, o = a.blockSize || x, s = a.maxBytes || S, c = a.dbName || C; + if (typeof indexedDB > "u" || !n) return !1; + try { + let e = await T(c); + await k(e, t, n, r, o, s); + let a = Math.ceil(r / o); + for (let n = 0; n < a; n += 64) { + let s = e.transaction("blocks", "readwrite"), c = s.objectStore("blocks"); + for (let e = n; e < Math.min(n + 64, a); e++) { + let n = e * o; + c.put(i.slice(n, Math.min(n + o, r)), [t, e]); + } + await D(s); + } + let l = e.transaction("files", "readwrite"), u = l.objectStore("files"), d = await E(u.get(t)); + return d && (d.bytes = r, d.lastUsed = Date.now(), u.put(d, t)), await D(l), !0; + } catch { + return !1; + } +} +//#endregion +//#region src/httpfile.js +var N = 65536; +async function P(e) { + let n = e.url, r = e.cache ? await j({ + fileKey: n, + options: e.cache + }) : null, i = { Range: "bytes=0-0" }; + r && r.validator && (r.validator[0] === "\"" || r.validator.indexOf("W/") === 0 ? i["If-None-Match"] = r.validator : i["If-Modified-Since"] = r.validator); + let a; + try { + a = await fetch(n, { headers: i }); + } catch (e) { + if (!("If-None-Match" in i) && !("If-Modified-Since" in i)) throw e; + a = await fetch(n, { headers: { Range: "bytes=0-0" } }); + } + if (a.status === 304) return await K(a), await F(n, r.validator, r.totalSize, e); + if (a.status === 206) { + let t = a.headers.get("content-range"), r = t ? /\/(\d+)\s*$/.exec(t) : null; + if (r) { + let t = parseInt(r[1]); + return await a.arrayBuffer(), await F(n, L(a), t, e); + } + return await a.arrayBuffer(), await I(n); + } + if (!a.ok && a.status !== 416) throw Error("HTTP " + a.status + " fetching " + n); + if (a.status === 416) { + let e = a.headers.get("content-range"); + return e && /\/0\s*$/.test(e) ? t({ + type: "mem", + data: /* @__PURE__ */ new Uint8Array() + }) : await I(n); + } + let o = new Uint8Array(await a.arrayBuffer()); + if (e.cache) { + let t = L(a); + t && await M({ + fileKey: n, + validator: t, + totalSize: o.length, + options: e.cache, + data: o + }); + } + return t({ + type: "mem", + data: o + }); +} +async function F(e, t, n, r) { + let i = null, a = async function(n, r, a, o) { + if (!i) try { + return await W(e, t, n, r, a, o); + } catch (e) { + if (!e || !e.degradeToFull) throw e; + i = e.fullBodyPromise; + } + let s = await i; + if (a + o > s.byteLength) throw Error(e + ": read past the end of the buffered body"); + n.set(s.subarray(a, a + o), r); + }, o = Math.min(r.pageSize || N, N); + return r.cache && (a = await A(a, { + fileKey: e, + validator: t, + totalSize: n, + options: r.cache + })), new y(a, n, r.cacheSize, o); +} +async function I(e) { + let n = await fetch(e); + if (!n.ok) throw Error("HTTP " + n.status + " fetching " + e); + return t({ + type: "mem", + data: new Uint8Array(await n.arrayBuffer()) + }); +} +function L(e) { + let t = e.headers.get("etag"); + return t && t.indexOf("W/") !== 0 ? t : e.headers.get("last-modified") || null; +} +var R = 4, z = 0, B = []; +async function V(e) { + for (; z >= R;) await new Promise((e) => B.push(e)); + z++; + try { + return await e(); + } finally { + z--; + let e = B.shift(); + e && e(); + } +} +var H = { + retries: 3, + backoffMs: 300, + stallTimeoutMs: 1e4 +}, U = (e) => new Promise((t) => setTimeout(t, e)); +async function W(e, t, n, r, i, a) { + return V(async () => { + for (let o = 0;; o++) try { + return await G(e, t, n, r, i, a); + } catch (e) { + if (e && (e.degradeToFull || e.permanent) || o >= H.retries) throw e; + await U(H.backoffMs * (1 << o)); + } + }); +} +async function G(e, t, n, r, i, a) { + let o = { Range: "bytes=" + i + "-" + (i + a - 1) }; + t && (o["If-Range"] = t); + let s = typeof AbortController < "u" ? new AbortController() : null, c = null, l = () => { + s && (c && clearTimeout(c), c = setTimeout(() => s.abort(/* @__PURE__ */ Error(e + ": no data for " + H.stallTimeoutMs + "ms")), H.stallTimeoutMs)); + }, u = () => { + c && clearTimeout(c), c = null; + }; + l(); + let d; + try { + d = await fetch(e, s ? { + headers: o, + signal: s.signal + } : { headers: o }); + } catch (e) { + throw u(), e; + } + if (l(), d.status === 200) { + let n = L(d); + if (!t || n && n === t) { + u(); + let t = /* @__PURE__ */ Error(e + ": origin ignored Range; degrading to full buffering"); + throw t.degradeToFull = !0, t.fullBodyPromise = d.arrayBuffer().then((e) => new Uint8Array(e)), t; + } + u(), await K(d); + let r = /* @__PURE__ */ Error(e + ": file changed (or server stopped honoring Range) while reading"); + throw r.permanent = !0, r; + } + if (d.status !== 206) { + u(), await K(d); + let t = /* @__PURE__ */ Error("HTTP " + d.status + " reading range " + i + "+" + a + " of " + e); + throw d.status < 500 && d.status !== 429 && (t.permanent = !0), t; + } + let f = d.headers.get("content-range"), p = f ? /bytes\s+(\d+)-(\d+)\//.exec(f) : null; + if (p && parseInt(p[1]) !== i) throw await K(d), Error(e + ": server returned range starting at " + p[1] + ", requested " + i); + let m = 0; + try { + if (d.body && typeof d.body.getReader == "function") { + let t = d.body.getReader(); + for (;;) { + let i = await t.read(); + if (l(), i.done) break; + if (m + i.value.byteLength > a) throw t.cancel().catch(function() {}), Error(e + ": range response longer than requested"); + n.set(i.value, r + m), m += i.value.byteLength; + } + } else { + /* c8 ignore start */ + let t = new Uint8Array(await d.arrayBuffer()); + if (t.byteLength > a) throw Error(e + ": range response longer than requested"); + n.set(t, r), m = t.byteLength; + } + } finally { + u(); + } + if (m !== a) throw Error(e + ": short range response (" + m + "/" + a + " bytes at " + i + ")"); +} +async function K(e) { + try { + e.body && typeof e.body.cancel == "function" ? await e.body.cancel() : await e.arrayBuffer(); + } catch {} +} +//#endregion +//#region src/blobfile.js +var q = 1 << 20; +function J(e) { + let t = e.blob, n = async function(e, n, r, i) { + let a = await t.slice(r, r + i).arrayBuffer(); + if (a.byteLength !== i) throw Error("short blob read (" + a.byteLength + "/" + i + " bytes at " + r + ")"); + e.set(new Uint8Array(a), n); + }, r = Math.min(e.pageSize || q, q); + return new y(n, t.size, e.cacheSize, r); +} +//#endregion +//#region src/fastfile.browser.js +function Y() { + throw Error("File I/O is not supported in the browser"); +} +function X(e) { + return e instanceof Uint8Array ? { + type: "mem", + data: e + } : (typeof e == "string" && Y(), e); +} +function Z(e, t, n) { + if (e.type === "file" && Y(), e.type === "mem") return t(e); + if (e.type === "bigMem") return n(e); + throw Error("Invalid FastFile type: " + e.type); +} +function Q(t) { + return Z(X(t), e, l); +} +var te = Q; +async function ne(e, n, r) { + return e instanceof Uint8Array && (e = { + type: "mem", + data: e + }), typeof Blob < "u" && e instanceof Blob && (e = { + type: "blob", + blob: e, + cacheSize: n, + pageSize: r + }), typeof e == "string" && (e = { + type: "http", + url: e, + cacheSize: n, + pageSize: r + }), e.type === "http" ? await P(e) : e.type === "blob" ? J(e) : Z(e, t, u); +} +function $(e) { + return typeof e == "string" && Y(), Z(e, n, d); +} +var re = $; +//#endregion +export { te as createNoOverride, Q as createOverride, ne as readExisting, $ as readWriteExisting, re as readWriteExistingOrCreate }; diff --git a/build/browser/browser.iife.js b/build/browser/browser.iife.js new file mode 100644 index 0000000..cbe3261 --- /dev/null +++ b/build/browser/browser.iife.js @@ -0,0 +1 @@ +var fastfile=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function t(e){let t=e.initialSize||1<<20,n=new c;return n.o=e,n.o.data=new Uint8Array(t),n.allocSize=t,n.totalSize=0,n.readOnly=!1,n.pos=0,n}function n(e){let t=new c;return t.o=e,t.allocSize=e.data.byteLength,t.totalSize=e.data.byteLength,t.readOnly=!0,t.pos=0,t}function r(e){let t=new c;return t.o=e,t.allocSize=e.data.byteLength,t.totalSize=e.data.byteLength,t.readOnly=!1,t.pos=0,t}var i=new Uint8Array(4),a=new DataView(i.buffer),o=new Uint8Array(8),s=new DataView(o.buffer),c=class{constructor(){this.pageSize=16384}_resizeIfNeeded(e){if(e>this.allocSize){let t=Math.max(this.allocSize+(1<<20),Math.floor(this.allocSize*1.1),e),n=new Uint8Array(t);n.set(this.o.data),this.o.data=n,this.allocSize=t}}async write(e,t){let n=this;if(t===void 0&&(t=n.pos),this.readOnly)throw Error(`Writing a read only file`);this._resizeIfNeeded(t+e.byteLength),this.o.data.set(e.slice(),t),t+e.byteLength>this.totalSize&&(this.totalSize=t+e.byteLength),this.pos=t+e.byteLength}async readToBuffer(e,t,n,r){let i=this;if(r===void 0&&(r=i.pos),this.readOnly&&r+n>this.totalSize)throw Error(`Reading out of bounds`);this._resizeIfNeeded(r+n);let a=new Uint8Array(this.o.data.buffer,this.o.data.byteOffset+r,n);e.set(a,t),this.pos=r+n}async read(e,t){let n=this,r=new Uint8Array(e);return await n.readToBuffer(r,0,e,t),r}close(){this.o.data.byteLength!=this.totalSize&&(this.o.data=this.o.data.slice(0,this.totalSize))}async discard(){}async writeULE32(e,t){let n=this;a.setUint32(0,e,!0),await n.write(i,t)}async writeUBE32(e,t){let n=this;a.setUint32(0,e,!1),await n.write(i,t)}async writeULE64(e,t){let n=this;s.setUint32(0,e&4294967295,!0),s.setUint32(4,Math.floor(e/4294967296),!0),await n.write(o,t)}async readULE32(e){let t=await this.read(4,e);return new Uint32Array(t.buffer)[0]}async readUBE32(e){let t=await this.read(4,e);return new DataView(t.buffer).getUint32(0,!1)}async readULE64(e){let t=await this.read(8,e),n=new Uint32Array(t.buffer);return n[1]*4294967296+n[0]}async readString(e){let t=this,n=e===void 0?t.pos:e;if(n>=this.totalSize){if(this.readOnly)throw Error(`Reading out of bounds`);return``}let r=new Uint8Array(t.o.data.buffer,n,this.totalSize-n),i=r.findIndex(e=>e===0),a=i!==-1,o=``;return a?(o=new TextDecoder().decode(r.slice(0,i)),t.pos=n+i+1):t.pos=n,o}},l=1<<22;function u(e){let t=e.initialSize||0,n=new _;n.o=e;let r=t?Math.floor((t-1)/l)+1:0;n.o.data=[];for(let e=0;e0;){let t=i+a>l?l-i:a,o=e.slice(e.byteLength-a,e.byteLength-a+t);new Uint8Array(n.o.data[r].buffer,i,t).set(o),a-=t,r++,i=0}this.pos=t+e.byteLength}async readToBuffer(e,t,n,r){let i=this;if(r===void 0&&(r=i.pos),this.readOnly&&r+n>this.totalSize)throw Error(`Reading out of bounds`);this._resizeIfNeeded(r+n);let a=Math.floor(r/l),o=r%l,s=n;for(;s>0;){let r=o+s>l?l-o:s,c=new Uint8Array(i.o.data[a].buffer,o,r);e.set(c,t+n-s),s-=r,a++,o=0}this.pos=r+n}async read(e,t){let n=this,r=new Uint8Array(e);return await n.readToBuffer(r,0,e,t),r}close(){}async discard(){}async writeULE32(e,t){let n=this;m.setUint32(0,e,!0),await n.write(p,t)}async writeUBE32(e,t){let n=this;m.setUint32(0,e,!1),await n.write(p,t)}async writeULE64(e,t){let n=this;g.setUint32(0,e&4294967295,!0),g.setUint32(4,Math.floor(e/4294967296),!0),await n.write(h,t)}async readULE32(e){let t=await this.read(4,e);return new Uint32Array(t.buffer)[0]}async readUBE32(e){let t=await this.read(4,e);return new DataView(t.buffer).getUint32(0,!1)}async readULE64(e){let t=await this.read(8,e),n=new Uint32Array(t.buffer);return n[1]*4294967296+n[0]}async readString(e){let t=this,n=e===void 0?t.pos:e;if(n>this.totalSize){if(this.readOnly)throw Error(`Reading out of bounds`);this._resizeIfNeeded(e)}let r=!1,i=``;for(;!r;){let e=Math.floor(n/l),a=n%l;if(t.o.data[e]===void 0)throw Error(`ERROR`);let o=Math.min(2048,t.o.data[e].length-a);if(o<=0)return t.pos=n,i;let s=new Uint8Array(t.o.data[e].buffer,a,o),c=s.findIndex(e=>e===0);r=c!==-1,r?(i+=new TextDecoder().decode(s.slice(0,c)),t.pos=e*l+a+c+1):(i+=new TextDecoder().decode(s),t.pos=e*l+a+s.length),n=t.pos}return i}},v=1<<20,y=8192,b=class{constructor(e,t,n,r){this.readRangeInto=e,this.totalSize=t,this.pos=0,this.pageSize=r||y,this.maxPagesLoaded=Math.floor((n||v)/this.pageSize)+1,this.pages=new Map,this.readOnly=!0}_pageLen(e){let t=e*this.pageSize;return Math.min(t+this.pageSize,this.totalSize)-t}_loadPage(e){let t=this,n=t.pages.get(e);if(n)return t.pages.delete(e),t.pages.set(e,n),n.promise;let r=new Uint8Array(t._pageLen(e));return n={buff:null,promise:null},n.promise=t.readRangeInto(r,0,e*t.pageSize,r.byteLength).then(function(){return n.buff=r,r},function(n){throw t.pages.delete(e),n}),t.pages.set(e,n),t._trimCache(),n.promise}_trimCache(){let e=this;if(!(e.pages.size<=e.maxPagesLoaded))for(let t of e.pages){if(e.pages.size<=e.maxPagesLoaded)return;t[1].buff&&e.pages.delete(t[0])}}async readToBuffer(e,t,n,r){let i=this;if(n===0)return;if(i.pendingClose)throw Error(`Reading a closing file`);if(r===void 0&&(r=i.pos),r+n>i.totalSize)throw Error(`Reading out of bounds`);if(i.pos=r+n,n>=i.pageSize){await i.readRangeInto(e,t,r,n);return}let a=Math.floor(r/i.pageSize),o=Math.floor((r+n-1)/i.pageSize),s=r%i.pageSize,c=0;for(let r=a;r<=o;r++){let a=await i._loadPage(r),o=Math.min(n-c,i.pageSize-s);e.set(a.subarray(s,s+o),t+c),c+=o,s=0}}async read(e,t){let n=new Uint8Array(e);return await this.readToBuffer(n,0,e,t),n}async readULE32(e){let t=await this.read(4,e);return new Uint32Array(t.buffer)[0]}async readUBE32(e){let t=await this.read(4,e);return new DataView(t.buffer).getUint32(0,!1)}async readULE64(e){let t=await this.read(8,e),n=new Uint32Array(t.buffer);return n[1]*4294967296+n[0]}async readString(e){let t=this;if(t.pendingClose)throw Error(`Reading a closing file`);let n=e===void 0?t.pos:e,r=[];for(;n=0)return r.push(i.subarray(0,a)),t.pos=n+a+1,x(r);r.push(i),n+=e}return t.pos=n,x(r)}async write(){throw Error(`Writing a read only file`)}async writeULE32(){throw Error(`Writing a read only file`)}async writeUBE32(){throw Error(`Writing a read only file`)}async writeULE64(){throw Error(`Writing a read only file`)}async close(){this.pendingClose||(this.pendingClose=!0,this.pages.clear())}async discard(){await this.close()}};function x(e){let t=0;for(let n=0;n{let r=indexedDB.open(e,1);r.onupgradeneeded=()=>{let e=r.result;e.createObjectStore(`files`),e.createObjectStore(`blocks`)},r.onsuccess=()=>t(r.result),r.onerror=()=>n(r.error),r.onblocked=()=>n(Error(`IndexedDB open blocked`))});return T.set(e,t),t.catch(()=>T.delete(e)),t}function D(e){return new Promise((t,n)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>n(e.error)})}function O(e){return new Promise((t,n)=>{e.oncomplete=()=>t(),e.onerror=()=>n(e.error),e.onabort=()=>n(e.error||Error(`IndexedDB transaction aborted`))})}function k(e,t,n){return IDBKeyRange.bound([e,t],[e,n])}async function ee(e,t){let n=e.transaction([`files`,`blocks`],`readwrite`);n.objectStore(`files`).delete(t),n.objectStore(`blocks`).delete(k(t,0,1/0)),await O(n)}async function A(e,t,n,r,i,a){let o=e.transaction([`files`,`blocks`],`readwrite`),s=o.objectStore(`files`),c=await D(s.get(t)),l=0;c&&c.validator===n&&c.totalSize===r&&c.blockSize===i?l=c.bytes:c&&o.objectStore(`blocks`).delete(k(t,0,1/0)),s.put({validator:n,totalSize:r,blockSize:i,bytes:l,lastUsed:Date.now()},t),await O(o);let u=e.transaction(`files`,`readonly`),d=u.objectStore(`files`),[f,p]=await Promise.all([D(d.getAllKeys()),D(d.getAll())]);await O(u);let m=p.reduce((e,t)=>e+t.bytes,0);if(m<=a)return;let h=f.map((e,t)=>({key:e,meta:p[t]})).filter(e=>e.key!==t).sort((e,t)=>e.meta.lastUsed-t.meta.lastUsed);for(let t of h){if(m<=a)break;await ee(e,t.key),m-=t.meta.bytes}}async function j(e,t){let{fileKey:n,validator:r,totalSize:i}=t,a=typeof t.options==`object`&&t.options||{},o=a.blockSize||S,s=a.maxBytes||C,c=a.dbName||w;if(typeof indexedDB>`u`||!r)return e;let l;try{l=await E(c),await A(l,n,r,i,o,s)}catch{return e}let u=!1;async function d(e,t){let r=l.transaction(`blocks`,`readonly`),i=r.objectStore(`blocks`),a=k(n,e,t),[o,s]=await Promise.all([D(i.getAllKeys(a)),D(i.getAll(a))]);await O(r);let c=new Map;for(let e=0;ee+t.data.byteLength,0),a.lastUsed=Date.now(),i.put(a,n)),await O(t)}catch{u=!0}}let p=e=>Math.min(o,i-e*o),m=new Map;function h(e){let t,n,r=new Promise((e,r)=>{t=e,n=r});return r.catch(()=>{}),m.set(e,r),{resolve:t,reject:n,promise:r}}return async function(t,n,r,i){if(i===0)return;let a=Math.floor(r/o),s=Math.floor((r+i-1)/o),c=await d(a,s),l=[],u=(e,a)=>{let s=a*o,c=Math.max(r,s),l=Math.min(r+i,s+p(a));t.set(e.subarray(c-s,l-s),n+(c-r))},g=a;for(;g<=s;){let a=g*o,d=a+p(g),f=c.get(g);if(f){u(f,g),g++;continue}let _=m.get(g);if(_){u(await _,g),g++;continue}if(a>=r&&d<=r+i){let u=g;for(;u+1<=s&&!c.get(u+1)&&!m.get(u+1)&&(u+1)*o+p(u+1)<=r+i;)u++;let d=a,f=u*o+p(u),_=[];for(let e=g;e<=u;e++)_.push(h(e));try{await e(t,n+(d-r),d,f-d)}catch(e){for(let t=g;t<=u;t++)_[t-g].reject(e),m.delete(t);throw e}for(let e=g;e<=u;e++){let i=e*o,a=t.slice(n+(i-r),n+(i-r)+p(e));_[e-g].resolve(a),l.push({index:e,data:a})}g=u+1}else{let t=h(g),n=new Uint8Array(p(g));try{await e(n,0,a,n.length)}catch(e){throw t.reject(e),m.delete(g),e}t.resolve(n),u(n,g),l.push({index:g,data:n}),g++}}await f(l);for(let e of l)m.delete(e.index)}}async function M(e){let t=(typeof e.options==`object`&&e.options||{}).dbName||w;if(typeof indexedDB>`u`)return null;try{let n=(await E(t)).transaction(`files`,`readonly`),r=await D(n.objectStore(`files`).get(e.fileKey));return await O(n),r?{validator:r.validator,totalSize:r.totalSize,blockSize:r.blockSize,bytes:r.bytes}:null}catch{return null}}async function N(e){let{fileKey:t,validator:n,totalSize:r,data:i}=e,a=typeof e.options==`object`&&e.options||{},o=a.blockSize||S,s=a.maxBytes||C,c=a.dbName||w;if(typeof indexedDB>`u`||!n)return!1;try{let e=await E(c);await A(e,t,n,r,o,s);let a=Math.ceil(r/o);for(let n=0;ns.byteLength)throw Error(e+`: read past the end of the buffered body`);n.set(s.subarray(a,a+o),r)},o=Math.min(r.pageSize||P,P);return r.cache&&(a=await j(a,{fileKey:e,validator:t,totalSize:n,options:r.cache})),new b(a,n,r.cacheSize,o)}async function L(e){let t=await fetch(e);if(!t.ok)throw Error(`HTTP `+t.status+` fetching `+e);return n({type:`mem`,data:new Uint8Array(await t.arrayBuffer())})}function R(e){let t=e.headers.get(`etag`);return t&&t.indexOf(`W/`)!==0?t:e.headers.get(`last-modified`)||null}var z=4,B=0,V=[];async function te(e){for(;B>=z;)await new Promise(e=>V.push(e));B++;try{return await e()}finally{B--;let e=V.shift();e&&e()}}var H={retries:3,backoffMs:300,stallTimeoutMs:1e4},U=e=>new Promise(t=>setTimeout(t,e));async function W(e,t,n,r,i,a){return te(async()=>{for(let o=0;;o++)try{return await G(e,t,n,r,i,a)}catch(e){if(e&&(e.degradeToFull||e.permanent)||o>=H.retries)throw e;await U(H.backoffMs*(1<{s&&(c&&clearTimeout(c),c=setTimeout(()=>s.abort(Error(e+`: no data for `+H.stallTimeoutMs+`ms`)),H.stallTimeoutMs))},u=()=>{c&&clearTimeout(c),c=null};l();let d;try{d=await fetch(e,s?{headers:o,signal:s.signal}:{headers:o})}catch(e){throw u(),e}if(l(),d.status===200){let n=R(d);if(!t||n&&n===t){u();let t=Error(e+`: origin ignored Range; degrading to full buffering`);throw t.degradeToFull=!0,t.fullBodyPromise=d.arrayBuffer().then(e=>new Uint8Array(e)),t}u(),await K(d);let r=Error(e+`: file changed (or server stopped honoring Range) while reading`);throw r.permanent=!0,r}if(d.status!==206){u(),await K(d);let t=Error(`HTTP `+d.status+` reading range `+i+`+`+a+` of `+e);throw d.status<500&&d.status!==429&&(t.permanent=!0),t}let f=d.headers.get(`content-range`),p=f?/bytes\s+(\d+)-(\d+)\//.exec(f):null;if(p&&parseInt(p[1])!==i)throw await K(d),Error(e+`: server returned range starting at `+p[1]+`, requested `+i);let m=0;try{if(d.body&&typeof d.body.getReader==`function`){let t=d.body.getReader();for(;;){let i=await t.read();if(l(),i.done)break;if(m+i.value.byteLength>a)throw t.cancel().catch(function(){}),Error(e+`: range response longer than requested`);n.set(i.value,r+m),m+=i.value.byteLength}}else{let t=new Uint8Array(await d.arrayBuffer());if(t.byteLength>a)throw Error(e+`: range response longer than requested`);n.set(t,r),m=t.byteLength}}finally{u()}if(m!==a)throw Error(e+`: short range response (`+m+`/`+a+` bytes at `+i+`)`)}async function K(e){try{e.body&&typeof e.body.cancel==`function`?await e.body.cancel():await e.arrayBuffer()}catch{}}var q=1<<20;function J(e){let t=e.blob,n=async function(e,n,r,i){let a=await t.slice(r,r+i).arrayBuffer();if(a.byteLength!==i)throw Error(`short blob read (`+a.byteLength+`/`+i+` bytes at `+r+`)`);e.set(new Uint8Array(a),n)},r=Math.min(e.pageSize||q,q);return new b(n,t.size,e.cacheSize,r)}function Y(){throw Error(`File I/O is not supported in the browser`)}function X(e){return e instanceof Uint8Array?{type:`mem`,data:e}:(typeof e==`string`&&Y(),e)}function Z(e,t,n){if(e.type===`file`&&Y(),e.type===`mem`)return t(e);if(e.type===`bigMem`)return n(e);throw Error(`Invalid FastFile type: `+e.type)}function Q(e){return Z(X(e),t,u)}var ne=Q;async function re(e,t,r){return e instanceof Uint8Array&&(e={type:`mem`,data:e}),typeof Blob<`u`&&e instanceof Blob&&(e={type:`blob`,blob:e,cacheSize:t,pageSize:r}),typeof e==`string`&&(e={type:`http`,url:e,cacheSize:t,pageSize:r}),e.type===`http`?await F(e):e.type===`blob`?J(e):Z(e,n,d)}function $(e){return typeof e==`string`&&Y(),Z(e,r,f)}var ie=$;return e.createNoOverride=ne,e.createOverride=Q,e.readExisting=re,e.readWriteExisting=$,e.readWriteExistingOrCreate=ie,e})({}); \ No newline at end of file diff --git a/build/main.cjs b/build/main.cjs deleted file mode 100644 index 260d9c9..0000000 --- a/build/main.cjs +++ /dev/null @@ -1,1029 +0,0 @@ -'use strict'; - -Object.defineProperty(exports, '__esModule', { value: true }); - -var fs = require('fs'); -var constants = require('constants'); - -function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } - -var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs); - -async function open(fileName, openFlags, cacheSize, pageSize) { - cacheSize = cacheSize || 4096*64; - if (typeof openFlags !== "number" && ["w+", "wx+", "r", "ax+", "a+"].indexOf(openFlags) <0) - throw new Error("Invalid open option"); - const fd =await fs__default["default"].promises.open(fileName, openFlags); - - const stats = await fd.stat(); - - return new FastFile(fd, stats, cacheSize, pageSize, fileName); -} - - -class FastFile { - - constructor(fd, stats, cacheSize, pageSize, fileName) { - this.fileName = fileName; - this.fd = fd; - this.pos = 0; - this.pageSize = pageSize || (1 << 8); - while (this.pageSize < stats.blksize) { - this.pageSize *= 2; - } - this.totalSize = stats.size; - this.totalPages = Math.floor((stats.size -1) / this.pageSize)+1; - this.maxPagesLoaded = Math.floor( cacheSize / this.pageSize)+1; - this.pages = {}; - this.pendingLoads = []; - this.writing = false; - this.reading = false; - this.avBuffs = []; - this.history = {}; - } - - _loadPage(p) { - const self = this; - const P = new Promise((resolve, reject)=> { - self.pendingLoads.push({ - page: p, - resolve: resolve, - reject: reject - }); - }); - self.__statusPage("After Load request: ", p); - return P; - } - - __statusPage(s, p) { - const logEntry = []; - const self=this; - if (!self.logHistory) return; - logEntry.push("==" + s+ " " +p); - let S = ""; - for (let i=0; i " + self.history[p][i][j]); - } - } - } - - - - _triggerLoad() { - const self = this; - - if (self.reading) return; - if (self.pendingLoads.length==0) return; - - const pageIdxs = Object.keys(self.pages); - - const deletablePages = []; - for (let i=0; i0) && - ( (typeof self.pages[self.pendingLoads[0].page] != "undefined" ) - ||( (freePages>0) - ||(deletablePages.length>0)))) { - const load = self.pendingLoads.shift(); - if (typeof self.pages[load.page] != "undefined") { - self.pages[load.page].pendingOps ++; - const idx = deletablePages.indexOf(load.page); - if (idx>=0) deletablePages.splice(idx, 1); - if (self.pages[load.page].loading) { - self.pages[load.page].loading.push(load); - } else { - load.resolve(); - } - self.__statusPage("After Load (cached): ", load.page); - - } else { - if (freePages) { - freePages--; - } else { - const fp = deletablePages.shift(); - self.__statusPage("Before Unload: ", fp); - self.avBuffs.unshift(self.pages[fp]); - delete self.pages[fp]; - self.__statusPage("After Unload: ", fp); - } - - if (load.page>=self.totalPages) { - self.pages[load.page] = getNewPage(); - load.resolve(); - self.__statusPage("After Load (new): ", load.page); - } else { - self.reading = true; - self.pages[load.page] = getNewPage(); - self.pages[load.page].loading = [load]; - ops.push(self.fd.read(self.pages[load.page].buff, 0, self.pageSize, load.page*self.pageSize).then((res)=> { - self.pages[load.page].size = res.bytesRead; - const loading = self.pages[load.page].loading; - delete self.pages[load.page].loading; - for (let i=0; i { - load.reject(err); - })); - self.__statusPage("After Load (loading): ", load.page); - } - } - } - // if (ops.length>1) console.log(ops.length); - - Promise.all(ops).then( () => { - self.reading = false; - if (self.pendingLoads.length>0) setImmediate(self._triggerLoad.bind(self)); - self._tryClose(); - }); - - function getNewPage() { - if (self.avBuffs.length>0) { - const p = self.avBuffs.shift(); - p.dirty = false; - p.pendingOps = 1; - p.size =0; - return p; - } else { - return { - dirty: false, - buff: new Uint8Array(self.pageSize), - pendingOps: 1, - size: 0 - }; - } - } - - } - - - _triggerWrite() { - const self = this; - if (self.writing) return; - - const pageIdxs = Object.keys(self.pages); - - const ops = []; - - for (let i=0; i { - page.writing = false; - return; - }, (err) => { - console.log("ERROR Writing: "+err); - self.error = err; - self._tryClose(); - })); - } - } - - if (self.writing) { - Promise.all(ops).then( () => { - self.writing = false; - setImmediate(self._triggerWrite.bind(self)); - self._tryClose(); - if (self.pendingLoads.length>0) setImmediate(self._triggerLoad.bind(self)); - }); - } - } - - _getDirtyPage() { - for (let p in this.pages) { - if (this.pages[p].dirty) return p; - } - return -1; - } - - async write(buff, pos) { - if (buff.byteLength == 0) return; - const self = this; - /* - if (buff.byteLength > self.pageSize*self.maxPagesLoaded*0.8) { - const cacheSize = Math.floor(buff.byteLength * 1.1); - this.maxPagesLoaded = Math.floor( cacheSize / self.pageSize)+1; - } - */ - if (typeof pos == "undefined") pos = self.pos; - self.pos = pos+buff.byteLength; - if (self.totalSize < pos + buff.byteLength) self.totalSize = pos + buff.byteLength; - if (self.pendingClose) - throw new Error("Writing a closing file"); - const firstPage = Math.floor(pos / self.pageSize); - const lastPage = Math.floor((pos + buff.byteLength -1) / self.pageSize); - - const pagePromises = []; - for (let i=firstPage; i<=lastPage; i++) pagePromises.push(self._loadPage(i)); - self._triggerLoad(); - - let p = firstPage; - let o = pos % self.pageSize; - let r = buff.byteLength; - while (r>0) { - await pagePromises[p-firstPage]; - const l = (o+r > self.pageSize) ? (self.pageSize -o) : r; - const srcView = buff.slice( buff.byteLength - r, buff.byteLength - r + l); - const dstView = new Uint8Array(self.pages[p].buff.buffer, o, l); - dstView.set(srcView); - self.pages[p].dirty = true; - self.pages[p].pendingOps --; - self.pages[p].size = Math.max(o+l, self.pages[p].size); - if (p>=self.totalPages) { - self.totalPages = p+1; - } - r = r-l; - p ++; - o = 0; - if (!self.writing) setImmediate(self._triggerWrite.bind(self)); - } - } - - async read(len, pos) { - const self = this; - let buff = new Uint8Array(len); - await self.readToBuffer(buff, 0, len, pos); - - return buff; - } - - async readToBuffer(buffDst, offset, len, pos) { - if (len == 0) { - return; - } - const self = this; - if (len > self.pageSize*self.maxPagesLoaded*0.8) { - const cacheSize = Math.floor(len * 1.1); - this.maxPagesLoaded = Math.floor( cacheSize / self.pageSize)+1; - } - if (typeof pos == "undefined") pos = self.pos; - self.pos = pos+len; - if (self.pendingClose) - throw new Error("Reading a closing file"); - const firstPage = Math.floor(pos / self.pageSize); - const lastPage = Math.floor((pos + len -1) / self.pageSize); - - const pagePromises = []; - for (let i=firstPage; i<=lastPage; i++) pagePromises.push(self._loadPage(i)); - - self._triggerLoad(); - - let p = firstPage; - let o = pos % self.pageSize; - // Remaining bytes to read - let r = pos + len > self.totalSize ? len - (pos + len - self.totalSize): len; - while (r>0) { - await pagePromises[p - firstPage]; - self.__statusPage("After Await (read): ", p); - - // bytes to copy from this page - const l = (o+r > self.pageSize) ? (self.pageSize -o) : r; - const srcView = new Uint8Array(self.pages[p].buff.buffer, self.pages[p].buff.byteOffset + o, l); - buffDst.set(srcView, offset+len-r); - self.pages[p].pendingOps --; - - self.__statusPage("After Op done: ", p); - - r = r-l; - p ++; - o = 0; - if (self.pendingLoads.length>0) setImmediate(self._triggerLoad.bind(self)); - } - - this.pos = pos + len; - - } - - - _tryClose() { - const self = this; - if (!self.pendingClose) return; - if (self.error) { - self.pendingCloseReject(self.error); - } - const p = self._getDirtyPage(); - if ((p>=0) || (self.writing) || (self.reading) || (self.pendingLoads.length>0)) return; - self.pendingClose(); - } - - close() { - const self = this; - if (self.pendingClose) - throw new Error("Closing the file twice"); - return new Promise((resolve, reject) => { - self.pendingClose = resolve; - self.pendingCloseReject = reject; - self._tryClose(); - }).then(()=> { - self.fd.close(); - }, (err) => { - self.fd.close(); - throw (err); - }); - } - - async discard() { - const self = this; - await self.close(); - await fs__default["default"].promises.unlink(this.fileName); - } - - async writeULE32(v, pos) { - const self = this; - const tmpBuff32 = new Uint8Array(4); - const tmpBuff32v = new DataView(tmpBuff32.buffer); - - tmpBuff32v.setUint32(0, v, true); - - await self.write(tmpBuff32, pos); - } - - async writeUBE32(v, pos) { - const self = this; - - const tmpBuff32 = new Uint8Array(4); - const tmpBuff32v = new DataView(tmpBuff32.buffer); - - tmpBuff32v.setUint32(0, v, false); - - await self.write(tmpBuff32, pos); - } - - - async writeULE64(v, pos) { - const self = this; - - const tmpBuff64 = new Uint8Array(8); - const tmpBuff64v = new DataView(tmpBuff64.buffer); - - tmpBuff64v.setUint32(0, v & 0xFFFFFFFF, true); - tmpBuff64v.setUint32(4, Math.floor(v / 0x100000000) , true); - - await self.write(tmpBuff64, pos); - } - - async readULE32(pos) { - const self = this; - const b = await self.read(4, pos); - - const view = new Uint32Array(b.buffer); - - return view[0]; - } - - async readUBE32(pos) { - const self = this; - const b = await self.read(4, pos); - - const view = new DataView(b.buffer); - - return view.getUint32(0, false); - } - - async readULE64(pos) { - const self = this; - const b = await self.read(8, pos); - - const view = new Uint32Array(b.buffer); - - return view[1] * 0x100000000 + view[0]; - } - - async readString(pos) { - const self = this; - - if (self.pendingClose) { - throw new Error("Reading a closing file"); - } - - let currentPosition = typeof pos == "undefined" ? self.pos : pos; - let currentPage = Math.floor(currentPosition / self.pageSize); - - let endOfStringFound = false; - let str = ""; - - while (!endOfStringFound) { - //Read page - let pagePromise = self._loadPage(currentPage); - self._triggerLoad(); - await pagePromise; - self.__statusPage("After Await (read): ", currentPage); - - let offsetOnPage = currentPosition % self.pageSize; - - const dataArray = new Uint8Array( - self.pages[currentPage].buff.buffer, - self.pages[currentPage].buff.byteOffset + offsetOnPage, - self.pageSize - offsetOnPage - ); - - let indexEndOfString = dataArray.findIndex(element => element === 0); - endOfStringFound = indexEndOfString !== -1; - - if (endOfStringFound) { - str += new TextDecoder().decode(dataArray.slice(0, indexEndOfString)); - self.pos = currentPage * this.pageSize + offsetOnPage + indexEndOfString + 1; - } else { - str += new TextDecoder().decode(dataArray); - self.pos = currentPage * this.pageSize + offsetOnPage + dataArray.length; - } - - self.pages[currentPage].pendingOps--; - self.__statusPage("After Op done: ", currentPage); - - currentPosition = self.pos; - currentPage++; - - if (self.pendingLoads.length > 0) setImmediate(self._triggerLoad.bind(self)); - } - - return str; - } -} - -function createNew$1(o) { - const initialSize = o.initialSize || 1<<20; - const fd = new MemFile(); - fd.o = o; - fd.o.data = new Uint8Array(initialSize); - fd.allocSize = initialSize; - fd.totalSize = 0; - fd.readOnly = false; - fd.pos = 0; - return fd; -} - -function readExisting$2(o) { - const fd = new MemFile(); - fd.o = o; - fd.allocSize = o.data.byteLength; - fd.totalSize = o.data.byteLength; - fd.readOnly = true; - fd.pos = 0; - return fd; -} - -function readWriteExisting$2(o) { - const fd = new MemFile(); - fd.o = o; - fd.allocSize = o.data.byteLength; - fd.totalSize = o.data.byteLength; - fd.readOnly = false; - fd.pos = 0; - return fd; -} - -const tmpBuff32$1 = new Uint8Array(4); -const tmpBuff32v$1 = new DataView(tmpBuff32$1.buffer); -const tmpBuff64$1 = new Uint8Array(8); -const tmpBuff64v$1 = new DataView(tmpBuff64$1.buffer); - -class MemFile { - - constructor() { - this.pageSize = 1 << 14; // for compatibility - } - - _resizeIfNeeded(newLen) { - if (newLen > this.allocSize) { - const newAllocSize = Math.max( - this.allocSize + (1 << 20), - Math.floor(this.allocSize * 1.1), - newLen - ); - const newData = new Uint8Array(newAllocSize); - newData.set(this.o.data); - this.o.data = newData; - this.allocSize = newAllocSize; - } - } - - async write(buff, pos) { - const self =this; - if (typeof pos == "undefined") pos = self.pos; - if (this.readOnly) throw new Error("Writing a read only file"); - - this._resizeIfNeeded(pos + buff.byteLength); - - this.o.data.set(buff.slice(), pos); - - if (pos + buff.byteLength > this.totalSize) this.totalSize = pos + buff.byteLength; - - this.pos = pos + buff.byteLength; - } - - async readToBuffer(buffDest, offset, len, pos) { - const self = this; - if (typeof pos == "undefined") pos = self.pos; - if (this.readOnly) { - if (pos + len > this.totalSize) throw new Error("Reading out of bounds"); - } - this._resizeIfNeeded(pos + len); - - const buffSrc = new Uint8Array(this.o.data.buffer, this.o.data.byteOffset + pos, len); - - buffDest.set(buffSrc, offset); - - this.pos = pos + len; - } - - async read(len, pos) { - const self = this; - - const buff = new Uint8Array(len); - await self.readToBuffer(buff, 0, len, pos); - - return buff; - } - - close() { - if (this.o.data.byteLength != this.totalSize) { - this.o.data = this.o.data.slice(0, this.totalSize); - } - } - - async discard() { - } - - - async writeULE32(v, pos) { - const self = this; - - tmpBuff32v$1.setUint32(0, v, true); - - await self.write(tmpBuff32$1, pos); - } - - async writeUBE32(v, pos) { - const self = this; - - tmpBuff32v$1.setUint32(0, v, false); - - await self.write(tmpBuff32$1, pos); - } - - - async writeULE64(v, pos) { - const self = this; - - tmpBuff64v$1.setUint32(0, v & 0xFFFFFFFF, true); - tmpBuff64v$1.setUint32(4, Math.floor(v / 0x100000000) , true); - - await self.write(tmpBuff64$1, pos); - } - - - async readULE32(pos) { - const self = this; - const b = await self.read(4, pos); - - const view = new Uint32Array(b.buffer); - - return view[0]; - } - - async readUBE32(pos) { - const self = this; - const b = await self.read(4, pos); - - const view = new DataView(b.buffer); - - return view.getUint32(0, false); - } - - async readULE64(pos) { - const self = this; - const b = await self.read(8, pos); - - const view = new Uint32Array(b.buffer); - - return view[1] * 0x100000000 + view[0]; - } - - async readString(pos) { - const self = this; - - let currentPosition = typeof pos == "undefined" ? self.pos : pos; - - if (currentPosition > this.totalSize) { - if (this.readOnly) { - throw new Error("Reading out of bounds"); - } - this._resizeIfNeeded(pos); - } - const dataArray = new Uint8Array( - self.o.data.buffer, - currentPosition, - this.totalSize - currentPosition - ); - - let indexEndOfString = dataArray.findIndex(element => element === 0); - let endOfStringFound = indexEndOfString !== -1; - - let str = ""; - if (endOfStringFound) { - str = new TextDecoder().decode(dataArray.slice(0, indexEndOfString)); - self.pos = currentPosition + indexEndOfString + 1; - } else { - self.pos = currentPosition; - } - return str; - } -} - -const PAGE_SIZE = 1<<22; - -function createNew(o) { - const initialSize = o.initialSize || 0; - const fd = new BigMemFile(); - fd.o = o; - const nPages = initialSize ? Math.floor((initialSize - 1) / PAGE_SIZE)+1 : 0; - fd.o.data = []; - for (let i=0; i0) { - const l = (o+r > PAGE_SIZE) ? (PAGE_SIZE -o) : r; - const srcView = buff.slice(buff.byteLength - r, buff.byteLength - r + l); - const dstView = new Uint8Array(self.o.data[p].buffer, o, l); - dstView.set(srcView); - r = r-l; - p ++; - o = 0; - } - - this.pos = pos + buff.byteLength; - } - - async readToBuffer(buffDst, offset, len, pos) { - const self = this; - if (typeof pos == "undefined") pos = self.pos; - if (this.readOnly) { - if (pos + len > this.totalSize) throw new Error("Reading out of bounds"); - } - this._resizeIfNeeded(pos + len); - - const firstPage = Math.floor(pos / PAGE_SIZE); - - let p = firstPage; - let o = pos % PAGE_SIZE; - // Remaining bytes to read - let r = len; - while (r>0) { - // bytes to copy from this page - const l = (o+r > PAGE_SIZE) ? (PAGE_SIZE -o) : r; - const srcView = new Uint8Array(self.o.data[p].buffer, o, l); - buffDst.set(srcView, offset+len-r); - r = r-l; - p ++; - o = 0; - } - - this.pos = pos + len; - } - - async read(len, pos) { - const self = this; - const buff = new Uint8Array(len); - - await self.readToBuffer(buff, 0, len, pos); - - return buff; - } - - close() { - } - - async discard() { - } - - - async writeULE32(v, pos) { - const self = this; - - tmpBuff32v.setUint32(0, v, true); - - await self.write(tmpBuff32, pos); - } - - async writeUBE32(v, pos) { - const self = this; - - tmpBuff32v.setUint32(0, v, false); - - await self.write(tmpBuff32, pos); - } - - - async writeULE64(v, pos) { - const self = this; - - tmpBuff64v.setUint32(0, v & 0xFFFFFFFF, true); - tmpBuff64v.setUint32(4, Math.floor(v / 0x100000000) , true); - - await self.write(tmpBuff64, pos); - } - - - async readULE32(pos) { - const self = this; - const b = await self.read(4, pos); - - const view = new Uint32Array(b.buffer); - - return view[0]; - } - - async readUBE32(pos) { - const self = this; - const b = await self.read(4, pos); - - const view = new DataView(b.buffer); - - return view.getUint32(0, false); - } - - async readULE64(pos) { - const self = this; - const b = await self.read(8, pos); - - const view = new Uint32Array(b.buffer); - - return view[1] * 0x100000000 + view[0]; - } - - async readString(pos) { - const self = this; - const fixedSize = 2048; - - let currentPosition = typeof pos == "undefined" ? self.pos : pos; - - if (currentPosition > this.totalSize) { - if (this.readOnly) { - throw new Error("Reading out of bounds"); - } - this._resizeIfNeeded(pos); - } - - let endOfStringFound = false; - let str = ""; - - while (!endOfStringFound) { - let currentPage = Math.floor(currentPosition / PAGE_SIZE); - let offsetOnPage = currentPosition % PAGE_SIZE; - - if (self.o.data[currentPage] === undefined) { - throw new Error("ERROR"); - } - - let readLength = Math.min(fixedSize, self.o.data[currentPage].length - offsetOnPage); - const dataArray = new Uint8Array(self.o.data[currentPage].buffer, offsetOnPage, readLength); - - let indexEndOfString = dataArray.findIndex(element => element === 0); - endOfStringFound = indexEndOfString !== -1; - - if (endOfStringFound) { - str += new TextDecoder().decode(dataArray.slice(0, indexEndOfString)); - self.pos = currentPage * PAGE_SIZE + offsetOnPage + indexEndOfString + 1; - } else { - str += new TextDecoder().decode(dataArray); - self.pos = currentPage * PAGE_SIZE + offsetOnPage + dataArray.length; - } - - currentPosition = self.pos; - } - return str; - } -} - -const DEFAULT_CACHE_SIZE = (1 << 16); -const DEFAULT_PAGE_SIZE = (1 << 13); - - -async function createOverride(o, b, c) { - if (typeof o === "string") { - o = { - type: "file", - fileName: o, - cacheSize: b || DEFAULT_CACHE_SIZE, - pageSize: c || DEFAULT_PAGE_SIZE - }; - } - if (o.type == "file") { - return await open(o.fileName, constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR, o.cacheSize, o.pageSize); - } else if (o.type == "mem") { - return createNew$1(o); - } else if (o.type == "bigMem") { - return createNew(o); - } else { - throw new Error("Invalid FastFile type: " + o.type); - } -} - -function createNoOverride(o, b, c) { - if (typeof o === "string") { - o = { - type: "file", - fileName: o, - cacheSize: b || DEFAULT_CACHE_SIZE, - pageSize: c || DEFAULT_PAGE_SIZE - }; - } - if (o.type == "file") { - return open(o.fileName, constants.O_TRUNC | constants.O_CREAT | constants.O_RDWR | constants.O_EXCL, o.cacheSize, o.pageSize); - } else if (o.type == "mem") { - return createNew$1(o); - } else if (o.type == "bigMem") { - return createNew(o); - } else { - throw new Error("Invalid FastFile type: " + o.type); - } -} - -async function readExisting(o, b, c) { - if (o instanceof Uint8Array) { - o = { - type: "mem", - data: o - }; - } - if (process.browser) { - if (typeof o === "string") { - const buff = await fetch(o).then(function (res) { - return res.arrayBuffer(); - }).then(function (ab) { - return new Uint8Array(ab); - }); - o = { - type: "mem", - data: buff - }; - } - } else { - if (typeof o === "string") { - o = { - type: "file", - fileName: o, - cacheSize: b || DEFAULT_CACHE_SIZE, - pageSize: c || DEFAULT_PAGE_SIZE - }; - } - } - if (o.type == "file") { - return await open(o.fileName, constants.O_RDONLY, o.cacheSize, o.pageSize); - } else if (o.type == "mem") { - return await readExisting$2(o); - } else if (o.type == "bigMem") { - return await readExisting$1(o); - } else { - throw new Error("Invalid FastFile type: " + o.type); - } -} - -function readWriteExisting(o, b, c) { - if (typeof o === "string") { - o = { - type: "file", - fileName: o, - cacheSize: b || DEFAULT_CACHE_SIZE, - pageSize: c || DEFAULT_PAGE_SIZE - }; - } - if (o.type == "file") { - return open(o.fileName, constants.O_CREAT | constants.O_RDWR, o.cacheSize, o.pageSize); - } else if (o.type == "mem") { - return readWriteExisting$2(o); - } else if (o.type == "bigMem") { - return readWriteExisting$1(o); - } else { - throw new Error("Invalid FastFile type: " + o.type); - } -} - -function readWriteExistingOrCreate(o, b, c) { - if (typeof o === "string") { - o = { - type: "file", - fileName: o, - cacheSize: b || DEFAULT_CACHE_SIZE, - pageSize: c || DEFAULT_PAGE_SIZE - }; - } - if (o.type == "file") { - return open(o.fileName, constants.O_CREAT | constants.O_RDWR | constants.O_EXCL, o.cacheSize); - } else if (o.type == "mem") { - return readWriteExisting$2(o); - } else if (o.type == "bigMem") { - return readWriteExisting$1(o); - } else { - throw new Error("Invalid FastFile type: " + o.type); - } -} - -exports.createNoOverride = createNoOverride; -exports.createOverride = createOverride; -exports.readExisting = readExisting; -exports.readWriteExisting = readWriteExisting; -exports.readWriteExistingOrCreate = readWriteExistingOrCreate; diff --git a/build/node/main.cjs b/build/node/main.cjs new file mode 100644 index 0000000..22b2962 --- /dev/null +++ b/build/node/main.cjs @@ -0,0 +1 @@ +Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;li[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require("fs");c=s(c,1);let l=require("constants");async function u(e,t,n,r){if(n||=4096*64,typeof t!=`number`&&[`w+`,`wx+`,`r`,`ax+`,`a+`].indexOf(t)<0)throw Error(`Invalid open option`);let i=await c.default.promises.open(e,t);return new d(i,await i.stat(),n,r,e)}var d=class{constructor(e,t,n,r,i){for(this.fileName=i,this.fd=e,this.pos=0,this.pageSize=r||256;this.pageSize{t.pendingLoads.push({page:e,resolve:n,reject:r})});return t.__statusPage(`After Load request: `,e),n}__statusPage(e,t){let n=[],r=this;if(!r.logHistory)return;n.push(`==`+e+` `+t);let i=``;for(let e=0;e `+t.history[e][n][r])}_triggerLoad(){let e=this;if(e.reading||e.pendingLoads.length==0)return;let t=Object.keys(e.pages),n=[];for(let r=0;r0&&(e.pages[e.pendingLoads[0].page]!==void 0||r>0||n.length>0);){let t=e.pendingLoads.shift();if(e.pages[t.page]!==void 0){e.pages[t.page].pendingOps++;let r=n.indexOf(t.page);r>=0&&n.splice(r,1),e.pages[t.page].loading?e.pages[t.page].loading.push(t):t.resolve(),e.__statusPage(`After Load (cached): `,t.page)}else{if(r)r--;else{let t=n.shift();e.__statusPage(`Before Unload: `,t),e.avBuffs.unshift(e.pages[t]),delete e.pages[t],e.__statusPage(`After Unload: `,t)}t.page>=e.totalPages?(e.pages[t.page]=a(),t.resolve(),e.__statusPage(`After Load (new): `,t.page)):(e.reading=!0,e.pages[t.page]=a(),e.pages[t.page].loading=[t],i.push(e.fd.read(e.pages[t.page].buff,0,e.pageSize,t.page*e.pageSize).then(n=>{e.pages[t.page].size=n.bytesRead;let r=e.pages[t.page].loading;delete e.pages[t.page].loading;for(let e=0;e{let r=e.pages[t.page],i=r&&r.loading?r.loading:[t];delete e.pages[t.page];for(let e=0;e{e.reading=!1,e.pendingLoads.length>0&&setImmediate(e._triggerLoad.bind(e)),e._tryClose()});function a(){if(e.avBuffs.length>0){let t=e.avBuffs.shift();return t.dirty=!1,t.pendingOps=1,t.size=0,t}else return{dirty:!1,buff:new Uint8Array(e.pageSize),pendingOps:1,size:0}}}_triggerWrite(){let e=this;if(e.writing)return;let t=Object.keys(e.pages),n=[];for(let r=0;r{i.writing=!1},t=>{console.log(`ERROR Writing: `+t),i.writing=!1,e.error=t,e._tryClose()})))}e.writing&&Promise.all(n).then(()=>{e.writing=!1,setImmediate(e._triggerWrite.bind(e)),e._tryClose(),e.pendingLoads.length>0&&setImmediate(e._triggerLoad.bind(e))})}_getDirtyPage(){for(let e in this.pages)if(this.pages[e].dirty)return e;return-1}_rangeHasCachedPages(e,t){let n=Math.floor(e/this.pageSize),r=Math.floor((e+t-1)/this.pageSize);for(let e of Object.keys(this.pages)){let t=+e;if(t>=n&&t<=r)return!0}return!1}async write(e,t){if(e.byteLength==0)return;let n=this;if(n.error)throw n.error;if(t===void 0&&(t=n.pos),n.pos=t+e.byteLength,n.totalSize=n.directWriteThreshold&&ArrayBuffer.isView(e)&&!n._rangeHasCachedPages(t,e.byteLength)){let r=0;for(;rn.totalPages&&(n.totalPages=i+1);return}let r=Math.floor(t/n.pageSize),i=Math.floor((t+e.byteLength-1)/n.pageSize),a=[];for(let e=r;e<=i;e++)a.push(n._loadPage(e));n._triggerLoad();let o=r,s=t%n.pageSize,c=e.byteLength;for(;c>0;){await a[o-r];let t=s+c>n.pageSize?n.pageSize-s:c,i=e.slice(e.byteLength-c,e.byteLength-c+t);new Uint8Array(n.pages[o].buff.buffer,s,t).set(i),n.pages[o].dirty=!0,n.pages[o].pendingOps--,n.pages[o].size=Math.max(s+t,n.pages[o].size),o>=n.totalPages&&(n.totalPages=o+1),c-=t,o++,s=0,n.writing||setImmediate(n._triggerWrite.bind(n))}}async read(e,t){let n=this,r=new Uint8Array(e);return await n.readToBuffer(r,0,e,t),r}_rangeHasDirtyPages(e,t){let n=Math.floor(e/this.pageSize),r=Math.floor((e+t-1)/this.pageSize);for(let e of Object.keys(this.pages)){let t=+e;if(t>=n&&t<=r){let e=this.pages[t];if(e.dirty||e.writing)return!0}}return!1}async readToBuffer(e,t,n,r){if(n==0)return;let i=this;if(i.error)throw i.error;if(r===void 0&&(r=i.pos),i.pos=r+n,i.pendingClose)throw Error(`Reading a closing file`);if(n>=i.directReadThreshold&&ArrayBuffer.isView(e)&&!i._rangeHasDirtyPages(r,n)){let a=r+n>i.totalSize?i.totalSize-r:n;a<0&&(a=0);let o=0;for(;oi.pageSize*i.maxPagesLoaded*.8){let e=Math.floor(n*1.1);this.maxPagesLoaded=Math.floor(e/i.pageSize)+1}let a=Math.floor(r/i.pageSize),o=Math.floor((r+n-1)/i.pageSize),s=[];for(let e=a;e<=o;e++)s.push(i._loadPage(e));i._triggerLoad();let c=a,l=r%i.pageSize,u=r+n>i.totalSize?n-(r+n-i.totalSize):n,d=0;for(;u>0;){await s[c-a],i.__statusPage(`After Await (read): `,c);let n=l+u>i.pageSize?i.pageSize-l:u,r=new Uint8Array(i.pages[c].buff.buffer,i.pages[c].buff.byteOffset+l,n);e.set(r,t+d),i.pages[c].pendingOps--,i.__statusPage(`After Op done: `,c),u-=n,d+=n,c++,l=0,i.pendingLoads.length>0&&setImmediate(i._triggerLoad.bind(i))}this.pos=r+n}_tryClose(){let e=this;if(e.pendingClose){if(e.error){e.pendingCloseReject(e.error);return}e._getDirtyPage()>=0||e.writing||e.reading||e.pendingLoads.length>0||e.pendingClose()}}close(){let e=this;return e.closePromise||=new Promise((t,n)=>{e.pendingClose=t,e.pendingCloseReject=n,e._tryClose()}).then(()=>{e.fd.close()},t=>{throw e.fd.close(),t}),e.closePromise}async discard(){await this.close(),await c.default.promises.unlink(this.fileName)}async writeULE32(e,t){let n=this,r=new Uint8Array(4);new DataView(r.buffer).setUint32(0,e,!0),await n.write(r,t)}async writeUBE32(e,t){let n=this,r=new Uint8Array(4);new DataView(r.buffer).setUint32(0,e,!1),await n.write(r,t)}async writeULE64(e,t){let n=this,r=new Uint8Array(8),i=new DataView(r.buffer);i.setUint32(0,e&4294967295,!0),i.setUint32(4,Math.floor(e/4294967296),!0),await n.write(r,t)}async readULE32(e){let t=await this.read(4,e);return new Uint32Array(t.buffer)[0]}async readUBE32(e){let t=await this.read(4,e);return new DataView(t.buffer).getUint32(0,!1)}async readULE64(e){let t=await this.read(8,e),n=new Uint32Array(t.buffer);return n[1]*4294967296+n[0]}async readString(e){let t=this;if(t.pendingClose)throw Error(`Reading a closing file`);let n=e===void 0?t.pos:e,r=Math.floor(n/t.pageSize),i=!1,a=``;for(;!i;){let e=t._loadPage(r);t._triggerLoad(),await e,t.__statusPage(`After Await (read): `,r);let o=n%t.pageSize,s=new Uint8Array(t.pages[r].buff.buffer,t.pages[r].buff.byteOffset+o,t.pageSize-o),c=s.findIndex(e=>e===0);i=c!==-1,i?(a+=new TextDecoder().decode(s.slice(0,c)),t.pos=r*this.pageSize+o+c+1):(a+=new TextDecoder().decode(s),t.pos=r*this.pageSize+o+s.length),t.pages[r].pendingOps--,t.__statusPage(`After Op done: `,r),n=t.pos,r++,t.pendingLoads.length>0&&setImmediate(t._triggerLoad.bind(t))}return a}};function f(e){let t=e.initialSize||1<<20,n=new y;return n.o=e,n.o.data=new Uint8Array(t),n.allocSize=t,n.totalSize=0,n.readOnly=!1,n.pos=0,n}function p(e){let t=new y;return t.o=e,t.allocSize=e.data.byteLength,t.totalSize=e.data.byteLength,t.readOnly=!0,t.pos=0,t}function m(e){let t=new y;return t.o=e,t.allocSize=e.data.byteLength,t.totalSize=e.data.byteLength,t.readOnly=!1,t.pos=0,t}var h=new Uint8Array(4),g=new DataView(h.buffer),_=new Uint8Array(8),v=new DataView(_.buffer),y=class{constructor(){this.pageSize=16384}_resizeIfNeeded(e){if(e>this.allocSize){let t=Math.max(this.allocSize+(1<<20),Math.floor(this.allocSize*1.1),e),n=new Uint8Array(t);n.set(this.o.data),this.o.data=n,this.allocSize=t}}async write(e,t){let n=this;if(t===void 0&&(t=n.pos),this.readOnly)throw Error(`Writing a read only file`);this._resizeIfNeeded(t+e.byteLength),this.o.data.set(e.slice(),t),t+e.byteLength>this.totalSize&&(this.totalSize=t+e.byteLength),this.pos=t+e.byteLength}async readToBuffer(e,t,n,r){let i=this;if(r===void 0&&(r=i.pos),this.readOnly&&r+n>this.totalSize)throw Error(`Reading out of bounds`);this._resizeIfNeeded(r+n);let a=new Uint8Array(this.o.data.buffer,this.o.data.byteOffset+r,n);e.set(a,t),this.pos=r+n}async read(e,t){let n=this,r=new Uint8Array(e);return await n.readToBuffer(r,0,e,t),r}close(){this.o.data.byteLength!=this.totalSize&&(this.o.data=this.o.data.slice(0,this.totalSize))}async discard(){}async writeULE32(e,t){let n=this;g.setUint32(0,e,!0),await n.write(h,t)}async writeUBE32(e,t){let n=this;g.setUint32(0,e,!1),await n.write(h,t)}async writeULE64(e,t){let n=this;v.setUint32(0,e&4294967295,!0),v.setUint32(4,Math.floor(e/4294967296),!0),await n.write(_,t)}async readULE32(e){let t=await this.read(4,e);return new Uint32Array(t.buffer)[0]}async readUBE32(e){let t=await this.read(4,e);return new DataView(t.buffer).getUint32(0,!1)}async readULE64(e){let t=await this.read(8,e),n=new Uint32Array(t.buffer);return n[1]*4294967296+n[0]}async readString(e){let t=this,n=e===void 0?t.pos:e;if(n>=this.totalSize){if(this.readOnly)throw Error(`Reading out of bounds`);return``}let r=new Uint8Array(t.o.data.buffer,n,this.totalSize-n),i=r.findIndex(e=>e===0),a=i!==-1,o=``;return a?(o=new TextDecoder().decode(r.slice(0,i)),t.pos=n+i+1):t.pos=n,o}},b=1<<22;function x(e){let t=e.initialSize||0,n=new D;n.o=e;let r=t?Math.floor((t-1)/b)+1:0;n.o.data=[];for(let e=0;e0;){let t=i+a>b?b-i:a,o=e.slice(e.byteLength-a,e.byteLength-a+t);new Uint8Array(n.o.data[r].buffer,i,t).set(o),a-=t,r++,i=0}this.pos=t+e.byteLength}async readToBuffer(e,t,n,r){let i=this;if(r===void 0&&(r=i.pos),this.readOnly&&r+n>this.totalSize)throw Error(`Reading out of bounds`);this._resizeIfNeeded(r+n);let a=Math.floor(r/b),o=r%b,s=n;for(;s>0;){let r=o+s>b?b-o:s,c=new Uint8Array(i.o.data[a].buffer,o,r);e.set(c,t+n-s),s-=r,a++,o=0}this.pos=r+n}async read(e,t){let n=this,r=new Uint8Array(e);return await n.readToBuffer(r,0,e,t),r}close(){}async discard(){}async writeULE32(e,t){let n=this;w.setUint32(0,e,!0),await n.write(C,t)}async writeUBE32(e,t){let n=this;w.setUint32(0,e,!1),await n.write(C,t)}async writeULE64(e,t){let n=this;E.setUint32(0,e&4294967295,!0),E.setUint32(4,Math.floor(e/4294967296),!0),await n.write(T,t)}async readULE32(e){let t=await this.read(4,e);return new Uint32Array(t.buffer)[0]}async readUBE32(e){let t=await this.read(4,e);return new DataView(t.buffer).getUint32(0,!1)}async readULE64(e){let t=await this.read(8,e),n=new Uint32Array(t.buffer);return n[1]*4294967296+n[0]}async readString(e){let t=this,n=e===void 0?t.pos:e;if(n>this.totalSize){if(this.readOnly)throw Error(`Reading out of bounds`);this._resizeIfNeeded(e)}let r=!1,i=``;for(;!r;){let e=Math.floor(n/b),a=n%b;if(t.o.data[e]===void 0)throw Error(`ERROR`);let o=Math.min(2048,t.o.data[e].length-a);if(o<=0)return t.pos=n,i;let s=new Uint8Array(t.o.data[e].buffer,a,o),c=s.findIndex(e=>e===0);r=c!==-1,r?(i+=new TextDecoder().decode(s.slice(0,c)),t.pos=e*b+a+c+1):(i+=new TextDecoder().decode(s),t.pos=e*b+a+s.length),n=t.pos}return i}},te=1<<20,O=8192,k=class{constructor(e,t,n,r){this.readRangeInto=e,this.totalSize=t,this.pos=0,this.pageSize=r||O,this.maxPagesLoaded=Math.floor((n||te)/this.pageSize)+1,this.pages=new Map,this.readOnly=!0}_pageLen(e){let t=e*this.pageSize;return Math.min(t+this.pageSize,this.totalSize)-t}_loadPage(e){let t=this,n=t.pages.get(e);if(n)return t.pages.delete(e),t.pages.set(e,n),n.promise;let r=new Uint8Array(t._pageLen(e));return n={buff:null,promise:null},n.promise=t.readRangeInto(r,0,e*t.pageSize,r.byteLength).then(function(){return n.buff=r,r},function(n){throw t.pages.delete(e),n}),t.pages.set(e,n),t._trimCache(),n.promise}_trimCache(){let e=this;if(!(e.pages.size<=e.maxPagesLoaded))for(let t of e.pages){if(e.pages.size<=e.maxPagesLoaded)return;t[1].buff&&e.pages.delete(t[0])}}async readToBuffer(e,t,n,r){let i=this;if(n===0)return;if(i.pendingClose)throw Error(`Reading a closing file`);if(r===void 0&&(r=i.pos),r+n>i.totalSize)throw Error(`Reading out of bounds`);if(i.pos=r+n,n>=i.pageSize){await i.readRangeInto(e,t,r,n);return}let a=Math.floor(r/i.pageSize),o=Math.floor((r+n-1)/i.pageSize),s=r%i.pageSize,c=0;for(let r=a;r<=o;r++){let a=await i._loadPage(r),o=Math.min(n-c,i.pageSize-s);e.set(a.subarray(s,s+o),t+c),c+=o,s=0}}async read(e,t){let n=new Uint8Array(e);return await this.readToBuffer(n,0,e,t),n}async readULE32(e){let t=await this.read(4,e);return new Uint32Array(t.buffer)[0]}async readUBE32(e){let t=await this.read(4,e);return new DataView(t.buffer).getUint32(0,!1)}async readULE64(e){let t=await this.read(8,e),n=new Uint32Array(t.buffer);return n[1]*4294967296+n[0]}async readString(e){let t=this;if(t.pendingClose)throw Error(`Reading a closing file`);let n=e===void 0?t.pos:e,r=[];for(;n=0)return r.push(i.subarray(0,a)),t.pos=n+a+1,A(r);r.push(i),n+=e}return t.pos=n,A(r)}async write(){throw Error(`Writing a read only file`)}async writeULE32(){throw Error(`Writing a read only file`)}async writeUBE32(){throw Error(`Writing a read only file`)}async writeULE64(){throw Error(`Writing a read only file`)}async close(){this.pendingClose||(this.pendingClose=!0,this.pages.clear())}async discard(){await this.close()}};function A(e){let t=0;for(let n=0;n{let r=indexedDB.open(e,1);r.onupgradeneeded=()=>{let e=r.result;e.createObjectStore(`files`),e.createObjectStore(`blocks`)},r.onsuccess=()=>t(r.result),r.onerror=()=>n(r.error),r.onblocked=()=>n(Error(`IndexedDB open blocked`))});return P.set(e,t),t.catch(()=>P.delete(e)),t}function I(e){return new Promise((t,n)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>n(e.error)})}function L(e){return new Promise((t,n)=>{e.oncomplete=()=>t(),e.onerror=()=>n(e.error),e.onabort=()=>n(e.error||Error(`IndexedDB transaction aborted`))})}function R(e,t,n){return IDBKeyRange.bound([e,t],[e,n])}async function z(e,t){let n=e.transaction([`files`,`blocks`],`readwrite`);n.objectStore(`files`).delete(t),n.objectStore(`blocks`).delete(R(t,0,1/0)),await L(n)}async function B(e,t,n,r,i,a){let o=e.transaction([`files`,`blocks`],`readwrite`),s=o.objectStore(`files`),c=await I(s.get(t)),l=0;c&&c.validator===n&&c.totalSize===r&&c.blockSize===i?l=c.bytes:c&&o.objectStore(`blocks`).delete(R(t,0,1/0)),s.put({validator:n,totalSize:r,blockSize:i,bytes:l,lastUsed:Date.now()},t),await L(o);let u=e.transaction(`files`,`readonly`),d=u.objectStore(`files`),[f,p]=await Promise.all([I(d.getAllKeys()),I(d.getAll())]);await L(u);let m=p.reduce((e,t)=>e+t.bytes,0);if(m<=a)return;let h=f.map((e,t)=>({key:e,meta:p[t]})).filter(e=>e.key!==t).sort((e,t)=>e.meta.lastUsed-t.meta.lastUsed);for(let t of h){if(m<=a)break;await z(e,t.key),m-=t.meta.bytes}}async function ne(e,t){let{fileKey:n,validator:r,totalSize:i}=t,a=typeof t.options==`object`&&t.options||{},o=a.blockSize||j,s=a.maxBytes||M,c=a.dbName||N;if(typeof indexedDB>`u`||!r)return e;let l;try{l=await F(c),await B(l,n,r,i,o,s)}catch{return e}let u=!1;async function d(e,t){let r=l.transaction(`blocks`,`readonly`),i=r.objectStore(`blocks`),a=R(n,e,t),[o,s]=await Promise.all([I(i.getAllKeys(a)),I(i.getAll(a))]);await L(r);let c=new Map;for(let e=0;ee+t.data.byteLength,0),a.lastUsed=Date.now(),i.put(a,n)),await L(t)}catch{u=!0}}let p=e=>Math.min(o,i-e*o),m=new Map;function h(e){let t,n,r=new Promise((e,r)=>{t=e,n=r});return r.catch(()=>{}),m.set(e,r),{resolve:t,reject:n,promise:r}}return async function(t,n,r,i){if(i===0)return;let a=Math.floor(r/o),s=Math.floor((r+i-1)/o),c=await d(a,s),l=[],u=(e,a)=>{let s=a*o,c=Math.max(r,s),l=Math.min(r+i,s+p(a));t.set(e.subarray(c-s,l-s),n+(c-r))},g=a;for(;g<=s;){let a=g*o,d=a+p(g),f=c.get(g);if(f){u(f,g),g++;continue}let _=m.get(g);if(_){u(await _,g),g++;continue}if(a>=r&&d<=r+i){let u=g;for(;u+1<=s&&!c.get(u+1)&&!m.get(u+1)&&(u+1)*o+p(u+1)<=r+i;)u++;let d=a,f=u*o+p(u),_=[];for(let e=g;e<=u;e++)_.push(h(e));try{await e(t,n+(d-r),d,f-d)}catch(e){for(let t=g;t<=u;t++)_[t-g].reject(e),m.delete(t);throw e}for(let e=g;e<=u;e++){let i=e*o,a=t.slice(n+(i-r),n+(i-r)+p(e));_[e-g].resolve(a),l.push({index:e,data:a})}g=u+1}else{let t=h(g),n=new Uint8Array(p(g));try{await e(n,0,a,n.length)}catch(e){throw t.reject(e),m.delete(g),e}t.resolve(n),u(n,g),l.push({index:g,data:n}),g++}}await f(l);for(let e of l)m.delete(e.index)}}async function re(e){let t=(typeof e.options==`object`&&e.options||{}).dbName||N;if(typeof indexedDB>`u`)return null;try{let n=(await F(t)).transaction(`files`,`readonly`),r=await I(n.objectStore(`files`).get(e.fileKey));return await L(n),r?{validator:r.validator,totalSize:r.totalSize,blockSize:r.blockSize,bytes:r.bytes}:null}catch{return null}}async function ie(e){let{fileKey:t,validator:n,totalSize:r,data:i}=e,a=typeof e.options==`object`&&e.options||{},o=a.blockSize||j,s=a.maxBytes||M,c=a.dbName||N;if(typeof indexedDB>`u`||!n)return!1;try{let e=await F(c);await B(e,t,n,r,o,s);let a=Math.ceil(r/o);for(let n=0;ns.byteLength)throw Error(e+`: read past the end of the buffered body`);n.set(s.subarray(a,a+o),r)},o=Math.min(r.pageSize||V,V);return r.cache&&(a=await ne(a,{fileKey:e,validator:t,totalSize:n,options:r.cache})),new k(a,n,r.cacheSize,o)}async function W(e){let t=await fetch(e);if(!t.ok)throw Error(`HTTP `+t.status+` fetching `+e);return p({type:`mem`,data:new Uint8Array(await t.arrayBuffer())})}function G(e){let t=e.headers.get(`etag`);return t&&t.indexOf(`W/`)!==0?t:e.headers.get(`last-modified`)||null}var ae=4,K=0,q=[];async function oe(e){for(;K>=ae;)await new Promise(e=>q.push(e));K++;try{return await e()}finally{K--;let e=q.shift();e&&e()}}var J={retries:3,backoffMs:300,stallTimeoutMs:1e4},se=e=>new Promise(t=>setTimeout(t,e));async function ce(e,t,n,r,i,a){return oe(async()=>{for(let o=0;;o++)try{return await le(e,t,n,r,i,a)}catch(e){if(e&&(e.degradeToFull||e.permanent)||o>=J.retries)throw e;await se(J.backoffMs*(1<{s&&(c&&clearTimeout(c),c=setTimeout(()=>s.abort(Error(e+`: no data for `+J.stallTimeoutMs+`ms`)),J.stallTimeoutMs))},u=()=>{c&&clearTimeout(c),c=null};l();let d;try{d=await fetch(e,s?{headers:o,signal:s.signal}:{headers:o})}catch(e){throw u(),e}if(l(),d.status===200){let n=G(d);if(!t||n&&n===t){u();let t=Error(e+`: origin ignored Range; degrading to full buffering`);throw t.degradeToFull=!0,t.fullBodyPromise=d.arrayBuffer().then(e=>new Uint8Array(e)),t}u(),await Y(d);let r=Error(e+`: file changed (or server stopped honoring Range) while reading`);throw r.permanent=!0,r}if(d.status!==206){u(),await Y(d);let t=Error(`HTTP `+d.status+` reading range `+i+`+`+a+` of `+e);throw d.status<500&&d.status!==429&&(t.permanent=!0),t}let f=d.headers.get(`content-range`),p=f?/bytes\s+(\d+)-(\d+)\//.exec(f):null;if(p&&parseInt(p[1])!==i)throw await Y(d),Error(e+`: server returned range starting at `+p[1]+`, requested `+i);let m=0;try{if(d.body&&typeof d.body.getReader==`function`){let t=d.body.getReader();for(;;){let i=await t.read();if(l(),i.done)break;if(m+i.value.byteLength>a)throw t.cancel().catch(function(){}),Error(e+`: range response longer than requested`);n.set(i.value,r+m),m+=i.value.byteLength}}else{let t=new Uint8Array(await d.arrayBuffer());if(t.byteLength>a)throw Error(e+`: range response longer than requested`);n.set(t,r),m=t.byteLength}}finally{u()}if(m!==a)throw Error(e+`: short range response (`+m+`/`+a+` bytes at `+i+`)`)}async function Y(e){try{e.body&&typeof e.body.cancel==`function`?await e.body.cancel():await e.arrayBuffer()}catch{}}var X=1<<20;function ue(e){let t=e.blob,n=async function(e,n,r,i){let a=await t.slice(r,r+i).arrayBuffer();if(a.byteLength!==i)throw Error(`short blob read (`+a.byteLength+`/`+i+` bytes at `+r+`)`);e.set(new Uint8Array(a),n)},r=Math.min(e.pageSize||X,X);return new k(n,t.size,e.cacheSize,r)}var Z=65536,Q=8192,de=typeof process<`u`&&process.versions!=null&&process.versions.node!=null;function $(e,t,n){if(typeof e!=`object`||!e||e.type!==`file`&&e.type!==`http`&&e.type!==`blob`)return e;let r=e.cacheSize===void 0&&t!==void 0,i=e.pageSize===void 0&&n!==void 0;if(!r&&!i)return e;let a=Object.assign({},e);return r&&(a.cacheSize=t),i&&(a.pageSize=n),a}async function fe(e,t,n){if(typeof e==`string`&&(e={type:`file`,fileName:e,cacheSize:t||Z,pageSize:n||Q}),e=$(e,t,n),e.type==`file`)return await u(e.fileName,l.O_TRUNC|l.O_CREAT|l.O_RDWR,e.cacheSize,e.pageSize);if(e.type==`mem`)return f(e);if(e.type==`bigMem`)return x(e);throw Error(`Invalid FastFile type: `+e.type)}function pe(e,t,n){if(typeof e==`string`&&(e={type:`file`,fileName:e,cacheSize:t||Z,pageSize:n||Q}),e=$(e,t,n),e.type==`file`)return u(e.fileName,l.O_TRUNC|l.O_CREAT|l.O_RDWR|l.O_EXCL,e.cacheSize,e.pageSize);if(e.type==`mem`)return f(e);if(e.type==`bigMem`)return x(e);throw Error(`Invalid FastFile type: `+e.type)}async function me(e,t,n){if(e instanceof Uint8Array&&(e={type:`mem`,data:e}),typeof Blob<`u`&&e instanceof Blob&&(e={type:`blob`,blob:e,cacheSize:t,pageSize:n}),typeof e==`string`&&(e=!de||/^https?:\/\//i.test(e)?{type:`http`,url:e,cacheSize:t,pageSize:n}:{type:`file`,fileName:e,cacheSize:t||Z,pageSize:n||Q}),e=$(e,t,n),e.type==`file`)return await u(e.fileName,l.O_RDONLY,e.cacheSize,e.pageSize);if(e.type==`mem`)return await p(e);if(e.type==`bigMem`)return await ee(e);if(e.type==`http`)return await H(e);if(e.type==`blob`)return ue(e);throw Error(`Invalid FastFile type: `+e.type)}function he(e,t,n){if(typeof e==`string`&&(e={type:`file`,fileName:e,cacheSize:t||Z,pageSize:n||Q}),e=$(e,t,n),e.type==`file`)return u(e.fileName,l.O_CREAT|l.O_RDWR,e.cacheSize,e.pageSize);if(e.type==`mem`)return m(e);if(e.type==`bigMem`)return S(e);throw Error(`Invalid FastFile type: `+e.type)}function ge(e,t,n){if(typeof e==`string`&&(e={type:`file`,fileName:e,cacheSize:t||Z,pageSize:n||Q}),e=$(e,t,n),e.type==`file`)return u(e.fileName,l.O_CREAT|l.O_RDWR|l.O_EXCL,e.cacheSize);if(e.type==`mem`)return m(e);if(e.type==`bigMem`)return S(e);throw Error(`Invalid FastFile type: `+e.type)}exports.createNoOverride=pe,exports.createOverride=fe,exports.readExisting=me,exports.readWriteExisting=he,exports.readWriteExistingOrCreate=ge; \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..db65781 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,33 @@ +import js from "@eslint/js"; +import globals from "globals"; + +export default [ + { ignores: ["build/**", "node_modules/**"] }, + js.configs.recommended, + { + languageOptions: { + ecmaVersion: 2022, + sourceType: "module", + globals: { + ...globals.node, + ...globals.browser, + }, + }, + rules: { + "no-unused-vars": ["error", { caughtErrors: "none" }], + "indent": ["error", 4], + "linebreak-style": ["warn", "unix"], + "quotes": ["error", "double"], + "semi": ["error", "always"], + }, + }, + { + // Vitest globals for test files (describe, it, beforeEach, etc.) + files: ["test/**/*.js"], + languageOptions: { + globals: { + ...globals.mocha, + }, + }, + }, +]; diff --git a/package-lock.json b/package-lock.json index a475cd1..b63021f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,528 +9,941 @@ "version": "0.0.20", "license": "GPL-3.0", "devDependencies": { - "chai": "^4.3.6", - "chai-as-promised": "^7.1.1", - "eslint": "^8.17.0", - "ffjavascript": "^0.3.0", - "mocha": "^10.0.0", - "rollup": "^2.20.0" + "@eslint/js": "^10.0.1", + "@vitest/browser": "^4.1.11", + "@vitest/browser-playwright": "^4.1.11", + "eslint": "^10.1.0", + "ffjavascript": "git+https://github.com/iden3/ffjavascript.git#cc88563e1686646616ad0fcfafa6cb09c879dd25", + "globals": "^17.4.0", + "playwright": "^1.59.0", + "vite": "^8.0.3", + "vitest": "^4.1.11" + } + }, + "node_modules/@blazediff/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@blazediff/core/-/core-1.9.1.tgz", + "integrity": "sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" } }, - "node_modules/@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", "dev": true, - "engines": { - "node": ">=0.10.0" + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.2.0.tgz", - "integrity": "sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@eslint-community/regexpp": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", - "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" + "@types/json-schema": "^7.0.15" }, "engines": { - "node": ">=10.10.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12.22" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", - "dev": true + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" }, "engines": { - "node": ">= 8" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { - "node": ">= 8" + "node": ">=18.18.0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": ">= 8" + "node": ">=18.18.0" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } }, - "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, + "license": "Apache-2.0", "engines": { - "node": ">=0.4.0" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "@tybys/wasm-util": "^0.10.3" }, "funding": { "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "node_modules/@oxc-project/types": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", + "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", "dev": true, - "engines": { - "node": ">=6" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", + "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==", + "cpu": [ + "arm64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz", + "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz", + "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz", + "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "*" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz", + "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==", + "cpu": [ + "arm" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz", + "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz", + "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz", + "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz", + "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==", + "cpu": [ + "s390x" + ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz", + "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/chai": { - "version": "4.3.10", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", - "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz", + "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==", + "cpu": [ + "x64" + ], "dev": true, - "peer": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.0.8" - }, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=4" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/chai-as-promised": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", - "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz", + "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "check-error": "^1.0.2" - }, - "peerDependencies": { - "chai": ">= 2.1.2 < 5" + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz", + "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==", + "cpu": [ + "wasm32" + ], "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", + "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "get-func-name": "^2.0.2" - }, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "*" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz", + "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==", + "cpu": [ + "x64" + ], "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "tslib": "^2.4.0" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/browser": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.11.tgz", + "integrity": "sha512-bwMovvAeuTFOK5kIFevw4VEf+1gVEICv4SYK4k3knJOxl6b1zEWud8mYKD73e1B0odAn174h1MofURy2TPWf3w==", + "dev": true, + "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "@blazediff/core": "1.9.1", + "@vitest/mocker": "4.1.11", + "@vitest/utils": "4.1.11", + "magic-string": "^0.30.21", + "pngjs": "^7.0.0", + "sirv": "^3.0.2", + "tinyrainbow": "^3.1.0", + "ws": "^8.19.0" }, - "engines": { - "node": ">= 8" + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "4.1.11" } }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/@vitest/browser-playwright": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.11.tgz", + "integrity": "sha512-riLBxPqwnJ0lWs2DN2WeUfYeKLoAjbP2Xx8cLQdSddzMi20sksIa6K2mPz79DyMZKKVKH2ksOC2yJvtNcZg8cg==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "@vitest/browser": "4.1.11", + "@vitest/mocker": "4.1.11", + "tinyrainbow": "^3.1.0" }, - "engines": { - "node": ">=6.0" + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "playwright": "*", + "vitest": "4.1.11" }, "peerDependenciesMeta": { - "supports-color": { + "playwright": { + "optional": false + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.11", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { "optional": true } } }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "node_modules/@vitest/pretty-format": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.11", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.11", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, "engines": { - "node": ">=10" + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, + "license": "MIT", "dependencies": { - "type-detect": "^4.0.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=6" + "node": "20 || >=22" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, "engines": { - "node": ">=0.3.1" + "node": ">= 8" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, + "license": "MIT", "dependencies": { - "esutils": "^2.0.2" + "ms": "^2.1.3" }, "engines": { - "node": ">=6.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=6" + "node": ">=8" } }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -544,84 +957,91 @@ } }, "node_modules/eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz", + "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", "dev": true, - "peer": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^10.2.4", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -632,6 +1052,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -640,27 +1061,29 @@ } }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.2.tgz", - "integrity": "sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -673,6 +1096,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -685,79 +1109,87 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } + "license": "MIT" }, "node_modules/ffjavascript": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.3.0.tgz", - "integrity": "sha512-l7sR5kmU3gRwDy8g0Z2tYBXy5ttmafRPFOqY7S6af5cq51JqJWt5eQ/lSR/rs2wQNbDYaYlQr5O+OSUf/oMLoQ==", + "version": "0.3.1", + "resolved": "git+https://github.com/iden3/ffjavascript.git#cc88563e1686646616ad0fcfafa6cb09c879dd25", + "integrity": "sha512-CLLd+R83k5OOIj14sPLlxlLX8YwuOFUn51Rt3XwQIegHbQyga5fMZ8DO/uCx/jVZzm1NEqSaevmQO7VNW8KVtA==", "dev": true, + "license": "GPL-3.0", "dependencies": { + "web-worker": "1.5.0" + }, + "optionalDependencies": { "wasmbuilder": "0.0.16", - "wasmcurves": "0.2.2", - "web-worker": "1.2.0" + "wasmcurves": "git+https://github.com/iden3/wasmcurves.git#13d4acc125b794a4e28a5dc1ea99f015d863ec98" } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "flat-cache": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=16.0.0" } }, "node_modules/find-up": { @@ -776,39 +1208,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", - "dev": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.2", @@ -824,273 +1243,383 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/globals": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", + "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", "dev": true, + "license": "MIT", "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">= 4" } }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, + "license": "MIT", "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=0.8.19" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, "engines": { - "node": ">= 6" + "node": ">=0.10.0" } }, - "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "dependencies": { - "type-fest": "^0.20.2" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, - "bin": { - "he": "bin/he" - } + "license": "MIT" }, - "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, - "engines": { - "node": ">= 4" - } + "license": "MIT" }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "json-buffer": "3.0.1" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, "engines": { - "node": ">=0.8.19" + "node": ">= 0.8.0" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, + "license": "MPL-2.0", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "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" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=8" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=8" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.12.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], "dev": true, - "dependencies": { - "argparse": "^2.0.1" + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/js-yaml/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.8.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/locate-path": { @@ -1108,42 +1637,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, + "license": "MIT", "dependencies": { - "get-func-name": "^2.0.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1151,99 +1660,35 @@ "node": "*" } }, - "node_modules/mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", - "dev": true, - "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" - } - }, - "node_modules/mocha/node_modules/brace-expansion": { + "node_modules/mrmime": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, + "license": "MIT", "engines": { "node": ">=10" } }, - "node_modules/mocha/node_modules/ms": { + "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -1254,39 +1699,37 @@ "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, - "engines": { - "node": ">=0.10.0" - } + "license": "MIT" }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", "dev": true, - "dependencies": { - "wrappy": "1" + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" } }, "node_modules/optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "dependencies": { - "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -1331,18 +1774,6 @@ "node": ">=6" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -1352,212 +1783,166 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/pathval": { + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, - "engines": { - "node": "*" - } + "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, "engines": { - "node": ">= 0.8.0" + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=14.19.0" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "dev": true, "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/feross" + "type": "opencollective", + "url": "https://opencollective.com/postcss/" }, { - "type": "patreon", - "url": "https://www.patreon.com/feross" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" }, { - "type": "consulting", - "url": "https://feross.org/support" + "type": "github", + "url": "https://github.com/sponsors/ai" } - ] - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, + ], + "license": "MIT", "dependencies": { - "picomatch": "^2.2.1" + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">=8.10.0" + "node": "^10 || ^12 || >=14" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 0.8.0" } }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "node_modules/rolldown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", + "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==", "dev": true, + "license": "MIT", "dependencies": { - "glob": "^7.1.3" + "@oxc-project/types": "=0.137.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=10.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" + "@rolldown/binding-android-arm64": "1.1.3", + "@rolldown/binding-darwin-arm64": "1.1.3", + "@rolldown/binding-darwin-x64": "1.1.3", + "@rolldown/binding-freebsd-x64": "1.1.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", + "@rolldown/binding-linux-arm64-gnu": "1.1.3", + "@rolldown/binding-linux-arm64-musl": "1.1.3", + "@rolldown/binding-linux-ppc64-gnu": "1.1.3", + "@rolldown/binding-linux-s390x-gnu": "1.1.3", + "@rolldown/binding-linux-x64-gnu": "1.1.3", + "@rolldown/binding-linux-x64-musl": "1.1.3", + "@rolldown/binding-openharmony-arm64": "1.1.3", + "@rolldown/binding-wasm32-wasi": "1.1.3", + "@rolldown/binding-win32-arm64-msvc": "1.1.3", + "@rolldown/binding-win32-x64-msvc": "1.1.3" } }, "node_modules/shebang-command": { @@ -1565,6 +1950,7 @@ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -1577,83 +1963,143 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", "dev": true, + "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, + "license": "BSD-3-Clause", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { - "node": ">=8" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, - "dependencies": { - "is-number": "^7.0.0" + "license": "MIT", + "engines": { + "node": ">=12.0.0" }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8.0" + "node": ">=14.0.0" } }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -1661,461 +2107,798 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "engines": { - "node": ">=4" + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/vite": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.0.tgz", + "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "~1.1.2", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, "engines": { - "node": ">=10" + "node": "^20.19.0 || >=22.12.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "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 + } } }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "dependencies": { - "punycode": "^2.1.0" + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/vitest": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "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.11", + "@vitest/browser-preview": "4.1.11", + "@vitest/browser-webdriverio": "4.1.11", + "@vitest/coverage-istanbul": "4.1.11", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", + "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 + }, + "vite": { + "optional": false + } } }, "node_modules/wasmbuilder": { "version": "0.0.16", "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.16.tgz", "integrity": "sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==", - "dev": true + "dev": true, + "license": "GPL-3.0", + "optional": true }, "node_modules/wasmcurves": { "version": "0.2.2", - "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.2.tgz", - "integrity": "sha512-JRY908NkmKjFl4ytnTu5ED6AwPD+8VJ9oc94kdq7h5bIwbj0L4TDJ69mG+2aLs2SoCmGfqIesMWTEJjtYsoQXQ==", + "resolved": "git+https://github.com/iden3/wasmcurves.git#13d4acc125b794a4e28a5dc1ea99f015d863ec98", + "integrity": "sha512-HYvEBTL7NaGyLvhCT3as3/SIFWloyB+4iUy4wt3OSwTgK7jQXNRiXcHXsUwSCtLDPOnU9qX/gI+EKb5/UFyw4g==", "dev": true, + "license": "GPL-3.0", + "optional": true, "dependencies": { "wasmbuilder": "0.0.16" } }, "node_modules/web-worker": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz", - "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==", - "dev": true + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, "engines": { - "node": ">=10" + "node": ">= 8" } }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, + "license": "MIT", "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } } }, "dependencies": { - "@aashutoshrathi/word-wrap": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", - "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "@blazediff/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@blazediff/core/-/core-1.9.1.tgz", + "integrity": "sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==", "dev": true }, + "@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "optional": true, + "requires": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "optional": true, + "requires": { + "tslib": "^2.4.0" + } + }, + "@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "optional": true, + "requires": { + "tslib": "^2.4.0" + } + }, "@eslint-community/eslint-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.2.0.tgz", - "integrity": "sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "requires": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + } } }, "@eslint-community/regexpp": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.2.tgz", - "integrity": "sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true }, - "@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^3.1.4" + } + }, + "@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "dev": true, + "requires": { + "@eslint/core": "^1.2.1" + } + }, + "@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.15" } }, "@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "requires": {} + }, + "@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true }, - "@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "requires": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + } + }, + "@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "requires": { + "@humanfs/types": "^0.15.0" + } + }, + "@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" } }, + "@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true + }, "@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true }, - "@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", "dev": true, + "optional": true, "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@tybys/wasm-util": "^0.10.3" } }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "@oxc-project/types": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", + "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", + "dev": true + }, + "@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", "dev": true }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "@rolldown/binding-android-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", + "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==", "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } + "optional": true }, - "@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "@rolldown/binding-darwin-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz", + "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==", + "dev": true, + "optional": true }, - "acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "@rolldown/binding-darwin-x64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz", + "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==", "dev": true, - "peer": true + "optional": true }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "@rolldown/binding-freebsd-x64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz", + "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==", "dev": true, - "requires": {} + "optional": true }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz", + "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz", + "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-arm64-musl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz", + "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz", + "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz", + "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-x64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz", + "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==", + "dev": true, + "optional": true + }, + "@rolldown/binding-linux-x64-musl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz", + "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==", + "dev": true, + "optional": true + }, + "@rolldown/binding-openharmony-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz", + "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==", + "dev": true, + "optional": true + }, + "@rolldown/binding-wasm32-wasi": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz", + "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==", "dev": true, + "optional": true, "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" } }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", + "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==", + "dev": true, + "optional": true + }, + "@rolldown/binding-win32-x64-msvc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz", + "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==", + "dev": true, + "optional": true + }, + "@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, + "optional": true, "requires": { - "color-convert": "^2.0.1" + "tslib": "^2.4.0" } }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", "dev": true }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "@vitest/browser": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.11.tgz", + "integrity": "sha512-bwMovvAeuTFOK5kIFevw4VEf+1gVEICv4SYK4k3knJOxl6b1zEWud8mYKD73e1B0odAn174h1MofURy2TPWf3w==", "dev": true, "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@blazediff/core": "1.9.1", + "@vitest/mocker": "4.1.11", + "@vitest/utils": "4.1.11", + "magic-string": "^0.30.21", + "pngjs": "^7.0.0", + "sirv": "^3.0.2", + "tinyrainbow": "^3.1.0", + "ws": "^8.19.0" } }, - "braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "@vitest/browser-playwright": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/browser-playwright/-/browser-playwright-4.1.11.tgz", + "integrity": "sha512-riLBxPqwnJ0lWs2DN2WeUfYeKLoAjbP2Xx8cLQdSddzMi20sksIa6K2mPz79DyMZKKVKH2ksOC2yJvtNcZg8cg==", "dev": true, "requires": { - "fill-range": "^7.1.1" + "@vitest/browser": "4.1.11", + "@vitest/mocker": "4.1.11", + "tinyrainbow": "^3.1.0" } }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true + "@vitest/expect": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", + "dev": true, + "requires": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + } }, - "chai": { - "version": "4.3.10", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", - "integrity": "sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==", + "@vitest/mocker": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", "dev": true, - "peer": true, "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.0.8" + "@vitest/spy": "4.1.11", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" } }, - "chai-as-promised": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", - "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "@vitest/pretty-format": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", "dev": true, "requires": { - "check-error": "^1.0.2" + "tinyrainbow": "^3.1.0" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "@vitest/runner": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@vitest/utils": "4.1.11", + "pathe": "^2.0.3" } }, - "check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "@vitest/snapshot": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", "dev": true, "requires": { - "get-func-name": "^2.0.2" + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" } }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "@vitest/spy": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", + "dev": true + }, + "@vitest/utils": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", "dev": true, "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "@vitest/pretty-format": "4.1.11", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" } }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "color-convert": { + "assertion-error": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true + }, + "balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true + }, + "brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "requires": { - "color-name": "~1.1.4" + "balanced-match": "^4.0.2" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -2124,27 +2907,12 @@ } }, "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true - }, - "deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "requires": { - "type-detect": "^4.0.0" + "ms": "^2.1.3" } }, "deep-is": { @@ -2153,31 +2921,16 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", "dev": true }, "escape-string-regexp": { @@ -2187,50 +2940,41 @@ "dev": true }, "eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", - "dev": true, - "peer": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz", + "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.4", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "dependencies": { "glob-parent": { @@ -2245,36 +2989,38 @@ } }, "eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, "requires": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true }, "espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, "requires": { - "acorn": "^8.9.0", + "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^5.0.1" } }, "esquery": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.2.tgz", - "integrity": "sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -2295,12 +3041,27 @@ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, + "estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0" + } + }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2319,210 +3080,83 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, "ffjavascript": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/ffjavascript/-/ffjavascript-0.3.0.tgz", - "integrity": "sha512-l7sR5kmU3gRwDy8g0Z2tYBXy5ttmafRPFOqY7S6af5cq51JqJWt5eQ/lSR/rs2wQNbDYaYlQr5O+OSUf/oMLoQ==", + "version": "git+https://github.com/iden3/ffjavascript.git#cc88563e1686646616ad0fcfafa6cb09c879dd25", + "integrity": "sha512-CLLd+R83k5OOIj14sPLlxlLX8YwuOFUn51Rt3XwQIegHbQyga5fMZ8DO/uCx/jVZzm1NEqSaevmQO7VNW8KVtA==", "dev": true, + "from": "ffjavascript@git+https://github.com/iden3/ffjavascript.git#cc88563e1686646616ad0fcfafa6cb09c879dd25", "requires": { "wasmbuilder": "0.0.16", - "wasmcurves": "0.2.2", - "web-worker": "1.2.0" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "wasmcurves": "git+https://github.com/iden3/wasmcurves.git#13d4acc125b794a4e28a5dc1ea99f015d863ec98", + "web-worker": "1.5.0" } }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "flat-cache": "^4.0.0" } }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "requires": { - "is-glob": "^4.0.1" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" } }, - "globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "requires": { - "type-fest": "^0.20.2" + "flatted": "^3.2.9", + "keyv": "^4.5.4" } }, - "graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "globals": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", + "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", "dev": true }, "ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -2532,52 +3166,17 @@ "is-extglob": "^2.1.1" } }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - } - } + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true }, "json-schema-traverse": { "version": "0.4.1", @@ -2588,9 +3187,18 @@ "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2601,158 +3209,172 @@ "type-check": "~0.4.0" } }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, "requires": { - "p-locate": "^5.0.0" + "detect-libc": "^2.0.3", + "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" } }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "dev": true, + "optional": true }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - } + "optional": true + }, + "lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "dev": true, + "optional": true + }, + "lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "dev": true, + "optional": true + }, + "lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "dev": true, + "optional": true + }, + "lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "dev": true, + "optional": true + }, + "lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "dev": true, + "optional": true + }, + "lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "dev": true, + "optional": true + }, + "lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "dev": true, + "optional": true }, - "loupe": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", - "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "dev": true, + "optional": true + }, + "lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "dev": true, + "optional": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "requires": { - "get-func-name": "^2.0.0" + "p-locate": "^5.0.0" } }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "requires": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "brace-expansion": "^5.0.9" } }, + "mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true + }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "dev": true }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", "dev": true }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, "optionator": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", - "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "requires": { - "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0" + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" } }, "p-limit": { @@ -2779,139 +3401,104 @@ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true }, - "punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", "dev": true, "requires": { - "picomatch": "^2.2.1" + "fsevents": "2.3.2", + "playwright-core": "1.61.1" } }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", "dev": true }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", "dev": true }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rollup": { - "version": "2.79.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", - "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==", + "postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "dev": true, "requires": { - "fsevents": "~2.3.2" + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" } }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "rolldown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", + "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==", "dev": true, "requires": { - "randombytes": "^2.1.0" + "@oxc-project/types": "=0.137.0", + "@rolldown/binding-android-arm64": "1.1.3", + "@rolldown/binding-darwin-arm64": "1.1.3", + "@rolldown/binding-darwin-x64": "1.1.3", + "@rolldown/binding-freebsd-x64": "1.1.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", + "@rolldown/binding-linux-arm64-gnu": "1.1.3", + "@rolldown/binding-linux-arm64-musl": "1.1.3", + "@rolldown/binding-linux-ppc64-gnu": "1.1.3", + "@rolldown/binding-linux-s390x-gnu": "1.1.3", + "@rolldown/binding-linux-x64-gnu": "1.1.3", + "@rolldown/binding-linux-x64-musl": "1.1.3", + "@rolldown/binding-openharmony-arm64": "1.1.3", + "@rolldown/binding-wasm32-wasi": "1.1.3", + "@rolldown/binding-win32-arm64-msvc": "1.1.3", + "@rolldown/binding-win32-x64-msvc": "1.1.3", + "@rolldown/pluginutils": "^1.0.0" } }, "shebang-command": { @@ -2929,55 +3516,90 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } + "siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", "dev": true, "requires": { - "ansi-regex": "^5.0.1" + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" } }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true + }, + "stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true + }, + "tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true + }, + "tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "dependencies": { + "fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "requires": {} + } } }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", "dev": true }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true + }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "requires": { - "is-number": "^7.0.0" - } + "optional": true }, "type-check": { "version": "0.4.0", @@ -2988,18 +3610,6 @@ "prelude-ls": "^1.2.1" } }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -3009,25 +3619,78 @@ "punycode": "^2.1.0" } }, + "vite": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.0.tgz", + "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", + "dev": true, + "requires": { + "fsevents": "~2.3.3", + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.26", + "rolldown": "~1.1.2", + "tinyglobby": "^0.2.17" + }, + "dependencies": { + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + } + } + }, + "vitest": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", + "dev": true, + "requires": { + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + } + }, "wasmbuilder": { "version": "0.0.16", "resolved": "https://registry.npmjs.org/wasmbuilder/-/wasmbuilder-0.0.16.tgz", "integrity": "sha512-Qx3lEFqaVvp1cEYW7Bfi+ebRJrOiwz2Ieu7ZG2l7YyeSJIok/reEQCQCuicj/Y32ITIJuGIM9xZQppGx5LrQdA==", - "dev": true + "dev": true, + "optional": true }, "wasmcurves": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/wasmcurves/-/wasmcurves-0.2.2.tgz", - "integrity": "sha512-JRY908NkmKjFl4ytnTu5ED6AwPD+8VJ9oc94kdq7h5bIwbj0L4TDJ69mG+2aLs2SoCmGfqIesMWTEJjtYsoQXQ==", + "version": "git+https://github.com/iden3/wasmcurves.git#13d4acc125b794a4e28a5dc1ea99f015d863ec98", + "integrity": "sha512-HYvEBTL7NaGyLvhCT3as3/SIFWloyB+4iUy4wt3OSwTgK7jQXNRiXcHXsUwSCtLDPOnU9qX/gI+EKb5/UFyw4g==", "dev": true, + "from": "wasmcurves@git+https://github.com/iden3/wasmcurves.git#13d4acc125b794a4e28a5dc1ea99f015d863ec98", + "optional": true, "requires": { "wasmbuilder": "0.0.16" } }, "web-worker": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz", - "integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", + "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", "dev": true }, "which": { @@ -3039,67 +3702,28 @@ "isexe": "^2.0.0" } }, - "workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "siginfo": "^2.0.0", + "stackback": "0.0.2" } }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true }, - "yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "dev": true, - "requires": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - } + "requires": {} } } } diff --git a/package.json b/package.json index b8c0049..6e8a046 100644 --- a/package.json +++ b/package.json @@ -3,16 +3,32 @@ "type": "module", "version": "0.0.20", "description": "fast cached read write of big binary files", - "main": "./build/main.cjs", + "main": "./build/node/main.cjs", "module": "./src/fastfile.js", "exports": { - "import": "./src/fastfile.js", - "require": "./build/main.cjs" + ".": { + "node": { + "import": "./src/fastfile.js", + "require": "./build/node/main.cjs" + }, + "browser": { + "import": "./build/browser/browser.esm.js", + "default": "./build/browser/browser.iife.js" + }, + "default": "./src/fastfile.js" + } }, "scripts": { - "pretest": "eslint .", - "test": "mocha", - "build": "rollup -c rollup.cjs.config.js" + "lint": "eslint .", + "pretest": "npm run build && npm run lint", + "test": "vitest run", + "test:node": "vitest run --project node-esm --project node-cjs", + "test:browser": "vitest run --project browser-esm", + "test:watch": "vitest", + "test:integration": "vitest run test/test.js --testTimeout=1000000000", + "build:node": "vite build --mode node", + "build:browser": "vite build --mode browser", + "build": "npm run build:node && npm run build:browser" }, "keywords": [ "file", @@ -24,17 +40,26 @@ ], "author": "0kims", "license": "GPL-3.0", - "dependencies": {}, "repository": { "type": "git", "url": "https://github.com/iden3/fastfile.git" }, "devDependencies": { - "eslint": "^8.17.0", - "ffjavascript": "^0.3.0", - "mocha": "^10.0.0", - "chai": "^4.3.6", - "chai-as-promised": "^7.1.1", - "rollup": "^2.20.0" + "@eslint/js": "^10.0.1", + "@vitest/browser": "^4.1.11", + "@vitest/browser-playwright": "^4.1.11", + "eslint": "^10.1.0", + "ffjavascript": "git+https://github.com/iden3/ffjavascript.git#cc88563e1686646616ad0fcfafa6cb09c879dd25", + "globals": "^17.4.0", + "playwright": "^1.59.0", + "vite": "^8.0.3", + "vitest": "^4.1.11" + }, + "overrides": { + "serialize-javascript": "^7.0.5", + "diff": "^8.0.3", + "minimatch": "^3.1.4", + "postcss": "^8.5.26", + "brace-expansion": "^5.0.9" } } diff --git a/rollup.cjs.config.js b/rollup.cjs.config.js deleted file mode 100644 index e3f40b6..0000000 --- a/rollup.cjs.config.js +++ /dev/null @@ -1,16 +0,0 @@ -import fs from "fs"; -import { builtinModules as builtin } from "module"; - -const pkg = JSON.parse(fs.readFileSync("./package.json")); - -export default { - input: "./src/fastfile.js", - output: { - file: "build/main.cjs", - format: "cjs", - }, - external: [ - ...Object.keys(pkg.dependencies), - ...builtin, - ] -}; diff --git a/src/bigmemfile.js b/src/bigmemfile.js index 8513db0..d483c52 100644 --- a/src/bigmemfile.js +++ b/src/bigmemfile.js @@ -208,6 +208,13 @@ class BigMemFile { } let readLength = Math.min(fixedSize, self.o.data[currentPage].length - offsetOnPage); + if (readLength <= 0) { + // EOF without a terminator: the string ends at the end of the + // data (matches the rangefile backend). This used to spin + // forever re-reading an empty window. + self.pos = currentPosition; + return str; + } const dataArray = new Uint8Array(self.o.data[currentPage].buffer, offsetOnPage, readLength); let indexEndOfString = dataArray.findIndex(element => element === 0); diff --git a/src/blobfile.js b/src/blobfile.js new file mode 100644 index 0000000..0fd13eb --- /dev/null +++ b/src/blobfile.js @@ -0,0 +1,25 @@ +// Read-only file over a Blob/File (e.g. a browser +// selection). blob.slice() is a zero-copy view; bytes only reach memory when +// a sub-range is read, so large zkeys stream from disk chunk-by-chunk with +// the same bounded footprint as the Node file backend. + +import { RangeFile } from "./rangefile.js"; + +// Same rationale as httpfile's cap, relaxed for a local source: callers pass +// disk-tuned page sizes (MiBs) that would make every small header read +// materialize a huge slice; 1 MiB keeps that bounded while costing at most a +// handful of slice reads per file open. +const MAX_BLOB_PAGE_SIZE = 1 << 20; + +export function readExisting(o) { + const blob = o.blob; + const readRangeInto = async function (dst, dstOffset, pos, len) { + const ab = await blob.slice(pos, pos + len).arrayBuffer(); + if (ab.byteLength !== len) { + throw new Error("short blob read (" + ab.byteLength + "/" + len + " bytes at " + pos + ")"); + } + dst.set(new Uint8Array(ab), dstOffset); + }; + const pageSize = Math.min(o.pageSize || MAX_BLOB_PAGE_SIZE, MAX_BLOB_PAGE_SIZE); + return new RangeFile(readRangeInto, blob.size, o.cacheSize, pageSize); +} diff --git a/src/fastfile.browser.js b/src/fastfile.browser.js new file mode 100644 index 0000000..b7dba5c --- /dev/null +++ b/src/fastfile.browser.js @@ -0,0 +1,55 @@ +import * as memFile from "./memfile.js"; +import * as bigMemFile from "./bigmemfile.js"; +import * as httpFile from "./httpfile.js"; +import * as blobFile from "./blobfile.js"; + +function noFileSupport() { + throw new Error("File I/O is not supported in the browser"); +} + +function normalizeOpts(o) { + if (o instanceof Uint8Array) return { type: "mem", data: o }; + // A string means a file path/name in the Node API. There are no files in + // the browser, so reject it instead of silently creating an anonymous mem + // file (which would mask code copied from the Node README). + if (typeof o === "string") noFileSupport(); + return o; +} + +function dispatchMem(o, memFn, bigMemFn) { + if (o.type === "file") noFileSupport(); + if (o.type === "mem") return memFn(o); + if (o.type === "bigMem") return bigMemFn(o); + throw new Error("Invalid FastFile type: " + o.type); +} + +export function createOverride(o) { + return dispatchMem(normalizeOpts(o), memFile.createNew, bigMemFile.createNew); +} + +export const createNoOverride = createOverride; + +export async function readExisting(o, b, c) { + if (o instanceof Uint8Array) { + o = { type: "mem", data: o }; + } + if (typeof Blob !== "undefined" && o instanceof Blob) { + o = { type: "blob", blob: o, cacheSize: b, pageSize: c }; + } + if (typeof o === "string") { + // URLs (historically fetched whole) go through the http backend: it + // streams via Range requests when the server supports them and falls + // back to buffering the full body when it does not. + o = { type: "http", url: o, cacheSize: b, pageSize: c }; + } + if (o.type === "http") return await httpFile.readExisting(o); + if (o.type === "blob") return blobFile.readExisting(o); + return dispatchMem(o, memFile.readExisting, bigMemFile.readExisting); +} + +export function readWriteExisting(o) { + if (typeof o === "string") noFileSupport(); + return dispatchMem(o, memFile.readWriteExisting, bigMemFile.readWriteExisting); +} + +export const readWriteExistingOrCreate = readWriteExisting; diff --git a/src/fastfile.js b/src/fastfile.js index d065634..afb639a 100644 --- a/src/fastfile.js +++ b/src/fastfile.js @@ -1,12 +1,40 @@ import { open } from "./osfile.js"; import * as memFile from "./memfile.js"; import * as bigMemFile from "./bigmemfile.js"; +import * as httpFile from "./httpfile.js"; +import * as blobFile from "./blobfile.js"; import { O_TRUNC, O_CREAT, O_RDWR, O_EXCL, O_RDONLY } from "constants"; const DEFAULT_CACHE_SIZE = (1 << 16); const DEFAULT_PAGE_SIZE = (1 << 13); +// Robust Node detection that never throws (unlike `process.browser`, which is a +// webpack-ism and is undefined under Vite/esbuild/SES). +const isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null; + +// The b/c arguments are the caller's cacheSize/pageSize hints. Historically +// they were only applied while coercing string (and Blob) sources into +// descriptor objects, so a caller passing {type: "file"|"http"} without +// explicit sizes silently fell back to the small built-in defaults even when +// it supplied tuned hints for the string case. Fill exactly the fields the +// descriptor leaves undefined; never mutate the caller's object. +function withSizeFallbacks(o, b, c) { + if (typeof o !== "object" || o === null) return o; + // Only the page-cache backends consume these sizes. mem/bigMem + // descriptors must pass through UNTOUCHED and by identity: the mem + // backend stores the written data on the caller's object (o.data), so + // handing a copy to createNew would leave the caller's descriptor empty. + if (o.type !== "file" && o.type !== "http" && o.type !== "blob") return o; + const needCache = o.cacheSize === undefined && b !== undefined; + const needPage = o.pageSize === undefined && c !== undefined; + if (!needCache && !needPage) return o; + const res = Object.assign({}, o); + if (needCache) res.cacheSize = b; + if (needPage) res.pageSize = c; + return res; +} + export async function createOverride(o, b, c) { if (typeof o === "string") { @@ -17,6 +45,7 @@ export async function createOverride(o, b, c) { pageSize: c || DEFAULT_PAGE_SIZE }; } + o = withSizeFallbacks(o, b, c); if (o.type == "file") { return await open(o.fileName, O_TRUNC | O_CREAT | O_RDWR, o.cacheSize, o.pageSize); } else if (o.type == "mem") { @@ -24,7 +53,7 @@ export async function createOverride(o, b, c) { } else if (o.type == "bigMem") { return bigMemFile.createNew(o); } else { - throw new Error("Invalid FastFile type: " + o.type); + throw new Error("Invalid FastFile type: "+o.type); } } @@ -37,6 +66,7 @@ export function createNoOverride(o, b, c) { pageSize: c || DEFAULT_PAGE_SIZE }; } + o = withSizeFallbacks(o, b, c); if (o.type == "file") { return open(o.fileName, O_TRUNC | O_CREAT | O_RDWR | O_EXCL, o.cacheSize, o.pageSize); } else if (o.type == "mem") { @@ -44,7 +74,7 @@ export function createNoOverride(o, b, c) { } else if (o.type == "bigMem") { return bigMemFile.createNew(o); } else { - throw new Error("Invalid FastFile type: " + o.type); + throw new Error("Invalid FastFile type: "+o.type); } } @@ -55,20 +85,27 @@ export async function readExisting(o, b, c) { data: o }; } - if (process.browser) { - if (typeof o === "string") { - const buff = await fetch(o).then(function (res) { - return res.arrayBuffer(); - }).then(function (ab) { - return new Uint8Array(ab); - }); + if (typeof Blob !== "undefined" && o instanceof Blob) { + o = { + type: "blob", + blob: o, + cacheSize: b, + pageSize: c + }; + } + if (typeof o === "string") { + // URLs (and, in the browser, any string -- historically fetched + // whole) go through the http backend: it streams via Range requests + // when the server supports them and falls back to buffering the + // full body (the previous behavior) when it does not. + if (!isNode || /^https?:\/\//i.test(o)) { o = { - type: "mem", - data: buff + type: "http", + url: o, + cacheSize: b, + pageSize: c }; - } - } else { - if (typeof o === "string") { + } else { o = { type: "file", fileName: o, @@ -77,14 +114,19 @@ export async function readExisting(o, b, c) { }; } } + o = withSizeFallbacks(o, b, c); if (o.type == "file") { return await open(o.fileName, O_RDONLY, o.cacheSize, o.pageSize); } else if (o.type == "mem") { return await memFile.readExisting(o); } else if (o.type == "bigMem") { return await bigMemFile.readExisting(o); + } else if (o.type == "http") { + return await httpFile.readExisting(o); + } else if (o.type == "blob") { + return blobFile.readExisting(o); } else { - throw new Error("Invalid FastFile type: " + o.type); + throw new Error("Invalid FastFile type: "+o.type); } } @@ -97,6 +139,7 @@ export function readWriteExisting(o, b, c) { pageSize: c || DEFAULT_PAGE_SIZE }; } + o = withSizeFallbacks(o, b, c); if (o.type == "file") { return open(o.fileName, O_CREAT | O_RDWR, o.cacheSize, o.pageSize); } else if (o.type == "mem") { @@ -104,7 +147,7 @@ export function readWriteExisting(o, b, c) { } else if (o.type == "bigMem") { return bigMemFile.readWriteExisting(o); } else { - throw new Error("Invalid FastFile type: " + o.type); + throw new Error("Invalid FastFile type: "+o.type); } } @@ -117,6 +160,7 @@ export function readWriteExistingOrCreate(o, b, c) { pageSize: c || DEFAULT_PAGE_SIZE }; } + o = withSizeFallbacks(o, b, c); if (o.type == "file") { return open(o.fileName, O_CREAT | O_RDWR | O_EXCL, o.cacheSize); } else if (o.type == "mem") { @@ -124,6 +168,6 @@ export function readWriteExistingOrCreate(o, b, c) { } else if (o.type == "bigMem") { return bigMemFile.readWriteExisting(o); } else { - throw new Error("Invalid FastFile type: " + o.type); + throw new Error("Invalid FastFile type: "+o.type); } } diff --git a/src/httpfile.js b/src/httpfile.js new file mode 100644 index 0000000..1d003ce --- /dev/null +++ b/src/httpfile.js @@ -0,0 +1,336 @@ +// Read-only file over HTTP(S) using Range requests. +// +// Open probes the server with `Range: bytes=0-0`: +// - 206 + parseable Content-Range total -> a RangeFile whose reads become +// range requests; large reads (zkey/ptau sections, MSM chunks) stream +// the response body straight into the caller's buffer, so the full file +// is never resident; +// - anything else (200, no range support, unknown total) -> fall back to +// buffering the body in memory, i.e. the historical browser behavior. +// +// Consistency: the validator captured at open (strong ETag, else +// Last-Modified) is sent as If-Range on every range request. If the remote +// file changes mid-read the server answers 200 instead of 206 and the read +// throws, rather than silently mixing chunks of two file versions. +// +// Server requirements (CORS deployments): allow the `Range`, `If-Range`, +// `If-None-Match` and `If-Modified-Since` request headers and expose +// `Content-Range` + `ETag`; serve zkeys without Content-Encoding (ranges +// address encoded bytes). A missing allow-list entry for the conditional +// headers only costs the 304 warm-start (the probe retries unconditionally). + +import { RangeFile } from "./rangefile.js"; +import * as memFile from "./memfile.js"; +import { wrapWithPersistentCache, peekPersistentMeta, persistFullBody } from "./idbcache.js"; + +// Callers tune cacheSize/pageSize for the disk backend (snarkjs passes 8 MiB +// pages); over HTTP a page that large turns a 4-byte header read into a +// megabytes-range request -- for files below the page size, the whole file in +// one response, defeating streaming. Cap pages at 64 KiB: big enough to +// coalesce a binfile section-table scan, small enough to stay incidental. +// Reads >= the page size bypass the cache entirely (RangeFile direct path), +// so large section/chunk reads are unaffected by the cap. +const MAX_HTTP_PAGE_SIZE = 1 << 16; + +export async function readExisting(o) { + const url = o.url; + // With the persistent cache on, probe conditionally: if we hold cached + // blocks for this URL, an unchanged file answers 304 with no body -- + // which is what makes warm starts work even against servers that ignore + // Range and would otherwise ship the entire file in the probe response. + const cacheMeta = o.cache + ? await peekPersistentMeta({ fileKey: url, options: o.cache }) + : null; + const probeHeaders = { "Range": "bytes=0-0" }; + if (cacheMeta && cacheMeta.validator) { + if (cacheMeta.validator[0] === "\"" || cacheMeta.validator.indexOf("W/") === 0) { + probeHeaders["If-None-Match"] = cacheMeta.validator; + } else { + probeHeaders["If-Modified-Since"] = cacheMeta.validator; + } + } + let probe; + try { + probe = await fetch(url, { headers: probeHeaders }); + } catch (err) { + // Conditional headers are not CORS-safelisted; a cross-origin server + // whose preflight does not allow If-None-Match/If-Modified-Since + // fails the fetch outright. Never let the cache break an open that + // would work without it: retry unconditionally. + if (!("If-None-Match" in probeHeaders) && !("If-Modified-Since" in probeHeaders)) throw err; + probe = await fetch(url, { headers: { "Range": "bytes=0-0" } }); + } + + if (probe.status === 304) { + // Unchanged since it was cached. Serve through the cache; missing + // blocks go over range requests, or through the degrade-to-full path + // when the origin turns out to ignore Range. + await abandonBody(probe); + return await buildStreamingFile(url, cacheMeta.validator, cacheMeta.totalSize, o); + } + + if (probe.status === 206) { + const contentRange = probe.headers.get("content-range"); + const m = contentRange ? /\/(\d+)\s*$/.exec(contentRange) : null; + if (m) { + const totalSize = parseInt(m[1]); + // Drain the 1-byte probe body so the connection can be reused. + await probe.arrayBuffer(); + const validator = strongValidator(probe); + return await buildStreamingFile(url, validator, totalSize, o); + } + // 206 but total size unknown (Content-Range: bytes 0-0/*): we cannot + // do bounded positioned reads; refetch whole and buffer. + await probe.arrayBuffer(); + return await readFullyToMem(url); + } + + if (!probe.ok && probe.status !== 416) { + throw new Error("HTTP " + probe.status + " fetching " + url); + } + + if (probe.status === 416) { + // Range not satisfiable -- only legitimate for an empty file. + const contentRange = probe.headers.get("content-range"); + if (contentRange && /\/0\s*$/.test(contentRange)) { + return memFile.readExisting({ type: "mem", data: new Uint8Array(0) }); + } + return await readFullyToMem(url); + } + + // 200: the server ignored Range and sent the whole file; reuse this body. + const data = new Uint8Array(await probe.arrayBuffer()); + if (o.cache) { + // Persist the body so the next session's conditional probe gets a + // bodyless 304 and reads locally. Best-effort, validator-keyed. + const validator = strongValidator(probe); + if (validator) { + await persistFullBody({ + fileKey: url, + validator: validator, + totalSize: data.length, + options: o.cache, + data: data, + }); + } + } + return memFile.readExisting({ type: "mem", data: data }); +} + +// Positioned reader over range requests with the degrade-to-buffering escape +// hatch, optionally wrapped with the persistent block cache, presented as a +// RangeFile. Shared by the fresh (206 probe) and warm (304 probe) paths. +async function buildStreamingFile(url, validator, totalSize, o) { + // Degrade-to-buffering escape hatch: a 206 probe answer can come from an + // intermediary (browsers satisfy ranges out of their own HTTP cache of a + // full 200), while the origin itself ignores Range. When a later range + // request gets a 200 whose strong validator still matches, the file is + // unchanged -- buffer that full body once and serve every subsequent + // read from it. + let fullBody = null; + let readRangeInto = async function (dst, dstOffset, pos, len) { + if (!fullBody) { + try { + return await httpReadRangeInto(url, validator, dst, dstOffset, pos, len); + } catch (err) { + if (!err || !err.degradeToFull) throw err; + fullBody = err.fullBodyPromise; + } + } + const data = await fullBody; + if (pos + len > data.byteLength) { + throw new Error(url + ": read past the end of the buffered body"); + } + dst.set(data.subarray(pos, pos + len), dstOffset); + }; + const pageSize = Math.min(o.pageSize || MAX_HTTP_PAGE_SIZE, MAX_HTTP_PAGE_SIZE); + if (o.cache) { + // Opt-in warm start: persist fetched blocks in IndexedDB so a later + // session against the same URL reads locally. Keyed on the strong + // validator -- without one the wrapper declines and this stays a + // plain streaming reader. + readRangeInto = await wrapWithPersistentCache(readRangeInto, { + fileKey: url, + validator: validator, + totalSize: totalSize, + options: o.cache, + }); + } + return new RangeFile(readRangeInto, totalSize, o.cacheSize, pageSize); +} + +async function readFullyToMem(url) { + const res = await fetch(url); + if (!res.ok) throw new Error("HTTP " + res.status + " fetching " + url); + const data = new Uint8Array(await res.arrayBuffer()); + return memFile.readExisting({ type: "mem", data: data }); +} + +// Only a strong validator is usable with If-Range (RFC 9110 §13.1.5): a weak +// ETag would make conditional range requests always fail on some servers. +function strongValidator(res) { + const etag = res.headers.get("etag"); + if (etag && etag.indexOf("W/") !== 0) return etag; + const lastModified = res.headers.get("last-modified"); + if (lastModified) return lastModified; + return null; +} + +// Browsers cap HTTP/1.1 connections per host (Chrome: 6). A prover's +// read-ahead can put more range requests in flight than that; on a +// bandwidth-limited link the extra requests then queue behind stalled +// not-yet-consumed responses and the connection pool deadlocks -- observed +// as a browser proof of a 1 GiB zkey freezing with exactly six open +// responses. Cap concurrent range fetches below the browser's limit, and +// hold each slot until the response body is fully consumed (that is when +// the connection actually frees). +const MAX_CONCURRENT_RANGE_FETCHES = 4; +let activeRangeFetches = 0; +const rangeFetchQueue = []; +async function withRangeFetchSlot(fn) { + while (activeRangeFetches >= MAX_CONCURRENT_RANGE_FETCHES) { + await new Promise((res) => rangeFetchQueue.push(res)); + } + activeRangeFetches++; + try { + return await fn(); + } finally { + activeRangeFetches--; + const next = rangeFetchQueue.shift(); + if (next) next(); + } +} + +// Unstable-connection handling. A range GET is idempotent and If-Range +// guards consistency, so transient failures (network drops, 5xx/429, and +// stalls -- a response that stops delivering bytes) are retried with +// exponential backoff. Errors that retrying cannot fix (4xx, a changed +// validator) are marked err.permanent at their throw site and rethrown +// immediately; the degrade-to-full signal passes through untouched. +// Exported so tests (and unusual deployments) can tune it. +export const httpRetryConfig = { + retries: 3, // extra attempts after the first + backoffMs: 300, // doubled per retry + stallTimeoutMs: 10_000, // abort when no bytes (or headers) arrive for this long +}; + +const sleep = (ms) => new Promise((res) => setTimeout(res, ms)); + +async function httpReadRangeInto(url, validator, dst, dstOffset, pos, len) { + return withRangeFetchSlot(async () => { + for (let attempt = 0; ; attempt++) { + try { + return await httpReadRangeIntoOnce(url, validator, dst, dstOffset, pos, len); + } catch (err) { + if (err && (err.degradeToFull || err.permanent)) throw err; + if (attempt >= httpRetryConfig.retries) throw err; + await sleep(httpRetryConfig.backoffMs * (1 << attempt)); + } + } + }); +} + +async function httpReadRangeIntoOnce(url, validator, dst, dstOffset, pos, len) { + const headers = { "Range": "bytes=" + pos + "-" + (pos + len - 1) }; + if (validator) headers["If-Range"] = validator; + // Stall watchdog: abort when neither headers nor body bytes arrive for + // stallTimeoutMs. Without it a wedged connection hangs the read forever + // (fetch has no timeout of its own); with it, the retry loop above turns + // a stall into a bounded delay. + const ctrl = typeof AbortController !== "undefined" ? new AbortController() : null; + let stallTimer = null; + const bumpStall = () => { + if (!ctrl) return; + if (stallTimer) clearTimeout(stallTimer); + stallTimer = setTimeout( + () => ctrl.abort(new Error(url + ": no data for " + httpRetryConfig.stallTimeoutMs + "ms")), + httpRetryConfig.stallTimeoutMs); + }; + const clearStall = () => { if (stallTimer) clearTimeout(stallTimer); stallTimer = null; }; + bumpStall(); + let res; + try { + res = await fetch(url, ctrl ? { headers: headers, signal: ctrl.signal } : { headers: headers }); + } catch (err) { + clearStall(); + throw err; + } + bumpStall(); + if (res.status === 200) { + // A 200 mid-session means the origin does not honor Range (the 206 + // probe answer may have come from a browser cache). If the strong + // validator still matches -- or we never had one -- the file is + // unchanged: hand the full body to the caller to degrade to + // buffered mode. Only a changed validator is a hard error. + const nowValidator = strongValidator(res); + if (!validator || (nowValidator && nowValidator === validator)) { + clearStall(); + const degrade = new Error(url + ": origin ignored Range; degrading to full buffering"); + degrade.degradeToFull = true; + degrade.fullBodyPromise = res.arrayBuffer().then((b) => new Uint8Array(b)); + throw degrade; + } + clearStall(); + await abandonBody(res); + const changed = new Error(url + ": file changed (or server stopped honoring Range) while reading"); + changed.permanent = true; // retrying cannot bring the old bytes back + throw changed; + } + if (res.status !== 206) { + clearStall(); + await abandonBody(res); + const httpErr = new Error("HTTP " + res.status + " reading range " + pos + "+" + len + " of " + url); + // 5xx and 429 are transient by definition; other 4xx will not change + if (res.status < 500 && res.status !== 429) httpErr.permanent = true; + throw httpErr; + } + const contentRange = res.headers.get("content-range"); + const m = contentRange ? /bytes\s+(\d+)-(\d+)\//.exec(contentRange) : null; + if (m && parseInt(m[1]) !== pos) { + await abandonBody(res); + throw new Error(url + ": server returned range starting at " + m[1] + ", requested " + pos); + } + + // Stream the body straight into the destination (typed array or + // BigBuffer -- both expose .set(chunk, offset)); avoids materializing + // a second full-size copy of large section reads. + let done = 0; + try { + if (res.body && typeof res.body.getReader === "function") { + const reader = res.body.getReader(); + for (;;) { + const it = await reader.read(); + bumpStall(); + if (it.done) break; + if (done + it.value.byteLength > len) { + reader.cancel().catch(function () {}); + throw new Error(url + ": range response longer than requested"); + } + dst.set(it.value, dstOffset + done); + done += it.value.byteLength; + } + } else { + // coverage: fetch implementations without a streaming body (older + // browser polyfills); Node's undici always streams + /* c8 ignore start */ + const buff = new Uint8Array(await res.arrayBuffer()); + if (buff.byteLength > len) throw new Error(url + ": range response longer than requested"); + dst.set(buff, dstOffset); + done = buff.byteLength; + /* c8 ignore stop */ + } + } finally { + clearStall(); + } + if (done !== len) { + throw new Error(url + ": short range response (" + done + "/" + len + " bytes at " + pos + ")"); + } +} + +async function abandonBody(res) { + try { + if (res.body && typeof res.body.cancel === "function") await res.body.cancel(); + /* c8 ignore next -- non-streaming fetch polyfills only */ + else await res.arrayBuffer(); + } catch (e) { /* body teardown is best-effort */ } +} diff --git a/src/idbcache.js b/src/idbcache.js new file mode 100644 index 0000000..caf6551 --- /dev/null +++ b/src/idbcache.js @@ -0,0 +1,332 @@ +// IndexedDB-backed persistent block cache for positioned range readers. +// +// Wraps a `readRangeInto(dst, dstOffset, pos, len)` function (the http +// backend's reader) so that fetched bytes persist across page loads: a +// second proving session against the same URL serves its reads from +// IndexedDB instead of the network ("warm start"), while keeping every +// property of the streaming design -- the standard single-file format, and +// bounded memory (reads land directly in the caller's buffer; the cache +// only ever allocates up to two extra blocks for the request boundaries). +// +// Identity and consistency: entries are keyed by URL and stamped with the +// file's strong validator (ETag / Last-Modified) and total size. A changed +// validator or size drops the stale blocks at open. Without a strong +// validator the wrapper refuses to cache and returns the reader unchanged -- +// a URL-only identity could silently serve bytes of a replaced file. +// +// Storage layout (one shared database): +// files: fileKey -> { validator, totalSize, blockSize, bytes, lastUsed } +// blocks: [fileKey, blockIndex] -> Uint8Array (blockSize, tail may be short) +// +// Eviction is per-file LRU, applied at open: when the sum of cached bytes +// exceeds maxBytes, least-recently-opened files are dropped (never the one +// being opened). A zkey is useful as a whole or not at all, so file +// granularity beats block granularity here. +// +// Prototype limitations (documented, deliberate): +// - concurrent readers of the same cold block both fetch it (last write +// wins; bytes are identical by the validator guard); +// - the 206-unknown-total and 416 fallbacks are not persisted; the +// streaming path and the Range-less 200 path both populate the cache +// (the latter via persistFullBody + conditional 304 probes); +// - eviction only runs at open, not while a session writes new blocks. + +const DEFAULT_BLOCK_SIZE = 1 << 21; // 2 MiB: amortizes IDB per-op cost +const DEFAULT_MAX_BYTES = 1 << 29; // 512 MiB across all cached files +const DEFAULT_DB_NAME = "fastfile-http-cache"; + +const dbConnections = new Map(); // dbName -> Promise + +function openDb(dbName) { + if (dbConnections.has(dbName)) return dbConnections.get(dbName); + const p = new Promise((resolve, reject) => { + const req = indexedDB.open(dbName, 1); + req.onupgradeneeded = () => { + const db = req.result; + db.createObjectStore("files"); + db.createObjectStore("blocks"); + }; + req.onsuccess = () => resolve(req.result); + req.onerror = () => reject(req.error); + req.onblocked = () => reject(new Error("IndexedDB open blocked")); + }); + dbConnections.set(dbName, p); + p.catch(() => dbConnections.delete(dbName)); + return p; +} + +// Small promise adapters over the IDB request API. +function idbReq(req) { + return new Promise((resolve, reject) => { + req.onsuccess = () => resolve(req.result); + req.onerror = () => reject(req.error); + }); +} +function txDone(tx) { + return new Promise((resolve, reject) => { + tx.oncomplete = () => resolve(); + tx.onerror = () => reject(tx.error); + tx.onabort = () => reject(tx.error || new Error("IndexedDB transaction aborted")); + }); +} + +function blockRange(fileKey, from, to) { + return IDBKeyRange.bound([fileKey, from], [fileKey, to]); +} + +async function deleteFileEntries(db, fileKey) { + const tx = db.transaction(["files", "blocks"], "readwrite"); + tx.objectStore("files").delete(fileKey); + tx.objectStore("blocks").delete(blockRange(fileKey, 0, Infinity)); + await txDone(tx); +} + +// Register the file, invalidate stale blocks, apply LRU eviction. +async function prepareFile(db, fileKey, validator, totalSize, blockSize, maxBytes) { + const tx = db.transaction(["files", "blocks"], "readwrite"); + const files = tx.objectStore("files"); + const existing = await idbReq(files.get(fileKey)); + let bytes = 0; + if (existing && + existing.validator === validator && + existing.totalSize === totalSize && + existing.blockSize === blockSize) { + bytes = existing.bytes; + } else if (existing) { + // stale: same URL, different content or geometry + tx.objectStore("blocks").delete(blockRange(fileKey, 0, Infinity)); + } + files.put({ validator, totalSize, blockSize, bytes, lastUsed: Date.now() }, fileKey); + await txDone(tx); + + // LRU eviction across files (never the one just opened). + const rtx = db.transaction("files", "readonly"); + const store = rtx.objectStore("files"); + const [keys, metas] = await Promise.all([idbReq(store.getAllKeys()), idbReq(store.getAll())]); + await txDone(rtx); + let total = metas.reduce((s, mm) => s + mm.bytes, 0); + if (total <= maxBytes) return; + const victims = keys.map((k, i) => ({ key: k, meta: metas[i] })) + .filter((f) => f.key !== fileKey) + .sort((a, b) => a.meta.lastUsed - b.meta.lastUsed); + for (const v of victims) { + if (total <= maxBytes) break; + await deleteFileEntries(db, v.key); + total -= v.meta.bytes; + } +} + +// Wrap `readRangeInto` with the persistent cache. Returns the original +// reader unchanged when caching is impossible (no IndexedDB in this +// environment, storage blocked, or no strong validator to key on). +export async function wrapWithPersistentCache(readRangeInto, o) { + const { fileKey, validator, totalSize } = o; + const opts = (typeof o.options === "object" && o.options) || {}; + const blockSize = opts.blockSize || DEFAULT_BLOCK_SIZE; + const maxBytes = opts.maxBytes || DEFAULT_MAX_BYTES; + const dbName = opts.dbName || DEFAULT_DB_NAME; + + if (typeof indexedDB === "undefined" || !validator) return readRangeInto; + let db; + try { + db = await openDb(dbName); + await prepareFile(db, fileKey, validator, totalSize, blockSize, maxBytes); + } catch (e) { + // Private windows, blocked site data, quota pressure at open: the + // cache is a convenience, never a requirement. + return readRangeInto; + } + + let broken = false; // a failed write disables persistence, not reads + + async function loadCached(firstBlock, lastBlock) { + const tx = db.transaction("blocks", "readonly"); + const store = tx.objectStore("blocks"); + const range = blockRange(fileKey, firstBlock, lastBlock); + const [keys, values] = await Promise.all([idbReq(store.getAllKeys(range)), idbReq(store.getAll(range))]); + await txDone(tx); + const found = new Map(); + for (let i = 0; i < keys.length; i++) found.set(keys[i][1], values[i]); + return found; + } + + async function persistBlocks(entries) { // [{index, data}] with data copied already + if (broken || entries.length === 0) return; + try { + const tx = db.transaction(["files", "blocks"], "readwrite"); + const blocks = tx.objectStore("blocks"); + const files = tx.objectStore("files"); + for (const e of entries) blocks.put(e.data, [fileKey, e.index]); + const meta = await idbReq(files.get(fileKey)); + /* c8 ignore next -- meta vanishes only if another tab evicts mid-session */ + if (meta) { + meta.bytes += entries.reduce((s, e) => s + e.data.byteLength, 0); + meta.lastUsed = Date.now(); + files.put(meta, fileKey); + } + await txDone(tx); + } catch (e) { + broken = true; // quota exceeded etc.: keep reading, stop writing + } + } + + const blockLen = (index) => + Math.min(blockSize, totalSize - index * blockSize); + + // In-flight dedupe: provers issue concurrent, overlapping reads (MSM + // read-ahead, coefficient streaming), and without this every concurrent + // miss of the same block refetched it -- measured 3.3x the zkey size on + // the wire for a cold authV3 proof. A missing block gets one fetch; every + // other reader awaits its promise. + const inFlight = new Map(); // blockIndex -> Promise + function deferBlock(index) { + let resolve, reject; + const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); + promise.catch(() => {}); // waiters handle it; avoid unhandled-rejection noise + inFlight.set(index, promise); + return { resolve, reject, promise }; + } + + return async function cachedReadRangeInto(dst, dstOffset, pos, len) { + if (len === 0) return; + const firstBlock = Math.floor(pos / blockSize); + const lastBlock = Math.floor((pos + len - 1) / blockSize); + const found = await loadCached(firstBlock, lastBlock); + + const toPersist = []; + const copyInto = (block, i) => { + const bStart = i * blockSize; + const from = Math.max(pos, bStart); + const to = Math.min(pos + len, bStart + blockLen(i)); + dst.set(block.subarray(from - bStart, to - bStart), dstOffset + (from - pos)); + }; + // Walk the block range, serving hits (cached or in-flight) and + // grouping misses into runs of blocks that lie fully inside the + // request (fetched directly into dst, no extra copy) -- boundary + // blocks that stick out past the request are fetched whole into a + // one-block temp so the cache always stores complete blocks. + let i = firstBlock; + while (i <= lastBlock) { + const bStart = i * blockSize; + const bEnd = bStart + blockLen(i); + const hit = found.get(i); + if (hit) { + copyInto(hit, i); + i++; + continue; + } + const pending = inFlight.get(i); + if (pending) { + copyInto(await pending, i); + i++; + continue; + } + if (bStart >= pos && bEnd <= pos + len) { + // interior run: extend over consecutive interior misses + let j = i; + while (j + 1 <= lastBlock && !found.get(j + 1) && !inFlight.get(j + 1) && + (j + 1) * blockSize + blockLen(j + 1) <= pos + len) j++; + const runStart = bStart; + const runEnd = j * blockSize + blockLen(j); + const defers = []; + for (let b = i; b <= j; b++) defers.push(deferBlock(b)); + try { + await readRangeInto(dst, dstOffset + (runStart - pos), runStart, runEnd - runStart); + } catch (err) { + for (let b = i; b <= j; b++) { defers[b - i].reject(err); inFlight.delete(b); } + throw err; + } + for (let b = i; b <= j; b++) { + const s = b * blockSize; + const data = dst.slice(dstOffset + (s - pos), dstOffset + (s - pos) + blockLen(b)); + defers[b - i].resolve(data); + toPersist.push({ index: b, data }); + } + i = j + 1; + } else { + // boundary block partially outside the request: fetch it + // whole so the stored block is complete + const defer = deferBlock(i); + const block = new Uint8Array(blockLen(i)); + try { + await readRangeInto(block, 0, bStart, block.length); + } catch (err) { + defer.reject(err); + inFlight.delete(i); + throw err; + } + defer.resolve(block); + copyInto(block, i); + toPersist.push({ index: i, data: block }); + i++; + } + } + await persistBlocks(toPersist); + // keep resolved blocks discoverable until they are persisted, then + // let waiters fall through to IndexedDB + for (const e of toPersist) inFlight.delete(e.index); + }; +} + +// Peek at what the cache knows about a URL before any network request. +// Returns { validator, totalSize, blockSize, bytes } or null. Used to send a +// conditional probe (If-None-Match / If-Modified-Since): an unchanged file +// answers 304 with no body, which is what makes warm starts work even +// against servers that ignore Range and would otherwise ship the whole file +// in the probe response. +export async function peekPersistentMeta(o) { + const opts = (typeof o.options === "object" && o.options) || {}; + const dbName = opts.dbName || DEFAULT_DB_NAME; + if (typeof indexedDB === "undefined") return null; + try { + const db = await openDb(dbName); + const tx = db.transaction("files", "readonly"); + const meta = await idbReq(tx.objectStore("files").get(o.fileKey)); + await txDone(tx); + return meta ? { validator: meta.validator, totalSize: meta.totalSize, blockSize: meta.blockSize, bytes: meta.bytes } : null; + } catch (e) { + return null; + } +} + +// Persist a fully-downloaded body (the Range-less server path) so the next +// session can warm-start from it. Blocks are written in bounded batches; +// meta.bytes is set to the exact total afterwards (overwrites make +// incremental accounting drift). Best-effort: returns false when the +// environment has no usable IndexedDB or a write fails. +export async function persistFullBody(o) { + const { fileKey, validator, totalSize, data } = o; + const opts = (typeof o.options === "object" && o.options) || {}; + const blockSize = opts.blockSize || DEFAULT_BLOCK_SIZE; + const maxBytes = opts.maxBytes || DEFAULT_MAX_BYTES; + const dbName = opts.dbName || DEFAULT_DB_NAME; + if (typeof indexedDB === "undefined" || !validator) return false; + try { + const db = await openDb(dbName); + await prepareFile(db, fileKey, validator, totalSize, blockSize, maxBytes); + const nBlocks = Math.ceil(totalSize / blockSize); + const BATCH = 64; + for (let b0 = 0; b0 < nBlocks; b0 += BATCH) { + const tx = db.transaction("blocks", "readwrite"); + const blocks = tx.objectStore("blocks"); + for (let b = b0; b < Math.min(b0 + BATCH, nBlocks); b++) { + const s = b * blockSize; + blocks.put(data.slice(s, Math.min(s + blockSize, totalSize)), [fileKey, b]); + } + await txDone(tx); + } + const tx = db.transaction("files", "readwrite"); + const files = tx.objectStore("files"); + const meta = await idbReq(files.get(fileKey)); + /* c8 ignore else -- meta vanishes only if another tab evicts mid-write */ + if (meta) { + meta.bytes = totalSize; + meta.lastUsed = Date.now(); + files.put(meta, fileKey); + } + await txDone(tx); + return true; + } catch (e) { + return false; + } +} diff --git a/src/memfile.js b/src/memfile.js index fa74a7d..5b13aab 100644 --- a/src/memfile.js +++ b/src/memfile.js @@ -162,11 +162,14 @@ class MemFile { let currentPosition = typeof pos == "undefined" ? self.pos : pos; - if (currentPosition > this.totalSize) { + if (currentPosition >= this.totalSize) { if (this.readOnly) { throw new Error("Reading out of bounds"); } - this._resizeIfNeeded(pos); + // Past the written data there is no string to read. (This used to + // grow the allocation and then build a negative-length view, which + // threw a RangeError.) + return ""; } const dataArray = new Uint8Array( self.o.data.buffer, diff --git a/src/osfile.js b/src/osfile.js index fcd8445..b2fe940 100644 --- a/src/osfile.js +++ b/src/osfile.js @@ -26,6 +26,12 @@ class FastFile { this.totalSize = stats.size; this.totalPages = Math.floor((stats.size -1) / this.pageSize)+1; this.maxPagesLoaded = Math.floor( cacheSize / this.pageSize)+1; + // Reads/writes at least this large bypass the page cache and move bytes + // straight between disk and the caller's buffer (see readToBuffer / + // write), avoiding the extra buffer<->page copy that dominates large + // sequential transfers (e.g. zkey/ptau sections). + this.directReadThreshold = 1 << 20; + this.directWriteThreshold = 1 << 20; this.pages = {}; this.pendingLoads = []; this.writing = false; @@ -47,6 +53,8 @@ class FastFile { return P; } + // coverage: debug instrumentation, only enabled by hand (this.logHistory) + /* c8 ignore start */ __statusPage(s, p) { const logEntry = []; const self=this; @@ -84,6 +92,8 @@ class FastFile { + /* c8 ignore stop */ + _triggerLoad() { const self = this; @@ -150,7 +160,17 @@ class FastFile { self.__statusPage("After Load (loaded): ", load.page); return res; }, (err) => { - load.reject(err); + // Reject EVERY waiter, not just the first: co-readers of the + // same page were appended to page.loading (not to `load`) and + // would otherwise await forever. Drop the page entirely so a + // later retry re-reads it instead of queueing onto a dead + // loading list. + const page = self.pages[load.page]; + const loading = (page && page.loading) ? page.loading : [load]; + delete self.pages[load.page]; + for (let i=0; i { console.log("ERROR Writing: "+err); + // Clear `writing` (a stuck flag pins the page forever and + // blocks _tryClose) and record the error. write()/read() + // surface it on their next call (fail fast) and close() + // rejects with it -- previously it was only visible at + // close(), so a prover that skipped close on error paths + // silently produced a truncated/corrupt file. + page.writing = false; self.error = err; self._tryClose(); })); @@ -226,20 +253,47 @@ class FastFile { return -1; } + // Iterate the actually-cached pages (usually few) rather than every page + // index in [firstPage, lastPage], so the check stays O(cached pages) even + // for very large ranges / small page sizes. + _rangeHasCachedPages(pos, len) { + const firstPage = Math.floor(pos / this.pageSize); + const lastPage = Math.floor((pos + len - 1) / this.pageSize); + for (const k of Object.keys(this.pages)) { + const p = +k; + if (p >= firstPage && p <= lastPage) return true; + } + return false; + } + async write(buff, pos) { if (buff.byteLength == 0) return; const self = this; - /* - if (buff.byteLength > self.pageSize*self.maxPagesLoaded*0.8) { - const cacheSize = Math.floor(buff.byteLength * 1.1); - this.maxPagesLoaded = Math.floor( cacheSize / self.pageSize)+1; - } - */ + if (self.error) throw self.error; if (typeof pos == "undefined") pos = self.pos; self.pos = pos+buff.byteLength; if (self.totalSize < pos + buff.byteLength) self.totalSize = pos + buff.byteLength; if (self.pendingClose) throw new Error("Writing a closing file"); + + // Direct-write fast path: for large writes to a region with no cached + // pages, write straight to disk, skipping the buff->page copy and the + // deferred page flush. Any cached page in range (even clean) would go + // stale after a direct write, so we fall back to the cached path then. + // ArrayBuffer.isView gate: fd.write needs a real TypedArray/DataView; a + // BigBuffer (paged, not a view) must use the cached path. + if (buff.byteLength >= self.directWriteThreshold && ArrayBuffer.isView(buff) && !self._rangeHasCachedPages(pos, buff.byteLength)) { + let done = 0; + while (done < buff.byteLength) { + const { bytesWritten } = await self.fd.write(buff, done, buff.byteLength - done, pos + done); + if (bytesWritten === 0) break; // should not happen + done += bytesWritten; + } + const lastPage = Math.floor((pos + buff.byteLength - 1) / self.pageSize); + if (lastPage + 1 > self.totalPages) self.totalPages = lastPage + 1; + return; + } + const firstPage = Math.floor(pos / self.pageSize); const lastPage = Math.floor((pos + buff.byteLength -1) / self.pageSize); @@ -277,19 +331,56 @@ class FastFile { return buff; } + // Iterate the actually-cached pages (usually few) rather than every page + // index in [firstPage, lastPage], so the check stays O(cached pages) even + // for very large ranges / small page sizes. + _rangeHasDirtyPages(pos, len) { + const firstPage = Math.floor(pos / this.pageSize); + const lastPage = Math.floor((pos + len - 1) / this.pageSize); + for (const k of Object.keys(this.pages)) { + const p = +k; + if (p >= firstPage && p <= lastPage) { + const page = this.pages[p]; + if (page.dirty || page.writing) return true; + } + } + return false; + } + async readToBuffer(buffDst, offset, len, pos) { if (len == 0) { return; } const self = this; - if (len > self.pageSize*self.maxPagesLoaded*0.8) { - const cacheSize = Math.floor(len * 1.1); - this.maxPagesLoaded = Math.floor( cacheSize / self.pageSize)+1; - } + if (self.error) throw self.error; if (typeof pos == "undefined") pos = self.pos; self.pos = pos+len; if (self.pendingClose) throw new Error("Reading a closing file"); + + // Direct-read fast path: for large reads with no overlapping unwritten + // (dirty) pages, copy straight from disk into the destination buffer. + // This skips the page cache and the page->destination copy it incurs, + // which dominates large sequential reads (e.g. zkey/ptau sections). + // ArrayBuffer.isView gate: the direct path hands buffDst to fd.read, which + // needs a real TypedArray/DataView. A BigBuffer (paged, not a view) must + // go through the cached path, which copies into it via its own .set(). + if (len >= self.directReadThreshold && ArrayBuffer.isView(buffDst) && !self._rangeHasDirtyPages(pos, len)) { + let toRead = (pos + len > self.totalSize) ? (self.totalSize - pos) : len; + if (toRead < 0) toRead = 0; + let done = 0; + while (done < toRead) { + const { bytesRead } = await self.fd.read(buffDst, offset + done, toRead - done, pos + done); + if (bytesRead === 0) break; // EOF + done += bytesRead; + } + return; + } + + if (len > self.pageSize*self.maxPagesLoaded*0.8) { + const cacheSize = Math.floor(len * 1.1); + this.maxPagesLoaded = Math.floor( cacheSize / self.pageSize)+1; + } const firstPage = Math.floor(pos / self.pageSize); const lastPage = Math.floor((pos + len -1) / self.pageSize); @@ -300,8 +391,16 @@ class FastFile { let p = firstPage; let o = pos % self.pageSize; - // Remaining bytes to read + // Remaining bytes to read (clamped to EOF: a read past the end of a + // truncated/short file reads fewer bytes than requested). let r = pos + len > self.totalSize ? len - (pos + len - self.totalSize): len; + // Bytes already written to buffDst -- tracked independently of `r` + // (which shrinks on EOF-clamping) so the destination offset stays + // correct. Previously computed as `offset + len - r`: with `r` + // pre-clamped below `len`, that put the first bytes read at a + // nonzero offset instead of the real EOF-truncated tail, silently + // shifting valid data to the wrong position in the output buffer. + let done = 0; while (r>0) { await pagePromises[p - firstPage]; self.__statusPage("After Await (read): ", p); @@ -309,12 +408,13 @@ class FastFile { // bytes to copy from this page const l = (o+r > self.pageSize) ? (self.pageSize -o) : r; const srcView = new Uint8Array(self.pages[p].buff.buffer, self.pages[p].buff.byteOffset + o, l); - buffDst.set(srcView, offset+len-r); + buffDst.set(srcView, offset+done); self.pages[p].pendingOps --; self.__statusPage("After Op done: ", p); r = r-l; + done = done+l; p ++; o = 0; if (self.pendingLoads.length>0) setImmediate(self._triggerLoad.bind(self)); @@ -330,6 +430,7 @@ class FastFile { if (!self.pendingClose) return; if (self.error) { self.pendingCloseReject(self.error); + return; } const p = self._getDirtyPage(); if ((p>=0) || (self.writing) || (self.reading) || (self.pendingLoads.length>0)) return; @@ -338,9 +439,11 @@ class FastFile { close() { const self = this; - if (self.pendingClose) - throw new Error("Closing the file twice"); - return new Promise((resolve, reject) => { + // Idempotent, matching fs.promises.FileHandle.close(): repeated calls + // return the same promise, so cleanup code (e.g. a finally block) can + // close unconditionally. Reads/writes after close still throw. + if (self.closePromise) return self.closePromise; + self.closePromise = new Promise((resolve, reject) => { self.pendingClose = resolve; self.pendingCloseReject = reject; self._tryClose(); @@ -350,6 +453,7 @@ class FastFile { self.fd.close(); throw (err); }); + return self.closePromise; } async discard() { diff --git a/src/rangefile.js b/src/rangefile.js new file mode 100644 index 0000000..36b8942 --- /dev/null +++ b/src/rangefile.js @@ -0,0 +1,182 @@ +// Shared read-only file over an abstract positioned range reader. +// +// Backends (httpfile, blobfile) supply a single primitive: +// readRangeInto(dstBuff, dstOffset, pos, len) -> Promise +// and this class layers the FastFile read interface on top: +// - reads >= pageSize bypass the cache and go straight to the backend +// (the file is read-only, so a direct read is always coherent); +// - smaller reads (header scans: magics, section tables, ULE32/64) are +// served from page-aligned cached ranges so that e.g. a binfile section +// scan does not issue one backend request per 4-byte field. +// +// Write operations throw: remote ranges are strictly a read transport. + +const DEFAULT_CACHE_SIZE = 1 << 20; +const DEFAULT_PAGE_SIZE = 1 << 13; + +export class RangeFile { + + constructor(readRangeInto, totalSize, cacheSize, pageSize) { + this.readRangeInto = readRangeInto; + this.totalSize = totalSize; + this.pos = 0; + this.pageSize = pageSize || DEFAULT_PAGE_SIZE; + this.maxPagesLoaded = Math.floor((cacheSize || DEFAULT_CACHE_SIZE) / this.pageSize) + 1; + this.pages = new Map(); // page index -> { buff: Uint8Array|null, promise: Promise } + this.readOnly = true; + } + + _pageLen(p) { + const start = p * this.pageSize; + const end = Math.min(start + this.pageSize, this.totalSize); + return end - start; + } + + // Load (or join the in-flight load of) page p; resolves to its buffer. + // A failed load removes the entry so a later retry re-requests it. + _loadPage(p) { + const self = this; + let page = self.pages.get(p); + if (page) { + // LRU touch: re-insert so Map iteration order tracks recency. + self.pages.delete(p); + self.pages.set(p, page); + return page.promise; + } + const buff = new Uint8Array(self._pageLen(p)); + page = { buff: null, promise: null }; + page.promise = self.readRangeInto(buff, 0, p * self.pageSize, buff.byteLength).then(function () { + page.buff = buff; + return buff; + }, function (err) { + self.pages.delete(p); + throw err; + }); + self.pages.set(p, page); + self._trimCache(); + return page.promise; + } + + _trimCache() { + const self = this; + if (self.pages.size <= self.maxPagesLoaded) return; + // Evict oldest settled pages first; in-flight loads are kept. + for (const entry of self.pages) { + if (self.pages.size <= self.maxPagesLoaded) return; + if (entry[1].buff) self.pages.delete(entry[0]); + } + } + + async readToBuffer(buffDst, offset, len, pos) { + const self = this; + if (len === 0) return; + if (self.pendingClose) throw new Error("Reading a closing file"); + if (typeof pos === "undefined") pos = self.pos; + if (pos + len > self.totalSize) throw new Error("Reading out of bounds"); + self.pos = pos + len; + + // Direct path: one backend request straight into the destination + // (works for both typed arrays and BigBuffer via .set()). + if (len >= self.pageSize) { + await self.readRangeInto(buffDst, offset, pos, len); + return; + } + + const firstPage = Math.floor(pos / self.pageSize); + const lastPage = Math.floor((pos + len - 1) / self.pageSize); + let o = pos % self.pageSize; + let done = 0; + for (let p = firstPage; p <= lastPage; p++) { + const buff = await self._loadPage(p); + const l = Math.min(len - done, self.pageSize - o); + buffDst.set(buff.subarray(o, o + l), offset + done); + done += l; + o = 0; + } + } + + async read(len, pos) { + const buff = new Uint8Array(len); + await this.readToBuffer(buff, 0, len, pos); + return buff; + } + + async readULE32(pos) { + const b = await this.read(4, pos); + const view = new Uint32Array(b.buffer); + return view[0]; + } + + async readUBE32(pos) { + const b = await this.read(4, pos); + const view = new DataView(b.buffer); + return view.getUint32(0, false); + } + + async readULE64(pos) { + const b = await this.read(8, pos); + const view = new Uint32Array(b.buffer); + return view[1] * 0x100000000 + view[0]; + } + + async readString(pos) { + const self = this; + if (self.pendingClose) throw new Error("Reading a closing file"); + let p = typeof pos === "undefined" ? self.pos : pos; + const chunks = []; + while (p < self.totalSize) { + const l = Math.min(self.pageSize, self.totalSize - p); + const chunk = await self.read(l, p); + const z = chunk.indexOf(0); + if (z >= 0) { + chunks.push(chunk.subarray(0, z)); + self.pos = p + z + 1; + return decodeChunks(chunks); + } + chunks.push(chunk); + p += l; + } + // No terminator before EOF: the string ends at EOF. + self.pos = p; + return decodeChunks(chunks); + } + + async write() { + throw new Error("Writing a read only file"); + } + + async writeULE32() { + throw new Error("Writing a read only file"); + } + + async writeUBE32() { + throw new Error("Writing a read only file"); + } + + async writeULE64() { + throw new Error("Writing a read only file"); + } + + async close() { + // Idempotent: a second close is a no-op, matching the os backend. + if (this.pendingClose) return; + this.pendingClose = true; + this.pages.clear(); + } + + async discard() { + await this.close(); + } +} + +function decodeChunks(chunks) { + let total = 0; + for (let i = 0; i < chunks.length; i++) total += chunks[i].byteLength; + const all = new Uint8Array(total); + let o = 0; + for (let i = 0; i < chunks.length; i++) { + all.set(chunks[i], o); + o += chunks[i].byteLength; + } + return new TextDecoder().decode(all); +} diff --git a/test/api_surface.test.js b/test/api_surface.test.js new file mode 100644 index 0000000..12b12b8 --- /dev/null +++ b/test/api_surface.test.js @@ -0,0 +1,519 @@ +import fs from "fs"; +import http from "http"; +import * as fastFile from "../src/fastfile.js"; + +import assert from "assert"; +import { expect } from "vitest"; + + +// Behavioral coverage of the full FastFile API surface: the dispatcher +// variants in fastfile.js, and the ULE/UBE/string helpers plus error paths +// of every backend (mem, bigMem, os, http/range, blob). + +const BIGMEM_PAGE = 1 << 22; // bigmemfile's fixed page size + +function makeData(n) { + const data = new Uint8Array(n); + for (let i = 0; i < n; i++) data[i] = (i * 7 + 3) & 0xFF; + return data; +} + +// Round-trips every numeric helper plus a string on a writable fd. +async function roundTripHelpers(fd) { + await fd.writeULE32(0x01020304, 0); + await fd.writeUBE32(0x0A0B0C0D, 4); + await fd.writeULE64(0x1FFFFFFFFF, 8); // > 2^32: exercises the high word + + assert.strictEqual(await fd.readULE32(0), 0x01020304); + assert.strictEqual(await fd.readUBE32(4), 0x0A0B0C0D); + assert.strictEqual(await fd.readULE64(8), 0x1FFFFFFFFF); + + // byte-level check of endianness + const raw = await fd.read(8, 0); + assert.deepStrictEqual([...raw], [4, 3, 2, 1, 0x0A, 0x0B, 0x0C, 0x0D]); +} + +describe("fastfile API surface", function () { + + describe("dispatcher (fastfile.js)", function () { + it("createOverride / createNoOverride / readWriteExisting reject unknown types", async () => { + await expect(fastFile.createOverride({ type: "nope" })).rejects.toThrow("Invalid FastFile type"); + expect(() => fastFile.createNoOverride({ type: "nope" })).to.throw("Invalid FastFile type"); + await expect(fastFile.readExisting({ type: "nope" })).rejects.toThrow("Invalid FastFile type"); + expect(() => fastFile.readWriteExisting({ type: "nope" })).to.throw("Invalid FastFile type"); + expect(() => fastFile.readWriteExistingOrCreate({ type: "nope" })).to.throw("Invalid FastFile type"); + }); + + it("createNoOverride creates mem/bigMem files and a fresh os file, but refuses an existing one", async () => { + const mem = fastFile.createNoOverride({ type: "mem" }); + await mem.write(new Uint8Array([1, 2]), 0); + mem.close(); + + const big = fastFile.createNoOverride({ type: "bigMem" }); + await big.write(new Uint8Array([1, 2]), 0); + big.close(); + + const fileName = "test_no_override.bin"; + const fd = await fastFile.createNoOverride(fileName); + await fd.write(new Uint8Array([9]), 0); + await fd.close(); + // O_EXCL: a second createNoOverride on the same path must fail + await expect(fastFile.createNoOverride(fileName)).rejects.toThrow(); + await fs.promises.unlink(fileName); + }); + + it("readWriteExisting opens mem, bigMem and os files for update", async () => { + const memData = { type: "mem", data: makeData(16) }; + const mem = fastFile.readWriteExisting(memData); + await mem.write(new Uint8Array([0xFF]), 0); + assert.strictEqual((await mem.read(1, 0))[0], 0xFF); + mem.close(); + + const big = fastFile.readWriteExisting({ type: "bigMem", data: [makeData(16)] }); + await big.write(new Uint8Array([0xEE]), 0); + assert.strictEqual((await big.read(1, 0))[0], 0xEE); + big.close(); + + const fileName = "test_rw_existing.bin"; + await fs.promises.writeFile(fileName, Buffer.from(makeData(32))); + const fd = await fastFile.readWriteExisting(fileName); + await fd.write(new Uint8Array([0xAB]), 0); + assert.strictEqual((await fd.read(1, 0))[0], 0xAB); + await fd.close(); + await fs.promises.unlink(fileName); + }); + + it("readWriteExistingOrCreate creates a fresh os file and opens mem/bigMem", async () => { + const fileName = "test_rw_or_create.bin"; + const fd = await fastFile.readWriteExistingOrCreate(fileName); + await fd.write(new Uint8Array([1, 2, 3]), 0); + await fd.close(); + assert.strictEqual((await fs.promises.stat(fileName)).size, 3); + await fs.promises.unlink(fileName); + + const mem = fastFile.readWriteExistingOrCreate({ type: "mem", data: makeData(4) }); + assert.strictEqual(mem.totalSize, 4); + mem.close(); + + const big = fastFile.readWriteExistingOrCreate({ type: "bigMem", data: [makeData(4)] }); + assert.strictEqual(big.totalSize, 4); + big.close(); + }); + + it("readExisting accepts a bare Uint8Array and a bigMem descriptor", async () => { + const data = makeData(8); + const fd = await fastFile.readExisting(data); + assert.deepStrictEqual([...(await fd.read(8, 0))], [...data]); + fd.close(); + + const big = await fastFile.readExisting({ type: "bigMem", data: [makeData(8)] }); + assert.strictEqual(big.totalSize, 8); + assert.strictEqual(big.readOnly, true); + big.close(); + }); + }); + + describe("mem backend", function () { + it("round-trips the numeric helpers", async () => { + const fd = await fastFile.createOverride({ type: "mem" }); + await roundTripHelpers(fd); + fd.close(); + }); + + it("grows the allocation when writing beyond it, and trims on close", async () => { + const o = { type: "mem", initialSize: 4 }; + const fd = await fastFile.createOverride(o); + const big = makeData(1 << 21); // 2 MiB: beyond the 1 MiB growth step + await fd.write(big, 0); + assert.strictEqual(fd.totalSize, big.byteLength); + fd.close(); + assert.strictEqual(o.data.byteLength, big.byteLength); + // a second close finds byteLength == totalSize and does not re-trim + fd.close(); + }); + + it("readToBuffer beyond the end grows a writable file but rejects on a read-only one", async () => { + const rw = fastFile.readWriteExisting({ type: "mem", data: makeData(4) }); + const got = await rw.read(8, 0); // grows to 8; tail is zero-filled + assert.deepStrictEqual([...got.slice(4)], [0, 0, 0, 0]); + + const ro = await fastFile.readExisting({ type: "mem", data: makeData(4) }); + await expect(ro.read(8, 0)).rejects.toThrow("Reading out of bounds"); + await expect(ro.write(new Uint8Array(1), 0)).rejects.toThrow("read only"); + }); + + it("readString finds terminated strings and returns empty when unterminated", async () => { + const enc = new TextEncoder(); + const fd = await fastFile.createOverride({ type: "mem" }); + await fd.write(enc.encode("hello"), 0); + await fd.write(new Uint8Array([0]), 5); + await fd.write(enc.encode("tail"), 6); // no terminator + + assert.strictEqual(await fd.readString(0), "hello"); + // sequential read continues after the terminator: unterminated tail + const tail = await fd.readString(); + assert.strictEqual(tail, ""); + + // read-only fd: reading a string past the end rejects + const ro = await fastFile.readExisting({ type: "mem", data: enc.encode("x") }); + await expect(ro.readString(10)).rejects.toThrow("Reading out of bounds"); + + // writable fd: a position past the end resizes and yields "" + assert.strictEqual(await fd.readString(1000), ""); + + await fd.discard(); + fd.close(); + }); + }); + + describe("bigMem backend", function () { + it("preallocates pages for initialSize and round-trips the numeric helpers", async () => { + const fd = await fastFile.createOverride({ type: "bigMem", initialSize: BIGMEM_PAGE + 10 }); + assert.strictEqual(fd.o.data.length, 2); + await roundTripHelpers(fd); + await fd.discard(); + fd.close(); + }); + + it("writes and reads across the page boundary", async () => { + const fd = await fastFile.createOverride({ type: "bigMem" }); + const chunk = makeData(4096); + const pos = BIGMEM_PAGE - 100; // straddles pages 0 and 1 + await fd.write(chunk, pos); + const got = await fd.read(4096, pos); + assert.deepStrictEqual([...got], [...chunk]); + assert.strictEqual(fd.o.data.length, 2); + fd.close(); + }); + + it("read-only files reject out-of-bounds reads and writes", async () => { + const fd = await fastFile.readExisting({ type: "bigMem", data: [makeData(16)] }); + await expect(fd.read(32, 0)).rejects.toThrow(); + await expect(fd.write(new Uint8Array(1), 0)).rejects.toThrow("read only"); + }); + + it("readString handles long strings, page boundaries and a missing terminator", async () => { + const enc = new TextEncoder(); + const fd = await fastFile.createOverride({ type: "bigMem" }); + + // longer than the 2048-byte scan chunk + const long = "a".repeat(5000); + await fd.write(enc.encode(long), 0); + await fd.write(new Uint8Array([0]), 5000); + assert.strictEqual(await fd.readString(0), long); + + // string crossing the 4 MiB page boundary + const cross = "b".repeat(200); + await fd.write(enc.encode(cross), BIGMEM_PAGE - 100); + await fd.write(new Uint8Array([0]), BIGMEM_PAGE + 100); + assert.strictEqual(await fd.readString(BIGMEM_PAGE - 100), cross); + + // unterminated string: ends at EOF instead of spinning forever + const fd2 = await fastFile.createOverride({ type: "bigMem" }); + await fd2.write(enc.encode("no-end"), 0); + assert.strictEqual(await fd2.readString(0), "no-end"); + fd2.close(); + + fd.close(); + }); + + it("readString past the end of a writable file yields the empty string or ERROR at a page boundary", async () => { + const fd = await fastFile.createOverride({ type: "bigMem" }); + await fd.write(new Uint8Array([1, 2, 3]), 0); + assert.strictEqual(await fd.readString(100), ""); + // exactly at a page boundary the resized data has no next page + await expect(fd.readString(BIGMEM_PAGE)).rejects.toThrow("ERROR"); + fd.close(); + }); + }); + + describe("os backend extras", function () { + const fileName = "test_os_extras.bin"; + + afterEach(async () => { + try { await fs.promises.unlink(fileName); } catch (e) { /* may not exist */ } + }); + + it("round-trips the numeric helpers", async () => { + const fd = await fastFile.createOverride(fileName); + await roundTripHelpers(fd); + await fd.close(); + }); + + it("grows a sub-blksize pageSize up to the filesystem block size", async () => { + const fd = await fastFile.createOverride(fileName, 1 << 16, 256); + assert.strictEqual(fd.pageSize % 256, 0); + const stats = await fs.promises.stat(fileName); + assert(fd.pageSize >= stats.blksize); + await fd.close(); + }); + + it("readString spans multiple pages", async () => { + const enc = new TextEncoder(); + const fd = await fastFile.createOverride(fileName, 1 << 16, 1 << 12); + const long = "s".repeat(10000); // > 2 pages of 4096 + await fd.write(enc.encode(long), 0); + await fd.write(new Uint8Array([0]), 10000); + assert.strictEqual(await fd.readString(0), long); + await fd.close(); + }); + + it("read(0) is a no-op and a direct-size read past EOF returns zero-filled bytes", async () => { + const fd = await fastFile.createOverride(fileName, 1 << 22, 1 << 12); + await fd.write(makeData(1 << 12), 0); + const empty = await fd.read(0, 0); + assert.strictEqual(empty.byteLength, 0); + + // >= directReadThreshold (1 MiB) with pos beyond EOF: clamps to + // zero bytes read, destination stays zero-filled + const past = await fd.read(1 << 20, 1 << 21); + assert(past.every((b) => b === 0)); + await fd.close(); + }); + + it("a large read overlapping dirty pages takes the cached path", async () => { + const fd = await fastFile.createOverride(fileName, 1 << 23, 1 << 12); + const small = makeData(4096); + await fd.write(small, 0); // small cached write: page 0 is dirty + const got = await fd.read(1 << 20, 0); // direct-size read overlaps it + assert.deepStrictEqual([...got.slice(0, 64)], [...small.slice(0, 64)]); + await fd.close(); + }); + + it("a direct-size write over previously cached pages stays coherent", async () => { + const fd = await fastFile.createOverride(fileName, 1 << 22, 1 << 12); + await fd.write(makeData(1 << 12), 0); + await fd.read(16, 0); // populate the page cache + const big = new Uint8Array(1 << 20).fill(0x5A); + await fd.write(big, 0); // >= directWriteThreshold with cached overlap + const got = await fd.read(32, 0); + assert(got.every((b) => b === 0x5A)); + await fd.close(); + }); + + it("discard closes and unlinks the file", async () => { + const fd = await fastFile.createOverride(fileName); + await fd.write(makeData(8), 0); + await fd.discard(); + await expect(fs.promises.stat(fileName)).rejects.toThrow(); + }); + + it("writing after close fails fast", async () => { + const fd = await fastFile.createOverride(fileName); + await fd.write(makeData(8), 0); + await fd.close(); + await expect(fd.write(makeData(8), 0)).rejects.toThrow("Writing a closing file"); + await expect(fd.readString(0)).rejects.toThrow("Reading a closing file"); + }); + }); + + describe("blob backend (RangeFile)", function () { + function blobOf(data) { + return new Blob([data]); + } + + it("round-trips numeric helpers and strings, and evicts old pages", async () => { + const enc = new TextEncoder(); + const data = new Uint8Array(1 << 16); + data.set([4, 3, 2, 1], 0); // ULE32 0x01020304 + data.set([0x0A, 0x0B, 0x0C, 0x0D], 4); // UBE32 + data.set([0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0, 0, 0], 8); // ULE64 0x1FFFFFFFFF + data.set(enc.encode("blob-str"), 16); + data[24] = 0; + + // pageSize 4 KiB, cacheSize == pageSize -> maxPagesLoaded 2: + // touching many pages exercises cache eviction + const fd = await fastFile.readExisting({ type: "blob", blob: blobOf(data), cacheSize: 1 << 12, pageSize: 1 << 12 }); + assert.strictEqual(await fd.readULE32(0), 0x01020304); + assert.strictEqual(await fd.readUBE32(4), 0x0A0B0C0D); + assert.strictEqual(await fd.readULE64(8), 0x1FFFFFFFFF); + assert.strictEqual(await fd.readString(16), "blob-str"); + for (let p = 0; p < 8; p++) { + await fd.read(4, p * (1 << 12)); + } + assert(fd.pages.size <= fd.maxPagesLoaded); + await fd.close(); + }); + + it("readString without a terminator ends at EOF; sequential reads advance pos", async () => { + const enc = new TextEncoder(); + const fd = await fastFile.readExisting({ type: "blob", blob: blobOf(enc.encode("abc\0defgh")) }); + assert.strictEqual(await fd.readString(), "abc"); + assert.strictEqual(await fd.readString(), "defgh"); // runs to EOF + await fd.close(); + }); + + it("rejects out-of-bounds reads and every write helper", async () => { + const fd = await fastFile.readExisting({ type: "blob", blob: blobOf(makeData(16)) }); + await expect(fd.read(32, 0)).rejects.toThrow("Reading out of bounds"); + await expect(fd.write(new Uint8Array(1), 0)).rejects.toThrow("read only"); + await expect(fd.writeULE32(1, 0)).rejects.toThrow("read only"); + await expect(fd.writeUBE32(1, 0)).rejects.toThrow("read only"); + await expect(fd.writeULE64(1, 0)).rejects.toThrow("read only"); + await fd.discard(); // close()s; second close via discard is a no-op + await expect(fd.readString(0)).rejects.toThrow("Reading a closing file"); + }); + + it("surfaces short reads from a lying blob", async () => { + const fake = { + size: 100, + slice: function () { + return { arrayBuffer: function () { return Promise.resolve(new ArrayBuffer(1)); } }; + } + }; + const fd = await fastFile.readExisting({ type: "blob", blob: fake }); + await expect(fd.read(50, 0)).rejects.toThrow("short blob read"); + // the failed page load was evicted, not cached + assert.strictEqual(fd.pages.size, 0); + }); + }); + + describe("http backend probe fallbacks", function () { + // Minimal configurable server: `mode` selects the probe behavior. + function startServer(data, mode) { + const server = http.createServer(function (req, res) { + const range = req.headers.range; + if (!range) { // plain GET (readFullyToMem) + res.writeHead(200, { "Content-Length": data.length }); + res.end(Buffer.from(data)); + return; + } + const m = /^bytes=(\d+)-(\d+)$/.exec(range); + const start = parseInt(m[1]); + const end = Math.min(parseInt(m[2]), data.length - 1); + const isProbe = start === 0 && parseInt(m[1]) === 0 && parseInt(m[2]) === 0; + + if (mode === "probe-500") { + res.writeHead(500); res.end("boom"); return; + } + if (mode === "416-empty") { + res.writeHead(416, { "Content-Range": "bytes */0" }); res.end(); return; + } + if (mode === "416-other") { + res.writeHead(416, { "Content-Range": "bytes */" + data.length }); res.end(); return; + } + if (mode === "no-total") { + res.writeHead(206, { "Content-Range": "bytes 0-0/*", "Content-Length": 1 }); + res.end(Buffer.from(data.slice(0, 1))); return; + } + if (mode === "fail-after-probe" && !isProbe) { + res.writeHead(500); res.end("boom"); return; + } + if (mode === "wrong-start" && !isProbe) { + const body = Buffer.from(data.slice(start + 1, end + 2)); + res.writeHead(206, { + "Content-Range": "bytes " + (start + 1) + "-" + (end + 1) + "/" + data.length, + "Content-Length": body.length + }); + res.end(body); return; + } + if (mode === "overlong" && !isProbe) { + const body = Buffer.concat([Buffer.from(data.slice(start, end + 1)), Buffer.alloc(10, 7)]); + res.writeHead(206, { + "Content-Range": "bytes " + start + "-" + end + "/" + data.length, + "Content-Length": body.length + }); + res.end(body); return; + } + if (mode === "short" && !isProbe) { + const body = Buffer.from(data.slice(start, end)); // one byte short + res.writeHead(206, { + "Content-Range": "bytes " + start + "-" + end + "/" + data.length, + "Content-Length": body.length + }); + res.end(body); return; + } + const body = Buffer.from(data.slice(start, end + 1)); + res.writeHead(206, { + "Content-Range": "bytes " + start + "-" + end + "/" + data.length, + "Content-Length": body.length, + "ETag": "\"v1\"" + }); + res.end(body); + }); + return new Promise(function (resolve) { + server.listen(0, "127.0.0.1", function () { + resolve({ + url: "http://127.0.0.1:" + server.address().port + "/f.bin", + close: function () { return new Promise(function (r) { server.close(r); }); } + }); + }); + }); + } + + it("buffers fully when the total size is unknown (Content-Range */*)", async () => { + const data = makeData(4096); + const srv = await startServer(data, "no-total"); + try { + const fd = await fastFile.readExisting(srv.url); + assert.strictEqual(fd.totalSize, data.length); + assert.deepStrictEqual([...(await fd.read(8, 100))], [...data.slice(100, 108)]); + fd.close(); + } finally { await srv.close(); } + }); + + it("throws on a failing probe", async () => { + const srv = await startServer(makeData(16), "probe-500"); + try { + await expect(fastFile.readExisting(srv.url)).rejects.toThrow("HTTP 500"); + } finally { await srv.close(); } + }); + + it("treats 416 with a zero total as an empty file", async () => { + const srv = await startServer(new Uint8Array(0), "416-empty"); + try { + const fd = await fastFile.readExisting(srv.url); + assert.strictEqual(fd.totalSize, 0); + fd.close(); + } finally { await srv.close(); } + }); + + it("falls back to a full fetch on other 416 responses", async () => { + const data = makeData(64); + const srv = await startServer(data, "416-other"); + try { + const fd = await fastFile.readExisting(srv.url); + assert.strictEqual(fd.totalSize, data.length); + fd.close(); + } finally { await srv.close(); } + }); + + it("rejects a range answered at the wrong offset", async () => { + const srv = await startServer(makeData(1 << 17), "wrong-start"); + try { + const fd = await fastFile.readExisting(srv.url); + await expect(fd.read(1 << 16, 0)).rejects.toThrow("server returned range starting at"); + fd.close(); + } finally { await srv.close(); } + }); + + it("rejects a range response longer than requested", async () => { + const srv = await startServer(makeData(1 << 17), "overlong"); + try { + const fd = await fastFile.readExisting(srv.url); + await expect(fd.read(1 << 16, 0)).rejects.toThrow("longer than requested"); + fd.close(); + } finally { await srv.close(); } + }); + + it("rejects a short range response", async () => { + const srv = await startServer(makeData(1 << 17), "short"); + try { + const fd = await fastFile.readExisting(srv.url); + await expect(fd.read(1 << 16, 0)).rejects.toThrow("short range response"); + fd.close(); + } finally { await srv.close(); } + }); + + it("a failed page load is retried on the next read", async () => { + const srv = await startServer(makeData(1 << 14), "fail-after-probe"); + try { + const fd = await fastFile.readExisting(srv.url); + await expect(fd.read(4, 0)).rejects.toThrow("HTTP 500"); + // the rejected page was evicted so a retry issues a new request + await expect(fd.read(4, 0)).rejects.toThrow("HTTP 500"); + fd.close(); + } finally { await srv.close(); } + }); + }); +}); diff --git a/test/bigmemfile.test.js b/test/bigmemfile.test.js index 719bb4b..d94885e 100644 --- a/test/bigmemfile.test.js +++ b/test/bigmemfile.test.js @@ -1,16 +1,8 @@ import * as testUtils from "./testUtils.js"; import * as fastFile from "../src/fastfile.js"; - -import assert from "assert"; -import * as chai from "chai"; -import chaiAsPromised from "chai-as-promised"; - -chai.use(chaiAsPromised); -const expect = chai.expect; +import { expect } from "vitest"; describe("fastfile testing suite for bigMemFile", function () { - this.timeout(100000); - let str1 = "0123456789"; let str2 = "Hi_there"; let str3 = "/!!--::**"; @@ -29,13 +21,13 @@ describe("fastfile testing suite for bigMemFile", function () { await testUtils.writeStringToFile(fd, str3); let str = await fd.readString((1 << 22) - 9); - assert.strictEqual(str, str1); + expect(str).toBe(str1); str = await fd.readString(); - assert.strictEqual(str, str2); + expect(str).toBe(str2); str = await fd.readString(); - assert.strictEqual(str, str3); + expect(str).toBe(str3); await fd.close(); }); @@ -47,8 +39,8 @@ describe("fastfile testing suite for bigMemFile", function () { byteLength: 1 }] }; - let fd = await fastFile.readExisting(file); - expect(fd.readString(10)).to.be.rejectedWith("Reading out of bounds"); + const fd = await fastFile.readExisting(file); + await expect(fd.readString(10)).rejects.toThrow("Reading out of bounds"); }); }); diff --git a/test/blobfile.test.js b/test/blobfile.test.js new file mode 100644 index 0000000..50ec9fc --- /dev/null +++ b/test/blobfile.test.js @@ -0,0 +1,46 @@ +import * as fastFile from "../src/fastfile.js"; + +import assert from "assert"; +import { expect } from "vitest"; + + +function makeData(n) { + const data = new Uint8Array(n); + let x = 0x9E3779B9; + for (let i = 0; i < n; i++) { + x ^= x << 13; x >>>= 0; + x ^= x >> 17; + x ^= x << 5; x >>>= 0; + data[i] = x & 0xFF; + } + return data; +} + +describe("fastfile testing suite for blobfile", function () { + + it("should read positioned ranges from a Blob", async () => { + const data = makeData(1 << 18); + const fd = await fastFile.readExisting(new Blob([data])); + assert.strictEqual(fd.totalSize, data.length); + + const head = await fd.read(4, 0); + assert.deepStrictEqual(Buffer.from(head), Buffer.from(data.slice(0, 4))); + + const big = new Uint8Array(100000); + await fd.readToBuffer(big, 0, big.length, 30000); + assert.deepStrictEqual(Buffer.from(big), Buffer.from(data.slice(30000, 130000))); + + const v = await fd.readULE64(1024); + const dv = new DataView(data.buffer); + assert.strictEqual(v, dv.getUint32(1028, true) * 0x100000000 + dv.getUint32(1024, true)); + + await fd.close(); + }); + + it("should reject reads out of bounds and any write", async () => { + const fd = await fastFile.readExisting(new Blob([makeData(100)])); + await expect(fd.read(8, 96)).rejects.toThrow(/out of bounds/); + await expect(fd.write(new Uint8Array(4), 0)).rejects.toThrow(/read only/); + await fd.close(); + }); +}); diff --git a/test/fastfile.browser.test.js b/test/fastfile.browser.test.js new file mode 100644 index 0000000..e8d9446 --- /dev/null +++ b/test/fastfile.browser.test.js @@ -0,0 +1,120 @@ +import * as fastFile from "../src/fastfile.browser.js"; +import * as testUtils from "./testUtils.js"; +import { vi, expect } from "vitest"; + +describe("fastfile browser — mem", function () { + const str1 = "0123456789"; + const str2 = "Hi_there"; + const str3 = "/!!--::**"; + + it("should read valid strings from mem file", async () => { + const fd = await fastFile.createOverride({ type: "mem" }); + + await testUtils.writeFakeStringToFile(fd, 10); + await testUtils.writeStringToFile(fd, str1); + await testUtils.writeStringToFile(fd, str2); + await testUtils.writeStringToFile(fd, str3); + + let str = await fd.readString(10); + expect(str).toBe(str1); + + str = await fd.readString(); + expect(str).toBe(str2); + + str = await fd.readString(); + expect(str).toBe(str3); + + await fd.close(); + }); + + it("createNoOverride is an alias for createOverride in browser", async () => { + const fd = await fastFile.createNoOverride({ type: "mem" }); + expect(fd).toBeTruthy(); + await fd.close(); + }); + + it("readExisting from Uint8Array", async () => { + const data = new Uint8Array([10, 20, 30]); + const fd = await fastFile.readExisting(data); + const result = await fd.read(3, 0); + expect([...result]).toEqual([10, 20, 30]); + await fd.close(); + }); + + it("readExisting from a Blob streams through the blob backend", async () => { + const bytes = new Uint8Array(64 * 1024); + for (let i = 0; i < bytes.length; i++) bytes[i] = i & 0xff; + const fd = await fastFile.readExisting(new Blob([bytes])); + expect(fd.totalSize).toBe(bytes.length); + // small read (page-cached) and large read (direct slice) both match + const head = await fd.read(16, 0); + expect([...head]).toEqual([...bytes.subarray(0, 16)]); + const tail = await fd.read(32 * 1024, 32 * 1024); + expect([...tail]).toEqual([...bytes.subarray(32 * 1024)]); + await fd.close(); + }); + + it("readExisting from URL fetches data", async () => { + const mockData = new Uint8Array([1, 2, 3]); + // A server that ignores Range and sends the whole body (the http + // backend probes with Range: bytes=0-0 and falls back to buffering). + vi.stubGlobal("fetch", () => + Promise.resolve({ + ok: true, + status: 200, + arrayBuffer: () => Promise.resolve(mockData.buffer.slice(0)), + }) + ); + try { + const fd = await fastFile.readExisting("https://example.com/test.bin"); + const result = await fd.read(3, 0); + expect([...result]).toEqual([1, 2, 3]); + await fd.close(); + } finally { + vi.unstubAllGlobals(); + } + }); + + it("throws when using file type", () => { + expect(() => fastFile.createOverride({ type: "file", fileName: "test" })) + .toThrow("not supported in the browser"); + }); + + it("readWriteExistingOrCreate is an alias for readWriteExisting", async () => { + const o = { type: "mem", data: new Uint8Array([5, 6, 7]) }; + const fd = await fastFile.readWriteExistingOrCreate(o); + expect(fd).toBeTruthy(); + await fd.close(); + }); +}); + +describe("fastfile browser — bigMem", function () { + const str1 = "0123456789"; + const str2 = "Hi_there"; + const str3 = "/!!--::**"; + + it("should read valid strings from bigMem file", async () => { + const fd = await fastFile.createOverride({ type: "bigMem" }); + + await testUtils.writeFakeStringToFile(fd, 10); + await testUtils.writeStringToFile(fd, str1); + await testUtils.writeStringToFile(fd, str2); + await testUtils.writeStringToFile(fd, str3); + + let str = await fd.readString(10); + expect(str).toBe(str1); + + str = await fd.readString(); + expect(str).toBe(str2); + + str = await fd.readString(); + expect(str).toBe(str3); + + await fd.close(); + }); + + it("throws for file type on bigMem project", () => { + expect(() => fastFile.readWriteExisting({ type: "file" })) + .toThrow("not supported in the browser"); + }); +}); diff --git a/test/httpfile.test.js b/test/httpfile.test.js new file mode 100644 index 0000000..6ad2569 --- /dev/null +++ b/test/httpfile.test.js @@ -0,0 +1,459 @@ +import * as fastFile from "../src/fastfile.js"; + +import http from "http"; +import assert from "assert"; +import { expect } from "vitest"; + + +// Deterministic pseudo-random content so failures are reproducible. +function makeData(n) { + const data = new Uint8Array(n); + let x = 0x12345678; + for (let i = 0; i < n; i++) { + x ^= x << 13; x >>>= 0; + x ^= x >> 17; + x ^= x << 5; x >>>= 0; + data[i] = x & 0xFF; + } + return data; +} + +// Minimal static server. opts: +// ranges: false -> ignore Range headers, always 200 full body +// etag -> served as ETag; If-Range mismatches answer 200 full +// state.data / state.etag may be swapped mid-test to simulate a changed file. +function startServer(state, opts) { + opts = opts || {}; + const log = { requests: [], bytesServed: 0, maxResponse: 0 }; + const server = http.createServer(function (req, res) { + const entry = { range: req.headers.range || null, ifRange: req.headers["if-range"] || null }; + log.requests.push(entry); + const data = state.data; + const headers = {}; + if (state.etag) headers["ETag"] = state.etag; + + const m = (opts.ranges !== false && req.headers.range) ? + /^bytes=(\d+)-(\d+)$/.exec(req.headers.range) : null; + const ifRangeOk = !req.headers["if-range"] || req.headers["if-range"] === state.etag; + + if (m && ifRangeOk) { + const start = parseInt(m[1]); + if (start >= data.length) { + headers["Content-Range"] = "bytes */" + data.length; + res.writeHead(416, headers); + res.end(); + return; + } + const end = Math.min(parseInt(m[2]), data.length - 1); + const body = Buffer.from(data.slice(start, end + 1)); + headers["Content-Range"] = "bytes " + start + "-" + end + "/" + data.length; + headers["Content-Length"] = body.length; + headers["Accept-Ranges"] = "bytes"; + res.writeHead(206, headers); + log.bytesServed += body.length; + log.maxResponse = Math.max(log.maxResponse, body.length); + res.end(body); + } else { + const body = Buffer.from(data); + headers["Content-Length"] = body.length; + res.writeHead(200, headers); + log.bytesServed += body.length; + res.end(body); + } + }); + return new Promise(function (resolve) { + server.listen(0, "127.0.0.1", function () { + resolve({ + server: server, + log: log, + url: "http://127.0.0.1:" + server.address().port + "/data.bin", + close: function () { + return new Promise(function (res2) { server.close(res2); }); + } + }); + }); + }); +} + +describe("fastfile testing suite for httpfile", function () { + + describe("unstable connections (retry + stall timeout)", () => { + let saved; + beforeEach(async () => { + const { httpRetryConfig } = await import("../src/httpfile.js"); + saved = { ...httpRetryConfig }; + httpRetryConfig.backoffMs = 10; + httpRetryConfig.stallTimeoutMs = 250; + }); + afterEach(async () => { + const { httpRetryConfig } = await import("../src/httpfile.js"); + Object.assign(httpRetryConfig, saved); + }); + + // Minimal faulty range server: `plan` decides per zkey request. + function faultyServer(data, plan) { + let n = 0; + const server = http.createServer((req, res) => { + const m = /^bytes=(\d+)-(\d+)$/.exec(req.headers.range || ""); + const start = m ? parseInt(m[1]) : 0; + const end = m ? Math.min(parseInt(m[2]), data.length - 1) : data.length - 1; + const isProbe = start === 0 && end === 0; + const action = isProbe ? "ok" : plan(++n); + if (action === "503") { res.writeHead(503); res.end(); return; } + res.writeHead(action === "404" ? 404 : 206, { + "Content-Range": `bytes ${start}-${end}/${data.length}`, + "Content-Length": end - start + 1, + "ETag": "\"u1\"", "Accept-Ranges": "bytes", + }); + if (action === "404") { res.end(); return; } + const body = Buffer.from(data.slice(start, end + 1)); + if (action === "drop") { + res.write(body.subarray(0, Math.floor(body.length / 2))); + setTimeout(() => res.destroy(), 10); + return; + } + if (action === "stall") { + res.write(body.subarray(0, 16)); // then silence, socket open + return; + } + res.end(body); + }); + return new Promise((r) => server.listen(0, "127.0.0.1", () => r({ + url: `http://127.0.0.1:${server.address().port}/f.bin`, + attempts: () => n, + close: () => new Promise((x) => { server.closeAllConnections(); server.close(x); }), + }))); + } + + it("recovers from a mid-body connection drop", async () => { + const data = makeData(1 << 18); + const srv = await faultyServer(data, (n) => (n === 1 ? "drop" : "ok")); + try { + const fd = await fastFile.readExisting(srv.url); + const got = await fd.read(200000, 1000); + assert.deepStrictEqual(Buffer.from(got), Buffer.from(data.slice(1000, 201000))); + await fd.close(); + assert.strictEqual(srv.attempts(), 2); + } finally { await srv.close(); } + }); + + it("retries transient 503s but fails fast on 404", async () => { + const data = makeData(1 << 17); + const srv = await faultyServer(data, (n) => (n <= 2 ? "503" : "ok")); + try { + const fd = await fastFile.readExisting(srv.url); + const got = await fd.read(65536, 0); + assert.deepStrictEqual(Buffer.from(got), Buffer.from(data.slice(0, 65536))); + await fd.close(); + assert.strictEqual(srv.attempts(), 3); + } finally { await srv.close(); } + + const srv2 = await faultyServer(data, () => "404"); + try { + const fd = await fastFile.readExisting(srv2.url); + await expect(fd.read(65536, 0)).rejects.toThrow("HTTP 404"); + assert.strictEqual(srv2.attempts(), 1); // permanent: no retry + await fd.close(); + } finally { await srv2.close(); } + }); + + it("aborts a stalled response and recovers on retry", async () => { + const data = makeData(1 << 18); + const srv = await faultyServer(data, (n) => (n === 1 ? "stall" : "ok")); + try { + const t0 = Date.now(); + const fd = await fastFile.readExisting(srv.url); + const got = await fd.read(150000, 5000); + assert.deepStrictEqual(Buffer.from(got), Buffer.from(data.slice(5000, 155000))); + await fd.close(); + assert.strictEqual(srv.attempts(), 2); + assert.ok(Date.now() - t0 < 5000, "stall was not bounded"); + } finally { await srv.close(); } + }); + + it("gives up after exhausting retries", async () => { + const data = makeData(1 << 17); + const srv = await faultyServer(data, () => "503"); + try { + const fd = await fastFile.readExisting(srv.url); + await expect(fd.read(65536, 0)).rejects.toThrow("HTTP 503"); + assert.strictEqual(srv.attempts(), 4); // 1 try + 3 retries + await fd.close(); + } finally { await srv.close(); } + }); + }); + + it("caps concurrent range fetches below the browser connection limit", async () => { + const data = makeData(1 << 20); + let inFlight = 0, maxInFlight = 0; + const fakeFetch = async (url, opts) => { + const m = /bytes=(\d+)-(\d+)/.exec((opts && opts.headers && opts.headers["Range"]) || ""); + const from = m ? parseInt(m[1]) : 0; + const to = m ? Math.min(parseInt(m[2]), data.length - 1) : data.length - 1; + inFlight++; maxInFlight = Math.max(maxInFlight, inFlight); + await new Promise((r) => setTimeout(r, 20)); + inFlight--; + return { + ok: true, status: 206, + headers: new Map([ + ["content-range", `bytes ${from}-${to}/${data.length}`], + ["etag", "\"cap\""], + ]), + arrayBuffer: async () => data.slice(from, to + 1).buffer, + body: null, + }; + }; + fakeFetch.headersGet = true; + const realFetch = globalThis.fetch; + globalThis.fetch = (u, o) => fakeFetch(u, o).then((r) => ({ ...r, headers: { get: (k) => r.headers.get(k.toLowerCase()) } })); + try { + const fd = await fastFile.readExisting({ type: "http", url: "http://cap.example/f.bin" }); + const reads = []; + for (let k = 0; k < 12; k++) reads.push(fd.read(70000, k * 80000)); // > pageSize: direct path + await Promise.all(reads); + await fd.close(); + assert.ok(maxInFlight <= 4, "expected <= 4 concurrent range fetches, saw " + maxInFlight); + } finally { + globalThis.fetch = realFetch; + } + }); + + it("http cache option is a no-op where IndexedDB does not exist (Node)", async () => { + const data = makeData(1 << 18); + const srv = await startServer({ data: data, etag: "\"v1\"" }); + try { + const fd = await fastFile.readExisting({ + type: "http", url: srv.url, cache: true, + }); + const got = await fd.read(64, 12345); + assert.deepStrictEqual(Buffer.from(got), Buffer.from(data.slice(12345, 12345 + 64))); + await fd.close(); + } finally { + await srv.close(); + } + }); + + it("should read ranges without ever downloading the full file", async () => { + const data = makeData(1 << 20); // 1 MiB + const srv = await startServer({ data: data, etag: "\"v1\"" }); + try { + const fd = await fastFile.readExisting(srv.url); + assert.strictEqual(fd.totalSize, data.length); + + // header-style small reads + const head = await fd.read(4, 0); + assert.deepStrictEqual([...head], [...data.slice(0, 4)]); + const v = await fd.readULE32(4); + assert.strictEqual(v, new DataView(data.buffer).getUint32(4, true)); + + // large positioned read (direct path) + const big = new Uint8Array(300000); + await fd.readToBuffer(big, 0, big.length, 500000); + assert.deepStrictEqual(Buffer.from(big), Buffer.from(data.slice(500000, 800000))); + + // scattered small reads (cached path), spanning a page boundary + const ps = fd.pageSize; + const cross = await fd.read(16, ps - 8); + assert.deepStrictEqual(Buffer.from(cross), Buffer.from(data.slice(ps - 8, ps + 8))); + + await fd.close(); + + // every request carried a Range header (no full-file GET) ... + for (const r of srv.log.requests) assert.ok(r.range, "expected only range requests"); + // ... and far less than the file was transferred + assert.ok(srv.log.bytesServed < data.length / 2, + "served " + srv.log.bytesServed + " of " + data.length); + } finally { + await srv.close(); + } + }); + + it("should serve repeated small reads on one page from cache (single request)", async () => { + const data = makeData(1 << 16); + const srv = await startServer({ data: data, etag: "\"v1\"" }); + try { + const fd = await fastFile.readExisting(srv.url); + const before = srv.log.requests.length; + await fd.read(4, 100); + await fd.read(4, 200); + await fd.readULE64(300); + assert.strictEqual(srv.log.requests.length, before + 1); + await fd.close(); + } finally { + await srv.close(); + } + }); + + it("should read sequentially without explicit positions", async () => { + const data = makeData(4096); + const srv = await startServer({ data: data }); + try { + const fd = await fastFile.readExisting(srv.url); + const a = await fd.read(10); + const b = await fd.read(10); + assert.deepStrictEqual(Buffer.concat([Buffer.from(a), Buffer.from(b)]), Buffer.from(data.slice(0, 20))); + assert.strictEqual(fd.pos, 20); + await fd.close(); + } finally { + await srv.close(); + } + }); + + it("should fall back to full buffering when the server ignores Range", async () => { + const data = makeData(100000); + const srv = await startServer({ data: data }, { ranges: false }); + try { + const fd = await fastFile.readExisting(srv.url); + assert.strictEqual(fd.totalSize, data.length); + const all = await fd.read(data.length, 0); + assert.deepStrictEqual(Buffer.from(all), Buffer.from(data)); + await fd.close(); + // the probe response body was reused: exactly one request + assert.strictEqual(srv.log.requests.length, 1); + } finally { + await srv.close(); + } + }); + + it("should fail a read when the remote file changes mid-session (If-Range)", async () => { + const state = { data: makeData(1 << 20), etag: "\"v1\"" }; + const srv = await startServer(state); + try { + const fd = await fastFile.readExisting(srv.url); + const buff = new Uint8Array(1 << 16); + await fd.readToBuffer(buff, 0, buff.length, 0); // works on v1 + + state.data = makeData(1 << 20).map(function (b) { return b ^ 0xFF; }); + state.etag = "\"v2\""; + + // direct (uncached) read must reject, not silently mix versions + await expect(fd.readToBuffer(new Uint8Array(1 << 16), 0, 1 << 16, 1 << 17)) + .rejects.toThrow(/file changed/); + await fd.close(); + } finally { + await srv.close(); + } + }); + + it("should reject reads out of bounds and any write", async () => { + const data = makeData(1000); + const srv = await startServer({ data: data, etag: "\"v1\"" }); + try { + const fd = await fastFile.readExisting(srv.url); + await expect(fd.read(16, 992)).rejects.toThrow(/out of bounds/); + await expect(fd.write(new Uint8Array(4), 0)).rejects.toThrow(/read only/); + await expect(fd.writeULE32(1, 0)).rejects.toThrow(/read only/); + await fd.close(); + } finally { + await srv.close(); + } + }); + + it("should cap disk-tuned page-size hints (small read must not fetch the file)", async () => { + const data = makeData(1 << 19); // 512 KiB, well under an 8 MiB "page" + const srv = await startServer({ data: data, etag: "\"v1\"" }); + try { + // snarkjs-style hints: 32 MiB cache, 8 MiB pages (meant for disk) + const fd = await fastFile.readExisting(srv.url, 1 << 25, 1 << 23); + await fd.read(4, 0); + await fd.readULE64(300000); + assert.ok(srv.log.maxResponse <= 1 << 16, + "largest response was " + srv.log.maxResponse + " bytes; page hint not capped"); + await fd.close(); + } finally { + await srv.close(); + } + }); + + it("close() is idempotent on the http backend", async () => { + const data = makeData(1 << 12); + const srv = await startServer({ data: data, etag: "\"v1\"" }); + try { + const fd = await fastFile.readExisting(srv.url); + await fd.read(4, 0); + await fd.close(); + await fd.close(); // second close: no-op, no throw + } finally { + await srv.close(); + } + }); + + it("degrades to full buffering when the origin stops honoring Range (matching validator)", async () => { + // Browser scenario: the probe is answered 206 by an HTTP cache, but + // the origin itself ignores Range and answers 200 with the same + // strong ETag. Reads must succeed via buffered mode, not throw + // "file changed". + const data = makeData(1 << 18); + let first = true; + const server = http.createServer((req, res) => { + if (first && req.headers.range === "bytes=0-0") { + first = false; + res.writeHead(206, { + "Content-Range": "bytes 0-0/" + data.length, + "Content-Length": 1, + "ETag": "\"stable\"", + }); + return res.end(Buffer.from(data.slice(0, 1))); + } + res.writeHead(200, { "Content-Length": data.length, "ETag": "\"stable\"" }); + res.end(Buffer.from(data)); + }); + await new Promise((r) => server.listen(0, "127.0.0.1", r)); + const url = "http://127.0.0.1:" + server.address().port + "/data.bin"; + try { + const fd = await fastFile.readExisting(url); + assert.strictEqual(fd.totalSize, data.length); + const head = await fd.read(8, 0); + assert.deepStrictEqual([...head], [...data.slice(0, 8)]); + const mid = await fd.read(1 << 12, 1 << 17); + assert.deepStrictEqual([...mid], [...data.slice(1 << 17, (1 << 17) + (1 << 12))]); + await fd.close(); + } finally { + await new Promise((r) => server.close(r)); + } + }); + + it("still fails hard when a mid-session 200 carries a different validator", async () => { + const data = makeData(1 << 16); + let first = true; + const server = http.createServer((req, res) => { + if (first && req.headers.range === "bytes=0-0") { + first = false; + res.writeHead(206, { + "Content-Range": "bytes 0-0/" + data.length, + "Content-Length": 1, + "ETag": "\"v1\"", + }); + return res.end(Buffer.from(data.slice(0, 1))); + } + res.writeHead(200, { "Content-Length": data.length, "ETag": "\"v2\"" }); + res.end(Buffer.from(data)); + }); + await new Promise((r) => server.listen(0, "127.0.0.1", r)); + const url = "http://127.0.0.1:" + server.address().port + "/data.bin"; + try { + const fd = await fastFile.readExisting(url); + await expect(fd.read(64, 0)).rejects.toThrow(/file changed/); + await fd.close(); + } finally { + await new Promise((r) => server.close(r)); + } + }); + + it("should read strings through the page cache", async () => { + const data = makeData(9000); + const msg = "hello_snarkjs"; + for (let i = 0; i < msg.length; i++) data[8000 + i] = msg.charCodeAt(i); + data[8000 + msg.length] = 0; + const srv = await startServer({ data: data, etag: "\"v1\"" }); + try { + const fd = await fastFile.readExisting(srv.url); + const str = await fd.readString(8000); + assert.strictEqual(str, msg); + assert.strictEqual(fd.pos, 8000 + msg.length + 1); + await fd.close(); + } finally { + await srv.close(); + } + }); +}); diff --git a/test/idbcache.browser.test.js b/test/idbcache.browser.test.js new file mode 100644 index 0000000..a502a6f --- /dev/null +++ b/test/idbcache.browser.test.js @@ -0,0 +1,278 @@ +import * as fastFile from "../src/fastfile.browser.js"; +import { expect, vi } from "vitest"; + +// In-Chromium tests for the IndexedDB-backed persistent block cache +// (cache option of the http backend). fetch is stubbed with a +// deterministic Range-honoring server that counts requests, so warm starts +// are provable as "zero network requests". + +const FILE_SIZE = 5 * 1024 * 1024 + 123; // deliberately not block-aligned +const BLOCK = 1 << 20; // 1 MiB test blocks + +function makeContent(seed) { + const data = new Uint8Array(FILE_SIZE); + for (let i = 0; i < data.length; i++) data[i] = (i * 31 + seed) & 0xff; + return data; +} + +// A minimal Range-capable origin: 206 with Content-Range, strong ETag. +function serveRanges(content, etag, counter) { + return (url, opts) => { + counter.count++; + const range = opts && opts.headers && opts.headers["Range"]; + const m = /bytes=(\d+)-(\d+)/.exec(range || ""); + const from = m ? parseInt(m[1]) : 0; + const to = m ? Math.min(parseInt(m[2]), content.length - 1) : content.length - 1; + counter.bytes += to - from + 1; + const body = content.slice(from, to + 1); + return Promise.resolve({ + ok: true, + status: 206, + headers: new Headers({ + "content-range": `bytes ${from}-${to}/${content.length}`, + "etag": etag, + }), + arrayBuffer: () => Promise.resolve(body.buffer.slice(0)), + body: null, // exercise the non-streaming fallback (jsdom-style) + }); + }; +} + +async function openCached(url, cacheOpts) { + return fastFile.readExisting({ + type: "http", + url, + cache: cacheOpts || { blockSize: BLOCK, dbName: "fastfile-test-cache" }, + }); +} + +function clearDb(name) { + return new Promise((resolve) => { + const req = indexedDB.deleteDatabase(name); + req.onsuccess = req.onerror = req.onblocked = () => resolve(); + }); +} + +describe("IndexedDB persistent block cache", function () { + const URL1 = "https://cache.example/circuit.zkey"; + + beforeEach(async () => { + // fresh DB per test -- but note the module keeps its connection + // per dbName, so tests use distinct dbNames where isolation matters + await clearDb("fastfile-test-cache"); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("cold reads are correct across block boundaries and warm reads hit no network", async () => { + const content = makeContent(7); + const counter = { count: 0, bytes: 0 }; + vi.stubGlobal("fetch", serveRanges(content, "\"v1\"", counter)); + + const dbName = "fastfile-test-warm"; + await clearDb(dbName); + const fd = await openCached(URL1, { blockSize: BLOCK, dbName }); + + // unaligned read spanning interior + boundary blocks + const big = await fd.read(3 * BLOCK, BLOCK + 17); + expect([...big.slice(0, 8)]).toEqual([...content.subarray(BLOCK + 17, BLOCK + 25)]); + expect([...big.slice(-8)]).toEqual([...content.subarray(4 * BLOCK + 9, 4 * BLOCK + 17)]); + // small header-style read + const head = await fd.read(16, 4); + expect([...head]).toEqual([...content.subarray(4, 20)]); + // tail read hitting the short last block + const tail = await fd.read(100, FILE_SIZE - 100); + expect([...tail]).toEqual([...content.subarray(FILE_SIZE - 100)]); + await fd.close(); + const coldFetches = counter.count; + expect(coldFetches).toBeGreaterThan(0); + + // warm start: new handle, same URL -- all reads served from IDB + const fd2 = await openCached(URL1, { blockSize: BLOCK, dbName }); + const probeFetches = counter.count - coldFetches; // open() probe only + const big2 = await fd2.read(3 * BLOCK, BLOCK + 17); + expect([...big2.slice(0, 32)]).toEqual([...big.slice(0, 32)]); + const tail2 = await fd2.read(100, FILE_SIZE - 100); + expect([...tail2]).toEqual([...tail]); + await fd2.close(); + expect(counter.count - coldFetches - probeFetches).toBe(0); + expect(probeFetches).toBe(1); + }); + + it("a changed validator invalidates the cached blocks", async () => { + const dbName = "fastfile-test-inval"; + await clearDb(dbName); + + const v1 = makeContent(1); + const c1 = { count: 0, bytes: 0 }; + vi.stubGlobal("fetch", serveRanges(v1, "\"v1\"", c1)); + const fd1 = await openCached(URL1, { blockSize: BLOCK, dbName }); + await fd1.read(64, 0); + await fd1.close(); + + // same URL, new content + new ETag + const v2 = makeContent(2); + const c2 = { count: 0, bytes: 0 }; + vi.stubGlobal("fetch", serveRanges(v2, "\"v2\"", c2)); + const fd2 = await openCached(URL1, { blockSize: BLOCK, dbName }); + const got = await fd2.read(64, 0); + await fd2.close(); + expect([...got]).toEqual([...v2.subarray(0, 64)]); + expect(c2.count).toBeGreaterThan(1); // probe + at least one real refetch + }); + + it("LRU eviction drops the least recently opened file, never the current one", async () => { + const dbName = "fastfile-test-evict"; + await clearDb(dbName); + const opts = { blockSize: BLOCK, dbName, maxBytes: 4 * BLOCK }; + + const contentA = makeContent(10); + const cA = { count: 0, bytes: 0 }; + vi.stubGlobal("fetch", serveRanges(contentA, "\"a1\"", cA)); + const fa = await fastFile.readExisting({ type: "http", url: "https://cache.example/a.zkey", cache: opts }); + await fa.read(3 * BLOCK, 0); // caches 3 MiB for A + await fa.close(); + + const contentB = makeContent(20); + const cB = { count: 0, bytes: 0 }; + vi.stubGlobal("fetch", serveRanges(contentB, "\"b1\"", cB)); + const fb = await fastFile.readExisting({ type: "http", url: "https://cache.example/b.zkey", cache: opts }); + await fb.read(3 * BLOCK, 0); // A(3) + B(3) > maxBytes at next open + await fb.close(); + + // reopening B evicts A (older lastUsed) and keeps B warm + const cB2 = { count: 0, bytes: 0 }; + vi.stubGlobal("fetch", serveRanges(contentB, "\"b1\"", cB2)); + const fb2 = await fastFile.readExisting({ type: "http", url: "https://cache.example/b.zkey", cache: opts }); + await fb2.read(3 * BLOCK, 0); + await fb2.close(); + expect(cB2.count).toBe(1); // probe only: B stayed cached + + // A must refetch its data + const cA2 = { count: 0, bytes: 0 }; + vi.stubGlobal("fetch", serveRanges(contentA, "\"a1\"", cA2)); + const fa2 = await fastFile.readExisting({ type: "http", url: "https://cache.example/a.zkey", cache: opts }); + const back = await fa2.read(64, 0); + await fa2.close(); + expect([...back]).toEqual([...contentA.subarray(0, 64)]); + expect(cA2.count).toBeGreaterThan(1); + }); + + it("concurrent cold reads of one block fetch it once (in-flight dedupe)", async () => { + const dbName = "fastfile-test-dedupe"; + await clearDb(dbName); + const content = makeContent(9); + const counter = { count: 0, bytes: 0 }; + vi.stubGlobal("fetch", serveRanges(content, "\"d1\"", counter)); + + const fd = await openCached("https://cache.example/dedupe.zkey", { blockSize: BLOCK, dbName }); + const afterOpen = counter.count; // the probe + // 8 concurrent direct reads inside block 0 (each is a boundary-case + // miss); without dedupe each fetched the whole block independently + const reads = []; + for (let k = 0; k < 8; k++) reads.push(fd.read(128 * 1024, k * 64 * 1024)); + const got = await Promise.all(reads); + for (let k = 0; k < 8; k++) { + expect([...got[k].slice(0, 8)]).toEqual([...content.subarray(k * 64 * 1024, k * 64 * 1024 + 8)]); + } + await fd.close(); + // block 0 once, plus at most one more block touched by the last read + expect(counter.count - afterOpen).toBeLessThanOrEqual(2); + }); + + it("full-download servers: body persists cold, 304 probe serves warm from cache", async () => { + const dbName = "fastfile-test-fulldl"; + await clearDb(dbName); + const content = makeContent(11); + const etag = "\"full-v1\""; + const counter = { count: 0, bytes: 0 }; + // Range-less origin honoring conditional requests: If-None-Match + // match -> bodyless 304; otherwise 200 with the whole body. + const fullServer = (url, opts) => { + counter.count++; + const inm = opts && opts.headers && opts.headers["If-None-Match"]; + if (inm === etag) { + return Promise.resolve({ ok: false, status: 304, headers: new Headers({ etag }), + arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)), body: null }); + } + counter.bytes += content.length; + return Promise.resolve({ ok: true, status: 200, headers: new Headers({ etag }), + arrayBuffer: () => Promise.resolve(content.buffer.slice(0)), body: null }); + }; + vi.stubGlobal("fetch", fullServer); + + const url = "https://cache.example/fulldl.zkey"; + // cold: 200 with the whole body; body gets persisted + const fd = await openCached(url, { blockSize: BLOCK, dbName }); + expect(fd.totalSize).toBe(content.length); + const a = await fd.read(64, 12345); + expect([...a]).toEqual([...content.subarray(12345, 12345 + 64)]); + await fd.close(); + expect(counter.count).toBe(1); + expect(counter.bytes).toBe(content.length); + + // warm: conditional probe -> 304, no body; reads served from IDB + const fd2 = await openCached(url, { blockSize: BLOCK, dbName }); + expect(fd2.totalSize).toBe(content.length); + const b = await fd2.read(3 * BLOCK, BLOCK + 17); + expect([...b.slice(0, 8)]).toEqual([...content.subarray(BLOCK + 17, BLOCK + 25)]); + const tail = await fd2.read(100, FILE_SIZE - 100); + expect([...tail]).toEqual([...content.subarray(FILE_SIZE - 100)]); + await fd2.close(); + expect(counter.count).toBe(2); // just the 304 probe + expect(counter.bytes).toBe(content.length); // no second body + + // changed file: validator mismatch -> fresh 200, new bytes served + const v2 = makeContent(12); + const c2 = { count: 0, bytes: 0 }; + const etag2 = "\"full-v2\""; + vi.stubGlobal("fetch", (u, opts) => { + c2.count++; + const inm = opts && opts.headers && opts.headers["If-None-Match"]; + if (inm === etag2) { + return Promise.resolve({ ok: false, status: 304, headers: new Headers({ etag: etag2 }), + arrayBuffer: () => Promise.resolve(new ArrayBuffer(0)), body: null }); + } + c2.bytes += v2.length; + return Promise.resolve({ ok: true, status: 200, headers: new Headers({ etag: etag2 }), + arrayBuffer: () => Promise.resolve(v2.buffer.slice(0)), body: null }); + }); + const fd3 = await openCached(url, { blockSize: BLOCK, dbName }); + const g = await fd3.read(64, 0); + expect([...g]).toEqual([...v2.subarray(0, 64)]); + await fd3.close(); + expect(c2.bytes).toBe(v2.length); // one fresh body, re-persisted + + // and the new version is warm on the next open + const fd4 = await openCached(url, { blockSize: BLOCK, dbName }); + const h = await fd4.read(64, 0); + expect([...h]).toEqual([...v2.subarray(0, 64)]); + await fd4.close(); + expect(c2.bytes).toBe(v2.length); // still only the one body + }); + + it("without a strong validator the reader works but nothing persists", async () => { + const dbName = "fastfile-test-noval"; + await clearDb(dbName); + const content = makeContent(3); + const counter = { count: 0, bytes: 0 }; + const server = serveRanges(content, "", counter); + vi.stubGlobal("fetch", (url, opts) => server(url, opts).then((r) => { + r.headers = new Headers({ "content-range": r.headers.get("content-range") }); + return r; + })); + + const fd = await openCached("https://cache.example/nova.zkey", { blockSize: BLOCK, dbName }); + const got = await fd.read(64, 0); + expect([...got]).toEqual([...content.subarray(0, 64)]); + await fd.close(); + const cold = counter.count; + + const fd2 = await openCached("https://cache.example/nova.zkey", { blockSize: BLOCK, dbName }); + await fd2.read(64, 0); + await fd2.close(); + expect(counter.count).toBeGreaterThan(cold + 1); // re-fetched: no cache without validator + }); +}); diff --git a/test/memfile.test.js b/test/memfile.test.js index 89c780a..67985db 100644 --- a/test/memfile.test.js +++ b/test/memfile.test.js @@ -1,16 +1,8 @@ import * as testUtils from "./testUtils.js"; import * as fastFile from "../src/fastfile.js"; - -import assert from "assert"; -import * as chai from "chai"; -import chaiAsPromised from "chai-as-promised"; - -chai.use(chaiAsPromised); -const expect = chai.expect; +import { expect } from "vitest"; describe("fastfile testing suite for memfile", function () { - this.timeout(100000); - let str1 = "0123456789"; let str2 = "Hi_there"; let str3 = "/!!--::**"; @@ -29,13 +21,13 @@ describe("fastfile testing suite for memfile", function () { await testUtils.writeStringToFile(fd, str3); let str = await fd.readString(10); - assert.strictEqual(str, str1); + expect(str).toBe(str1); str = await fd.readString(); - assert.strictEqual(str, str2); + expect(str).toBe(str2); str = await fd.readString(); - assert.strictEqual(str, str3); + expect(str).toBe(str3); await fd.close(); }); @@ -47,8 +39,8 @@ describe("fastfile testing suite for memfile", function () { byteLength: 1 } }; - let fd = await fastFile.readExisting(file); - expect(fd.readString(10)).to.be.rejectedWith("Reading out of bounds"); + const fd = await fastFile.readExisting(file); + await expect(fd.readString(10)).rejects.toThrow("Reading out of bounds"); }); }); diff --git a/test/osfile.test.js b/test/osfile.test.js index 855d644..603152d 100644 --- a/test/osfile.test.js +++ b/test/osfile.test.js @@ -1,19 +1,11 @@ import fs from "fs"; import * as testUtils from "./testUtils.js"; import * as fastFile from "../src/fastfile.js"; - -import assert from "assert"; -import * as chai from "chai"; -import chaiAsPromised from "chai-as-promised"; - -chai.use(chaiAsPromised); -const expect = chai.expect; +import { BigBuffer } from "ffjavascript"; +import { expect, assert } from "vitest"; describe("fastfile testing suite for osfile", function () { let fileName = "test_osfile.bin"; - - this.timeout(100000); - let str1 = "0123456789"; let str2 = "Hi_there"; let str3 = "/!!--::**"; @@ -28,29 +20,177 @@ describe("fastfile testing suite for osfile", function () { await testUtils.writeStringToFile(fd, str3); let str = await fd.readString(fd.pageSize - 11); - assert.strictEqual(str, str1); + expect(str).toBe(str1); str = await fd.readString(); - assert.strictEqual(str, str2); + expect(str).toBe(str2); str = await fd.readString(); - assert.strictEqual(str, str3); + expect(str).toBe(str3); await fd.close(); - assert(fs.existsSync(fileName)); - await fs.promises.unlink(fileName); }); it("should throw error when try to read a closed file", async () => { + const fd = await fastFile.createOverride(fileName); + await fd.close(); + await expect(fd.readString(0)).rejects.toThrow("Reading a closing file"); + await fs.promises.unlink(fileName); + }); + + // Regression: the direct-io fast paths hand the buffer to fd.read/fd.write, + // which require a real TypedArray. A BigBuffer (paged, not an ArrayBufferView) + // must fall back to the cached path. A direct read into a BigBuffer used to + // silently leave it unfilled -> corrupted ptau/zkey sections. The read must be + // >= directReadThreshold (1MB) so it would take the direct path if not gated. + it("should read a large section into a BigBuffer (direct-io must not bypass it)", async () => { + const N = 1 << 21; // 2 MB > directReadThreshold + const ref = new Uint8Array(N); + for (let i = 0; i < N; i++) ref[i] = (i * 131 + 7) & 0xff; + let fd = await fastFile.createOverride(fileName); + await fd.write(ref, 0); await fd.close(); - expect(fd.readString(0)).to.be.rejectedWith("Reading a closing file"); + + fd = await fastFile.readExisting(fileName); + expect(ArrayBuffer.isView(new BigBuffer(8))).toBe(false); + const bb = new BigBuffer(N); + await fd.readToBuffer(bb, 0, N, 0); + await fd.close(); + + expect(Buffer.from(bb.slice(0, N)).equals(Buffer.from(ref))).toBe(true); await fs.promises.unlink(fileName); }); -}); + it("should write a large section from a BigBuffer (direct-io must not bypass it)", async () => { + const N = 1 << 21; // 2 MB > directWriteThreshold + const ref = new Uint8Array(N); + for (let i = 0; i < N; i++) ref[i] = (i * 197 + 13) & 0xff; + const bb = new BigBuffer(N); + bb.set(ref, 0); + let fd = await fastFile.createOverride(fileName); + await fd.write(bb, 0); + await fd.close(); + const onDisk = fs.readFileSync(fileName); + expect(onDisk.length).toBe(N); + expect(onDisk.equals(Buffer.from(ref))).toBe(true); + await fs.promises.unlink(fileName); + }); + // Regression: readToBuffer's cached-page path (reads below directReadThreshold, + // i.e. the common case for most zkey/ptau/wtns sections) computed the + // destination-buffer offset as `offset + len - r`, where `r` had already been + // clamped to the EOF-truncated byte count. That put the on-disk bytes at a + // nonzero offset in the output instead of at the start, and left the START of + // the buffer as zeros instead of the tail -- valid bytes silently shifted to + // the wrong position rather than the read cleanly failing or zero-padding + // only the truncated tail. This simulates a corrupted/incomplete file (a + // section header claims more bytes than the file actually has). + it("readToBuffer past EOF on a truncated file places real bytes at the start, not shifted (below directReadThreshold)", async () => { + const declaredLen = 2000; // < directReadThreshold (1MB): exercises the cached-page path + const actualLen = 1000; // how many bytes actually exist past `pos` + const pos = 24; // simulate a section starting after a small header + + let fd = await fastFile.createOverride(fileName); + await fd.write(new Uint8Array(pos + actualLen).fill(0xAB), 0); + await fd.close(); + + assert.strictEqual(fs.statSync(fileName).size, pos + actualLen); + + fd = await fastFile.readExisting(fileName); + const buff = await fd.read(declaredLen, pos); + await fd.close(); + + assert.strictEqual(buff.length, declaredLen); + // The real on-disk bytes must land at the START of the buffer... + for (let i = 0; i < actualLen; i++) { + assert.strictEqual(buff[i], 0xAB, `expected real data at index ${i}`); + } + // ...and only the truncated TAIL (bytes that don't exist on disk) is zero. + for (let i = actualLen; i < declaredLen; i++) { + assert.strictEqual(buff[i], 0, `expected zero padding at index ${i}`); + } + await fs.promises.unlink(fileName); + }); + + // Regression: when a page read failed, only the FIRST waiter was rejected; + // co-readers queued on page.loading hung forever, and the page stayed + // cached with a dead loading list so every future reader of it hung too. + it("a failed page read rejects all waiters and the page recovers", async () => { + fs.writeFileSync(fileName, new Uint8Array(1 << 16).fill(0xab)); + const fd = await fastFile.readExisting(fileName, 1 << 20, 1 << 12); + + const realRead = fd.fd.read.bind(fd.fd); + let failures = 1; + fd.fd.read = (...args) => { + if (failures > 0) { failures--; return Promise.reject(new Error("simulated EIO")); } + return realRead(...args); + }; + + // Two concurrent readers of the same page: both must settle (reject + // or, for the one that arrives after recovery, resolve) -- never hang. + const r1 = await fd.read(64, 0).then(() => "resolved", () => "rejected"); + const r2 = await fd.read(64, 128).then(() => "resolved", () => "rejected"); + assert(r1 === "rejected" || r2 === "rejected", "at least one reader sees the EIO"); + + // A later reader of the same page must succeed (page not poisoned). + const b = await fd.read(64, 0); + assert.strictEqual(b[0], 0xab); + await fd.close(); + await fs.promises.unlink(fileName); + }); + + // Regression: a failed background page flush was only visible at close(); + // page.writing also stayed true, pinning the page. Subsequent writes/reads + // must now fail fast and close() must reject. + it("close() is idempotent: repeated and concurrent closes resolve", async () => { + const fd = await fastFile.createOverride(fileName); + await testUtils.writeStringToFile(fd, str1); + + // concurrent double close: both settle without throwing + await Promise.all([fd.close(), fd.close()]); + // close after close: still a no-op + await fd.close(); + + // I/O after close keeps failing fast + await expect(fd.read(4, 0)).rejects.toThrow("Reading a closing file"); + + await fs.promises.unlink(fileName); + }); + + it("a failing close() rejects every caller with the same error", async () => { + const fd = await fastFile.createOverride(fileName, 1 << 20, 1 << 12); + + const realWrite = fd.fd.write.bind(fd.fd); + fd.fd.write = () => Promise.reject(new Error("simulated ENOSPC")); + await fd.write(new Uint8Array(64).fill(1), 0); // dirty page; flush will fail + + await expect(fd.close()).rejects.toThrow("simulated ENOSPC"); + // the repeated close reports the same failure instead of throwing + // "Closing the file twice" + await expect(fd.close()).rejects.toThrow("simulated ENOSPC"); + + fd.fd.write = realWrite; + await fs.promises.unlink(fileName); + }); + + it("a failed background write surfaces on the next write and on close()", async () => { + const fd = await fastFile.createOverride(fileName, 1 << 20, 1 << 12); + + const realWrite = fd.fd.write.bind(fd.fd); + fd.fd.write = () => Promise.reject(new Error("simulated ENOSPC")); + + await fd.write(new Uint8Array(64).fill(1), 0); // dirty page; background flush fails + await new Promise((resolve) => setTimeout(resolve, 50)); + + await expect(fd.write(new Uint8Array(64).fill(2), 4096)).rejects.toThrow("simulated ENOSPC"); + await expect(fd.close()).rejects.toThrow("simulated ENOSPC"); + + fd.fd.write = realWrite; // let the underlying fd close cleanly + await fs.promises.unlink(fileName); + }); +}); diff --git a/test/size_fallbacks.test.js b/test/size_fallbacks.test.js new file mode 100644 index 0000000..a758377 --- /dev/null +++ b/test/size_fallbacks.test.js @@ -0,0 +1,65 @@ +import fs from "fs"; +import * as fastFile from "../src/fastfile.js"; +import { afterAll, expect } from "vitest"; + +// The b/c (cacheSize/pageSize) arguments of the entry points historically +// applied only to string/Blob sources; a descriptor object without explicit +// sizes silently got the small built-in defaults. They now act as fallbacks +// for object sources too, filling exactly the fields the caller left +// undefined -- so e.g. snarkjs passing {type: "file"} gets the same tuned +// cache as when it passes a plain path string. +describe("cacheSize/pageSize fallbacks for descriptor objects", function () { + const fileName = "test_size_fallbacks.bin"; + + afterAll(() => { + if (fs.existsSync(fileName)) fs.unlinkSync(fileName); + }); + + it("object source picks up the fallback sizes it does not set", async () => { + const fdString = await fastFile.createOverride(fileName, 1 << 22, 1 << 16); + await fdString.write(new Uint8Array(64), 0); + await fdString.close(); + + const fdObject = await fastFile.readExisting({ type: "file", fileName }, 1 << 22, 1 << 16); + const fdPlain = await fastFile.readExisting(fileName, 1 << 22, 1 << 16); + // identical tuning to the string source + expect(fdObject.pageSize).toBe(fdPlain.pageSize); + expect(fdObject.maxPagesLoaded).toBe(fdPlain.maxPagesLoaded); + await fdObject.close(); + await fdPlain.close(); + }); + + it("explicit object fields win over the fallbacks", async () => { + const fd = await fastFile.readExisting( + { type: "file", fileName, cacheSize: 1 << 20, pageSize: 1 << 13 }, 1 << 25, 1 << 16); + const fdRef = await fastFile.readExisting( + { type: "file", fileName, cacheSize: 1 << 20, pageSize: 1 << 13 }); + expect(fd.pageSize).toBe(fdRef.pageSize); + expect(fd.maxPagesLoaded).toBe(fdRef.maxPagesLoaded); + await fd.close(); + await fdRef.close(); + }); + + it("mem descriptors keep their identity (data written through createNew stays on the caller's object)", async () => { + // regression: applying the fallbacks via a copy to a {type: "mem"} + // descriptor detached the memfile store from the caller's object, + // so a later readExisting(sameObject) found no data + const memSrc = { type: "mem" }; + const fdW = await fastFile.createOverride(memSrc, 1 << 22, 1 << 16); + await fdW.write(new Uint8Array([1, 2, 3, 4]), 0); + await fdW.close(); + expect(memSrc.data).toBeInstanceOf(Uint8Array); + const fdR = await fastFile.readExisting(memSrc, 1 << 22, 1 << 16); + const back = await fdR.read(4, 0); + expect(Array.from(back)).toEqual([1, 2, 3, 4]); + await fdR.close(); + }); + + it("does not mutate the caller's descriptor object", async () => { + const src = { type: "file", fileName }; + const fd = await fastFile.readExisting(src, 1 << 22, 1 << 16); + expect(src.cacheSize).toBe(undefined); + expect(src.pageSize).toBe(undefined); + await fd.close(); + }); +}); diff --git a/test/test.js b/test/test.js index 38da971..7195aaa 100644 --- a/test/test.js +++ b/test/test.js @@ -1,4 +1,4 @@ -import assert from "assert"; +import { expect } from "vitest"; import fs from "fs"; import * as fastFile from "../src/fastfile.js"; @@ -20,14 +20,13 @@ async function readBigInt(f, pos) { const n8 = 32; const buff = await f.read(n8, pos); - assert(buff.byteLength == n8); + expect(buff.byteLength).toBe(n8); return Scalar.fromRprLE(buff, 0); } describe("fastfile test", function () { let fileName = "test.bin"; - this.timeout(1000000000); const values = {}; it("should write a big file sequentially", async () => { @@ -43,40 +42,15 @@ describe("fastfile test", function () { }); it("should fail if trying to override", async () => { - let throwed = false; - try { - await fastFile.createNoOverride(fileName); - } catch (err) { - throwed = true; - } - assert(throwed == true); + await expect(fastFile.createNoOverride(fileName)).rejects.toThrow(); }); it("trying to write a readonly File", async () => { - let throwed = false; - try { + await expect(async () => { const f = await fastFile.readExisting(fileName); await writeBigInt(f, Scalar.add(q,3), 0); await f.close(); - } catch (err) { - throwed = true; - } - assert(throwed == true); - }); - - it("should read the file", async () => { - const f = await fastFile.readExisting(fileName); - for (let i=0; i<1000000; i++) { - const n = await readBigInt(f, i*32); - if (Scalar.sub(n, q) != i) { - console.log(f); - console.log(Scalar.sub(n, q)); - console.log(i); - } - assert(Scalar.sub(n, q) == i); - if ((i%100000) == 0) console.log("Reading: " + i); - } - await f.close(); + }).rejects.toThrow(); }); it("should read the file", async () => { @@ -88,7 +62,7 @@ describe("fastfile test", function () { console.log(Scalar.sub(n, q)); console.log(i); } - assert(Scalar.sub(n, q) == i); + expect(Scalar.sub(n, q) == i).toBe(true); if ((i%100000) == 0) console.log("Reading: " + i); } await f.close(); @@ -106,7 +80,7 @@ describe("fastfile test", function () { } else { expectedOldVal = Scalar.add(q,j); } - assert(Scalar.eq(expectedOldVal, oldVal)); + expect(Scalar.eq(expectedOldVal, oldVal)).toBe(true); const newVal = F.random(); values[j] = newVal; // console.log("Start Writing", j); @@ -137,7 +111,7 @@ describe("fastfile test", function () { const vals = await Promise.all(ops); for (let i=0; i resolve(root, ...parts); + +export default defineConfig(({ mode }) => { + const build = mode === "browser" + ? { + lib: { + entry: "./src/fastfile.browser.js", + name: "fastfile", + formats: ["es", "iife"], + fileName: (format) => format === "es" ? "browser.esm.js" : "browser.iife.js", + }, + outDir: "build/browser", + emptyOutDir: true, + } + : { + lib: { + entry: "./src/fastfile.js", + formats: ["cjs"], + fileName: () => "main.cjs", + }, + outDir: "build/node", + emptyOutDir: true, + rollupOptions: { + external: builtinModules, + }, + }; + + return { + build, + test: { + projects: [ + { + // Node — ESM source + test: { + name: "node-esm", + include: ["test/**/*.test.js"], + exclude: ["test/fastfile.browser.test.js", "test/idbcache.browser.test.js"], + environment: "node", + globals: true, + testTimeout: 100_000, + }, + }, + { + // Node — CJS build + test: { + name: "node-cjs", + include: [ + "test/memfile.test.js", + "test/bigmemfile.test.js", + ], + environment: "node", + globals: true, + testTimeout: 100_000, + }, + resolve: { + alias: [ + { + find: abs("src/fastfile.js"), + replacement: abs("build/node/main.cjs"), + }, + ], + }, + }, + { + // Browser — ESM source inside real Chromium via Playwright + test: { + name: "browser-esm", + include: ["test/fastfile.browser.test.js", "test/idbcache.browser.test.js"], + globals: true, + testTimeout: 100_000, + hookTimeout: 100_000, + browser: { + provider: playwright(), + enabled: true, + headless: true, + instances: [{ browser: "chromium" }], + }, + }, + }, + ], + }, + }; +});