diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index fe55d95..d38558d 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -2,15 +2,7 @@ name: Build
on:
push:
branches: [main, next]
- paths-ignore:
- - "**.md"
- - "**.txt"
- - "docs/**"
pull_request:
- paths-ignore:
- - "**.md"
- - "**.txt"
- - "docs/**"
permissions:
contents: read
@@ -23,8 +15,37 @@ env:
CARGO_PROFILE_DEV_DEBUG: 0
jobs:
+ # Pre-flight: detect whether any non-docs files changed. We deliberately
+ # trigger this workflow on EVERY push/PR (no top-level paths-ignore) so
+ # required status checks always report a state — `paths-ignore` at the
+ # workflow level prevents the workflow from running at all on docs-only
+ # PRs, which leaves required checks stuck in "Expected" forever and blocks
+ # merging. Instead, every downstream job gates on this filter's output and
+ # reports `skipped` when there's nothing relevant to build (skipped jobs
+ # satisfy required checks).
+ changes:
+ name: Detect relevant changes
+ runs-on: ubuntu-24.04
+ outputs:
+ non_docs: ${{ steps.filter.outputs.non_docs }}
+ steps:
+ - uses: actions/checkout@v6
+ - uses: dorny/paths-filter@v3
+ id: filter
+ with:
+ # `non_docs` matches whenever a changed file is NOT a doc/text file
+ # under one of the listed paths. Mirror of the previous
+ # `paths-ignore` set.
+ filters: |
+ non_docs:
+ - '!**/*.md'
+ - '!**/*.txt'
+ - '!docs/**'
+
build-wasm:
name: Build Client for Wasm
+ needs: [changes]
+ if: needs.changes.outputs.non_docs == 'true'
runs-on: ubuntu-24.04
env:
# See test.yml's build-web-client-dist-folder for rationale.
diff --git a/.github/workflows/check-publish.yml b/.github/workflows/check-publish.yml
new file mode 100644
index 0000000..de5690e
--- /dev/null
+++ b/.github/workflows/check-publish.yml
@@ -0,0 +1,109 @@
+name: Check Publish
+permissions:
+ contents: read
+on:
+ push:
+ branches: [main, next]
+ paths-ignore:
+ - "**.md"
+ - "**.txt"
+ - "docs/**"
+ pull_request:
+ paths-ignore:
+ - "**.md"
+ - "**.txt"
+ - "docs/**"
+
+concurrency:
+ group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
+ cancel-in-progress: true
+
+env:
+ CARGO_PROFILE_DEV_DEBUG: 0
+ RUST_CACHE_KEY: rust-cache-2026.02.18
+
+jobs:
+ check-publish:
+ # Runs publint + arethetypeswrong (attw) against the three published
+ # packages: @miden-sdk/miden-sdk (WASM web-client), @miden-sdk/react,
+ # @miden-sdk/vite-plugin. Each tool runs against the actual `pnpm pack`
+ # tarball, so the dist/ output must exist first — hence we build all
+ # three packages before invoking the gates.
+ #
+ # WASM build setup mirrors test.yml's build-web-client-dist-folder job
+ # (sccache + rust-cache + binaryen + MIDEN_FAST_BUILD on PRs) so PR CI
+ # stays in the same ballpark.
+ name: publint + attw
+ runs-on: ubuntu-24.04
+ env:
+ SCCACHE_GHA_ENABLED: "true"
+ steps:
+ - uses: actions/checkout@v6
+
+ - name: Install Rust (needed for WASM build)
+ run: |
+ rustup update --no-self-update
+ rustup target add wasm32-unknown-unknown
+
+ # See test.yml for why these are forwarded explicitly.
+ - name: Configure sccache cache backend (v2)
+ uses: actions/github-script@v7
+ with:
+ script: |
+ core.exportVariable('ACTIONS_RESULTS_URL', process.env.ACTIONS_RESULTS_URL || '');
+ core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
+
+ - name: Run sccache
+ uses: mozilla-actions/sccache-action@v0.0.10
+ continue-on-error: true
+ id: sccache-install
+
+ - name: Probe sccache and enable wrapper if healthy
+ if: steps.sccache-install.outcome == 'success' && env.SCCACHE_PATH != ''
+ shell: bash
+ run: |
+ probe_out=$("$SCCACHE_PATH" --start-server 2>&1) && rc=0 || rc=$?
+ echo "$probe_out"
+ if [ $rc -eq 0 ]; then
+ echo "RUSTC_WRAPPER=$SCCACHE_PATH" >> "$GITHUB_ENV"
+ echo "sccache enabled as RUSTC_WRAPPER ($SCCACHE_PATH)"
+ else
+ echo "sccache --start-server failed (rc=$rc); running without wrapper"
+ fi
+
+ - name: Install binaryen (wasm-opt)
+ run: |
+ sudo apt-get update && sudo apt-get install -y binaryen
+
+ - name: Add Rust Cache
+ uses: Swatinem/rust-cache@v2
+ with:
+ shared-key: rust-wasm
+ prefix-key: ${{ env.RUST_CACHE_KEY }}
+ save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next' }}
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v3
+ with:
+ version: 9
+
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: "20"
+ cache: 'pnpm'
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ # MIDEN_FAST_BUILD skips wasm-opt and uses the release-fast cargo
+ # profile. The publint/attw gates inspect the package shape and type
+ # surface, not the optimization level of the WASM blob, so the fast
+ # profile is fine here. The canonical full-optimization build is
+ # exercised by test.yml's verify-release-build job on push.
+ - name: Run check:publish (build + publint + attw)
+ run: MIDEN_FAST_BUILD=true pnpm run check:publish
+
+ - name: Show sccache stats
+ if: always()
+ run: sccache --show-stats || true
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 6140dba..8f27b39 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -54,6 +54,25 @@ jobs:
run: pnpm install --no-frozen-lockfile
- run: make rust-client-ts-lint
+ knip:
+ name: Knip (unused code/deps)
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@v6
+ - name: Install pnpm
+ uses: pnpm/action-setup@v3
+ with:
+ version: 9
+ - name: Install Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: "20"
+ cache: 'pnpm'
+ - name: Install dependencies
+ run: pnpm install --no-frozen-lockfile
+ - name: Knip
+ run: pnpm run check:knip
+
clippy-wasm:
name: Clippy WASM
runs-on: ubuntu-24.04
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000..209e3ef
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+20
diff --git a/.prettierignore b/.prettierignore
index 02a91c0..c1cf928 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -2,5 +2,16 @@
*.yml
*.yaml
+# Build / tooling output
+**/dist/**
+**/target/**
+**/node_modules/**
+
+# Generated TypeScript declarations
+**/*.d.ts
+
# Generated JS (codegen)
crates/idxdb-store/src/js/**
+
+# Vendored docs
+docs/**
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..39021b4
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,127 @@
+# CLAUDE.md — repo notes for AI agents
+
+Conventions and tooling notes for `0xMiden/web-sdk`. End-user docs live in [README.md](README.md); per-package usage guides live alongside the packages (e.g. [`packages/react-sdk/CLAUDE.md`](packages/react-sdk/CLAUDE.md)).
+
+## What this repo is
+
+A pnpm monorepo holding the JS / WASM / React bits previously part of [`0xMiden/miden-client`](https://github.com/0xMiden/miden-client). Five published artifacts:
+
+| Artifact | Path | Registry |
+|---|---|---|
+| `@miden-sdk/miden-sdk` | `crates/web-client/` (Rust + WASM + JS bindings) | npm |
+| `@miden-sdk/react` | `packages/react-sdk/` | npm |
+| `@miden-sdk/vite-plugin` | `packages/vite-plugin/` | npm |
+| `@miden-sdk/node-{darwin-arm64,darwin-x64,linux-x64-gnu}` | `packages/node-sdk-*` | npm (platform-specific native binaries; consumed via `optionalDependencies` on `@miden-sdk/miden-sdk`) |
+| `miden-idxdb-store` | `crates/idxdb-store/` | crates.io |
+
+The `Cargo.toml` workspace dep `miden-client = "x.y.z"` pins compatibility with the upstream Rust crate. Changes to shared types (Account, Note, gRPC schema, …) usually need a coordinated PR in `0xMiden/miden-client` first.
+
+## Toolchain
+
+- **Package manager**: pnpm 9 (workspace at `pnpm-workspace.yaml`). **Never** use `yarn` or `npm install` — they will desync the lockfile.
+- **Node**: ≥ 20 (`engines.node` in `package.json`, `.nvmrc`).
+- **Rust**: stable 1.93 + nightly (for `cargo +nightly fmt`, `clippy`, and `fix`). Pinned in `rust-toolchain.toml`.
+- **Lefthook** runs pre-commit; `pnpm install` wires it via the `prepare` script.
+
+## Build / lint / test
+
+Drive everything through the `Makefile` — never call `cargo fmt` directly (the project requires nightly + an exact prettier/eslint pass that vanilla `cargo fmt` skips).
+
+```bash
+make help # list targets
+
+# Build
+make build-wasm # WASM crates only (wasm32-unknown-unknown)
+make build-web-client # WASM + JS bindings + dist
+make build-react-sdk # everything @miden-sdk/react needs
+
+# Lint + format
+make format # nightly cargo fmt + prettier write + eslint --fix
+make format-check # CI form (no writes)
+make clippy-wasm # clippy for both WASM crates
+make typos-check # spellcheck
+make lint # umbrella: fix-wasm + format + clippy-wasm + typos + checks
+make web-client-check-methods # verifies every WASM method is classified in the JS proxy
+
+# Test
+make test-coverage # all coverage gates (react-sdk + idxdb-store + vite-plugin + web-client unit)
+make test-react-sdk # vitest unit (jsdom)
+make test-web-client-unit # vitest unit (web-client)
+make integration-test-web-client # playwright (chromium); accepts SHARD_PARAMETER
+make integration-test-web-client-webkit
+```
+
+CI (`.github/workflows/test.yml`) runs all of the above on every PR. `main` and `next` warm sccache + Swatinem/rust-cache.
+
+## Coverage thresholds
+
+`packages/react-sdk/vitest.config.ts` enforces `lines / branches / functions / statements ≥ 95`. Two files are excluded because they require the real WASM binary and are covered by Playwright integration tests:
+
+- `src/utils/accountBech32.ts` — covered by `test/accountBech32.test.ts`
+- `src/hooks/useAssetMetadata.ts` — covered by `test/useAssetMetadata.test.ts`
+
+**Always run `make test-react-sdk` locally before pushing** — CI will block the merge if any threshold dips. Lowering thresholds is not the right fix; either add tests or move the file to the excluded list with justification.
+
+## WASM concurrency: `runExclusive`
+
+The wasm-bindgen `WebClient` is **not** safe under concurrent access. Calls that go through it from multiple call sites must serialize via the AsyncLock exposed by `MidenProvider`:
+
+```ts
+const { runExclusive } = useMiden();
+await runExclusive(async (client) => { /* … */ });
+```
+
+Symptom of a violation: `Error: recursive use of an object detected which would lead to unsafe aliasing in rust`. The `crates/web-client/test/sync_lock.test.ts` integration test guards against regressions — if you add a hook that touches the client, route it through `runExclusive` (or one of the existing serialized helpers) or the lock test will fail.
+
+## Eager vs lazy entry points
+
+`@miden-sdk/miden-sdk` ships two entry points with identical APIs but different init behaviour:
+
+| Specifier | When WASM loads | Use when |
+|---|---|---|
+| `@miden-sdk/miden-sdk` | At import (top-level await) | Vite/Webpack browser bundles where TLA is fine |
+| `@miden-sdk/miden-sdk/lazy` | On first `await MidenClient.ready()` (or first awaited SDK method) | SSR (Next.js, Remix, SvelteKit), Capacitor WKWebView hosts, anywhere TLA is unsafe |
+
+Same split applies to `@miden-sdk/react` (`react/lazy` pulls `miden-sdk/lazy`). The eager/lazy contract is guarded by `crates/web-client/test/eager_entry.test.ts` — if you change the public API in one entry, mirror it in the other and re-run the type-check scripts under `crates/web-client/scripts/`.
+
+## Releases
+
+Two long-lived branches:
+
+- **`main`** → npm `latest` dist-tag. Released on GitHub release events.
+- **`next`** → npm `next` dist-tag. Released when a PR merges into `next` carrying the `patch release` label.
+
+Both branches have protection enabled; required status checks mirror across the two.
+
+The release-publish gate compares the local `package.json` version against the **npm registry** (not against the previous git commit) — see `scripts/check-{web-client,react-sdk,vite-plugin}-version-release.sh`. So a release tag publishes whichever of the four packages have versions not yet on npm; bumping a single package is a clean release of just that one.
+
+WASM size is gated at 25 MB in the publish workflow — if `wasm-opt` ever silently fails, the bloated binary never reaches npm.
+
+Crate publishing (`miden-idxdb-store`, `miden-client-web`) goes through `.github/workflows/publish-crates-release.yml` and uses the `CARGO_REGISTRY_TOKEN` org secret.
+
+## Gotchas worth remembering
+
+- **No yarn.** The repo migrated from yarn to pnpm. If you see a doc, comment, or script that says `yarn ...`, it's stale — fix it (or flag it).
+- **Don't chain `pnpm --filter ... -- arg` through npm-script `&&`.** pnpm's argument forwarding only wires through to the LAST command in the chain. The Makefile splits multi-step playwright invocations across explicit Make recipes for this reason; preserve that pattern (see `integration-test-web-client` in `Makefile`).
+- **Test sharding is manually balanced.** `packages/react-sdk/playwright.config.ts` defines four CI shard projects (`ci-shard-1` … `ci-shard-4`) with explicit `testMatch` arrays sized empirically from observed run timings. Rebalance by moving file paths between arrays — no workflow edits needed. Comment block at the top of the config explains the history.
+- **Network-bound tests don't belong in CI.** Anything that hits a live RPC node (testnet/devnet) is excluded. If you add such a test, gate it on an env var and skip by default.
+- **Account ID display.** Hooks accept hex (`0x…`) and bech32 (`mtst1q…`) interchangeably. Bech32 prefix tracks the active network — `mtst1` for testnet/devnet, `mid1` for mainnet (when it lands). Don't hardcode prefixes.
+
+## Cross-repo coordination
+
+| Concern | Repo |
+|---|---|
+| Shared Rust types, gRPC schema, `MidenClient` semantics | [`0xMiden/miden-client`](https://github.com/0xMiden/miden-client) |
+| Account compiler, MASM standard library, base protocol types | [`0xMiden/miden-base`](https://github.com/0xMiden/miden-base) |
+| MidenFi browser-extension wallet adapter | [`0xMiden/miden-wallet-adapter`](https://github.com/0xMiden/miden-wallet-adapter) |
+| Para signer integration | [`0xMiden/miden-para`](https://github.com/0xMiden/miden-para) |
+| Turnkey signer integration | [`0xMiden/miden-turnkey`](https://github.com/0xMiden/miden-turnkey) |
+
+PRs that touch the WASM/JS boundary often need a synchronized PR in miden-client — bump the workspace dep and verify the integration tests still pass.
+
+## Contributing checklist
+
+1. `make lint` clean.
+2. `make test-coverage` clean (and locally verify thresholds before pushing).
+3. For changes to public API: update the relevant per-package CLAUDE.md (e.g. `packages/react-sdk/CLAUDE.md` for hook signatures) and the type-check scripts under `crates/web-client/scripts/`.
+4. For changes to release flow: cross-check both `publish-web-client-release.yml` (latest channel) and `publish-web-client-next.yml` (next channel) — they intentionally mirror each other.
diff --git a/README.md b/README.md
index b7cbb20..3d4419d 100644
--- a/README.md
+++ b/README.md
@@ -111,15 +111,76 @@ export default defineConfig({
## Eager vs lazy entry points
-The SDK ships with two parallel entry points so you can trade WASM init time against bundle reach:
+The SDK ships with two parallel entry points with an identical public API. They differ only in **when** the WASM module is initialized:
| Specifier | When it loads WASM | Use this when |
|---|---|---|
-| `@miden-sdk/miden-sdk` | At import (top-level await) | Server-rendered apps where the user will definitely use the SDK |
-| `@miden-sdk/miden-sdk/lazy` | Deferred until first method call | Apps where most users never touch crypto (multi-megabyte WASM stays uncached until needed) |
+| `@miden-sdk/miden-sdk` | At import (top-level `await`) | Plain browser apps with a synchronous bundler (Vite, CRA, esbuild, Webpack client bundles). After `import` resolves, every wasm-bindgen constructor (`new Felt(…)`, `AccountId.fromHex(…)`, `TransactionProver.newLocalProver()`, etc.) is safe to call synchronously — no `await MidenClient.ready()` needed. |
+| `@miden-sdk/miden-sdk/lazy` | Only when you ask — via `await MidenClient.ready()`, or implicitly the first time you `await` an SDK method that needs WASM | Anywhere top-level `await` is unsafe or you want to control when to pay the WASM-init cost: **server-side rendering** (Next.js, Remix, SvelteKit), **Capacitor WKWebView hosts** (the iOS/Android scheme handler hangs on TLA), and any code path where you want to defer the multi-megabyte WASM download until the user actually performs a crypto-touching action. |
+
+### Using the lazy entry: `await MidenClient.ready()` first
+
+The lazy entry runs no top-level `await`, so **until you await initialization, every wasm-bindgen type is just a stub**. Calling `new Felt(…)` or `AccountId.fromHex(…)` before WASM is ready throws `TypeError: Cannot read properties of undefined`.
+
+The contract is:
+
+```typescript
+import { MidenClient, AccountId, Felt } from "@miden-sdk/miden-sdk/lazy";
+
+// Stubs — DO NOT touch wasm-bindgen types here:
+// const id = AccountId.fromHex("0x…"); // ❌ throws
+
+// Initialize WASM exactly once (idempotent + concurrency-safe):
+await MidenClient.ready();
+
+// Now everything is real and synchronous:
+const id = AccountId.fromHex("0x…"); // ✓
+const felt = new Felt(42n); // ✓
+```
+
+`MidenClient.ready()` is idempotent: concurrent callers share the same in-flight promise, and post-init callers resolve immediately from cache. Call it from `MidenProvider`, route loaders, button handlers — wherever the first WASM use is guarded.
+
+You only need to call it explicitly when you're constructing wasm-bindgen types yourself. **Async SDK methods** (`client.accounts.create()`, `client.transactions.send()`, `MidenClient.createTestnet()`, etc.) await initialization internally, so importing them and calling them is enough — the first call transparently triggers WASM load.
The same split applies to `@miden-sdk/react`. The choice cascades: if you use `@miden-sdk/react/lazy`, it pulls `@miden-sdk/miden-sdk/lazy` automatically; the eager variant pulls eager.
+### React: gating on `isReady` from `useMiden()`
+
+The React SDK hides the `MidenClient.ready()` plumbing behind `MidenProvider` — you don't call `ready()` yourself. Instead, the provider initializes WASM (lazily on the `/lazy` entry, eagerly on the default), and exposes the readiness state through `useMiden()`:
+
+```tsx
+import { MidenProvider, useMiden } from "@miden-sdk/react/lazy";
+
+function App() {
+ return (
+
+
+
+ );
+}
+
+function Wallet() {
+ const { isReady, isInitializing, error } = useMiden();
+ if (error) return
Failed to load: {error.message}
;
+ if (!isReady) return Loading wallet…
;
+ // SDK is initialized — safe to call hooks that touch WASM
+ return ;
+}
+```
+
+`useMiden()` returns:
+
+| Field | Type | Meaning |
+| ---------------- | ----------------- | ---------------------------------------------------------------------- |
+| `isInitializing` | `boolean` | WASM and client are being loaded. Show a loading UI. |
+| `isReady` | `boolean` | Client is ready. SDK hooks (`useAccount`, `useSend`, …) are safe to use. |
+| `error` | `Error \| null` | Initialization failed (network, WASM load, etc.). Show an error UI. |
+| `client` | `WebClient \| null` | The underlying client, populated once `isReady === true`. |
+
+For zero-glue UI, pass `loadingComponent` and `errorComponent` (or `(err) => ReactNode`) props to `MidenProvider` — the provider renders them in place of children until the SDK is ready, and you can drop the `isReady` check in consumer hooks.
+
+The other SDK hooks (`useCreateWallet`, `useSend`, `useNotes`, etc.) all gate on `isReady` internally and return their own `isLoading` / `error` states, so you don't need to chain readiness checks through every component once you've gated at the top.
+
---
## Architecture
diff --git a/crates/idxdb-store/src/package.json b/crates/idxdb-store/src/package.json
index 47a173e..3d8ca61 100644
--- a/crates/idxdb-store/src/package.json
+++ b/crates/idxdb-store/src/package.json
@@ -15,7 +15,6 @@
},
"devDependencies": {
"@eslint/js": "^9.33.0",
- "@typescript-eslint/eslint-plugin": "^8.39.1",
"@types/semver": "^7.5.8",
"@vitest/coverage-v8": "^3.0.0",
"eslint": "^9.33.0",
diff --git a/crates/idxdb-store/src/ts/accounts.test.ts b/crates/idxdb-store/src/ts/accounts.test.ts
index de644ba..f865551 100644
--- a/crates/idxdb-store/src/ts/accounts.test.ts
+++ b/crates/idxdb-store/src/ts/accounts.test.ts
@@ -1,129 +1,1341 @@
-import { describe, it, expect } from "vitest";
+import { describe, it, expect, afterEach } from "vitest";
import { openDatabase, getDatabase } from "./schema.js";
-import { applyTransactionDelta, undoAccountStates } from "./accounts.js";
+import {
+ getAccountIds,
+ getAllAccountHeaders,
+ getAccountHeader,
+ getAccountHeaderByCommitment,
+ getAccountCode,
+ getAccountStorage,
+ getAccountStorageMaps,
+ getAccountVaultAssets,
+ getAccountAddresses,
+ upsertAccountCode,
+ upsertAccountStorage,
+ upsertStorageMapEntries,
+ upsertVaultAssets,
+ applyTransactionDelta,
+ applyFullAccountState,
+ upsertAccountRecord,
+ insertAccountAddress,
+ removeAccountAddress,
+ upsertForeignAccountCode,
+ getForeignAccountCode,
+ lockAccount,
+ pruneAccountHistory,
+ undoAccountStates,
+} from "./accounts.js";
import { uniqueDbName } from "./test-utils.js";
-describe("Account delta and undo operations", () => {
- // Use a consistent version so ensureClientVersion doesn't nuke the DB.
+// Track opened DB IDs for per-test cleanup.
+const openDbIds: string[] = [];
+
+afterEach(async () => {
+ for (const dbId of openDbIds) {
+ const db = getDatabase(dbId);
+ db.dexie.close();
+ await db.dexie.delete();
+ }
+ openDbIds.length = 0;
+});
+
+async function openTestDb(version = "0.1.0"): Promise {
+ const name = uniqueDbName();
+ await openDatabase(name, version);
+ openDbIds.push(name);
+ return name;
+}
+
+// ============================================================
+// Test data helpers
+// ============================================================
+const ACC = "0xacc1";
+const CODE_ROOT = "0xcode1";
+const STORAGE_ROOT = "0xsroot1";
+const VAULT_ROOT = "0xvroot1";
+const COMMITMENT = "0xcommit1";
+const NONCE = "1";
+
+async function seedAccount(
+ dbId: string,
+ opts: {
+ accountId?: string;
+ codeRoot?: string;
+ storageRoot?: string;
+ vaultRoot?: string;
+ nonce?: string;
+ committed?: boolean;
+ commitment?: string;
+ seed?: Uint8Array;
+ } = {}
+) {
+ const id = opts.accountId ?? ACC;
+ await upsertAccountRecord(
+ dbId,
+ id,
+ opts.codeRoot ?? CODE_ROOT,
+ opts.storageRoot ?? STORAGE_ROOT,
+ opts.vaultRoot ?? VAULT_ROOT,
+ opts.nonce ?? NONCE,
+ opts.committed ?? false,
+ opts.commitment ?? COMMITMENT,
+ opts.seed
+ );
+ return id;
+}
+
+// ============================================================
+// getAccountIds
+// ============================================================
+describe("getAccountIds", () => {
+ it("returns empty array when no accounts exist", async () => {
+ const dbId = await openTestDb();
+ const ids = await getAccountIds(dbId);
+ expect(ids).toEqual([]);
+ });
+
+ it("returns all account ids", async () => {
+ const dbId = await openTestDb();
+ await seedAccount(dbId, { accountId: "0xacc1" });
+ await seedAccount(dbId, { accountId: "0xacc2", commitment: "0xcommit2" });
+ const ids = await getAccountIds(dbId);
+ expect(ids).toHaveLength(2);
+ expect(ids).toContain("0xacc1");
+ expect(ids).toContain("0xacc2");
+ });
+});
+
+// ============================================================
+// getAllAccountHeaders
+// ============================================================
+describe("getAllAccountHeaders", () => {
+ it("returns empty array when no accounts", async () => {
+ const dbId = await openTestDb();
+ const headers = await getAllAccountHeaders(dbId);
+ expect(headers).toEqual([]);
+ });
+
+ it("returns mapped headers including optional fields", async () => {
+ const dbId = await openTestDb();
+ await seedAccount(dbId, {
+ seed: new Uint8Array([1, 2, 3]),
+ committed: true,
+ });
+ const headers = await getAllAccountHeaders(dbId);
+ expect(headers).toHaveLength(1);
+ const h = headers![0];
+ expect(h.id).toBe(ACC);
+ expect(h.codeRoot).toBe(CODE_ROOT);
+ expect(h.storageRoot).toBe(STORAGE_ROOT);
+ expect(h.vaultRoot).toBe(VAULT_ROOT);
+ expect(h.nonce).toBe(NONCE);
+ expect(h.committed).toBe(true);
+ // seed was provided — should be base64 encoded
+ expect(typeof h.accountSeed).toBe("string");
+ expect(h.locked).toBe(false);
+ });
+
+ it("handles undefined accountSeed gracefully", async () => {
+ const dbId = await openTestDb();
+ await seedAccount(dbId); // no seed
+ const headers = await getAllAccountHeaders(dbId);
+ expect(headers![0].accountSeed).toBeUndefined();
+ });
+});
+
+// ============================================================
+// getAccountHeader
+// ============================================================
+describe("getAccountHeader", () => {
+ it("returns null when account does not exist", async () => {
+ const dbId = await openTestDb();
+ const result = await getAccountHeader(dbId, "nonexistent");
+ expect(result).toBeNull();
+ });
+
+ it("returns the correct account header", async () => {
+ const dbId = await openTestDb();
+ await seedAccount(dbId);
+ const result = await getAccountHeader(dbId, ACC);
+ expect(result).not.toBeNull();
+ expect(result!.id).toBe(ACC);
+ expect(result!.codeRoot).toBe(CODE_ROOT);
+ expect(result!.storageRoot).toBe(STORAGE_ROOT);
+ expect(result!.vaultRoot).toBe(VAULT_ROOT);
+ expect(result!.nonce).toBe(NONCE);
+ expect(result!.locked).toBe(false);
+ });
+});
+
+// ============================================================
+// getAccountHeaderByCommitment
+// ============================================================
+describe("getAccountHeaderByCommitment", () => {
+ it("returns undefined when no matching commitment", async () => {
+ const dbId = await openTestDb();
+ const result = await getAccountHeaderByCommitment(dbId, "nonexistent");
+ expect(result).toBeUndefined();
+ });
+
+ it("returns historical header by commitment", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+ // Seed a historical record directly
+ await db.historicalAccountHeaders.put({
+ id: ACC,
+ replacedAtNonce: "1",
+ codeRoot: CODE_ROOT,
+ storageRoot: STORAGE_ROOT,
+ vaultRoot: VAULT_ROOT,
+ nonce: "0",
+ committed: false,
+ accountSeed: undefined,
+ accountCommitment: "0xoldcommit",
+ locked: false,
+ });
+ const result = await getAccountHeaderByCommitment(dbId, "0xoldcommit");
+ expect(result).toBeDefined();
+ expect(result!.id).toBe(ACC);
+ expect(result!.nonce).toBe("0");
+ });
+});
+
+// ============================================================
+// getAccountCode
+// ============================================================
+describe("getAccountCode", () => {
+ it("returns null when no code found", async () => {
+ const dbId = await openTestDb();
+ const result = await getAccountCode(dbId, "nonexistent");
+ expect(result).toBeNull();
+ });
+
+ it("returns base64-encoded code", async () => {
+ const dbId = await openTestDb();
+ const code = new Uint8Array([10, 20, 30]);
+ await upsertAccountCode(dbId, CODE_ROOT, code);
+ const result = await getAccountCode(dbId, CODE_ROOT);
+ expect(result).not.toBeNull();
+ expect(result!.root).toBe(CODE_ROOT);
+ // Verify it's base64-encoded
+ expect(typeof result!.code).toBe("string");
+ const decoded = Uint8Array.from(atob(result!.code), (c) => c.charCodeAt(0));
+ expect(decoded).toEqual(code);
+ });
+});
+
+// ============================================================
+// upsertAccountCode
+// ============================================================
+describe("upsertAccountCode", () => {
+ it("inserts code and overwrites on re-insert", async () => {
+ const dbId = await openTestDb();
+ await upsertAccountCode(dbId, CODE_ROOT, new Uint8Array([1, 2, 3]));
+ await upsertAccountCode(dbId, CODE_ROOT, new Uint8Array([4, 5, 6]));
+ const db = getDatabase(dbId);
+ const record = await db.accountCodes.get(CODE_ROOT);
+ expect(record!.code).toEqual(new Uint8Array([4, 5, 6]));
+ });
+});
+
+// ============================================================
+// getAccountStorage / upsertAccountStorage
+// ============================================================
+describe("getAccountStorage", () => {
+ it("returns empty array when no storage", async () => {
+ const dbId = await openTestDb();
+ const result = await getAccountStorage(dbId, ACC, []);
+ expect(result).toEqual([]);
+ });
+
+ it("returns all storage slots when no filter", async () => {
+ const dbId = await openTestDb();
+ await upsertAccountStorage(dbId, ACC, [
+ { slotName: "slot1", slotValue: "0xval1", slotType: 0 },
+ { slotName: "slot2", slotValue: "0xval2", slotType: 1 },
+ ]);
+ const result = await getAccountStorage(dbId, ACC, []);
+ expect(result).toHaveLength(2);
+ });
+
+ it("filters by slotNames when provided", async () => {
+ const dbId = await openTestDb();
+ await upsertAccountStorage(dbId, ACC, [
+ { slotName: "slot1", slotValue: "0xval1", slotType: 0 },
+ { slotName: "slot2", slotValue: "0xval2", slotType: 1 },
+ { slotName: "slot3", slotValue: "0xval3", slotType: 0 },
+ ]);
+ const result = await getAccountStorage(dbId, ACC, ["slot1", "slot3"]);
+ expect(result).toHaveLength(2);
+ const names = result!.map((r) => r.slotName);
+ expect(names).toContain("slot1");
+ expect(names).toContain("slot3");
+ });
+
+ it("replaces existing slots on re-upsert", async () => {
+ const dbId = await openTestDb();
+ await upsertAccountStorage(dbId, ACC, [
+ { slotName: "slot1", slotValue: "0xold", slotType: 0 },
+ ]);
+ await upsertAccountStorage(dbId, ACC, [
+ { slotName: "slot1", slotValue: "0xnew", slotType: 0 },
+ ]);
+ const result = await getAccountStorage(dbId, ACC, []);
+ expect(result![0].slotValue).toBe("0xnew");
+ });
+
+ it("handles empty newSlots (clears storage)", async () => {
+ const dbId = await openTestDb();
+ await upsertAccountStorage(dbId, ACC, [
+ { slotName: "slot1", slotValue: "0xval", slotType: 0 },
+ ]);
+ await upsertAccountStorage(dbId, ACC, []);
+ const result = await getAccountStorage(dbId, ACC, []);
+ expect(result).toHaveLength(0);
+ });
+});
+
+// ============================================================
+// getAccountStorageMaps / upsertStorageMapEntries
+// ============================================================
+describe("getAccountStorageMaps", () => {
+ it("returns empty when no map entries", async () => {
+ const dbId = await openTestDb();
+ const result = await getAccountStorageMaps(dbId, ACC);
+ expect(result).toEqual([]);
+ });
+
+ it("returns all map entries for account", async () => {
+ const dbId = await openTestDb();
+ await upsertStorageMapEntries(dbId, ACC, [
+ { slotName: "map1", key: "k1", value: "v1" },
+ { slotName: "map1", key: "k2", value: "v2" },
+ ]);
+ const result = await getAccountStorageMaps(dbId, ACC);
+ expect(result).toHaveLength(2);
+ });
+
+ it("replaces entries on re-upsert", async () => {
+ const dbId = await openTestDb();
+ await upsertStorageMapEntries(dbId, ACC, [
+ { slotName: "map1", key: "k1", value: "v1" },
+ ]);
+ await upsertStorageMapEntries(dbId, ACC, [
+ { slotName: "map1", key: "k1", value: "v2" },
+ ]);
+ const result = await getAccountStorageMaps(dbId, ACC);
+ expect(result![0].value).toBe("v2");
+ });
+
+ it("handles empty entries (clears maps)", async () => {
+ const dbId = await openTestDb();
+ await upsertStorageMapEntries(dbId, ACC, [
+ { slotName: "map1", key: "k1", value: "v1" },
+ ]);
+ await upsertStorageMapEntries(dbId, ACC, []);
+ const result = await getAccountStorageMaps(dbId, ACC);
+ expect(result).toHaveLength(0);
+ });
+});
+
+// ============================================================
+// getAccountVaultAssets / upsertVaultAssets
+// ============================================================
+describe("getAccountVaultAssets", () => {
+ it("returns empty when no assets", async () => {
+ const dbId = await openTestDb();
+ const result = await getAccountVaultAssets(dbId, ACC, []);
+ expect(result).toEqual([]);
+ });
+
+ it("returns all assets when no filter", async () => {
+ const dbId = await openTestDb();
+ await upsertVaultAssets(dbId, ACC, [
+ { vaultKey: "vk1", asset: "0xasset1" },
+ { vaultKey: "vk2", asset: "0xasset2" },
+ ]);
+ const result = await getAccountVaultAssets(dbId, ACC, []);
+ expect(result).toHaveLength(2);
+ });
+
+ it("filters by vaultKeys when provided", async () => {
+ const dbId = await openTestDb();
+ await upsertVaultAssets(dbId, ACC, [
+ { vaultKey: "vk1", asset: "0xasset1" },
+ { vaultKey: "vk2", asset: "0xasset2" },
+ { vaultKey: "vk3", asset: "0xasset3" },
+ ]);
+ const result = await getAccountVaultAssets(dbId, ACC, ["vk1", "vk3"]);
+ expect(result).toHaveLength(2);
+ const keys = result!.map((r) => r.vaultKey);
+ expect(keys).toContain("vk1");
+ expect(keys).toContain("vk3");
+ });
+
+ it("handles empty assets (clears vault)", async () => {
+ const dbId = await openTestDb();
+ await upsertVaultAssets(dbId, ACC, [
+ { vaultKey: "vk1", asset: "0xasset1" },
+ ]);
+ await upsertVaultAssets(dbId, ACC, []);
+ const result = await getAccountVaultAssets(dbId, ACC, []);
+ expect(result).toHaveLength(0);
+ });
+});
+
+// ============================================================
+// getAccountAddresses / insertAccountAddress / removeAccountAddress
+// ============================================================
+describe("addresses", () => {
+ it("returns empty array when no addresses", async () => {
+ const dbId = await openTestDb();
+ const result = await getAccountAddresses(dbId, ACC);
+ expect(result).toEqual([]);
+ });
+
+ it("inserts and retrieves an address", async () => {
+ const dbId = await openTestDb();
+ const addr = new Uint8Array([0xaa, 0xbb, 0xcc]);
+ await insertAccountAddress(dbId, ACC, addr);
+ const result = await getAccountAddresses(dbId, ACC);
+ expect(result).toHaveLength(1);
+ });
+
+ it("removes an address", async () => {
+ const dbId = await openTestDb();
+ const addr = new Uint8Array([0xaa, 0xbb]);
+ await insertAccountAddress(dbId, ACC, addr);
+ await removeAccountAddress(dbId, addr);
+ const result = await getAccountAddresses(dbId, ACC);
+ expect(result).toEqual([]);
+ });
+});
+
+// ============================================================
+// upsertAccountRecord
+// ============================================================
+describe("upsertAccountRecord", () => {
+ it("inserts account and can be retrieved via getAccountHeader", async () => {
+ const dbId = await openTestDb();
+ await upsertAccountRecord(
+ dbId,
+ ACC,
+ CODE_ROOT,
+ STORAGE_ROOT,
+ VAULT_ROOT,
+ NONCE,
+ false,
+ COMMITMENT,
+ undefined
+ );
+ const header = await getAccountHeader(dbId, ACC);
+ expect(header).not.toBeNull();
+ expect(header!.id).toBe(ACC);
+ });
+
+ it("overwrites existing account on re-upsert", async () => {
+ const dbId = await openTestDb();
+ await seedAccount(dbId);
+ await upsertAccountRecord(
+ dbId,
+ ACC,
+ CODE_ROOT,
+ "0xnewsroot",
+ VAULT_ROOT,
+ "2",
+ true,
+ "0xnewcommit",
+ undefined
+ );
+ const header = await getAccountHeader(dbId, ACC);
+ expect(header!.nonce).toBe("2");
+ expect(header!.storageRoot).toBe("0xnewsroot");
+ });
+});
+
+// ============================================================
+// applyTransactionDelta
+// ============================================================
+describe("applyTransactionDelta", () => {
const CLIENT_VERSION = "0.0.1";
- // The JS layer doesn't validate data formats — all validation happens in the
- // Rust layer before values reach IndexedDB. So we use short readable strings
- // here instead of real-length hex values.
- const ACCOUNT_ID = "0xacc1";
- const CODE_ROOT = "0xcode1";
-
- // Nonce "1" state
- const STORAGE_ROOT_N1 = "0xsroot1";
- const VAULT_ROOT_N1 = "0xvroot1";
- const COMMITMENT_N1 = "0xcommit1";
- const SLOT_VALUE_N1 = "0xbal100";
- const MAP_VALUE_N1 = "0xmval1";
- const ASSET_N1 = "0xasset1";
-
- // Nonce "2" state
- const STORAGE_ROOT_N2 = "0xsroot2";
- const VAULT_ROOT_N2 = "0xvroot2";
- const COMMITMENT_N2 = "0xcommit2";
- const SLOT_VALUE_N2 = "0xbal200";
- const MAP_VALUE_N2 = "0xmval2";
- const ASSET_N2 = "0xasset2";
-
- // Shared keys
- const SLOT_NAME = "balance";
- const MAP_SLOT_NAME = "metadata";
- const MAP_KEY = "0xmkey1";
- const VAULT_KEY = "0xvk1";
+ it("creates initial account state when no prior state exists", async () => {
+ const dbId = await openTestDb(CLIENT_VERSION);
+ const db = getDatabase(dbId);
- it("undo restores previous account state", async () => {
- const dbId = uniqueDbName();
- await openDatabase(dbId, CLIENT_VERSION);
+ await applyTransactionDelta(
+ dbId,
+ ACC,
+ "1",
+ [{ slotName: "slot1", slotValue: "0xval1", slotType: 0 }],
+ [{ slotName: "map1", key: "k1", value: "v1" }],
+ [{ vaultKey: "vk1", asset: "0xasset1" }],
+ CODE_ROOT,
+ STORAGE_ROOT,
+ VAULT_ROOT,
+ false,
+ COMMITMENT
+ );
+
+ const header = await db.latestAccountHeaders
+ .where("id")
+ .equals(ACC)
+ .first();
+ expect(header?.nonce).toBe("1");
+ expect(header?.storageRoot).toBe(STORAGE_ROOT);
+
+ const slots = await db.latestAccountStorages
+ .where("accountId")
+ .equals(ACC)
+ .toArray();
+ expect(slots).toHaveLength(1);
+ expect(slots[0].slotValue).toBe("0xval1");
+
+ const maps = await db.latestStorageMapEntries
+ .where("accountId")
+ .equals(ACC)
+ .toArray();
+ expect(maps).toHaveLength(1);
+ expect(maps[0].value).toBe("v1");
+
+ const assets = await db.latestAccountAssets
+ .where("accountId")
+ .equals(ACC)
+ .toArray();
+ expect(assets).toHaveLength(1);
+ expect(assets[0].asset).toBe("0xasset1");
+ });
+
+ it("archives old state and updates to new state", async () => {
+ const dbId = await openTestDb(CLIENT_VERSION);
const db = getDatabase(dbId);
- // Apply nonce "1" — initial account state
+ // First delta: initial state
await applyTransactionDelta(
dbId,
- ACCOUNT_ID, // accountId
- "1", // nonce
- [{ slotName: SLOT_NAME, slotValue: SLOT_VALUE_N1, slotType: 0 }], // updatedSlots
- [{ slotName: MAP_SLOT_NAME, key: MAP_KEY, value: MAP_VALUE_N1 }], // changedMapEntries
- [{ vaultKey: VAULT_KEY, asset: ASSET_N1 }], // changedAssets
- CODE_ROOT, // codeRoot
- STORAGE_ROOT_N1, // storageRoot
- VAULT_ROOT_N1, // vaultRoot
- false, // committed
- COMMITMENT_N1 // commitment
+ ACC,
+ "1",
+ [{ slotName: "slot1", slotValue: "0xval1", slotType: 0 }],
+ [{ slotName: "map1", key: "k1", value: "v1" }],
+ [{ vaultKey: "vk1", asset: "0xasset1" }],
+ CODE_ROOT,
+ STORAGE_ROOT,
+ VAULT_ROOT,
+ false,
+ COMMITMENT
);
- // Apply nonce "2" — updated account state with changed values
+ // Second delta: update
await applyTransactionDelta(
dbId,
- ACCOUNT_ID, // accountId
- "2", // nonce
- [{ slotName: SLOT_NAME, slotValue: SLOT_VALUE_N2, slotType: 0 }], // updatedSlots
- [{ slotName: MAP_SLOT_NAME, key: MAP_KEY, value: MAP_VALUE_N2 }], // changedMapEntries
- [{ vaultKey: VAULT_KEY, asset: ASSET_N2 }], // changedAssets
- CODE_ROOT, // codeRoot
- STORAGE_ROOT_N2, // storageRoot
- VAULT_ROOT_N2, // vaultRoot
- false, // committed
- COMMITMENT_N2 // commitment
+ ACC,
+ "2",
+ [{ slotName: "slot1", slotValue: "0xval2", slotType: 0 }],
+ [{ slotName: "map1", key: "k1", value: "" }], // empty string = removal
+ [{ vaultKey: "vk1", asset: "" }], // empty string = removal
+ CODE_ROOT,
+ "0xsroot2",
+ "0xvroot2",
+ false,
+ "0xcommit2"
);
- // Verify latest shows nonce "2" state
- const beforeUndo = await db.latestAccountHeaders
+ // Latest should reflect nonce 2
+ const header = await db.latestAccountHeaders
.where("id")
- .equals(ACCOUNT_ID)
+ .equals(ACC)
.first();
- expect(beforeUndo?.nonce).toBe("2");
- expect(beforeUndo?.storageRoot).toBe(STORAGE_ROOT_N2);
+ expect(header?.nonce).toBe("2");
+
+ // Storage updated
+ const slots = await db.latestAccountStorages
+ .where("accountId")
+ .equals(ACC)
+ .toArray();
+ expect(slots[0].slotValue).toBe("0xval2");
- // Undo nonce "2" — should restore nonce "1" as the latest state
- await undoAccountStates(dbId, [COMMITMENT_N2]);
+ // Map entry removed (empty string = deletion)
+ const maps = await db.latestStorageMapEntries
+ .where("accountId")
+ .equals(ACC)
+ .toArray();
+ expect(maps).toHaveLength(0);
- // Validation: Check that latest state now shows the initial account state
+ // Asset removed
+ const assets = await db.latestAccountAssets
+ .where("accountId")
+ .equals(ACC)
+ .toArray();
+ expect(assets).toHaveLength(0);
- const afterUndo = await db.latestAccountHeaders
+ // Historical should have the old state
+ const histHeaders = await db.historicalAccountHeaders
.where("id")
- .equals(ACCOUNT_ID)
+ .equals(ACC)
+ .toArray();
+ expect(histHeaders.length).toBeGreaterThan(0);
+ });
+});
+
+// ============================================================
+// applyFullAccountState
+// ============================================================
+describe("applyFullAccountState", () => {
+ it("replaces full account state and archives prior", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+
+ // Seed initial state
+ await seedAccount(dbId);
+ await upsertAccountStorage(dbId, ACC, [
+ { slotName: "slot1", slotValue: "0xold", slotType: 0 },
+ ]);
+ await upsertStorageMapEntries(dbId, ACC, [
+ { slotName: "map1", key: "k1", value: "vold" },
+ ]);
+ await upsertVaultAssets(dbId, ACC, [
+ { vaultKey: "vk1", asset: "0xoldasset" },
+ ]);
+
+ // Apply full state
+ await applyFullAccountState(dbId, {
+ accountId: ACC,
+ nonce: "2",
+ storageSlots: [{ slotName: "slot1", slotValue: "0xnew", slotType: 0 }],
+ storageMapEntries: [{ slotName: "map1", key: "k1", value: "vnew" }],
+ assets: [{ vaultKey: "vk1", asset: "0xnewasset" }],
+ codeRoot: CODE_ROOT,
+ storageRoot: "0xsroot2",
+ vaultRoot: "0xvroot2",
+ committed: true,
+ accountCommitment: "0xnewcommit",
+ accountSeed: undefined,
+ });
+
+ const header = await db.latestAccountHeaders
+ .where("id")
+ .equals(ACC)
.first();
- expect(afterUndo).toBeDefined();
- expect(afterUndo?.nonce).toBe("1");
- expect(afterUndo?.storageRoot).toBe(STORAGE_ROOT_N1);
- expect(afterUndo?.vaultRoot).toBe(VAULT_ROOT_N1);
+ expect(header?.nonce).toBe("2");
+ expect(header?.committed).toBe(true);
- // Storage
- const latestStorage = await db.latestAccountStorages
+ const slots = await db.latestAccountStorages
.where("accountId")
- .equals(ACCOUNT_ID)
+ .equals(ACC)
.toArray();
- expect(latestStorage).toHaveLength(1);
- expect(latestStorage[0].slotValue).toBe(SLOT_VALUE_N1);
+ expect(slots[0].slotValue).toBe("0xnew");
- // Map entries
- const latestMaps = await db.latestStorageMapEntries
+ const maps = await db.latestStorageMapEntries
.where("accountId")
- .equals(ACCOUNT_ID)
+ .equals(ACC)
.toArray();
- expect(latestMaps).toHaveLength(1);
- expect(latestMaps[0].value).toBe(MAP_VALUE_N1);
+ expect(maps[0].value).toBe("vnew");
- // Assets
- const latestAssets = await db.latestAccountAssets
+ const assets = await db.latestAccountAssets
.where("accountId")
- .equals(ACCOUNT_ID)
+ .equals(ACC)
.toArray();
- expect(latestAssets).toHaveLength(1);
- expect(latestAssets[0].asset).toBe(ASSET_N1);
+ expect(assets[0].asset).toBe("0xnewasset");
+ });
+
+ it("applies full state when no existing header (no-history branch)", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+
+ // No prior state for account
+ await applyFullAccountState(dbId, {
+ accountId: "0xbrand-new",
+ nonce: "1",
+ storageSlots: [],
+ storageMapEntries: [],
+ assets: [],
+ codeRoot: "0xcodeNew",
+ storageRoot: "0xsrootNew",
+ vaultRoot: "0xvrootNew",
+ committed: false,
+ accountCommitment: "0xcommitNew",
+ accountSeed: new Uint8Array([5, 6, 7]),
+ });
- // Historical headers should be empty: undoAccountStates consumes the
- // nonce-1 historical entry when restoring it back to the latest table.
- // (Pre-next behavior was to retain the historical row alongside the
- // restored latest; the next-branch logic moves rather than copies.)
- const historicalHeaders = await db.historicalAccountHeaders
+ const header = await db.latestAccountHeaders
.where("id")
- .equals(ACCOUNT_ID)
+ .equals("0xbrand-new")
+ .first();
+ expect(header?.nonce).toBe("1");
+ });
+
+ it("archives new slots as null-old-value when new slot has no old counterpart", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+
+ // Start with an existing account but NO storage
+ await seedAccount(dbId);
+
+ await applyFullAccountState(dbId, {
+ accountId: ACC,
+ nonce: "2",
+ storageSlots: [
+ { slotName: "brand-new-slot", slotValue: "0xv", slotType: 0 },
+ ],
+ storageMapEntries: [{ slotName: "brand-new-map", key: "k", value: "v" }],
+ assets: [{ vaultKey: "brand-new-key", asset: "0xa" }],
+ codeRoot: CODE_ROOT,
+ storageRoot: STORAGE_ROOT,
+ vaultRoot: VAULT_ROOT,
+ committed: false,
+ accountCommitment: "0xcommit2",
+ accountSeed: undefined,
+ });
+
+ // Historical should have null old values for all brand-new entries
+ const histSlots = await db.historicalAccountStorages
+ .where("[accountId+replacedAtNonce]")
+ .equals([ACC, "2"])
+ .toArray();
+ expect(histSlots.length).toBeGreaterThan(0);
+ expect(histSlots[0].oldSlotValue).toBeNull();
+
+ const histMaps = await db.historicalStorageMapEntries
+ .where("[accountId+replacedAtNonce]")
+ .equals([ACC, "2"])
+ .toArray();
+ expect(histMaps.length).toBeGreaterThan(0);
+ expect(histMaps[0].oldValue).toBeNull();
+
+ const histAssets = await db.historicalAccountAssets
+ .where("[accountId+replacedAtNonce]")
+ .equals([ACC, "2"])
+ .toArray();
+ expect(histAssets.length).toBeGreaterThan(0);
+ expect(histAssets[0].oldAsset).toBeNull();
+ });
+});
+
+// ============================================================
+// upsertForeignAccountCode / getForeignAccountCode
+// ============================================================
+describe("getForeignAccountCode", () => {
+ it("returns null when no records found", async () => {
+ const dbId = await openTestDb();
+ const result = await getForeignAccountCode(dbId, ["0xacc-foreign"]);
+ expect(result).toBeNull();
+ });
+
+ it("returns code for foreign accounts", async () => {
+ const dbId = await openTestDb();
+ const code = new Uint8Array([11, 22, 33]);
+ await upsertForeignAccountCode(dbId, "0xforeign1", code, "0xfcoderoot");
+ const result = await getForeignAccountCode(dbId, ["0xforeign1"]);
+ expect(result).not.toBeNull();
+ expect(result).toHaveLength(1);
+ expect(result![0].accountId).toBe("0xforeign1");
+ expect(typeof result![0].code).toBe("string"); // base64
+ });
+
+ it("handles missing code record gracefully (undefined filtered out)", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+ // Insert foreign account reference without actual code record
+ await db.foreignAccountCode.put({
+ accountId: "0xbroken",
+ codeRoot: "0xmissingcode",
+ });
+ const result = await getForeignAccountCode(dbId, ["0xbroken"]);
+ // Should return empty array (undefined entries filtered)
+ expect(result).toBeDefined();
+ expect((result as unknown[]).length).toBe(0);
+ });
+});
+
+// ============================================================
+// lockAccount
+// ============================================================
+describe("lockAccount", () => {
+ it("locks the latest account header", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+ await seedAccount(dbId);
+
+ const before = await db.latestAccountHeaders
+ .where("id")
+ .equals(ACC)
+ .first();
+ expect(before?.locked).toBe(false);
+
+ await lockAccount(dbId, ACC);
+
+ const after = await db.latestAccountHeaders.where("id").equals(ACC).first();
+ expect(after?.locked).toBe(true);
+ });
+
+ it("locks historical account headers for the same account", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+
+ await seedAccount(dbId);
+ // Create a historical record
+ await db.historicalAccountHeaders.put({
+ id: ACC,
+ replacedAtNonce: "1",
+ codeRoot: CODE_ROOT,
+ storageRoot: STORAGE_ROOT,
+ vaultRoot: VAULT_ROOT,
+ nonce: "0",
+ committed: false,
+ accountSeed: undefined,
+ accountCommitment: "0xoldcommit",
+ locked: false,
+ });
+
+ await lockAccount(dbId, ACC);
+
+ const histHeaders = await db.historicalAccountHeaders
+ .where("id")
+ .equals(ACC)
+ .toArray();
+ expect(histHeaders.every((h) => h.locked === true)).toBe(true);
+ });
+});
+
+// ============================================================
+// pruneAccountHistory
+// ============================================================
+describe("pruneAccountHistory", () => {
+ it("returns 0 when there is no history to prune", async () => {
+ const dbId = await openTestDb();
+ await seedAccount(dbId);
+ const deleted = await pruneAccountHistory(dbId, ACC, "10");
+ expect(deleted).toBe(0);
+ });
+
+ it("prunes historical records at or below the given nonce", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+
+ // Build up history via applyTransactionDelta (nonce 1 → 2 → 3)
+ await applyTransactionDelta(
+ dbId,
+ ACC,
+ "1",
+ [{ slotName: "s1", slotValue: "v1", slotType: 0 }],
+ [],
+ [],
+ CODE_ROOT,
+ STORAGE_ROOT,
+ VAULT_ROOT,
+ false,
+ "0xc1"
+ );
+ await applyTransactionDelta(
+ dbId,
+ ACC,
+ "2",
+ [{ slotName: "s1", slotValue: "v2", slotType: 0 }],
+ [],
+ [],
+ CODE_ROOT,
+ "0xsr2",
+ VAULT_ROOT,
+ false,
+ "0xc2"
+ );
+ await applyTransactionDelta(
+ dbId,
+ ACC,
+ "3",
+ [{ slotName: "s1", slotValue: "v3", slotType: 0 }],
+ [],
+ [],
+ CODE_ROOT,
+ "0xsr3",
+ VAULT_ROOT,
+ false,
+ "0xc3"
+ );
+
+ // Prune up to and including nonce 2
+ const deleted = await pruneAccountHistory(dbId, ACC, "2");
+ expect(deleted).toBeGreaterThan(0);
+
+ // Historical headers at nonce <= 2 should be gone
+ const remaining = await db.historicalAccountHeaders
+ .where("id")
+ .equals(ACC)
+ .toArray();
+ const remainingNonces = remaining.map((h) => Number(h.replacedAtNonce));
+ expect(remainingNonces.every((n) => n > 2)).toBe(true);
+ });
+
+ it("also prunes orphaned account code", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+
+ const OLD_CODE = "0xoldcode";
+ const NEW_CODE = "0xnewcode";
+ await upsertAccountCode(dbId, OLD_CODE, new Uint8Array([1]));
+ await upsertAccountCode(dbId, NEW_CODE, new Uint8Array([2]));
+
+ // Manually build a historical header with replacedAtNonce = "1" and OLD_CODE.
+ // This simulates a state archived when nonce "1" replaced the prior nonce.
+ // The latest header uses NEW_CODE so OLD_CODE has no remaining references.
+ await db.historicalAccountHeaders.put({
+ id: ACC,
+ replacedAtNonce: "1",
+ codeRoot: OLD_CODE,
+ storageRoot: STORAGE_ROOT,
+ vaultRoot: VAULT_ROOT,
+ nonce: "0",
+ committed: false,
+ accountSeed: undefined,
+ accountCommitment: "0xc0",
+ locked: false,
+ });
+
+ // Latest account uses NEW_CODE
+ await upsertAccountRecord(
+ dbId,
+ ACC,
+ NEW_CODE,
+ STORAGE_ROOT,
+ VAULT_ROOT,
+ "2",
+ false,
+ "0xc2",
+ undefined
+ );
+
+ // Prune up to nonce "1" — removes the historical header (replacedAtNonce=1),
+ // leaving OLD_CODE unreferenced → should delete it from accountCodes.
+ await pruneAccountHistory(dbId, ACC, "1");
+
+ const oldCodeRecord = await db.accountCodes.get(OLD_CODE);
+ expect(oldCodeRecord).toBeUndefined();
+
+ // NEW_CODE should still be there (referenced by latest header)
+ const newCodeRecord = await db.accountCodes.get(NEW_CODE);
+ expect(newCodeRecord).toBeDefined();
+ });
+});
+
+// ============================================================
+// undoAccountStates
+// ============================================================
+describe("undoAccountStates", () => {
+ const CV = "0.0.1";
+
+ it("undo restores previous account state", async () => {
+ const dbId = await openTestDb(CV);
+ const db = getDatabase(dbId);
+
+ await applyTransactionDelta(
+ dbId,
+ ACC,
+ "1",
+ [{ slotName: "slot1", slotValue: "0xval1", slotType: 0 }],
+ [{ slotName: "map1", key: "k1", value: "v1" }],
+ [{ vaultKey: "vk1", asset: "0xasset1" }],
+ CODE_ROOT,
+ STORAGE_ROOT,
+ VAULT_ROOT,
+ false,
+ COMMITMENT
+ );
+
+ await applyTransactionDelta(
+ dbId,
+ ACC,
+ "2",
+ [{ slotName: "slot1", slotValue: "0xval2", slotType: 0 }],
+ [{ slotName: "map1", key: "k1", value: "v2" }],
+ [{ vaultKey: "vk1", asset: "0xasset2" }],
+ CODE_ROOT,
+ "0xsroot2",
+ "0xvroot2",
+ false,
+ "0xcommit2"
+ );
+
+ await undoAccountStates(dbId, ["0xcommit2"]);
+
+ const header = await db.latestAccountHeaders
+ .where("id")
+ .equals(ACC)
+ .first();
+ expect(header?.nonce).toBe("1");
+ expect(header?.storageRoot).toBe(STORAGE_ROOT);
+
+ const slots = await db.latestAccountStorages
+ .where("accountId")
+ .equals(ACC)
+ .toArray();
+ expect(slots[0].slotValue).toBe("0xval1");
+
+ const maps = await db.latestStorageMapEntries
+ .where("accountId")
+ .equals(ACC)
+ .toArray();
+ expect(maps[0].value).toBe("v1");
+
+ const assets = await db.latestAccountAssets
+ .where("accountId")
+ .equals(ACC)
+ .toArray();
+ expect(assets[0].asset).toBe("0xasset1");
+ });
+
+ it("deletes the account entirely when no previous header exists", async () => {
+ const dbId = await openTestDb(CV);
+ const db = getDatabase(dbId);
+
+ // Insert account directly (no prior history)
+ await upsertAccountRecord(
+ dbId,
+ "0xnewaccount",
+ CODE_ROOT,
+ STORAGE_ROOT,
+ VAULT_ROOT,
+ "1",
+ false,
+ "0xcommitNew",
+ undefined
+ );
+ await upsertAccountStorage(dbId, "0xnewaccount", [
+ { slotName: "slot1", slotValue: "0xval1", slotType: 0 },
+ ]);
+
+ // Undo the commitment that corresponds to this account's current state
+ await undoAccountStates(dbId, ["0xcommitNew"]);
+
+ // Account should be deleted from latest (commitment found in latest header)
+ const header = await db.latestAccountHeaders
+ .where("id")
+ .equals("0xnewaccount")
+ .first();
+ expect(header).toBeUndefined();
+ });
+
+ it("resolves commitment from historical headers when not in latest", async () => {
+ const dbId = await openTestDb(CV);
+ const db = getDatabase(dbId);
+
+ await applyTransactionDelta(
+ dbId,
+ ACC,
+ "1",
+ [],
+ [],
+ [],
+ CODE_ROOT,
+ STORAGE_ROOT,
+ VAULT_ROOT,
+ false,
+ "0xc1"
+ );
+ await applyTransactionDelta(
+ dbId,
+ ACC,
+ "2",
+ [],
+ [],
+ [],
+ CODE_ROOT,
+ "0xsr2",
+ VAULT_ROOT,
+ false,
+ "0xc2"
+ );
+
+ // "0xc1" is now in historical (archived when nonce 2 applied)
+ // undoAccountStates("0xc1") should find it in historical and restore
+ await undoAccountStates(dbId, ["0xc1"]);
+
+ // Latest header should now be at nonce "0" (before nonce "1" was applied)
+ // — no prior historical means account deleted
+ const header = await db.latestAccountHeaders
+ .where("id")
+ .equals(ACC)
+ .first();
+ expect(header).toBeUndefined();
+ });
+
+ it("no-ops when commitment does not exist anywhere", async () => {
+ const dbId = await openTestDb(CV);
+ const db = getDatabase(dbId);
+ await seedAccount(dbId);
+
+ // Should not throw
+ await expect(
+ undoAccountStates(dbId, ["0xnonexistent"])
+ ).resolves.not.toThrow();
+
+ // Account should still be there
+ const header = await db.latestAccountHeaders
+ .where("id")
+ .equals(ACC)
+ .first();
+ expect(header).toBeDefined();
+ });
+
+ it("restores null old values by deleting from latest (slot null branch)", async () => {
+ const dbId = await openTestDb(CV);
+ const db = getDatabase(dbId);
+
+ // Apply nonce "1" adding a brand-new slot/map/asset (no prior state)
+ await applyTransactionDelta(
+ dbId,
+ ACC,
+ "1",
+ [{ slotName: "newslot", slotValue: "0xv", slotType: 0 }],
+ [{ slotName: "newmap", key: "k", value: "v" }],
+ [{ vaultKey: "newkey", asset: "0xa" }],
+ CODE_ROOT,
+ STORAGE_ROOT,
+ VAULT_ROOT,
+ false,
+ COMMITMENT
+ );
+
+ // Historical entries for nonce "1" have null old values (brand-new)
+ const histSlots = await db.historicalAccountStorages
+ .where("[accountId+replacedAtNonce]")
+ .equals([ACC, "1"])
+ .toArray();
+ expect(histSlots[0].oldSlotValue).toBeNull();
+
+ // Undo nonce "1" — null old values should cause deletion from latest
+ await undoAccountStates(dbId, [COMMITMENT]);
+
+ const slots = await db.latestAccountStorages
+ .where("accountId")
+ .equals(ACC)
+ .toArray();
+ expect(slots).toHaveLength(0);
+
+ const maps = await db.latestStorageMapEntries
+ .where("accountId")
+ .equals(ACC)
+ .toArray();
+ expect(maps).toHaveLength(0);
+
+ const assets = await db.latestAccountAssets
+ .where("accountId")
+ .equals(ACC)
+ .toArray();
+ expect(assets).toHaveLength(0);
+ });
+});
+
+// ============================================================
+// Error-path coverage: catch blocks call logWebStoreError (re-throws)
+// Passing an unregistered dbId causes getDatabase() to throw, which
+// exercises the catch body in every function.
+// ============================================================
+const BAD_DB = "does-not-exist-db";
+
+describe("error paths: unregistered dbId re-throws", () => {
+ it("getAccountIds rejects on bad dbId", async () => {
+ await expect(getAccountIds(BAD_DB)).rejects.toThrow();
+ });
+
+ it("getAllAccountHeaders rejects on bad dbId", async () => {
+ await expect(getAllAccountHeaders(BAD_DB)).rejects.toThrow();
+ });
+
+ it("getAccountHeader rejects on bad dbId", async () => {
+ await expect(getAccountHeader(BAD_DB, "0xacc")).rejects.toThrow();
+ });
+
+ it("getAccountHeaderByCommitment rejects on bad dbId", async () => {
+ await expect(
+ getAccountHeaderByCommitment(BAD_DB, "0xcommit")
+ ).rejects.toThrow();
+ });
+
+ it("getAccountCode rejects on bad dbId", async () => {
+ await expect(getAccountCode(BAD_DB, "0xroot")).rejects.toThrow();
+ });
+
+ it("getAccountStorage rejects on bad dbId", async () => {
+ await expect(getAccountStorage(BAD_DB, "0xacc", [])).rejects.toThrow();
+ });
+
+ it("getAccountStorageMaps rejects on bad dbId", async () => {
+ await expect(getAccountStorageMaps(BAD_DB, "0xacc")).rejects.toThrow();
+ });
+
+ it("getAccountVaultAssets rejects on bad dbId", async () => {
+ await expect(getAccountVaultAssets(BAD_DB, "0xacc", [])).rejects.toThrow();
+ });
+
+ it("getAccountAddresses rejects on bad dbId", async () => {
+ await expect(getAccountAddresses(BAD_DB, "0xacc")).rejects.toThrow();
+ });
+
+ it("upsertAccountCode rejects on bad dbId", async () => {
+ await expect(
+ upsertAccountCode(BAD_DB, "0xroot", new Uint8Array([1]))
+ ).rejects.toThrow();
+ });
+
+ it("upsertAccountStorage rejects on bad dbId", async () => {
+ await expect(upsertAccountStorage(BAD_DB, "0xacc", [])).rejects.toThrow();
+ });
+
+ it("upsertStorageMapEntries rejects on bad dbId", async () => {
+ await expect(
+ upsertStorageMapEntries(BAD_DB, "0xacc", [])
+ ).rejects.toThrow();
+ });
+
+ it("upsertVaultAssets rejects on bad dbId", async () => {
+ await expect(upsertVaultAssets(BAD_DB, "0xacc", [])).rejects.toThrow();
+ });
+
+ it("upsertAccountRecord rejects on bad dbId", async () => {
+ await expect(
+ upsertAccountRecord(
+ BAD_DB,
+ "0xacc",
+ "0xcode",
+ "0xsroot",
+ "0xvroot",
+ "1",
+ false,
+ "0xcommit",
+ undefined
+ )
+ ).rejects.toThrow();
+ });
+
+ it("insertAccountAddress rejects on bad dbId", async () => {
+ await expect(
+ insertAccountAddress(BAD_DB, "0xacc", new Uint8Array([1]))
+ ).rejects.toThrow();
+ });
+
+ it("removeAccountAddress rejects on bad dbId", async () => {
+ await expect(
+ removeAccountAddress(BAD_DB, new Uint8Array([1]))
+ ).rejects.toThrow();
+ });
+
+ it("upsertForeignAccountCode rejects on bad dbId", async () => {
+ await expect(
+ upsertForeignAccountCode(BAD_DB, "0xacc", new Uint8Array([1]), "0xroot")
+ ).rejects.toThrow();
+ });
+
+ it("getForeignAccountCode rejects on bad dbId", async () => {
+ await expect(getForeignAccountCode(BAD_DB, ["0xacc"])).rejects.toThrow();
+ });
+
+ it("lockAccount rejects on bad dbId", async () => {
+ await expect(lockAccount(BAD_DB, "0xacc")).rejects.toThrow();
+ });
+
+ it("applyTransactionDelta rejects on bad dbId", async () => {
+ await expect(
+ applyTransactionDelta(
+ BAD_DB,
+ "0xacc",
+ "1",
+ [],
+ [],
+ [],
+ "0xcode",
+ "0xsr",
+ "0xvr",
+ false,
+ "0xcommit"
+ )
+ ).rejects.toThrow();
+ });
+
+ it("applyFullAccountState rejects on bad dbId", async () => {
+ await expect(
+ applyFullAccountState(BAD_DB, {
+ accountId: "0xacc",
+ nonce: "1",
+ storageSlots: [],
+ storageMapEntries: [],
+ assets: [],
+ codeRoot: "0xcode",
+ storageRoot: "0xsr",
+ vaultRoot: "0xvr",
+ committed: false,
+ accountCommitment: "0xcommit",
+ accountSeed: undefined,
+ })
+ ).rejects.toThrow();
+ });
+
+ it("undoAccountStates rejects on bad dbId", async () => {
+ await expect(undoAccountStates(BAD_DB, ["0xcommit"])).rejects.toThrow();
+ });
+
+ it("pruneAccountHistory rejects on bad dbId", async () => {
+ await expect(pruneAccountHistory(BAD_DB, "0xacc", "10")).rejects.toThrow();
+ });
+});
+
+// ============================================================
+// Additional coverage: line 1119 — sort comparator (multiple nonces same account)
+// ============================================================
+describe("undoAccountStates: multiple nonces for same account (sort comparator)", () => {
+ it("undoes multiple nonces for the same account in descending order", async () => {
+ const dbId = await openTestDb("0.0.1");
+ const db = getDatabase(dbId);
+
+ // Build 3 deltas for the same account to exercise the sort comparator at 1119
+ await applyTransactionDelta(
+ dbId,
+ ACC,
+ "1",
+ [{ slotName: "slot1", slotValue: "0xv1", slotType: 0 }],
+ [],
+ [],
+ CODE_ROOT,
+ STORAGE_ROOT,
+ VAULT_ROOT,
+ false,
+ "0xc1"
+ );
+ await applyTransactionDelta(
+ dbId,
+ ACC,
+ "2",
+ [{ slotName: "slot1", slotValue: "0xv2", slotType: 0 }],
+ [],
+ [],
+ CODE_ROOT,
+ "0xsr2",
+ VAULT_ROOT,
+ false,
+ "0xc2"
+ );
+ await applyTransactionDelta(
+ dbId,
+ ACC,
+ "3",
+ [{ slotName: "slot1", slotValue: "0xv3", slotType: 0 }],
+ [],
+ [],
+ CODE_ROOT,
+ "0xsr3",
+ VAULT_ROOT,
+ false,
+ "0xc3"
+ );
+
+ // Undo both nonce 2 and 3 at once — they have the same accountId,
+ // so accountNonces will have one entry with {2, 3}, triggering the sort.
+ await undoAccountStates(dbId, ["0xc2", "0xc3"]);
+
+ // After undoing nonces 2 and 3, the slot value should be back to nonce "1" state
+ const slots = await db.latestAccountStorages
+ .where("accountId")
+ .equals(ACC)
.toArray();
- expect(historicalHeaders).toHaveLength(0);
+ expect(slots[0].slotValue).toBe("0xv1");
});
});
diff --git a/crates/idxdb-store/src/ts/auth.test.ts b/crates/idxdb-store/src/ts/auth.test.ts
new file mode 100644
index 0000000..abbf06c
--- /dev/null
+++ b/crates/idxdb-store/src/ts/auth.test.ts
@@ -0,0 +1,228 @@
+import { describe, it, expect, afterEach, vi, beforeEach } from "vitest";
+import { openDatabase, getDatabase } from "./schema.js";
+import {
+ insertAccountAuth,
+ getAccountAuthByPubKeyCommitment,
+ removeAccountAuth,
+ insertAccountKeyMapping,
+ getKeyCommitmentsByAccountId,
+ removeAllMappingsForKey,
+ getAccountIdByKeyCommitment,
+} from "./auth.js";
+
+let dbCounter = 0;
+function uniqueDbName(): string {
+ return `test-auth-${++dbCounter}-${Date.now()}`;
+}
+
+const openDbIds: string[] = [];
+
+afterEach(async () => {
+ for (const dbId of openDbIds) {
+ const db = getDatabase(dbId);
+ db.dexie.close();
+ await db.dexie.delete();
+ }
+ openDbIds.length = 0;
+});
+
+async function openTestDb(): Promise {
+ const name = uniqueDbName();
+ await openDatabase(name, "0.1.0");
+ openDbIds.push(name);
+ return name;
+}
+
+describe("auth", () => {
+ let errorSpy: any;
+ let logSpy: any;
+
+ beforeEach(() => {
+ errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ errorSpy.mockRestore();
+ logSpy.mockRestore();
+ });
+
+ // ---------------------------------------------------------------------------
+ // insertAccountAuth / getAccountAuthByPubKeyCommitment
+ // ---------------------------------------------------------------------------
+
+ it("inserts an account auth and retrieves it by pubkey commitment", async () => {
+ const dbId = await openTestDb();
+ await insertAccountAuth(dbId, "pubkey-abc", "secretkey-xyz");
+ const result = await getAccountAuthByPubKeyCommitment(dbId, "pubkey-abc");
+ expect(result).toEqual({ secretKey: "secretkey-xyz" });
+ });
+
+ it("stores multiple account auths independently", async () => {
+ const dbId = await openTestDb();
+ await insertAccountAuth(dbId, "pubkey-1", "secret-1");
+ await insertAccountAuth(dbId, "pubkey-2", "secret-2");
+ const r1 = await getAccountAuthByPubKeyCommitment(dbId, "pubkey-1");
+ const r2 = await getAccountAuthByPubKeyCommitment(dbId, "pubkey-2");
+ expect(r1).toEqual({ secretKey: "secret-1" });
+ expect(r2).toEqual({ secretKey: "secret-2" });
+ });
+
+ it("getAccountAuthByPubKeyCommitment throws when record does not exist", async () => {
+ const dbId = await openTestDb();
+ await expect(
+ getAccountAuthByPubKeyCommitment(dbId, "nonexistent-key")
+ ).rejects.toThrow("Account auth not found in cache.");
+ });
+
+ it("insertAccountAuth throws (via logWebStoreError rethrow) when db is not opened", async () => {
+ await expect(
+ insertAccountAuth("never-opened", "pubkey-abc", "secretkey-xyz")
+ ).rejects.toThrow();
+ });
+
+ it("getAccountAuthByPubKeyCommitment throws when db is not opened", async () => {
+ // No try/catch in getAccountAuthByPubKeyCommitment — getDatabase throws propagate
+ await expect(
+ getAccountAuthByPubKeyCommitment("never-opened", "pubkey-abc")
+ ).rejects.toThrow();
+ });
+
+ // ---------------------------------------------------------------------------
+ // removeAccountAuth
+ // ---------------------------------------------------------------------------
+
+ it("removes an account auth", async () => {
+ const dbId = await openTestDb();
+ await insertAccountAuth(dbId, "pubkey-del", "secret-del");
+ await removeAccountAuth(dbId, "pubkey-del");
+ await expect(
+ getAccountAuthByPubKeyCommitment(dbId, "pubkey-del")
+ ).rejects.toThrow("Account auth not found in cache.");
+ });
+
+ it("removeAccountAuth on a missing key is a no-op", async () => {
+ const dbId = await openTestDb();
+ // Should not throw
+ await removeAccountAuth(dbId, "nonexistent-key");
+ });
+
+ it("removeAccountAuth throws (via logWebStoreError rethrow) when db is not opened", async () => {
+ await expect(
+ removeAccountAuth("never-opened", "pubkey-abc")
+ ).rejects.toThrow();
+ });
+
+ // ---------------------------------------------------------------------------
+ // insertAccountKeyMapping / getKeyCommitmentsByAccountId
+ // ---------------------------------------------------------------------------
+
+ it("inserts a key mapping and retrieves commitments by account id", async () => {
+ const dbId = await openTestDb();
+ await insertAccountKeyMapping(dbId, "account-1", "pubkey-commitment-1");
+ const commitments = await getKeyCommitmentsByAccountId(dbId, "account-1");
+ expect(commitments).toEqual(["pubkey-commitment-1"]);
+ });
+
+ it("inserts multiple mappings for the same account and retrieves all commitments", async () => {
+ const dbId = await openTestDb();
+ await insertAccountKeyMapping(dbId, "account-multi", "commitment-a");
+ await insertAccountKeyMapping(dbId, "account-multi", "commitment-b");
+ const commitments = await getKeyCommitmentsByAccountId(
+ dbId,
+ "account-multi"
+ );
+ expect(commitments).toHaveLength(2);
+ expect(commitments).toEqual(
+ expect.arrayContaining(["commitment-a", "commitment-b"])
+ );
+ });
+
+ it("insertAccountKeyMapping is idempotent (put semantics) for the same pair", async () => {
+ const dbId = await openTestDb();
+ await insertAccountKeyMapping(dbId, "account-idem", "commitment-idem");
+ await insertAccountKeyMapping(dbId, "account-idem", "commitment-idem");
+ const commitments = await getKeyCommitmentsByAccountId(
+ dbId,
+ "account-idem"
+ );
+ // put semantics: the second call replaces the first — still one entry
+ expect(commitments).toHaveLength(1);
+ });
+
+ it("getKeyCommitmentsByAccountId returns empty array when no mappings exist", async () => {
+ const dbId = await openTestDb();
+ const commitments = await getKeyCommitmentsByAccountId(dbId, "no-account");
+ expect(commitments).toEqual([]);
+ });
+
+ it("insertAccountKeyMapping throws (via logWebStoreError rethrow) when db is not opened", async () => {
+ await expect(
+ insertAccountKeyMapping("never-opened", "account-1", "commitment-1")
+ ).rejects.toThrow();
+ });
+
+ it("getKeyCommitmentsByAccountId throws (via logWebStoreError rethrow) when db is not opened", async () => {
+ await expect(
+ getKeyCommitmentsByAccountId("never-opened", "account-1")
+ ).rejects.toThrow();
+ });
+
+ // ---------------------------------------------------------------------------
+ // removeAllMappingsForKey
+ // ---------------------------------------------------------------------------
+
+ it("removes all account key mappings for a given key commitment", async () => {
+ const dbId = await openTestDb();
+ await insertAccountKeyMapping(dbId, "account-a", "shared-commitment");
+ await insertAccountKeyMapping(dbId, "account-b", "shared-commitment");
+ await removeAllMappingsForKey(dbId, "shared-commitment");
+ // Both accounts should now have no mappings for shared-commitment
+ const idResult = await getAccountIdByKeyCommitment(
+ dbId,
+ "shared-commitment"
+ );
+ expect(idResult).toBeNull();
+ });
+
+ it("removeAllMappingsForKey on a missing key is a no-op", async () => {
+ const dbId = await openTestDb();
+ await removeAllMappingsForKey(dbId, "nonexistent-commitment");
+ // No throw means success
+ });
+
+ it("removeAllMappingsForKey throws (via logWebStoreError rethrow) when db is not opened", async () => {
+ await expect(
+ removeAllMappingsForKey("never-opened", "commitment-x")
+ ).rejects.toThrow();
+ });
+
+ // ---------------------------------------------------------------------------
+ // getAccountIdByKeyCommitment
+ // ---------------------------------------------------------------------------
+
+ it("retrieves account id by key commitment", async () => {
+ const dbId = await openTestDb();
+ await insertAccountKeyMapping(dbId, "account-lookup", "commitment-lookup");
+ const accountId = await getAccountIdByKeyCommitment(
+ dbId,
+ "commitment-lookup"
+ );
+ expect(accountId).toBe("account-lookup");
+ });
+
+ it("getAccountIdByKeyCommitment returns null when commitment is not found", async () => {
+ const dbId = await openTestDb();
+ const accountId = await getAccountIdByKeyCommitment(
+ dbId,
+ "nonexistent-commitment"
+ );
+ expect(accountId).toBeNull();
+ });
+
+ it("getAccountIdByKeyCommitment throws (via logWebStoreError rethrow) when db is not opened", async () => {
+ await expect(
+ getAccountIdByKeyCommitment("never-opened", "commitment-x")
+ ).rejects.toThrow();
+ });
+});
diff --git a/crates/idxdb-store/src/ts/chainData.test.ts b/crates/idxdb-store/src/ts/chainData.test.ts
index 32a428f..858d245 100644
--- a/crates/idxdb-store/src/ts/chainData.test.ts
+++ b/crates/idxdb-store/src/ts/chainData.test.ts
@@ -3,6 +3,14 @@ import { afterEach, describe, expect, it } from "vitest";
import {
getPartialBlockchainPeaksByBlockNum,
insertBlockHeader,
+ insertPartialBlockchainNodes,
+ getBlockHeaders,
+ getTrackedBlockHeaders,
+ getTrackedBlockHeaderNumbers,
+ getPartialBlockchainNodesAll,
+ getPartialBlockchainNodes,
+ getPartialBlockchainNodesUpToInOrderIndex,
+ pruneIrrelevantBlocks,
} from "./chainData.js";
import { getDatabase, openDatabase } from "./schema.js";
import { uniqueDbName } from "./test-utils.js";
@@ -131,3 +139,366 @@ describe("insertBlockHeader: add-if-not-exists semantics", () => {
expect(stored!.hasClientNotes).toBe("true");
});
});
+
+// ============================================================
+// insertPartialBlockchainNodes
+// ============================================================
+describe("insertPartialBlockchainNodes", () => {
+ it("inserts nodes and retrieves them", async () => {
+ const dbId = await openTestDb();
+ await insertPartialBlockchainNodes(
+ dbId,
+ ["1", "2", "3"],
+ ["0xnode1", "0xnode2", "0xnode3"]
+ );
+ const db = getDatabase(dbId);
+ const all = await db.partialBlockchainNodes.toArray();
+ expect(all).toHaveLength(3);
+ });
+
+ it("no-ops when ids array is empty", async () => {
+ const dbId = await openTestDb();
+ await insertPartialBlockchainNodes(dbId, [], []);
+ const db = getDatabase(dbId);
+ const all = await db.partialBlockchainNodes.toArray();
+ expect(all).toHaveLength(0);
+ });
+
+ it("rejects when ids and nodes arrays have different lengths", async () => {
+ const dbId = await openTestDb();
+ // The error is thrown, caught by the catch block, then re-thrown by
+ // logWebStoreError — so the outer promise rejects.
+ await expect(
+ insertPartialBlockchainNodes(dbId, ["1", "2"], ["0xnode1"])
+ ).rejects.toThrow("ids and nodes arrays must be of the same length");
+ });
+
+ it("overwrites existing nodes on re-insert (bulkPut semantics)", async () => {
+ const dbId = await openTestDb();
+ await insertPartialBlockchainNodes(dbId, ["1"], ["0xold"]);
+ await insertPartialBlockchainNodes(dbId, ["1"], ["0xnew"]);
+ const db = getDatabase(dbId);
+ const node = await db.partialBlockchainNodes.get(1);
+ expect(node!.node).toBe("0xnew");
+ });
+});
+
+// ============================================================
+// getBlockHeaders
+// ============================================================
+describe("getBlockHeaders", () => {
+ it("returns null entries for missing block numbers", async () => {
+ const dbId = await openTestDb();
+ const results = await getBlockHeaders(dbId, [999]);
+ expect(results).toHaveLength(1);
+ expect(results![0]).toBeNull();
+ });
+
+ it("returns base64-encoded headers for existing blocks", async () => {
+ const dbId = await openTestDb();
+ await insertBlockHeader(dbId, 1, HEADER_V1, PEAKS_FROM_SYNC, false);
+ await insertBlockHeader(dbId, 2, HEADER_V2, PEAKS_FROM_BACKFILL, true);
+
+ const results = await getBlockHeaders(dbId, [1, 2]);
+ expect(results).toHaveLength(2);
+ expect(results![0]).not.toBeNull();
+ expect(results![1]).not.toBeNull();
+ expect(results![0]!.blockNum).toBe(1);
+ expect(results![1]!.blockNum).toBe(2);
+ // Both should be base64 strings
+ expect(typeof results![0]!.header).toBe("string");
+ expect(results![0]!.hasClientNotes).toBe(false);
+ expect(results![1]!.hasClientNotes).toBe(true);
+ });
+
+ it("returns empty array for empty block list", async () => {
+ const dbId = await openTestDb();
+ const results = await getBlockHeaders(dbId, []);
+ expect(results).toEqual([]);
+ });
+});
+
+// ============================================================
+// getTrackedBlockHeaders
+// ============================================================
+describe("getTrackedBlockHeaders", () => {
+ it("returns only blocks with hasClientNotes=true", async () => {
+ const dbId = await openTestDb();
+ await insertBlockHeader(dbId, 10, HEADER_V1, PEAKS_FROM_SYNC, false);
+ await insertBlockHeader(dbId, 20, HEADER_V2, PEAKS_FROM_BACKFILL, true);
+
+ const results = await getTrackedBlockHeaders(dbId);
+ expect(results).toHaveLength(1);
+ expect(results![0].blockNum).toBe(20);
+ expect(results![0].hasClientNotes).toBe(true);
+ expect(typeof results![0].header).toBe("string");
+ expect(typeof results![0].partialBlockchainPeaks).toBe("string");
+ });
+
+ it("returns empty array when no tracked blocks", async () => {
+ const dbId = await openTestDb();
+ await insertBlockHeader(dbId, 10, HEADER_V1, PEAKS_FROM_SYNC, false);
+ const results = await getTrackedBlockHeaders(dbId);
+ expect(results).toEqual([]);
+ });
+});
+
+// ============================================================
+// getTrackedBlockHeaderNumbers
+// ============================================================
+describe("getTrackedBlockHeaderNumbers", () => {
+ it("returns primary keys of tracked blocks only", async () => {
+ const dbId = await openTestDb();
+ await insertBlockHeader(dbId, 5, HEADER_V1, PEAKS_FROM_SYNC, true);
+ await insertBlockHeader(dbId, 6, HEADER_V2, PEAKS_FROM_BACKFILL, false);
+ await insertBlockHeader(dbId, 7, HEADER_V1, PEAKS_FROM_SYNC, true);
+
+ const nums = await getTrackedBlockHeaderNumbers(dbId);
+ expect(nums).toHaveLength(2);
+ expect(nums).toContain(5);
+ expect(nums).toContain(7);
+ });
+
+ it("returns empty when no tracked blocks", async () => {
+ const dbId = await openTestDb();
+ const nums = await getTrackedBlockHeaderNumbers(dbId);
+ expect(nums).toEqual([]);
+ });
+});
+
+// ============================================================
+// getPartialBlockchainPeaksByBlockNum
+// ============================================================
+describe("getPartialBlockchainPeaksByBlockNum", () => {
+ it("returns {peaks: undefined} for non-existent block", async () => {
+ const dbId = await openTestDb();
+ const result = await getPartialBlockchainPeaksByBlockNum(dbId, 999);
+ expect(result).toBeDefined();
+ expect(result!.peaks).toBeUndefined();
+ });
+
+ it("returns base64-encoded peaks for existing block", async () => {
+ const dbId = await openTestDb();
+ await insertBlockHeader(dbId, 50, HEADER_V1, PEAKS_FROM_SYNC, false);
+ const result = await getPartialBlockchainPeaksByBlockNum(dbId, 50);
+ expect(result!.peaks).toBeDefined();
+ const decoded = Uint8Array.from(atob(result!.peaks!), (c) =>
+ c.charCodeAt(0)
+ );
+ expect(decoded).toEqual(PEAKS_FROM_SYNC);
+ });
+});
+
+// ============================================================
+// getPartialBlockchainNodesAll
+// ============================================================
+describe("getPartialBlockchainNodesAll", () => {
+ it("returns empty array when no nodes", async () => {
+ const dbId = await openTestDb();
+ const result = await getPartialBlockchainNodesAll(dbId);
+ expect(result).toEqual([]);
+ });
+
+ it("returns all inserted nodes", async () => {
+ const dbId = await openTestDb();
+ await insertPartialBlockchainNodes(dbId, ["10", "20"], ["0xa", "0xb"]);
+ const result = await getPartialBlockchainNodesAll(dbId);
+ expect(result).toHaveLength(2);
+ });
+});
+
+// ============================================================
+// getPartialBlockchainNodes
+// ============================================================
+describe("getPartialBlockchainNodes", () => {
+ it("returns nodes for the given ids, filtering undefined for missing", async () => {
+ const dbId = await openTestDb();
+ await insertPartialBlockchainNodes(
+ dbId,
+ ["1", "3"],
+ ["0xnode1", "0xnode3"]
+ );
+ const result = await getPartialBlockchainNodes(dbId, ["1", "2", "3"]);
+ // id 2 is missing → filtered out
+ expect(result).toHaveLength(2);
+ const ids = result!.map((n) => n!.id);
+ expect(ids).toContain(1);
+ expect(ids).toContain(3);
+ });
+
+ it("returns empty array when none of the requested ids exist", async () => {
+ const dbId = await openTestDb();
+ const result = await getPartialBlockchainNodes(dbId, ["99", "100"]);
+ expect(result).toEqual([]);
+ });
+});
+
+// ============================================================
+// getPartialBlockchainNodesUpToInOrderIndex
+// ============================================================
+describe("getPartialBlockchainNodesUpToInOrderIndex", () => {
+ it("returns nodes with id <= maxIndex", async () => {
+ const dbId = await openTestDb();
+ await insertPartialBlockchainNodes(
+ dbId,
+ ["1", "2", "3", "4", "5"],
+ ["0xa", "0xb", "0xc", "0xd", "0xe"]
+ );
+ const result = await getPartialBlockchainNodesUpToInOrderIndex(dbId, "3");
+ expect(result).toHaveLength(3);
+ const ids = result!.map((n) => n.id);
+ expect(ids).toContain(1);
+ expect(ids).toContain(2);
+ expect(ids).toContain(3);
+ expect(ids).not.toContain(4);
+ });
+
+ it("returns empty when no nodes exist below threshold", async () => {
+ const dbId = await openTestDb();
+ await insertPartialBlockchainNodes(dbId, ["10", "20"], ["0xa", "0xb"]);
+ const result = await getPartialBlockchainNodesUpToInOrderIndex(dbId, "5");
+ expect(result).toEqual([]);
+ });
+});
+
+// ============================================================
+// pruneIrrelevantBlocks
+// ============================================================
+describe("pruneIrrelevantBlocks", () => {
+ it("deletes non-tracked non-sync-height non-genesis blocks", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+
+ // Insert sync height = 10 (default populate gives block 0)
+ await db.stateSync.put({ id: 1, blockNum: 10 });
+
+ // Block 0 (genesis), block 5 (irrelevant), block 10 (sync height), block 20 (tracked)
+ await insertBlockHeader(dbId, 0, HEADER_V1, PEAKS_FROM_SYNC, false);
+ await insertBlockHeader(dbId, 5, HEADER_V1, PEAKS_FROM_SYNC, false); // should be pruned
+ await insertBlockHeader(dbId, 10, HEADER_V1, PEAKS_FROM_SYNC, false); // sync height, keep
+ await insertBlockHeader(dbId, 20, HEADER_V2, PEAKS_FROM_BACKFILL, true); // tracked, keep
+
+ await pruneIrrelevantBlocks(dbId, [], []);
+
+ const remaining = await db.blockHeaders.toArray();
+ const blockNums = remaining.map((r) => r.blockNum);
+ expect(blockNums).not.toContain(5);
+ expect(blockNums).toContain(0);
+ expect(blockNums).toContain(10);
+ expect(blockNums).toContain(20);
+ });
+
+ it("untracks listed blocks then prunes them", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+
+ await db.stateSync.put({ id: 1, blockNum: 10 });
+ await insertBlockHeader(dbId, 0, HEADER_V1, PEAKS_FROM_SYNC, false);
+ await insertBlockHeader(dbId, 7, HEADER_V1, PEAKS_FROM_SYNC, true); // tracked, will untrack
+ await insertBlockHeader(dbId, 10, HEADER_V1, PEAKS_FROM_SYNC, false);
+ await insertBlockHeader(dbId, 20, HEADER_V2, PEAKS_FROM_BACKFILL, true);
+
+ await pruneIrrelevantBlocks(dbId, [7], []);
+
+ const remaining = await db.blockHeaders.toArray();
+ const blockNums = remaining.map((r) => r.blockNum);
+ expect(blockNums).not.toContain(7); // untracked then pruned
+ expect(blockNums).toContain(20); // still tracked
+ });
+
+ it("removes listed MMR authentication nodes", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+
+ await db.stateSync.put({ id: 1, blockNum: 10 });
+ await insertPartialBlockchainNodes(
+ dbId,
+ ["1", "2", "3"],
+ ["0xa", "0xb", "0xc"]
+ );
+
+ await pruneIrrelevantBlocks(dbId, [], ["1", "3"]);
+
+ const nodes = await db.partialBlockchainNodes.toArray();
+ const ids = nodes.map((n) => Number(n.id));
+ expect(ids).not.toContain(1);
+ expect(ids).toContain(2);
+ expect(ids).not.toContain(3);
+ });
+
+ it("rejects when stateSync is undefined", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+
+ // Delete the default stateSync entry that was populated by the 'populate' hook
+ await db.stateSync.clear();
+
+ // logWebStoreError re-throws, so the promise rejects
+ await expect(pruneIrrelevantBlocks(dbId, [], [])).rejects.toThrow(
+ "SyncHeight is undefined"
+ );
+ });
+});
+
+// ============================================================
+// Error-path coverage: catch blocks call logWebStoreError (re-throws)
+// Passing an unregistered dbId exercises the catch body in each function.
+// ============================================================
+const BAD_DB = "does-not-exist-chaindata";
+
+describe("error paths: unregistered dbId re-throws", () => {
+ it("insertBlockHeader rejects on bad dbId", async () => {
+ await expect(
+ insertBlockHeader(
+ BAD_DB,
+ 1,
+ new Uint8Array([1]),
+ new Uint8Array([2]),
+ false
+ )
+ ).rejects.toThrow();
+ });
+
+ it("insertPartialBlockchainNodes rejects on bad dbId (empty ids are a no-op before db access)", async () => {
+ // Non-empty ids will hit getDatabase, which throws
+ await expect(
+ insertPartialBlockchainNodes(BAD_DB, ["1"], ["0xnode"])
+ ).rejects.toThrow();
+ });
+
+ it("getBlockHeaders rejects on bad dbId", async () => {
+ await expect(getBlockHeaders(BAD_DB, [1])).rejects.toThrow();
+ });
+
+ it("getTrackedBlockHeaders rejects on bad dbId", async () => {
+ await expect(getTrackedBlockHeaders(BAD_DB)).rejects.toThrow();
+ });
+
+ it("getTrackedBlockHeaderNumbers rejects on bad dbId", async () => {
+ await expect(getTrackedBlockHeaderNumbers(BAD_DB)).rejects.toThrow();
+ });
+
+ it("getPartialBlockchainPeaksByBlockNum rejects on bad dbId", async () => {
+ await expect(
+ getPartialBlockchainPeaksByBlockNum(BAD_DB, 1)
+ ).rejects.toThrow();
+ });
+
+ it("getPartialBlockchainNodesAll rejects on bad dbId", async () => {
+ await expect(getPartialBlockchainNodesAll(BAD_DB)).rejects.toThrow();
+ });
+
+ it("getPartialBlockchainNodes rejects on bad dbId", async () => {
+ await expect(getPartialBlockchainNodes(BAD_DB, ["1"])).rejects.toThrow();
+ });
+
+ it("getPartialBlockchainNodesUpToInOrderIndex rejects on bad dbId", async () => {
+ await expect(
+ getPartialBlockchainNodesUpToInOrderIndex(BAD_DB, "5")
+ ).rejects.toThrow();
+ });
+
+ it("pruneIrrelevantBlocks rejects on bad dbId", async () => {
+ await expect(pruneIrrelevantBlocks(BAD_DB, [], [])).rejects.toThrow();
+ });
+});
diff --git a/crates/idxdb-store/src/ts/export.test.ts b/crates/idxdb-store/src/ts/export.test.ts
new file mode 100644
index 0000000..7ecf3ab
--- /dev/null
+++ b/crates/idxdb-store/src/ts/export.test.ts
@@ -0,0 +1,231 @@
+import { describe, it, expect, afterEach, vi, beforeEach } from "vitest";
+import { openDatabase, getDatabase } from "./schema.js";
+import { exportStore, transformForExport } from "./export.js";
+import { uint8ArrayToBase64 } from "./utils.js";
+
+let dbCounter = 0;
+function uniqueDbName(): string {
+ return `test-export-${++dbCounter}-${Date.now()}`;
+}
+
+const openDbIds: string[] = [];
+
+afterEach(async () => {
+ for (const dbId of openDbIds) {
+ const db = getDatabase(dbId);
+ db.dexie.close();
+ await db.dexie.delete();
+ }
+ openDbIds.length = 0;
+});
+
+async function openTestDb(): Promise {
+ const name = uniqueDbName();
+ await openDatabase(name, "0.1.0");
+ openDbIds.push(name);
+ return name;
+}
+
+// ================================================================================================
+// transformForExport unit tests
+// ================================================================================================
+
+describe("transformForExport", () => {
+ let logSpy: any;
+
+ beforeEach(() => {
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ logSpy.mockRestore();
+ });
+
+ it("converts a Uint8Array to a tagged base64 object", async () => {
+ const bytes = new Uint8Array([1, 2, 3]);
+ const result = await transformForExport(bytes);
+ expect(result).toEqual({
+ __type: "Uint8Array",
+ data: uint8ArrayToBase64(bytes),
+ });
+ });
+
+ it("converts a Blob to a tagged base64 object", async () => {
+ const bytes = new Uint8Array([4, 5, 6]);
+ const blob = new Blob([bytes]);
+ const result = await transformForExport(blob);
+ expect(result).toEqual({
+ __type: "Blob",
+ data: uint8ArrayToBase64(bytes),
+ });
+ });
+
+ it("transforms an array recursively", async () => {
+ const input = [new Uint8Array([1]), "hello", 42];
+ const result = await transformForExport(input);
+ expect(result).toEqual([
+ { __type: "Uint8Array", data: uint8ArrayToBase64(new Uint8Array([1])) },
+ "hello",
+ 42,
+ ]);
+ });
+
+ it("transforms a nested record recursively", async () => {
+ const bytes = new Uint8Array([7, 8]);
+ const input = { key: bytes, count: 5, label: "abc" };
+ const result = await transformForExport(input);
+ expect(result).toEqual({
+ key: { __type: "Uint8Array", data: uint8ArrayToBase64(bytes) },
+ count: 5,
+ label: "abc",
+ });
+ });
+
+ it("returns primitives unchanged", async () => {
+ expect(await transformForExport(42)).toBe(42);
+ expect(await transformForExport("hello")).toBe("hello");
+ expect(await transformForExport(null)).toBeNull();
+ expect(await transformForExport(true)).toBe(true);
+ });
+
+ it("handles deeply nested structures", async () => {
+ const bytes = new Uint8Array([99]);
+ const input = { outer: { inner: [bytes] } };
+ const result = await transformForExport(input);
+ expect(result).toEqual({
+ outer: {
+ inner: [{ __type: "Uint8Array", data: uint8ArrayToBase64(bytes) }],
+ },
+ });
+ });
+});
+
+// ================================================================================================
+// exportStore tests
+// ================================================================================================
+
+describe("exportStore", () => {
+ let logSpy: any;
+
+ beforeEach(() => {
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ logSpy.mockRestore();
+ });
+
+ it("exports an empty DB as a JSON object with all table keys present", async () => {
+ const dbId = await openTestDb();
+ const jsonStr = await exportStore(dbId);
+ const parsed = JSON.parse(jsonStr);
+
+ // All tables in the schema should be present as keys
+ const db = getDatabase(dbId);
+ const tableNames = db.dexie.tables.map((t) => t.name);
+ for (const name of tableNames) {
+ expect(parsed).toHaveProperty(name);
+ expect(Array.isArray(parsed[name])).toBe(true);
+ }
+ });
+
+ it("empty DB tables are empty arrays", async () => {
+ const dbId = await openTestDb();
+ const jsonStr = await exportStore(dbId);
+ const parsed = JSON.parse(jsonStr);
+
+ // stateSync gets one row on populate and settings gets the clientVersion row.
+ // Everything else should be empty.
+ const db = getDatabase(dbId);
+ const tableNames = db.dexie.tables.map((t) => t.name);
+ const nonEmptyTables = tableNames.filter((name) => parsed[name].length > 0);
+ expect(nonEmptyTables).toEqual(
+ expect.arrayContaining(["stateSync", "settings"])
+ );
+ // tables other than these two must be empty
+ const otherNonEmpty = nonEmptyTables.filter(
+ (n) => n !== "stateSync" && n !== "settings"
+ );
+ expect(otherNonEmpty).toHaveLength(0);
+ });
+
+ it("exports inputNotes rows and serializes Uint8Array fields as tagged base64", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+
+ const assetBytes = new Uint8Array([10, 20, 30]);
+ const serialBytes = new Uint8Array([1, 2, 3, 4]);
+ const inputsBytes = new Uint8Array([5, 6]);
+ const stateBytes = new Uint8Array([7, 8, 9]);
+
+ await db.inputNotes.put({
+ noteId: "note-abc",
+ stateDiscriminant: 0,
+ assets: assetBytes,
+ serialNumber: serialBytes,
+ inputs: inputsBytes,
+ scriptRoot: "script-root-x",
+ nullifier: "nullifier-abc",
+ serializedCreatedAt: "2024-01-01",
+ state: stateBytes,
+ });
+
+ const jsonStr = await exportStore(dbId);
+ const parsed = JSON.parse(jsonStr);
+
+ expect(parsed.inputNotes).toHaveLength(1);
+ const note = parsed.inputNotes[0];
+
+ // Uint8Array fields should be serialized as tagged base64
+ expect(note.assets).toEqual({
+ __type: "Uint8Array",
+ data: uint8ArrayToBase64(assetBytes),
+ });
+ expect(note.serialNumber).toEqual({
+ __type: "Uint8Array",
+ data: uint8ArrayToBase64(serialBytes),
+ });
+ expect(note.state).toEqual({
+ __type: "Uint8Array",
+ data: uint8ArrayToBase64(stateBytes),
+ });
+
+ // Primitive fields stay as-is
+ expect(note.noteId).toBe("note-abc");
+ expect(note.scriptRoot).toBe("script-root-x");
+ expect(note.nullifier).toBe("nullifier-abc");
+ });
+
+ it("exports multiple tables with data", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+
+ await db.accountCodes.put({
+ root: "root-1",
+ code: new Uint8Array([1, 2]),
+ });
+
+ await db.settings.put({
+ key: "test-key",
+ value: new Uint8Array([3, 4]),
+ });
+
+ const jsonStr = await exportStore(dbId);
+ const parsed = JSON.parse(jsonStr);
+
+ expect(parsed.accountCode).toHaveLength(1);
+ expect(parsed.accountCode[0].root).toBe("root-1");
+ expect(parsed.accountCode[0].code).toEqual({
+ __type: "Uint8Array",
+ data: uint8ArrayToBase64(new Uint8Array([1, 2])),
+ });
+
+ // settings has the initial clientVersion row + our test-key
+ const settingsKeys = parsed.settings.map((s: any) => s.key);
+ expect(settingsKeys).toContain("test-key");
+ });
+
+ it("throws for a db that was never opened", async () => {
+ await expect(exportStore("never-opened")).rejects.toThrow();
+ });
+});
diff --git a/crates/idxdb-store/src/ts/import.test.ts b/crates/idxdb-store/src/ts/import.test.ts
new file mode 100644
index 0000000..80bc27c
--- /dev/null
+++ b/crates/idxdb-store/src/ts/import.test.ts
@@ -0,0 +1,294 @@
+import { describe, it, expect, afterEach, vi, beforeEach } from "vitest";
+import { openDatabase, getDatabase } from "./schema.js";
+import { exportStore } from "./export.js";
+import { forceImportStore, transformForImport } from "./import.js";
+import { uint8ArrayToBase64 } from "./utils.js";
+
+let dbCounter = 0;
+function uniqueDbName(): string {
+ return `test-import-${++dbCounter}-${Date.now()}`;
+}
+
+const openDbIds: string[] = [];
+
+afterEach(async () => {
+ for (const dbId of openDbIds) {
+ const db = getDatabase(dbId);
+ db.dexie.close();
+ await db.dexie.delete();
+ }
+ openDbIds.length = 0;
+});
+
+async function openTestDb(): Promise {
+ const name = uniqueDbName();
+ await openDatabase(name, "0.1.0");
+ openDbIds.push(name);
+ return name;
+}
+
+// ================================================================================================
+// transformForImport unit tests
+// ================================================================================================
+
+describe("transformForImport", () => {
+ let logSpy: any;
+
+ beforeEach(() => {
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ logSpy.mockRestore();
+ });
+
+ it("converts a tagged Uint8Array object back to Uint8Array", async () => {
+ const original = new Uint8Array([1, 2, 3]);
+ const encoded = {
+ __type: "Uint8Array",
+ data: uint8ArrayToBase64(original),
+ };
+ const result = await transformForImport(encoded);
+ expect(result).toBeInstanceOf(Uint8Array);
+ expect(result).toEqual(original);
+ });
+
+ it("converts a tagged Blob object back to Blob", async () => {
+ const original = new Uint8Array([4, 5, 6]);
+ const encoded = { __type: "Blob", data: uint8ArrayToBase64(original) };
+ const result = await transformForImport(encoded);
+ expect(result).toBeInstanceOf(Blob);
+ const buf = await result.arrayBuffer();
+ expect(new Uint8Array(buf)).toEqual(original);
+ });
+
+ it("transforms an array recursively", async () => {
+ const original = new Uint8Array([7]);
+ const encoded = [
+ { __type: "Uint8Array", data: uint8ArrayToBase64(original) },
+ 42,
+ "hello",
+ ];
+ const result = await transformForImport(encoded);
+ expect(result[0]).toEqual(original);
+ expect(result[1]).toBe(42);
+ expect(result[2]).toBe("hello");
+ });
+
+ it("transforms a nested record recursively", async () => {
+ const bytes = new Uint8Array([8, 9]);
+ const encoded = {
+ data: { __type: "Uint8Array", data: uint8ArrayToBase64(bytes) },
+ count: 5,
+ };
+ const result = await transformForImport(encoded);
+ expect(result.data).toEqual(bytes);
+ expect(result.count).toBe(5);
+ });
+
+ it("returns primitives unchanged", async () => {
+ expect(await transformForImport(42)).toBe(42);
+ expect(await transformForImport("hello")).toBe("hello");
+ expect(await transformForImport(null)).toBeNull();
+ expect(await transformForImport(true)).toBe(true);
+ });
+
+ it("round-trips through export transformForExport", async () => {
+ const original = new Uint8Array([10, 20, 30]);
+ const { transformForExport } = await import("./export.js");
+ const exported = await transformForExport(original);
+ const reimported = await transformForImport(exported);
+ expect(reimported).toEqual(original);
+ });
+});
+
+// ================================================================================================
+// forceImportStore tests
+// ================================================================================================
+
+describe("forceImportStore", () => {
+ let logSpy: any;
+ let errorSpy: any;
+ let warnSpy: any;
+
+ beforeEach(() => {
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
+ errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
+ warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ logSpy.mockRestore();
+ errorSpy.mockRestore();
+ warnSpy.mockRestore();
+ });
+
+ it("round-trip: export DB-A, import into DB-B, rows match", async () => {
+ const dbIdA = await openTestDb();
+ const dbA = getDatabase(dbIdA);
+
+ const assetBytes = new Uint8Array([11, 22, 33]);
+ const serialBytes = new Uint8Array([44, 55, 66, 77]);
+ const inputsBytes = new Uint8Array([1]);
+ const stateBytes = new Uint8Array([2, 3]);
+
+ // Insert a row into DB-A
+ await dbA.inputNotes.put({
+ noteId: "round-trip-note",
+ stateDiscriminant: 0,
+ assets: assetBytes,
+ serialNumber: serialBytes,
+ inputs: inputsBytes,
+ scriptRoot: "sr-round-trip",
+ nullifier: "nullifier-round-trip",
+ serializedCreatedAt: "2024-06-01",
+ state: stateBytes,
+ });
+
+ // Export DB-A
+ const jsonStr = await exportStore(dbIdA);
+
+ // Open DB-B and import
+ const dbIdB = await openTestDb();
+ await forceImportStore(dbIdB, jsonStr);
+
+ // Verify DB-B has the same inputNotes row
+ const dbB = getDatabase(dbIdB);
+ const notesB = await dbB.inputNotes.toArray();
+ const imported = notesB.find((n) => n.noteId === "round-trip-note");
+ expect(imported).toBeDefined();
+ expect(imported!.assets).toEqual(assetBytes);
+ expect(imported!.serialNumber).toEqual(serialBytes);
+ expect(imported!.inputs).toEqual(inputsBytes);
+ expect(imported!.state).toEqual(stateBytes);
+ expect(imported!.scriptRoot).toBe("sr-round-trip");
+ });
+
+ it("round-trip preserves all populated tables", async () => {
+ const dbIdA = await openTestDb();
+ const dbA = getDatabase(dbIdA);
+
+ await dbA.accountCodes.put({
+ root: "root-rt",
+ code: new Uint8Array([1, 2, 3]),
+ });
+
+ await dbA.tags.put({ tag: "tag-rt" });
+
+ const jsonStr = await exportStore(dbIdA);
+
+ const dbIdB = await openTestDb();
+ await forceImportStore(dbIdB, jsonStr);
+
+ const dbB = getDatabase(dbIdB);
+ const codesB = await dbB.accountCodes.toArray();
+ expect(codesB).toHaveLength(1);
+ expect(codesB[0].root).toBe("root-rt");
+ expect(codesB[0].code).toEqual(new Uint8Array([1, 2, 3]));
+
+ const tagsB = await dbB.tags.toArray();
+ const tagFound = tagsB.find((t) => t.tag === "tag-rt");
+ expect(tagFound).toBeDefined();
+ });
+
+ it("import clears existing rows in target DB before importing", async () => {
+ const dbIdA = await openTestDb();
+ const dbA = getDatabase(dbIdA);
+
+ await dbA.accountCodes.put({ root: "root-a1", code: new Uint8Array([1]) });
+
+ const jsonStr = await exportStore(dbIdA);
+
+ const dbIdB = await openTestDb();
+ const dbB = getDatabase(dbIdB);
+
+ // Pre-populate DB-B with a row that should be wiped
+ await dbB.accountCodes.put({
+ root: "root-b-old",
+ code: new Uint8Array([9]),
+ });
+ expect(await dbB.accountCodes.count()).toBe(1);
+
+ await forceImportStore(dbIdB, jsonStr);
+
+ const codesAfter = await dbB.accountCodes.toArray();
+ // Only the row from DB-A should remain
+ expect(codesAfter.map((c) => c.root)).toContain("root-a1");
+ expect(codesAfter.map((c) => c.root)).not.toContain("root-b-old");
+ });
+
+ it("handles double-serialized JSON (string payload)", async () => {
+ const dbIdA = await openTestDb();
+ const jsonStr = await exportStore(dbIdA);
+ // Double-encode: JSON.stringify the string again
+ const doubleEncoded = JSON.stringify(jsonStr);
+
+ const dbIdB = await openTestDb();
+ // Should not throw — import.ts handles double-encoded payloads
+ await forceImportStore(dbIdB, doubleEncoded);
+ });
+
+ it("throws when payload has no tables (empty JSON object {})", async () => {
+ const dbId = await openTestDb();
+ // {} parses to an object with zero keys — triggers "No tables found" error
+ await expect(forceImportStore(dbId, "{}")).rejects.toThrow(
+ "No tables found"
+ );
+ });
+
+ it("throws when the payload contains only unknown table names", async () => {
+ // Dexie.table() throws InvalidTableError before the warn+skip guard in import.ts
+ // can fire, because the table name is not in the transaction scope. This verifies
+ // the real (observed) behavior of the source rather than its intent comment.
+ const dbId = await openTestDb();
+ const payload = JSON.stringify({ unknownTable: [{ id: 1, value: "x" }] });
+ await expect(forceImportStore(dbId, payload)).rejects.toThrow();
+ });
+
+ it("warn+skip guard: covers lines 86-90 by mocking dexie.table not to throw for unknown names", async () => {
+ // The warn+skip guard in import.ts (lines 85-90) is normally dead code because
+ // db.dexie.table() throws InvalidTableError for unknown tables before the guard is reached.
+ // We mock the table accessor to make it return a fake table object for unknown names,
+ // allowing execution to reach the guard and exercise the console.warn + continue path.
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+
+ const origTable = db.dexie.table.bind(db.dexie);
+ const fakeBulkPut = vi.fn().mockResolvedValue(undefined);
+ const fakeTableStub = { bulkPut: fakeBulkPut };
+
+ vi.spyOn(db.dexie, "table").mockImplementation((name: string) => {
+ // For unknown table names, return a stub instead of throwing.
+ // For known tables, delegate to the real implementation.
+ try {
+ return origTable(name);
+ } catch {
+ return fakeTableStub as any;
+ }
+ });
+
+ const payload = JSON.stringify({ unknownTable: [{ id: 1 }] });
+
+ // Should resolve without error (unknown table is warned and skipped)
+ await forceImportStore(dbId, payload);
+
+ expect(warnSpy).toHaveBeenCalledWith(
+ expect.stringContaining("unknownTable")
+ );
+ // The stub's bulkPut should NOT have been called (we skipped it)
+ expect(fakeBulkPut).not.toHaveBeenCalled();
+
+ vi.restoreAllMocks();
+ });
+
+ it("throws for a db that was never opened", async () => {
+ await expect(
+ forceImportStore("never-opened", JSON.stringify({ someTable: [] }))
+ ).rejects.toThrow();
+ });
+
+ it("throws on malformed JSON payload", async () => {
+ const dbId = await openTestDb();
+ await expect(forceImportStore(dbId, "not-valid-json{{")).rejects.toThrow();
+ });
+});
diff --git a/crates/idxdb-store/src/ts/notes.test.ts b/crates/idxdb-store/src/ts/notes.test.ts
index 43b57d2..0686a7f 100644
--- a/crates/idxdb-store/src/ts/notes.test.ts
+++ b/crates/idxdb-store/src/ts/notes.test.ts
@@ -1,6 +1,19 @@
import { describe, it, expect, afterEach } from "vitest";
import { openDatabase, getDatabase } from "./schema.js";
-import { upsertInputNote, getInputNoteByOffset } from "./notes.js";
+import {
+ upsertInputNote,
+ upsertOutputNote,
+ upsertNoteScript,
+ getInputNoteByOffset,
+ getInputNotes,
+ getInputNotesFromIds,
+ getInputNotesFromNullifiers,
+ getOutputNotes,
+ getOutputNotesFromIds,
+ getOutputNotesFromNullifiers,
+ getUnspentInputNoteNullifiers,
+ getNoteScript,
+} from "./notes.js";
// Unique DB names to avoid collisions between tests.
let dbCounter = 0;
@@ -39,6 +52,11 @@ const CONSUMED_STATES = new Uint8Array([
STATE_CONSUMED_EXTERNAL,
]);
+// Unspent state discriminants (stateDiscriminant 2, 4, 5)
+const STATE_COMMITTED = 2;
+const STATE_PROCESSING_AUTHENTICATED = 4;
+const STATE_PROCESSING_UNAUTHENTICATED = 5;
+
const DUMMY_BYTES = new Uint8Array([1, 2, 3]);
const DUMMY_SCRIPT_ROOT = "script-root-1";
@@ -55,6 +73,8 @@ async function insertNote(
consumedBlockHeight?: number;
consumedTxOrder?: number;
consumerAccountId?: string;
+ scriptRoot?: string;
+ nullifier?: string;
} = {}
) {
await upsertInputNote(
@@ -63,9 +83,9 @@ async function insertNote(
DUMMY_BYTES,
DUMMY_BYTES,
DUMMY_BYTES,
- DUMMY_SCRIPT_ROOT,
+ opts.scriptRoot ?? DUMMY_SCRIPT_ROOT,
DUMMY_BYTES,
- `nullifier-${noteId}`,
+ opts.nullifier ?? `nullifier-${noteId}`,
noteId, // store noteId as createdAt so we can read it back from processed output
opts.stateDiscriminant ?? STATE_CONSUMED_EXTERNAL,
DUMMY_BYTES,
@@ -296,6 +316,74 @@ describe("getInputNoteByOffset block range filtering", () => {
});
});
+describe("getInputNoteByOffset unordered-filter branches", () => {
+ it("excludes unordered notes with null consumedBlockHeight when blockStart is set", async () => {
+ // Exercises the `consumedBlockHeight == null` branch in the unordered filter
+ // (line 218 of notes.ts: blockStart != null && (consumedBlockHeight == null || ...))
+ const dbId = await openTestDb();
+
+ await insertNote(dbId, "note-ordered-b5", {
+ consumedBlockHeight: 5,
+ consumedTxOrder: 0,
+ });
+ // Unordered note with null consumedBlockHeight — should be excluded by blockStart filter
+ await insertNote(dbId, "note-unordered-no-height", {
+ // no consumedBlockHeight — null
+ });
+
+ const ids = await collectAllNoteIds(
+ dbId,
+ CONSUMED_STATES,
+ undefined,
+ 3,
+ undefined
+ );
+ expect(ids).toContain("note-ordered-b5");
+ expect(ids).not.toContain("note-unordered-no-height");
+ });
+
+ it("excludes unordered notes with null consumedBlockHeight when blockEnd is set", async () => {
+ // Exercises the `consumedBlockHeight == null` branch in the unordered filter
+ // (line 220 of notes.ts: blockEnd != null && (consumedBlockHeight == null || ...))
+ const dbId = await openTestDb();
+
+ await insertNote(dbId, "note-ordered-b5", {
+ consumedBlockHeight: 5,
+ consumedTxOrder: 0,
+ });
+ await insertNote(dbId, "note-unordered-no-height-2", {
+ // no consumedBlockHeight — null
+ });
+
+ const ids = await collectAllNoteIds(
+ dbId,
+ CONSUMED_STATES,
+ undefined,
+ undefined,
+ 10
+ );
+ expect(ids).toContain("note-ordered-b5");
+ expect(ids).not.toContain("note-unordered-no-height-2");
+ });
+
+ it("excludes unordered notes with consumerAccountId != undefined (line 217 branch)", async () => {
+ // In the unordered path, consumerAccountId filter is undefined. If a note has
+ // a non-undefined consumerAccountId, it should be excluded via line 217.
+ const dbId = await openTestDb();
+
+ await insertNote(dbId, "note-no-tx-with-consumer", {
+ consumerAccountId: "0xsomeconsumer",
+ consumedBlockHeight: 5,
+ // no consumedTxOrder — so not in compound index
+ });
+
+ // Query with no consumer (undefined) — the unordered filter line 217 excludes
+ // notes with a different consumerAccountId
+ const ids = await collectAllNoteIds(dbId, CONSUMED_STATES);
+ expect(ids).not.toContain("note-no-tx-with-consumer");
+ });
+});
+
// STATE FILTER TESTS
// ================================================================================================
@@ -330,3 +418,556 @@ describe("getInputNoteByOffset state filtering", () => {
expect(result).toEqual([]);
});
});
+
+// ================================================================================================
+// getInputNotes
+// ================================================================================================
+
+describe("getInputNotes", () => {
+ it("returns all notes when states is empty", async () => {
+ const dbId = await openTestDb();
+ await insertNote(dbId, "n1", {
+ stateDiscriminant: STATE_CONSUMED_EXTERNAL,
+ consumedBlockHeight: 1,
+ });
+ await insertNote(dbId, "n2", { stateDiscriminant: STATE_EXPECTED });
+ const result = await getInputNotes(dbId, new Uint8Array([]));
+ expect(result).toHaveLength(2);
+ });
+
+ it("filters by state discriminants when non-empty", async () => {
+ const dbId = await openTestDb();
+ await insertNote(dbId, "n-consumed", {
+ stateDiscriminant: STATE_CONSUMED_EXTERNAL,
+ consumedBlockHeight: 1,
+ });
+ await insertNote(dbId, "n-expected", { stateDiscriminant: STATE_EXPECTED });
+ const result = await getInputNotes(
+ dbId,
+ new Uint8Array([STATE_CONSUMED_EXTERNAL])
+ );
+ expect(result).toHaveLength(1);
+ // createdAt holds the noteId
+ expect(result![0].createdAt).toBe("n-consumed");
+ });
+
+ it("returns empty array when no notes exist", async () => {
+ const dbId = await openTestDb();
+ const result = await getInputNotes(dbId, new Uint8Array([]));
+ expect(result).toEqual([]);
+ });
+
+ it("includes note script in processed result when available", async () => {
+ const dbId = await openTestDb();
+ const SCRIPT_ROOT = "my-script-root";
+ await insertNote(dbId, "note-with-script", {
+ stateDiscriminant: STATE_CONSUMED_EXTERNAL,
+ consumedBlockHeight: 1,
+ scriptRoot: SCRIPT_ROOT,
+ });
+ const result = await getInputNotes(
+ dbId,
+ new Uint8Array([STATE_CONSUMED_EXTERNAL])
+ );
+ expect(result).toHaveLength(1);
+ // Script was inserted via upsertInputNote → notesScripts table
+ expect(result![0].serializedNoteScript).toBeDefined();
+ expect(typeof result![0].serializedNoteScript).toBe("string");
+ });
+
+ it("returns undefined for serializedNoteScript when script root is empty", async () => {
+ const dbId = await openTestDb();
+ // Insert with empty scriptRoot
+ await upsertInputNote(
+ dbId,
+ "note-no-script",
+ DUMMY_BYTES,
+ DUMMY_BYTES,
+ DUMMY_BYTES,
+ "", // empty script root
+ DUMMY_BYTES,
+ "null-nullifier",
+ "note-no-script",
+ STATE_CONSUMED_EXTERNAL,
+ DUMMY_BYTES,
+ 1,
+ 0,
+ undefined
+ );
+ const result = await getInputNotes(
+ dbId,
+ new Uint8Array([STATE_CONSUMED_EXTERNAL])
+ );
+ expect(result).toHaveLength(1);
+ expect(result![0].serializedNoteScript).toBeUndefined();
+ });
+});
+
+// ================================================================================================
+// getInputNotesFromIds
+// ================================================================================================
+
+describe("getInputNotesFromIds", () => {
+ it("returns notes matching the given IDs", async () => {
+ const dbId = await openTestDb();
+ await insertNote(dbId, "id-note-1", {
+ stateDiscriminant: STATE_CONSUMED_EXTERNAL,
+ consumedBlockHeight: 1,
+ });
+ await insertNote(dbId, "id-note-2", {
+ stateDiscriminant: STATE_CONSUMED_EXTERNAL,
+ consumedBlockHeight: 2,
+ });
+ await insertNote(dbId, "id-note-3", { stateDiscriminant: STATE_EXPECTED });
+
+ const result = await getInputNotesFromIds(dbId, ["id-note-1", "id-note-2"]);
+ expect(result).toHaveLength(2);
+ });
+
+ it("returns empty array for unmatched IDs", async () => {
+ const dbId = await openTestDb();
+ const result = await getInputNotesFromIds(dbId, ["nonexistent"]);
+ expect(result).toEqual([]);
+ });
+});
+
+// ================================================================================================
+// getInputNotesFromNullifiers
+// ================================================================================================
+
+describe("getInputNotesFromNullifiers", () => {
+ it("returns notes matching the given nullifiers", async () => {
+ const dbId = await openTestDb();
+ await insertNote(dbId, "null-note-1", {
+ stateDiscriminant: STATE_CONSUMED_EXTERNAL,
+ consumedBlockHeight: 1,
+ nullifier: "0xnullifier1",
+ });
+ await insertNote(dbId, "null-note-2", {
+ stateDiscriminant: STATE_CONSUMED_EXTERNAL,
+ consumedBlockHeight: 2,
+ nullifier: "0xnullifier2",
+ });
+
+ const result = await getInputNotesFromNullifiers(dbId, ["0xnullifier1"]);
+ expect(result).toHaveLength(1);
+ expect(result![0].createdAt).toBe("null-note-1");
+ });
+
+ it("returns empty array for unknown nullifiers", async () => {
+ const dbId = await openTestDb();
+ const result = await getInputNotesFromNullifiers(dbId, ["0xunknown"]);
+ expect(result).toEqual([]);
+ });
+});
+
+// ================================================================================================
+// getUnspentInputNoteNullifiers
+// ================================================================================================
+
+describe("getUnspentInputNoteNullifiers", () => {
+ it("returns nullifiers for notes with discriminant 2, 4, or 5", async () => {
+ const dbId = await openTestDb();
+ await insertNote(dbId, "note-committed", {
+ stateDiscriminant: STATE_COMMITTED,
+ nullifier: "0xnull-committed",
+ });
+ await insertNote(dbId, "note-proc-auth", {
+ stateDiscriminant: STATE_PROCESSING_AUTHENTICATED,
+ nullifier: "0xnull-proc-auth",
+ });
+ await insertNote(dbId, "note-proc-unauth", {
+ stateDiscriminant: STATE_PROCESSING_UNAUTHENTICATED,
+ nullifier: "0xnull-proc-unauth",
+ });
+ await insertNote(dbId, "note-expected", {
+ stateDiscriminant: STATE_EXPECTED,
+ nullifier: "0xnull-expected",
+ });
+
+ const nullifiers = await getUnspentInputNoteNullifiers(dbId);
+ expect(nullifiers).toHaveLength(3);
+ expect(nullifiers).toContain("0xnull-committed");
+ expect(nullifiers).toContain("0xnull-proc-auth");
+ expect(nullifiers).toContain("0xnull-proc-unauth");
+ expect(nullifiers).not.toContain("0xnull-expected");
+ });
+
+ it("returns empty array when no unspent notes", async () => {
+ const dbId = await openTestDb();
+ const nullifiers = await getUnspentInputNoteNullifiers(dbId);
+ expect(nullifiers).toEqual([]);
+ });
+});
+
+// ================================================================================================
+// getNoteScript
+// ================================================================================================
+
+describe("getNoteScript", () => {
+ it("returns undefined when script not found", async () => {
+ const dbId = await openTestDb();
+ const result = await getNoteScript(dbId, "nonexistent-root");
+ expect(result).toBeUndefined();
+ });
+
+ it("returns the script record when found", async () => {
+ const dbId = await openTestDb();
+ const scriptRoot = "my-script";
+ const scriptBytes = new Uint8Array([7, 8, 9]);
+ await upsertNoteScript(dbId, scriptRoot, scriptBytes);
+ const result = await getNoteScript(dbId, scriptRoot);
+ expect(result).toBeDefined();
+ expect(result!.scriptRoot).toBe(scriptRoot);
+ expect(result!.serializedNoteScript).toEqual(scriptBytes);
+ });
+});
+
+// ================================================================================================
+// upsertNoteScript
+// ================================================================================================
+
+describe("upsertNoteScript", () => {
+ it("inserts and overwrites a note script", async () => {
+ const dbId = await openTestDb();
+ const scriptRoot = "root-1";
+ await upsertNoteScript(dbId, scriptRoot, new Uint8Array([1, 2, 3]));
+ await upsertNoteScript(dbId, scriptRoot, new Uint8Array([4, 5, 6]));
+ const result = await getNoteScript(dbId, scriptRoot);
+ expect(result!.serializedNoteScript).toEqual(new Uint8Array([4, 5, 6]));
+ });
+});
+
+// ================================================================================================
+// getOutputNotes
+// ================================================================================================
+
+describe("getOutputNotes", () => {
+ it("returns all output notes when states is empty", async () => {
+ const dbId = await openTestDb();
+ await upsertOutputNote(
+ dbId,
+ "out-1",
+ DUMMY_BYTES,
+ "recipient1",
+ DUMMY_BYTES,
+ "0xnull1",
+ 100,
+ 3,
+ DUMMY_BYTES
+ );
+ await upsertOutputNote(
+ dbId,
+ "out-2",
+ DUMMY_BYTES,
+ "recipient2",
+ DUMMY_BYTES,
+ undefined,
+ 200,
+ 4,
+ DUMMY_BYTES
+ );
+ const result = await getOutputNotes(dbId, new Uint8Array([]));
+ expect(result).toHaveLength(2);
+ });
+
+ it("filters output notes by state discriminant", async () => {
+ const dbId = await openTestDb();
+ await upsertOutputNote(
+ dbId,
+ "out-state3",
+ DUMMY_BYTES,
+ "r1",
+ DUMMY_BYTES,
+ "0xn1",
+ 100,
+ 3,
+ DUMMY_BYTES
+ );
+ await upsertOutputNote(
+ dbId,
+ "out-state4",
+ DUMMY_BYTES,
+ "r2",
+ DUMMY_BYTES,
+ "0xn2",
+ 200,
+ 4,
+ DUMMY_BYTES
+ );
+
+ const result = await getOutputNotes(dbId, new Uint8Array([3]));
+ expect(result).toHaveLength(1);
+ });
+
+ it("returns processed output note with base64 fields", async () => {
+ const dbId = await openTestDb();
+ await upsertOutputNote(
+ dbId,
+ "out-processed",
+ DUMMY_BYTES,
+ "recipient-x",
+ DUMMY_BYTES,
+ "0xnull-x",
+ 50,
+ 3,
+ DUMMY_BYTES
+ );
+ const result = await getOutputNotes(dbId, new Uint8Array([]));
+ expect(result).toHaveLength(1);
+ const note = result![0];
+ expect(typeof note.assets).toBe("string"); // base64
+ expect(typeof note.metadata).toBe("string"); // base64
+ expect(note.recipientDigest).toBe("recipient-x");
+ expect(note.expectedHeight).toBe(50);
+ });
+
+ it("returns empty array when no output notes", async () => {
+ const dbId = await openTestDb();
+ const result = await getOutputNotes(dbId, new Uint8Array([]));
+ expect(result).toEqual([]);
+ });
+});
+
+// ================================================================================================
+// getOutputNotesFromIds
+// ================================================================================================
+
+describe("getOutputNotesFromIds", () => {
+ it("returns output notes matching the given IDs", async () => {
+ const dbId = await openTestDb();
+ await upsertOutputNote(
+ dbId,
+ "out-id-1",
+ DUMMY_BYTES,
+ "r1",
+ DUMMY_BYTES,
+ "0xn1",
+ 100,
+ 3,
+ DUMMY_BYTES
+ );
+ await upsertOutputNote(
+ dbId,
+ "out-id-2",
+ DUMMY_BYTES,
+ "r2",
+ DUMMY_BYTES,
+ "0xn2",
+ 200,
+ 4,
+ DUMMY_BYTES
+ );
+
+ const result = await getOutputNotesFromIds(dbId, ["out-id-1"]);
+ expect(result).toHaveLength(1);
+ expect(result![0].recipientDigest).toBe("r1");
+ });
+
+ it("returns empty array for unmatched IDs", async () => {
+ const dbId = await openTestDb();
+ const result = await getOutputNotesFromIds(dbId, ["does-not-exist"]);
+ expect(result).toEqual([]);
+ });
+});
+
+// ================================================================================================
+// getOutputNotesFromNullifiers
+// ================================================================================================
+
+describe("getOutputNotesFromNullifiers", () => {
+ it("returns output notes matching the given nullifiers", async () => {
+ const dbId = await openTestDb();
+ await upsertOutputNote(
+ dbId,
+ "out-null-1",
+ DUMMY_BYTES,
+ "r1",
+ DUMMY_BYTES,
+ "0xoutnull1",
+ 100,
+ 3,
+ DUMMY_BYTES
+ );
+ await upsertOutputNote(
+ dbId,
+ "out-null-2",
+ DUMMY_BYTES,
+ "r2",
+ DUMMY_BYTES,
+ "0xoutnull2",
+ 200,
+ 4,
+ DUMMY_BYTES
+ );
+
+ const result = await getOutputNotesFromNullifiers(dbId, ["0xoutnull1"]);
+ expect(result).toHaveLength(1);
+ expect(result![0].recipientDigest).toBe("r1");
+ });
+
+ it("returns empty when nullifier not found", async () => {
+ const dbId = await openTestDb();
+ const result = await getOutputNotesFromNullifiers(dbId, ["0xunknown"]);
+ expect(result).toEqual([]);
+ });
+});
+
+// ================================================================================================
+// upsertInputNote with provided transaction
+// ================================================================================================
+
+describe("upsertInputNote with external transaction", () => {
+ it("uses an external transaction when provided", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+
+ // Pass a transaction object to upsertInputNote (the `tx` code path)
+ await db.dexie.transaction(
+ "rw",
+ db.inputNotes,
+ db.notesScripts,
+ async (tx) => {
+ await upsertInputNote(
+ dbId,
+ "tx-note-1",
+ DUMMY_BYTES,
+ DUMMY_BYTES,
+ DUMMY_BYTES,
+ "tx-script-root",
+ DUMMY_BYTES,
+ "tx-nullifier",
+ "tx-note-1",
+ STATE_CONSUMED_EXTERNAL,
+ DUMMY_BYTES,
+ 10,
+ 0,
+ undefined,
+ tx
+ );
+ }
+ );
+
+ const result = await getInputNotesFromIds(dbId, ["tx-note-1"]);
+ expect(result).toHaveLength(1);
+ });
+});
+
+// ================================================================================================
+// upsertOutputNote with external transaction
+// ================================================================================================
+
+describe("upsertOutputNote with external transaction", () => {
+ it("uses an external transaction when provided", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+
+ await db.dexie.transaction(
+ "rw",
+ db.outputNotes,
+ db.notesScripts,
+ async (tx) => {
+ await upsertOutputNote(
+ dbId,
+ "out-tx-1",
+ DUMMY_BYTES,
+ "recipient-tx",
+ DUMMY_BYTES,
+ "0xtxnull",
+ 999,
+ 3,
+ DUMMY_BYTES,
+ tx
+ );
+ }
+ );
+
+ const result = await getOutputNotesFromIds(dbId, ["out-tx-1"]);
+ expect(result).toHaveLength(1);
+ expect(result![0].recipientDigest).toBe("recipient-tx");
+ });
+});
+
+// ================================================================================================
+// Error-path coverage: catch blocks call logWebStoreError (re-throws)
+// Passing an unregistered dbId exercises the catch body in each function.
+// ================================================================================================
+const BAD_DB = "does-not-exist-notes";
+
+describe("error paths: unregistered dbId re-throws", () => {
+ it("getOutputNotes rejects on bad dbId", async () => {
+ await expect(getOutputNotes(BAD_DB, new Uint8Array([]))).rejects.toThrow();
+ });
+
+ it("getInputNotes rejects on bad dbId", async () => {
+ await expect(getInputNotes(BAD_DB, new Uint8Array([]))).rejects.toThrow();
+ });
+
+ it("getInputNotesFromIds rejects on bad dbId", async () => {
+ await expect(getInputNotesFromIds(BAD_DB, ["id1"])).rejects.toThrow();
+ });
+
+ it("getInputNotesFromNullifiers rejects on bad dbId", async () => {
+ await expect(
+ getInputNotesFromNullifiers(BAD_DB, ["null1"])
+ ).rejects.toThrow();
+ });
+
+ it("getOutputNotesFromNullifiers rejects on bad dbId", async () => {
+ await expect(
+ getOutputNotesFromNullifiers(BAD_DB, ["null1"])
+ ).rejects.toThrow();
+ });
+
+ it("getOutputNotesFromIds rejects on bad dbId", async () => {
+ await expect(getOutputNotesFromIds(BAD_DB, ["id1"])).rejects.toThrow();
+ });
+
+ it("getUnspentInputNoteNullifiers rejects on bad dbId", async () => {
+ await expect(getUnspentInputNoteNullifiers(BAD_DB)).rejects.toThrow();
+ });
+
+ it("getNoteScript rejects on bad dbId", async () => {
+ await expect(getNoteScript(BAD_DB, "root1")).rejects.toThrow();
+ });
+
+ it("getInputNoteByOffset rejects on bad dbId", async () => {
+ await expect(
+ getInputNoteByOffset(
+ BAD_DB,
+ new Uint8Array([]),
+ undefined,
+ undefined,
+ undefined,
+ 0
+ )
+ ).rejects.toThrow();
+ });
+
+ it("upsertInputNote rejects on bad dbId (no tx, bad db)", async () => {
+ await expect(
+ upsertInputNote(
+ BAD_DB,
+ "note-1",
+ DUMMY_BYTES,
+ DUMMY_BYTES,
+ DUMMY_BYTES,
+ "root",
+ DUMMY_BYTES,
+ "null-1",
+ "note-1",
+ 0,
+ DUMMY_BYTES,
+ undefined,
+ undefined,
+ undefined
+ )
+ ).rejects.toThrow();
+ });
+
+ it("upsertNoteScript rejects on bad dbId", async () => {
+ await expect(
+ upsertNoteScript(BAD_DB, "root", new Uint8Array([1]))
+ ).rejects.toThrow();
+ });
+});
diff --git a/crates/idxdb-store/src/ts/schema.test.ts b/crates/idxdb-store/src/ts/schema.test.ts
index 6a3ebbf..79e3441 100644
--- a/crates/idxdb-store/src/ts/schema.test.ts
+++ b/crates/idxdb-store/src/ts/schema.test.ts
@@ -1,5 +1,11 @@
import { describe, it, expect, afterEach } from "vitest";
import Dexie from "dexie";
+import {
+ openDatabase,
+ getDatabase,
+ MidenDatabase,
+ CLIENT_VERSION_SETTING_KEY,
+} from "./schema.js";
import { uniqueDbName } from "./test-utils.js";
const encoder = new TextEncoder();
@@ -21,6 +27,22 @@ function trackDb(db: Dexie): Dexie {
return db;
}
+// Track MidenDatabase instances separately (they wrap a Dexie under .dexie)
+const openMidenDbs: MidenDatabase[] = [];
+
+afterEach(async () => {
+ for (const mdb of openMidenDbs) {
+ mdb.dexie.close();
+ await mdb.dexie.delete();
+ }
+ openMidenDbs.length = 0;
+});
+
+function trackMidenDb(mdb: MidenDatabase): MidenDatabase {
+ openMidenDbs.push(mdb);
+ return mdb;
+}
+
describe("MidenDatabase migrations", () => {
// Placeholder for the actual v1→v2 migration test. When the first real
// migration is introduced, replace the dummy schema and upgrade logic below
@@ -85,3 +107,188 @@ describe("MidenDatabase migrations", () => {
expect(decoder.decode(setting.value)).toBe("blue");
});
});
+
+// ============================================================
+// openDatabase
+// ============================================================
+describe("openDatabase", () => {
+ it("opens a fresh database and registers it in the registry", async () => {
+ const name = uniqueDbName();
+ const dbId = await openDatabase(name, "1.0.0");
+ openMidenDbs.push(getDatabase(dbId));
+ expect(dbId).toBe(name);
+ const db = getDatabase(dbId);
+ expect(db).toBeDefined();
+ });
+
+ it("persists the client version on first open", async () => {
+ const name = uniqueDbName();
+ await openDatabase(name, "1.0.0");
+ const db = getDatabase(name);
+ openMidenDbs.push(db);
+ const record = await db.settings.get(CLIENT_VERSION_SETTING_KEY);
+ expect(record).toBeDefined();
+ expect(new TextDecoder().decode(record!.value)).toBe("1.0.0");
+ });
+});
+
+// ============================================================
+// ensureClientVersion — same version (no-op)
+// ============================================================
+describe("ensureClientVersion: same version already stored", () => {
+ it("re-opening with the same version is a no-op", async () => {
+ const name = uniqueDbName();
+ // First open
+ await openDatabase(name, "2.3.4");
+ const db1 = getDatabase(name);
+ openMidenDbs.push(db1);
+
+ // Insert a sentinel row that should survive if the DB is NOT nuked
+ await db1.settings.put({
+ key: "sentinel",
+ value: new TextEncoder().encode("alive"),
+ });
+
+ // Close and re-open with the same version
+ db1.dexie.close();
+
+ const mdb2 = trackMidenDb(new MidenDatabase(name));
+ const success = await mdb2.open("2.3.4");
+ expect(success).toBe(true);
+
+ // Sentinel must still be there
+ const sentinel = await mdb2.settings.get("sentinel");
+ expect(sentinel).toBeDefined();
+ expect(new TextDecoder().decode(sentinel!.value)).toBe("alive");
+ });
+});
+
+// ============================================================
+// ensureClientVersion — same major.minor, patch bump (update only)
+// ============================================================
+describe("ensureClientVersion: same major.minor, new patch", () => {
+ it("updates persisted version without nuking the store", async () => {
+ const name = uniqueDbName();
+ await openDatabase(name, "1.2.0");
+ const db1 = getDatabase(name);
+ openMidenDbs.push(db1);
+ await db1.settings.put({
+ key: "sentinel",
+ value: new TextEncoder().encode("safe"),
+ });
+ db1.dexie.close();
+
+ // Patch bump: 1.2.0 → 1.2.5
+ const mdb2 = trackMidenDb(new MidenDatabase(name));
+ const success = await mdb2.open("1.2.5");
+ expect(success).toBe(true);
+
+ // Sentinel must survive (no nuke)
+ const sentinel = await mdb2.settings.get("sentinel");
+ expect(sentinel).toBeDefined();
+
+ // Version must be updated
+ const versionRecord = await mdb2.settings.get(CLIENT_VERSION_SETTING_KEY);
+ expect(new TextDecoder().decode(versionRecord!.value)).toBe("1.2.5");
+ });
+});
+
+// ============================================================
+// ensureClientVersion — stored version is newer than requested (downgrade path)
+// ============================================================
+describe("ensureClientVersion: stored version is newer (downgrade path)", () => {
+ it("does not nuke on downgrade — updates persisted version only", async () => {
+ const name = uniqueDbName();
+ await openDatabase(name, "2.0.0");
+ const db1 = getDatabase(name);
+ openMidenDbs.push(db1);
+ await db1.settings.put({
+ key: "sentinel",
+ value: new TextEncoder().encode("present"),
+ });
+ db1.dexie.close();
+
+ // Open with an older version (1.9.0 < 2.0.0)
+ const mdb2 = trackMidenDb(new MidenDatabase(name));
+ await mdb2.open("1.9.0");
+
+ // The non-gt branch just persists the new version without nuking
+ const sentinel = await mdb2.settings.get("sentinel");
+ expect(sentinel).toBeDefined();
+ });
+});
+
+// ============================================================
+// ensureClientVersion — major version bump (nuke path)
+// ============================================================
+describe("ensureClientVersion: major version bump triggers nuke", () => {
+ it("nukes the database and persists the new version", async () => {
+ const name = uniqueDbName();
+ await openDatabase(name, "1.0.0");
+ const db1 = getDatabase(name);
+ openMidenDbs.push(db1);
+ // Insert a sentinel row that should be GONE after nuke
+ await db1.settings.put({
+ key: "sentinel",
+ value: new TextEncoder().encode("gone-after-nuke"),
+ });
+ db1.dexie.close();
+
+ // Open with a new major version (2.0.0 > 1.0.0, different minor)
+ const mdb2 = trackMidenDb(new MidenDatabase(name));
+ const success = await mdb2.open("2.0.0");
+ expect(success).toBe(true);
+
+ // Sentinel should be gone (DB was nuked)
+ const sentinel = await mdb2.settings.get("sentinel");
+ expect(sentinel).toBeUndefined();
+
+ // New version should be persisted
+ const versionRecord = await mdb2.settings.get(CLIENT_VERSION_SETTING_KEY);
+ expect(new TextDecoder().decode(versionRecord!.value)).toBe("2.0.0");
+ });
+});
+
+// ============================================================
+// ensureClientVersion — invalid semver strings (warn + nuke path)
+// ============================================================
+describe("ensureClientVersion: invalid semver strings", () => {
+ it("falls through to nuke when stored version is not valid semver", async () => {
+ const name = uniqueDbName();
+ // First open with a non-semver string
+ await openDatabase(name, "not-a-version");
+ const db1 = getDatabase(name);
+ openMidenDbs.push(db1);
+ await db1.settings.put({
+ key: "sentinel",
+ value: new TextEncoder().encode("will-be-nuked"),
+ });
+ db1.dexie.close();
+
+ // Re-open with a different non-semver string — triggers the else branch
+ const mdb2 = trackMidenDb(new MidenDatabase(name));
+ const success = await mdb2.open("also-not-a-version");
+ expect(success).toBe(true);
+
+ // After the nuke the sentinel is gone
+ const sentinel = await mdb2.settings.get("sentinel");
+ expect(sentinel).toBeUndefined();
+ });
+});
+
+// ============================================================
+// ensureClientVersion — empty clientVersion (warn + skip)
+// ============================================================
+describe("ensureClientVersion: empty clientVersion", () => {
+ it("skips version enforcement when clientVersion is empty string", async () => {
+ const name = uniqueDbName();
+ const mdb = trackMidenDb(new MidenDatabase(name));
+ // Pass empty string — should open successfully and skip enforcement
+ const success = await mdb.open("");
+ expect(success).toBe(true);
+
+ // No version record should be stored
+ const versionRecord = await mdb.settings.get(CLIENT_VERSION_SETTING_KEY);
+ expect(versionRecord).toBeUndefined();
+ });
+});
diff --git a/crates/idxdb-store/src/ts/settings.test.ts b/crates/idxdb-store/src/ts/settings.test.ts
new file mode 100644
index 0000000..0bcdc74
--- /dev/null
+++ b/crates/idxdb-store/src/ts/settings.test.ts
@@ -0,0 +1,119 @@
+import { describe, it, expect, afterEach, vi, beforeEach } from "vitest";
+import {
+ openDatabase,
+ getDatabase,
+ CLIENT_VERSION_SETTING_KEY,
+} from "./schema.js";
+import {
+ getSetting,
+ insertSetting,
+ removeSetting,
+ listSettingKeys,
+} from "./settings.js";
+
+let dbCounter = 0;
+function uniqueDbName(): string {
+ return `test-settings-${++dbCounter}-${Date.now()}`;
+}
+
+const openDbIds: string[] = [];
+
+afterEach(async () => {
+ for (const dbId of openDbIds) {
+ const db = getDatabase(dbId);
+ db.dexie.close();
+ await db.dexie.delete();
+ }
+ openDbIds.length = 0;
+});
+
+async function openTestDb(): Promise {
+ const name = uniqueDbName();
+ await openDatabase(name, "0.1.0");
+ openDbIds.push(name);
+ return name;
+}
+
+describe("settings", () => {
+ let errorSpy: any;
+ let logSpy: any;
+
+ beforeEach(() => {
+ errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ errorSpy.mockRestore();
+ logSpy.mockRestore();
+ });
+
+ it("returns null when key is missing", async () => {
+ const dbId = await openTestDb();
+ const result = await getSetting(dbId, "nope");
+ expect(result).toBeNull();
+ });
+
+ it("inserts and retrieves a setting", async () => {
+ const dbId = await openTestDb();
+ const value = new Uint8Array([1, 2, 3]);
+ await insertSetting(dbId, "k1", value);
+ const got = await getSetting(dbId, "k1");
+ expect(got).toEqual({ key: "k1", value: "AQID" });
+ });
+
+ it("upserts on duplicate key", async () => {
+ const dbId = await openTestDb();
+ await insertSetting(dbId, "k1", new Uint8Array([1]));
+ await insertSetting(dbId, "k1", new Uint8Array([2]));
+ const got = await getSetting(dbId, "k1");
+ expect(got!.value).toBe("Ag==");
+ });
+
+ it("removes a setting", async () => {
+ const dbId = await openTestDb();
+ await insertSetting(dbId, "k1", new Uint8Array([1]));
+ await removeSetting(dbId, "k1");
+ expect(await getSetting(dbId, "k1")).toBeNull();
+ });
+
+ it("removeSetting on a missing key is a no-op", async () => {
+ const dbId = await openTestDb();
+ await removeSetting(dbId, "nope");
+ // No throw means success.
+ });
+
+ it("listSettingKeys excludes internal keys", async () => {
+ const dbId = await openTestDb();
+ await insertSetting(dbId, "user-a", new Uint8Array([1]));
+ await insertSetting(dbId, "user-b", new Uint8Array([2]));
+ await insertSetting(dbId, CLIENT_VERSION_SETTING_KEY, new Uint8Array([3]));
+ const keys = await listSettingKeys(dbId);
+ expect(keys).toEqual(expect.arrayContaining(["user-a", "user-b"]));
+ expect(keys).not.toContain(CLIENT_VERSION_SETTING_KEY);
+ });
+
+ it("listSettingKeys returns empty list when no user keys are present", async () => {
+ const dbId = await openTestDb();
+ const keys = await listSettingKeys(dbId);
+ expect(keys).toEqual([]);
+ });
+
+ it("getSetting throws on Dexie error (e.g., db not opened)", async () => {
+ await expect(getSetting("never-opened", "k")).rejects.toThrow();
+ });
+
+ it("insertSetting throws on Dexie error", async () => {
+ await expect(
+ insertSetting("never-opened", "k", new Uint8Array([1]))
+ ).rejects.toThrow();
+ });
+
+ it("removeSetting throws on Dexie error", async () => {
+ await expect(removeSetting("never-opened", "k")).rejects.toThrow();
+ });
+
+ it("listSettingKeys throws on Dexie error", async () => {
+ await expect(listSettingKeys("never-opened")).rejects.toThrow();
+ });
+});
diff --git a/crates/idxdb-store/src/ts/sync.test.ts b/crates/idxdb-store/src/ts/sync.test.ts
new file mode 100644
index 0000000..39ca48c
--- /dev/null
+++ b/crates/idxdb-store/src/ts/sync.test.ts
@@ -0,0 +1,931 @@
+import { describe, it, expect, afterEach, vi, beforeEach } from "vitest";
+import { openDatabase, getDatabase } from "./schema.js";
+import {
+ getNoteTags,
+ getSyncHeight,
+ addNoteTag,
+ removeNoteTag,
+ applyStateSync,
+ discardTransactions,
+} from "./sync.js";
+
+// ---------------------------------------------------------------------------
+// Test DB helpers
+// ---------------------------------------------------------------------------
+
+let dbCounter = 0;
+function uniqueDbName(): string {
+ return `test-sync-${++dbCounter}-${Date.now()}`;
+}
+
+const openDbIds: string[] = [];
+
+afterEach(async () => {
+ for (const dbId of openDbIds) {
+ const db = getDatabase(dbId);
+ db.dexie.close();
+ await db.dexie.delete();
+ }
+ openDbIds.length = 0;
+});
+
+async function openTestDb(): Promise {
+ const name = uniqueDbName();
+ await openDatabase(name, "0.1.0");
+ openDbIds.push(name);
+ return name;
+}
+
+// Helper: uint8Array -> base64 (mirrors the source util)
+function toBase64(bytes: Uint8Array): string {
+ const binary = bytes.reduce((acc, b) => acc + String.fromCharCode(b), "");
+ return btoa(binary);
+}
+
+// ---------------------------------------------------------------------------
+// Minimal applyStateSync builder
+// ---------------------------------------------------------------------------
+
+/** A FlattenedU8Vec-compatible object with zero entries. */
+function emptyFlattenedVec() {
+ return {
+ data: () => new Uint8Array(0),
+ lengths: () => [] as number[],
+ };
+}
+
+/** A FlattenedU8Vec holding a single Uint8Array chunk. */
+function singleFlattenedVec(chunk: Uint8Array) {
+ return {
+ data: () => chunk,
+ lengths: () => [chunk.length],
+ };
+}
+
+/** Build a minimal JsStateSyncUpdate that performs only what the test needs. */
+function minimalStateUpdate(
+ overrides: Partial[1]> = {}
+): Parameters[1] {
+ return {
+ blockNum: 5,
+ flattenedNewBlockHeaders: emptyFlattenedVec(),
+ flattenedPartialBlockChainPeaks: emptyFlattenedVec(),
+ newBlockNums: [],
+ blockHasRelevantNotes: new Uint8Array(0),
+ serializedNodeIds: [],
+ serializedNodes: [],
+ committedNoteIds: [],
+ serializedInputNotes: [],
+ serializedOutputNotes: [],
+ accountUpdates: [],
+ transactionUpdates: [],
+ ...overrides,
+ };
+}
+
+// ---------------------------------------------------------------------------
+// describe blocks
+// ---------------------------------------------------------------------------
+
+describe("sync", () => {
+ let errorSpy: ReturnType;
+ let logSpy: ReturnType;
+ let warnSpy: ReturnType;
+
+ beforeEach(() => {
+ errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
+ warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ errorSpy.mockRestore();
+ logSpy.mockRestore();
+ warnSpy.mockRestore();
+ });
+
+ // -------------------------------------------------------------------------
+ // getSyncHeight
+ // -------------------------------------------------------------------------
+
+ describe("getSyncHeight", () => {
+ it("returns blockNum 0 when DB was just created (populate hook seeds record)", async () => {
+ const dbId = await openTestDb();
+ const result = await getSyncHeight(dbId);
+ expect(result).toEqual({ blockNum: 0 });
+ });
+
+ it("returns the persisted blockNum after updating stateSync", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+ // Manually bump blockNum to verify getSyncHeight reads it back
+ await db.stateSync.update(1, { blockNum: 42 });
+ const result = await getSyncHeight(dbId);
+ expect(result).toEqual({ blockNum: 42 });
+ });
+
+ it("returns null when no stateSync record exists (deleted)", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+ await db.stateSync.delete(1);
+ const result = await getSyncHeight(dbId);
+ expect(result).toBeNull();
+ });
+
+ it("rejects when db is not opened (logWebStoreError re-throws)", async () => {
+ await expect(getSyncHeight("never-opened-sync")).rejects.toThrow();
+ expect(errorSpy).toHaveBeenCalled();
+ });
+ });
+
+ // -------------------------------------------------------------------------
+ // getNoteTags
+ // -------------------------------------------------------------------------
+
+ describe("getNoteTags", () => {
+ it("returns an empty array when no tags exist", async () => {
+ const dbId = await openTestDb();
+ const result = await getNoteTags(dbId);
+ expect(result).toEqual([]);
+ });
+
+ it("returns tags with sourceNoteId/sourceAccountId populated correctly", async () => {
+ const dbId = await openTestDb();
+ await addNoteTag(dbId, new Uint8Array([0x01, 0x02]), "note-1", "acct-1");
+ const tags = await getNoteTags(dbId);
+ expect(tags).toHaveLength(1);
+ expect(tags![0].sourceNoteId).toBe("note-1");
+ expect(tags![0].sourceAccountId).toBe("acct-1");
+ });
+
+ it("converts empty string sourceNoteId to undefined", async () => {
+ const dbId = await openTestDb();
+ // addNoteTag stores "" when sourceNoteId is falsy; getNoteTags should normalise it back
+ const db = getDatabase(dbId);
+ await db.tags.add({
+ tag: toBase64(new Uint8Array([0x0a])),
+ sourceNoteId: "",
+ sourceAccountId: "",
+ });
+ const tags = await getNoteTags(dbId);
+ expect(tags).toHaveLength(1);
+ expect(tags![0].sourceNoteId).toBeUndefined();
+ expect(tags![0].sourceAccountId).toBeUndefined();
+ });
+
+ it("returns multiple tags in insertion order", async () => {
+ const dbId = await openTestDb();
+ await addNoteTag(dbId, new Uint8Array([0x01]), "note-a", "acct-a");
+ await addNoteTag(dbId, new Uint8Array([0x02]), "note-b", "acct-b");
+ const tags = await getNoteTags(dbId);
+ expect(tags).toHaveLength(2);
+ });
+
+ it("rejects when db is not opened (logWebStoreError re-throws)", async () => {
+ await expect(getNoteTags("never-opened-sync")).rejects.toThrow();
+ expect(errorSpy).toHaveBeenCalled();
+ });
+ });
+
+ // -------------------------------------------------------------------------
+ // addNoteTag
+ // -------------------------------------------------------------------------
+
+ describe("addNoteTag", () => {
+ it("adds a tag with both sourceNoteId and sourceAccountId", async () => {
+ const dbId = await openTestDb();
+ const tagBytes = new Uint8Array([0xde, 0xad]);
+ await addNoteTag(dbId, tagBytes, "note-1", "acct-1");
+
+ const db = getDatabase(dbId);
+ const stored = await db.tags.toArray();
+ expect(stored).toHaveLength(1);
+ expect(stored[0].tag).toBe(toBase64(tagBytes));
+ expect(stored[0].sourceNoteId).toBe("note-1");
+ expect(stored[0].sourceAccountId).toBe("acct-1");
+ });
+
+ it("stores empty string when sourceNoteId is falsy", async () => {
+ const dbId = await openTestDb();
+ await addNoteTag(dbId, new Uint8Array([0x01]), "", "");
+
+ const db = getDatabase(dbId);
+ const stored = await db.tags.toArray();
+ expect(stored[0].sourceNoteId).toBe("");
+ expect(stored[0].sourceAccountId).toBe("");
+ });
+
+ it("rejects when db is not opened (logWebStoreError re-throws)", async () => {
+ await expect(
+ addNoteTag("never-opened-sync", new Uint8Array([1]), "n", "a")
+ ).rejects.toThrow();
+ expect(errorSpy).toHaveBeenCalled();
+ });
+ });
+
+ // -------------------------------------------------------------------------
+ // removeNoteTag
+ // -------------------------------------------------------------------------
+
+ describe("removeNoteTag", () => {
+ it("removes the matching tag and returns delete count 1", async () => {
+ const dbId = await openTestDb();
+ const tagBytes = new Uint8Array([0xab]);
+ await addNoteTag(dbId, tagBytes, "note-x", "acct-x");
+
+ const deleted = await removeNoteTag(dbId, tagBytes, "note-x", "acct-x");
+ expect(deleted).toBe(1);
+
+ const db = getDatabase(dbId);
+ expect(await db.tags.count()).toBe(0);
+ });
+
+ it("returns 0 when no matching tag exists", async () => {
+ const dbId = await openTestDb();
+ const deleted = await removeNoteTag(
+ dbId,
+ new Uint8Array([0xff]),
+ "no-such-note"
+ );
+ expect(deleted).toBe(0);
+ });
+
+ it("only removes the matching tag, leaving others intact", async () => {
+ const dbId = await openTestDb();
+ await addNoteTag(dbId, new Uint8Array([0x01]), "note-1", "acct-1");
+ await addNoteTag(dbId, new Uint8Array([0x02]), "note-2", "acct-2");
+
+ await removeNoteTag(dbId, new Uint8Array([0x01]), "note-1", "acct-1");
+
+ const db = getDatabase(dbId);
+ const remaining = await db.tags.toArray();
+ expect(remaining).toHaveLength(1);
+ expect(remaining[0].sourceNoteId).toBe("note-2");
+ });
+
+ it("uses empty string for sourceNoteId/sourceAccountId when undefined is passed", async () => {
+ const dbId = await openTestDb();
+ // Add tag with empty sourceNoteId/sourceAccountId
+ await addNoteTag(dbId, new Uint8Array([0x05]), "", "");
+ // Remove using undefined — internally converts to ""
+ const deleted = await removeNoteTag(
+ dbId,
+ new Uint8Array([0x05]),
+ undefined,
+ undefined
+ );
+ expect(deleted).toBe(1);
+ });
+
+ it("rejects when db is not opened (logWebStoreError re-throws)", async () => {
+ await expect(
+ removeNoteTag("never-opened-sync", new Uint8Array([1]))
+ ).rejects.toThrow();
+ expect(errorSpy).toHaveBeenCalled();
+ });
+ });
+
+ // -------------------------------------------------------------------------
+ // discardTransactions
+ // -------------------------------------------------------------------------
+
+ describe("discardTransactions", () => {
+ it("removes transactions matching the provided ids", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+ const status = new Uint8Array([0]);
+ await db.transactions.put({
+ id: "tx-1",
+ details: new Uint8Array([1]),
+ blockNum: 1,
+ statusVariant: 0,
+ status,
+ });
+ await db.transactions.put({
+ id: "tx-2",
+ details: new Uint8Array([2]),
+ blockNum: 2,
+ statusVariant: 0,
+ status,
+ });
+ await db.transactions.put({
+ id: "tx-3",
+ details: new Uint8Array([3]),
+ blockNum: 3,
+ statusVariant: 0,
+ status,
+ });
+
+ await discardTransactions(dbId, ["tx-1", "tx-3"]);
+
+ const remaining = await db.transactions.toArray();
+ expect(remaining).toHaveLength(1);
+ expect(remaining[0].id).toBe("tx-2");
+ });
+
+ it("is a no-op when the ids array is empty", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+ const status = new Uint8Array([0]);
+ await db.transactions.put({
+ id: "tx-keep",
+ details: new Uint8Array([1]),
+ blockNum: 1,
+ statusVariant: 0,
+ status,
+ });
+
+ await discardTransactions(dbId, []);
+
+ const remaining = await db.transactions.toArray();
+ expect(remaining).toHaveLength(1);
+ });
+
+ it("is a no-op when none of the ids exist", async () => {
+ const dbId = await openTestDb();
+ const db = getDatabase(dbId);
+ await db.transactions.put({
+ id: "tx-1",
+ details: new Uint8Array([1]),
+ blockNum: 1,
+ statusVariant: 0,
+ status: new Uint8Array([0]),
+ });
+
+ await discardTransactions(dbId, ["nonexistent"]);
+ const remaining = await db.transactions.toArray();
+ expect(remaining).toHaveLength(1);
+ });
+
+ it("rejects when db is not opened (logWebStoreError re-throws)", async () => {
+ await expect(
+ discardTransactions("never-opened-sync", ["tx-1"])
+ ).rejects.toThrow();
+ expect(errorSpy).toHaveBeenCalled();
+ });
+ });
+
+ // -------------------------------------------------------------------------
+ // applyStateSync — sync height update
+ // -------------------------------------------------------------------------
+
+ describe("applyStateSync — sync height", () => {
+ it("updates sync height to the given blockNum", async () => {
+ const dbId = await openTestDb();
+ await applyStateSync(dbId, minimalStateUpdate({ blockNum: 10 }));
+ const result = await getSyncHeight(dbId);
+ expect(result).toEqual({ blockNum: 10 });
+ });
+
+ it("does not regress sync height when a lower blockNum is applied", async () => {
+ const dbId = await openTestDb();
+ // First advance to 20
+ await applyStateSync(dbId, minimalStateUpdate({ blockNum: 20 }));
+ // Then apply a lower blockNum — should not overwrite
+ await applyStateSync(dbId, minimalStateUpdate({ blockNum: 5 }));
+ const result = await getSyncHeight(dbId);
+ expect(result).toEqual({ blockNum: 20 });
+ });
+
+ it("advances sync height when a higher blockNum is applied", async () => {
+ const dbId = await openTestDb();
+ await applyStateSync(dbId, minimalStateUpdate({ blockNum: 10 }));
+ await applyStateSync(dbId, minimalStateUpdate({ blockNum: 30 }));
+ const result = await getSyncHeight(dbId);
+ expect(result).toEqual({ blockNum: 30 });
+ });
+ });
+
+ // -------------------------------------------------------------------------
+ // applyStateSync — block headers
+ // -------------------------------------------------------------------------
+
+ describe("applyStateSync — block headers", () => {
+ it("inserts a new block header during sync", async () => {
+ const dbId = await openTestDb();
+ const headerBytes = new Uint8Array([0x10, 0x20]);
+ const peaksBytes = new Uint8Array([0x30, 0x40]);
+
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 7,
+ newBlockNums: [7],
+ blockHasRelevantNotes: new Uint8Array([0]),
+ flattenedNewBlockHeaders: singleFlattenedVec(headerBytes),
+ flattenedPartialBlockChainPeaks: singleFlattenedVec(peaksBytes),
+ })
+ );
+
+ const db = getDatabase(dbId);
+ const header = await db.blockHeaders.get(7);
+ expect(header).toBeDefined();
+ expect(header!.blockNum).toBe(7);
+ expect(header!.hasClientNotes).toBe("false");
+ });
+
+ it("marks block header hasClientNotes=true when blockHasRelevantNotes[i] === 1", async () => {
+ const dbId = await openTestDb();
+ const headerBytes = new Uint8Array([0xaa]);
+ const peaksBytes = new Uint8Array([0xbb]);
+
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 15,
+ newBlockNums: [15],
+ blockHasRelevantNotes: new Uint8Array([1]),
+ flattenedNewBlockHeaders: singleFlattenedVec(headerBytes),
+ flattenedPartialBlockChainPeaks: singleFlattenedVec(peaksBytes),
+ })
+ );
+
+ const db = getDatabase(dbId);
+ const header = await db.blockHeaders.get(15);
+ expect(header!.hasClientNotes).toBe("true");
+ });
+
+ it("does not overwrite an existing block header", async () => {
+ const dbId = await openTestDb();
+ const original = new Uint8Array([0x01]);
+ const replacement = new Uint8Array([0xff]);
+ const peaks = new Uint8Array([0x00]);
+
+ // Insert block header 5 first time
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 5,
+ newBlockNums: [5],
+ blockHasRelevantNotes: new Uint8Array([0]),
+ flattenedNewBlockHeaders: singleFlattenedVec(original),
+ flattenedPartialBlockChainPeaks: singleFlattenedVec(peaks),
+ })
+ );
+
+ // Try to insert same block num with different data — should be skipped
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 6,
+ newBlockNums: [5],
+ blockHasRelevantNotes: new Uint8Array([0]),
+ flattenedNewBlockHeaders: singleFlattenedVec(replacement),
+ flattenedPartialBlockChainPeaks: singleFlattenedVec(peaks),
+ })
+ );
+
+ const db = getDatabase(dbId);
+ const header = await db.blockHeaders.get(5);
+ expect(header!.header).toEqual(original);
+ });
+
+ it("handles zero block headers (empty newBlockNums)", async () => {
+ const dbId = await openTestDb();
+ // No block headers — should complete without error
+ await applyStateSync(dbId, minimalStateUpdate({ blockNum: 3 }));
+ const result = await getSyncHeight(dbId);
+ expect(result).toEqual({ blockNum: 3 });
+ });
+ });
+
+ // -------------------------------------------------------------------------
+ // applyStateSync — partial blockchain nodes
+ // -------------------------------------------------------------------------
+
+ describe("applyStateSync — partial blockchain nodes", () => {
+ it("inserts partial blockchain nodes", async () => {
+ const dbId = await openTestDb();
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 1,
+ serializedNodeIds: ["42"],
+ serializedNodes: ["node-data-42"],
+ })
+ );
+
+ const db = getDatabase(dbId);
+ const node = await db.partialBlockchainNodes.get(42);
+ expect(node).toBeDefined();
+ expect(node!.node).toBe("node-data-42");
+ });
+
+ it("overwrites an existing partial blockchain node (bulkPut)", async () => {
+ const dbId = await openTestDb();
+
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 1,
+ serializedNodeIds: ["10"],
+ serializedNodes: ["first-data"],
+ })
+ );
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 2,
+ serializedNodeIds: ["10"],
+ serializedNodes: ["second-data"],
+ })
+ );
+
+ const db = getDatabase(dbId);
+ const node = await db.partialBlockchainNodes.get(10);
+ expect(node!.node).toBe("second-data");
+ });
+
+ it("is a no-op when serializedNodeIds is empty", async () => {
+ const dbId = await openTestDb();
+ // Should complete without error
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 1,
+ serializedNodeIds: [],
+ serializedNodes: [],
+ })
+ );
+ const db = getDatabase(dbId);
+ expect(await db.partialBlockchainNodes.count()).toBe(0);
+ });
+
+ it("rejects when nodeIndexes and nodes arrays have different lengths", async () => {
+ const dbId = await openTestDb();
+ // Mismatched arrays — error thrown inside Dexie transaction, aborts tx and rejects
+ await expect(
+ applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 1,
+ serializedNodeIds: ["1", "2"],
+ serializedNodes: ["only-one"],
+ })
+ )
+ ).rejects.toThrow(
+ "nodeIndexes and nodes arrays must be of the same length"
+ );
+ });
+ });
+
+ // -------------------------------------------------------------------------
+ // applyStateSync — committed note tags
+ // -------------------------------------------------------------------------
+
+ describe("applyStateSync — committed note tags (updateCommittedNoteTags)", () => {
+ it("removes tags whose sourceNoteId matches a committedNoteId", async () => {
+ const dbId = await openTestDb();
+ // Add a tag that is associated with note-A
+ await addNoteTag(dbId, new Uint8Array([0x01]), "note-A", "acct-1");
+ // Add a tag associated with note-B (should survive)
+ await addNoteTag(dbId, new Uint8Array([0x02]), "note-B", "acct-2");
+
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 1,
+ committedNoteIds: ["note-A"],
+ })
+ );
+
+ const tags = await getNoteTags(dbId);
+ expect(tags).toHaveLength(1);
+ expect(tags![0].sourceNoteId).toBe("note-B");
+ });
+
+ it("is a no-op when committedNoteIds is empty", async () => {
+ const dbId = await openTestDb();
+ await addNoteTag(dbId, new Uint8Array([0x01]), "note-A", "acct-1");
+
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 1,
+ committedNoteIds: [],
+ })
+ );
+
+ const tags = await getNoteTags(dbId);
+ expect(tags).toHaveLength(1);
+ });
+
+ it("removes all tags for multiple committedNoteIds", async () => {
+ const dbId = await openTestDb();
+ await addNoteTag(dbId, new Uint8Array([0x01]), "note-A", "acct-1");
+ await addNoteTag(dbId, new Uint8Array([0x02]), "note-B", "acct-2");
+ await addNoteTag(dbId, new Uint8Array([0x03]), "note-C", "acct-3");
+
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 1,
+ committedNoteIds: ["note-A", "note-B"],
+ })
+ );
+
+ const tags = await getNoteTags(dbId);
+ expect(tags).toHaveLength(1);
+ expect(tags![0].sourceNoteId).toBe("note-C");
+ });
+ });
+
+ // -------------------------------------------------------------------------
+ // applyStateSync — transaction updates
+ // -------------------------------------------------------------------------
+
+ describe("applyStateSync — transaction updates", () => {
+ it("upserts a transaction record without a script", async () => {
+ const dbId = await openTestDb();
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 5,
+ transactionUpdates: [
+ {
+ id: "tx-sync-1",
+ details: new Uint8Array([1, 2, 3]),
+ blockNum: 5,
+ statusVariant: 1,
+ status: new Uint8Array([4, 5, 6]),
+ scriptRoot: undefined,
+ txScript: undefined,
+ },
+ ],
+ })
+ );
+
+ const db = getDatabase(dbId);
+ const tx = await db.transactions.where("id").equals("tx-sync-1").first();
+ expect(tx).toBeDefined();
+ expect(tx!.blockNum).toBe(5);
+ expect(tx!.statusVariant).toBe(1);
+ });
+
+ it("upserts a transaction record WITH a script when both scriptRoot and txScript are provided", async () => {
+ const dbId = await openTestDb();
+ const scriptRootBytes = new Uint8Array([0xca, 0xfe]);
+ const txScriptBytes = new Uint8Array([0xba, 0xbe]);
+
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 5,
+ transactionUpdates: [
+ {
+ id: "tx-with-script",
+ details: new Uint8Array([1]),
+ blockNum: 5,
+ statusVariant: 1,
+ status: new Uint8Array([0]),
+ scriptRoot: scriptRootBytes,
+ txScript: txScriptBytes,
+ },
+ ],
+ })
+ );
+
+ const db = getDatabase(dbId);
+ const script = await db.transactionScripts
+ .where("scriptRoot")
+ .equals(toBase64(scriptRootBytes))
+ .first();
+ expect(script).toBeDefined();
+ expect(script!.txScript).toEqual(txScriptBytes);
+ });
+
+ it("does NOT insert a script when txScript is absent (scriptRoot only)", async () => {
+ const dbId = await openTestDb();
+ const scriptRootBytes = new Uint8Array([0x11]);
+
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 5,
+ transactionUpdates: [
+ {
+ id: "tx-script-root-only",
+ details: new Uint8Array([1]),
+ blockNum: 5,
+ statusVariant: 1,
+ status: new Uint8Array([0]),
+ scriptRoot: scriptRootBytes,
+ txScript: undefined,
+ },
+ ],
+ })
+ );
+
+ const db = getDatabase(dbId);
+ // Script should not exist since txScript was absent
+ const scripts = await db.transactionScripts.toArray();
+ expect(scripts).toHaveLength(0);
+ });
+ });
+
+ // -------------------------------------------------------------------------
+ // applyStateSync — output notes
+ // -------------------------------------------------------------------------
+
+ describe("applyStateSync — output notes", () => {
+ it("upserts an output note during sync", async () => {
+ const dbId = await openTestDb();
+
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 5,
+ serializedOutputNotes: [
+ {
+ noteId: "out-note-1",
+ noteAssets: new Uint8Array([0x01, 0x02]),
+ recipientDigest: "recipient-digest-abc",
+ metadata: new Uint8Array([0x03, 0x04]),
+ nullifier: undefined,
+ expectedHeight: 100,
+ stateDiscriminant: 1,
+ state: new Uint8Array([0x05]),
+ },
+ ],
+ })
+ );
+
+ const db = getDatabase(dbId);
+ const note = await db.outputNotes
+ .where("noteId")
+ .equals("out-note-1")
+ .first();
+ expect(note).toBeDefined();
+ expect(note!.recipientDigest).toBe("recipient-digest-abc");
+ expect(note!.expectedHeight).toBe(100);
+ expect(note!.stateDiscriminant).toBe(1);
+ });
+
+ it("upserts multiple output notes in one sync call", async () => {
+ const dbId = await openTestDb();
+
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 5,
+ serializedOutputNotes: [
+ {
+ noteId: "out-a",
+ noteAssets: new Uint8Array([0x01]),
+ recipientDigest: "digest-a",
+ metadata: new Uint8Array([0x02]),
+ nullifier: "null-a",
+ expectedHeight: 10,
+ stateDiscriminant: 2,
+ state: new Uint8Array([0x03]),
+ },
+ {
+ noteId: "out-b",
+ noteAssets: new Uint8Array([0x04]),
+ recipientDigest: "digest-b",
+ metadata: new Uint8Array([0x05]),
+ nullifier: undefined,
+ expectedHeight: 20,
+ stateDiscriminant: 3,
+ state: new Uint8Array([0x06]),
+ },
+ ],
+ })
+ );
+
+ const db = getDatabase(dbId);
+ const notes = await db.outputNotes.toArray();
+ expect(notes).toHaveLength(2);
+ });
+ });
+
+ // -------------------------------------------------------------------------
+ // applyStateSync — input notes
+ // -------------------------------------------------------------------------
+
+ describe("applyStateSync — input notes", () => {
+ it("upserts an input note during sync", async () => {
+ const dbId = await openTestDb();
+
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 5,
+ serializedInputNotes: [
+ {
+ noteId: "in-note-1",
+ noteAssets: new Uint8Array([0x0a]),
+ serialNumber: new Uint8Array([0x0b]),
+ inputs: new Uint8Array([0x0c]),
+ noteScriptRoot: "script-root-in",
+ noteScript: new Uint8Array([0x0d]),
+ nullifier: "nullifier-in-1",
+ createdAt: "100",
+ stateDiscriminant: 2,
+ state: new Uint8Array([0x0e]),
+ consumedBlockHeight: undefined,
+ consumedTxOrder: undefined,
+ consumerAccountId: undefined,
+ },
+ ],
+ })
+ );
+
+ const db = getDatabase(dbId);
+ const note = await db.inputNotes
+ .where("noteId")
+ .equals("in-note-1")
+ .first();
+ expect(note).toBeDefined();
+ expect(note!.nullifier).toBe("nullifier-in-1");
+ expect(note!.stateDiscriminant).toBe(2);
+ });
+ });
+
+ // -------------------------------------------------------------------------
+ // applyStateSync — account updates
+ // -------------------------------------------------------------------------
+
+ describe("applyStateSync — account updates", () => {
+ it("applies a full account state during sync", async () => {
+ const dbId = await openTestDb();
+
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 5,
+ accountUpdates: [
+ {
+ accountId: "acct-sync-1",
+ nonce: "1",
+ storageRoot: "storage-root-1",
+ storageSlots: [],
+ storageMapEntries: [],
+ vaultRoot: "vault-root-1",
+ assets: [],
+ codeRoot: "code-root-1",
+ committed: true,
+ accountCommitment: "commitment-1",
+ accountSeed: undefined,
+ },
+ ],
+ })
+ );
+
+ const db = getDatabase(dbId);
+ const account = await db.latestAccountHeaders
+ .where("id")
+ .equals("acct-sync-1")
+ .first();
+ expect(account).toBeDefined();
+ expect(account!.nonce).toBe("1");
+ expect(account!.committed).toBe(true);
+ expect(account!.codeRoot).toBe("code-root-1");
+ });
+
+ it("applies multiple account updates in one sync call", async () => {
+ const dbId = await openTestDb();
+
+ await applyStateSync(
+ dbId,
+ minimalStateUpdate({
+ blockNum: 5,
+ accountUpdates: [
+ {
+ accountId: "acct-sync-A",
+ nonce: "1",
+ storageRoot: "sr-A",
+ storageSlots: [],
+ storageMapEntries: [],
+ vaultRoot: "vr-A",
+ assets: [],
+ codeRoot: "cr-A",
+ committed: true,
+ accountCommitment: "com-A",
+ accountSeed: undefined,
+ },
+ {
+ accountId: "acct-sync-B",
+ nonce: "2",
+ storageRoot: "sr-B",
+ storageSlots: [],
+ storageMapEntries: [],
+ vaultRoot: "vr-B",
+ assets: [],
+ codeRoot: "cr-B",
+ committed: false,
+ accountCommitment: "com-B",
+ accountSeed: new Uint8Array([0xca, 0xfe]),
+ },
+ ],
+ })
+ );
+
+ const db = getDatabase(dbId);
+ const all = await db.latestAccountHeaders.toArray();
+ const ids = all.map((a) => a.id);
+ expect(ids).toContain("acct-sync-A");
+ expect(ids).toContain("acct-sync-B");
+ });
+ });
+});
diff --git a/crates/idxdb-store/src/ts/transactions.test.ts b/crates/idxdb-store/src/ts/transactions.test.ts
new file mode 100644
index 0000000..ed130d1
--- /dev/null
+++ b/crates/idxdb-store/src/ts/transactions.test.ts
@@ -0,0 +1,464 @@
+import { describe, it, expect, afterEach, vi, beforeEach } from "vitest";
+import { openDatabase, getDatabase } from "./schema.js";
+import {
+ getTransactions,
+ insertTransactionScript,
+ upsertTransactionRecord,
+} from "./transactions.js";
+
+let dbCounter = 0;
+function uniqueDbName(): string {
+ return `test-transactions-${++dbCounter}-${Date.now()}`;
+}
+
+const openDbIds: string[] = [];
+
+afterEach(async () => {
+ for (const dbId of openDbIds) {
+ const db = getDatabase(dbId);
+ db.dexie.close();
+ await db.dexie.delete();
+ }
+ openDbIds.length = 0;
+});
+
+async function openTestDb(): Promise {
+ const name = uniqueDbName();
+ await openDatabase(name, "0.1.0");
+ openDbIds.push(name);
+ return name;
+}
+
+// Helper: uint8Array -> base64 (mirrors the source util)
+function toBase64(bytes: Uint8Array): string {
+ const binary = bytes.reduce((acc, b) => acc + String.fromCharCode(b), "");
+ return btoa(binary);
+}
+
+describe("transactions", () => {
+ let errorSpy: any;
+ let logSpy: any;
+
+ beforeEach(() => {
+ errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ errorSpy.mockRestore();
+ logSpy.mockRestore();
+ });
+
+ // -------------------------------------------------------------------------
+ // upsertTransactionRecord / getTransactions — basic round-trip
+ // -------------------------------------------------------------------------
+
+ it("upserts a transaction and retrieves it with the 'all' filter", async () => {
+ const dbId = await openTestDb();
+ const details = new Uint8Array([1, 2, 3]);
+ const status = new Uint8Array([4, 5, 6]);
+
+ await upsertTransactionRecord(dbId, "tx-1", details, 10, 1, status);
+
+ const results = await getTransactions(dbId, "All");
+ expect(results).toHaveLength(1);
+ const tx = results![0];
+ expect(tx.id).toBe("tx-1");
+ expect(tx.blockNum).toBe(10);
+ expect(tx.statusVariant).toBe(1);
+ expect(tx.details).toBe(toBase64(details));
+ expect(tx.status).toBe(toBase64(status));
+ expect(tx.scriptRoot).toBeUndefined();
+ expect(tx.txScript).toBeUndefined();
+ });
+
+ it("upserts a transaction with a scriptRoot and retrieves it with txScript", async () => {
+ const dbId = await openTestDb();
+ const details = new Uint8Array([10]);
+ const status = new Uint8Array([20]);
+ const scriptRootBytes = new Uint8Array([0xaa, 0xbb]);
+ const txScriptBytes = new Uint8Array([0xcc, 0xdd]);
+
+ // Insert the script first
+ await insertTransactionScript(dbId, scriptRootBytes, txScriptBytes);
+
+ // Insert transaction referencing that script root
+ await upsertTransactionRecord(
+ dbId,
+ "tx-with-script",
+ details,
+ 5,
+ 1,
+ status,
+ scriptRootBytes
+ );
+
+ const results = await getTransactions(dbId, "All");
+ expect(results).toHaveLength(1);
+ const tx = results![0];
+ expect(tx.id).toBe("tx-with-script");
+ expect(tx.scriptRoot).toBe(toBase64(scriptRootBytes));
+ expect(tx.txScript).toBe(toBase64(txScriptBytes));
+ });
+
+ it("upserts a transaction with scriptRoot but no matching script (txScript undefined)", async () => {
+ const dbId = await openTestDb();
+ const details = new Uint8Array([1]);
+ const status = new Uint8Array([2]);
+ const scriptRootBytes = new Uint8Array([0x01, 0x02]);
+
+ // Do NOT insert a script — scriptRoot points to nothing
+ await upsertTransactionRecord(
+ dbId,
+ "tx-no-script",
+ details,
+ 7,
+ 0,
+ status,
+ scriptRootBytes
+ );
+
+ const results = await getTransactions(dbId, "All");
+ expect(results).toHaveLength(1);
+ const tx = results![0];
+ expect(tx.txScript).toBeUndefined();
+ expect(tx.scriptRoot).toBe(toBase64(scriptRootBytes));
+ });
+
+ it("upsert replaces existing record with same id", async () => {
+ const dbId = await openTestDb();
+ const details1 = new Uint8Array([1]);
+ const details2 = new Uint8Array([99]);
+ const status = new Uint8Array([0]);
+
+ await upsertTransactionRecord(dbId, "tx-upsert", details1, 1, 0, status);
+ await upsertTransactionRecord(dbId, "tx-upsert", details2, 2, 1, status);
+
+ const results = await getTransactions(dbId, "All");
+ expect(results).toHaveLength(1);
+ expect(results![0].blockNum).toBe(2);
+ expect(results![0].details).toBe(toBase64(details2));
+ });
+
+ // -------------------------------------------------------------------------
+ // getTransactions — empty result path
+ // -------------------------------------------------------------------------
+
+ it("returns empty array when no transactions exist (All filter)", async () => {
+ const dbId = await openTestDb();
+ const results = await getTransactions(dbId, "All");
+ expect(results).toEqual([]);
+ });
+
+ // -------------------------------------------------------------------------
+ // getTransactions — 'Uncommitted' filter (statusVariant === 0)
+ // -------------------------------------------------------------------------
+
+ it("Uncommitted filter returns only pending transactions (statusVariant 0)", async () => {
+ const dbId = await openTestDb();
+ const status = new Uint8Array([0]);
+
+ // pending
+ await upsertTransactionRecord(
+ dbId,
+ "tx-pending",
+ new Uint8Array([1]),
+ 1,
+ 0 /* STATUS_PENDING_VARIANT */,
+ status
+ );
+ // committed
+ await upsertTransactionRecord(
+ dbId,
+ "tx-committed",
+ new Uint8Array([2]),
+ 2,
+ 1 /* STATUS_COMMITTED_VARIANT */,
+ status
+ );
+ // discarded
+ await upsertTransactionRecord(
+ dbId,
+ "tx-discarded",
+ new Uint8Array([3]),
+ 3,
+ 2 /* STATUS_DISCARDED_VARIANT */,
+ status
+ );
+
+ const results = await getTransactions(dbId, "Uncommitted");
+ expect(results).toHaveLength(1);
+ expect(results![0].id).toBe("tx-pending");
+ });
+
+ it("Uncommitted filter returns empty array when no pending transactions exist", async () => {
+ const dbId = await openTestDb();
+ await upsertTransactionRecord(
+ dbId,
+ "tx-committed",
+ new Uint8Array([1]),
+ 1,
+ 1,
+ new Uint8Array([0])
+ );
+
+ const results = await getTransactions(dbId, "Uncommitted");
+ expect(results).toEqual([]);
+ });
+
+ // -------------------------------------------------------------------------
+ // getTransactions — 'Ids:' filter
+ // -------------------------------------------------------------------------
+
+ it("Ids filter returns transactions matching provided ids", async () => {
+ const dbId = await openTestDb();
+ const status = new Uint8Array([0]);
+
+ await upsertTransactionRecord(
+ dbId,
+ "tx-a",
+ new Uint8Array([1]),
+ 1,
+ 1,
+ status
+ );
+ await upsertTransactionRecord(
+ dbId,
+ "tx-b",
+ new Uint8Array([2]),
+ 2,
+ 1,
+ status
+ );
+ await upsertTransactionRecord(
+ dbId,
+ "tx-c",
+ new Uint8Array([3]),
+ 3,
+ 1,
+ status
+ );
+
+ const results = await getTransactions(dbId, "Ids:tx-a,tx-c");
+ expect(results).toHaveLength(2);
+ const ids = results!.map((r) => r.id);
+ expect(ids).toEqual(expect.arrayContaining(["tx-a", "tx-c"]));
+ expect(ids).not.toContain("tx-b");
+ });
+
+ it("Ids filter with a single id returns that transaction", async () => {
+ const dbId = await openTestDb();
+ await upsertTransactionRecord(
+ dbId,
+ "tx-single",
+ new Uint8Array([9]),
+ 5,
+ 1,
+ new Uint8Array([1])
+ );
+
+ const results = await getTransactions(dbId, "Ids:tx-single");
+ expect(results).toHaveLength(1);
+ expect(results![0].id).toBe("tx-single");
+ });
+
+ it("Ids filter returns empty array when none of the ids exist", async () => {
+ const dbId = await openTestDb();
+ const results = await getTransactions(
+ dbId,
+ "Ids:nonexistent-1,nonexistent-2"
+ );
+ expect(results).toEqual([]);
+ });
+
+ // -------------------------------------------------------------------------
+ // getTransactions — 'ExpiredPending:' filter
+ // -------------------------------------------------------------------------
+
+ it("ExpiredPending filter returns pending txs with blockNum below threshold", async () => {
+ const dbId = await openTestDb();
+ const status = new Uint8Array([0]);
+
+ // pending, blockNum 5 — should match ExpiredPending:10
+ await upsertTransactionRecord(
+ dbId,
+ "tx-expired-pending",
+ new Uint8Array([1]),
+ 5,
+ 0 /* pending */,
+ status
+ );
+ // pending, blockNum 15 — above threshold, should NOT match
+ await upsertTransactionRecord(
+ dbId,
+ "tx-fresh-pending",
+ new Uint8Array([2]),
+ 15,
+ 0 /* pending */,
+ status
+ );
+ // committed, blockNum 5 — committed, should NOT match
+ await upsertTransactionRecord(
+ dbId,
+ "tx-committed",
+ new Uint8Array([3]),
+ 5,
+ 1 /* committed */,
+ status
+ );
+ // discarded, blockNum 5 — discarded, should NOT match
+ await upsertTransactionRecord(
+ dbId,
+ "tx-discarded",
+ new Uint8Array([4]),
+ 5,
+ 2 /* discarded */,
+ status
+ );
+
+ const results = await getTransactions(dbId, "ExpiredPending:10");
+ expect(results).toHaveLength(1);
+ expect(results![0].id).toBe("tx-expired-pending");
+ });
+
+ it("ExpiredPending filter returns empty array when no transactions match", async () => {
+ const dbId = await openTestDb();
+ // Only committed transactions, none pending
+ await upsertTransactionRecord(
+ dbId,
+ "tx-committed",
+ new Uint8Array([1]),
+ 5,
+ 1,
+ new Uint8Array([0])
+ );
+
+ const results = await getTransactions(dbId, "ExpiredPending:100");
+ expect(results).toEqual([]);
+ });
+
+ it("ExpiredPending filter boundary: blockNum equal to threshold is excluded", async () => {
+ const dbId = await openTestDb();
+ await upsertTransactionRecord(
+ dbId,
+ "tx-boundary",
+ new Uint8Array([1]),
+ 10 /* blockNum == threshold */,
+ 0,
+ new Uint8Array([0])
+ );
+
+ // filter is strict < blockNum, so blockNum === 10 with threshold 10 is excluded
+ const results = await getTransactions(dbId, "ExpiredPending:10");
+ expect(results).toEqual([]);
+ });
+
+ // -------------------------------------------------------------------------
+ // insertTransactionScript — round-trip without a transaction context
+ // -------------------------------------------------------------------------
+
+ it("insertTransactionScript stores a script retrievable via transactionScripts table", async () => {
+ const dbId = await openTestDb();
+ const scriptRootBytes = new Uint8Array([0x01, 0x02, 0x03]);
+ const txScriptBytes = new Uint8Array([0xde, 0xad, 0xbe, 0xef]);
+
+ await insertTransactionScript(dbId, scriptRootBytes, txScriptBytes);
+
+ const db = getDatabase(dbId);
+ const stored = await db.transactionScripts
+ .where("scriptRoot")
+ .equals(toBase64(scriptRootBytes))
+ .first();
+
+ expect(stored).toBeDefined();
+ expect(stored!.scriptRoot).toBe(toBase64(scriptRootBytes));
+ expect(stored!.txScript).toEqual(txScriptBytes);
+ });
+
+ it("insertTransactionScript upserts on duplicate scriptRoot", async () => {
+ const dbId = await openTestDb();
+ const scriptRootBytes = new Uint8Array([0xaa]);
+ const script1 = new Uint8Array([0x01]);
+ const script2 = new Uint8Array([0x02]);
+
+ await insertTransactionScript(dbId, scriptRootBytes, script1);
+ await insertTransactionScript(dbId, scriptRootBytes, script2);
+
+ const db = getDatabase(dbId);
+ const all = await db.transactionScripts.toArray();
+ expect(all).toHaveLength(1);
+ expect(all[0].txScript).toEqual(script2);
+ });
+
+ // -------------------------------------------------------------------------
+ // Error paths — "never-opened" dbId
+ // -------------------------------------------------------------------------
+
+ it("getTransactions throws when db is not opened", async () => {
+ await expect(getTransactions("never-opened", "All")).rejects.toThrow();
+ });
+
+ it("upsertTransactionRecord throws when db is not opened", async () => {
+ await expect(
+ upsertTransactionRecord(
+ "never-opened",
+ "tx-err",
+ new Uint8Array([1]),
+ 0,
+ 0,
+ new Uint8Array([0])
+ )
+ ).rejects.toThrow();
+ });
+
+ it("insertTransactionScript throws when db is not opened", async () => {
+ await expect(
+ insertTransactionScript(
+ "never-opened",
+ new Uint8Array([1]),
+ new Uint8Array([2])
+ )
+ ).rejects.toThrow();
+ });
+
+ // -------------------------------------------------------------------------
+ // Multiple transactions — verify all are returned by All filter
+ // -------------------------------------------------------------------------
+
+ it("returns all transactions when multiple are inserted", async () => {
+ const dbId = await openTestDb();
+ const status = new Uint8Array([0]);
+
+ await upsertTransactionRecord(
+ dbId,
+ "multi-1",
+ new Uint8Array([1]),
+ 1,
+ 0,
+ status
+ );
+ await upsertTransactionRecord(
+ dbId,
+ "multi-2",
+ new Uint8Array([2]),
+ 2,
+ 1,
+ status
+ );
+ await upsertTransactionRecord(
+ dbId,
+ "multi-3",
+ new Uint8Array([3]),
+ 3,
+ 2,
+ status
+ );
+
+ const results = await getTransactions(dbId, "All");
+ expect(results).toHaveLength(3);
+ const ids = results!.map((r) => r.id);
+ expect(ids).toEqual(
+ expect.arrayContaining(["multi-1", "multi-2", "multi-3"])
+ );
+ });
+});
diff --git a/crates/idxdb-store/src/ts/utils.test.ts b/crates/idxdb-store/src/ts/utils.test.ts
new file mode 100644
index 0000000..ac9c322
--- /dev/null
+++ b/crates/idxdb-store/src/ts/utils.test.ts
@@ -0,0 +1,105 @@
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
+import Dexie from "dexie";
+import { mapOption, logWebStoreError, uint8ArrayToBase64 } from "./utils.js";
+
+describe("mapOption", () => {
+ it("applies the function when value is defined", () => {
+ expect(mapOption(5, (n) => n * 2)).toBe(10);
+ });
+
+ it("returns undefined when value is null", () => {
+ expect(mapOption(null, (n) => n * 2)).toBeUndefined();
+ });
+
+ it("returns undefined when value is undefined", () => {
+ expect(mapOption(undefined, (n) => n * 2)).toBeUndefined();
+ });
+
+ it("treats 0 and empty string as defined", () => {
+ expect(mapOption(0, (n) => n + 1)).toBe(1);
+ expect(mapOption("", (s) => s.length)).toBe(0);
+ });
+});
+
+describe("uint8ArrayToBase64", () => {
+ it("encodes bytes correctly", () => {
+ expect(uint8ArrayToBase64(new Uint8Array([1, 2, 3]))).toBe("AQID");
+ });
+
+ it("encodes an empty array to an empty string", () => {
+ expect(uint8ArrayToBase64(new Uint8Array([]))).toBe("");
+ });
+});
+
+describe("logWebStoreError", () => {
+ let errorSpy: any;
+ let traceSpy: any;
+
+ beforeEach(() => {
+ errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
+ traceSpy = vi.spyOn(console, "trace").mockImplementation(() => {});
+ });
+
+ afterEach(() => {
+ errorSpy.mockRestore();
+ traceSpy.mockRestore();
+ });
+
+ it("logs and rethrows a Dexie error with context", () => {
+ const err = new Dexie.DexieError("OpenError", "DB closed");
+ expect(() => logWebStoreError(err, "ctx")).toThrow();
+ expect(errorSpy).toHaveBeenCalledWith(
+ expect.stringContaining("ctx: Indexdb error")
+ );
+ });
+
+ it("logs a Dexie error without context", () => {
+ const err = new Dexie.DexieError("OpenError", "DB closed");
+ expect(() => logWebStoreError(err)).toThrow();
+ expect(errorSpy).toHaveBeenCalledWith(
+ expect.stringMatching(/^Indexdb error:/)
+ );
+ });
+
+ it("logs a Dexie error's stack when present", () => {
+ const err = new Dexie.DexieError("OpenError", "DB closed");
+ (err as any).stack = "stack-line";
+ expect(() => logWebStoreError(err)).toThrow();
+ expect(errorSpy).toHaveBeenCalledWith(
+ expect.stringContaining("Stacktrace")
+ );
+ });
+
+ it("recurses into Dexie inner exception", () => {
+ const inner = new Error("inner-cause");
+ const err = new Dexie.DexieError("OpenError", "outer");
+ (err as any).inner = inner;
+ expect(() => logWebStoreError(err)).toThrow();
+ expect(errorSpy.mock.calls.length).toBeGreaterThan(1);
+ });
+
+ it("logs a plain Error with stack", () => {
+ const err = new Error("boom");
+ expect(() => logWebStoreError(err)).toThrow();
+ expect(errorSpy).toHaveBeenCalledWith(
+ expect.stringContaining("Unexpected error")
+ );
+ });
+
+ it("logs a plain Error without stack", () => {
+ const err = new Error("boom");
+ err.stack = undefined;
+ expect(() => logWebStoreError(err)).toThrow();
+ expect(errorSpy).toHaveBeenCalledWith(
+ expect.stringContaining("Unexpected error")
+ );
+ });
+
+ it("logs and rethrows a non-Error value", () => {
+ expect(() => logWebStoreError({ thrown: "thing" })).toThrow();
+ expect(errorSpy).toHaveBeenCalledWith(
+ expect.stringContaining("non-error value")
+ );
+ expect(traceSpy).toHaveBeenCalled();
+ });
+});
diff --git a/crates/idxdb-store/src/vitest.config.ts b/crates/idxdb-store/src/vitest.config.ts
index adbb67d..7cbe249 100644
--- a/crates/idxdb-store/src/vitest.config.ts
+++ b/crates/idxdb-store/src/vitest.config.ts
@@ -4,5 +4,12 @@ export default defineConfig({
test: {
environment: "node",
setupFiles: ["fake-indexeddb/auto"],
+ coverage: {
+ provider: "v8",
+ reporter: ["text", "json", "json-summary", "html", "lcov"],
+ include: ["ts/**/*.ts"],
+ exclude: ["ts/**/*.test.ts", "ts/test-utils.ts"],
+ thresholds: { lines: 95, branches: 95, functions: 95, statements: 95 },
+ },
},
});
diff --git a/crates/web-client/.attw.json b/crates/web-client/.attw.json
new file mode 100644
index 0000000..daeca4e
--- /dev/null
+++ b/crates/web-client/.attw.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://raw.githubusercontent.com/arethetypeswrong/arethetypeswrong.github.io/main/docs/configuration.md",
+ "profile": "esm-only"
+}
diff --git a/crates/web-client/.mocharc.json b/crates/web-client/.mocharc.json
deleted file mode 100644
index 15f2118..0000000
--- a/crates/web-client/.mocharc.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "require": ["ts-node/register", "esm"],
- "extension": ["ts"],
- "spec": "test/**/*.test.ts",
- "timeout": 600000
-}
diff --git a/crates/web-client/js/eager.js b/crates/web-client/js/eager.js
new file mode 100644
index 0000000..a1047db
--- /dev/null
+++ b/crates/web-client/js/eager.js
@@ -0,0 +1,33 @@
+// Eager entry point for @miden-sdk/miden-sdk (browser builds).
+//
+// Awaits WASM initialization at module top level, so importing this module
+// guarantees that any wasm-bindgen constructor (`new RpcClient(...)`,
+// `AccountId.fromHex(...)`, `TransactionProver.newRemoteProver(...)`, etc.)
+// is safe to call synchronously on the next line. No explicit
+// `await MidenClient.ready()` / `isReady` gate is required.
+//
+// This is the default entry for browser bundlers (`@miden-sdk/miden-sdk`
+// → `./dist/eager.js`). Node.js consumers resolve the `node` exports
+// condition instead and get the napi binding via `./js/node-index.js`,
+// bypassing this file entirely.
+//
+// When NOT to use this entry:
+// - **Capacitor mobile apps** (Miden Wallet iOS/Android): Capacitor's
+// `capacitor://localhost` scheme handler interacts poorly with top-level
+// await in the main WKWebView. Verified empirically: TLA in a Capacitor
+// host WKWebView hangs module evaluation indefinitely, while the same
+// TLA in the dApp-browser WKWebView (vanilla HTTPS) resolves in <100ms.
+// - **Next.js / SSR**: TLA blocks server-side module evaluation.
+// - **Framework adapters (@miden-sdk/react, etc.)**: they manage readiness
+// via their own state machine (e.g. `isReady`) and should not impose
+// TLA on consumer bundles.
+//
+// For those contexts, import from `@miden-sdk/miden-sdk/lazy` — identical
+// API surface, no top-level await, callers are responsible for awaiting
+// `MidenClient.ready()` (or the equivalent) before touching wasm-bindgen
+// types.
+import { getWasmOrThrow } from "./index.js";
+
+await getWasmOrThrow();
+
+export * from "./index.js";
diff --git a/crates/web-client/js/node/napi-compat.js b/crates/web-client/js/node/napi-compat.js
index 8c74661..29017b0 100644
--- a/crates/web-client/js/node/napi-compat.js
+++ b/crates/web-client/js/node/napi-compat.js
@@ -33,7 +33,7 @@ export function normalizeArg(val) {
/**
* Wraps a napi class so constructor and static method args are normalized.
*/
-export function wrapClass(Cls) {
+function wrapClass(Cls) {
if (!Cls) return Cls;
const Wrapper = function (...args) {
return new Cls(...args.map(normalizeArg));
@@ -126,7 +126,7 @@ export function wrapClient(rawClient, storeName) {
* - Converts null -> undefined for Option returns
* - Aliases static methods
*/
-export function patchSdkPrototypes(rawSdk) {
+function patchSdkPrototypes(rawSdk) {
// snake_case aliases for instance methods
/* eslint-disable camelcase */
for (const [cls, aliases] of [
@@ -176,7 +176,7 @@ export function patchSdkPrototypes(rawSdk) {
* typed wrappers (NoteAndArgsArray, FeltArray, etc.). These polyfills
* let `new sdk.FeltArray([a, b])` work on Node.js by returning a plain array.
*/
-export function makeArrayPolyfills() {
+function makeArrayPolyfills() {
function polyfill(items) {
const arr =
items === undefined || items === null
diff --git a/crates/web-client/lazy/package.json b/crates/web-client/lazy/package.json
new file mode 100644
index 0000000..45739eb
--- /dev/null
+++ b/crates/web-client/lazy/package.json
@@ -0,0 +1,4 @@
+{
+ "main": "../dist/index.js",
+ "types": "../dist/index.d.ts"
+}
diff --git a/crates/web-client/package.json b/crates/web-client/package.json
index b96ac79..b0c783e 100644
--- a/crates/web-client/package.json
+++ b/crates/web-client/package.json
@@ -6,18 +6,31 @@
"Miden"
],
"type": "module",
- "main": "./dist/index.js",
- "browser": "./dist/index.js",
+ "main": "./dist/eager.js",
+ "browser": "./dist/eager.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
- "browser": "./dist/index.js",
- "node": "./js/node-index.js",
- "default": "./dist/index.js"
- }
+ "node": {
+ "types": "./dist/index.d.ts",
+ "default": "./js/node-index.js"
+ },
+ "import": {
+ "types": "./dist/index.d.ts",
+ "default": "./dist/eager.js"
+ }
+ },
+ "./lazy": {
+ "import": {
+ "types": "./dist/index.d.ts",
+ "default": "./dist/index.js"
+ }
+ },
+ "./package.json": "./package.json"
},
"files": [
"dist",
+ "lazy",
"js/node-index.js",
"js/node",
"js/client.js",
@@ -28,7 +41,7 @@
],
"scripts": {
"build-rust-client-js": "pnpm --filter web_store run build",
- "build": "rimraf dist && pnpm run build-rust-client-js && cross-env RUSTFLAGS=\"--cfg getrandom_backend=\\\"wasm_js\\\"\" rollup -c rollup.config.js && cpr js/types dist && node clean.js",
+ "build": "rimraf dist && pnpm run build-rust-client-js && cross-env RUSTFLAGS=\"--cfg getrandom_backend=\\\"wasm_js\\\"\" rollup -c rollup.config.js && cpr js/types dist && node clean.js && node ./scripts/post-build.js",
"build-dev": "pnpm install && MIDEN_WEB_DEV=true pnpm run build",
"check:wasm-types": "node ./scripts/check-bindgen-types.js",
"check:method-classification": "node ./scripts/check-method-classification.js",
@@ -54,23 +67,16 @@
"@types/node": "^24.9.2",
"@wasm-tool/rollup-plugin-rust": "^3.0.3",
"binaryen": "^129.0.0",
- "chai": "^5.1.1",
"cpr": "^3.0.1",
"cross-env": "^7.0.3",
- "esm": "^3.2.25",
- "http-server": "^14.1.1",
- "mocha": "^10.7.3",
- "puppeteer": "^23.1.0",
"rimraf": "^6.0.1",
"rollup": "^4.59.0",
"rollup-plugin-copy": "^3.5.0",
- "ts-node": "^10.9.2",
"typedoc": "^0.28.1",
"typedoc-plugin-markdown": "^4.8.1",
"typescript": "^5.5.4"
},
"dependencies": {
- "@rollup/plugin-typescript": "^12.3.0",
"dexie": "^4.0.1",
"glob": "^11.0.0"
}
diff --git a/crates/web-client/rollup.config.js b/crates/web-client/rollup.config.js
index 6f913e6..f38f06b 100644
--- a/crates/web-client/rollup.config.js
+++ b/crates/web-client/rollup.config.js
@@ -98,7 +98,7 @@ const baseCargoArgs = [
*/
export default [
{
- input: ["./js/wasm.js", "./js/index.js"],
+ input: ["./js/wasm.js", "./js/index.js", "./js/eager.js"],
output: {
dir: `dist`,
format: "es",
diff --git a/crates/web-client/scripts/post-build.js b/crates/web-client/scripts/post-build.js
new file mode 100644
index 0000000..71cc718
--- /dev/null
+++ b/crates/web-client/scripts/post-build.js
@@ -0,0 +1,66 @@
+#!/usr/bin/env node
+// Post-build step that prepares dist/ for `attw` / `publint` compliance.
+//
+// Rewrites extensionless relative imports in dist/*.d.ts to use explicit
+// `.js` extensions. TypeScript's Node16/NodeNext module resolution
+// requires explicit extensions on relative specifiers; without this,
+// attw reports `InternalResolutionError` for the published types.
+//
+// The `lazy/package.json` node10 fallback shim is checked into the repo
+// at `crates/web-client/lazy/package.json` rather than emitted here; this
+// keeps the published artifact set fully visible in source control and
+// avoids a script-generated file outside `dist/`.
+//
+// This file only touches generated output in `dist/`. It does not modify
+// any source under `src/` or `js/types/`.
+
+import fs from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const distDir = path.resolve(__dirname, "..", "dist");
+
+function rewriteDtsImports(dir) {
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
+ const full = path.join(dir, entry.name);
+ if (entry.isDirectory()) {
+ rewriteDtsImports(full);
+ continue;
+ }
+ if (!entry.name.endsWith(".d.ts")) continue;
+
+ const original = fs.readFileSync(full, "utf8");
+ // Match relative specifiers in two forms used by the hand-authored
+ // declaration files in `js/types/`:
+ // 1. `from "./foo"` / `from "../foo"` (static import/re-export)
+ // 2. `import("./foo")` (dynamic import in type position)
+ // For each, append `.js` so Node16 type resolution finds the sibling
+ // `.d.ts` (TS resolves `./foo.js` -> `./foo.d.ts` automatically).
+ //
+ // Does not handle bare side-effect imports (import "./foo") — none
+ // currently exist in dist/**.d.ts.
+ const rewriteSpec = (match, prefix, spec, suffix) => {
+ if (/\.[a-zA-Z0-9]+$/.test(spec)) return match; // already has extension
+ if (spec.endsWith("/")) return match; // directory specifier, leave alone
+ return `${prefix}${spec}.js${suffix}`;
+ };
+ const updated = original
+ .replace(/(from\s+["'])(\.\.?\/[^"']+?)(["'])/g, rewriteSpec)
+ .replace(/(import\s*\(\s*["'])(\.\.?\/[^"']+?)(["']\s*\))/g, rewriteSpec);
+
+ if (updated !== original) {
+ fs.writeFileSync(full, updated);
+ console.log(
+ `[post-build] Rewrote relative imports in ${path.relative(distDir, full)}`
+ );
+ }
+ }
+}
+
+if (!fs.existsSync(distDir)) {
+ console.error(`[post-build] dist directory not found at ${distDir}`);
+ process.exit(1);
+}
+
+rewriteDtsImports(distDir);
diff --git a/crates/web-client/test/global.test.d.ts b/crates/web-client/test/global.test.d.ts
index a5d6559..0b84b0e 100644
--- a/crates/web-client/test/global.test.d.ts
+++ b/crates/web-client/test/global.test.d.ts
@@ -1,4 +1,4 @@
-import { Page } from "puppeteer";
+import { Page } from "@playwright/test";
import { WebClient as WasmWebClient } from "../dist/crates/miden_client_web";
import {
Account,
diff --git a/crates/web-client/test/node-adapter.ts b/crates/web-client/test/node-adapter.ts
index 64e9c9f..037efd7 100644
--- a/crates/web-client/test/node-adapter.ts
+++ b/crates/web-client/test/node-adapter.ts
@@ -116,7 +116,7 @@ function initSdk(): any {
return rawSdk;
}
-export const sdk = new Proxy(
+const sdk = new Proxy(
{},
{
get(_target, prop) {
@@ -323,7 +323,7 @@ function tmpTestDir(): string {
/**
* Matches the browser's `window.MockWasmWebClient` interface.
*/
-export const MockWasmWebClient = {
+const MockWasmWebClient = {
createClient: async (
seed?: any,
serializedMockChain?: any,
diff --git a/crates/web-client/test/sync_lock.test.ts b/crates/web-client/test/sync_lock.test.ts
index 7a067b4..a837617 100644
--- a/crates/web-client/test/sync_lock.test.ts
+++ b/crates/web-client/test/sync_lock.test.ts
@@ -600,19 +600,10 @@ test.describe("Sync Lock Timeout Race Condition", () => {
// This test verifies that waiters (coalesced callers) are properly
// rejected when the sync they're waiting on times out
const result = await page.evaluate(async () => {
- // Access the sync lock functions directly from the idxdb-store module
- const { acquireSyncLock, releaseSyncLock, releaseSyncLockWithError } =
- await import("@aspect-build/aspect-rsdoctor/index.js").catch(() => {
- // Fallback: the functions may not be directly exported
- // In this case, we test via the client API
- return {
- acquireSyncLock: null,
- releaseSyncLock: null,
- releaseSyncLockWithError: null,
- };
- });
-
- // If we can't access the low-level functions, test via client API
+ // Test via the client API: an in-flight sync that holds the lock
+ // plus two coalesced waiters. If the lock implementation regresses
+ // (e.g. waiters aren't rejected on timeout), Promise.all rejects
+ // and the assertions below fail.
const client = window.client;
// Start a sync that will hold the lock
diff --git a/crates/web-client/test/test-helpers.ts b/crates/web-client/test/test-helpers.ts
index 6d13db6..e0a9fb8 100644
--- a/crates/web-client/test/test-helpers.ts
+++ b/crates/web-client/test/test-helpers.ts
@@ -655,83 +655,3 @@ export async function createIntegrationClient(): Promise<{
return null;
}
}
-
-/**
- * Mints tokens using integration flow (executeAndApplyTransaction + waitForTransaction).
- * Requires a running node.
- */
-export async function integrationMint(
- client: any,
- sdk: any,
- targetId: any,
- faucetId: any,
- opts?: { amount?: number; publicNote?: boolean; sync?: boolean }
-): Promise<{
- transactionId: string;
- createdNoteId: string;
- numOutputNotesCreated: number;
-}> {
- const amount = opts?.amount ?? 1000;
- const noteType = opts?.publicNote
- ? sdk.NoteType.Public
- : sdk.NoteType.Private;
- const shouldSync = opts?.sync !== false;
-
- await client.syncState();
-
- const mintRequest = await client.newMintTransactionRequest(
- targetId,
- faucetId,
- noteType,
- sdk.u64(amount)
- );
- const result = await executeAndApplyTransaction(
- client,
- sdk,
- faucetId,
- mintRequest
- );
-
- const transactionId = result.executedTransaction().id().toHex();
- const createdNoteId = result.createdNotes().notes()[0].id().toString();
- const numOutputNotesCreated = result.createdNotes().numNotes();
-
- if (shouldSync) {
- await waitForTransaction(client, sdk, transactionId);
- }
-
- return { transactionId, createdNoteId, numOutputNotesCreated };
-}
-
-/**
- * Consumes a note using integration flow.
- */
-export async function integrationConsume(
- client: any,
- sdk: any,
- accountId: any,
- faucetId: any,
- noteId: string
-): Promise<{ transactionId: string; targetAccountBalance: string }> {
- await client.syncState();
-
- const inputNoteRecord = await client.getInputNote(noteId);
- if (!inputNoteRecord) throw new Error(`Note ${noteId} not found`);
-
- const note = inputNoteRecord.toNote();
- const consumeRequest = client.newConsumeTransactionRequest([note]);
- const result = await executeAndApplyTransaction(
- client,
- sdk,
- accountId,
- consumeRequest
- );
-
- const transactionId = result.executedTransaction().id().toHex();
- await waitForTransaction(client, sdk, transactionId);
-
- const account = await client.getAccount(accountId);
- const balance = account.vault().getBalance(faucetId).toString();
-
- return { transactionId, targetAccountBalance: balance };
-}
diff --git a/crates/web-client/test/test-setup.ts b/crates/web-client/test/test-setup.ts
index fec621c..f61713d 100644
--- a/crates/web-client/test/test-setup.ts
+++ b/crates/web-client/test/test-setup.ts
@@ -81,7 +81,7 @@ export function loadNodeSdk(): any {
let _nodeTestCounter = 0;
-export async function createNodeMockClient(): Promise<{
+async function createNodeMockClient(): Promise<{
client: any;
sdk: any;
}> {
@@ -333,7 +333,7 @@ function patchNapiPrototypes(rawSdk: any) {
}
}
-export function createNodeSdkWrapper(rawSdk: any): any {
+function createNodeSdkWrapper(rawSdk: any): any {
patchNapiPrototypes(rawSdk);
// Expose the StorageView JS wrapper on `sdk.*` so tests can reach it via the
// same namespace on both platforms (browser exposes it on `window.*`).
diff --git a/crates/web-client/test/webClientTestUtils.ts b/crates/web-client/test/webClientTestUtils.ts
index a171e6e..47b1a8e 100644
--- a/crates/web-client/test/webClientTestUtils.ts
+++ b/crates/web-client/test/webClientTestUtils.ts
@@ -1,5 +1,5 @@
// @ts-nocheck
-import { expect } from "chai";
+import { expect } from "@playwright/test";
import { TransactionProver } from "../dist";
import test from "./playwright.global.setup";
import { Page } from "@playwright/test";
@@ -942,7 +942,7 @@ export const clearStore = async (page: Page) => {
// Misc test utils
export const isValidAddress = (address: string) => {
- expect(address.startsWith("0x")).to.be.true;
+ expect(address.startsWith("0x")).toBe(true);
};
// Constants
diff --git a/crates/web-client/tsconfig.json b/crates/web-client/tsconfig.json
index a9c6c4b..263dabf 100644
--- a/crates/web-client/tsconfig.json
+++ b/crates/web-client/tsconfig.json
@@ -22,10 +22,5 @@
"tsconfig.json",
"./test/playwright.global.setup.ts"
],
- "exclude": ["node_modules", "js"],
- "ts-node": {
- "esm": true,
- "experimentalSpecifierResolution": "node",
- "transpileOnly": true
- }
+ "exclude": ["node_modules", "js"]
}
diff --git a/eslint.config.js b/eslint.config.js
index f483ca7..3268e2c 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -1,3 +1,5 @@
+const prettierConfig = require("eslint-config-prettier");
+
module.exports = [
{
// Ignore patterns
@@ -10,6 +12,7 @@ module.exports = [
"crates/idxdb-store/src/**",
"packages/react-sdk/**",
"packages/vite-plugin/**",
+ "vitest.config.ts",
],
},
{
@@ -23,34 +26,6 @@ module.exports = [
},
rules: {
camelcase: ["error", { properties: "always" }],
- semi: ["error", "always"],
- "keyword-spacing": [
- "error",
- {
- before: true,
- after: true,
- },
- ],
- "comma-dangle": [
- "error",
- {
- arrays: "always-multiline",
- objects: "always-multiline",
- imports: "always-multiline",
- exports: "always-multiline",
- functions: "never",
- },
- ],
- "eol-last": ["error", "always"],
- "space-before-blocks": ["error", "always"],
- "no-multiple-empty-lines": [
- "error",
- {
- max: 1,
- maxBOF: 0,
- maxEOF: 0,
- },
- ],
},
},
{
@@ -66,34 +41,8 @@ module.exports = [
},
rules: {
camelcase: ["error", { properties: "always" }],
- semi: ["error", "always"],
- "keyword-spacing": [
- "error",
- {
- before: true,
- after: true,
- },
- ],
- "comma-dangle": [
- "error",
- {
- arrays: "always-multiline",
- objects: "always-multiline",
- imports: "always-multiline",
- exports: "always-multiline",
- functions: "never",
- },
- ],
- "eol-last": ["error", "always"],
- "space-before-blocks": ["error", "always"],
- "no-multiple-empty-lines": [
- "error",
- {
- max: 1,
- maxBOF: 0,
- maxEOF: 0,
- },
- ],
},
},
+ // Must be last: disables any stylistic rules that conflict with Prettier.
+ prettierConfig,
];
diff --git a/knip.jsonc b/knip.jsonc
new file mode 100644
index 0000000..d2246b2
--- /dev/null
+++ b/knip.jsonc
@@ -0,0 +1,131 @@
+// Knip — unused-exports / unused-deps / unused-files detector.
+//
+// Strict mode: package.json#scripts.check:knip runs `knip` with no
+// `--no-exit-code` flag, so any finding fails CI. The allowlists below
+// each carry a comment naming the consumer that knip cannot statically
+// see (browser-resolved imports, makefile targets, GitHub workflows,
+// post-build copy, etc.) — touch them only if that consumer changes.
+//
+// Workspace layout: pnpm 9 monorepo with three published TS packages
+// plus a small TS-bundled crate. `entry` patterns are reserved for files
+// knip can't infer from a plugin (e.g. rollup configs, tsup configs,
+// vitest configs, eslint configs are all picked up automatically).
+{
+ "$schema": "https://unpkg.com/knip@6/schema.json",
+ // Top-level binaries that show up in CI workflows / Makefiles but
+ // aren't pulled in by any source file.
+ "ignoreBinaries": [
+ // Used in .github/workflows/wallet-pages.yml via `pnpm exec vite
+ // build` from the wallet example workspace (which has its own
+ // package.json + lockfile, hidden from this monorepo's graph).
+ "vite",
+ ],
+ "ignoreDependencies": [
+ // `dexie` is bundled into the web-client test page via rollup at
+ // test runtime — `page.evaluate(...)` blocks load the rolled-up
+ // bundle from http://localhost:8080, which transitively imports
+ // dexie. Knip's static scan can't see across the build boundary.
+ "dexie",
+ // `publint` and `@arethetypeswrong/cli` are invoked by the
+ // `check:publint` / `check:attw` scripts in root package.json via
+ // `pnpm exec publint` / `pnpm exec attw`. Knip flags them as unused
+ // because it only follows ESM/CJS imports in source, not script
+ // strings.
+ "publint",
+ "@arethetypeswrong/cli",
+ ],
+ "rules": {
+ // Three intentional dual-export patterns in this repo:
+ // - packages/vite-plugin/src/index.ts: `export function midenVitePlugin`
+ // + `export default midenVitePlugin` — both forms are documented
+ // public API; consumers use either named or default import.
+ // - crates/web-client/test/playwright.global.setup.ts: `export const
+ // test` + `export default test` — fixture re-exports used by both
+ // `import test from ...` and `import { test as base } from ...`
+ // patterns across ~50 test files.
+ // - packages/react-sdk/test/test-app/react-jsx-runtime.js: `jsx`,
+ // `jsxs`, `jsxDEV` are all aliased to the same function because
+ // the React JSX transform looks for whichever name matches the
+ // classic/automatic runtime.
+ "duplicates": "off",
+ },
+ "workspaces": {
+ ".": {
+ "entry": ["scripts/**/*.{js,ts}"],
+ },
+ "packages/react-sdk": {
+ "entry": [
+ "src/__tests__/**/*.test.{ts,tsx}",
+ "test/**/*.test.ts",
+ "test/serve-tests.cjs",
+ "test/test-app/**/*.{js,html}",
+ ],
+ },
+ "packages/vite-plugin": {
+ "entry": ["src/__tests__/**/*.test.ts"],
+ },
+ "crates/web-client": {
+ // Hand-rolled JS in `js/` is the published surface (bundled via
+ // rollup). Files not picked up by the rollup plugin entry trace
+ // (worker bundles, JS unit tests, helper modules, Playwright TS
+ // tests, build scripts) are listed explicitly here.
+ "entry": [
+ "js/standalone.js",
+ "js/client.js",
+ "js/asyncLock.js",
+ "js/syncLock.js",
+ "js/webLock.js",
+ "js/utils.js",
+ "js/constants.js",
+ "js/storageView.js",
+ // js/node-index.js is auto-detected from the package.json `node`
+ // exports condition; only the rest of js/node/ needs to be listed
+ // explicitly here (helpers loaded via dynamic require / wrapper
+ // factories that knip can't statically follow).
+ "js/node/**/*.js",
+ "js/workers/**/*.js",
+ // The .d.ts files in js/types/ ship as the package's published
+ // type surface — `pnpm run build` runs `cpr js/types dist` after
+ // rollup, so dist/index.d.ts (the package's `types` field) is a
+ // copy of js/types/index.d.ts. Knip can't see the post-build
+ // copy step, so we register the source files as entry points.
+ "js/types/index.d.ts",
+ "js/types/api-types.d.ts",
+ "js/types/docs-entry.d.ts",
+ "test/**/*.test.ts",
+ "test/webClientTestUtils.ts",
+ "test/playwright.global.setup.ts",
+ "scripts/**/*.js",
+ ],
+ // The `./index.js` strings inside `page.evaluate(...)` blocks are
+ // dynamic imports executed in the *browser* against
+ // http://localhost:8080 — they resolve to files in dist/ at test
+ // runtime, not relative to the Playwright test file.
+ // `./crates/miden_client_web` is the wasm-bindgen module emitted
+ // into dist/ by the rollup rust plugin; the .d.ts files in
+ // js/types/ reference it but it doesn't exist until after build.
+ // Knip can't follow either of these dynamic targets.
+ "ignoreUnresolved": ["./index.js", "./crates/miden_client_web"],
+ },
+ "crates/idxdb-store/src": {
+ // Each `ts/*.ts` is independently tsc-compiled into `js/` and
+ // consumed by the Rust crate, so each file is its own entry point.
+ "entry": [
+ "ts/accounts.ts",
+ "ts/auth.ts",
+ "ts/chainData.ts",
+ "ts/export.ts",
+ "ts/import.ts",
+ "ts/notes.ts",
+ "ts/schema.ts",
+ "ts/settings.ts",
+ "ts/sync.ts",
+ "ts/transactions.ts",
+ "ts/utils.ts",
+ "ts/test-utils.ts",
+ "ts/**/*.test.ts",
+ ],
+ },
+ },
+ "ignore": ["packages/react-sdk/examples/**", "crates/idxdb-store/src/js/**"],
+}
diff --git a/lefthook.yml b/lefthook.yml
new file mode 100644
index 0000000..57781c1
--- /dev/null
+++ b/lefthook.yml
@@ -0,0 +1,13 @@
+# Lefthook config — local dev hygiene hooks.
+# Docs: https://lefthook.dev/configuration/
+#
+# Install: `pnpm install` triggers `prepare` → `lefthook install`.
+# Manual: `pnpm exec lefthook install`
+
+pre-commit:
+ commands:
+ lint-staged:
+ run: pnpm exec lint-staged
+ stage_fixed: true
+
+# commit-msg: future commitlint integration goes here
diff --git a/package.json b/package.json
index a42e5a5..53064cf 100644
--- a/package.json
+++ b/package.json
@@ -3,20 +3,42 @@
"private": true,
"scripts": {
"check:sync:react-sdk": "node scripts/check-react-sdk-sync.js",
+ "check:knip": "knip",
"build:web-client": "pnpm --filter @miden-sdk/miden-sdk run build",
"build:react-sdk": "pnpm --filter @miden-sdk/react run build",
"build:vite-plugin": "pnpm --filter @miden-sdk/vite-plugin run build",
+ "test": "vitest run",
+ "test:watch": "vitest",
"test:react-sdk": "pnpm --filter @miden-sdk/react run test:unit",
- "test:react-sdk:coverage": "pnpm --filter @miden-sdk/react run test:coverage"
+ "test:react-sdk:coverage": "pnpm --filter @miden-sdk/react run test:coverage",
+ "prepare": "lefthook install || true",
+ "check:publint": "pnpm --filter @miden-sdk/miden-sdk --filter @miden-sdk/react --filter @miden-sdk/vite-plugin --workspace-concurrency=1 exec publint",
+ "check:attw": "pnpm --filter @miden-sdk/miden-sdk --filter @miden-sdk/react --filter @miden-sdk/vite-plugin --workspace-concurrency=1 exec attw --pack .",
+ "check:publish": "pnpm run build:web-client && pnpm run build:react-sdk && pnpm run build:vite-plugin && pnpm run check:publint && pnpm run check:attw"
},
"dependencies": {
"prettier": "^3.8.1"
},
"devDependencies": {
- "@typescript-eslint/eslint-plugin": "^8.25.0",
+ "@arethetypeswrong/cli": "^0.18.2",
"@typescript-eslint/parser": "^8.25.0",
"eslint": "^9.30.1",
- "typescript": "^5.5.4"
+ "eslint-config-prettier": "^10.1.8",
+ "knip": "^6.7.0",
+ "lefthook": "^1.13.6",
+ "lint-staged": "^16.4.0",
+ "publint": "^0.3.18",
+ "typescript": "^5.5.4",
+ "vitest": "^3.0.0"
+ },
+ "lint-staged": {
+ "*.{ts,tsx,js,jsx}": [
+ "eslint --fix",
+ "prettier --write"
+ ],
+ "*.{json,md,yml,yaml,css}": [
+ "prettier --write"
+ ]
},
"packageManager": "pnpm@9.15.4",
"engines": {
diff --git a/packages/react-sdk/.attw.json b/packages/react-sdk/.attw.json
new file mode 100644
index 0000000..daeca4e
--- /dev/null
+++ b/packages/react-sdk/.attw.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://raw.githubusercontent.com/arethetypeswrong/arethetypeswrong.github.io/main/docs/configuration.md",
+ "profile": "esm-only"
+}
diff --git a/packages/react-sdk/CLAUDE.md b/packages/react-sdk/CLAUDE.md
index a8a1c8c..9dcf1b0 100644
--- a/packages/react-sdk/CLAUDE.md
+++ b/packages/react-sdk/CLAUDE.md
@@ -45,36 +45,37 @@ function App() {
## Reading Data (Query Hooks)
-All query hooks return `{ data, isLoading, error, refetch }`.
+Query hooks return `{ ...data, isLoading, error, refetch }` — the data fields are spread directly onto the result object, with hook-specific names (no generic `data` field).
### List Accounts
```tsx
-const { data: accounts, isLoading } = useAccounts();
+const { accounts, wallets, faucets, isLoading } = useAccounts();
-// accounts.wallets - regular accounts
-// accounts.faucets - token faucets
-// accounts.all - everything
+// wallets - regular accounts
+// faucets - token faucets
+// accounts - both, combined
```
### Get Account Details
```tsx
-const { data: account } = useAccount(accountId);
+const { account, isLoading } = useAccount(accountId);
-// account.id, account.nonce, account.bech32id()
-// account.balance(faucetId) - get token balance
+// account.id(), account.nonce(), account.bech32id()
+// account.vault().getBalance(assetId) - get token balance
```
### Get Notes
```tsx
-const { data: notes } = useNotes();
+const { notes, consumableNotes, noteSummaries, consumableNoteSummaries } = useNotes();
-// notes.input - incoming notes
-// notes.consumable - ready to claim
+// notes - all input notes for this account
+// consumableNotes - subset that's ready to claim
+// noteSummaries / consumableNoteSummaries - same lists, projected to UI-friendly summaries
```
### Check Sync Status
```tsx
-const { syncHeight, isSyncing, sync } = useSyncState();
+const { syncHeight, isSyncing, lastSyncTime, sync, error } = useSyncState();
// Manual sync
await sync();
@@ -82,19 +83,19 @@ await sync();
### Get Token Metadata
```tsx
-const { data: metadata } = useAssetMetadata(faucetId);
+const { metadata, isLoading } = useAssetMetadata(assetId);
// metadata.symbol, metadata.decimals
```
## Writing Data (Mutation Hooks)
-All mutation hooks return `{ mutate, data, isLoading, stage, error, reset }`.
+Mutation hooks return `{ , result, isLoading, stage, error, reset }` — the action callback is named after the hook (`send`, `consume`, `mint`, ...) and the resolved value is on `result`.
**Transaction stages:** `idle` → `executing` → `proving` → `submitting` → `complete`
### Create Wallet
```tsx
-const { mutate: createWallet, isLoading } = useCreateWallet();
+const { createWallet, isLoading } = useCreateWallet();
const account = await createWallet({
storageMode: "private", // "private" | "public" | "network"
@@ -103,12 +104,12 @@ const account = await createWallet({
### Send Tokens
```tsx
-const { mutate: send, stage } = useSend();
+const { send, stage } = useSend();
await send({
from: senderAccountId,
to: recipientAccountId,
- faucetId: tokenFaucetId,
+ assetId: tokenFaucetId,
amount: 1000n,
noteType: "private", // "private" | "public"
});
@@ -116,20 +117,20 @@ await send({
### Send to Multiple Recipients
```tsx
-const { mutate: multiSend } = useMultiSend();
+const { multiSend } = useMultiSend();
await multiSend({
from: senderAccountId,
- outputs: [
- { to: recipient1, faucetId, amount: 500n },
- { to: recipient2, faucetId, amount: 300n },
+ recipients: [
+ { to: recipient1, assetId, amount: 500n },
+ { to: recipient2, assetId, amount: 300n },
],
});
```
### Claim Notes
```tsx
-const { mutate: consume } = useConsume();
+const { consume } = useConsume();
await consume({
accountId: myAccountId,
@@ -139,7 +140,7 @@ await consume({
### Mint Tokens (Faucet Owner)
```tsx
-const { mutate: mint } = useMint();
+const { mint } = useMint();
await mint({
faucetId: myFaucetId,
@@ -150,7 +151,7 @@ await mint({
### Create Faucet
```tsx
-const { mutate: createFaucet } = useCreateFaucet();
+const { createFaucet } = useCreateFaucet();
const faucet = await createFaucet({
symbol: "TOKEN",
@@ -165,11 +166,11 @@ const faucet = await createFaucet({
### Show Transaction Progress
```tsx
function SendButton() {
- const { mutate: send, stage, isLoading, error } = useSend();
+ const { send, stage, isLoading, error } = useSend();
const handleSend = async () => {
try {
- await send({ from, to, faucetId, amount });
+ await send({ from, to, assetId, amount });
} catch (err) {
console.error("Transaction failed:", err);
}
@@ -207,11 +208,11 @@ const text = formatNoteSummary(summary); // "1.5 TOKEN"
### Wait for Transaction Confirmation
```tsx
-const { mutate: waitForCommit } = useWaitForCommit();
+const { waitForCommit } = useWaitForCommit();
// After sending
const result = await send({ ... });
-await waitForCommit({ transactionId: result.transactionId });
+await waitForCommit({ txId: result.txId });
```
### Access Client Directly
@@ -316,7 +317,8 @@ import { SignerContext } from "@miden-sdk/react";
storeName: `mywallet_${userAddress}`, // unique per user for DB isolation
isConnected: true,
accountConfig: {
- publicKey: userPublicKeyCommitment, // Uint8Array
+ publicKeyCommitment: userPublicKeyCommitment, // Uint8Array
+ accountType: "RegularAccountUpdatableCode",
storageMode: "private",
},
signCb: async (pubKey, signingInputs) => {
@@ -377,23 +379,40 @@ account.bech32id(); // "miden1qy35..."
## Hook Reference
-| Hook | Returns | Purpose |
-|------|---------|---------|
-| `useAccounts()` | `{ wallets, faucets, all }` | List all accounts |
-| `useAccount(id)` | `Account` | Account details + balances |
-| `useNotes(filter?)` | `{ input, consumable }` | Available notes |
-| `useSyncState()` | `{ syncHeight, sync() }` | Sync status |
-| `useAssetMetadata(id)` | `{ symbol, decimals }` | Token info |
-| `useCreateWallet()` | `Account` | Create wallet |
-| `useCreateFaucet()` | `Account` | Create faucet |
-| `useImportAccount()` | `Account` | Import account |
-| `useSend()` | `TransactionResult` | Send tokens |
-| `useMultiSend()` | `TransactionResult` | Multi-recipient send |
-| `useMint()` | `TransactionResult` | Mint tokens |
-| `useConsume()` | `TransactionResult` | Claim notes |
-| `useSwap()` | `TransactionResult` | Atomic swap |
-| `useTransaction()` | `TransactionResult` | Custom transaction |
-| `useCompile()` | `{ component, txScript, noteScript }` | Compile MASM into `AccountComponent` / `TransactionScript` / `NoteScript` |
+Query hooks return `{ ...data, isLoading, error, refetch }`. Mutation hooks return `{ , result, isLoading, stage, error, reset }`.
+
+### Query (read)
+| Hook | Data fields | Purpose |
+|------|-------------|---------|
+| `useAccounts()` | `accounts`, `wallets`, `faucets` | List local accounts |
+| `useAccount(id)` | `account` | Account details + balances |
+| `useNotes(filter?)` | `notes`, `consumableNotes`, `noteSummaries`, `consumableNoteSummaries` | Input notes + UI summaries |
+| `useNoteStream(filter?)` | streaming variant of `useNotes` | Auto-updates as notes arrive |
+| `useSyncState()` | `syncHeight`, `isSyncing`, `lastSyncTime`, `sync()` | Sync status + manual trigger |
+| `useSyncControl()` | `pause()`, `resume()`, `isPaused` | Pause/resume the auto-sync timer |
+| `useAssetMetadata(id)` | `metadata: { symbol, decimals }` | Token info |
+| `useTransactionHistory(...)` | `transactions` | Local transaction log |
+| `useSessionAccount()` | `account` | The signer's connected account |
+| `useWaitForNotes(...)` | resolves when matching notes appear | Pull-style note waiting |
+
+### Mutation (write)
+| Hook | Action | Returns on success |
+|------|--------|--------------------|
+| `useCreateWallet()` | `createWallet({ storageMode })` | `Account` |
+| `useCreateFaucet()` | `createFaucet({ symbol, decimals, ... })` | `Account` |
+| `useImportAccount()` | `importAccount(...)` | `Account` |
+| `useImportNote()` | `importNote(...)` | imported `InputNoteRecord` |
+| `useExportNote()` | `exportNote(...)` | serialized note bytes |
+| `useImportStore()` / `useExportStore()` | store import/export | bytes / `void` |
+| `useSend()` | `send({ from, to, assetId, amount, noteType })` | `SendResult` (with `txId`, `note`) |
+| `useMultiSend()` | `multiSend({ from, recipients })` | `TransactionResult` |
+| `useMint()` | `mint({ faucetId, to, amount })` | `TransactionResult` |
+| `useConsume()` | `consume({ accountId, notes })` | `TransactionResult` |
+| `useSwap()` | `swap({ ... })` | `TransactionResult` |
+| `useTransaction()` | `transact({ ... })` | `TransactionResult` (custom tx) |
+| `useExecuteProgram()` | `execute(...)` | program output |
+| `useCompile()` | `compile({ source })` | `{ component, txScript, noteScript }` |
+| `useWaitForCommit()` | `waitForCommit({ txId })` | resolves when committed on-chain |
## Type Imports
diff --git a/packages/react-sdk/eslint.config.js b/packages/react-sdk/eslint.config.cjs
similarity index 83%
rename from packages/react-sdk/eslint.config.js
rename to packages/react-sdk/eslint.config.cjs
index 71027b0..9c2d6f7 100644
--- a/packages/react-sdk/eslint.config.js
+++ b/packages/react-sdk/eslint.config.cjs
@@ -1,5 +1,6 @@
const tsParser = require("@typescript-eslint/parser");
const tsPlugin = require("@typescript-eslint/eslint-plugin");
+const prettierConfig = require("eslint-config-prettier");
module.exports = [
{
@@ -33,4 +34,6 @@ module.exports = [
],
},
},
+ // Must be last: disables any stylistic rules that conflict with Prettier.
+ prettierConfig,
];
diff --git a/packages/react-sdk/package.json b/packages/react-sdk/package.json
index 15d07bf..99fcee7 100644
--- a/packages/react-sdk/package.json
+++ b/packages/react-sdk/package.json
@@ -7,10 +7,16 @@
"types": "dist/index.d.ts",
"exports": {
".": {
- "types": "./dist/index.d.ts",
- "import": "./dist/index.mjs",
- "require": "./dist/index.js"
- }
+ "import": {
+ "types": "./dist/index.d.mts",
+ "default": "./dist/index.mjs"
+ },
+ "require": {
+ "types": "./dist/index.d.ts",
+ "default": "./dist/index.js"
+ }
+ },
+ "./package.json": "./package.json"
},
"files": [
"dist",
@@ -40,11 +46,10 @@
"@playwright/test": "^1.55.0",
"@testing-library/react": "^14.0.0",
"@types/react": "^18.2.0",
- "@typescript-eslint/eslint-plugin": "^6.0.0",
- "@typescript-eslint/parser": "^6.0.0",
+ "@typescript-eslint/eslint-plugin": "^8.25.0",
+ "@typescript-eslint/parser": "^8.25.0",
"@vitest/coverage-v8": "^3.0.0",
- "eslint": "^8.0.0",
- "http-server": "^14.1.1",
+ "eslint": "^9.30.1",
"jsdom": "^24.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
diff --git a/packages/react-sdk/playwright.config.ts b/packages/react-sdk/playwright.config.ts
index 1b484f8..eead91e 100644
--- a/packages/react-sdk/playwright.config.ts
+++ b/packages/react-sdk/playwright.config.ts
@@ -31,7 +31,7 @@ export default defineConfig({
],
webServer: {
- command: "node ./test/serve-tests.js",
+ command: "node ./test/serve-tests.cjs",
url: "http://127.0.0.1:8081",
reuseExistingServer: true,
timeout: 30000,
diff --git a/packages/react-sdk/src/__tests__/mocks/miden-sdk.ts b/packages/react-sdk/src/__tests__/mocks/miden-sdk.ts
index fddcb9e..99481cf 100644
--- a/packages/react-sdk/src/__tests__/mocks/miden-sdk.ts
+++ b/packages/react-sdk/src/__tests__/mocks/miden-sdk.ts
@@ -76,7 +76,7 @@ export const createMockNote = (id: string = "0xnote1") => ({
free: vi.fn(),
});
-export const createMockOutputNote = (note = createMockNote()) => ({
+const createMockOutputNote = (note = createMockNote()) => ({
intoFull: vi.fn(() => note),
});
@@ -162,7 +162,7 @@ export const createMockTransactionId = (id: string = "0xtx123") => ({
});
// Mock TransactionRecord
-export const createMockTransactionRecord = (
+const createMockTransactionRecord = (
status: "committed" | "pending" | "discarded" = "committed"
) => ({
id: vi.fn(() => createMockTransactionId()),
@@ -184,120 +184,6 @@ export const createMockTransactionRequest = () => ({
[Symbol.dispose]: vi.fn(),
});
-// Mock NoteFilter
-export const MockNoteFilter = vi.fn().mockImplementation(() => ({
- free: vi.fn(),
-}));
-
-// Mock NoteFilterTypes enum
-export const MockNoteFilterTypes = {
- All: 0,
- Consumed: 1,
- Committed: 2,
- Expected: 3,
- Processing: 4,
- List: 5,
- Unique: 6,
- Nullifiers: 7,
- Unverified: 8,
-};
-
-// Mock NoteType enum
-export const MockNoteType = {
- Private: 2,
- Public: 1,
-};
-
-export const MockNote = {
- createP2IDNote: vi.fn(
- (
- sender: ReturnType,
- receiver: ReturnType,
- assets: unknown,
- noteType: number,
- attachment: unknown
- ) => ({
- id: vi.fn(() => ({ toString: () => "0xnote" })),
- sender,
- receiver,
- assets,
- noteType,
- attachment,
- })
- ),
-};
-
-export const MockNoteAssets = class NoteAssets {
- assets: unknown[];
- constructor(assets: unknown[]) {
- this.assets = assets;
- }
-};
-
-export const MockFungibleAsset = class FungibleAsset {
- faucetId: ReturnType;
- amount: bigint;
- constructor(
- faucetId: ReturnType,
- amount: bigint
- ) {
- this.faucetId = faucetId;
- this.amount = amount;
- }
-};
-
-export const MockNoteAttachment = class NoteAttachment {};
-
-export const MockNoteArray = class NoteArray {
- notes: unknown[];
- constructor(notes?: unknown[]) {
- this.notes = notes ?? [];
- }
- push(note: unknown) {
- this.notes.push(note);
- }
-};
-
-export const MockNoteAndArgs = class NoteAndArgs {
- note: unknown;
- args: unknown;
- constructor(note: unknown, args: unknown) {
- this.note = note;
- this.args = args;
- }
-};
-
-export const MockNoteAndArgsArray = class NoteAndArgsArray {
- notes: unknown[];
- constructor(notes: unknown[]) {
- this.notes = notes;
- }
-};
-
-export const MockTransactionRequestBuilder = class TransactionRequestBuilder {
- withOwnOutputNotes = vi.fn(() => this);
- withInputNotes = vi.fn(() => this);
- build = vi.fn(() => ({}));
-};
-
-// Mock NoteId static methods
-export const MockNoteId = {
- fromHex: vi.fn((hex: string) => ({ toString: () => hex })),
-};
-
-// Mock AccountStorageMode
-export const MockAccountStorageMode = {
- private: vi.fn(() => ({ type: "private" })),
- public: vi.fn(() => ({ type: "public" })),
- network: vi.fn(() => ({ type: "network" })),
-};
-
-// Mock AccountId static methods
-export const MockAccountId = {
- fromHex: vi.fn((hex: string) => createMockAccountId(hex)),
- fromBech32: vi.fn((bech32: string) => createMockAccountId(bech32)),
-};
-
// Mock FeltArray
export const createMockFeltArray = (length: number = 16) => ({
length: vi.fn(() => length),
@@ -306,27 +192,6 @@ export const createMockFeltArray = (length: number = 16) => ({
})),
});
-// Mock AdviceInputs
-export const MockAdviceInputs = class AdviceInputs {};
-
-// Mock ForeignAccount
-export const MockForeignAccount = Object.assign(class ForeignAccount {}, {
- public: vi.fn(
- (_id: unknown, _storage: unknown) => new (class ForeignAccount {})()
- ),
-});
-
-// Mock ForeignAccountArray
-export const MockForeignAccountArray = class ForeignAccountArray {
- accounts: unknown[];
- constructor(accounts: unknown[] = []) {
- this.accounts = accounts;
- }
-};
-
-// Mock AccountStorageRequirements
-export const MockAccountStorageRequirements = class AccountStorageRequirements {};
-
// Create a mock WebClient
export const createMockWebClient = (
overrides: Partial = {}
@@ -406,7 +271,7 @@ export const createMockWebClient = (
return { ...defaultClient, ...overrides };
};
-export type MockWebClientType = {
+type MockWebClientType = {
createClient: ReturnType;
getAccounts: ReturnType;
getAccount: ReturnType;
@@ -440,65 +305,3 @@ export type MockWebClientType = {
executeProgram: ReturnType;
free: ReturnType;
};
-
-// Factory to create mock SDK module
-export const createMockSdkModule = (
- clientOverrides: Partial = {}
-) => {
- const mockClient = createMockWebClient(clientOverrides);
-
- const WebClientMock = Object.assign(
- vi.fn().mockImplementation(() => mockClient),
- {
- createClient: vi.fn().mockResolvedValue(mockClient),
- createClientWithExternalKeystore: vi.fn().mockResolvedValue(mockClient),
- }
- );
-
- return {
- WebClient: WebClientMock,
- WasmWebClient: WebClientMock,
- AccountId: MockAccountId,
- Address: {
- fromBech32: vi.fn((bech32: string) => ({
- accountId: vi.fn(() => createMockAccountId(bech32)),
- toString: vi.fn(() => bech32),
- })),
- fromAccountId: vi.fn(
- (accountId: ReturnType) => ({
- accountId: vi.fn(() => accountId),
- toString: vi.fn(() => accountId.toString()),
- })
- ),
- },
- AccountStorageMode: MockAccountStorageMode,
- NoteType: MockNoteType,
- Note: MockNote,
- NoteAssets: MockNoteAssets,
- FungibleAsset: MockFungibleAsset,
- NoteAttachment: MockNoteAttachment,
- NoteArray: MockNoteArray,
- NoteAndArgs: MockNoteAndArgs,
- NoteAndArgsArray: MockNoteAndArgsArray,
- TransactionRequestBuilder: MockTransactionRequestBuilder,
- TransactionFilter: {
- all: vi.fn(() => ({})),
- uncommitted: vi.fn(() => ({})),
- ids: vi.fn((ids: unknown) => ({ ids })),
- },
- AdviceInputs: MockAdviceInputs,
- ForeignAccount: MockForeignAccount,
- ForeignAccountArray: MockForeignAccountArray,
- AccountStorageRequirements: MockAccountStorageRequirements,
- AccountFile: Object.assign(
- vi.fn().mockImplementation(() => createMockAccountFile()),
- {
- deserialize: vi.fn(() => createMockAccountFile()),
- }
- ),
- NoteId: MockNoteId,
- NoteFilter: MockNoteFilter,
- NoteFilterTypes: MockNoteFilterTypes,
- __mockClient: mockClient, // Expose for test assertions
- };
-};
diff --git a/packages/react-sdk/src/__tests__/mocks/signer-context.ts b/packages/react-sdk/src/__tests__/mocks/signer-context.ts
index 5487a17..049ea59 100644
--- a/packages/react-sdk/src/__tests__/mocks/signer-context.ts
+++ b/packages/react-sdk/src/__tests__/mocks/signer-context.ts
@@ -12,7 +12,7 @@ import type {
* Creates a mock AccountStorageMode.
* Matches the SDK's AccountStorageMode interface.
*/
-export const createMockAccountStorageMode = (
+const createMockAccountStorageMode = (
mode: "private" | "public" | "network" = "public"
) => ({
toString: vi.fn(() => mode),
@@ -37,7 +37,7 @@ export function createMockSignerAccountConfig(
* Creates a mock sign callback function.
* Returns a mock 67-byte signature (typical ECDSA signature size).
*/
-export function createMockSignCallback(): SignCallback {
+function createMockSignCallback(): SignCallback {
return vi.fn().mockResolvedValue(new Uint8Array(67).fill(0xab));
}
diff --git a/packages/react-sdk/src/__tests__/store/MidenStore.test.ts b/packages/react-sdk/src/__tests__/store/MidenStore.test.ts
index 54bcb66..38a91bc 100644
--- a/packages/react-sdk/src/__tests__/store/MidenStore.test.ts
+++ b/packages/react-sdk/src/__tests__/store/MidenStore.test.ts
@@ -280,19 +280,13 @@ describe("MidenStore", () => {
});
describe("selector hooks", () => {
- it("should provide useClient selector", () => {
+ it("setClient stores the client and flips isReady", () => {
const mockClient = createMockWebClient();
- useMidenStore.getState().setClient(mockClient as any);
-
- // Test via getState (since we can't use React hooks directly in tests)
- expect(useMidenStore.getState().client).toBe(mockClient);
- });
-
- it("should provide useIsReady selector", () => {
expect(useMidenStore.getState().isReady).toBe(false);
- useMidenStore.getState().setClient(createMockWebClient() as any);
+ useMidenStore.getState().setClient(mockClient as any);
+ expect(useMidenStore.getState().client).toBe(mockClient);
expect(useMidenStore.getState().isReady).toBe(true);
});
diff --git a/packages/react-sdk/src/__tests__/utils/test-utils.tsx b/packages/react-sdk/src/__tests__/utils/test-utils.tsx
deleted file mode 100644
index 546e3a2..0000000
--- a/packages/react-sdk/src/__tests__/utils/test-utils.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import React, { type ReactNode } from "react";
-import { render, type RenderOptions, renderHook } from "@testing-library/react";
-import { useMidenStore } from "../../store/MidenStore";
-import type { MidenConfig } from "../../types";
-import {
- createMockWebClient,
- type MockWebClientType,
-} from "../mocks/miden-sdk";
-
-// Reset store between tests
-export const resetStore = () => {
- useMidenStore.getState().reset();
-};
-
-// Provider wrapper with mock client already set
-interface WrapperProps {
- children: ReactNode;
-}
-
-interface TestProviderOptions {
- config?: MidenConfig;
- mockClient?: Partial;
- initialReady?: boolean;
-}
-
-// Create a test provider that sets the client directly (bypassing async init)
-export const createTestProvider = (options: TestProviderOptions = {}) => {
- const mockClient = createMockWebClient(options.mockClient);
-
- const TestProvider = ({ children }: WrapperProps) => {
- // Set up the store directly with our mock client
- React.useEffect(() => {
- if (options.initialReady !== false) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- useMidenStore.getState().setClient(mockClient as any);
- }
- }, []);
-
- return <>{children}>;
- };
-
- return { TestProvider, mockClient };
-};
-
-// Render hook with test provider
-export const renderHookWithProvider = (
- hook: (props: TProps) => TResult,
- options: TestProviderOptions & { hookProps?: TProps } = {}
-) => {
- const { TestProvider, mockClient } = createTestProvider(options);
-
- const result = renderHook(hook, {
- wrapper: TestProvider,
- initialProps: options.hookProps as TProps,
- });
-
- return { ...result, mockClient };
-};
-
-// Render component with provider
-export const renderWithProvider = (
- ui: React.ReactElement,
- options: TestProviderOptions & Omit = {}
-): ReturnType & { mockClient: MockWebClientType } => {
- const { TestProvider, mockClient } = createTestProvider(options);
-
- const result = render(ui, {
- wrapper: TestProvider,
- ...options,
- });
-
- return { ...result, mockClient };
-};
-
-// Wait for async state updates
-export const waitForStateUpdate = () =>
- new Promise((resolve) => setTimeout(resolve, 0));
-
-// Helper to wait for loading to complete
-export const waitForLoading = async (
- getLoadingState: () => boolean,
- timeout: number = 5000
-): Promise => {
- const start = Date.now();
- while (getLoadingState() && Date.now() - start < timeout) {
- await waitForStateUpdate();
- }
-};
-
-// Helper to set up store with mock data
-export const setupStoreWithData = (options: {
- client?: MockWebClientType;
- accounts?: ReturnType<
- typeof import("../mocks/miden-sdk").createMockAccountHeader
- >[];
- notes?: ReturnType<
- typeof import("../mocks/miden-sdk").createMockInputNoteRecord
- >[];
- syncHeight?: number;
- isReady?: boolean;
-}) => {
- const store = useMidenStore.getState();
-
- if (options.client) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- store.setClient(options.client as any);
- }
-
- if (options.accounts) {
- store.setAccounts(options.accounts as unknown as typeof store.accounts);
- }
-
- if (options.notes) {
- store.setNotes(options.notes as unknown as typeof store.notes);
- }
-
- if (options.syncHeight !== undefined) {
- store.setSyncState({ syncHeight: options.syncHeight });
- }
-};
diff --git a/packages/react-sdk/src/context/SignerContext.ts b/packages/react-sdk/src/context/SignerContext.ts
index d9f467f..1a7bf64 100644
--- a/packages/react-sdk/src/context/SignerContext.ts
+++ b/packages/react-sdk/src/context/SignerContext.ts
@@ -27,7 +27,7 @@ export type SignCallback = (
* @param pubKey - Public key commitment bytes
* @returns Promise resolving to the secret key bytes
*/
-export type GetKeyCallback = (pubKey: Uint8Array) => Promise;
+type GetKeyCallback = (pubKey: Uint8Array) => Promise;
/**
* Insert-key callback for WebClient.createClientWithExternalKeystore.
@@ -36,10 +36,7 @@ export type GetKeyCallback = (pubKey: Uint8Array) => Promise;
* @param pubKey - Public key commitment bytes
* @param secretKey - Secret key bytes to store
*/
-export type InsertKeyCallback = (
- pubKey: Uint8Array,
- secretKey: Uint8Array
-) => void;
+type InsertKeyCallback = (pubKey: Uint8Array, secretKey: Uint8Array) => void;
/**
* Account type for signer accounts.
diff --git a/packages/react-sdk/src/store/MidenStore.ts b/packages/react-sdk/src/store/MidenStore.ts
index 1dcfdfc..6770919 100644
--- a/packages/react-sdk/src/store/MidenStore.ts
+++ b/packages/react-sdk/src/store/MidenStore.ts
@@ -242,14 +242,10 @@ export const useMidenStore = create()((set) => ({
}));
// Selector hooks for optimal re-renders
-export const useClient = () => useMidenStore((state) => state.client);
-export const useIsReady = () => useMidenStore((state) => state.isReady);
export const useSignerConnected = () =>
useMidenStore((state) => state.signerConnected);
export const useIsInitializing = () =>
useMidenStore((state) => state.isInitializing);
-export const useInitError = () => useMidenStore((state) => state.initError);
-export const useConfig = () => useMidenStore((state) => state.config);
export const useSyncStateStore = () => useMidenStore((state) => state.sync);
export const useAccountsStore = () => useMidenStore((state) => state.accounts);
export const useNotesStore = () => useMidenStore((state) => state.notes);
diff --git a/packages/react-sdk/src/types/index.ts b/packages/react-sdk/src/types/index.ts
index 630872e..47244d0 100644
--- a/packages/react-sdk/src/types/index.ts
+++ b/packages/react-sdk/src/types/index.ts
@@ -38,11 +38,8 @@ export type {
TransactionRecord,
TransactionRequest,
NoteType,
- NoteId,
Note,
AccountStorageMode,
- NoteVisibility,
- StorageMode,
};
export type { AccountRef } from "../utils/accountParsing";
@@ -50,8 +47,6 @@ export type { AccountRef } from "../utils/accountParsing";
// Re-export signer types for external signer providers
export type {
SignCallback,
- GetKeyCallback,
- InsertKeyCallback,
SignerAccountType,
SignerAccountConfig,
SignerContextValue,
diff --git a/packages/react-sdk/src/utils/transactions.ts b/packages/react-sdk/src/utils/transactions.ts
index 8c123ba..bdbaa02 100644
--- a/packages/react-sdk/src/utils/transactions.ts
+++ b/packages/react-sdk/src/utils/transactions.ts
@@ -1,7 +1,7 @@
import { NoteType, TransactionFilter } from "@miden-sdk/miden-sdk";
import type { Note, TransactionId } from "@miden-sdk/miden-sdk";
-export type ClientWithTransactions = {
+type ClientWithTransactions = {
syncState: () => Promise;
getTransactions: (filter: TransactionFilter) => Promise<
Array<{
diff --git a/packages/react-sdk/test/serve-tests.js b/packages/react-sdk/test/serve-tests.cjs
similarity index 100%
rename from packages/react-sdk/test/serve-tests.js
rename to packages/react-sdk/test/serve-tests.cjs
diff --git a/packages/vite-plugin/package.json b/packages/vite-plugin/package.json
index a5c63e4..9082471 100644
--- a/packages/vite-plugin/package.json
+++ b/packages/vite-plugin/package.json
@@ -2,14 +2,20 @@
"name": "@miden-sdk/vite-plugin",
"version": "0.14.5",
"description": "Vite plugin for Miden dApps — WASM dedup, COOP/COEP headers, and gRPC-web proxy",
+ "type": "commonjs",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"exports": {
".": {
- "types": "./dist/index.d.ts",
- "import": "./dist/index.mjs",
- "require": "./dist/index.js"
+ "import": {
+ "types": "./dist/index.d.mts",
+ "default": "./dist/index.mjs"
+ },
+ "require": {
+ "types": "./dist/index.d.ts",
+ "default": "./dist/index.js"
+ }
}
},
"files": [
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index d181818..22ca8b8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,18 +26,36 @@ importers:
specifier: ^3.8.1
version: 3.8.3
devDependencies:
- '@typescript-eslint/eslint-plugin':
- specifier: 8.39.1
- version: 8.39.1(@typescript-eslint/parser@8.39.1(eslint@9.33.0)(typescript@5.9.3))(eslint@9.33.0)(typescript@5.9.3)
+ '@arethetypeswrong/cli':
+ specifier: ^0.18.2
+ version: 0.18.2
'@typescript-eslint/parser':
specifier: 8.39.1
- version: 8.39.1(eslint@9.33.0)(typescript@5.9.3)
+ version: 8.39.1(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3)
eslint:
specifier: 9.33.0
- version: 9.33.0
+ version: 9.33.0(jiti@2.6.1)
+ eslint-config-prettier:
+ specifier: ^10.1.8
+ version: 10.1.8(eslint@9.33.0(jiti@2.6.1))
+ knip:
+ specifier: ^6.7.0
+ version: 6.7.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
+ lefthook:
+ specifier: ^1.13.6
+ version: 1.13.6
+ lint-staged:
+ specifier: ^16.4.0
+ version: 16.4.0
+ publint:
+ specifier: ^0.3.18
+ version: 0.3.18
typescript:
specifier: ^5.5.4
version: 5.9.3
+ vitest:
+ specifier: ^3.0.0
+ version: 3.2.4(@types/node@24.12.2)(jsdom@24.1.3)
crates/idxdb-store/src:
dependencies:
@@ -57,30 +75,24 @@ importers:
'@types/semver':
specifier: ^7.5.8
version: 7.7.1
- '@typescript-eslint/eslint-plugin':
- specifier: 8.39.1
- version: 8.39.1(@typescript-eslint/parser@8.39.1(eslint@9.33.0)(typescript@5.9.3))(eslint@9.33.0)(typescript@5.9.3)
'@vitest/coverage-v8':
specifier: ^3.0.0
version: 3.2.4(vitest@3.2.4(@types/node@24.12.2)(jsdom@24.1.3))
eslint:
specifier: 9.33.0
- version: 9.33.0
+ version: 9.33.0(jiti@2.6.1)
fake-indexeddb:
specifier: ^6.0.0
version: 6.2.5
typescript-eslint:
specifier: 8.39.1
- version: 8.39.1(eslint@9.33.0)(typescript@5.9.3)
+ version: 8.39.1(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3)
vitest:
specifier: ^3.0.0
version: 3.2.4(@types/node@24.12.2)(jsdom@24.1.3)
crates/web-client:
dependencies:
- '@rollup/plugin-typescript':
- specifier: ^12.3.0
- version: 12.3.0(rollup@4.60.2)(tslib@2.8.1)(typescript@5.9.3)
dexie:
specifier: ^4.0.1
version: 4.4.2
@@ -100,36 +112,18 @@ importers:
'@types/node':
specifier: ^24.9.2
version: 24.12.2
- '@vitest/coverage-v8':
- specifier: ^3.0.0
- version: 3.2.4(vitest@3.2.4(@types/node@24.12.2)(jsdom@24.1.3))
'@wasm-tool/rollup-plugin-rust':
specifier: ^3.0.3
version: 3.1.5(binaryen@129.0.0)(rollup@4.60.2)
binaryen:
specifier: ^129.0.0
version: 129.0.0
- chai:
- specifier: ^5.1.1
- version: 5.3.3
cpr:
specifier: ^3.0.1
version: 3.0.1
cross-env:
specifier: ^7.0.3
version: 7.0.3
- esm:
- specifier: ^3.2.25
- version: 3.2.25
- http-server:
- specifier: ^14.1.1
- version: 14.1.1
- mocha:
- specifier: ^10.7.3
- version: 10.8.2
- puppeteer:
- specifier: ^23.1.0
- version: 23.11.1(typescript@5.9.3)
rimraf:
specifier: ^6.0.1
version: 6.1.3
@@ -139,9 +133,6 @@ importers:
rollup-plugin-copy:
specifier: ^3.5.0
version: 3.5.0
- ts-node:
- specifier: ^10.9.2
- version: 10.9.2(@types/node@24.12.2)(typescript@5.9.3)
typedoc:
specifier: ^0.28.1
version: 0.28.19(typescript@5.9.3)
@@ -151,9 +142,6 @@ importers:
typescript:
specifier: ^5.5.4
version: 5.9.3
- vitest:
- specifier: ^3.0.0
- version: 3.2.4(@types/node@24.12.2)(jsdom@24.1.3)
packages/react-sdk:
dependencies:
@@ -175,19 +163,16 @@ importers:
version: 18.3.28
'@typescript-eslint/eslint-plugin':
specifier: 8.39.1
- version: 8.39.1(@typescript-eslint/parser@8.39.1(eslint@9.33.0)(typescript@5.9.3))(eslint@9.33.0)(typescript@5.9.3)
+ version: 8.39.1(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser':
specifier: 8.39.1
- version: 8.39.1(eslint@9.33.0)(typescript@5.9.3)
+ version: 8.39.1(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3)
'@vitest/coverage-v8':
specifier: ^3.0.0
version: 3.2.4(vitest@3.2.4(@types/node@24.12.2)(jsdom@24.1.3))
eslint:
specifier: 9.33.0
- version: 9.33.0
- http-server:
- specifier: ^14.1.1
- version: 14.1.1
+ version: 9.33.0(jiti@2.6.1)
jsdom:
specifier: ^24.0.0
version: 24.1.3
@@ -199,7 +184,7 @@ importers:
version: 18.3.1(react@18.3.1)
tsup:
specifier: ^8.0.0
- version: 8.5.1(postcss@8.5.12)(typescript@5.9.3)(yaml@2.8.3)
+ version: 8.5.1(jiti@2.6.1)(postcss@8.5.12)(typescript@5.9.3)(yaml@2.8.3)
typescript:
specifier: ^5.0.0
version: 5.9.3
@@ -217,7 +202,7 @@ importers:
version: 3.2.4(vitest@3.2.4(@types/node@20.19.39)(jsdom@24.1.3))
tsup:
specifier: ^8.0.0
- version: 8.5.1(postcss@8.5.12)(typescript@5.9.3)(yaml@2.8.3)
+ version: 8.5.1(jiti@2.6.1)(postcss@8.5.12)(typescript@5.9.3)(yaml@2.8.3)
typescript:
specifier: ^5.0.0
version: 5.9.3
@@ -234,6 +219,18 @@ packages:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
+ '@andrewbranch/untar.js@1.0.3':
+ resolution: {integrity: sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw==}
+
+ '@arethetypeswrong/cli@0.18.2':
+ resolution: {integrity: sha512-PcFM20JNlevEDKBg4Re29Rtv2xvjvQZzg7ENnrWFSS0PHgdP2njibVFw+dRUhNkPgNfac9iUqO0ohAXqQL4hbw==}
+ engines: {node: '>=20'}
+ hasBin: true
+
+ '@arethetypeswrong/core@0.18.2':
+ resolution: {integrity: sha512-GiwTmBFOU1/+UVNqqCGzFJYfBXEytUkiI+iRZ6Qx7KmUVtLm00sYySkfe203C9QtPG11yOz1ZaMek8dT/xnlgg==}
+ engines: {node: '>=20'}
+
'@asamuzakjp/css-color@3.2.0':
resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
@@ -266,9 +263,12 @@ packages:
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
engines: {node: '>=18'}
- '@cspotcode/source-map-support@0.8.1':
- resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
- engines: {node: '>=12'}
+ '@braidai/lang@1.1.2':
+ resolution: {integrity: sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==}
+
+ '@colors/colors@1.5.0':
+ resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
+ engines: {node: '>=0.1.90'}
'@csstools/color-helpers@5.1.0':
resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
@@ -298,6 +298,15 @@ packages:
resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
engines: {node: '>=18'}
+ '@emnapi/core@1.9.2':
+ resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==}
+
+ '@emnapi/runtime@1.9.2':
+ resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==}
+
+ '@emnapi/wasi-threads@1.2.1':
+ resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==}
+
'@esbuild/aix-ppc64@0.28.0':
resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==}
engines: {node: '>=18'}
@@ -551,8 +560,14 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
- '@jridgewell/trace-mapping@0.3.9':
- resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
+ '@loaderkit/resolve@1.0.5':
+ resolution: {integrity: sha512-fhkdGM57xhJ7CO91MUgbQlb0ClP0AJ9vB3yoVnBTslYJqrJOCVEbOprZcxZlexdMbmTBPQqVcQYr+j4oRRtIZA==}
+
+ '@napi-rs/wasm-runtime@1.1.4':
+ resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==}
+ peerDependencies:
+ '@emnapi/core': ^1.7.1
+ '@emnapi/runtime': ^1.7.1
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
@@ -566,6 +581,228 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
+ '@oxc-parser/binding-android-arm-eabi@0.127.0':
+ resolution: {integrity: sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [android]
+
+ '@oxc-parser/binding-android-arm64@0.127.0':
+ resolution: {integrity: sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [android]
+
+ '@oxc-parser/binding-darwin-arm64@0.127.0':
+ resolution: {integrity: sha512-obCE8B7ISKkJidjlhv9xRGJPOSDG2Yu6PRga9Ruaz35uintHxbp1Ki/Yc71wx4rj3Edrm0a1kzG1TAwit0wFpg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@oxc-parser/binding-darwin-x64@0.127.0':
+ resolution: {integrity: sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@oxc-parser/binding-freebsd-x64@0.127.0':
+ resolution: {integrity: sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0':
+ resolution: {integrity: sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-arm-musleabihf@0.127.0':
+ resolution: {integrity: sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-arm64-gnu@0.127.0':
+ resolution: {integrity: sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-arm64-musl@0.127.0':
+ resolution: {integrity: sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-ppc64-gnu@0.127.0':
+ resolution: {integrity: sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-riscv64-gnu@0.127.0':
+ resolution: {integrity: sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-riscv64-musl@0.127.0':
+ resolution: {integrity: sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-s390x-gnu@0.127.0':
+ resolution: {integrity: sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [s390x]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-x64-gnu@0.127.0':
+ resolution: {integrity: sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-x64-musl@0.127.0':
+ resolution: {integrity: sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@oxc-parser/binding-openharmony-arm64@0.127.0':
+ resolution: {integrity: sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@oxc-parser/binding-wasm32-wasi@0.127.0':
+ resolution: {integrity: sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [wasm32]
+
+ '@oxc-parser/binding-win32-arm64-msvc@0.127.0':
+ resolution: {integrity: sha512-Ujvw4X+LD1CCGULcsQcvb4YNVoBGqt+JHgNNzGGaCImELiZLk477ifUH53gIbE7EKd933NdTi25JWEr9K2HwXw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@oxc-parser/binding-win32-ia32-msvc@0.127.0':
+ resolution: {integrity: sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@oxc-parser/binding-win32-x64-msvc@0.127.0':
+ resolution: {integrity: sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@oxc-project/types@0.127.0':
+ resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==}
+
+ '@oxc-resolver/binding-android-arm-eabi@11.19.1':
+ resolution: {integrity: sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==}
+ cpu: [arm]
+ os: [android]
+
+ '@oxc-resolver/binding-android-arm64@11.19.1':
+ resolution: {integrity: sha512-oolbkRX+m7Pq2LNjr/kKgYeC7bRDMVTWPgxBGMjSpZi/+UskVo4jsMU3MLheZV55jL6c3rNelPl4oD60ggYmqA==}
+ cpu: [arm64]
+ os: [android]
+
+ '@oxc-resolver/binding-darwin-arm64@11.19.1':
+ resolution: {integrity: sha512-nUC6d2i3R5B12sUW4O646qD5cnMXf2oBGPLIIeaRfU9doJRORAbE2SGv4eW6rMqhD+G7nf2Y8TTJTLiiO3Q/dQ==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@oxc-resolver/binding-darwin-x64@11.19.1':
+ resolution: {integrity: sha512-cV50vE5+uAgNcFa3QY1JOeKDSkM/9ReIcc/9wn4TavhW/itkDGrXhw9jaKnkQnGbjJ198Yh5nbX/Gr2mr4Z5jQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@oxc-resolver/binding-freebsd-x64@11.19.1':
+ resolution: {integrity: sha512-xZOQiYGFxtk48PBKff+Zwoym7ScPAIVp4c14lfLxizO2LTTTJe5sx9vQNGrBymrf/vatSPNMD4FgsaaRigPkqw==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@oxc-resolver/binding-linux-arm-gnueabihf@11.19.1':
+ resolution: {integrity: sha512-lXZYWAC6kaGe/ky2su94e9jN9t6M0/6c+GrSlCqL//XO1cxi5lpAhnJYdyrKfm0ZEr/c7RNyAx3P7FSBcBd5+A==}
+ cpu: [arm]
+ os: [linux]
+
+ '@oxc-resolver/binding-linux-arm-musleabihf@11.19.1':
+ resolution: {integrity: sha512-veG1kKsuK5+t2IsO9q0DErYVSw2azvCVvWHnfTOS73WE0STdLLB7Q1bB9WR+yHPQM76ASkFyRbogWo1GR1+WbQ==}
+ cpu: [arm]
+ os: [linux]
+
+ '@oxc-resolver/binding-linux-arm64-gnu@11.19.1':
+ resolution: {integrity: sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@oxc-resolver/binding-linux-arm64-musl@11.19.1':
+ resolution: {integrity: sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@oxc-resolver/binding-linux-ppc64-gnu@11.19.1':
+ resolution: {integrity: sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@oxc-resolver/binding-linux-riscv64-gnu@11.19.1':
+ resolution: {integrity: sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@oxc-resolver/binding-linux-riscv64-musl@11.19.1':
+ resolution: {integrity: sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@oxc-resolver/binding-linux-s390x-gnu@11.19.1':
+ resolution: {integrity: sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@oxc-resolver/binding-linux-x64-gnu@11.19.1':
+ resolution: {integrity: sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ==}
+ cpu: [x64]
+ os: [linux]
+
+ '@oxc-resolver/binding-linux-x64-musl@11.19.1':
+ resolution: {integrity: sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@oxc-resolver/binding-openharmony-arm64@11.19.1':
+ resolution: {integrity: sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@oxc-resolver/binding-wasm32-wasi@11.19.1':
+ resolution: {integrity: sha512-w8UCKhX826cP/ZLokXDS6+milN8y4X7zidsAttEdWlVoamTNf6lhBJldaWr3ukTDiye7s4HRcuPEPOXNC432Vg==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+
+ '@oxc-resolver/binding-win32-arm64-msvc@11.19.1':
+ resolution: {integrity: sha512-nJ4AsUVZrVKwnU/QRdzPCCrO0TrabBqgJ8pJhXITdZGYOV28TIYystV1VFLbQ7DtAcaBHpocT5/ZJnF78YJPtQ==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@oxc-resolver/binding-win32-ia32-msvc@11.19.1':
+ resolution: {integrity: sha512-EW+ND5q2Tl+a3pH81l1QbfgbF3HmqgwLfDfVithRFheac8OTcnbXt/JxqD2GbDkb7xYEqy1zNaVFRr3oeG8npA==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@oxc-resolver/binding-win32-x64-msvc@11.19.1':
+ resolution: {integrity: sha512-6hIU3RQu45B+VNTY4Ru8ppFwjVS/S5qwYyGhBotmjxfEKk41I2DlGtRfGJndZ5+6lneE2pwloqunlOyZuX/XAw==}
+ cpu: [x64]
+ os: [win32]
+
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@@ -575,10 +812,9 @@ packages:
engines: {node: '>=18'}
hasBin: true
- '@puppeteer/browsers@2.6.1':
- resolution: {integrity: sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==}
+ '@publint/pack@0.1.4':
+ resolution: {integrity: sha512-HDVTWq3H0uTXiU0eeSQntcVUTPP3GamzeXI41+x7uU9J65JgWQh3qWZHblR1i0npXfFtF+mxBiU2nJH8znxWnQ==}
engines: {node: '>=18'}
- hasBin: true
'@rollup/plugin-commonjs@25.0.8':
resolution: {integrity: sha512-ZEZWTK5n6Qde0to4vS9Mr5x/0UZoqCxPVR9KRUjU4kA2sO7GEUn1fop0DAwpO6z0Nw/kJON9bDmSxdWxO/TT1A==}
@@ -598,19 +834,6 @@ packages:
rollup:
optional: true
- '@rollup/plugin-typescript@12.3.0':
- resolution: {integrity: sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big==}
- engines: {node: '>=14.0.0'}
- peerDependencies:
- rollup: '>=4.59.0'
- tslib: '*'
- typescript: '>=3.7.0'
- peerDependenciesMeta:
- rollup:
- optional: true
- tslib:
- optional: true
-
'@rollup/pluginutils@5.3.0':
resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
engines: {node: '>=14.0.0'}
@@ -760,6 +983,10 @@ packages:
'@shikijs/vscode-textmate@10.0.2':
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
+ '@sindresorhus/is@4.6.0':
+ resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
+ engines: {node: '>=10'}
+
'@testing-library/dom@9.3.4':
resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==}
engines: {node: '>=14'}
@@ -771,20 +998,8 @@ packages:
react: ^18.0.0
react-dom: ^18.0.0
- '@tootallnate/quickjs-emscripten@0.23.0':
- resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
-
- '@tsconfig/node10@1.0.12':
- resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==}
-
- '@tsconfig/node12@1.0.11':
- resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
-
- '@tsconfig/node14@1.0.3':
- resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
-
- '@tsconfig/node16@1.0.4':
- resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
+ '@tybys/wasm-util@0.10.1':
+ resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
'@types/aria-query@5.0.4':
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
@@ -840,9 +1055,6 @@ packages:
'@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
- '@types/yauzl@2.10.3':
- resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
-
'@typescript-eslint/eslint-plugin@8.39.1':
resolution: {integrity: sha512-yYegZ5n3Yr6eOcqgj2nJH8cH/ZZgF+l0YIdKILSDjYFRjgYQMgv/lRjV5Z7Up04b9VYUondt8EPMqg7kTWgJ2g==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -960,10 +1172,6 @@ packages:
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
- acorn-walk@8.3.5:
- resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==}
- engines: {node: '>=0.4.0'}
-
acorn@8.16.0:
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
engines: {node: '>=0.4.0'}
@@ -976,9 +1184,9 @@ packages:
ajv@6.15.0:
resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==}
- ansi-colors@4.1.3:
- resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
- engines: {node: '>=6'}
+ ansi-escapes@7.3.0:
+ resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==}
+ engines: {node: '>=18'}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
@@ -1003,13 +1211,6 @@ packages:
any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
- anymatch@3.1.3:
- resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
- engines: {node: '>= 8'}
-
- arg@4.1.3:
- resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
-
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
@@ -1028,16 +1229,9 @@ packages:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
- ast-types@0.13.4:
- resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==}
- engines: {node: '>=4'}
-
ast-v8-to-istanbul@0.3.12:
resolution: {integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==}
- async@3.2.6:
- resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
-
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
@@ -1045,14 +1239,6 @@ packages:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
- b4a@1.8.0:
- resolution: {integrity: sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==}
- peerDependencies:
- react-native-b4a: '*'
- peerDependenciesMeta:
- react-native-b4a:
- optional: true
-
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -1060,62 +1246,6 @@ packages:
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
engines: {node: 18 || 20 || >=22}
- bare-events@2.8.2:
- resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==}
- peerDependencies:
- bare-abort-controller: '*'
- peerDependenciesMeta:
- bare-abort-controller:
- optional: true
-
- bare-fs@4.7.1:
- resolution: {integrity: sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==}
- engines: {bare: '>=1.16.0'}
- peerDependencies:
- bare-buffer: '*'
- peerDependenciesMeta:
- bare-buffer:
- optional: true
-
- bare-os@3.9.0:
- resolution: {integrity: sha512-JTjuZyNIDpw+GytMO4a6TK1VXdVKKJr6DRxEHasyuYyShV2deuiHJK/ahGZlebc+SG0/wJCB9XK8gprBGDFi/Q==}
- engines: {bare: '>=1.14.0'}
-
- bare-path@3.0.0:
- resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==}
-
- bare-stream@2.13.0:
- resolution: {integrity: sha512-3zAJRZMDFGjdn+RVnNpF9kuELw+0Fl3lpndM4NcEOhb9zwtSo/deETfuIwMSE5BXanA0FrN1qVjffGwAg2Y7EA==}
- peerDependencies:
- bare-abort-controller: '*'
- bare-buffer: '*'
- bare-events: '*'
- peerDependenciesMeta:
- bare-abort-controller:
- optional: true
- bare-buffer:
- optional: true
- bare-events:
- optional: true
-
- bare-url@2.4.2:
- resolution: {integrity: sha512-/9a2j4ac6ckpmAHvod/ob7x439OAHst/drc2Clnq+reRYd/ovddwcF4LfoxHyNk5AuGBnPg+HqFjmE/Zpq6v0A==}
-
- base64-js@1.5.1:
- resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
-
- basic-auth@2.0.1:
- resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==}
- engines: {node: '>= 0.8'}
-
- basic-ftp@5.3.0:
- resolution: {integrity: sha512-5K9eNNn7ywHPsYnFwjKgYH8Hf8B5emh7JKcPaVjjrMJFQQwGpwowEnZNEtHs7DfR7hCZsmaK3VA4HUK0YarT+w==}
- engines: {node: '>=10.0.0'}
-
- binary-extensions@2.3.0:
- resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
- engines: {node: '>=8'}
-
binaryen@129.0.0:
resolution: {integrity: sha512-NyF5J0SfRoLDthpPh36FGTycOEv3Eqnkq3+mP5Cqt6iD9BLGGJMEVuPzu81nhLy2MMpPKmRTM9VLZihfyRQv8A==}
hasBin: true
@@ -1131,15 +1261,6 @@ packages:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
- browser-stdout@1.3.1:
- resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==}
-
- buffer-crc32@0.2.13:
- resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
-
- buffer@5.7.1:
- resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
-
bundle-require@5.1.0:
resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -1166,10 +1287,6 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
- camelcase@6.3.0:
- resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
- engines: {node: '>=10'}
-
chai@5.3.3:
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
engines: {node: '>=18'}
@@ -1182,14 +1299,14 @@ packages:
resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
+ char-regex@1.0.2:
+ resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
+ engines: {node: '>=10'}
+
check-error@2.1.3:
resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
engines: {node: '>= 16'}
- chokidar@3.6.0:
- resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
- engines: {node: '>= 8.10.0'}
-
chokidar@4.0.3:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
@@ -1198,18 +1315,29 @@ packages:
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
engines: {node: '>=18'}
- chromium-bidi@0.11.0:
- resolution: {integrity: sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA==}
- peerDependencies:
- devtools-protocol: '*'
+ cjs-module-lexer@1.4.3:
+ resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==}
+
+ cli-cursor@5.0.0:
+ resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
+ engines: {node: '>=18'}
+
+ cli-highlight@2.1.11:
+ resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==}
+ engines: {node: '>=8.0.0', npm: '>=5.0.0'}
+ hasBin: true
+
+ cli-table3@0.6.5:
+ resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==}
+ engines: {node: 10.* || >= 12.*}
+
+ cli-truncate@5.2.0:
+ resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==}
+ engines: {node: '>=20'}
cliui@7.0.4:
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
- cliui@8.0.1:
- resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
- engines: {node: '>=12'}
-
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -1220,10 +1348,21 @@ packages:
colorette@1.4.0:
resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==}
+ colorette@2.0.20:
+ resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
+
combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
+ commander@10.0.1:
+ resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
+ engines: {node: '>=14'}
+
+ commander@14.0.3:
+ resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
+ engines: {node: '>=20'}
+
commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
@@ -1238,26 +1377,10 @@ packages:
resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
engines: {node: ^14.18.0 || >=16.10.0}
- corser@2.0.1:
- resolution: {integrity: sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==}
- engines: {node: '>= 0.4.0'}
-
- cosmiconfig@9.0.1:
- resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==}
- engines: {node: '>=14'}
- peerDependencies:
- typescript: '>=4.9.5'
- peerDependenciesMeta:
- typescript:
- optional: true
-
cpr@3.0.1:
resolution: {integrity: sha512-Xch4PXQ/KC8lJ+KfJ9JI6eG/nmppLrPPWg5Q+vh65Qr9EjuJEubxh/H/Le1TmCZ7+Xv7iJuNRqapyOFZB+wsxA==}
hasBin: true
- create-require@1.1.1:
- resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
-
cross-env@7.0.3:
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
@@ -1278,10 +1401,6 @@ packages:
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
engines: {node: '>= 12'}
- data-uri-to-buffer@6.0.2:
- resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==}
- engines: {node: '>= 14'}
-
data-urls@5.0.0:
resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
engines: {node: '>=18'}
@@ -1295,10 +1414,6 @@ packages:
supports-color:
optional: true
- decamelize@4.0.0:
- resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==}
- engines: {node: '>=10'}
-
decimal.js@10.6.0:
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
@@ -1325,28 +1440,13 @@ packages:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
- degenerator@5.0.1:
- resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
- engines: {node: '>= 14'}
-
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
- devtools-protocol@0.0.1367902:
- resolution: {integrity: sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==}
-
dexie@4.4.2:
resolution: {integrity: sha512-zMtV8q79EFE5U8FKZvt0Y/77PCU/Hr/RDxv1EDeo228L+m/HTbeN2AjoQm674rhQCX8n3ljK87lajt7UQuZfvw==}
- diff@4.0.4:
- resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==}
- engines: {node: '>=0.3.1'}
-
- diff@5.2.2:
- resolution: {integrity: sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==}
- engines: {node: '>=0.3.1'}
-
dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
@@ -1361,14 +1461,17 @@ packages:
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+ emoji-regex@10.6.0:
+ resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
+
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
- end-of-stream@1.4.5:
- resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
+ emojilib@2.4.0:
+ resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==}
entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
@@ -1378,12 +1481,9 @@ packages:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'}
- env-paths@2.2.1:
- resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
- engines: {node: '>=6'}
-
- error-ex@1.3.4:
- resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
+ environment@1.1.0:
+ resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
+ engines: {node: '>=18'}
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
@@ -1420,10 +1520,11 @@ packages:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
- escodegen@2.1.0:
- resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==}
- engines: {node: '>=6.0'}
+ eslint-config-prettier@10.1.8:
+ resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==}
hasBin: true
+ peerDependencies:
+ eslint: 9.33.0
eslint-scope@8.4.0:
resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
@@ -1447,19 +1548,10 @@ packages:
jiti:
optional: true
- esm@3.2.25:
- resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==}
- engines: {node: '>=6'}
-
espree@10.4.0:
resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- esprima@4.0.1:
- resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
- engines: {node: '>=4'}
- hasBin: true
-
esquery@1.7.0:
resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==}
engines: {node: '>=0.10'}
@@ -1482,21 +1574,13 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
- eventemitter3@4.0.7:
- resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
-
- events-universal@1.0.1:
- resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==}
+ eventemitter3@5.0.4:
+ resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
expect-type@1.3.0:
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
engines: {node: '>=12.0.0'}
- extract-zip@2.0.1:
- resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==}
- engines: {node: '>= 10.17.0'}
- hasBin: true
-
fake-indexeddb@6.2.5:
resolution: {integrity: sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==}
engines: {node: '>=18'}
@@ -1504,9 +1588,6 @@ packages:
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
- fast-fifo@1.3.2:
- resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
-
fast-glob@3.3.3:
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
engines: {node: '>=8.6.0'}
@@ -1520,8 +1601,8 @@ packages:
fastq@1.20.1:
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
- fd-slicer@1.1.0:
- resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
+ fd-package-json@2.0.0:
+ resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==}
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
@@ -1536,6 +1617,9 @@ packages:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
engines: {node: ^12.20 || >= 14.13}
+ fflate@0.8.2:
+ resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
+
file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
@@ -1555,22 +1639,9 @@ packages:
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
engines: {node: '>=16'}
- flat@5.0.2:
- resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==}
- hasBin: true
-
flatted@3.4.2:
resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
- follow-redirects@1.16.0:
- resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
- engines: {node: '>=4.0'}
- peerDependencies:
- debug: '*'
- peerDependenciesMeta:
- debug:
- optional: true
-
for-each@0.3.5:
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
engines: {node: '>= 0.4'}
@@ -1583,6 +1654,11 @@ packages:
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
engines: {node: '>= 6'}
+ formatly@0.3.0:
+ resolution: {integrity: sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==}
+ engines: {node: '>=18.3.0'}
+ hasBin: true
+
formdata-polyfill@4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
@@ -1614,6 +1690,10 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
+ get-east-asian-width@1.5.0:
+ resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==}
+ engines: {node: '>=18'}
+
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
@@ -1622,13 +1702,8 @@ packages:
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
engines: {node: '>= 0.4'}
- get-stream@5.2.0:
- resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
- engines: {node: '>=8'}
-
- get-uri@6.0.5:
- resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==}
- engines: {node: '>= 14'}
+ get-tsconfig@4.14.0:
+ resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==}
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
@@ -1703,13 +1778,8 @@ packages:
resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==}
engines: {node: '>= 0.4'}
- he@1.2.0:
- resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
- hasBin: true
-
- html-encoding-sniffer@3.0.0:
- resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==}
- engines: {node: '>=12'}
+ highlight.js@10.7.3:
+ resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
html-encoding-sniffer@4.0.0:
resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
@@ -1722,15 +1792,6 @@ packages:
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'}
- http-proxy@1.18.1:
- resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==}
- engines: {node: '>=8.0.0'}
-
- http-server@14.1.1:
- resolution: {integrity: sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==}
- engines: {node: '>=12'}
- hasBin: true
-
https-proxy-agent@7.0.6:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'}
@@ -1739,9 +1800,6 @@ packages:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
- ieee754@1.2.1:
- resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
-
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@@ -1769,10 +1827,6 @@ packages:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
- ip-address@10.1.1:
- resolution: {integrity: sha512-1FMu8/N15Ck1BL551Jf42NYIoin2unWjLQ2Fze/DXryJRl5twqtwNHlO39qERGbIOcKYWHdgRryhOC+NG4eaLw==}
- engines: {node: '>= 12'}
-
is-arguments@1.2.0:
resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==}
engines: {node: '>= 0.4'}
@@ -1781,17 +1835,10 @@ packages:
resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
engines: {node: '>= 0.4'}
- is-arrayish@0.2.1:
- resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
-
is-bigint@1.1.0:
resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
engines: {node: '>= 0.4'}
- is-binary-path@2.1.0:
- resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
- engines: {node: '>=8'}
-
is-boolean-object@1.2.2:
resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
engines: {node: '>= 0.4'}
@@ -1816,6 +1863,10 @@ packages:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
+ is-fullwidth-code-point@5.1.0:
+ resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==}
+ engines: {node: '>=18'}
+
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
@@ -1835,10 +1886,6 @@ packages:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
- is-plain-obj@2.1.0:
- resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
- engines: {node: '>=8'}
-
is-plain-object@3.0.1:
resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==}
engines: {node: '>=0.10.0'}
@@ -1869,10 +1916,6 @@ packages:
resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
engines: {node: '>= 0.4'}
- is-unicode-supported@0.1.0:
- resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
- engines: {node: '>=10'}
-
is-weakmap@2.0.2:
resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
engines: {node: '>= 0.4'}
@@ -1910,6 +1953,10 @@ packages:
resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==}
engines: {node: 20 || >=22}
+ jiti@2.6.1:
+ resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
+ hasBin: true
+
joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
@@ -1939,9 +1986,6 @@ packages:
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
- json-parse-even-better-errors@2.3.1:
- resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
-
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
@@ -1954,6 +1998,65 @@ packages:
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+ knip@6.7.0:
+ resolution: {integrity: sha512-ckL51NDH1YJxnv1kNB0iUdDngB4f/e9Igz8uIqYfmNDoyOFmmk1V0WFv3LQ7/hzC63b2Z9X41gGUE9eOWrZpaA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+
+ lefthook-darwin-arm64@1.13.6:
+ resolution: {integrity: sha512-m6Lb77VGc84/Qo21Lhq576pEvcgFCnvloEiP02HbAHcIXD0RTLy9u2yAInrixqZeaz13HYtdDaI7OBYAAdVt8A==}
+ cpu: [arm64]
+ os: [darwin]
+
+ lefthook-darwin-x64@1.13.6:
+ resolution: {integrity: sha512-CoRpdzanu9RK3oXR1vbEJA5LN7iB+c7hP+sONeQJzoOXuq4PNKVtEaN84Gl1BrVtCNLHWFAvCQaZPPiiXSy8qg==}
+ cpu: [x64]
+ os: [darwin]
+
+ lefthook-freebsd-arm64@1.13.6:
+ resolution: {integrity: sha512-X4A7yfvAJ68CoHTqP+XvQzdKbyd935sYy0bQT6Ajz7FL1g7hFiro8dqHSdPdkwei9hs8hXeV7feyTXbYmfjKQQ==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ lefthook-freebsd-x64@1.13.6:
+ resolution: {integrity: sha512-ai2m+Sj2kGdY46USfBrCqLKe9GYhzeq01nuyDYCrdGISePeZ6udOlD1k3lQKJGQCHb0bRz4St0r5nKDSh1x/2A==}
+ cpu: [x64]
+ os: [freebsd]
+
+ lefthook-linux-arm64@1.13.6:
+ resolution: {integrity: sha512-cbo4Wtdq81GTABvikLORJsAWPKAJXE8Q5RXsICFUVznh5PHigS9dFW/4NXywo0+jfFPCT6SYds2zz4tCx6DA0Q==}
+ cpu: [arm64]
+ os: [linux]
+
+ lefthook-linux-x64@1.13.6:
+ resolution: {integrity: sha512-uJl9vjCIIBTBvMZkemxCE+3zrZHlRO7Oc+nZJ+o9Oea3fu+W82jwX7a7clw8jqNfaeBS+8+ZEQgiMHWCloTsGw==}
+ cpu: [x64]
+ os: [linux]
+
+ lefthook-openbsd-arm64@1.13.6:
+ resolution: {integrity: sha512-7r153dxrNRQ9ytRs2PmGKKkYdvZYFPre7My7XToSTiRu5jNCq++++eAKVkoyWPduk97dGIA+YWiEr5Noe0TK2A==}
+ cpu: [arm64]
+ os: [openbsd]
+
+ lefthook-openbsd-x64@1.13.6:
+ resolution: {integrity: sha512-Z+UhLlcg1xrXOidK3aLLpgH7KrwNyWYE3yb7ITYnzJSEV8qXnePtVu8lvMBHs/myzemjBzeIr/U/+ipjclR06g==}
+ cpu: [x64]
+ os: [openbsd]
+
+ lefthook-windows-arm64@1.13.6:
+ resolution: {integrity: sha512-Uxef6qoDxCmUNQwk8eBvddYJKSBFglfwAY9Y9+NnnmiHpWTjjYiObE9gT2mvGVpEgZRJVAatBXc+Ha5oDD/OgQ==}
+ cpu: [arm64]
+ os: [win32]
+
+ lefthook-windows-x64@1.13.6:
+ resolution: {integrity: sha512-mOZoM3FQh3o08M8PQ/b3IYuL5oo36D9ehczIw1dAgp1Ly+Tr4fJ96A+4SEJrQuYeRD4mex9bR7Ps56I73sBSZA==}
+ cpu: [x64]
+ os: [win32]
+
+ lefthook@1.13.6:
+ resolution: {integrity: sha512-ojj4/4IJ29Xn4drd5emqVgilegAPN3Kf0FQM2p/9+lwSTpU+SZ1v4Ig++NF+9MOa99UKY8bElmVrLhnUUNFh5g==}
+ hasBin: true
+
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
@@ -1968,6 +2071,15 @@ packages:
linkify-it@5.0.0:
resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
+ lint-staged@16.4.0:
+ resolution: {integrity: sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==}
+ engines: {node: '>=20.17'}
+ hasBin: true
+
+ listr2@9.0.5:
+ resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==}
+ engines: {node: '>=20.0.0'}
+
load-tsconfig@0.2.5:
resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -1979,9 +2091,9 @@ packages:
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
- log-symbols@4.1.0:
- resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
- engines: {node: '>=10'}
+ log-update@6.1.0:
+ resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
+ engines: {node: '>=18'}
loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
@@ -1997,10 +2109,6 @@ packages:
resolution: {integrity: sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==}
engines: {node: 20 || >=22}
- lru-cache@7.18.3:
- resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
- engines: {node: '>=12'}
-
lunr@2.3.9:
resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==}
@@ -2018,13 +2126,21 @@ packages:
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
engines: {node: '>=10'}
- make-error@1.3.6:
- resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
-
markdown-it@14.1.1:
resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==}
hasBin: true
+ marked-terminal@7.3.0:
+ resolution: {integrity: sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==}
+ engines: {node: '>=16.0.0'}
+ peerDependencies:
+ marked: '>=1 <16'
+
+ marked@9.1.6:
+ resolution: {integrity: sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==}
+ engines: {node: '>= 16'}
+ hasBin: true
+
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
@@ -2048,10 +2164,9 @@ packages:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
- mime@1.6.0:
- resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
- engines: {node: '>=4'}
- hasBin: true
+ mimic-function@5.0.1:
+ resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
+ engines: {node: '>=18'}
minimatch@10.2.5:
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
@@ -2072,9 +2187,6 @@ packages:
resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
engines: {node: '>= 18'}
- mitt@3.0.1:
- resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
-
mkdirp@0.5.6:
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
hasBin: true
@@ -2082,10 +2194,9 @@ packages:
mlly@1.8.2:
resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
- mocha@10.8.2:
- resolution: {integrity: sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==}
- engines: {node: '>= 14.0.0'}
- hasBin: true
+ mri@1.2.0:
+ resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
+ engines: {node: '>=4'}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -2101,23 +2212,19 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
- netmask@2.1.1:
- resolution: {integrity: sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==}
- engines: {node: '>= 0.4.0'}
-
node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
deprecated: Use your platform's native DOMException instead
+ node-emoji@2.2.0:
+ resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==}
+ engines: {node: '>=18'}
+
node-fetch@3.3.2:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
- normalize-path@3.0.0:
- resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
- engines: {node: '>=0.10.0'}
-
nwsapi@2.2.23:
resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==}
@@ -2144,14 +2251,21 @@ packages:
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
- opener@1.5.2:
- resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==}
- hasBin: true
+ onetime@7.0.0:
+ resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
+ engines: {node: '>=18'}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
+ oxc-parser@0.127.0:
+ resolution: {integrity: sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+
+ oxc-resolver@11.19.1:
+ resolution: {integrity: sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg==}
+
p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
engines: {node: '>=10'}
@@ -2160,24 +2274,24 @@ packages:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
- pac-proxy-agent@7.2.0:
- resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==}
- engines: {node: '>= 14'}
-
- pac-resolver@7.0.1:
- resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==}
- engines: {node: '>= 14'}
-
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
+ package-manager-detector@1.6.0:
+ resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==}
+
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
- parse-json@5.2.0:
- resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
- engines: {node: '>=8'}
+ parse5-htmlparser2-tree-adapter@6.0.1:
+ resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==}
+
+ parse5@5.1.1:
+ resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==}
+
+ parse5@6.0.1:
+ resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==}
parse5@7.3.0:
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
@@ -2216,9 +2330,6 @@ packages:
resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
engines: {node: '>= 14.16'}
- pend@1.2.0:
- resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
-
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -2247,10 +2358,6 @@ packages:
engines: {node: '>=18'}
hasBin: true
- portfinder@1.0.38:
- resolution: {integrity: sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==}
- engines: {node: '>= 10.12'}
-
possible-typed-array-names@1.1.0:
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
engines: {node: '>= 0.4'}
@@ -2290,22 +2397,13 @@ packages:
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
- progress@2.0.3:
- resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
- engines: {node: '>=0.4.0'}
-
- proxy-agent@6.5.0:
- resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==}
- engines: {node: '>= 14'}
-
- proxy-from-env@1.1.0:
- resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
-
psl@1.15.0:
resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==}
- pump@3.0.4:
- resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
+ publint@0.3.18:
+ resolution: {integrity: sha512-JRJFeBTrfx4qLwEuGFPk+haJOJN97KnPuK01yj+4k/Wj5BgoOK5uNsivporiqBjk2JDaslg7qJOhGRnpltGeog==}
+ engines: {node: '>=18'}
+ hasBin: true
punycode.js@2.3.1:
resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
@@ -2315,29 +2413,12 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
- puppeteer-core@23.11.1:
- resolution: {integrity: sha512-3HZ2/7hdDKZvZQ7dhhITOUg4/wOrDRjyK2ZBllRB0ZCOi9u0cwq1ACHDjBB+nX+7+kltHjQvBRdeY7+W0T+7Gg==}
- engines: {node: '>=18'}
-
- puppeteer@23.11.1:
- resolution: {integrity: sha512-53uIX3KR5en8l7Vd8n5DUv90Ae9QDQsyIthaUFVzwV6yU750RjqRznEtNMBT20VthqAdemnJN+hxVdmMHKt7Zw==}
- engines: {node: '>=18'}
- deprecated: < 24.15.0 is no longer supported
- hasBin: true
-
- qs@6.15.1:
- resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==}
- engines: {node: '>=0.6'}
-
querystringify@2.2.0:
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
- randombytes@2.1.0:
- resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
-
react-dom@18.3.1:
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
peerDependencies:
@@ -2350,10 +2431,6 @@ packages:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
- readdirp@3.6.0:
- resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
- engines: {node: '>=8.10.0'}
-
readdirp@4.1.2:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
@@ -2377,15 +2454,25 @@ packages:
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
engines: {node: '>=8'}
+ resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+
resolve@1.22.12:
resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==}
engines: {node: '>= 0.4'}
hasBin: true
+ restore-cursor@5.1.0:
+ resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
+ engines: {node: '>=18'}
+
reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+ rfdc@1.4.1:
+ resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
+
rimraf@2.7.1:
resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
deprecated: Rimraf versions prior to v4 are no longer supported
@@ -2414,11 +2501,9 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
- safe-buffer@5.1.2:
- resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
-
- safe-buffer@5.2.1:
- resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+ sade@1.8.1:
+ resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
+ engines: {node: '>=6'}
safe-regex-test@1.1.0:
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
@@ -2434,17 +2519,11 @@ packages:
scheduler@0.23.2:
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
- secure-compare@3.0.1:
- resolution: {integrity: sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==}
-
semver@7.7.4:
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
engines: {node: '>=10'}
hasBin: true
- serialize-javascript@6.0.2:
- resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
-
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
@@ -2484,30 +2563,30 @@ packages:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
+ skin-tone@2.0.0:
+ resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==}
+ engines: {node: '>=8'}
+
slash@3.0.0:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'}
- smart-buffer@4.2.0:
- resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
- engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
+ slice-ansi@7.1.2:
+ resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==}
+ engines: {node: '>=18'}
- socks-proxy-agent@8.0.5:
- resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==}
- engines: {node: '>= 14'}
+ slice-ansi@8.0.0:
+ resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==}
+ engines: {node: '>=20'}
- socks@2.8.7:
- resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==}
- engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
+ smol-toml@1.6.1:
+ resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==}
+ engines: {node: '>= 18'}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
- source-map@0.6.1:
- resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
- engines: {node: '>=0.10.0'}
-
source-map@0.7.6:
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
engines: {node: '>= 12'}
@@ -2522,8 +2601,9 @@ packages:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
- streamx@2.25.0:
- resolution: {integrity: sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==}
+ string-argv@0.3.2:
+ resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
+ engines: {node: '>=0.6.19'}
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
@@ -2533,6 +2613,14 @@ packages:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
+ string-width@7.2.0:
+ resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
+ engines: {node: '>=18'}
+
+ string-width@8.2.1:
+ resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==}
+ engines: {node: '>=20'}
+
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
@@ -2545,6 +2633,10 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
+ strip-json-comments@5.0.3:
+ resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==}
+ engines: {node: '>=14.16'}
+
strip-literal@3.1.0:
resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
@@ -2557,9 +2649,9 @@ packages:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
- supports-color@8.1.1:
- resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
- engines: {node: '>=10'}
+ supports-hyperlinks@3.2.0:
+ resolution: {integrity: sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==}
+ engines: {node: '>=14.18'}
supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
@@ -2568,26 +2660,14 @@ packages:
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
- tar-fs@3.1.2:
- resolution: {integrity: sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==}
-
- tar-stream@3.1.8:
- resolution: {integrity: sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==}
-
tar@7.5.13:
resolution: {integrity: sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==}
engines: {node: '>=18'}
- teex@1.0.1:
- resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==}
-
test-exclude@7.0.2:
resolution: {integrity: sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==}
engines: {node: '>=18'}
- text-decoder@1.2.7:
- resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==}
-
thenify-all@1.6.0:
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
engines: {node: '>=0.8'}
@@ -2595,15 +2675,16 @@ packages:
thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
- through@2.3.8:
- resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
-
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
+ tinyexec@1.1.1:
+ resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==}
+ engines: {node: '>=18'}
+
tinyglobby@0.2.16:
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
engines: {node: '>=12.0.0'}
@@ -2645,20 +2726,6 @@ packages:
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
- ts-node@10.9.2:
- resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
- hasBin: true
- peerDependencies:
- '@swc/core': '>=1.2.50'
- '@swc/wasm': '>=1.2.50'
- '@types/node': '*'
- typescript: '>=2.7'
- peerDependenciesMeta:
- '@swc/core':
- optional: true
- '@swc/wasm':
- optional: true
-
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
@@ -2685,9 +2752,6 @@ packages:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
- typed-query-selector@2.12.2:
- resolution: {integrity: sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==}
-
typedoc-plugin-markdown@4.11.0:
resolution: {integrity: sha512-2iunh2ALyfyh204OF7h2u0kuQ84xB3jFZtFyUr01nThJkLvR8oGGSSDlyt2gyO4kXhvUxDcVbO0y43+qX+wFbw==}
engines: {node: '>= 18'}
@@ -2708,6 +2772,11 @@ packages:
eslint: 9.33.0
typescript: '>=4.8.4 <6.0.0'
+ typescript@5.6.1-rc:
+ resolution: {integrity: sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'}
@@ -2719,8 +2788,9 @@ packages:
ufo@1.6.3:
resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
- unbzip2-stream@1.4.3:
- resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==}
+ unbash@3.0.0:
+ resolution: {integrity: sha512-FeFPZ/WFT0mbRCuydiZzpPFlrYN8ZUpphQKoq4EeElVIYjYyGzPMxQR/simUwCOJIyVhpFk4RbtyO7RuMpMnHA==}
+ engines: {node: '>=14'}
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
@@ -2728,9 +2798,9 @@ packages:
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
- union@0.5.0:
- resolution: {integrity: sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==}
- engines: {node: '>= 0.8.0'}
+ unicode-emoji-modifier-base@1.0.0:
+ resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==}
+ engines: {node: '>=4'}
universalify@0.1.2:
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
@@ -2743,14 +2813,12 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
- url-join@4.0.1:
- resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==}
-
url-parse@1.5.10:
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
- v8-compile-cache-lib@3.0.1:
- resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
+ validate-npm-package-name@5.0.1:
+ resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
vite-node@3.2.4:
resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==}
@@ -2820,6 +2888,10 @@ packages:
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
engines: {node: '>=18'}
+ walk-up-path@4.0.0:
+ resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==}
+ engines: {node: 20 || >=22}
+
web-streams-polyfill@3.3.3:
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
engines: {node: '>= 8'}
@@ -2828,11 +2900,6 @@ packages:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'}
- whatwg-encoding@2.0.0:
- resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==}
- engines: {node: '>=12'}
- deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
-
whatwg-encoding@3.1.1:
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
engines: {node: '>=18'}
@@ -2872,9 +2939,6 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
- workerpool@6.5.1:
- resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==}
-
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
@@ -2883,6 +2947,10 @@ packages:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
+ wrap-ansi@9.0.2:
+ resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==}
+ engines: {node: '>=18'}
+
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
@@ -2922,35 +2990,16 @@ packages:
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
engines: {node: '>=10'}
- yargs-parser@21.1.1:
- resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
- engines: {node: '>=12'}
-
- yargs-unparser@2.0.0:
- resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==}
- engines: {node: '>=10'}
-
yargs@16.2.0:
resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
engines: {node: '>=10'}
- yargs@17.7.2:
- resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
- engines: {node: '>=12'}
-
- yauzl@2.10.0:
- resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
-
- yn@3.1.1:
- resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
- engines: {node: '>=6'}
-
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
- zod@3.23.8:
- resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
+ zod@4.3.6:
+ resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
zustand@5.0.12:
resolution: {integrity: sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==}
@@ -2977,6 +3026,29 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
+ '@andrewbranch/untar.js@1.0.3': {}
+
+ '@arethetypeswrong/cli@0.18.2':
+ dependencies:
+ '@arethetypeswrong/core': 0.18.2
+ chalk: 4.1.2
+ cli-table3: 0.6.5
+ commander: 10.0.1
+ marked: 9.1.6
+ marked-terminal: 7.3.0(marked@9.1.6)
+ semver: 7.7.4
+
+ '@arethetypeswrong/core@0.18.2':
+ dependencies:
+ '@andrewbranch/untar.js': 1.0.3
+ '@loaderkit/resolve': 1.0.5
+ cjs-module-lexer: 1.4.3
+ fflate: 0.8.2
+ lru-cache: 11.3.5
+ semver: 7.7.4
+ typescript: 5.6.1-rc
+ validate-npm-package-name: 5.0.1
+
'@asamuzakjp/css-color@3.2.0':
dependencies:
'@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
@@ -3008,9 +3080,10 @@ snapshots:
'@bcoe/v8-coverage@1.0.2': {}
- '@cspotcode/source-map-support@0.8.1':
- dependencies:
- '@jridgewell/trace-mapping': 0.3.9
+ '@braidai/lang@1.1.2': {}
+
+ '@colors/colors@1.5.0':
+ optional: true
'@csstools/color-helpers@5.1.0': {}
@@ -3032,6 +3105,22 @@ snapshots:
'@csstools/css-tokenizer@3.0.4': {}
+ '@emnapi/core@1.9.2':
+ dependencies:
+ '@emnapi/wasi-threads': 1.2.1
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/runtime@1.9.2':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@emnapi/wasi-threads@1.2.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
'@esbuild/aix-ppc64@0.28.0':
optional: true
@@ -3110,9 +3199,9 @@ snapshots:
'@esbuild/win32-x64@0.28.0':
optional: true
- '@eslint-community/eslint-utils@4.9.1(eslint@9.33.0)':
+ '@eslint-community/eslint-utils@4.9.1(eslint@9.33.0(jiti@2.6.1))':
dependencies:
- eslint: 9.33.0
+ eslint: 9.33.0(jiti@2.6.1)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {}
@@ -3120,7 +3209,7 @@ snapshots:
'@eslint/config-array@0.21.2':
dependencies:
'@eslint/object-schema': 2.1.7
- debug: 4.4.3(supports-color@8.1.1)
+ debug: 4.4.3
minimatch: 10.2.5
transitivePeerDependencies:
- supports-color
@@ -3134,7 +3223,7 @@ snapshots:
'@eslint/eslintrc@3.3.5':
dependencies:
ajv: 6.15.0
- debug: 4.4.3(supports-color@8.1.1)
+ debug: 4.4.3
espree: 10.4.0
globals: 14.0.0
ignore: 5.3.2
@@ -3213,10 +3302,16 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
- '@jridgewell/trace-mapping@0.3.9':
+ '@loaderkit/resolve@1.0.5':
dependencies:
- '@jridgewell/resolve-uri': 3.1.2
- '@jridgewell/sourcemap-codec': 1.5.5
+ '@braidai/lang': 1.1.2
+
+ '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)':
+ dependencies:
+ '@emnapi/core': 1.9.2
+ '@emnapi/runtime': 1.9.2
+ '@tybys/wasm-util': 0.10.1
+ optional: true
'@nodelib/fs.scandir@2.1.5':
dependencies:
@@ -3230,6 +3325,137 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.20.1
+ '@oxc-parser/binding-android-arm-eabi@0.127.0':
+ optional: true
+
+ '@oxc-parser/binding-android-arm64@0.127.0':
+ optional: true
+
+ '@oxc-parser/binding-darwin-arm64@0.127.0':
+ optional: true
+
+ '@oxc-parser/binding-darwin-x64@0.127.0':
+ optional: true
+
+ '@oxc-parser/binding-freebsd-x64@0.127.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-arm-musleabihf@0.127.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-arm64-gnu@0.127.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-arm64-musl@0.127.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-ppc64-gnu@0.127.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-riscv64-gnu@0.127.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-riscv64-musl@0.127.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-s390x-gnu@0.127.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-x64-gnu@0.127.0':
+ optional: true
+
+ '@oxc-parser/binding-linux-x64-musl@0.127.0':
+ optional: true
+
+ '@oxc-parser/binding-openharmony-arm64@0.127.0':
+ optional: true
+
+ '@oxc-parser/binding-wasm32-wasi@0.127.0':
+ dependencies:
+ '@emnapi/core': 1.9.2
+ '@emnapi/runtime': 1.9.2
+ '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
+ optional: true
+
+ '@oxc-parser/binding-win32-arm64-msvc@0.127.0':
+ optional: true
+
+ '@oxc-parser/binding-win32-ia32-msvc@0.127.0':
+ optional: true
+
+ '@oxc-parser/binding-win32-x64-msvc@0.127.0':
+ optional: true
+
+ '@oxc-project/types@0.127.0': {}
+
+ '@oxc-resolver/binding-android-arm-eabi@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-android-arm64@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-darwin-arm64@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-darwin-x64@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-freebsd-x64@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-arm-gnueabihf@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-arm-musleabihf@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-arm64-gnu@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-arm64-musl@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-ppc64-gnu@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-riscv64-gnu@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-riscv64-musl@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-s390x-gnu@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-x64-gnu@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-x64-musl@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-openharmony-arm64@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-wasm32-wasi@11.19.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)':
+ dependencies:
+ '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
+ transitivePeerDependencies:
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ optional: true
+
+ '@oxc-resolver/binding-win32-arm64-msvc@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-win32-ia32-msvc@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-win32-x64-msvc@11.19.1':
+ optional: true
+
'@pkgjs/parseargs@0.11.0':
optional: true
@@ -3237,21 +3463,7 @@ snapshots:
dependencies:
playwright: 1.59.1
- '@puppeteer/browsers@2.6.1':
- dependencies:
- debug: 4.4.3(supports-color@8.1.1)
- extract-zip: 2.0.1
- progress: 2.0.3
- proxy-agent: 6.5.0
- semver: 7.7.4
- tar-fs: 3.1.2
- unbzip2-stream: 1.4.3
- yargs: 17.7.2
- transitivePeerDependencies:
- - bare-abort-controller
- - bare-buffer
- - react-native-b4a
- - supports-color
+ '@publint/pack@0.1.4': {}
'@rollup/plugin-commonjs@25.0.8(rollup@4.60.2)':
dependencies:
@@ -3274,15 +3486,6 @@ snapshots:
optionalDependencies:
rollup: 4.60.2
- '@rollup/plugin-typescript@12.3.0(rollup@4.60.2)(tslib@2.8.1)(typescript@5.9.3)':
- dependencies:
- '@rollup/pluginutils': 5.3.0(rollup@4.60.2)
- resolve: 1.22.12
- typescript: 5.9.3
- optionalDependencies:
- rollup: 4.60.2
- tslib: 2.8.1
-
'@rollup/pluginutils@5.3.0(rollup@4.60.2)':
dependencies:
'@types/estree': 1.0.8
@@ -3386,6 +3589,8 @@ snapshots:
'@shikijs/vscode-textmate@10.0.2': {}
+ '@sindresorhus/is@4.6.0': {}
+
'@testing-library/dom@9.3.4':
dependencies:
'@babel/code-frame': 7.29.0
@@ -3407,15 +3612,10 @@ snapshots:
transitivePeerDependencies:
- '@types/react'
- '@tootallnate/quickjs-emscripten@0.23.0': {}
-
- '@tsconfig/node10@1.0.12': {}
-
- '@tsconfig/node12@1.0.11': {}
-
- '@tsconfig/node14@1.0.3': {}
-
- '@tsconfig/node16@1.0.4': {}
+ '@tybys/wasm-util@0.10.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
'@types/aria-query@5.0.4': {}
@@ -3472,20 +3672,15 @@ snapshots:
'@types/unist@3.0.3': {}
- '@types/yauzl@2.10.3':
- dependencies:
- '@types/node': 24.12.2
- optional: true
-
- '@typescript-eslint/eslint-plugin@8.39.1(@typescript-eslint/parser@8.39.1(eslint@9.33.0)(typescript@5.9.3))(eslint@9.33.0)(typescript@5.9.3)':
+ '@typescript-eslint/eslint-plugin@8.39.1(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 8.39.1(eslint@9.33.0)(typescript@5.9.3)
+ '@typescript-eslint/parser': 8.39.1(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.39.1
- '@typescript-eslint/type-utils': 8.39.1(eslint@9.33.0)(typescript@5.9.3)
- '@typescript-eslint/utils': 8.39.1(eslint@9.33.0)(typescript@5.9.3)
+ '@typescript-eslint/type-utils': 8.39.1(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.39.1(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.39.1
- eslint: 9.33.0
+ eslint: 9.33.0(jiti@2.6.1)
graphemer: 1.4.0
ignore: 7.0.5
natural-compare: 1.4.0
@@ -3494,14 +3689,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.39.1(eslint@9.33.0)(typescript@5.9.3)':
+ '@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.39.1
'@typescript-eslint/types': 8.39.1
'@typescript-eslint/typescript-estree': 8.39.1(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.39.1
- debug: 4.4.3(supports-color@8.1.1)
- eslint: 9.33.0
+ debug: 4.4.3
+ eslint: 9.33.0(jiti@2.6.1)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -3510,7 +3705,7 @@ snapshots:
dependencies:
'@typescript-eslint/tsconfig-utils': 8.59.1(typescript@5.9.3)
'@typescript-eslint/types': 8.59.1
- debug: 4.4.3(supports-color@8.1.1)
+ debug: 4.4.3
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -3528,13 +3723,13 @@ snapshots:
dependencies:
typescript: 5.9.3
- '@typescript-eslint/type-utils@8.39.1(eslint@9.33.0)(typescript@5.9.3)':
+ '@typescript-eslint/type-utils@8.39.1(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/types': 8.39.1
'@typescript-eslint/typescript-estree': 8.39.1(typescript@5.9.3)
- '@typescript-eslint/utils': 8.39.1(eslint@9.33.0)(typescript@5.9.3)
- debug: 4.4.3(supports-color@8.1.1)
- eslint: 9.33.0
+ '@typescript-eslint/utils': 8.39.1(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3)
+ debug: 4.4.3
+ eslint: 9.33.0(jiti@2.6.1)
ts-api-utils: 2.5.0(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
@@ -3550,7 +3745,7 @@ snapshots:
'@typescript-eslint/tsconfig-utils': 8.39.1(typescript@5.9.3)
'@typescript-eslint/types': 8.39.1
'@typescript-eslint/visitor-keys': 8.39.1
- debug: 4.4.3(supports-color@8.1.1)
+ debug: 4.4.3
fast-glob: 3.3.3
is-glob: 4.0.3
minimatch: 10.2.5
@@ -3560,13 +3755,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.39.1(eslint@9.33.0)(typescript@5.9.3)':
+ '@typescript-eslint/utils@8.39.1(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
- '@eslint-community/eslint-utils': 4.9.1(eslint@9.33.0)
+ '@eslint-community/eslint-utils': 4.9.1(eslint@9.33.0(jiti@2.6.1))
'@typescript-eslint/scope-manager': 8.39.1
'@typescript-eslint/types': 8.39.1
'@typescript-eslint/typescript-estree': 8.39.1(typescript@5.9.3)
- eslint: 9.33.0
+ eslint: 9.33.0(jiti@2.6.1)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
@@ -3581,7 +3776,7 @@ snapshots:
'@ampproject/remapping': 2.3.0
'@bcoe/v8-coverage': 1.0.2
ast-v8-to-istanbul: 0.3.12
- debug: 4.4.3(supports-color@8.1.1)
+ debug: 4.4.3
istanbul-lib-coverage: 3.2.2
istanbul-lib-report: 3.0.1
istanbul-lib-source-maps: 5.0.6
@@ -3600,7 +3795,7 @@ snapshots:
'@ampproject/remapping': 2.3.0
'@bcoe/v8-coverage': 1.0.2
ast-v8-to-istanbul: 0.3.12
- debug: 4.4.3(supports-color@8.1.1)
+ debug: 4.4.3
istanbul-lib-coverage: 3.2.2
istanbul-lib-report: 3.0.1
istanbul-lib-source-maps: 5.0.6
@@ -3681,10 +3876,6 @@ snapshots:
dependencies:
acorn: 8.16.0
- acorn-walk@8.3.5:
- dependencies:
- acorn: 8.16.0
-
acorn@8.16.0: {}
agent-base@7.1.4: {}
@@ -3696,7 +3887,9 @@ snapshots:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
- ansi-colors@4.1.3: {}
+ ansi-escapes@7.3.0:
+ dependencies:
+ environment: 1.1.0
ansi-regex@5.0.1: {}
@@ -3712,13 +3905,6 @@ snapshots:
any-promise@1.3.0: {}
- anymatch@3.1.3:
- dependencies:
- normalize-path: 3.0.0
- picomatch: 2.3.2
-
- arg@4.1.3: {}
-
argparse@2.0.1: {}
aria-query@5.1.3:
@@ -3734,72 +3920,22 @@ snapshots:
assertion-error@2.0.1: {}
- ast-types@0.13.4:
- dependencies:
- tslib: 2.8.1
-
ast-v8-to-istanbul@0.3.12:
dependencies:
'@jridgewell/trace-mapping': 0.3.31
estree-walker: 3.0.3
js-tokens: 10.0.0
- async@3.2.6: {}
-
asynckit@0.4.0: {}
available-typed-arrays@1.0.7:
dependencies:
possible-typed-array-names: 1.1.0
- b4a@1.8.0: {}
-
balanced-match@1.0.2: {}
balanced-match@4.0.4: {}
- bare-events@2.8.2: {}
-
- bare-fs@4.7.1:
- dependencies:
- bare-events: 2.8.2
- bare-path: 3.0.0
- bare-stream: 2.13.0(bare-events@2.8.2)
- bare-url: 2.4.2
- fast-fifo: 1.3.2
- transitivePeerDependencies:
- - bare-abort-controller
- - react-native-b4a
-
- bare-os@3.9.0: {}
-
- bare-path@3.0.0:
- dependencies:
- bare-os: 3.9.0
-
- bare-stream@2.13.0(bare-events@2.8.2):
- dependencies:
- streamx: 2.25.0
- teex: 1.0.1
- optionalDependencies:
- bare-events: 2.8.2
- transitivePeerDependencies:
- - react-native-b4a
-
- bare-url@2.4.2:
- dependencies:
- bare-path: 3.0.0
-
- base64-js@1.5.1: {}
-
- basic-auth@2.0.1:
- dependencies:
- safe-buffer: 5.1.2
-
- basic-ftp@5.3.0: {}
-
- binary-extensions@2.3.0: {}
-
binaryen@129.0.0: {}
brace-expansion@2.1.0:
@@ -3814,15 +3950,6 @@ snapshots:
dependencies:
fill-range: 7.1.1
- browser-stdout@1.3.1: {}
-
- buffer-crc32@0.2.13: {}
-
- buffer@5.7.1:
- dependencies:
- base64-js: 1.5.1
- ieee754: 1.2.1
-
bundle-require@5.1.0(esbuild@0.28.0):
dependencies:
esbuild: 0.28.0
@@ -3849,8 +3976,6 @@ snapshots:
callsites@3.1.0: {}
- camelcase@6.3.0: {}
-
chai@5.3.3:
dependencies:
assertion-error: 2.0.1
@@ -3866,19 +3991,9 @@ snapshots:
chalk@5.6.2: {}
- check-error@2.1.3: {}
+ char-regex@1.0.2: {}
- chokidar@3.6.0:
- dependencies:
- anymatch: 3.1.3
- braces: 3.0.3
- glob-parent: 5.1.2
- is-binary-path: 2.1.0
- is-glob: 4.0.3
- normalize-path: 3.0.0
- readdirp: 3.6.0
- optionalDependencies:
- fsevents: 2.3.3
+ check-error@2.1.3: {}
chokidar@4.0.3:
dependencies:
@@ -3886,19 +4001,33 @@ snapshots:
chownr@3.0.0: {}
- chromium-bidi@0.11.0(devtools-protocol@0.0.1367902):
+ cjs-module-lexer@1.4.3: {}
+
+ cli-cursor@5.0.0:
dependencies:
- devtools-protocol: 0.0.1367902
- mitt: 3.0.1
- zod: 3.23.8
+ restore-cursor: 5.1.0
- cliui@7.0.4:
+ cli-highlight@2.1.11:
+ dependencies:
+ chalk: 4.1.2
+ highlight.js: 10.7.3
+ mz: 2.7.0
+ parse5: 5.1.1
+ parse5-htmlparser2-tree-adapter: 6.0.1
+ yargs: 16.2.0
+
+ cli-table3@0.6.5:
dependencies:
string-width: 4.2.3
- strip-ansi: 6.0.1
- wrap-ansi: 7.0.0
+ optionalDependencies:
+ '@colors/colors': 1.5.0
- cliui@8.0.1:
+ cli-truncate@5.2.0:
+ dependencies:
+ slice-ansi: 8.0.0
+ string-width: 8.2.1
+
+ cliui@7.0.4:
dependencies:
string-width: 4.2.3
strip-ansi: 6.0.1
@@ -3912,10 +4041,16 @@ snapshots:
colorette@1.4.0: {}
+ colorette@2.0.20: {}
+
combined-stream@1.0.8:
dependencies:
delayed-stream: 1.0.0
+ commander@10.0.1: {}
+
+ commander@14.0.3: {}
+
commander@4.1.1: {}
commondir@1.0.1: {}
@@ -3924,17 +4059,6 @@ snapshots:
consola@3.4.2: {}
- corser@2.0.1: {}
-
- cosmiconfig@9.0.1(typescript@5.9.3):
- dependencies:
- env-paths: 2.2.1
- import-fresh: 3.3.1
- js-yaml: 4.1.1
- parse-json: 5.2.0
- optionalDependencies:
- typescript: 5.9.3
-
cpr@3.0.1:
dependencies:
graceful-fs: 4.2.11
@@ -3942,8 +4066,6 @@ snapshots:
mkdirp: 0.5.6
rimraf: 2.7.1
- create-require@1.1.1: {}
-
cross-env@7.0.3:
dependencies:
cross-spawn: 7.0.6
@@ -3963,20 +4085,14 @@ snapshots:
data-uri-to-buffer@4.0.1: {}
- data-uri-to-buffer@6.0.2: {}
-
data-urls@5.0.0:
dependencies:
whatwg-mimetype: 4.0.0
whatwg-url: 14.2.0
- debug@4.4.3(supports-color@8.1.1):
+ debug@4.4.3:
dependencies:
ms: 2.1.3
- optionalDependencies:
- supports-color: 8.1.1
-
- decamelize@4.0.0: {}
decimal.js@10.6.0: {}
@@ -4017,24 +4133,12 @@ snapshots:
dependencies:
define-data-property: 1.1.4
has-property-descriptors: 1.0.2
- object-keys: 1.1.1
-
- degenerator@5.0.1:
- dependencies:
- ast-types: 0.13.4
- escodegen: 2.1.0
- esprima: 4.0.1
+ object-keys: 1.1.1
delayed-stream@1.0.0: {}
- devtools-protocol@0.0.1367902: {}
-
dexie@4.4.2: {}
- diff@4.0.4: {}
-
- diff@5.2.2: {}
-
dir-glob@3.0.1:
dependencies:
path-type: 4.0.0
@@ -4049,23 +4153,19 @@ snapshots:
eastasianwidth@0.2.0: {}
+ emoji-regex@10.6.0: {}
+
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
- end-of-stream@1.4.5:
- dependencies:
- once: 1.4.0
+ emojilib@2.4.0: {}
entities@4.5.0: {}
entities@6.0.1: {}
- env-paths@2.2.1: {}
-
- error-ex@1.3.4:
- dependencies:
- is-arrayish: 0.2.1
+ environment@1.1.0: {}
es-define-property@1.0.1: {}
@@ -4129,13 +4229,9 @@ snapshots:
escape-string-regexp@4.0.0: {}
- escodegen@2.1.0:
+ eslint-config-prettier@10.1.8(eslint@9.33.0(jiti@2.6.1)):
dependencies:
- esprima: 4.0.1
- estraverse: 5.3.0
- esutils: 2.0.3
- optionalDependencies:
- source-map: 0.6.1
+ eslint: 9.33.0(jiti@2.6.1)
eslint-scope@8.4.0:
dependencies:
@@ -4146,9 +4242,9 @@ snapshots:
eslint-visitor-keys@4.2.1: {}
- eslint@9.33.0:
+ eslint@9.33.0(jiti@2.6.1):
dependencies:
- '@eslint-community/eslint-utils': 4.9.1(eslint@9.33.0)
+ '@eslint-community/eslint-utils': 4.9.1(eslint@9.33.0(jiti@2.6.1))
'@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.21.2
'@eslint/config-helpers': 0.3.1
@@ -4164,7 +4260,7 @@ snapshots:
ajv: 6.15.0
chalk: 4.1.2
cross-spawn: 7.0.6
- debug: 4.4.3(supports-color@8.1.1)
+ debug: 4.4.3
escape-string-regexp: 4.0.0
eslint-scope: 8.4.0
eslint-visitor-keys: 4.2.1
@@ -4183,19 +4279,17 @@ snapshots:
minimatch: 10.2.5
natural-compare: 1.4.0
optionator: 0.9.4
+ optionalDependencies:
+ jiti: 2.6.1
transitivePeerDependencies:
- supports-color
- esm@3.2.25: {}
-
espree@10.4.0:
dependencies:
acorn: 8.16.0
acorn-jsx: 5.3.2(acorn@8.16.0)
eslint-visitor-keys: 4.2.1
- esprima@4.0.1: {}
-
esquery@1.7.0:
dependencies:
estraverse: 5.3.0
@@ -4214,32 +4308,14 @@ snapshots:
esutils@2.0.3: {}
- eventemitter3@4.0.7: {}
-
- events-universal@1.0.1:
- dependencies:
- bare-events: 2.8.2
- transitivePeerDependencies:
- - bare-abort-controller
+ eventemitter3@5.0.4: {}
expect-type@1.3.0: {}
- extract-zip@2.0.1:
- dependencies:
- debug: 4.4.3(supports-color@8.1.1)
- get-stream: 5.2.0
- yauzl: 2.10.0
- optionalDependencies:
- '@types/yauzl': 2.10.3
- transitivePeerDependencies:
- - supports-color
-
fake-indexeddb@6.2.5: {}
fast-deep-equal@3.1.3: {}
- fast-fifo@1.3.2: {}
-
fast-glob@3.3.3:
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -4256,9 +4332,9 @@ snapshots:
dependencies:
reusify: 1.1.0
- fd-slicer@1.1.0:
+ fd-package-json@2.0.0:
dependencies:
- pend: 1.2.0
+ walk-up-path: 4.0.0
fdir@6.5.0(picomatch@4.0.4):
optionalDependencies:
@@ -4269,6 +4345,8 @@ snapshots:
node-domexception: 1.0.0
web-streams-polyfill: 3.3.3
+ fflate@0.8.2: {}
+
file-entry-cache@8.0.0:
dependencies:
flat-cache: 4.0.1
@@ -4293,12 +4371,8 @@ snapshots:
flatted: 3.4.2
keyv: 4.5.4
- flat@5.0.2: {}
-
flatted@3.4.2: {}
- follow-redirects@1.16.0: {}
-
for-each@0.3.5:
dependencies:
is-callable: 1.2.7
@@ -4316,6 +4390,10 @@ snapshots:
hasown: 2.0.3
mime-types: 2.1.35
+ formatly@0.3.0:
+ dependencies:
+ fd-package-json: 2.0.0
+
formdata-polyfill@4.0.10:
dependencies:
fetch-blob: 3.2.0
@@ -4340,6 +4418,8 @@ snapshots:
get-caller-file@2.0.5: {}
+ get-east-asian-width@1.5.0: {}
+
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -4358,17 +4438,9 @@ snapshots:
dunder-proto: 1.0.1
es-object-atoms: 1.1.1
- get-stream@5.2.0:
- dependencies:
- pump: 3.0.4
-
- get-uri@6.0.5:
+ get-tsconfig@4.14.0:
dependencies:
- basic-ftp: 5.3.0
- data-uri-to-buffer: 6.0.2
- debug: 4.4.3(supports-color@8.1.1)
- transitivePeerDependencies:
- - supports-color
+ resolve-pkg-maps: 1.0.0
glob-parent@5.1.2:
dependencies:
@@ -4456,11 +4528,7 @@ snapshots:
dependencies:
function-bind: 1.1.2
- he@1.2.0: {}
-
- html-encoding-sniffer@3.0.0:
- dependencies:
- whatwg-encoding: 2.0.0
+ highlight.js@10.7.3: {}
html-encoding-sniffer@4.0.0:
dependencies:
@@ -4471,41 +4539,14 @@ snapshots:
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.4
- debug: 4.4.3(supports-color@8.1.1)
- transitivePeerDependencies:
- - supports-color
-
- http-proxy@1.18.1:
- dependencies:
- eventemitter3: 4.0.7
- follow-redirects: 1.16.0
- requires-port: 1.0.0
- transitivePeerDependencies:
- - debug
-
- http-server@14.1.1:
- dependencies:
- basic-auth: 2.0.1
- chalk: 4.1.2
- corser: 2.0.1
- he: 1.2.0
- html-encoding-sniffer: 3.0.0
- http-proxy: 1.18.1
- mime: 1.6.0
- minimist: 1.2.8
- opener: 1.5.2
- portfinder: 1.0.38
- secure-compare: 3.0.1
- union: 0.5.0
- url-join: 4.0.1
+ debug: 4.4.3
transitivePeerDependencies:
- - debug
- supports-color
https-proxy-agent@7.0.6:
dependencies:
agent-base: 7.1.4
- debug: 4.4.3(supports-color@8.1.1)
+ debug: 4.4.3
transitivePeerDependencies:
- supports-color
@@ -4513,8 +4554,6 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
- ieee754@1.2.1: {}
-
ignore@5.3.2: {}
ignore@7.0.5: {}
@@ -4539,8 +4578,6 @@ snapshots:
hasown: 2.0.3
side-channel: 1.1.0
- ip-address@10.1.1: {}
-
is-arguments@1.2.0:
dependencies:
call-bound: 1.0.4
@@ -4552,16 +4589,10 @@ snapshots:
call-bound: 1.0.4
get-intrinsic: 1.3.0
- is-arrayish@0.2.1: {}
-
is-bigint@1.1.0:
dependencies:
has-bigints: 1.1.0
- is-binary-path@2.1.0:
- dependencies:
- binary-extensions: 2.3.0
-
is-boolean-object@1.2.2:
dependencies:
call-bound: 1.0.4
@@ -4582,6 +4613,10 @@ snapshots:
is-fullwidth-code-point@3.0.0: {}
+ is-fullwidth-code-point@5.1.0:
+ dependencies:
+ get-east-asian-width: 1.5.0
+
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
@@ -4597,8 +4632,6 @@ snapshots:
is-number@7.0.0: {}
- is-plain-obj@2.1.0: {}
-
is-plain-object@3.0.1: {}
is-potential-custom-element-name@1.0.1: {}
@@ -4631,8 +4664,6 @@ snapshots:
has-symbols: 1.1.0
safe-regex-test: 1.1.0
- is-unicode-supported@0.1.0: {}
-
is-weakmap@2.0.2: {}
is-weakset@2.0.4:
@@ -4655,7 +4686,7 @@ snapshots:
istanbul-lib-source-maps@5.0.6:
dependencies:
'@jridgewell/trace-mapping': 0.3.31
- debug: 4.4.3(supports-color@8.1.1)
+ debug: 4.4.3
istanbul-lib-coverage: 3.2.2
transitivePeerDependencies:
- supports-color
@@ -4675,6 +4706,8 @@ snapshots:
dependencies:
'@isaacs/cliui': 9.0.0
+ jiti@2.6.1: {}
+
joycon@3.1.1: {}
js-tokens@10.0.0: {}
@@ -4717,8 +4750,6 @@ snapshots:
json-buffer@3.0.1: {}
- json-parse-even-better-errors@2.3.1: {}
-
json-schema-traverse@0.4.1: {}
json-stable-stringify-without-jsonify@1.0.1: {}
@@ -4731,6 +4762,69 @@ snapshots:
dependencies:
json-buffer: 3.0.1
+ knip@6.7.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2):
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.4)
+ formatly: 0.3.0
+ get-tsconfig: 4.14.0
+ jiti: 2.6.1
+ minimist: 1.2.8
+ oxc-parser: 0.127.0
+ oxc-resolver: 11.19.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
+ picomatch: 4.0.4
+ smol-toml: 1.6.1
+ strip-json-comments: 5.0.3
+ tinyglobby: 0.2.16
+ unbash: 3.0.0
+ yaml: 2.8.3
+ zod: 4.3.6
+ transitivePeerDependencies:
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+
+ lefthook-darwin-arm64@1.13.6:
+ optional: true
+
+ lefthook-darwin-x64@1.13.6:
+ optional: true
+
+ lefthook-freebsd-arm64@1.13.6:
+ optional: true
+
+ lefthook-freebsd-x64@1.13.6:
+ optional: true
+
+ lefthook-linux-arm64@1.13.6:
+ optional: true
+
+ lefthook-linux-x64@1.13.6:
+ optional: true
+
+ lefthook-openbsd-arm64@1.13.6:
+ optional: true
+
+ lefthook-openbsd-x64@1.13.6:
+ optional: true
+
+ lefthook-windows-arm64@1.13.6:
+ optional: true
+
+ lefthook-windows-x64@1.13.6:
+ optional: true
+
+ lefthook@1.13.6:
+ optionalDependencies:
+ lefthook-darwin-arm64: 1.13.6
+ lefthook-darwin-x64: 1.13.6
+ lefthook-freebsd-arm64: 1.13.6
+ lefthook-freebsd-x64: 1.13.6
+ lefthook-linux-arm64: 1.13.6
+ lefthook-linux-x64: 1.13.6
+ lefthook-openbsd-arm64: 1.13.6
+ lefthook-openbsd-x64: 1.13.6
+ lefthook-windows-arm64: 1.13.6
+ lefthook-windows-x64: 1.13.6
+
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
@@ -4744,6 +4838,24 @@ snapshots:
dependencies:
uc.micro: 2.1.0
+ lint-staged@16.4.0:
+ dependencies:
+ commander: 14.0.3
+ listr2: 9.0.5
+ picomatch: 4.0.4
+ string-argv: 0.3.2
+ tinyexec: 1.1.1
+ yaml: 2.8.3
+
+ listr2@9.0.5:
+ dependencies:
+ cli-truncate: 5.2.0
+ colorette: 2.0.20
+ eventemitter3: 5.0.4
+ log-update: 6.1.0
+ rfdc: 1.4.1
+ wrap-ansi: 9.0.2
+
load-tsconfig@0.2.5: {}
locate-path@6.0.0:
@@ -4752,10 +4864,13 @@ snapshots:
lodash.merge@4.6.2: {}
- log-symbols@4.1.0:
+ log-update@6.1.0:
dependencies:
- chalk: 4.1.2
- is-unicode-supported: 0.1.0
+ ansi-escapes: 7.3.0
+ cli-cursor: 5.0.0
+ slice-ansi: 7.1.2
+ strip-ansi: 7.2.0
+ wrap-ansi: 9.0.2
loose-envify@1.4.0:
dependencies:
@@ -4767,8 +4882,6 @@ snapshots:
lru-cache@11.3.5: {}
- lru-cache@7.18.3: {}
-
lunr@2.3.9: {}
lz-string@1.5.0: {}
@@ -4787,8 +4900,6 @@ snapshots:
dependencies:
semver: 7.7.4
- make-error@1.3.6: {}
-
markdown-it@14.1.1:
dependencies:
argparse: 2.0.1
@@ -4798,6 +4909,19 @@ snapshots:
punycode.js: 2.3.1
uc.micro: 2.1.0
+ marked-terminal@7.3.0(marked@9.1.6):
+ dependencies:
+ ansi-escapes: 7.3.0
+ ansi-regex: 6.2.2
+ chalk: 5.6.2
+ cli-highlight: 2.1.11
+ cli-table3: 0.6.5
+ marked: 9.1.6
+ node-emoji: 2.2.0
+ supports-hyperlinks: 3.2.0
+
+ marked@9.1.6: {}
+
math-intrinsics@1.1.0: {}
mdurl@2.0.0: {}
@@ -4815,7 +4939,7 @@ snapshots:
dependencies:
mime-db: 1.52.0
- mime@1.6.0: {}
+ mimic-function@5.0.1: {}
minimatch@10.2.5:
dependencies:
@@ -4833,8 +4957,6 @@ snapshots:
dependencies:
minipass: 7.1.3
- mitt@3.0.1: {}
-
mkdirp@0.5.6:
dependencies:
minimist: 1.2.8
@@ -4846,28 +4968,7 @@ snapshots:
pkg-types: 1.3.1
ufo: 1.6.3
- mocha@10.8.2:
- dependencies:
- ansi-colors: 4.1.3
- browser-stdout: 1.3.1
- chokidar: 3.6.0
- debug: 4.4.3(supports-color@8.1.1)
- diff: 5.2.2
- escape-string-regexp: 4.0.0
- find-up: 5.0.0
- glob: 8.1.0
- he: 1.2.0
- js-yaml: 4.1.1
- log-symbols: 4.1.0
- minimatch: 10.2.5
- ms: 2.1.3
- serialize-javascript: 6.0.2
- strip-json-comments: 3.1.1
- supports-color: 8.1.1
- workerpool: 6.5.1
- yargs: 16.2.0
- yargs-parser: 20.2.9
- yargs-unparser: 2.0.0
+ mri@1.2.0: {}
ms@2.1.3: {}
@@ -4881,18 +4982,21 @@ snapshots:
natural-compare@1.4.0: {}
- netmask@2.1.1: {}
-
node-domexception@1.0.0: {}
+ node-emoji@2.2.0:
+ dependencies:
+ '@sindresorhus/is': 4.6.0
+ char-regex: 1.0.2
+ emojilib: 2.4.0
+ skin-tone: 2.0.0
+
node-fetch@3.3.2:
dependencies:
data-uri-to-buffer: 4.0.1
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
- normalize-path@3.0.0: {}
-
nwsapi@2.2.23: {}
object-assign@4.1.1: {}
@@ -4919,7 +5023,9 @@ snapshots:
dependencies:
wrappy: 1.0.2
- opener@1.5.2: {}
+ onetime@7.0.0:
+ dependencies:
+ mimic-function: 5.0.1
optionator@0.9.4:
dependencies:
@@ -4930,6 +5036,57 @@ snapshots:
type-check: 0.4.0
word-wrap: 1.2.5
+ oxc-parser@0.127.0:
+ dependencies:
+ '@oxc-project/types': 0.127.0
+ optionalDependencies:
+ '@oxc-parser/binding-android-arm-eabi': 0.127.0
+ '@oxc-parser/binding-android-arm64': 0.127.0
+ '@oxc-parser/binding-darwin-arm64': 0.127.0
+ '@oxc-parser/binding-darwin-x64': 0.127.0
+ '@oxc-parser/binding-freebsd-x64': 0.127.0
+ '@oxc-parser/binding-linux-arm-gnueabihf': 0.127.0
+ '@oxc-parser/binding-linux-arm-musleabihf': 0.127.0
+ '@oxc-parser/binding-linux-arm64-gnu': 0.127.0
+ '@oxc-parser/binding-linux-arm64-musl': 0.127.0
+ '@oxc-parser/binding-linux-ppc64-gnu': 0.127.0
+ '@oxc-parser/binding-linux-riscv64-gnu': 0.127.0
+ '@oxc-parser/binding-linux-riscv64-musl': 0.127.0
+ '@oxc-parser/binding-linux-s390x-gnu': 0.127.0
+ '@oxc-parser/binding-linux-x64-gnu': 0.127.0
+ '@oxc-parser/binding-linux-x64-musl': 0.127.0
+ '@oxc-parser/binding-openharmony-arm64': 0.127.0
+ '@oxc-parser/binding-wasm32-wasi': 0.127.0
+ '@oxc-parser/binding-win32-arm64-msvc': 0.127.0
+ '@oxc-parser/binding-win32-ia32-msvc': 0.127.0
+ '@oxc-parser/binding-win32-x64-msvc': 0.127.0
+
+ oxc-resolver@11.19.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2):
+ optionalDependencies:
+ '@oxc-resolver/binding-android-arm-eabi': 11.19.1
+ '@oxc-resolver/binding-android-arm64': 11.19.1
+ '@oxc-resolver/binding-darwin-arm64': 11.19.1
+ '@oxc-resolver/binding-darwin-x64': 11.19.1
+ '@oxc-resolver/binding-freebsd-x64': 11.19.1
+ '@oxc-resolver/binding-linux-arm-gnueabihf': 11.19.1
+ '@oxc-resolver/binding-linux-arm-musleabihf': 11.19.1
+ '@oxc-resolver/binding-linux-arm64-gnu': 11.19.1
+ '@oxc-resolver/binding-linux-arm64-musl': 11.19.1
+ '@oxc-resolver/binding-linux-ppc64-gnu': 11.19.1
+ '@oxc-resolver/binding-linux-riscv64-gnu': 11.19.1
+ '@oxc-resolver/binding-linux-riscv64-musl': 11.19.1
+ '@oxc-resolver/binding-linux-s390x-gnu': 11.19.1
+ '@oxc-resolver/binding-linux-x64-gnu': 11.19.1
+ '@oxc-resolver/binding-linux-x64-musl': 11.19.1
+ '@oxc-resolver/binding-openharmony-arm64': 11.19.1
+ '@oxc-resolver/binding-wasm32-wasi': 11.19.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
+ '@oxc-resolver/binding-win32-arm64-msvc': 11.19.1
+ '@oxc-resolver/binding-win32-ia32-msvc': 11.19.1
+ '@oxc-resolver/binding-win32-x64-msvc': 11.19.1
+ transitivePeerDependencies:
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+
p-limit@3.1.0:
dependencies:
yocto-queue: 0.1.0
@@ -4938,36 +5095,21 @@ snapshots:
dependencies:
p-limit: 3.1.0
- pac-proxy-agent@7.2.0:
- dependencies:
- '@tootallnate/quickjs-emscripten': 0.23.0
- agent-base: 7.1.4
- debug: 4.4.3(supports-color@8.1.1)
- get-uri: 6.0.5
- http-proxy-agent: 7.0.2
- https-proxy-agent: 7.0.6
- pac-resolver: 7.0.1
- socks-proxy-agent: 8.0.5
- transitivePeerDependencies:
- - supports-color
-
- pac-resolver@7.0.1:
- dependencies:
- degenerator: 5.0.1
- netmask: 2.1.1
-
package-json-from-dist@1.0.1: {}
+ package-manager-detector@1.6.0: {}
+
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
- parse-json@5.2.0:
+ parse5-htmlparser2-tree-adapter@6.0.1:
dependencies:
- '@babel/code-frame': 7.29.0
- error-ex: 1.3.4
- json-parse-even-better-errors: 2.3.1
- lines-and-columns: 1.2.4
+ parse5: 6.0.1
+
+ parse5@5.1.1: {}
+
+ parse5@6.0.1: {}
parse5@7.3.0:
dependencies:
@@ -4997,8 +5139,6 @@ snapshots:
pathval@2.0.1: {}
- pend@1.2.0: {}
-
picocolors@1.1.1: {}
picomatch@2.3.2: {}
@@ -5021,19 +5161,13 @@ snapshots:
optionalDependencies:
fsevents: 2.3.2
- portfinder@1.0.38:
- dependencies:
- async: 3.2.6
- debug: 4.4.3(supports-color@8.1.1)
- transitivePeerDependencies:
- - supports-color
-
possible-typed-array-names@1.1.0: {}
- postcss-load-config@6.0.1(postcss@8.5.12)(yaml@2.8.3):
+ postcss-load-config@6.0.1(jiti@2.6.1)(postcss@8.5.12)(yaml@2.8.3):
dependencies:
lilconfig: 3.1.3
optionalDependencies:
+ jiti: 2.6.1
postcss: 8.5.12
yaml: 2.8.3
@@ -5053,81 +5187,25 @@ snapshots:
ansi-styles: 5.2.0
react-is: 17.0.2
- progress@2.0.3: {}
-
- proxy-agent@6.5.0:
- dependencies:
- agent-base: 7.1.4
- debug: 4.4.3(supports-color@8.1.1)
- http-proxy-agent: 7.0.2
- https-proxy-agent: 7.0.6
- lru-cache: 7.18.3
- pac-proxy-agent: 7.2.0
- proxy-from-env: 1.1.0
- socks-proxy-agent: 8.0.5
- transitivePeerDependencies:
- - supports-color
-
- proxy-from-env@1.1.0: {}
-
psl@1.15.0:
dependencies:
punycode: 2.3.1
- pump@3.0.4:
+ publint@0.3.18:
dependencies:
- end-of-stream: 1.4.5
- once: 1.4.0
+ '@publint/pack': 0.1.4
+ package-manager-detector: 1.6.0
+ picocolors: 1.1.1
+ sade: 1.8.1
punycode.js@2.3.1: {}
punycode@2.3.1: {}
- puppeteer-core@23.11.1:
- dependencies:
- '@puppeteer/browsers': 2.6.1
- chromium-bidi: 0.11.0(devtools-protocol@0.0.1367902)
- debug: 4.4.3(supports-color@8.1.1)
- devtools-protocol: 0.0.1367902
- typed-query-selector: 2.12.2
- ws: 8.20.0
- transitivePeerDependencies:
- - bare-abort-controller
- - bare-buffer
- - bufferutil
- - react-native-b4a
- - supports-color
- - utf-8-validate
-
- puppeteer@23.11.1(typescript@5.9.3):
- dependencies:
- '@puppeteer/browsers': 2.6.1
- chromium-bidi: 0.11.0(devtools-protocol@0.0.1367902)
- cosmiconfig: 9.0.1(typescript@5.9.3)
- devtools-protocol: 0.0.1367902
- puppeteer-core: 23.11.1
- typed-query-selector: 2.12.2
- transitivePeerDependencies:
- - bare-abort-controller
- - bare-buffer
- - bufferutil
- - react-native-b4a
- - supports-color
- - typescript
- - utf-8-validate
-
- qs@6.15.1:
- dependencies:
- side-channel: 1.1.0
-
querystringify@2.2.0: {}
queue-microtask@1.2.3: {}
- randombytes@2.1.0:
- dependencies:
- safe-buffer: 5.2.1
-
react-dom@18.3.1(react@18.3.1):
dependencies:
loose-envify: 1.4.0
@@ -5140,10 +5218,6 @@ snapshots:
dependencies:
loose-envify: 1.4.0
- readdirp@3.6.0:
- dependencies:
- picomatch: 2.3.2
-
readdirp@4.1.2: {}
regexp.prototype.flags@1.5.4:
@@ -5163,6 +5237,8 @@ snapshots:
resolve-from@5.0.0: {}
+ resolve-pkg-maps@1.0.0: {}
+
resolve@1.22.12:
dependencies:
es-errors: 1.3.0
@@ -5170,8 +5246,15 @@ snapshots:
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
+ restore-cursor@5.1.0:
+ dependencies:
+ onetime: 7.0.0
+ signal-exit: 4.1.0
+
reusify@1.1.0: {}
+ rfdc@1.4.1: {}
+
rimraf@2.7.1:
dependencies:
glob: 7.2.3
@@ -5228,9 +5311,9 @@ snapshots:
dependencies:
queue-microtask: 1.2.3
- safe-buffer@5.1.2: {}
-
- safe-buffer@5.2.1: {}
+ sade@1.8.1:
+ dependencies:
+ mri: 1.2.0
safe-regex-test@1.1.0:
dependencies:
@@ -5248,14 +5331,8 @@ snapshots:
dependencies:
loose-envify: 1.4.0
- secure-compare@3.0.1: {}
-
semver@7.7.4: {}
- serialize-javascript@6.0.2:
- dependencies:
- randombytes: 2.1.0
-
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4
@@ -5310,27 +5387,25 @@ snapshots:
signal-exit@4.1.0: {}
- slash@3.0.0: {}
+ skin-tone@2.0.0:
+ dependencies:
+ unicode-emoji-modifier-base: 1.0.0
- smart-buffer@4.2.0: {}
+ slash@3.0.0: {}
- socks-proxy-agent@8.0.5:
+ slice-ansi@7.1.2:
dependencies:
- agent-base: 7.1.4
- debug: 4.4.3(supports-color@8.1.1)
- socks: 2.8.7
- transitivePeerDependencies:
- - supports-color
+ ansi-styles: 6.2.3
+ is-fullwidth-code-point: 5.1.0
- socks@2.8.7:
+ slice-ansi@8.0.0:
dependencies:
- ip-address: 10.1.1
- smart-buffer: 4.2.0
+ ansi-styles: 6.2.3
+ is-fullwidth-code-point: 5.1.0
- source-map-js@1.2.1: {}
+ smol-toml@1.6.1: {}
- source-map@0.6.1:
- optional: true
+ source-map-js@1.2.1: {}
source-map@0.7.6: {}
@@ -5343,14 +5418,7 @@ snapshots:
es-errors: 1.3.0
internal-slot: 1.1.0
- streamx@2.25.0:
- dependencies:
- events-universal: 1.0.1
- fast-fifo: 1.3.2
- text-decoder: 1.2.7
- transitivePeerDependencies:
- - bare-abort-controller
- - react-native-b4a
+ string-argv@0.3.2: {}
string-width@4.2.3:
dependencies:
@@ -5364,6 +5432,17 @@ snapshots:
emoji-regex: 9.2.2
strip-ansi: 7.2.0
+ string-width@7.2.0:
+ dependencies:
+ emoji-regex: 10.6.0
+ get-east-asian-width: 1.5.0
+ strip-ansi: 7.2.0
+
+ string-width@8.2.1:
+ dependencies:
+ get-east-asian-width: 1.5.0
+ strip-ansi: 7.2.0
+
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
@@ -5374,6 +5453,8 @@ snapshots:
strip-json-comments@3.1.1: {}
+ strip-json-comments@5.0.3: {}
+
strip-literal@3.1.0:
dependencies:
js-tokens: 9.0.1
@@ -5392,37 +5473,15 @@ snapshots:
dependencies:
has-flag: 4.0.0
- supports-color@8.1.1:
+ supports-hyperlinks@3.2.0:
dependencies:
has-flag: 4.0.0
+ supports-color: 7.2.0
supports-preserve-symlinks-flag@1.0.0: {}
symbol-tree@3.2.4: {}
- tar-fs@3.1.2:
- dependencies:
- pump: 3.0.4
- tar-stream: 3.1.8
- optionalDependencies:
- bare-fs: 4.7.1
- bare-path: 3.0.0
- transitivePeerDependencies:
- - bare-abort-controller
- - bare-buffer
- - react-native-b4a
-
- tar-stream@3.1.8:
- dependencies:
- b4a: 1.8.0
- bare-fs: 4.7.1
- fast-fifo: 1.3.2
- streamx: 2.25.0
- transitivePeerDependencies:
- - bare-abort-controller
- - bare-buffer
- - react-native-b4a
-
tar@7.5.13:
dependencies:
'@isaacs/fs-minipass': 4.0.1
@@ -5431,25 +5490,12 @@ snapshots:
minizlib: 3.1.0
yallist: 5.0.0
- teex@1.0.1:
- dependencies:
- streamx: 2.25.0
- transitivePeerDependencies:
- - bare-abort-controller
- - react-native-b4a
-
test-exclude@7.0.2:
dependencies:
'@istanbuljs/schema': 0.1.6
glob: 10.5.0
minimatch: 9.0.9
- text-decoder@1.2.7:
- dependencies:
- b4a: 1.8.0
- transitivePeerDependencies:
- - react-native-b4a
-
thenify-all@1.6.0:
dependencies:
thenify: 3.3.1
@@ -5458,12 +5504,12 @@ snapshots:
dependencies:
any-promise: 1.3.0
- through@2.3.8: {}
-
tinybench@2.9.0: {}
tinyexec@0.3.2: {}
+ tinyexec@1.1.1: {}
+
tinyglobby@0.2.16:
dependencies:
fdir: 6.5.0(picomatch@4.0.4)
@@ -5498,38 +5544,21 @@ snapshots:
ts-interface-checker@0.1.13: {}
- ts-node@10.9.2(@types/node@24.12.2)(typescript@5.9.3):
- dependencies:
- '@cspotcode/source-map-support': 0.8.1
- '@tsconfig/node10': 1.0.12
- '@tsconfig/node12': 1.0.11
- '@tsconfig/node14': 1.0.3
- '@tsconfig/node16': 1.0.4
- '@types/node': 24.12.2
- acorn: 8.16.0
- acorn-walk: 8.3.5
- arg: 4.1.3
- create-require: 1.1.1
- diff: 4.0.4
- make-error: 1.3.6
- typescript: 5.9.3
- v8-compile-cache-lib: 3.0.1
- yn: 3.1.1
-
- tslib@2.8.1: {}
+ tslib@2.8.1:
+ optional: true
- tsup@8.5.1(postcss@8.5.12)(typescript@5.9.3)(yaml@2.8.3):
+ tsup@8.5.1(jiti@2.6.1)(postcss@8.5.12)(typescript@5.9.3)(yaml@2.8.3):
dependencies:
bundle-require: 5.1.0(esbuild@0.28.0)
cac: 6.7.14
chokidar: 4.0.3
consola: 3.4.2
- debug: 4.4.3(supports-color@8.1.1)
+ debug: 4.4.3
esbuild: 0.28.0
fix-dts-default-cjs-exports: 1.0.1
joycon: 3.1.1
picocolors: 1.1.1
- postcss-load-config: 6.0.1(postcss@8.5.12)(yaml@2.8.3)
+ postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.12)(yaml@2.8.3)
resolve-from: 5.0.0
rollup: 4.60.2
source-map: 0.7.6
@@ -5550,8 +5579,6 @@ snapshots:
dependencies:
prelude-ls: 1.2.1
- typed-query-selector@2.12.2: {}
-
typedoc-plugin-markdown@4.11.0(typedoc@0.28.19(typescript@5.9.3)):
dependencies:
typedoc: 0.28.19(typescript@5.9.3)
@@ -5565,35 +5592,32 @@ snapshots:
typescript: 5.9.3
yaml: 2.8.3
- typescript-eslint@8.39.1(eslint@9.33.0)(typescript@5.9.3):
+ typescript-eslint@8.39.1(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3):
dependencies:
- '@typescript-eslint/eslint-plugin': 8.39.1(@typescript-eslint/parser@8.39.1(eslint@9.33.0)(typescript@5.9.3))(eslint@9.33.0)(typescript@5.9.3)
- '@typescript-eslint/parser': 8.39.1(eslint@9.33.0)(typescript@5.9.3)
+ '@typescript-eslint/eslint-plugin': 8.39.1(@typescript-eslint/parser@8.39.1(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/parser': 8.39.1(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/typescript-estree': 8.39.1(typescript@5.9.3)
- '@typescript-eslint/utils': 8.39.1(eslint@9.33.0)(typescript@5.9.3)
- eslint: 9.33.0
+ '@typescript-eslint/utils': 8.39.1(eslint@9.33.0(jiti@2.6.1))(typescript@5.9.3)
+ eslint: 9.33.0(jiti@2.6.1)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
+ typescript@5.6.1-rc: {}
+
typescript@5.9.3: {}
uc.micro@2.1.0: {}
ufo@1.6.3: {}
- unbzip2-stream@1.4.3:
- dependencies:
- buffer: 5.7.1
- through: 2.3.8
+ unbash@3.0.0: {}
undici-types@6.21.0: {}
undici-types@7.16.0: {}
- union@0.5.0:
- dependencies:
- qs: 6.15.1
+ unicode-emoji-modifier-base@1.0.0: {}
universalify@0.1.2: {}
@@ -5603,19 +5627,17 @@ snapshots:
dependencies:
punycode: 2.3.1
- url-join@4.0.1: {}
-
url-parse@1.5.10:
dependencies:
querystringify: 2.2.0
requires-port: 1.0.0
- v8-compile-cache-lib@3.0.1: {}
+ validate-npm-package-name@5.0.1: {}
vite-node@3.2.4(@types/node@20.19.39):
dependencies:
cac: 6.7.14
- debug: 4.4.3(supports-color@8.1.1)
+ debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 5.4.21(@types/node@20.19.39)
@@ -5633,7 +5655,7 @@ snapshots:
vite-node@3.2.4(@types/node@24.12.2):
dependencies:
cac: 6.7.14
- debug: 4.4.3(supports-color@8.1.1)
+ debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 5.4.21(@types/node@24.12.2)
@@ -5677,7 +5699,7 @@ snapshots:
'@vitest/spy': 3.2.4
'@vitest/utils': 3.2.4
chai: 5.3.3
- debug: 4.4.3(supports-color@8.1.1)
+ debug: 4.4.3
expect-type: 1.3.0
magic-string: 0.30.21
pathe: 2.0.3
@@ -5716,7 +5738,7 @@ snapshots:
'@vitest/spy': 3.2.4
'@vitest/utils': 3.2.4
chai: 5.3.3
- debug: 4.4.3(supports-color@8.1.1)
+ debug: 4.4.3
expect-type: 1.3.0
magic-string: 0.30.21
pathe: 2.0.3
@@ -5748,14 +5770,12 @@ snapshots:
dependencies:
xml-name-validator: 5.0.0
+ walk-up-path@4.0.0: {}
+
web-streams-polyfill@3.3.3: {}
webidl-conversions@7.0.0: {}
- whatwg-encoding@2.0.0:
- dependencies:
- iconv-lite: 0.6.3
-
whatwg-encoding@3.1.1:
dependencies:
iconv-lite: 0.6.3
@@ -5803,8 +5823,6 @@ snapshots:
word-wrap@1.2.5: {}
- workerpool@6.5.1: {}
-
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
@@ -5817,6 +5835,12 @@ snapshots:
string-width: 5.1.2
strip-ansi: 7.2.0
+ wrap-ansi@9.0.2:
+ dependencies:
+ ansi-styles: 6.2.3
+ string-width: 7.2.0
+ strip-ansi: 7.2.0
+
wrappy@1.0.2: {}
ws@8.20.0: {}
@@ -5833,15 +5857,6 @@ snapshots:
yargs-parser@20.2.9: {}
- yargs-parser@21.1.1: {}
-
- yargs-unparser@2.0.0:
- dependencies:
- camelcase: 6.3.0
- decamelize: 4.0.0
- flat: 5.0.2
- is-plain-obj: 2.1.0
-
yargs@16.2.0:
dependencies:
cliui: 7.0.4
@@ -5852,26 +5867,9 @@ snapshots:
y18n: 5.0.8
yargs-parser: 20.2.9
- yargs@17.7.2:
- dependencies:
- cliui: 8.0.1
- escalade: 3.2.0
- get-caller-file: 2.0.5
- require-directory: 2.1.1
- string-width: 4.2.3
- y18n: 5.0.8
- yargs-parser: 21.1.1
-
- yauzl@2.10.0:
- dependencies:
- buffer-crc32: 0.2.13
- fd-slicer: 1.1.0
-
- yn@3.1.1: {}
-
yocto-queue@0.1.0: {}
- zod@3.23.8: {}
+ zod@4.3.6: {}
zustand@5.0.12(@types/react@18.3.28)(react@18.3.1):
optionalDependencies:
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000..985e31b
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,19 @@
+import { defineConfig } from "vitest/config";
+
+// Vitest 3 modern shape: `test.projects` supersedes the older
+// `defineWorkspace` API. Each entry points to that package's
+// existing vitest config — the per-package config remains the
+// source of truth for environment, includes, coverage thresholds,
+// etc. Running `vitest` from the repo root aggregates them.
+//
+// `crates/web-client` ships its vitest config as `.js` (not `.ts`),
+// so the path reflects that.
+export default defineConfig({
+ test: {
+ projects: [
+ "./packages/react-sdk/vitest.config.ts",
+ "./packages/vite-plugin/vitest.config.ts",
+ "./crates/web-client/vitest.config.js",
+ ],
+ },
+});