diff --git a/.changeset/expo-refresh-db-upgrades.md b/.changeset/expo-refresh-db-upgrades.md new file mode 100644 index 00000000..be6e99e8 --- /dev/null +++ b/.changeset/expo-refresh-db-upgrades.md @@ -0,0 +1,24 @@ +--- +"@party-stack/better-auth": patch +"@party-stack/blobs": patch +"@party-stack/connections": patch +"@party-stack/db-indexeddb-persistence": patch +"@party-stack/durable-object-ontology": patch +"@party-stack/expo-runtime": patch +"@party-stack/foundry-ontology": minor +"@party-stack/node-runtime": patch +"@party-stack/oauth": patch +"@party-stack/ontology": minor +"@party-stack/ontology-devtools": patch +"@party-stack/remote-ontology": patch +"@party-stack/runtime": patch +"@party-stack/salesforce-ontology": patch +"@party-stack/sqlite-ontology": patch +"@party-stack/web-runtime": patch +--- + +Align Expo runtime dependencies on SDK 57 and provide a required Expo Blob/File polyfill. Hash Expo storage names, and verify idempotent cleanup closes SQLite before deletion. + +Expose optional awaitable collection refresh utilities and implement full Foundry snapshot refresh, preserving subscriptions and propagating failures. Use fresh committed snapshots for operation acknowledgement when edit history is unavailable. + +Upgrade TanStack DB and integrations to their latest releases and adapt persistence adapters and coordination to explicit storage targets. diff --git a/.changeset/indexeddb-managed-cache-generations.md b/.changeset/indexeddb-managed-cache-generations.md new file mode 100644 index 00000000..377a28e6 --- /dev/null +++ b/.changeset/indexeddb-managed-cache-generations.md @@ -0,0 +1,18 @@ +--- +"@party-stack/db-indexeddb-persistence": minor +--- + +Implement managed on-demand cache generations with the same claim, rotation, +renewal, expiry, release, and private recovery semantics as upstream SQLite. +Validate claims transactionally on storage operations and collect retired data +only after its last live claim ends. Upgrade IndexedDB storage to version 4 while +preserving existing local-only/eager rows and metadata. + +Derive adapter options from SQLiteCoreAdapterOptions without its driver and add +only databaseName. Remove unused notification callback options while retaining +automatic connection closing for database upgrades. Direct adapter operations +now require explicit eager or managed storage targets, matching upstream. + +Port managed-cache contracts and receiving recovery histories from TanStack DB, +including the complete finite retired-storage matrix, and run them against +SQLite, fake IndexedDB, and native Chromium IndexedDB. diff --git a/apps/issue-tracker/package.json b/apps/issue-tracker/package.json index db22df04..f46dd085 100644 --- a/apps/issue-tracker/package.json +++ b/apps/issue-tracker/package.json @@ -18,8 +18,8 @@ "@party-stack/remote-ontology": "workspace:*", "@party-stack/sqlite-ontology": "workspace:*", "@party-stack/web-runtime": "workspace:*", - "@tanstack/db": "0.12.1", - "@tanstack/react-db": "0.5.5", + "@tanstack/db": "0.14.0", + "@tanstack/react-db": "0.5.9", "@tanstack/react-devtools": "^0.10.9", "@tanstack/react-router": "^1.168.7", "@tanstack/react-start": "^1.167.12", diff --git a/apps/ontology-explorer/package.json b/apps/ontology-explorer/package.json index 9551339c..0c837605 100644 --- a/apps/ontology-explorer/package.json +++ b/apps/ontology-explorer/package.json @@ -7,10 +7,10 @@ "@base-ui/react": "^1.8.0", "@party-stack/foundry-ontology": "workspace:*", "@party-stack/ontology": "workspace:*", - "@tanstack/db": "0.12.1", + "@tanstack/db": "0.14.0", "@tanstack/query-core": "^5.102.8", - "@tanstack/query-db-collection": "1.4.0", - "@tanstack/react-db": "0.5.5", + "@tanstack/query-db-collection": "1.4.4", + "@tanstack/react-db": "0.5.9", "@tanstack/react-router": "^1.168.7", "@tanstack/react-start": "^1.167.12", "@tanstack/react-table": "^9.2.8", diff --git a/apps/remote-notes/package.json b/apps/remote-notes/package.json index 01479d8b..611eb955 100644 --- a/apps/remote-notes/package.json +++ b/apps/remote-notes/package.json @@ -12,10 +12,10 @@ "@party-stack/remote-ontology": "workspace:*", "@party-stack/sqlite-ontology": "workspace:*", "@party-stack/web-runtime": "workspace:*", - "@tanstack/db": "0.12.1", + "@tanstack/db": "0.14.0", "@tanstack/query-core": "^5.102.8", - "@tanstack/query-db-collection": "1.4.0", - "@tanstack/react-db": "0.5.5", + "@tanstack/query-db-collection": "1.4.4", + "@tanstack/react-db": "0.5.9", "@tanstack/react-devtools": "^0.10.9", "@tanstack/react-router": "^1.168.7", "@tanstack/react-start": "^1.167.12", diff --git a/apps/salesforce-task-manager/package.json b/apps/salesforce-task-manager/package.json index a6bd3a50..890cd1bf 100644 --- a/apps/salesforce-task-manager/package.json +++ b/apps/salesforce-task-manager/package.json @@ -16,8 +16,8 @@ "@party-stack/ontology-devtools": "workspace:*", "@party-stack/salesforce-ontology": "workspace:*", "@party-stack/web-runtime": "workspace:*", - "@tanstack/db": "0.12.1", - "@tanstack/react-db": "0.5.5", + "@tanstack/db": "0.14.0", + "@tanstack/react-db": "0.5.9", "@tanstack/react-devtools": "^0.10.9", "@tanstack/react-router": "^1.168.7", "@tanstack/react-start": "^1.167.12", diff --git a/packages/better-auth/package.json b/packages/better-auth/package.json index f8a8f583..c07b2dc1 100644 --- a/packages/better-auth/package.json +++ b/packages/better-auth/package.json @@ -17,13 +17,13 @@ }, "devDependencies": { "@party-stack/build-config": "workspace:*", - "@tanstack/db": "0.12.1", + "@tanstack/db": "0.14.0", "better-auth": "^1.7.1", "typescript": "~7.0.2", "vite-plus": "catalog:" }, "peerDependencies": { - "@tanstack/db": "0.12.1", + "@tanstack/db": "0.14.0", "better-auth": "^1.7.1" } } diff --git a/packages/blobs/package.json b/packages/blobs/package.json index 6a29272a..114abc44 100644 --- a/packages/blobs/package.json +++ b/packages/blobs/package.json @@ -20,11 +20,11 @@ }, "devDependencies": { "@party-stack/build-config": "workspace:*", - "@tanstack/db": "0.12.1", + "@tanstack/db": "0.14.0", "typescript": "~7.0.2", "vite-plus": "catalog:" }, "peerDependencies": { - "@tanstack/db": "0.12.1" + "@tanstack/db": "0.14.0" } } diff --git a/packages/connections/package.json b/packages/connections/package.json index 710b834f..eac2c7bc 100644 --- a/packages/connections/package.json +++ b/packages/connections/package.json @@ -18,11 +18,11 @@ }, "devDependencies": { "@party-stack/build-config": "workspace:*", - "@tanstack/db": "0.12.1", + "@tanstack/db": "0.14.0", "typescript": "~7.0.2", "vite-plus": "catalog:" }, "peerDependencies": { - "@tanstack/db": "0.12.1" + "@tanstack/db": "0.14.0" } } diff --git a/packages/db-indexeddb-persistence/README.md b/packages/db-indexeddb-persistence/README.md index 3e539806..02dd40c2 100644 --- a/packages/db-indexeddb-persistence/README.md +++ b/packages/db-indexeddb-persistence/README.md @@ -8,11 +8,11 @@ for shared options, schema versioning, coordination, and lifecycle behavior. ## Installation ```sh -pnpm add @party-stack/db-indexeddb-persistence @tanstack/db@0.12.1 @tanstack/db-sqlite-persistence-core@0.4.5 +pnpm add @party-stack/db-indexeddb-persistence @tanstack/db@0.14.0 @tanstack/db-sqlite-persistence-core@0.4.9 ``` -Requires IndexedDB. The current release uses persistence core 0.4.5 and expects -TanStack DB 0.12.1. +Requires IndexedDB. The current release uses persistence core 0.4.9 and expects +TanStack DB 0.14.0. ## Persist a Query Collection @@ -23,7 +23,7 @@ restores the persisted data. For this example, also install the Query Collection adapter and TanStack Query: ```sh -pnpm add @tanstack/query-db-collection@1.4.0 @tanstack/query-core@^5.102.8 +pnpm add @tanstack/query-db-collection@1.4.4 @tanstack/query-core@^5.102.8 ``` ```ts @@ -188,3 +188,32 @@ mobile OS process-kill behavior. See [the contract audit and measurements](persistence-contract-audit.md) for coverage and limits. + +## Managed on-demand caches + +Synced collections with `syncMode: "on-demand"` use the same managed cache +protocol as upstream SQLite. A logical collection such as `tasks` maps to a +physical cache generation. Each active sync run claims that generation; recovery +can select fresh storage while delayed work remains bound to the older one. +Valid peers can continue using a retired generation until their claims end. +Retired rows, indexes, metadata, and transaction history are collected when no +live claims remain. The current shared generation remains available without +active claims; an expired claimant recovers privately without displacing it. + +`IndexedDBPersistenceAdapterOptions` extends +`Omit`, adding only `databaseName`. +`createIndexedDBPersistence` also accepts the existing optional `coordinator`. +`cacheGenerationClaimTtlMs` defaults to five minutes and `now` defaults to +`Date.now`, matching SQLite. Upstream's persistence wrapper renews live claims +and handles source restart and recovery. Database upgrades automatically close +older connections when requested by another connection. + +Direct adapter calls require explicit `eagerStorageTarget(collectionId)` or +`managedStorageTarget(storageCollectionId, claimId)` targets, as upstream does. +The wrapper supplies these automatically. An eager target describes direct +storage access; it does not require the collection's sync mode to be eager. + +The database upgrades from version 3 to 4 without clearing existing data. +Managed caches initially use fresh physical IDs rather than trusting legacy +unclaimed storage. Local-only/eager rows, including pending actions, remain +accessible at their original IDs. diff --git a/packages/db-indexeddb-persistence/package.json b/packages/db-indexeddb-persistence/package.json index b753fe42..4be197eb 100644 --- a/packages/db-indexeddb-persistence/package.json +++ b/packages/db-indexeddb-persistence/package.json @@ -12,14 +12,14 @@ } }, "dependencies": { - "@tanstack/db-sqlite-persistence-core": "0.4.5", + "@tanstack/db-sqlite-persistence-core": "0.4.9", "idb": "^8.0.3", "temporal-polyfill": "^0.3.0" }, "devDependencies": { "@party-stack/build-config": "workspace:*", - "@tanstack/db": "0.12.1", - "@tanstack/node-db-sqlite-persistence": "0.2.30", + "@tanstack/db": "0.14.0", + "@tanstack/node-db-sqlite-persistence": "0.2.34", "@types/better-sqlite3": "^7.6.13", "@vitest/browser-playwright": "catalog:", "better-sqlite3": "^12.9.0", @@ -30,6 +30,6 @@ "vitest": "catalog:" }, "peerDependencies": { - "@tanstack/db": "0.12.1" + "@tanstack/db": "0.14.0" } } diff --git a/packages/db-indexeddb-persistence/persistence-contract-audit.md b/packages/db-indexeddb-persistence/persistence-contract-audit.md index b33dc5f0..2a9eaf6f 100644 --- a/packages/db-indexeddb-persistence/persistence-contract-audit.md +++ b/packages/db-indexeddb-persistence/persistence-contract-audit.md @@ -121,3 +121,18 @@ persistence requires a supplied cross-tab coordinator for notifications. Manual storage edits remain unsupported; they bypass notification, index, and replay maintenance. Temporal query literals still have upstream remote-subset wire restrictions, separate from row serialization. + +## Managed cache update — 2026-10-14 + +The adapter now implements upstream core 0.4.9's complete managed cache lifecycle: +claim, rotate, renew, release, clock, transactional claim validation, and retired +storage collection. Shared options derive from the core interface. Targets are +explicit and database version 4 preserves legacy eager/local-only data. + +The additional source pin, ported test inventory, backend-specific adaptations, +and exclusions are documented in [the contract notice](src/contracts/NOTICE.md). +Managed contracts run against SQLite as a control, fake IndexedDB, and native +Chromium IndexedDB. IndexedDB-specific race histories additionally cover separate +connections, revoked/expired claims at registration and access boundaries, and +schema-upgrade preservation. Test-only contract modules are excluded from +published library outputs. diff --git a/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.browser.bench.ts b/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.browser.bench.ts index 3abda690..81231980 100644 --- a/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.browser.bench.ts +++ b/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.browser.bench.ts @@ -11,6 +11,7 @@ import { not, or, } from "@tanstack/db"; +import { eagerStorageTarget } from "@tanstack/db-sqlite-persistence-core"; import { afterAll, describe, test } from "vite-plus/test"; import { IndexedDBPersistenceAdapter } from "./IndexedDBPersistenceAdapter.js"; import type { LoadSubsetOptions } from "@tanstack/db"; @@ -121,12 +122,12 @@ const prefixLike = queryOptions((query) => query.where(({ item }) => like(item.n const adapter = new IndexedDBPersistenceAdapter({ databaseName: `runtime-browser-bench-${crypto.randomUUID()}`, }); -await adapter.applyCommittedTx("items", seedTransaction(buildRows())); +await adapter.applyCommittedTx(eagerStorageTarget("items"), seedTransaction(buildRows())); await Promise.all([ - adapter.ensureIndex("items", "category-index", indexSpec("category")), - adapter.ensureIndex("items", "priority-index", indexSpec("priority")), - adapter.ensureIndex("items", "name-index", indexSpec("name")), - adapter.ensureIndex("items", "status-index", indexSpec("status")), + adapter.ensureIndex(eagerStorageTarget("items"), "category-index", indexSpec("category")), + adapter.ensureIndex(eagerStorageTarget("items"), "priority-index", indexSpec("priority")), + adapter.ensureIndex(eagerStorageTarget("items"), "name-index", indexSpec("name")), + adapter.ensureIndex(eagerStorageTarget("items"), "status-index", indexSpec("status")), ]); const expectedCounts: Array<[string, LoadSubsetOptions, number]> = [ @@ -140,7 +141,7 @@ const expectedCounts: Array<[string, LoadSubsetOptions, number]> = [ ["LIKE", prefixLike, 500], ]; for (const [name, options, expected] of expectedCounts) { - const rows = await adapter.loadSubset("items", options); + const rows = await adapter.loadSubset(eagerStorageTarget("items"), options); if (rows.length !== expected) { throw new Error(`${name} expected ${expected} candidate rows, received ${rows.length}.`); } @@ -149,49 +150,49 @@ for (const [name, options, expected] of expectedCounts) { describe("IndexedDBPersistenceAdapter browser query planning", () => { test("full collection fallback: 10,000 rows loaded", async ({ bench }) => { await bench("full collection fallback: 10,000 rows loaded", async () => { - await adapter.loadSubset("items", unplannableOr); + await adapter.loadSubset(eagerStorageTarget("items"), unplannableOr); }).run(BENCH_OPTIONS); }); test("equality index: 100 entries scanned / rows loaded", async ({ bench }) => { await bench("equality index: 100 entries scanned / rows loaded", async () => { - await adapter.loadSubset("items", categoryEquals); + await adapter.loadSubset(eagerStorageTarget("items"), categoryEquals); }).run(BENCH_OPTIONS); }); test("range index: 1,000 entries scanned / rows loaded", async ({ bench }) => { await bench("range index: 1,000 entries scanned / rows loaded", async () => { - await adapter.loadSubset("items", highPriority); + await adapter.loadSubset(eagerStorageTarget("items"), highPriority); }).run(BENCH_OPTIONS); }); test("indexed AND: 1,100 entries scanned / 10 rows loaded", async ({ bench }) => { await bench("indexed AND: 1,100 entries scanned / 10 rows loaded", async () => { - await adapter.loadSubset("items", indexedAnd); + await adapter.loadSubset(eagerStorageTarget("items"), indexedAnd); }).run(BENCH_OPTIONS); }); test("indexed OR: 1,100 entries scanned / 1,090 rows loaded", async ({ bench }) => { await bench("indexed OR: 1,100 entries scanned / 1,090 rows loaded", async () => { - await adapter.loadSubset("items", indexedOr); + await adapter.loadSubset(eagerStorageTarget("items"), indexedOr); }).run(BENCH_OPTIONS); }); test("AND with residual NOT: 100 entries scanned / rows loaded", async ({ bench }) => { await bench("AND with residual NOT: 100 entries scanned / rows loaded", async () => { - await adapter.loadSubset("items", residualAnd); + await adapter.loadSubset(eagerStorageTarget("items"), residualAnd); }).run(BENCH_OPTIONS); }); test("IN with three values: 300 entries scanned / rows loaded", async ({ bench }) => { await bench("IN with three values: 300 entries scanned / rows loaded", async () => { - await adapter.loadSubset("items", categoryIn); + await adapter.loadSubset(eagerStorageTarget("items"), categoryIn); }).run(BENCH_OPTIONS); }); test("prefix LIKE: 500 entries scanned / rows loaded", async ({ bench }) => { await bench("prefix LIKE: 500 entries scanned / rows loaded", async () => { - await adapter.loadSubset("items", prefixLike); + await adapter.loadSubset(eagerStorageTarget("items"), prefixLike); }).run(BENCH_OPTIONS); }); }); @@ -201,37 +202,37 @@ describe("IndexedDBPersistenceAdapter browser query planning", () => { const fallbackAdapter = new IndexedDBPersistenceAdapter({ databaseName: `bench-fallback-${crypto.randomUUID()}`, }); -await fallbackAdapter.applyCommittedTx("items", seedTransaction(buildRows())); +await fallbackAdapter.applyCommittedTx(eagerStorageTarget("items"), seedTransaction(buildRows())); const page = queryOptions((query) => query.orderBy(({ item }) => item.priority, "asc").limit(10)); const emptyAnd = queryOptions((query) => query.where(({ item }) => and(eq(item.category, "absent"), gte(item.priority, 900))) ); -const indexedPage = await adapter.loadSubset("items", page); -const fallbackPage = await fallbackAdapter.loadSubset("items", page); +const indexedPage = await adapter.loadSubset(eagerStorageTarget("items"), page); +const fallbackPage = await fallbackAdapter.loadSubset(eagerStorageTarget("items"), page); if (indexedPage.length !== 10 || JSON.stringify(indexedPage) !== JSON.stringify(fallbackPage)) throw new Error("Pagination benchmark paths disagree"); let writeVersion = 1; describe("IndexedDB persistence pagination and incremental updates", () => { test("ordered index: first 10 of 10,000 rows", async ({ bench }) => { await bench("ordered index: first 10 of 10,000 rows", async () => { - await adapter.loadSubset("items", page); + await adapter.loadSubset(eagerStorageTarget("items"), page); }).run(BENCH_OPTIONS); }); test("unindexed fallback: same first 10 of 10,000 rows", async ({ bench }) => { await bench("unindexed fallback: same first 10 of 10,000 rows", async () => { - await fallbackAdapter.loadSubset("items", page); + await fallbackAdapter.loadSubset(eagerStorageTarget("items"), page); }).run(BENCH_OPTIONS); }); test("empty AND: absent category intersected with 1,000 priorities", async ({ bench }) => { await bench("empty AND: absent category intersected with 1,000 priorities", async () => { - if ((await adapter.loadSubset("items", emptyAnd)).length !== 0) + if ((await adapter.loadSubset(eagerStorageTarget("items"), emptyAnd)).length !== 0) throw new Error("Empty intersection returned rows"); }).run(BENCH_OPTIONS); }); test("one-row partial update with four maintained indexes", async ({ bench }) => { await bench("one-row partial update with four maintained indexes", async () => { const version = ++writeVersion; - await adapter.applyCommittedTx("items", { + await adapter.applyCommittedTx(eagerStorageTarget("items"), { txId: `update-${version}`, term: 1, seq: version, diff --git a/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.test.ts b/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.test.ts index 99d6883e..e4afab1a 100644 --- a/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.test.ts +++ b/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.test.ts @@ -1,4 +1,3 @@ -import "fake-indexeddb/auto"; import { BTreeIndex, IR, @@ -19,13 +18,19 @@ import { not, or, } from "@tanstack/db"; +import "fake-indexeddb/auto"; +import { eagerStorageTarget } from "@tanstack/db-sqlite-persistence-core"; import { persistedCollectionOptions } from "@tanstack/db-sqlite-persistence-core"; import { openDB } from "idb"; import { Temporal } from "temporal-polyfill"; import { afterAll, describe, expect, it, vi } from "vite-plus/test"; import { IndexedDBPersistenceAdapter } from "./IndexedDBPersistenceAdapter.js"; import type { LoadSubsetOptions } from "@tanstack/db"; -import type { PersistedIndexSpec, PersistedTx } from "@tanstack/db-sqlite-persistence-core"; +import type { + PersistedIndexSpec, + PersistedTx, + PersistedStorageTarget, +} from "@tanstack/db-sqlite-persistence-core"; interface Item { [key: string]: unknown; @@ -107,9 +112,13 @@ function expressionIndexSpec(expression: IR.BasicExpression): PersistedIndexSpec }; } -async function seed(adapter: IndexedDBPersistenceAdapter, items: Item[]): Promise { +async function seed( + adapter: IndexedDBPersistenceAdapter, + items: Item[], + target: PersistedStorageTarget = eagerStorageTarget("items") +): Promise { await adapter.applyCommittedTx( - "items", + target, tx( "seed", 1, @@ -138,11 +147,12 @@ describe("IndexedDBPersistenceAdapter", () => { adapter, values.map((mixed, priority) => ({ id: String(priority), priority, mixed })) ); - if (indexed) await adapter.ensureIndex("items", "bigint", indexSpec(["mixed"])); + if (indexed) + await adapter.ensureIndex(eagerStorageTarget("items"), "bigint", indexSpec(["mixed"])); expect( ids( await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => eq(item.mixed, 10n)).limit(100)) ) ) @@ -150,7 +160,7 @@ describe("IndexedDBPersistenceAdapter", () => { expect( ids( await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => gt(item.mixed, 2n)).limit(100)) ) ) @@ -158,7 +168,7 @@ describe("IndexedDBPersistenceAdapter", () => { expect( ( await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query .orderBy(({ item }) => item.mixed, "asc") @@ -171,7 +181,7 @@ describe("IndexedDBPersistenceAdapter", () => { expect( ( await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.orderBy(({ item }) => item.mixed, "desc").limit(3)) ) ).map((row) => row.value.mixed) @@ -182,7 +192,9 @@ describe("IndexedDBPersistenceAdapter", () => { const reopened = new IndexedDBPersistenceAdapter({ databaseName: name }); try { expect( - (await reopened.loadResumeSnapshot("items")).rows.map((row) => row.value.mixed) + (await reopened.loadResumeSnapshot(eagerStorageTarget("items"))).rows.map( + (row) => row.value.mixed + ) ).toEqual(values); } finally { reopened.close(); @@ -226,7 +238,7 @@ describe("IndexedDBPersistenceAdapter", () => { adapter, values.map((mixed, priority) => ({ id: String(priority).padStart(2, "0"), priority, mixed })) ); - await adapter.ensureIndex("items", "bigint", indexSpec(["mixed"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "bigint", indexSpec(["mixed"])); for (const threshold of [-1000n, -10n, -1n, 0n, 1n, 10n, 1000n, 10n ** 100n]) { for (const operator of ["eq", "gt", "gte", "lt", "lte"] as const) { const matches = (value: bigint) => @@ -239,7 +251,7 @@ describe("IndexedDBPersistenceAdapter", () => { : operator === "lt" ? value < threshold : value <= threshold; - const rows = await adapter.loadSubset("items", { + const rows = await adapter.loadSubset(eagerStorageTarget("items"), { where: new IR.Func(operator, [ new IR.PropRef(["mixed"]), new IR.Value(threshold), @@ -261,7 +273,7 @@ describe("IndexedDBPersistenceAdapter", () => { for (const direction of ["asc", "desc"] as const) { get.mockClear(); const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query .orderBy(({ item }) => item.mixed, direction) @@ -278,7 +290,7 @@ describe("IndexedDBPersistenceAdapter", () => { } get.mockClear(); const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query .where(({ item }) => gt(item.mixed, 9n)) @@ -312,11 +324,11 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "b", priority: 3, mixed: 0n, status: "open" }, { id: "c", priority: 4, mixed: 10n, status: "open" }, ]); - await adapter.ensureIndex("items", "bigint", indexSpec(["mixed"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "bigint", indexSpec(["mixed"])); expect( ( await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query .where(({ item }) => eq(item.status, "open")) @@ -330,7 +342,7 @@ describe("IndexedDBPersistenceAdapter", () => { expect( ( await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query .orderBy(({ item }) => item.mixed, "desc") @@ -341,7 +353,7 @@ describe("IndexedDBPersistenceAdapter", () => { ).map((row) => row.key) ).toEqual(["a"]); const orderBy = queryOptions((query) => query.orderBy(({ item }) => item.mixed, "asc")).orderBy; - const rows = await adapter.loadSubset("items", { + const rows = await adapter.loadSubset(eagerStorageTarget("items"), { orderBy, limit: 1, cursor: { @@ -365,7 +377,7 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "negative", priority: 1, mixed: -10n }, { id: "positive", priority: 2, mixed: 2n }, ]); - await adapter.ensureIndex("items", "bigint", indexSpec(["mixed"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "bigint", indexSpec(["mixed"])); const database = await openDB(name); const definition = (await database.get("indexDefinitions", ["items", "bigint"])) as Record< string, @@ -384,11 +396,13 @@ describe("IndexedDBPersistenceAdapter", () => { } database.close(); const page = queryOptions((query) => query.orderBy(({ item }) => item.mixed, "asc").limit(1)); - expect((await adapter.loadSubset("items", page)).map((row) => row.key)).toEqual(["negative"]); + expect( + (await adapter.loadSubset(eagerStorageTarget("items"), page)).map((row) => row.key) + ).toEqual(["negative"]); expect( ( await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => gt(item.mixed, 0n)).limit(1)) ) ).map((row) => row.key) @@ -396,10 +410,14 @@ describe("IndexedDBPersistenceAdapter", () => { const put = vi.spyOn(IDBObjectStore.prototype, "put"); try { if (trigger === "ensureIndex") - await adapter.ensureIndex("items", "bigint", indexSpec(["mixed"])); + await adapter.ensureIndex( + eagerStorageTarget("items"), + "bigint", + indexSpec(["mixed"]) + ); else await adapter.applyCommittedTx( - "items", + eagerStorageTarget("items"), tx("change", 2, [ { type: "update", @@ -412,7 +430,7 @@ describe("IndexedDBPersistenceAdapter", () => { put.mock.contexts.filter((store) => (store as IDBObjectStore).name === "indexEntries") ).toHaveLength(2); put.mockClear(); - await adapter.ensureIndex("items", "bigint", indexSpec(["mixed"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "bigint", indexSpec(["mixed"])); expect(put).not.toHaveBeenCalled(); } finally { put.mockRestore(); @@ -420,7 +438,7 @@ describe("IndexedDBPersistenceAdapter", () => { expect( ( await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => gt(item.mixed, 0n))) ) ).map((row) => row.key) @@ -436,7 +454,7 @@ describe("IndexedDBPersistenceAdapter", () => { const adapter = new IndexedDBPersistenceAdapter({ databaseName: name }); try { await seed(adapter, [{ id: "one", priority: 1, mixed: 1 }]); - await adapter.ensureIndex("items", "mixed", indexSpec(["mixed"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "mixed", indexSpec(["mixed"])); const database = await openDB(name); const definition = (await database.get("indexDefinitions", ["items", "mixed"])) as Record< string, @@ -447,10 +465,10 @@ describe("IndexedDBPersistenceAdapter", () => { const put = vi.spyOn(IDBObjectStore.prototype, "put"); const getAll = vi.spyOn(IDBIndex.prototype, "getAll"); try { - await adapter.ensureIndex("items", "mixed", indexSpec(["mixed"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "mixed", indexSpec(["mixed"])); expect(put).not.toHaveBeenCalled(); await adapter.applyCommittedTx( - "items", + eagerStorageTarget("items"), tx("bigint", 2, [ { type: "update", key: "one", value: { id: "one", priority: 1, mixed: 10n } }, ]) @@ -459,12 +477,12 @@ describe("IndexedDBPersistenceAdapter", () => { getAll.mock.contexts.some((index) => (index as IDBIndex).objectStore.name === "rows") ).toBe(false); put.mockClear(); - await adapter.ensureIndex("items", "mixed", indexSpec(["mixed"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "mixed", indexSpec(["mixed"])); expect(put).not.toHaveBeenCalled(); expect( ( await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.orderBy(({ item }) => item.mixed, "asc").limit(1)) ) ).map((row) => row.value.mixed) @@ -501,7 +519,7 @@ describe("IndexedDBPersistenceAdapter", () => { cycle, }; try { - await adapter.applyCommittedTx("items", { + await adapter.applyCommittedTx(eagerStorageTarget("items"), { ...tx("values", 1, [ { type: "insert", @@ -539,11 +557,11 @@ describe("IndexedDBPersistenceAdapter", () => { expect(restored.cycle.instant).toBeInstanceOf(Temporal.Instant); }; try { - const snapshot = await reopened.loadResumeSnapshot("items"); + const snapshot = await reopened.loadResumeSnapshot(eagerStorageTarget("items")); check(snapshot.rows[0]?.value.mixed); check(snapshot.rows[0]?.metadata); check(snapshot.collectionMetadata[0]?.value); - const replay = await reopened.pullSince("items", 0); + const replay = await reopened.pullSince(eagerStorageTarget("items"), 0); expect(replay.requiresFullReload).toBe(false); if (!replay.requiresFullReload) { check(replay.deltas?.[0]?.changedRows[0]?.value.mixed); @@ -565,8 +583,8 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "one", priority: 1, mixed: Temporal.PlainDateTime.from("2026-10-07T12:00:00") }, ]) ).rejects.toThrow("Unsupported Temporal.PlainDateTime"); - expect((await adapter.loadResumeSnapshot("items")).rows).toEqual([]); - expect((await adapter.loadResumeSnapshot("items")).latestRowVersion).toBe(0); + expect((await adapter.loadResumeSnapshot(eagerStorageTarget("items"))).rows).toEqual([]); + expect((await adapter.loadResumeSnapshot(eagerStorageTarget("items"))).latestRowVersion).toBe(0); } finally { adapter.close(); } @@ -576,7 +594,7 @@ describe("IndexedDBPersistenceAdapter", () => { const name = databaseName(); const adapter = new IndexedDBPersistenceAdapter({ databaseName: name }); await seed(adapter, [{ id: "one", priority: 1, status: "open" }]); - await adapter.ensureIndex("items", "status", indexSpec(["status"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "status", indexSpec(["status"])); const database = await openDB(name); const legacy = (await database.get("indexDefinitions", ["items", "status"])) as { valueTypeCounts?: unknown; @@ -591,23 +609,23 @@ describe("IndexedDBPersistenceAdapter", () => { expect( ids( await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => eq(item.status, "missing"))) ) ) ).toEqual(["one"]); const put = vi.spyOn(IDBObjectStore.prototype, "put"); try { - await adapter.ensureIndex("items", "status", indexSpec(["status"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "status", indexSpec(["status"])); expect( put.mock.contexts.filter((store) => (store as IDBObjectStore).name === "indexEntries") ).toHaveLength(2); put.mockClear(); - await adapter.ensureIndex("items", "status", indexSpec(["status"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "status", indexSpec(["status"])); expect(put).not.toHaveBeenCalled(); expect( await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => eq(item.status, "missing"))) ) ).toEqual([]); @@ -620,7 +638,7 @@ describe("IndexedDBPersistenceAdapter", () => { it("rolls back row writes when index maintenance fails", async () => { const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); await seed(adapter, [{ id: "one", priority: 1 }]); - await adapter.ensureIndex("items", "priority", indexSpec(["priority"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "priority", indexSpec(["priority"])); // oxlint-disable-next-line typescript/unbound-method -- Rebound to the actual store with call below. const originalPut = IDBObjectStore.prototype.put; const put = vi.spyOn(IDBObjectStore.prototype, "put").mockImplementation(function ( @@ -634,7 +652,7 @@ describe("IndexedDBPersistenceAdapter", () => { try { await expect( adapter.applyCommittedTx( - "items", + eagerStorageTarget("items"), tx("failed", 2, [{ type: "update", key: "one", value: { id: "one", priority: 2 } }]) ) ).rejects.toThrow("Index write failed"); @@ -642,10 +660,10 @@ describe("IndexedDBPersistenceAdapter", () => { put.mockRestore(); } try { - expect((await adapter.loadResumeSnapshot("items")).latestRowVersion).toBe(1); + expect((await adapter.loadResumeSnapshot(eagerStorageTarget("items"))).latestRowVersion).toBe(1); expect( await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => eq(item.priority, 1))) ) ).toMatchObject([{ key: "one", value: { priority: 1 } }]); @@ -660,17 +678,17 @@ describe("IndexedDBPersistenceAdapter", () => { adapter, Array.from({ length: 50 }, (_, priority) => ({ id: String(priority), priority, status: "open" })) ); - await adapter.ensureIndex("items", "status-index", indexSpec(["status"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "status-index", indexSpec(["status"])); const put = vi.spyOn(IDBObjectStore.prototype, "put"); const remove = vi.spyOn(IDBObjectStore.prototype, "delete"); const scan = vi.spyOn(IDBIndex.prototype, "getAll"); try { - await adapter.ensureIndex("items", "status-index", indexSpec(["status"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "status-index", indexSpec(["status"])); expect(put).not.toHaveBeenCalled(); expect(remove).not.toHaveBeenCalled(); expect(scan).not.toHaveBeenCalled(); await adapter.applyCommittedTx( - "items", + eagerStorageTarget("items"), tx("edit", 2, [ { type: "update", key: "0", value: { id: "0", priority: 0, status: "closed" } }, ]) @@ -693,7 +711,7 @@ describe("IndexedDBPersistenceAdapter", () => { put.mockClear(); remove.mockClear(); scan.mockClear(); - await adapter.applyCommittedTx("items", { + await adapter.applyCommittedTx(eagerStorageTarget("items"), { ...tx("metadata", 3, []), collectionMetadataMutations: [{ type: "set", key: "cursor", value: "next" }], }); @@ -719,9 +737,9 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "string", priority: 0, mixed: "text" }, { id: "object", priority: 0, mixed: {} }, ]); - await adapter.ensureIndex("items", "mixed", indexSpec(["mixed"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "mixed", indexSpec(["mixed"])); await adapter.applyCommittedTx( - "items", + eagerStorageTarget("items"), tx("remove-mixed", 2, [ { type: "delete", key: "string", value: { id: "string", priority: 0 } }, { type: "delete", key: "object", value: { id: "object", priority: 0 } }, @@ -730,20 +748,23 @@ describe("IndexedDBPersistenceAdapter", () => { expect( ids( await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => gt(item.mixed, 1))) ) ) ).toEqual(["two"]); - await adapter.applyCommittedTx("items", { ...tx("reset", 3, []), truncate: true }); + await adapter.applyCommittedTx(eagerStorageTarget("items"), { + ...tx("reset", 3, []), + truncate: true, + }); await adapter.applyCommittedTx( - "items", + eagerStorageTarget("items"), tx("reseed", 4, [{ type: "insert", key: "new", value: { id: "new", priority: 3, mixed: 3 } }]) ); expect( ids( await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => gt(item.mixed, 1))) ) ) @@ -761,11 +782,11 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "early", priority: 1, instant }, { id: "later", priority: 2, instant: instant.add({ nanoseconds: 1 }) }, ]); - await adapter.ensureIndex("items", "instant", indexSpec(["instant"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "instant", indexSpec(["instant"])); expect( ids( await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => gt(item.instant, instant))) ) ) @@ -784,11 +805,11 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "later", priority: 2, date: Temporal.PlainDate.from("-000001-01-01") }, { id: "future", priority: 3, date: Temporal.PlainDate.from("+010000-01-01") }, ]); - await adapter.ensureIndex("items", "date", indexSpec(["date"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "date", indexSpec(["date"])); expect( ids( await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => gt(item.date, date))) ) ) @@ -802,13 +823,13 @@ describe("IndexedDBPersistenceAdapter", () => { const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); const instant = Temporal.Instant.from("2026-10-05T12:00:00Z"); try { - await adapter.applyCommittedTx("items", { + await adapter.applyCommittedTx(eagerStorageTarget("items"), { ...tx("snapshot", 7, [ { type: "insert", key: "one", value: { id: "one", priority: 1, instant } }, ]), collectionMetadataMutations: [{ type: "set", key: "cursor", value: instant }], }); - const snapshot = await adapter.loadResumeSnapshot("items"); + const snapshot = await adapter.loadResumeSnapshot(eagerStorageTarget("items")); expect(snapshot).toMatchObject({ rows: [{ key: "one", value: { id: "one", priority: 1, instant } }], collectionMetadata: [{ key: "cursor", value: instant }], @@ -817,11 +838,13 @@ describe("IndexedDBPersistenceAdapter", () => { latestRowVersion: 7, resetEpoch: 0, }); - expect(await adapter.loadResumeSnapshot("items", { includeRows: false })).toEqual({ + expect( + await adapter.loadResumeSnapshot(eagerStorageTarget("items"), { includeRows: false }) + ).toEqual({ ...snapshot, rows: [], }); - expect(await adapter.loadResumeSnapshot("missing")).toEqual({ + expect(await adapter.loadResumeSnapshot(eagerStorageTarget("missing"))).toEqual({ rows: [], collectionMetadata: [], latestTerm: 0, @@ -834,13 +857,19 @@ describe("IndexedDBPersistenceAdapter", () => { } }); - it("makes a second ordered persisted query ready synchronously without rereading storage", async () => { + it("reuses an ordered persisted query without rereading storage", async () => { const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); - await seed(adapter, [ - { id: "three", priority: 3 }, - { id: "one", priority: 1 }, - { id: "two", priority: 2 }, - ]); + const claim = await adapter.claimCacheGeneration("items"); + await seed( + adapter, + [ + { id: "three", priority: 3 }, + { id: "one", priority: 1 }, + { id: "two", priority: 2 }, + ], + { kind: "managed", storageCollectionId: claim.storageCollectionId, claimId: claim.claimId } + ); + await adapter.releaseCacheGenerationClaim(claim.claimId); const loadSubset = vi.spyOn(adapter, "loadSubset"); const items = createCollection( persistedCollectionOptions({ @@ -874,6 +903,8 @@ describe("IndexedDBPersistenceAdapter", () => { expect([...first.values()].map((item) => item.id)).toEqual(["one", "two"]); const reads = loadSubset.mock.calls.length; second = createWindow(); + // Upstream now admits cached publications asynchronously. + await second.preload(); expect(second.status).toBe("ready"); expect([...second.values()].map((item) => item.id)).toEqual(["one", "two"]); expect(loadSubset).toHaveBeenCalledTimes(reads); @@ -986,7 +1017,7 @@ describe("IndexedDBPersistenceAdapter", () => { const second = new IndexedDBPersistenceAdapter({ databaseName: name, }); - const [row] = await second.loadSubset("items", {}); + const [row] = await second.loadSubset(eagerStorageTarget("items"), {}); expect(row?.value.date).toBeInstanceOf(Temporal.PlainDate); expect(String(row?.value.date)).toBe("2026-07-21"); @@ -1013,17 +1044,17 @@ describe("IndexedDBPersistenceAdapter", () => { instant: Temporal.Instant.from("2026-08-01T00:00:00Z"), }, ]); - await adapter.ensureIndex("items", "date-index", indexSpec(["date"])); - await adapter.ensureIndex("items", "instant-index", indexSpec(["instant"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "date-index", indexSpec(["date"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "instant-index", indexSpec(["instant"])); const dates = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => gt(item.date, Temporal.PlainDate.from("2026-06-01"))) ) ); const instants = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => gt(item.instant, Temporal.Instant.from("2026-06-01T00:00:00Z"))) ) @@ -1044,7 +1075,7 @@ describe("IndexedDBPersistenceAdapter", () => { ]); const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => eq(item.status, "open"))) ); @@ -1061,10 +1092,10 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "open-2", status: "open", priority: 2 }, { id: "closed", status: "closed", priority: 3 }, ]); - await adapter.ensureIndex("items", "status-index", indexSpec(["status"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "status-index", indexSpec(["status"])); const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => eq(item.status, "open"))) ); @@ -1081,10 +1112,10 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "middle", priority: 5 }, { id: "high", priority: 10 }, ]); - await adapter.ensureIndex("items", "priority-index", indexSpec(["priority"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "priority-index", indexSpec(["priority"])); const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => gt(item.priority, 4))) ); @@ -1101,11 +1132,11 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "open-high", status: "open", priority: 10 }, { id: "closed-high", status: "closed", priority: 10 }, ]); - await adapter.ensureIndex("items", "status-index", indexSpec(["status"])); - await adapter.ensureIndex("items", "priority-index", indexSpec(["priority"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "status-index", indexSpec(["status"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "priority-index", indexSpec(["priority"])); const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => and(eq(item.status, "open"), gt(item.priority, 4))) ) @@ -1124,10 +1155,10 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "middle", priority: 5 }, { id: "high", priority: 10 }, ]); - await adapter.ensureIndex("items", "priority-index", indexSpec(["priority"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "priority-index", indexSpec(["priority"])); const cursorWhere = queryOptions((query) => query.where(({ item }) => gt(item.priority, 4))).where!; - const rows = await adapter.loadSubset("items", { + const rows = await adapter.loadSubset(eagerStorageTarget("items"), { cursor: { whereFrom: cursorWhere, whereCurrent: queryOptions((query) => query.where(({ item }) => eq(item.priority, 4))).where!, @@ -1147,12 +1178,12 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "open-high", status: "open", priority: 10 }, { id: "closed-high", status: "closed", priority: 10 }, ]); - await adapter.ensureIndex("items", "status-index", indexSpec(["status"])); - await adapter.ensureIndex("items", "priority-index", indexSpec(["priority"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "status-index", indexSpec(["status"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "priority-index", indexSpec(["priority"])); const options = queryOptions((query) => query.where(({ item }) => eq(item.status, "open"))); const cursorWhere = queryOptions((query) => query.where(({ item }) => gt(item.priority, 4))).where!; - const rows = await adapter.loadSubset("items", { + const rows = await adapter.loadSubset(eagerStorageTarget("items"), { ...options, cursor: { whereFrom: cursorWhere, @@ -1173,11 +1204,11 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "open-high", status: "open", priority: 10 }, { id: "closed-high", status: "closed", priority: 10 }, ]); - await adapter.ensureIndex("items", "status-index", indexSpec(["status"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "status-index", indexSpec(["status"])); const options = queryOptions((query) => query.where(({ item }) => eq(item.status, "open"))); const cursorWhere = queryOptions((query) => query.where(({ item }) => gt(item.priority, 4))).where!; - const rows = await adapter.loadSubset("items", { + const rows = await adapter.loadSubset(eagerStorageTarget("items"), { ...options, cursor: { whereFrom: cursorWhere, @@ -1198,10 +1229,10 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "pending", status: "pending", priority: 2 }, { id: "closed", status: "closed", priority: 3 }, ]); - await adapter.ensureIndex("items", "status-index", indexSpec(["status"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "status-index", indexSpec(["status"])); const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => inArray(item.status, ["open", "pending"]))) ); @@ -1218,10 +1249,10 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "help", name: "helper", priority: 2 }, { id: "world", name: "world hello", priority: 3 }, ]); - await adapter.ensureIndex("items", "name-index", indexSpec(["name"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "name-index", indexSpec(["name"])); const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => like(item.name, "hel_o%"))) ); @@ -1238,10 +1269,10 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "helium", name: "hELium", priority: 2 }, { id: "world", name: "world", priority: 3 }, ]); - await adapter.ensureIndex("items", "name-index", indexSpec(["name"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "name-index", indexSpec(["name"])); const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => ilike(item.name, "HEL%"))) ); @@ -1258,14 +1289,14 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "missing", priority: 2 }, { id: "value", nullable: "value", priority: 3 }, ]); - await adapter.ensureIndex("items", "nullable-index", indexSpec(["nullable"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "nullable-index", indexSpec(["nullable"])); const nullRows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => isNull(item.nullable))) ); const undefinedRows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => isUndefined(item.nullable))) ); @@ -1283,11 +1314,11 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "open-high", status: "open", priority: 10 }, { id: "closed", status: "closed", priority: 2 }, ]); - await adapter.ensureIndex("items", "status-index", indexSpec(["status"])); - await adapter.ensureIndex("items", "priority-index", indexSpec(["priority"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "status-index", indexSpec(["status"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "priority-index", indexSpec(["priority"])); const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => or(eq(item.status, "closed"), gt(item.priority, 8))) ) @@ -1305,10 +1336,10 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "open", status: "open", priority: 1 }, { id: "closed", status: "closed", priority: 2 }, ]); - await adapter.ensureIndex("items", "status-index", indexSpec(["status"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "status-index", indexSpec(["status"])); const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => not(eq(item.status, "open")))) ); @@ -1325,13 +1356,13 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "world", name: "WORLD", priority: 2 }, ]); await adapter.ensureIndex( - "items", + eagerStorageTarget("items"), "lower-name-index", expressionIndexSpec(new IR.Func("lower", [new IR.PropRef(["name"])])) ); const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => eq(lower(item.name), "hello"))) ); @@ -1347,14 +1378,14 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "number", mixed: 1, priority: 1 }, { id: "string", mixed: "1", priority: 2 }, ]); - await adapter.ensureIndex("items", "mixed-index", indexSpec(["mixed"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "mixed-index", indexSpec(["mixed"])); const numbers = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => eq(item.mixed, 1))) ); const strings = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => eq(item.mixed, "1"))) ); @@ -1373,22 +1404,22 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "bigint", mixed: 1n, priority: 3 }, { id: "string", mixed: "1", priority: 4 }, ]); - await adapter.ensureIndex("items", "mixed-index", indexSpec(["mixed"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "mixed-index", indexSpec(["mixed"])); const booleans = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => eq(item.mixed, true))) ); const numbers = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => eq(item.mixed, 1))) ); const bigints = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => eq(item.mixed, 1n))) ); const strings = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => eq(item.mixed, "1"))) ); @@ -1407,10 +1438,10 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "number", mixed: 10, priority: 1 }, { id: "string", mixed: "10", priority: 2 }, ]); - await adapter.ensureIndex("items", "mixed-index", indexSpec(["mixed"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "mixed-index", indexSpec(["mixed"])); const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => gt(item.mixed, 4))) ); @@ -1426,9 +1457,9 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "one", status: "open", priority: 1 }, { id: "two", status: "closed", priority: 2 }, ]); - await adapter.ensureIndex("items", "status-index", indexSpec(["status"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "status-index", indexSpec(["status"])); await adapter.applyCommittedTx( - "items", + eagerStorageTarget("items"), tx("update", 2, [ { type: "update", @@ -1439,7 +1470,7 @@ describe("IndexedDBPersistenceAdapter", () => { ); const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => eq(item.status, "open"))) ); @@ -1455,11 +1486,11 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "open", status: "open", priority: 1 }, { id: "closed", status: "closed", priority: 2 }, ]); - await adapter.ensureIndex("items", "status-index", indexSpec(["status"])); - await adapter.markIndexRemoved("items", "status-index"); + await adapter.ensureIndex(eagerStorageTarget("items"), "status-index", indexSpec(["status"])); + await adapter.markIndexRemoved(eagerStorageTarget("items"), "status-index"); const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => eq(item.status, "open"))) ); @@ -1479,11 +1510,11 @@ describe("IndexedDBPersistenceAdapter", () => { }, ]); - await adapter.applyCommittedTx("items", committed); - await adapter.applyCommittedTx("items", committed); + await adapter.applyCommittedTx(eagerStorageTarget("items"), committed); + await adapter.applyCommittedTx(eagerStorageTarget("items"), committed); - expect(ids(await adapter.loadSubset("items", {}))).toEqual(["one"]); - await expect(adapter.getStreamPosition("items")).resolves.toMatchObject({ + expect(ids(await adapter.loadSubset(eagerStorageTarget("items"), {}))).toEqual(["one"]); + await expect(adapter.getStreamPosition(eagerStorageTarget("items"))).resolves.toMatchObject({ latestTerm: 1, latestSeq: 1, latestRowVersion: 1, @@ -1500,10 +1531,10 @@ describe("IndexedDBPersistenceAdapter", () => { { id: "open", status: "open", priority: 1 }, { id: "closed", status: "closed", priority: 2 }, ]); - await adapter.ensureIndex("items", "status-index", indexSpec(["status"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "status-index", indexSpec(["status"])); const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => gt(item.status, "a"))) ); @@ -1515,13 +1546,14 @@ describe("IndexedDBPersistenceAdapter", () => { describe("IndexedDB persistence lifecycle and paging", () => { it("clears collection initialization state on close and cannot repopulate it while opening", async () => { const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); - for (let index = 0; index < 20; index++) await adapter.loadResumeSnapshot(`dynamic-${index}`); + for (let index = 0; index < 20; index++) + await adapter.loadResumeSnapshot(eagerStorageTarget(`dynamic-${index}`)); const initialized = Reflect.get(adapter, "initialized") as Map; expect(initialized.size).toBe(20); adapter.close(); expect(initialized.size).toBe(0); const opening = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); - const load = opening.loadResumeSnapshot("pending"); + const load = opening.loadResumeSnapshot(eagerStorageTarget("pending")); opening.close(); await expect(load).rejects.toThrow("closed"); expect((Reflect.get(opening, "initialized") as Map).size).toBe(0); @@ -1531,16 +1563,18 @@ describe("IndexedDB persistence lifecycle and paging", () => { const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); try { await adapter.applyCommittedTx( - "saved", + eagerStorageTarget("saved"), tx("seed", 1, [{ type: "insert", key: "one", value: { id: "one", priority: 1 } }]) ); await Promise.all( - Array.from({ length: 160 }, (_, index) => adapter.loadResumeSnapshot(`dynamic-${index}`)) + Array.from({ length: 160 }, (_, index) => + adapter.loadResumeSnapshot(eagerStorageTarget(`dynamic-${index}`)) + ) ); expect((Reflect.get(adapter, "initialized") as Map).size).toBeLessThanOrEqual( 128 ); - expect((await adapter.loadResumeSnapshot("saved")).rows).toHaveLength(1); + expect((await adapter.loadResumeSnapshot(eagerStorageTarget("saved"))).rows).toHaveLength(1); } finally { adapter.close(); } @@ -1555,8 +1589,10 @@ describe("IndexedDB persistence lifecycle and paging", () => { schemaVersion: 2, schemaMismatchPolicy: "sync-absent-error", }); - await expect(next.loadResumeSnapshot("items")).rejects.toThrow("Local-only data was preserved"); - expect(ids(await original.loadSubset("items", {}))).toEqual(["saved"]); + await expect(next.loadResumeSnapshot(eagerStorageTarget("items"))).rejects.toThrow( + "Local-only data was preserved" + ); + expect(ids(await original.loadSubset(eagerStorageTarget("items"), {}))).toEqual(["saved"]); next.close(); original.close(); }); @@ -1564,17 +1600,17 @@ describe("IndexedDB persistence lifecycle and paging", () => { it("resets synced schemas atomically and fences old adapters", async () => { const name = databaseName(); const original = new IndexedDBPersistenceAdapter({ databaseName: name }); - await original.applyCommittedTx("items", { + await original.applyCommittedTx(eagerStorageTarget("items"), { ...tx("seed", 1, [{ type: "insert", key: "old", value: { id: "old", priority: 1 } }]), collectionMetadataMutations: [{ type: "set", key: "cursor", value: "old" }], }); - await original.ensureIndex("items", "priority-index", indexSpec(["priority"])); + await original.ensureIndex(eagerStorageTarget("items"), "priority-index", indexSpec(["priority"])); const next = new IndexedDBPersistenceAdapter({ databaseName: name, schemaMismatchPolicy: "sync-present-reset", schemaVersion: 2, }); - const snapshot = await next.loadResumeSnapshot("items"); + const snapshot = await next.loadResumeSnapshot(eagerStorageTarget("items")); expect(snapshot).toMatchObject({ rows: [], collectionMetadata: [], @@ -1583,17 +1619,21 @@ describe("IndexedDB persistence lifecycle and paging", () => { }); await expect( original.applyCommittedTx( - "items", + eagerStorageTarget("items"), tx("stale", 3, [{ type: "insert", key: "stale", value: { id: "stale", priority: 3 } }]) ) ).rejects.toThrow("stale IndexedDB adapter"); - await expect(original.loadSubset("items", {})).rejects.toThrow("stale IndexedDB adapter"); + await expect(original.loadSubset(eagerStorageTarget("items"), {})).rejects.toThrow( + "stale IndexedDB adapter" + ); await expect( - original.ensureIndex("items", "priority-index", indexSpec(["priority"])) + original.ensureIndex(eagerStorageTarget("items"), "priority-index", indexSpec(["priority"])) ).rejects.toThrow("stale IndexedDB adapter"); const reopenedOld = new IndexedDBPersistenceAdapter({ databaseName: name }); - await expect(reopenedOld.loadSubset("items", {})).rejects.toThrow("refusing to downgrade"); - expect(await next.pullSince("items", 1)).toMatchObject({ + await expect(reopenedOld.loadSubset(eagerStorageTarget("items"), {})).rejects.toThrow( + "refusing to downgrade" + ); + expect(await next.pullSince(eagerStorageTarget("items"), 1)).toMatchObject({ latestRowVersion: 2, requiresFullReload: true, }); @@ -1608,7 +1648,10 @@ describe("IndexedDB persistence lifecycle and paging", () => { schemaVersion: 5, schemaMismatchPolicy: "sync-absent-error", }); - expect(await adapter.loadResumeSnapshot("items")).toMatchObject({ rows: [], resetEpoch: 0 }); + expect(await adapter.loadResumeSnapshot(eagerStorageTarget("items"))).toMatchObject({ + rows: [], + resetEpoch: 0, + }); adapter.close(); }); @@ -1627,9 +1670,9 @@ describe("IndexedDB persistence lifecycle and paging", () => { rowMetadataMutations: [{ type: "set" as const, key: "a", value: { owner: "query" } }], collectionMetadataMutations: [{ type: "set" as const, key: "cursor", value: instant }], }; - await adapter.applyCommittedTx("items", edit); - await adapter.applyCommittedTx("items", edit); - expect(await adapter.pullSince("items", 1)).toMatchObject({ + await adapter.applyCommittedTx(eagerStorageTarget("items"), edit); + await adapter.applyCommittedTx(eagerStorageTarget("items"), edit); + expect(await adapter.pullSince(eagerStorageTarget("items"), 1)).toMatchObject({ latestRowVersion: 2, requiresFullReload: false, changedKeys: ["a"], @@ -1645,7 +1688,7 @@ describe("IndexedDB persistence lifecycle and paging", () => { }, ], }); - expect(await adapter.pullSince("items", 2)).toMatchObject({ + expect(await adapter.pullSince(eagerStorageTarget("items"), 2)).toMatchObject({ latestRowVersion: 2, requiresFullReload: false, changedKeys: [], @@ -1663,29 +1706,32 @@ describe("IndexedDB persistence lifecycle and paging", () => { pullSinceReloadThreshold: 0, }); await seed(adapter, [{ id: "a", priority: 1 }]); - await adapter.applyCommittedTx("items", tx("metadata", 2, [])); - await adapter.applyCommittedTx("items", tx("metadata-2", 3, [])); + await adapter.applyCommittedTx(eagerStorageTarget("items"), tx("metadata", 2, [])); + await adapter.applyCommittedTx(eagerStorageTarget("items"), tx("metadata-2", 3, [])); const database = await openDB(name); expect(await database.countFromIndex("transactions", "collectionId", "items")).toBe(2); - expect(await adapter.pullSince("items", 0)).toMatchObject({ + expect(await adapter.pullSince(eagerStorageTarget("items"), 0)).toMatchObject({ latestRowVersion: 3, requiresFullReload: true, }); - expect(await adapter.pullSince("items", 1)).toMatchObject({ + expect(await adapter.pullSince(eagerStorageTarget("items"), 1)).toMatchObject({ latestRowVersion: 3, requiresFullReload: false, deltas: [{ txId: "metadata" }, { txId: "metadata-2" }], }); - await adapter.applyCommittedTx("items", { ...tx("truncate", 4, []), truncate: true }); - expect(await adapter.pullSince("items", 3)).toMatchObject({ + await adapter.applyCommittedTx(eagerStorageTarget("items"), { + ...tx("truncate", 4, []), + truncate: true, + }); + expect(await adapter.pullSince(eagerStorageTarget("items"), 3)).toMatchObject({ latestRowVersion: 4, requiresFullReload: true, }); await adapter.applyCommittedTx( - "items", + eagerStorageTarget("items"), tx("insert", 5, [{ type: "insert", key: "b", value: { id: "b", priority: 2 } }]) ); - expect(await adapter.pullSince("items", 4)).toMatchObject({ + expect(await adapter.pullSince(eagerStorageTarget("items"), 4)).toMatchObject({ latestRowVersion: 5, requiresFullReload: true, }); @@ -1709,9 +1755,11 @@ describe("IndexedDB persistence lifecycle and paging", () => { delta: unknown; }; await database.put("transactions", { ...record, appliedAt: Date.now() - 11000 }); - await adapter.applyCommittedTx("items", tx("fresh", 2, [])); + await adapter.applyCommittedTx(eagerStorageTarget("items"), tx("fresh", 2, [])); expect(await database.countFromIndex("transactions", "collectionId", "items")).toBe(1); - expect(await adapter.pullSince("items", 0)).toMatchObject({ requiresFullReload: true }); + expect(await adapter.pullSince(eagerStorageTarget("items"), 0)).toMatchObject({ + requiresFullReload: true, + }); database.close(); adapter.close(); const noHistory = new IndexedDBPersistenceAdapter({ @@ -1719,8 +1767,10 @@ describe("IndexedDB persistence lifecycle and paging", () => { appliedTxPruneMaxRows: 0, }); await seed(noHistory, [{ id: "a", priority: 1 }]); - expect(await noHistory.pullSince("items", 0)).toMatchObject({ requiresFullReload: true }); - expect(await noHistory.pullSince("items", 1)).toMatchObject({ + expect(await noHistory.pullSince(eagerStorageTarget("items"), 0)).toMatchObject({ + requiresFullReload: true, + }); + expect(await noHistory.pullSince(eagerStorageTarget("items"), 1)).toMatchObject({ requiresFullReload: false, deltas: [], }); @@ -1729,7 +1779,7 @@ describe("IndexedDB persistence lifecycle and paging", () => { it("selects metadata rows through the metadata index and retains their values", async () => { const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); - await adapter.applyCommittedTx("items", { + await adapter.applyCommittedTx(eagerStorageTarget("items"), { ...tx("seed", 1, [ { type: "insert", @@ -1743,7 +1793,7 @@ describe("IndexedDB persistence lifecycle and paging", () => { }); const getAll = vi.spyOn(IDBIndex.prototype, "getAll"); try { - const rows = await adapter.scanRows("items", { metadataOnly: true }); + const rows = await adapter.scanRows(eagerStorageTarget("items"), { metadataOnly: true }); expect(rows).toEqual([ { key: "owned", value: { id: "owned", priority: 1 }, metadata: { owners: ["query"] } }, ]); @@ -1751,11 +1801,11 @@ describe("IndexedDB persistence lifecycle and paging", () => { } finally { getAll.mockRestore(); } - await adapter.applyCommittedTx("items", { + await adapter.applyCommittedTx(eagerStorageTarget("items"), { ...tx("clear", 2, []), rowMetadataMutations: [{ type: "delete", key: "owned" }], }); - expect(await adapter.scanRows("items", { metadataOnly: true })).toEqual([]); + expect(await adapter.scanRows(eagerStorageTarget("items"), { metadataOnly: true })).toEqual([]); adapter.close(); }); @@ -1769,12 +1819,12 @@ describe("IndexedDB persistence lifecycle and paging", () => { status: index % 2 ? "open" : "closed", })) ); - await adapter.ensureIndex("items", "priority-index", indexSpec(["priority"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "priority-index", indexSpec(["priority"])); const getAll = vi.spyOn(IDBIndex.prototype, "getAll"); const get = vi.spyOn(IDBObjectStore.prototype, "get"); try { const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query .where(({ item }) => eq(item.status, "open")) @@ -1792,7 +1842,7 @@ describe("IndexedDB persistence lifecycle and paging", () => { ).toBeLessThan(15); get.mockClear(); const tail = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query .where(({ item }) => gt(item.priority, 90)) @@ -1818,9 +1868,9 @@ describe("IndexedDB persistence lifecycle and paging", () => { { id: "z", priority: 1, name: "z" }, { id: "a", priority: 2, name: "a" }, ]); - await adapter.ensureIndex("items", "name-index", indexSpec(["name"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "name-index", indexSpec(["name"])); const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query .orderBy(({ item }) => item.name, { @@ -1833,7 +1883,7 @@ describe("IndexedDB persistence lifecycle and paging", () => { ); expect(rows.map((row) => row.key)).toEqual(["z", "a"]); const composite = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query .orderBy(({ item }) => item.priority, "asc") @@ -1853,9 +1903,9 @@ describe("IndexedDB persistence lifecycle and paging", () => { { id: "c", priority: 3 }, { id: "d", priority: 4 }, ]); - await adapter.ensureIndex("items", "priority-index", indexSpec(["priority"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "priority-index", indexSpec(["priority"])); const order = queryOptions((query) => query.orderBy(({ item }) => item.priority, "asc")); - const rows = await adapter.loadSubset("items", { + const rows = await adapter.loadSubset(eagerStorageTarget("items"), { ...order, limit: 1, cursor: { @@ -1914,23 +1964,26 @@ describe("IndexedDB compatibility", () => { await legacy.put("transactions", { id: JSON.stringify(["items", "old"]), collectionId: "items" }); legacy.close(); const adapter = new IndexedDBPersistenceAdapter({ databaseName: name }); - expect(ids(await adapter.loadSubset("items", {}))).toEqual(["owned", "plain"]); - expect(await adapter.scanRows("items", { metadataOnly: true })).toEqual([ + expect(ids(await adapter.loadSubset(eagerStorageTarget("items"), {}))).toEqual(["owned", "plain"]); + expect(await adapter.scanRows(eagerStorageTarget("items"), { metadataOnly: true })).toEqual([ { key: "owned", value: { id: "owned", priority: 1 }, metadata: { owners: ["query"] } }, ]); - expect(await adapter.pullSince("items", 0)).toMatchObject({ requiresFullReload: true }); + expect(await adapter.pullSince(eagerStorageTarget("items"), 0)).toMatchObject({ + requiresFullReload: true, + }); await adapter.applyCommittedTx( - "items", + eagerStorageTarget("items"), tx("old", 1, [{ type: "update", key: "owned", value: { id: "owned", priority: 99 } }]) ); expect( - (await adapter.loadSubset("items", {})).find((row) => row.key === "owned")?.value.priority + (await adapter.loadSubset(eagerStorageTarget("items"), {})).find((row) => row.key === "owned") + ?.value.priority ).toBe(1); await adapter.applyCommittedTx( - "items", + eagerStorageTarget("items"), tx("edit", 2, [{ type: "update", key: "owned", value: { id: "owned", priority: 3 } }]) ); - expect(await adapter.pullSince("items", 1)).toMatchObject({ + expect(await adapter.pullSince(eagerStorageTarget("items"), 1)).toMatchObject({ requiresFullReload: false, changedKeys: ["owned"], }); @@ -1943,14 +1996,14 @@ describe("IndexedDB compatibility", () => { { id: "a", priority: 1, details: { priority: 10 } }, { id: "b", priority: 10, details: { priority: 1 } }, ]); - await adapter.ensureIndex("items", "priority-index", indexSpec(["priority"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "priority-index", indexSpec(["priority"])); const nested = new IR.PropRef(["details", "priority"]); - const rows = await adapter.loadSubset("items", { + const rows = await adapter.loadSubset(eagerStorageTarget("items"), { where: new IR.Func("eq", [nested, new IR.Value(10)]), limit: 1, }); expect(rows.map((row) => row.key)).toEqual(["a"]); - const ordered = await adapter.loadSubset("items", { + const ordered = await adapter.loadSubset(eagerStorageTarget("items"), { orderBy: [ { expression: nested, @@ -1968,19 +2021,19 @@ describe("IndexedDB partial updates", () => { it("merges partial row updates and replays the full stored row", async () => { const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); await seed(adapter, [{ id: "a", priority: 1, name: "Retained" }]); - await adapter.ensureIndex("items", "name-index", indexSpec(["name"])); - await adapter.ensureIndex("items", "priority-index", indexSpec(["priority"])); - await adapter.applyCommittedTx("items", { + await adapter.ensureIndex(eagerStorageTarget("items"), "name-index", indexSpec(["name"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "priority-index", indexSpec(["priority"])); + await adapter.applyCommittedTx(eagerStorageTarget("items"), { ...tx("patch", 2, []), mutations: [{ type: "update", key: "a", value: { priority: 5 } }], }); expect( await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => eq(item.name, "Retained"))) ) ).toEqual([{ key: "a", value: { id: "a", priority: 5, name: "Retained" }, metadata: undefined }]); - expect(await adapter.pullSince("items", 1)).toMatchObject({ + expect(await adapter.pullSince(eagerStorageTarget("items"), 1)).toMatchObject({ requiresFullReload: false, deltas: [{ changedRows: [{ key: "a", value: { id: "a", priority: 5, name: "Retained" } }] }], }); @@ -1990,7 +2043,7 @@ describe("IndexedDB partial updates", () => { it("honors metadataChanged instead of silently replacing unchanged metadata", async () => { const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); await adapter.applyCommittedTx( - "items", + eagerStorageTarget("items"), tx("seed", 1, [ { type: "insert", @@ -2001,14 +2054,14 @@ describe("IndexedDB partial updates", () => { }, ]) ); - await adapter.applyCommittedTx("items", { + await adapter.applyCommittedTx(eagerStorageTarget("items"), { ...tx("patch", 2, []), mutations: [{ type: "update", key: "a", value: { priority: 2 }, metadata: { owner: "ignored" } }], }); - expect(await adapter.scanRows("items", { metadataOnly: true })).toMatchObject([ + expect(await adapter.scanRows(eagerStorageTarget("items"), { metadataOnly: true })).toMatchObject([ { key: "a", metadata: { owner: "saved" } }, ]); - await adapter.applyCommittedTx("items", { + await adapter.applyCommittedTx(eagerStorageTarget("items"), { ...tx("clear", 3, []), mutations: [ { @@ -2020,8 +2073,8 @@ describe("IndexedDB partial updates", () => { }, ], }); - expect(await adapter.scanRows("items", { metadataOnly: true })).toEqual([]); - expect(await adapter.pullSince("items", 2)).toMatchObject({ + expect(await adapter.scanRows(eagerStorageTarget("items"), { metadataOnly: true })).toEqual([]); + expect(await adapter.pullSince(eagerStorageTarget("items"), 2)).toMatchObject({ requiresFullReload: false, deltas: [{ rowMetadataMutations: [{ type: "delete", key: "a" }] }], }); @@ -2040,12 +2093,12 @@ describe("empty indexed intersections", () => { priority: index, })) ); - await adapter.ensureIndex("items", "status-index", indexSpec(["status"])); - await adapter.ensureIndex("items", "priority-index", indexSpec(["priority"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "status-index", indexSpec(["status"])); + await adapter.ensureIndex(eagerStorageTarget("items"), "priority-index", indexSpec(["priority"])); const scan = vi.spyOn(IDBIndex.prototype, "getAll"); try { const rows = await adapter.loadSubset( - "items", + eagerStorageTarget("items"), queryOptions((query) => query.where(({ item }) => and(eq(item.status, "absent"), gte(item.priority, 0))) ) @@ -2068,23 +2121,23 @@ describe("committed stream identity", () => { const adapter = new IndexedDBPersistenceAdapter({ databaseName: databaseName() }); try { await adapter.applyCommittedTx( - "items", + eagerStorageTarget("items"), tx("one", 1, [{ type: "insert", key: "one", value: { id: "one", priority: 1 } }]) ); await adapter.applyCommittedTx( - "items", + eagerStorageTarget("items"), tx("two", 2, [{ type: "insert", key: "two", value: { id: "two", priority: 2 } }]) ); await adapter.applyCommittedTx( - "items", + eagerStorageTarget("items"), tx("retry-new-id", 1, [{ type: "delete", key: "one", value: { id: "one", priority: 0 } }]) ); - expect(ids(await adapter.loadSubset("items", {}))).toEqual(["one", "two"]); - expect(await adapter.getStreamPosition("items")).toMatchObject({ + expect(ids(await adapter.loadSubset(eagerStorageTarget("items"), {}))).toEqual(["one", "two"]); + expect(await adapter.getStreamPosition(eagerStorageTarget("items"))).toMatchObject({ latestRowVersion: 2, latestSeq: 2, }); - const result = await adapter.pullSince("items", 0); + const result = await adapter.pullSince(eagerStorageTarget("items"), 0); expect(result).toMatchObject({ requiresFullReload: false }); if (result.requiresFullReload) throw new Error("Expected incremental replay"); expect(result.deltas).toHaveLength(2); @@ -2142,16 +2195,16 @@ describe("committed stream identity", () => { const adapter = new IndexedDBPersistenceAdapter({ databaseName: name }); try { await adapter.applyCommittedTx( - "items", + eagerStorageTarget("items"), tx("retry-new-id", 1, [{ type: "delete", key: "one", value: { id: "one", priority: 0 } }]) ); - expect(ids(await adapter.loadSubset("items", {}))).toEqual(["one"]); + expect(ids(await adapter.loadSubset(eagerStorageTarget("items"), {}))).toEqual(["one"]); await adapter.applyCommittedTx( - "items", + eagerStorageTarget("items"), tx("two", 2, [{ type: "insert", key: "two", value: { id: "two", priority: 2 } }]) ); const database = await openDB(name); - expect(database.version).toBe(3); + expect(database.version).toBe(4); expect(await database.getKeyFromIndex("transactions", "position", ["items", 1, 2])).toBeDefined(); expect(await database.get("transactions", JSON.stringify(["items", "old"]))).toBeDefined(); database.close(); diff --git a/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.ts b/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.ts index b3ebd34b..9905a739 100644 --- a/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.ts +++ b/packages/db-indexeddb-persistence/src/IndexedDBPersistenceAdapter.ts @@ -1,6 +1,12 @@ import { IR, compileSingleRowExpression, parseWhereExpression } from "@tanstack/db"; import { encodePersistedStorageKey, + DEFAULT_CACHE_GENERATION_CLAIM_TTL_MS, + InvalidPersistedCollectionConfigError, + safeRandomUUID, + type PersistedCacheGenerationClaim, + resolvePersistedStorageTarget, + type PersistedStorageTarget, type PersistedIndexSpec, type PersistedRowScanOptions, type PersistencePullSinceResult, @@ -9,7 +15,7 @@ import { type PersistenceAdapter, type SQLiteCoreAdapterOptions, } from "@tanstack/db-sqlite-persistence-core"; -import { openDB, type DBSchema, type IDBPDatabase } from "idb"; +import { openDB, type DBSchema, type IDBPDatabase, type IDBPTransaction } from "idb"; import { Temporal } from "temporal-polyfill"; import type { LoadSubsetOptions } from "@tanstack/db"; @@ -28,7 +34,32 @@ type IndexValueType = | "string-ci" | "undefined"; -const DATABASE_VERSION = 3; +const DATABASE_VERSION = 4; +const CACHE_GENERATIONS = "cacheGenerations"; +const CACHE_CLAIMS = "cacheClaims"; +const CACHE_HEADS = "cacheHeads"; +const CACHE_GENERATION_PREFIX = "tanstack-db-cache:"; +const DATA_STORES = [ + "rows", + "transactions", + "collectionMetadata", + "streams", + "indexDefinitions", + "indexEntries", +] as const; +const ALL_STORES = [...DATA_STORES, CACHE_GENERATIONS, CACHE_CLAIMS, CACHE_HEADS] as const; +type CacheTransaction = IDBPTransaction; +interface CacheGenerationRecord { + storageCollectionId: string; + logicalId: string; + generation: number; + retired: boolean; +} +interface CacheClaimRecord extends PersistedCacheGenerationClaim { + logicalId: string; + expiresAtMs: number; +} + const ROWS = "rows"; const TRANSACTIONS = "transactions"; const COLLECTION_METADATA = "collectionMetadata"; @@ -97,6 +128,13 @@ interface IndexEntryRecord { } interface IndexedDBPersistenceDB extends DBSchema { + cacheGenerations: { key: string; value: CacheGenerationRecord }; + cacheClaims: { key: string; value: CacheClaimRecord; indexes: { collectionId: string } }; + cacheHeads: { + key: string; + value: { logicalId: string; storageCollectionId: string; nextGeneration: number }; + }; + rows: { key: string; value: RowRecord; @@ -151,16 +189,8 @@ interface PersistedTemporalValue { value: string; } -export interface IndexedDBPersistenceAdapterOptions { +export interface IndexedDBPersistenceAdapterOptions extends Omit { databaseName: string; - schemaVersion?: number; - schemaMismatchPolicy?: SQLiteCoreAdapterOptions["schemaMismatchPolicy"]; - /** Retry deduplication and replay are retained for this bounded window. */ - appliedTxPruneMaxRows?: number; - appliedTxPruneMaxAgeSeconds?: number; - pullSinceReloadThreshold?: number; - onBlocked?: () => void; - onVersionChange?: (event: IDBVersionChangeEvent) => void; } function id(...parts: string[]): string { @@ -799,27 +829,202 @@ export class IndexedDBPersistenceAdapter implements PersistenceAdapter { this.schemaVersion = options.schemaVersion ?? 1; for (const [name, value] of Object.entries({ schemaVersion: this.schemaVersion, + cacheGenerationClaimTtlMs: + options.cacheGenerationClaimTtlMs ?? DEFAULT_CACHE_GENERATION_CLAIM_TTL_MS, appliedTxPruneMaxRows: options.appliedTxPruneMaxRows ?? 1000, appliedTxPruneMaxAgeSeconds: options.appliedTxPruneMaxAgeSeconds ?? 86400, pullSinceReloadThreshold: options.pullSinceReloadThreshold ?? 128, })) { - if (!Number.isSafeInteger(value) || value < (name === "schemaVersion" ? 1 : 0)) { + if (!Number.isSafeInteger(value) || value < (name === "cacheGenerationClaimTtlMs" ? 1 : 0)) { throw new Error( - `IndexedDB ${name} must be a ${name === "schemaVersion" ? "positive" : "non-negative"} safe integer.` + `IndexedDB ${name} must be a ${name === "cacheGenerationClaimTtlMs" ? "positive" : "non-negative"} safe integer.` ); } } } + getCacheGenerationNow(): number { + return this.options.now?.() ?? Date.now(); + } + + private get claimTtl(): number { + return this.options.cacheGenerationClaimTtlMs ?? DEFAULT_CACHE_GENERATION_CLAIM_TTL_MS; + } + + private async cacheTransaction(work: (transaction: CacheTransaction) => Promise): Promise { + const database = await this.database(); + const transaction = database.transaction(ALL_STORES, "readwrite"); + void transaction.done.catch(() => undefined); + try { + const result = await work(transaction); + await transaction.done; + return result; + } catch (error) { + try { + transaction.abort(); + } catch { + /* Already settled. */ + } + await transaction.done.catch(() => undefined); + throw error; + } + } + + async claimCacheGeneration(logicalId: string): Promise { + return this.cacheTransaction(async (transaction) => { + let head = await transaction.objectStore(CACHE_HEADS).get(logicalId); + if (!head) { + // Never reuse legacy eager storage: old clients may still write it without a claim. + const storageCollectionId = `${CACHE_GENERATION_PREFIX}${safeRandomUUID()}`; + head = { logicalId, storageCollectionId, nextGeneration: 1 }; + await transaction + .objectStore(CACHE_GENERATIONS) + .put({ logicalId, storageCollectionId, generation: 0, retired: false }); + await transaction.objectStore(CACHE_HEADS).put(head); + } + const claim = { + logicalId, + storageCollectionId: head.storageCollectionId, + claimId: safeRandomUUID(), + expiresAtMs: this.getCacheGenerationNow() + this.claimTtl, + }; + await transaction.objectStore(CACHE_CLAIMS).put(claim); + await this.collectRetiredGenerations(transaction); + return { + storageCollectionId: claim.storageCollectionId, + claimId: claim.claimId, + expiresAtMs: claim.expiresAtMs, + }; + }); + } + + async rotateCacheGeneration( + logicalId: string, + claimId: string, + resetMetadata?: { key: string; value: unknown }, + expectedStorageCollectionId?: string + ): Promise { + return this.cacheTransaction(async (transaction) => { + const now = this.getCacheGenerationNow(); + const claim = await transaction.objectStore(CACHE_CLAIMS).get(claimId); + const ownedClaim = claim?.logicalId === logicalId ? claim : undefined; + const claimedPhysicalId = + ownedClaim && ownedClaim.expiresAtMs > now ? ownedClaim.storageCollectionId : undefined; + if ( + claimedPhysicalId && + expectedStorageCollectionId && + claimedPhysicalId !== expectedStorageCollectionId + ) + throw new InvalidPersistedCollectionConfigError( + "Persisted cache claim changed generations before recovery" + ); + const head = await transaction.objectStore(CACHE_HEADS).get(logicalId); + if (!head) + throw new InvalidPersistedCollectionConfigError( + `No active persisted cache generation for collection "${logicalId}"` + ); + const ownedHead = claimedPhysicalId === head.storageCollectionId; + const storageCollectionId = `${CACHE_GENERATION_PREFIX}${safeRandomUUID()}`; + if (ownedHead) { + const old = (await transaction.objectStore(CACHE_GENERATIONS).get(head.storageCollectionId))!; + await transaction.objectStore(CACHE_GENERATIONS).put({ ...old, retired: true }); + } + await transaction.objectStore(CACHE_GENERATIONS).put({ + logicalId, + storageCollectionId, + generation: head.nextGeneration, + retired: !ownedHead, + }); + await transaction.objectStore(CACHE_HEADS).put({ + ...head, + nextGeneration: head.nextGeneration + 1, + ...(ownedHead ? { storageCollectionId } : {}), + }); + // A live or not-yet-collected expired claim keeps its identity, just as SQLite does. + const nextClaimId = ownedClaim ? claimId : safeRandomUUID(); + const expiresAtMs = now + this.claimTtl; + await transaction + .objectStore(CACHE_CLAIMS) + .put({ logicalId, storageCollectionId, claimId: nextClaimId, expiresAtMs }); + if (resetMetadata) + await transaction.objectStore(COLLECTION_METADATA).put({ + id: id(storageCollectionId, resetMetadata.key), + collectionId: storageCollectionId, + key: resetMetadata.key, + value: encodePersistedValue(resetMetadata.value), + }); + await this.collectRetiredGenerations(transaction); + return { storageCollectionId, claimId: nextClaimId, expiresAtMs }; + }); + } + + async releaseCacheGenerationClaim(claimId: string): Promise { + await this.cacheTransaction(async (transaction) => { + await transaction.objectStore(CACHE_CLAIMS).delete(claimId); + await this.collectRetiredGenerations(transaction); + }); + } + + async renewCacheGenerationClaim( + storageCollectionId: string, + claimId: string + ): Promise { + return this.cacheTransaction(async (transaction) => { + const claim = await transaction.objectStore(CACHE_CLAIMS).get(claimId); + const now = this.getCacheGenerationNow(); + if (!claim || claim.storageCollectionId !== storageCollectionId || claim.expiresAtMs <= now) { + await this.collectRetiredGenerations(transaction); + return undefined; + } + const expiresAtMs = now + this.claimTtl; + await transaction.objectStore(CACHE_CLAIMS).put({ ...claim, expiresAtMs }); + return expiresAtMs; + }); + } + + private async collectRetiredGenerations(transaction: CacheTransaction): Promise { + const now = this.getCacheGenerationNow(); + let claim = await transaction.objectStore(CACHE_CLAIMS).openCursor(); + while (claim) { + if (claim.value.expiresAtMs <= now) await claim.delete(); + claim = await claim.continue(); + } + let generation = await transaction.objectStore(CACHE_GENERATIONS).openCursor(); + while (generation) { + const collectionId = generation.value.storageCollectionId; + if ( + generation.value.retired && + (await transaction.objectStore(CACHE_CLAIMS).index(BY_COLLECTION).count(collectionId)) === 0 + ) { + for (const name of [ + ROWS, + TRANSACTIONS, + COLLECTION_METADATA, + INDEX_DEFINITIONS, + INDEX_ENTRIES, + ] as const) { + const store = transaction.objectStore(name); + const keys = await store.index(BY_COLLECTION).getAllKeys(collectionId); + await Promise.all(keys.map((key) => store.delete(key as never))); + } + await transaction.objectStore(STREAMS).delete(collectionId); + await generation.delete(); + this.initialized.delete(collectionId); + } + generation = await generation.continue(); + } + } + private async collectionDatabase( - collectionId: string + collectionId: string, + claimId?: string ): Promise<{ database: IDBPDatabase; epoch: number }> { const database = await this.database(); if (this.closed || this.connection.closed) throw new Error("IndexedDB persistence adapter is closed."); let initialized = this.initialized.get(collectionId); if (!initialized) { - initialized = this.initializeCollection(database, collectionId); + initialized = this.initializeCollection(database, collectionId, claimId); this.initialized.set(collectionId, initialized); void initialized.catch(() => { if (this.initialized.get(collectionId) === initialized) this.initialized.delete(collectionId); @@ -839,13 +1044,29 @@ export class IndexedDBPersistenceAdapter implements PersistenceAdapter { private async initializeCollection( database: IDBPDatabase, - collectionId: string + collectionId: string, + claimId?: string ): Promise { const transaction = database.transaction( - [ROWS, TRANSACTIONS, COLLECTION_METADATA, STREAMS, INDEX_DEFINITIONS, INDEX_ENTRIES], + [ + ROWS, + TRANSACTIONS, + COLLECTION_METADATA, + STREAMS, + INDEX_DEFINITIONS, + INDEX_ENTRIES, + CACHE_GENERATIONS, + CACHE_CLAIMS, + ], "readwrite" ); try { + await this.assertClaim( + transaction.objectStore(CACHE_GENERATIONS), + transaction.objectStore(CACHE_CLAIMS), + collectionId, + claimId + ); let stream = await transaction.objectStore(STREAMS).get(collectionId); // Existing version-1 databases were written with the default collection schema (1). const storedVersion = stream?.schemaVersion ?? (stream ? 1 : this.schemaVersion); @@ -943,14 +1164,63 @@ export class IndexedDBPersistenceAdapter implements PersistenceAdapter { } } - async loadResumeSnapshot( + private access(target: PersistedStorageTarget, legacyClaimId?: string) { + return resolvePersistedStorageTarget(target, legacyClaimId); + } + + private async assertClaim( + generations: { get(key: string): Promise }, + claims: { get(key: string): Promise }, collectionId: string, - context?: { requiredIndexSignatures?: ReadonlyArray; includeRows?: boolean } + claimId?: string + ): Promise { + if (claimId) { + const [claim, generation] = await Promise.all([ + claims.get(claimId), + generations.get(collectionId), + ]); + if ( + !claim || + !generation || + claim.storageCollectionId !== collectionId || + claim.expiresAtMs <= this.getCacheGenerationNow() + ) + throw new InvalidPersistedCollectionConfigError( + `Persisted cache claim is no longer active for collection "${collectionId}"` + ); + } else if ( + (collectionId.startsWith(CACHE_GENERATION_PREFIX) || + collectionId.startsWith(`\u0000${CACHE_GENERATION_PREFIX}`)) && + (await generations.get(collectionId)) + ) { + throw new InvalidPersistedCollectionConfigError( + `Persisted cache claim is required for collection "${collectionId}"` + ); + } + } + + async loadResumeSnapshot( + target: PersistedStorageTarget, + context?: { + requiredIndexSignatures?: ReadonlyArray; + includeRows?: boolean; + cacheGenerationClaimId?: string; + } ): ReturnType { - const { database, epoch } = await this.collectionDatabase(collectionId); + const { collectionId, claimId } = this.access(target, context?.cacheGenerationClaimId); + const { database, epoch } = await this.collectionDatabase(collectionId, claimId); // Read data and its cursor together so a concurrent commit cannot make // the resume position newer than the rows restored from this snapshot. - const transaction = database.transaction([ROWS, COLLECTION_METADATA, STREAMS], "readonly"); + const transaction = database.transaction( + [ROWS, COLLECTION_METADATA, STREAMS, CACHE_GENERATIONS, CACHE_CLAIMS], + "readonly" + ); + await this.assertClaim( + transaction.objectStore(CACHE_GENERATIONS), + transaction.objectStore(CACHE_CLAIMS), + collectionId, + claimId + ); const [rows, metadata, stream] = await Promise.all([ context?.includeRows === false ? Promise.resolve([]) @@ -977,21 +1247,34 @@ export class IndexedDBPersistenceAdapter implements PersistenceAdapter { }; } - async loadSubset(collectionId: string, options: LoadSubsetOptions): Promise { + async loadSubset( + target: PersistedStorageTarget, + options: LoadSubsetOptions, + context?: { requiredIndexSignatures?: ReadonlyArray; cacheGenerationClaimId?: string } + ): Promise { + const { collectionId, claimId } = this.access(target, context?.cacheGenerationClaimId); if (options.cursor) { const combine = (predicate: IR.BasicExpression) => options.where ? new IR.Func("and", [options.where, predicate]) : predicate; // Restore every row tied at the current boundary, then the requested following page. const [current, following] = await Promise.all([ - this.loadSubset(collectionId, { - where: combine(options.cursor.whereCurrent), - orderBy: options.orderBy, - }), - this.loadSubset(collectionId, { - where: combine(options.cursor.whereFrom), - orderBy: options.orderBy, - limit: options.limit, - }), + this.loadSubset( + target, + { + where: combine(options.cursor.whereCurrent), + orderBy: options.orderBy, + }, + context + ), + this.loadSubset( + target, + { + where: combine(options.cursor.whereFrom), + orderBy: options.orderBy, + limit: options.limit, + }, + context + ), ]); const currentPredicate = compileSingleRowExpression(combine(options.cursor.whereCurrent)); const merged = new Map( @@ -1001,11 +1284,17 @@ export class IndexedDBPersistenceAdapter implements PersistenceAdapter { ); return orderRows([...merged.values()], options.orderBy).rows; } - const { database, epoch } = await this.collectionDatabase(collectionId); + const { database, epoch } = await this.collectionDatabase(collectionId, claimId); const transaction = database.transaction( - [ROWS, INDEX_DEFINITIONS, INDEX_ENTRIES, STREAMS], + [ROWS, INDEX_DEFINITIONS, INDEX_ENTRIES, STREAMS, CACHE_GENERATIONS, CACHE_CLAIMS], "readonly" ); + await this.assertClaim( + transaction.objectStore(CACHE_GENERATIONS), + transaction.objectStore(CACHE_CLAIMS), + collectionId, + claimId + ); this.assertSchema(await transaction.objectStore(STREAMS).get(collectionId), collectionId, epoch); const definitions = await transaction .objectStore(INDEX_DEFINITIONS) @@ -1201,16 +1490,32 @@ export class IndexedDBPersistenceAdapter implements PersistenceAdapter { return ordered.rows.slice(offset, offset + limit); } - async applyCommittedTx(collectionId: string, committed: PersistedTx): Promise { - const { database, epoch } = await this.collectionDatabase(collectionId); + async applyCommittedTx(target: PersistedStorageTarget, committed: PersistedTx): Promise { + const { collectionId, claimId } = this.access(target, committed.cacheGenerationClaimId); + const { database, epoch } = await this.collectionDatabase(collectionId, claimId); const transaction = database.transaction( - [ROWS, TRANSACTIONS, COLLECTION_METADATA, STREAMS, INDEX_DEFINITIONS, INDEX_ENTRIES], + [ + ROWS, + TRANSACTIONS, + COLLECTION_METADATA, + STREAMS, + INDEX_DEFINITIONS, + INDEX_ENTRIES, + CACHE_GENERATIONS, + CACHE_CLAIMS, + ], "readwrite" ); // A cloning failure can abort while mutation promises are still unwinding. // Observe completion immediately; the catch below still propagates the write error. void transaction.done.catch(() => undefined); try { + await this.assertClaim( + transaction.objectStore(CACHE_GENERATIONS), + transaction.objectStore(CACHE_CLAIMS), + collectionId, + claimId + ); const stream = await transaction.objectStore(STREAMS).get(collectionId); this.assertSchema(stream, collectionId, epoch); const transactionId = id(collectionId, committed.txId); @@ -1460,9 +1765,22 @@ export class IndexedDBPersistenceAdapter implements PersistenceAdapter { } } - async loadCollectionMetadata(collectionId: string): Promise> { - const { database, epoch } = await this.collectionDatabase(collectionId); - const transaction = database.transaction([COLLECTION_METADATA, STREAMS], "readonly"); + async loadCollectionMetadata( + target: PersistedStorageTarget, + context?: { cacheGenerationClaimId?: string } + ): Promise> { + const { collectionId, claimId } = this.access(target, context?.cacheGenerationClaimId); + const { database, epoch } = await this.collectionDatabase(collectionId, claimId); + const transaction = database.transaction( + [COLLECTION_METADATA, STREAMS, CACHE_GENERATIONS, CACHE_CLAIMS], + "readonly" + ); + await this.assertClaim( + transaction.objectStore(CACHE_GENERATIONS), + transaction.objectStore(CACHE_CLAIMS), + collectionId, + claimId + ); this.assertSchema(await transaction.objectStore(STREAMS).get(collectionId), collectionId, epoch); const records = await transaction .objectStore(COLLECTION_METADATA) @@ -1475,10 +1793,24 @@ export class IndexedDBPersistenceAdapter implements PersistenceAdapter { })); } - async scanRows(collectionId: string, options?: PersistedRowScanOptions): Promise { - if (!options?.metadataOnly) return this.loadSubset(collectionId, {}); - const { database, epoch } = await this.collectionDatabase(collectionId); - const transaction = database.transaction([ROWS, STREAMS], "readonly"); + async scanRows( + target: PersistedStorageTarget, + options?: PersistedRowScanOptions, + context?: { cacheGenerationClaimId?: string } + ): Promise { + const { collectionId, claimId } = this.access(target, context?.cacheGenerationClaimId); + if (!options?.metadataOnly) return this.loadSubset(target, {}, context); + const { database, epoch } = await this.collectionDatabase(collectionId, claimId); + const transaction = database.transaction( + [ROWS, STREAMS, CACHE_GENERATIONS, CACHE_CLAIMS], + "readonly" + ); + await this.assertClaim( + transaction.objectStore(CACHE_GENERATIONS), + transaction.objectStore(CACHE_CLAIMS), + collectionId, + claimId + ); this.assertSchema(await transaction.objectStore(STREAMS).get(collectionId), collectionId, epoch); const rows = await transaction.objectStore(ROWS).index(BY_METADATA).getAll([collectionId, 1]); await transaction.done; @@ -1490,11 +1822,22 @@ export class IndexedDBPersistenceAdapter implements PersistenceAdapter { } async pullSince( - collectionId: string, - fromRowVersion: number + target: PersistedStorageTarget, + fromRowVersion: number, + context?: { cacheGenerationClaimId?: string } ): Promise { - const { database, epoch } = await this.collectionDatabase(collectionId); - const transaction = database.transaction([TRANSACTIONS, STREAMS], "readonly"); + const { collectionId, claimId } = this.access(target, context?.cacheGenerationClaimId); + const { database, epoch } = await this.collectionDatabase(collectionId, claimId); + const transaction = database.transaction( + [TRANSACTIONS, STREAMS, CACHE_GENERATIONS, CACHE_CLAIMS], + "readonly" + ); + await this.assertClaim( + transaction.objectStore(CACHE_GENERATIONS), + transaction.objectStore(CACHE_CLAIMS), + collectionId, + claimId + ); const stream = await transaction.objectStore(STREAMS).get(collectionId); this.assertSchema(stream, collectionId, epoch); const latestRowVersion = stream!.latestRowVersion; @@ -1557,14 +1900,26 @@ export class IndexedDBPersistenceAdapter implements PersistenceAdapter { }; } - async ensureIndex(collectionId: string, signature: string, spec: PersistedIndexSpec): Promise { - const { database, epoch } = await this.collectionDatabase(collectionId); + async ensureIndex( + target: PersistedStorageTarget, + signature: string, + spec: PersistedIndexSpec, + context?: { cacheGenerationClaimId?: string } + ): Promise { + const { collectionId, claimId } = this.access(target, context?.cacheGenerationClaimId); + const { database, epoch } = await this.collectionDatabase(collectionId, claimId); const expression = parseIndexExpression(spec); const transaction = database.transaction( - [ROWS, INDEX_DEFINITIONS, INDEX_ENTRIES, STREAMS], + [ROWS, INDEX_DEFINITIONS, INDEX_ENTRIES, STREAMS, CACHE_GENERATIONS, CACHE_CLAIMS], "readwrite" ); try { + await this.assertClaim( + transaction.objectStore(CACHE_GENERATIONS), + transaction.objectStore(CACHE_CLAIMS), + collectionId, + claimId + ); this.assertSchema(await transaction.objectStore(STREAMS).get(collectionId), collectionId, epoch); const existing = await transaction.objectStore(INDEX_DEFINITIONS).get([collectionId, signature]); if ( @@ -1607,9 +1962,23 @@ export class IndexedDBPersistenceAdapter implements PersistenceAdapter { } } - async markIndexRemoved(collectionId: string, signature: string): Promise { - const { database, epoch } = await this.collectionDatabase(collectionId); - const transaction = database.transaction([INDEX_DEFINITIONS, INDEX_ENTRIES, STREAMS], "readwrite"); + async markIndexRemoved( + target: PersistedStorageTarget, + signature: string, + context?: { cacheGenerationClaimId?: string } + ): Promise { + const { collectionId, claimId } = this.access(target, context?.cacheGenerationClaimId); + const { database, epoch } = await this.collectionDatabase(collectionId, claimId); + const transaction = database.transaction( + [INDEX_DEFINITIONS, INDEX_ENTRIES, STREAMS, CACHE_GENERATIONS, CACHE_CLAIMS], + "readwrite" + ); + await this.assertClaim( + transaction.objectStore(CACHE_GENERATIONS), + transaction.objectStore(CACHE_CLAIMS), + collectionId, + claimId + ); this.assertSchema(await transaction.objectStore(STREAMS).get(collectionId), collectionId, epoch); await transaction.objectStore(INDEX_DEFINITIONS).delete([collectionId, signature]); const entryStore = transaction.objectStore(INDEX_ENTRIES); @@ -1618,10 +1987,22 @@ export class IndexedDBPersistenceAdapter implements PersistenceAdapter { await transaction.done; } - async getStreamPosition(collectionId: string): Promise { - const { database, epoch } = await this.collectionDatabase(collectionId); - const stream = await database.get(STREAMS, collectionId); + async getStreamPosition( + target: PersistedStorageTarget, + context?: { cacheGenerationClaimId?: string } + ): Promise { + const { collectionId, claimId } = this.access(target, context?.cacheGenerationClaimId); + const { database, epoch } = await this.collectionDatabase(collectionId, claimId); + const transaction = database.transaction([STREAMS, CACHE_GENERATIONS, CACHE_CLAIMS], "readonly"); + await this.assertClaim( + transaction.objectStore(CACHE_GENERATIONS), + transaction.objectStore(CACHE_CLAIMS), + collectionId, + claimId + ); + const stream = await transaction.objectStore(STREAMS).get(collectionId); this.assertSchema(stream, collectionId, epoch); + await transaction.done; return ( stream ?? { collectionId, @@ -1649,12 +2030,7 @@ export class IndexedDBConnection { private databasePromise?: Promise>; closed = false; - constructor( - private readonly options: Pick< - IndexedDBPersistenceAdapterOptions, - "databaseName" | "onBlocked" | "onVersionChange" - > - ) {} + constructor(private readonly options: Pick) {} close(): void { this.closed = true; @@ -1719,6 +2095,13 @@ export class IndexedDBConnection { } })().catch(() => transaction.abort()); } + if (oldVersion < 4) { + database.createObjectStore(CACHE_GENERATIONS, { keyPath: "storageCollectionId" }); + database.createObjectStore(CACHE_HEADS, { keyPath: "logicalId" }); + database + .createObjectStore(CACHE_CLAIMS, { keyPath: "claimId" }) + .createIndex(BY_COLLECTION, "storageCollectionId"); + } if (oldVersion < 3) { // Older journal records retain tx-id deduplication; new records also // retain the coordinator stream identity, matching SQLite's contract. @@ -1727,9 +2110,7 @@ export class IndexedDBConnection { .createIndex(BY_POSITION, ["collectionId", "term", "seq"]); } }, - blocked: () => this.options.onBlocked?.(), - blocking: (_currentVersion, _blockedVersion, event) => { - this.options.onVersionChange?.(event); + blocking: () => { void this.databasePromise?.then((database) => database.close()); this.databasePromise = undefined; }, diff --git a/packages/db-indexeddb-persistence/src/contracts/NOTICE.md b/packages/db-indexeddb-persistence/src/contracts/NOTICE.md index dabe80dd..a146b06f 100644 --- a/packages/db-indexeddb-persistence/src/contracts/NOTICE.md +++ b/packages/db-indexeddb-persistence/src/contracts/NOTICE.md @@ -3,12 +3,70 @@ Source: [TanStack/db SQLite adapter contract](https://github.com/TanStack/db/blob/2c98b4992c412820f8476325ee6941db5ca9ac0f/packages/db-sqlite-persistence-core/tests/sqlite-core-adapter-oracle.test.ts). Pinned commit: `2c98b4992c412820f8476325ee6941db5ca9ac0f` (2026-10-07 audit). -These cases run through one harness against our IndexedDB adapter and the installed upstream SQLite adapter (core 0.4.5). They also run against native browser IndexedDB. Assertions and transaction fixtures are retained for stream-position idempotency, atomic rollback, metadata, truncate, replay thresholds, metadata-only replay, cursor ties, custom collation, distinct numeric/string keys, boolean/operator fallback, and typed bigint/Date predicates. +These cases run through one harness against our IndexedDB adapter and the installed upstream SQLite adapter (core 0.4.9). They also run against native browser IndexedDB. Assertions and transaction fixtures are retained for stream-position idempotency, atomic rollback, metadata, truncate, replay thresholds, metadata-only replay, cursor ties, custom collation, distinct numeric/string keys, boolean/operator fallback, and typed bigint/Date predicates. -Adaptations: replace SQLite driver/table introspection with public stream-position, replay, and metadata assertions; supply a backend-specific serialization failure (the original invalid Date for SQLite, an unsupported function for IndexedDB, which natively accepts invalid Date values); inject adapter creation/cleanup; remove Node imports; require the replay extension in the harness type; acquire typed indexes for the bigint/Date fixture, since unpaginated subset reads may return candidate supersets. SQL compilation, physical table naming, SQLite scheduling/driver admission, binding caps, and platform-specific SQLite bridges are not IndexedDB contracts. Other existing IndexedDB tests cover idempotency, schema policies, bounded histories, resume snapshots, query planning, and index lifecycle. +Adaptations: use explicit eager storage targets required by persistence core 0.4.9; replace SQLite driver/table introspection with public stream-position, replay, and metadata assertions; supply a backend-specific serialization failure (the original invalid Date for SQLite, an unsupported function for IndexedDB, which natively accepts invalid Date values); inject adapter creation/cleanup; remove Node imports; require the replay extension in the harness type; acquire typed indexes for the bigint/Date fixture, since unpaginated subset reads may return candidate supersets. SQL compilation, physical table naming, SQLite scheduling/driver admission, binding caps, and platform-specific SQLite bridges are not IndexedDB contracts. Other existing IndexedDB tests cover idempotency, schema policies, bounded histories, resume snapshots, query planning, and index lifecycle. No upstream persistence-specific benchmark suite was found at this commit. Upstream's incremental-update benchmark targets in-memory collections, joins, and materialization. Our browser benchmarks instead measure actual IndexedDB reads and incremental writes on a fixed 10,000-row dataset; they are not presented as a port of that benchmark or a cross-engine comparison. +## Managed cache generation contracts — 2026-10-14 + +Additional source: [SQLite resume snapshot oracle](https://github.com/TanStack/db/blob/8660cc0eb4914b8b0d3b2ba159416185f6d956c9/packages/db-sqlite-persistence-core/tests/sqlite-resume-snapshot-oracle.test.ts) +and [persisted runtime oracle](https://github.com/TanStack/db/blob/8660cc0eb4914b8b0d3b2ba159416185f6d956c9/packages/db-sqlite-persistence-core/tests/persisted-oracle.test.ts). +Pinned commit: `8660cc0eb4914b8b0d3b2ba159416185f6d956c9`. Its SQLite adapter +source is identical to installed persistence core 0.4.9 (checked by byte comparison). + +`managedCacheContract.ts` ports the relevant public storage and recovery histories +into one factory-driven suite, run unchanged against fake IndexedDB, upstream +SQLite, and native Chromium IndexedDB. The ports retain upstream's operation +ordering, controlled clocks, expiry cuts, expected head selection, and durable +state assertions. SQL catalog/table assertions become backend-specific physical +footprint counts, covering all of each backend's generation-scoped stores. +Indexes use serialized IR supported by both backends instead of SQLite-specific +`row_version` expressions. No upstream checkout or network is required by tests. + +Ported source histories: + +- Collected managed targets never become eager storage; bare and malformed targets + are rejected, every implemented storage entry point rejects revoked claims, + and explicit eager reuse of the same bytes remains a separate valid operation. +- Eager IDs with both current and legacy internal-looking prefixes remain valid. +- Retired storage persists only while another valid claim needs it; last release + collects rows, metadata, indexes, journals, and generation/claim records. +- The complete finite retired-storage grammar: release/expire/renew for both + retired peers, zero/two rows, and all six expiry cuts (108 cases per backend). + This exhaustively enumerates the domain sampled by upstream's fixed-seed and + random fast-check histories without introducing a property-testing dependency. +- Paused/expired runs recover privately; a current shared cache and its warm + peers remain claimable and retain their rows and resume metadata. +- Startup metadata returned after its claim expires cannot authorize a source. +- A custom source restarts after claim expiry, and retired callbacks cannot write + into the replacement generation. +- Old and queued source commits reject after expiry; subsequent source writes + succeed under the recovered claim. +- Timer-observed claim loss reloads retained demand. +- Explicit scoped recovery moves to a fresh generation and retains a reset marker; + old and fresh durable rows remain isolated. + +`managedCacheRaces.ts` ports SQL registration/revocation checkpoints to IndexedDB: +operations pause before or after collection initialization, then lose their claim +or physical generation. Every implemented read/write/index entry point must reject +without resurrecting storage. These tests also cover stale schema registration +without resetting a valid warm peer, concurrent claims and conditional rotations +across separate connections, and a real v3-to-v4 migration retaining local data. +They run against fake and native IndexedDB. The gated initialization seam replaces +SQL DDL/registry hooks: IndexedDB validates registration and data access inside +transactions containing the generation and claim stores, rather than doing DDL. + +Exclusions: SQLite CLI argument transport, physical table creation/trigger/index +SQL, driver admission and scheduling tests have no IndexedDB equivalent. SQLite's +optional `reconcileCommittedTx`, `reserveLeadershipTerm`, and certified key-set +capabilities are not implemented by this adapter and are not claimed as part of +managed cache support. Their SQL-specific oracle cases are not ported. Core-only +mock tests for all timer/retry/scheduler interleavings remain owned by the installed +upstream wrapper; this suite adds real-adapter receiving histories rather than +copying unrelated wrapper implementations or the entire 32,000-line oracle. + ## License MIT License diff --git a/packages/db-indexeddb-persistence/src/contracts/indexedDBManagedCacheHarness.ts b/packages/db-indexeddb-persistence/src/contracts/indexedDBManagedCacheHarness.ts new file mode 100644 index 00000000..997bdc3b --- /dev/null +++ b/packages/db-indexeddb-persistence/src/contracts/indexedDBManagedCacheHarness.ts @@ -0,0 +1,67 @@ +import { openDB } from "idb"; +import { IndexedDBPersistenceAdapter } from "../IndexedDBPersistenceAdapter.js"; +import type { ManagedCacheFactory } from "./managedCacheContract.js"; +import type { PersistedCacheGenerationClaim } from "@tanstack/db-sqlite-persistence-core"; + +const stores = [ + "rows", + "transactions", + "collectionMetadata", + "streams", + "indexDefinitions", + "indexEntries", + "cacheGenerations", + "cacheClaims", +]; +type RecordIdentity = { collectionId?: string; storageCollectionId?: string; logicalId?: string }; +export const indexedDBManagedCacheHarness: ManagedCacheFactory = (options = {}) => { + const name = `managed-contract-${crypto.randomUUID()}`; + const adapters: IndexedDBPersistenceAdapter[] = []; + const create = (overrides = {}) => { + const adapter = new IndexedDBPersistenceAdapter({ databaseName: name, ...options, ...overrides }); + adapters.push(adapter); + return adapter; + }; + return { + adapter: create(), + reopen: create, + footprint: async (id) => { + const database = await openDB(name); + try { + const transaction = database.transaction(stores, "readonly"); + const result: Record = {}; + for (const store of stores) { + const records = (await transaction.objectStore(store).getAll()) as RecordIdentity[]; + result[store === "cacheClaims" ? "claims" : store] = records.filter( + (row) => (row.collectionId ?? row.storageCollectionId) === id + ).length; + } + await transaction.done; + return result; + } finally { + database.close(); + } + }, + claims: async (logicalId) => { + const database = await openDB(name); + try { + const claims = (await database.getAll("cacheClaims")) as Array< + PersistedCacheGenerationClaim & { logicalId: string } + >; + return claims.filter((claim) => claim.logicalId === logicalId); + } finally { + database.close(); + } + }, + cleanup: async () => { + for (const adapter of adapters) adapter.close(); + await new Promise((resolve, reject) => { + const request = indexedDB.deleteDatabase(name); + request.onsuccess = () => resolve(); + request.onerror = () => + reject(request.error ?? new Error("Could not delete contract database")); + request.onblocked = () => reject(new Error("Contract database remained open")); + }); + }, + }; +}; diff --git a/packages/db-indexeddb-persistence/src/contracts/managedCacheContract.ts b/packages/db-indexeddb-persistence/src/contracts/managedCacheContract.ts new file mode 100644 index 00000000..753ef8e5 --- /dev/null +++ b/packages/db-indexeddb-persistence/src/contracts/managedCacheContract.ts @@ -0,0 +1,756 @@ +// Adapted from TanStack/db's SQLite resume snapshot oracle (MIT); see NOTICE.md. +import { IR, createCollection, whenSyncAccepted } from "@tanstack/db"; +import { + eagerStorageTarget, + managedStorageTarget, + persistedCollectionOptions, + type HydrationPersistenceAdapter, + type PersistedCacheGenerationClaim, + type PersistedTx, + type SQLiteCoreAdapterOptions, +} from "@tanstack/db-sqlite-persistence-core"; +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; +import type { SyncConfig } from "@tanstack/db"; + +export type ManagedAdapter = HydrationPersistenceAdapter & + Required< + Pick< + HydrationPersistenceAdapter, + | "claimCacheGeneration" + | "rotateCacheGeneration" + | "renewCacheGenerationClaim" + | "releaseCacheGenerationClaim" + | "loadCollectionMetadata" + | "scanRows" + | "getStreamPosition" + | "markIndexRemoved" + | "pullSince" + | "getCacheGenerationNow" + > + >; +export interface ManagedCacheHarness { + adapter: ManagedAdapter; + reopen(options?: Partial): ManagedAdapter; + footprint(storageCollectionId: string): Promise>; + claims(logicalId: string): Promise; + cleanup(): Promise | void; +} +export type ManagedCacheFactory = ( + options?: Pick +) => ManagedCacheHarness; +export function target(claim: PersistedCacheGenerationClaim) { + return managedStorageTarget(claim.storageCollectionId, claim.claimId); +} +function seed(key = "seed", rowVersion = 1): PersistedTx { + return { + txId: key, + term: 1, + seq: rowVersion, + rowVersion, + mutations: [{ type: "insert", key, value: { id: key } }], + }; +} +const indexSpec = { expressionSql: [JSON.stringify(new IR.PropRef(["id"]))] }; + +export function runManagedCacheContract(name: string, factory: ManagedCacheFactory): void { + const harnesses: ManagedCacheHarness[] = []; + function use(options?: Parameters[0]) { + const h = factory(options); + harnesses.push(h); + return h; + } + afterEach(async () => { + await Promise.all( + harnesses.splice(0).map(async (h) => { + await h.cleanup(); + }) + ); + }); + describe(`${name}`, () => { + it("mirrors SQLite claim TTL defaults, custom clocks, and option validation", async () => { + const h = use({ now: () => 1_000 }); + const claim = await h.adapter.claimCacheGeneration("defaults"); + expect(h.adapter.getCacheGenerationNow()).toBe(1_000); + expect(claim.expiresAtMs).toBe(301_000); + for (const ttl of [0, -1, 0.5, Infinity, NaN, Number.MAX_SAFE_INTEGER + 1]) + expect(() => h.reopen({ cacheGenerationClaimTtlMs: ttl })).toThrow(/./); + }); + + it("shares the current generation while giving each run a distinct claim", async () => { + const h = use(); + const first = await h.adapter.claimCacheGeneration("employees"); + const second = await h.reopen().claimCacheGeneration("employees"); + expect(first.storageCollectionId).toBe(second.storageCollectionId); + expect(first.claimId).not.toBe(second.claimId); + expect(first.storageCollectionId).not.toBe("employees"); + const other = await h.adapter.claimCacheGeneration("other"); + expect(other.storageCollectionId).not.toBe(first.storageCollectionId); + }); + + it("starts managed storage separately from legacy eager rows and local-only data", async () => { + const h = use(); + await h.adapter.applyCommittedTx(eagerStorageTarget("employees"), seed("legacy")); + const claim = await h.adapter.claimCacheGeneration("employees"); + expect((await h.adapter.loadResumeSnapshot(target(claim))).rows).toEqual([]); + expect( + (await h.adapter.loadResumeSnapshot(eagerStorageTarget("employees"))).rows.map((r) => r.key) + ).toEqual(["legacy"]); + }); + + it("keeps a collected managed target from becoming eager storage", async () => { + const h = use(); + const { adapter } = h; + const claim = await adapter.claimCacheGeneration("target-after-collection"); + const managed = target(claim); + await adapter.applyCommittedTx(managed, seed()); + expect((await adapter.loadResumeSnapshot(managed)).rows).toMatchObject([ + { key: "seed", value: { id: "seed" } }, + ]); + await adapter.rotateCacheGeneration("target-after-collection", claim.claimId); + const collected = await h.footprint(claim.storageCollectionId); + expect(Object.values(collected).every((count) => count === 0)).toBe(true); + for (const bare of [claim.storageCollectionId, "ordinary-bare-id"]) { + await expect(adapter.loadResumeSnapshot(bare as never)).rejects.toThrow( + "explicit eager or managed storage target" + ); + await expect(adapter.applyCommittedTx(bare as never, seed())).rejects.toThrow( + "explicit eager or managed storage target" + ); + } + await expect( + adapter.loadResumeSnapshot({ + kind: "managed", + storageCollectionId: claim.storageCollectionId, + } as never) + ).rejects.toThrow("requires a storageCollectionId and persisted cache claim"); + await expect( + adapter.loadResumeSnapshot({ + kind: "eager", + collectionId: claim.storageCollectionId, + claimId: claim.claimId, + } as never) + ).rejects.toThrow("cannot carry a persisted cache claim"); + const lateOperations = [ + () => adapter.loadSubset(managed, {}), + () => adapter.loadResumeSnapshot(managed), + () => adapter.loadCollectionMetadata(managed), + () => adapter.scanRows(managed), + () => adapter.scanRows(managed, { metadataOnly: true }), + () => adapter.getStreamPosition(managed), + () => adapter.pullSince(managed, 0), + () => adapter.applyCommittedTx(managed, seed("late", 2)), + () => adapter.ensureIndex(managed, "late-index", indexSpec), + () => adapter.markIndexRemoved(managed, "late-index"), + ]; + for (const operation of lateOperations) { + await expect(operation()).rejects.toThrow("no longer active"); + expect(await h.footprint(claim.storageCollectionId)).toEqual(collected); + } + const reopened = h.reopen(); + await expect(reopened.loadResumeSnapshot(managed)).rejects.toThrow("no longer active"); + await expect(reopened.applyCommittedTx(managed, seed())).rejects.toThrow("no longer active"); + const eager = eagerStorageTarget(claim.storageCollectionId); + await reopened.applyCommittedTx(eager, seed("eager")); + expect((await reopened.loadResumeSnapshot(eager)).rows.map((r) => r.key)).toEqual(["eager"]); + await expect(reopened.loadResumeSnapshot(managed)).rejects.toThrow("no longer active"); + await expect(reopened.applyCommittedTx(managed, seed("late", 2))).rejects.toThrow( + "no longer active" + ); + expect((await reopened.loadResumeSnapshot(eager)).rows.map((r) => r.key)).toEqual(["eager"]); + }); + + it.each(["tanstack-db-cache:user-eager-collection", "\u0000tanstack-db-cache:user-eager-collection"])( + "allows eager collection names with an internal-looking prefix: %s", + async (id) => { + const { adapter } = use(); + await adapter.applyCommittedTx(eagerStorageTarget(id), seed()); + expect((await adapter.loadResumeSnapshot(eagerStorageTarget(id))).rows).toHaveLength(1); + } + ); + + it("requires a matching claim for every managed operation and legacy claim context", async () => { + const { adapter } = use(); + const claim = await adapter.claimCacheGeneration("employees"); + const other = await adapter.claimCacheGeneration("other"); + await adapter.applyCommittedTx(target(claim), seed()); + await expect( + adapter.loadResumeSnapshot(eagerStorageTarget(claim.storageCollectionId)) + ).rejects.toThrow("claim is required"); + await expect( + adapter.loadResumeSnapshot(managedStorageTarget(claim.storageCollectionId, other.claimId)) + ).rejects.toThrow("no longer active"); + await expect( + adapter.loadResumeSnapshot(target(claim), { cacheGenerationClaimId: other.claimId }) + ).rejects.toThrow(/./); + await expect( + adapter.applyCommittedTx(target(claim), { + ...seed("bad", 2), + cacheGenerationClaimId: other.claimId, + }) + ).rejects.toThrow(/./); + expect( + ( + await adapter.loadResumeSnapshot(target(claim), { cacheGenerationClaimId: claim.claimId }) + ).rows.map((r) => r.key) + ).toEqual(["seed"]); + }); + + it("reclaims retired cache rows after their last claim releases", async () => { + const h = use(); + const { adapter } = h; + const first = await adapter.claimCacheGeneration("retired-cache-collection"); + const warm = await adapter.claimCacheGeneration("retired-cache-collection"); + await adapter.applyCommittedTx(target(first), seed("old")); + const current = await adapter.rotateCacheGeneration("retired-cache-collection", first.claimId); + await adapter.applyCommittedTx(target(current), seed("current")); + expect((await adapter.loadResumeSnapshot(target(warm))).rows.map((r) => r.key)).toEqual(["old"]); + expect((await h.footprint(first.storageCollectionId)).rows).toBe(1); + await adapter.releaseCacheGenerationClaim(warm.claimId); + expect( + Object.values(await h.footprint(first.storageCollectionId)).every((count) => count === 0) + ).toBe(true); + expect((await adapter.loadResumeSnapshot(target(current))).rows.map((r) => r.key)).toEqual([ + "current", + ]); + expect((await adapter.claimCacheGeneration("retired-cache-collection")).storageCollectionId).toBe( + current.storageCollectionId + ); + }); + + it("rotates atomically with reset metadata while preserving the warm generation", async () => { + const h = use(); + const { adapter } = h; + const first = await adapter.claimCacheGeneration("reset"); + const peer = await adapter.claimCacheGeneration("reset"); + await adapter.applyCommittedTx(target(first), { + ...seed("old"), + collectionMetadataMutations: [{ type: "set", key: "resume", value: "old-cursor" }], + }); + const next = await adapter.rotateCacheGeneration( + "reset", + first.claimId, + { key: "resume", value: { reset: true } }, + first.storageCollectionId + ); + const snapshot = await adapter.loadResumeSnapshot(target(next)); + expect(snapshot.rows).toEqual([]); + expect(snapshot.collectionMetadata).toEqual([{ key: "resume", value: { reset: true } }]); + expect((await adapter.loadResumeSnapshot(target(peer))).collectionMetadata).toEqual([ + { key: "resume", value: "old-cursor" }, + ]); + expect((await adapter.claimCacheGeneration("reset")).storageCollectionId).toBe( + next.storageCollectionId + ); + await expect( + adapter.rotateCacheGeneration("reset", next.claimId, undefined, first.storageCollectionId) + ).rejects.toThrow("changed generations"); + expect((await adapter.claimCacheGeneration("reset")).storageCollectionId).toBe( + next.storageCollectionId + ); + }); + + it("rolls back generation rotation if reset metadata cannot be serialized", async () => { + const h = use(); + const { adapter } = h; + const first = await adapter.claimCacheGeneration("reset-rollback"); + await adapter.applyCommittedTx(target(first), seed()); + await expect( + adapter.rotateCacheGeneration("reset-rollback", first.claimId, { + key: "invalid", + value: () => {}, + }) + ).rejects.toThrow(/./); + expect((await adapter.claimCacheGeneration("reset-rollback")).storageCollectionId).toBe( + first.storageCollectionId + ); + expect((await adapter.loadResumeSnapshot(target(first))).rows.map((r) => r.key)).toEqual([ + "seed", + ]); + }); + + it("expires a paused claim and recovers privately without displacing the current cache", async () => { + let now = 1_000; + const h = use({ cacheGenerationClaimTtlMs: 100, now: () => now }); + const { adapter } = h; + const old = await adapter.claimCacheGeneration("expired-cache-claim"); + const paused = await adapter.claimCacheGeneration("expired-cache-claim"); + await adapter.applyCommittedTx(target(old), seed("old")); + const head = await adapter.rotateCacheGeneration("expired-cache-claim", old.claimId); + await adapter.applyCommittedTx(target(head), seed("head")); + now = 1_090; + expect(await adapter.renewCacheGenerationClaim(head.storageCollectionId, head.claimId)).toBe( + 1_190 + ); + now = 1_110; + expect((await adapter.claimCacheGeneration("expired-cache-claim")).storageCollectionId).toBe( + head.storageCollectionId + ); + await expect(adapter.loadResumeSnapshot(target(paused))).rejects.toThrow("Persisted cache claim"); + expect( + Object.values(await h.footprint(old.storageCollectionId)).every((count) => count === 0) + ).toBe(true); + const recovery = await adapter.rotateCacheGeneration("expired-cache-claim", paused.claimId); + expect(recovery.storageCollectionId).not.toBe(head.storageCollectionId); + expect((await adapter.claimCacheGeneration("expired-cache-claim")).storageCollectionId).toBe( + head.storageCollectionId + ); + expect((await adapter.loadResumeSnapshot(target(head))).rows.map((r) => r.key)).toEqual(["head"]); + await adapter.releaseCacheGenerationClaim(recovery.claimId); + expect( + Object.values(await h.footprint(recovery.storageCollectionId)).every((count) => count === 0) + ).toBe(true); + }); + + it("keeps the current cache claimable when its expired claimant recovers", async () => { + let now = 1_000; + const { adapter } = use({ cacheGenerationClaimTtlMs: 100, now: () => now }); + const old = await adapter.claimCacheGeneration("expired-head-recovery"); + await adapter.applyCommittedTx(target(old), seed("old")); + now = 1_101; + expect( + await adapter.renewCacheGenerationClaim(old.storageCollectionId, old.claimId) + ).toBeUndefined(); + const repaired = await adapter.rotateCacheGeneration( + "expired-head-recovery", + old.claimId, + undefined, + old.storageCollectionId + ); + const next = await adapter.claimCacheGeneration("expired-head-recovery"); + expect(next.storageCollectionId).toBe(old.storageCollectionId); + expect(repaired.storageCollectionId).not.toBe(old.storageCollectionId); + expect((await adapter.loadResumeSnapshot(target(next))).rows.map((r) => r.key)).toEqual(["old"]); + await expect(adapter.loadResumeSnapshot(target(old))).rejects.toThrow("Persisted cache claim"); + }); + + it.each([false, true])( + "checks claim expiry on a warmed resume snapshot with includeRows=%s", + async (includeRows) => { + let now = 1_000; + const h = use({ cacheGenerationClaimTtlMs: 100, now: () => now }); + const claim = await h.adapter.claimCacheGeneration("resume"); + await h.adapter.applyCommittedTx(target(claim), seed()); + now = 1_099; + expect( + (await h.adapter.loadResumeSnapshot(target(claim), { includeRows })).rows + ).toHaveLength(includeRows ? 1 : 0); + now = 1_100; + await expect(h.adapter.loadResumeSnapshot(target(claim), { includeRows })).rejects.toThrow( + "no longer active" + ); + now = 1_101; + await expect(h.adapter.loadResumeSnapshot(target(claim), { includeRows })).rejects.toThrow( + "no longer active" + ); + } + ); + + it("rejects delayed writes and index changes from an expired claimant while its peer remains live", async () => { + let now = 1_000; + const h = use({ cacheGenerationClaimTtlMs: 100, now: () => now }); + const { adapter } = h; + const paused = await adapter.claimCacheGeneration("delayed"); + const peer = await adapter.claimCacheGeneration("delayed"); + await adapter.applyCommittedTx(target(peer), seed()); + now = 1_090; + await adapter.renewCacheGenerationClaim(peer.storageCollectionId, peer.claimId); + now = 1_100; + const footprint = await h.footprint(peer.storageCollectionId); + await expect(adapter.applyCommittedTx(target(paused), seed("late", 2))).rejects.toThrow( + "no longer active" + ); + await expect(adapter.ensureIndex(target(paused), "late", indexSpec)).rejects.toThrow( + "no longer active" + ); + await expect(adapter.markIndexRemoved(target(paused), "late")).rejects.toThrow( + "no longer active" + ); + expect(await h.footprint(peer.storageCollectionId)).toEqual(footprint); + expect((await adapter.loadResumeSnapshot(target(peer))).rows.map((r) => r.key)).toEqual(["seed"]); + }); + + // The exact finite grammar from upstream's fixed and generated retired-storage histories. + // Enumerate it entirely rather than adding fast-check as a dependency. + const histories = (["release", "expire", "renew"] as const).flatMap((oldClaim) => + (["release", "expire", "renew"] as const).flatMap((middleClaim) => + ([0, 2] as const).flatMap((rowCount) => + [1_170, 1_190, 1_209, 1_210, 1_229, 1_230].map((checkAtMs) => ({ + oldClaim, + middleClaim, + rowCount, + checkAtMs, + })) + ) + ) + ); + it.each(histories)( + "collects only unreachable retired storage: $oldClaim/$middleClaim, $rowCount rows, t=$checkAtMs", + async (history) => { + let now = 1_000; + const h = use({ cacheGenerationClaimTtlMs: 100, now: () => now }); + const { adapter } = h; + const logicalId = "bounded-retired-storage"; + const first = await adapter.claimCacheGeneration(logicalId); + now = 1_020; + const oldPeer = await adapter.claimCacheGeneration(logicalId); + const generations = [first]; + async function seedStorage(generation: number, claim: PersistedCacheGenerationClaim) { + await adapter.applyCommittedTx(target(claim), { + txId: `g${generation}-seed`, + term: generation + 1, + seq: 1, + rowVersion: 1, + truncate: true, + mutations: [ + ...Array.from({ length: history.rowCount }, (_, i) => ({ + type: "insert" as const, + key: `g${generation}-${i}`, + value: { id: `g${generation}-${i}` }, + })), + { + type: "delete", + key: `g${generation}-gone`, + value: { id: `g${generation}-gone` }, + }, + ], + collectionMetadataMutations: [ + { type: "set", key: "resume", value: `g${generation}-cursor` }, + ], + }); + await adapter.ensureIndex(target(claim), `g${generation}-index`, indexSpec); + return h.footprint(claim.storageCollectionId); + } + const footprints = [await seedStorage(0, first)]; + now = 1_040; + const middle = await adapter.rotateCacheGeneration(logicalId, first.claimId); + generations.push(middle); + now = 1_060; + const middlePeer = await adapter.claimCacheGeneration(logicalId); + footprints.push(await seedStorage(1, middle)); + now = 1_080; + const current = await adapter.rotateCacheGeneration(logicalId, middle.claimId); + generations.push(current); + footprints.push(await seedStorage(2, current)); + now = 1_110; + if (history.oldClaim === "release") + await adapter.releaseCacheGenerationClaim(oldPeer.claimId); + const oldRenewed = + history.oldClaim === "renew" + ? await adapter.renewCacheGenerationClaim( + oldPeer.storageCollectionId, + oldPeer.claimId + ) + : undefined; + expect(oldRenewed).toBe(history.oldClaim === "renew" ? 1_210 : undefined); + now = 1_130; + if (history.middleClaim === "release") + await adapter.releaseCacheGenerationClaim(middlePeer.claimId); + const middleRenewed = + history.middleClaim === "renew" + ? await adapter.renewCacheGenerationClaim( + middlePeer.storageCollectionId, + middlePeer.claimId + ) + : undefined; + expect(middleRenewed).toBe(history.middleClaim === "renew" ? 1_230 : undefined); + now = history.checkAtMs; + const checkpoint = await adapter.claimCacheGeneration(logicalId); + expect(checkpoint.storageCollectionId).toBe(current.storageCollectionId); + const peers = [oldPeer, middlePeer, checkpoint]; + for (let i = 0; i < generations.length; i++) { + const retained = + i === 2 || + (i === 0 + ? history.oldClaim === "renew" && now < 1_210 + : history.middleClaim === "renew" && now < 1_230); + const footprint = await h.footprint(generations[i]!.storageCollectionId); + // Claim counts change independently; retained data must be byte-for-byte stable. + const { claims: _before, ...before } = footprints[i]!; + const { claims: _after, ...after } = footprint; + const empty = Object.fromEntries(Object.keys(before).map((key) => [key, 0])); + expect(after).toEqual(retained ? before : empty); + const snapshot = retained + ? await adapter.loadResumeSnapshot(target(peers[i]!)) + : undefined; + expect( + snapshot?.rows.map((r) => r.key).sort((a, b) => String(a).localeCompare(String(b))) ?? + [] + ).toEqual( + retained ? Array.from({ length: history.rowCount }, (_, j) => `g${i}-${j}`) : [] + ); + expect(snapshot?.collectionMetadata ?? []).toEqual( + retained ? [{ key: "resume", value: `g${i}-cursor` }] : [] + ); + } + } + ); + + it("does not publish startup metadata read under an expired cache claim", async () => { + let now = 1_000; + const h = use({ cacheGenerationClaimTtlMs: 1_000_000, now: () => now }); + const { adapter } = h; + const seedClaim = await adapter.claimCacheGeneration("expired-startup-metadata"); + await adapter.applyCommittedTx(target(seedClaim), { + ...seed(), + collectionMetadataMutations: [{ type: "set", key: "resume", value: "old-resume" }], + }); + await adapter.releaseCacheGenerationClaim(seedClaim.claimId); + let completeRead!: () => void; + const heldRead = new Promise((resolve) => { + completeRead = resolve; + }); + let readEntered!: () => void; + const entered = new Promise((resolve) => { + readEntered = resolve; + }); + let hold = true; + const wrap = + (loan: Pick) => + async (...args: Parameters) => { + const snapshot = await loan.loadResumeSnapshot(...args); + if (hold) { + hold = false; + readEntered(); + await heldRead; + } + return snapshot; + }; + const originalScope = adapter.runInHydrationScope?.bind(adapter); + const originalRead = adapter.loadResumeSnapshot.bind(adapter); + if (originalScope) + adapter.runInHydrationScope = (task) => + originalScope((loan) => task({ ...loan, loadResumeSnapshot: wrap(loan) })); + else adapter.loadResumeSnapshot = wrap({ loadResumeSnapshot: originalRead }); + const resumeValues: unknown[] = []; + const collection = createCollection( + persistedCollectionOptions<{ id: string }, string>({ + id: "expired-startup-metadata", + syncMode: "on-demand", + getKey: (row) => row.id, + sync: { + sync: (source) => { + resumeValues.push(source.metadata?.collection.get("resume")); + source.markReady(); + return { restartAfterScopedRecovery: () => {} }; + }, + }, + persistence: { adapter }, + }) + ); + try { + collection.startSyncImmediate(); + await entered; + expect(resumeValues).toEqual([]); + now += 1_000_001; + completeRead(); + await vi.waitFor(() => expect(collection.status).toBe("ready")); + expect(resumeValues).toEqual([undefined]); + } finally { + completeRead(); + await collection.cleanup(); + } + }); + + it("rejects old and queued source commits after claim expiry and permits new writes", async () => { + let now = 1_000; + const h = use({ cacheGenerationClaimTtlMs: 1_000_000, now: () => now }); + let source!: Parameters["sync"]>[0]; + let restarted!: () => void; + const recovery = new Promise((resolve) => { + restarted = resolve; + }); + const collection = createCollection( + persistedCollectionOptions<{ id: string }, string>({ + id: "expired-source-write", + syncMode: "on-demand", + getKey: (row) => row.id, + sync: { + sync: (params) => { + source = params; + params.markReady(); + return { restartAfterScopedRecovery: (rotated) => rotated.then(restarted) }; + }, + }, + persistence: { adapter: h.adapter }, + }) + ); + try { + collection.startSyncImmediate(); + await vi.waitFor(() => expect(collection.status).toBe("ready")); + await source.metadata!.persistence!.hydrateBaseline(); + now += 1_000_001; + source.begin(); + source.write({ type: "insert", value: { id: "old" } }); + const old = Promise.resolve(source.commit()); + source.begin(); + source.write({ type: "insert", value: { id: "second-old" } }); + const queued = Promise.resolve(source.commit()); + await Promise.all([ + expect(old).rejects.toThrow("aborted"), + expect(queued).rejects.toThrow("aborted"), + ]); + await recovery; + await collection._sync.loadSubset({ limit: 0 }); + source.begin(); + source.write({ type: "insert", value: { id: "fresh" } }); + await whenSyncAccepted(source.commit()); + expect(collection.get("old")).toBeUndefined(); + expect(collection.get("second-old")).toBeUndefined(); + expect(collection.get("fresh")).toMatchObject({ id: "fresh" }); + const claims = await h.claims("expired-source-write"); + expect((await h.adapter.scanRows(target(claims[0]!))).map((row) => row.key)).toEqual([ + "fresh", + ]); + } finally { + await collection.cleanup(); + } + }); + + it("reloads an active subset when its renewal timer finds an expired claim", async () => { + let now = 1_000; + const h = use({ cacheGenerationClaimTtlMs: 100, now: () => now }); + const sourceLoads: Array = []; + const collection = createCollection( + persistedCollectionOptions<{ id: string }, string>({ + id: "timer-claim-reload", + syncMode: "on-demand", + getKey: (row) => row.id, + sync: { + sync: (source) => { + source.markReady(); + return { + restartAfterScopedRecovery: () => {}, + loadSubset: (options) => { + sourceLoads.push(options.refetch); + return true; + }, + }; + }, + }, + persistence: { adapter: h.adapter }, + }) + ); + try { + collection.startSyncImmediate(); + await collection._sync.loadSubset({ limit: 1 }); + expect(sourceLoads).toEqual([undefined]); + const [old] = await h.claims("timer-claim-reload"); + now = 1_101; + await vi.waitFor(() => expect(sourceLoads).toHaveLength(2)); + expect(sourceLoads[1]).toBe(true); + const [current] = await h.claims("timer-claim-reload"); + expect(current!.storageCollectionId).not.toBe(old!.storageCollectionId); + } finally { + await collection.cleanup(); + } + }); + + it("restarts a custom source after a real adapter cache claim expires", async () => { + type Row = { id: string; title: string }; + let now = 1_000; + const h = use({ cacheGenerationClaimTtlMs: 300_000, now: () => now }); + const sessions: Array["sync"]>[0]> = []; + let cleanups = 0; + const collection = createCollection( + persistedCollectionOptions({ + id: "custom-expired-cache", + syncMode: "on-demand", + getKey: (row) => row.id, + sync: { + sync: (params) => { + const session = sessions.push(params); + params.markReady(); + return { + cleanup: () => { + cleanups++; + }, + loadSubset: async () => { + params.begin(); + params.write({ + type: "update", + value: { id: `row-${session}`, title: `Session ${session}` }, + }); + await whenSyncAccepted(params.commit()); + }, + }; + }, + }, + persistence: { adapter: h.adapter }, + }) + ); + try { + collection.startSyncImmediate(); + await vi.waitFor(() => expect(collection.status).toBe("ready")); + await collection._sync.loadSubset({ limit: 1 }); + expect(collection.get("row-1")).toMatchObject({ id: "row-1" }); + const [old] = await h.claims("custom-expired-cache"); + now += 300_001; + await collection._sync.loadSubset({ limit: 2 }); + expect(sessions).toHaveLength(2); + expect(cleanups).toBe(1); + expect(collection.get("row-1")).toBeUndefined(); + expect(collection.get("row-2")).toMatchObject({ id: "row-2" }); + const [current] = await h.claims("custom-expired-cache"); + expect(current!.storageCollectionId).not.toBe(old!.storageCollectionId); + sessions[0]!.begin(); + sessions[0]!.write({ type: "update", value: { id: "late-old", title: "Retired session" } }); + await sessions[0]!.commit(); + expect(collection.get("late-old")).toBeUndefined(); + expect((await h.adapter.loadSubset(target(current!), {})).map((r) => r.key)).toEqual([ + "row-2", + ]); + } finally { + await collection.cleanup(); + } + }); + + it("moves scoped recovery to a new persisted cache generation", async () => { + const h = use(); + const { adapter } = h; + const seedClaim = await adapter.claimCacheGeneration("scoped-generation-boundary"); + await adapter.applyCommittedTx(target(seedClaim), seed("stale")); + let source!: Parameters["sync"]>[0]; + const collection = createCollection( + persistedCollectionOptions<{ id: string }, string>({ + id: "scoped-generation-boundary", + syncMode: "on-demand", + getKey: (row) => row.id, + sync: { + sync: (params) => { + source = params; + params.markReady(); + return { loadSubset: () => true, restartAfterScopedRecovery: () => {} }; + }, + }, + persistence: { adapter }, + }) + ); + try { + collection.startSyncImmediate(); + await vi.waitFor(() => expect(collection.status).toBe("ready")); + await collection._sync.loadSubset({}); + expect(collection.get("stale")).toMatchObject({ id: "stale" }); + const capability = source.metadata!.persistence!; + await capability.startScopedRecovery!({ key: "resume", value: { reset: true } }); + await collection._sync.loadSubset({}); + source.begin(); + source.write({ type: "insert", value: { id: "fresh" } }); + await whenSyncAccepted(source.commit()); + expect(collection.get("stale")).toBeUndefined(); + expect(collection.get("fresh")).toMatchObject({ id: "fresh" }); + expect((await adapter.loadResumeSnapshot(target(seedClaim))).rows.map((r) => r.key)).toEqual([ + "stale", + ]); + const claims = await h.claims("scoped-generation-boundary"); + const current = claims.find( + (claim) => claim.storageCollectionId !== seedClaim.storageCollectionId + )!; + expect((await adapter.loadResumeSnapshot(target(current))).rows.map((r) => r.key)).toEqual([ + "fresh", + ]); + } finally { + await collection.cleanup(); + } + }); + }); +} diff --git a/packages/db-indexeddb-persistence/src/contracts/managedCacheRaces.ts b/packages/db-indexeddb-persistence/src/contracts/managedCacheRaces.ts new file mode 100644 index 00000000..fa4836f7 --- /dev/null +++ b/packages/db-indexeddb-persistence/src/contracts/managedCacheRaces.ts @@ -0,0 +1,298 @@ +import { IR } from "@tanstack/db"; +import { eagerStorageTarget, type PersistedStorageTarget } from "@tanstack/db-sqlite-persistence-core"; +import { openDB } from "idb"; +// IndexedDB equivalents of upstream SQLite's held-registration and read-revocation histories. +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; +import { IndexedDBPersistenceAdapter } from "../IndexedDBPersistenceAdapter.js"; +import { indexedDBManagedCacheHarness } from "./indexedDBManagedCacheHarness.js"; +import { target, type ManagedAdapter, type ManagedCacheHarness } from "./managedCacheContract.js"; + +function deferred() { + let resolve!: () => void; + const promise = new Promise((complete) => { + resolve = complete; + }); + return { promise, resolve }; +} +const write = (key: string) => ({ + txId: key, + term: 1, + seq: 1, + rowVersion: 1, + mutations: [{ type: "insert" as const, key, value: { id: key } }], +}); +const indexSpec = { expressionSql: [JSON.stringify(new IR.PropRef(["id"]))] }; +const operations = { + resume: (adapter: ManagedAdapter, storage: PersistedStorageTarget) => adapter.loadResumeSnapshot(storage), + metadataResume: (adapter: ManagedAdapter, storage: PersistedStorageTarget) => + adapter.loadResumeSnapshot(storage, { includeRows: false }), + subset: (adapter: ManagedAdapter, storage: PersistedStorageTarget) => adapter.loadSubset(storage, {}), + cursor: (adapter: ManagedAdapter, storage: PersistedStorageTarget) => + adapter.loadSubset(storage, { + cursor: { whereCurrent: new IR.Value(true), whereFrom: new IR.Value(true) }, + limit: 1, + }), + metadata: (adapter: ManagedAdapter, storage: PersistedStorageTarget) => + adapter.loadCollectionMetadata(storage), + scan: (adapter: ManagedAdapter, storage: PersistedStorageTarget) => adapter.scanRows(storage), + metadataScan: (adapter: ManagedAdapter, storage: PersistedStorageTarget) => + adapter.scanRows(storage, { metadataOnly: true }), + position: (adapter: ManagedAdapter, storage: PersistedStorageTarget) => + adapter.getStreamPosition(storage), + replay: (adapter: ManagedAdapter, storage: PersistedStorageTarget) => adapter.pullSince(storage, 0), + write: (adapter: ManagedAdapter, storage: PersistedStorageTarget) => + adapter.applyCommittedTx(storage, write("late")), + index: (adapter: ManagedAdapter, storage: PersistedStorageTarget) => + adapter.ensureIndex(storage, "late-index", indexSpec), + removeIndex: (adapter: ManagedAdapter, storage: PersistedStorageTarget) => + adapter.markIndexRemoved(storage, "late-index"), +}; +type Initialization = { collectionDatabase: (...args: unknown[]) => Promise }; +export function runManagedCacheRaces(name: string): void { + const harnesses: ManagedCacheHarness[] = []; + function use(options = {}) { + const h = indexedDBManagedCacheHarness(options); + harnesses.push(h); + return h; + } + afterEach(async () => { + vi.restoreAllMocks(); + for (const h of harnesses.splice(0)) await h.cleanup(); + }); + describe(`${name}`, () => { + it("upgrades a v3 database without losing eager rows, metadata, or pending actions", async () => { + const name = `managed-upgrade-${crypto.randomUUID()}`; + const old = await openDB(name, 3, { + upgrade(database) { + const rows = database.createObjectStore("rows", { keyPath: "id" }); + rows.createIndex("collectionId", "collectionId"); + rows.createIndex("metadata", ["collectionId", "hasMetadata"]); + const transactions = database.createObjectStore("transactions", { keyPath: "id" }); + transactions.createIndex("collectionId", "collectionId"); + transactions.createIndex("version", ["collectionId", "rowVersion"]); + transactions.createIndex("position", ["collectionId", "term", "seq"]); + database.createObjectStore("streams", { keyPath: "collectionId" }); + database + .createObjectStore("collectionMetadata", { keyPath: "id" }) + .createIndex("collectionId", "collectionId"); + database + .createObjectStore("indexDefinitions", { keyPath: ["collectionId", "signature"] }) + .createIndex("collectionId", "collectionId"); + const entries = database.createObjectStore("indexEntries", { + keyPath: ["collectionId", "signature", "valueType", "rowId"], + }); + entries.createIndex("collectionId", "collectionId"); + entries.createIndex("index", ["collectionId", "signature"]); + entries.createIndex("lookup", [ + "collectionId", + "signature", + "valueType", + "value", + "rowId", + ]); + }, + }); + await old.put("rows", { + id: JSON.stringify(["outbox", "s:pending"]), + collectionId: "outbox", + key: "pending", + value: { id: "pending", action: "create" }, + }); + await old.put("streams", { + collectionId: "outbox", + schemaVersion: 1, + latestTerm: 1, + latestSeq: 1, + latestRowVersion: 1, + }); + await old.put("collectionMetadata", { + id: JSON.stringify(["outbox", "resume"]), + collectionId: "outbox", + key: "resume", + value: "legacy-cursor", + }); + old.close(); + const adapter = new IndexedDBPersistenceAdapter({ databaseName: name }); + try { + const snapshot = await adapter.loadResumeSnapshot(eagerStorageTarget("outbox")); + expect(snapshot.rows).toEqual([ + { key: "pending", value: { id: "pending", action: "create" }, metadata: undefined }, + ]); + expect(snapshot.collectionMetadata).toEqual([{ key: "resume", value: "legacy-cursor" }]); + expect(snapshot.latestRowVersion).toBe(1); + const claim = await adapter.claimCacheGeneration("outbox"); + expect(claim.storageCollectionId).not.toBe("outbox"); + expect((await adapter.loadResumeSnapshot(target(claim))).rows).toEqual([]); + expect( + (await adapter.loadResumeSnapshot(eagerStorageTarget("outbox"))).rows.map((r) => r.key) + ).toEqual(["pending"]); + const database = await openDB(name); + expect(database.version).toBe(4); + expect(Array.from(database.objectStoreNames)).toEqual( + expect.arrayContaining(["cacheGenerations", "cacheHeads", "cacheClaims"]) + ); + database.close(); + } finally { + adapter.close(); + await new Promise((resolve, reject) => { + const request = indexedDB.deleteDatabase(name); + request.onsuccess = () => resolve(); + request.onerror = () => + reject(request.error ?? new Error("Could not delete migration database")); + }); + } + }); + + it("serializes claims and conditional rotations across independent connections", async () => { + const h = use(); + const peer = h.reopen(); + const claims = await Promise.all( + Array.from({ length: 12 }, (_, i) => + (i % 2 ? peer : h.adapter).claimCacheGeneration("concurrent") + ) + ); + expect(new Set(claims.map((claim) => claim.storageCollectionId)).size).toBe(1); + expect(new Set(claims.map((claim) => claim.claimId)).size).toBe(12); + const first = claims[0]!; + const rotations = await Promise.allSettled( + [h.adapter, peer].map((adapter) => + adapter.rotateCacheGeneration( + "concurrent", + first.claimId, + undefined, + first.storageCollectionId + ) + ) + ); + expect(rotations.filter((result) => result.status === "fulfilled")).toHaveLength(1); + expect(rotations.filter((result) => result.status === "rejected")).toHaveLength(1); + expect((await peer.claimCacheGeneration("concurrent")).storageCollectionId).not.toBe( + first.storageCollectionId + ); + await h.adapter.applyCommittedTx(target(claims[1]!), write("old-peer")); + expect((await peer.loadResumeSnapshot(target(claims[1]!))).rows.map((row) => row.key)).toEqual([ + "old-peer", + ]); + }); + + const histories = Object.entries(operations).flatMap(([operation, run]) => + (["before", "after"] as const).map((phase) => ({ operation, run, phase })) + ); + it.each(histories)( + "does not resurrect collected storage when $operation pauses $phase initialization", + async ({ run, phase }) => { + const h = use(); + const { adapter } = h; + const moving = await adapter.claimCacheGeneration("held-registration"); + const warm = await adapter.claimCacheGeneration("held-registration"); + await adapter.applyCommittedTx(target(moving), write("old")); + const reader = h.reopen(); + const internals = reader as unknown as Initialization; + const original = internals.collectionDatabase.bind(reader); + const entered = deferred(); + const released = deferred(); + vi.spyOn(internals, "collectionDatabase").mockImplementationOnce(async (...args) => { + const result = phase === "after" ? await original(...args) : undefined; + entered.resolve(); + await released.promise; + return phase === "after" ? result : original(...args); + }); + const read = run(reader, target(warm)); + void read.catch(() => undefined); + try { + await entered.promise; + const current = await adapter.rotateCacheGeneration("held-registration", moving.claimId); + await adapter.applyCommittedTx(target(current), write("current")); + await adapter.releaseCacheGenerationClaim(warm.claimId); + const footprint = await h.footprint(moving.storageCollectionId); + expect(Object.values(footprint).every((count) => count === 0)).toBe(true); + released.resolve(); + await expect(read).rejects.toThrow("no longer active"); + expect(await h.footprint(moving.storageCollectionId)).toEqual(footprint); + expect( + (await adapter.loadResumeSnapshot(target(current))).rows.map((r) => r.key) + ).toEqual(["current"]); + } finally { + released.resolve(); + await Promise.allSettled([read]); + } + } + ); + + const reads = Object.entries(operations).map(([operation, run]) => ({ operation, run })); + it.each(reads)( + "fences $operation after registration when its claim expires while a peer remains live", + async ({ run }) => { + let now = 1_000; + const h = use({ cacheGenerationClaimTtlMs: 100, now: () => now }); + const { adapter } = h; + const paused = await adapter.claimCacheGeneration("read-claim"); + const warm = await adapter.claimCacheGeneration("read-claim"); + await adapter.applyCommittedTx(target(warm), write("warm")); + const reader = h.reopen(); + const internals = reader as unknown as Initialization; + const original = internals.collectionDatabase.bind(reader); + const entered = deferred(); + const released = deferred(); + vi.spyOn(internals, "collectionDatabase").mockImplementationOnce(async (...args) => { + const result = await original(...args); + entered.resolve(); + await released.promise; + return result; + }); + const read = run(reader, target(paused)); + void read.catch(() => undefined); + try { + await entered.promise; + now = 1_090; + await adapter.renewCacheGenerationClaim(warm.storageCollectionId, warm.claimId); + now = 1_100; + const footprint = await h.footprint(warm.storageCollectionId); + released.resolve(); + await expect(read).rejects.toThrow("no longer active"); + expect(await h.footprint(warm.storageCollectionId)).toEqual(footprint); + expect((await adapter.loadResumeSnapshot(target(warm))).rows.map((r) => r.key)).toEqual([ + "warm", + ]); + } finally { + released.resolve(); + await Promise.allSettled([read]); + } + } + ); + + it("preserves a warm generation when an expired reader would reset its schema", async () => { + let now = 1_000; + const h = use({ cacheGenerationClaimTtlMs: 100, now: () => now }); + const claim = await h.adapter.claimCacheGeneration("stale-schema-reset"); + const warm = await h.adapter.claimCacheGeneration("stale-schema-reset"); + await h.adapter.applyCommittedTx(target(claim), { + ...write("warm"), + collectionMetadataMutations: [{ type: "set", key: "resume", value: "cursor" }], + }); + const reader = h.reopen({ schemaVersion: 2, schemaMismatchPolicy: "sync-present-reset" }); + now = 1_090; + await h.adapter.renewCacheGenerationClaim(warm.storageCollectionId, warm.claimId); + now = 1_100; + const footprint = await h.footprint(warm.storageCollectionId); + await expect(reader.loadResumeSnapshot(target(claim))).rejects.toThrow("no longer active"); + expect(await h.footprint(warm.storageCollectionId)).toEqual(footprint); + expect((await h.adapter.loadResumeSnapshot(target(warm))).collectionMetadata).toEqual([ + { key: "resume", value: "cursor" }, + ]); + }); + + it("keeps eager storage usable after managed cache activity", async () => { + const h = use(); + const claim = await h.adapter.claimCacheGeneration("managed"); + await h.adapter.applyCommittedTx(target(claim), write("managed")); + await h.adapter.applyCommittedTx(eagerStorageTarget("local-outbox"), write("pending")); + await h.adapter.rotateCacheGeneration("managed", claim.claimId); + expect( + (await h.adapter.loadResumeSnapshot(eagerStorageTarget("local-outbox"))).rows.map( + (row) => row.key + ) + ).toEqual(["pending"]); + }); + }); +} diff --git a/packages/db-indexeddb-persistence/src/contracts/persistenceContract.ts b/packages/db-indexeddb-persistence/src/contracts/persistenceContract.ts index 09011f8d..5cb4a6bd 100644 --- a/packages/db-indexeddb-persistence/src/contracts/persistenceContract.ts +++ b/packages/db-indexeddb-persistence/src/contracts/persistenceContract.ts @@ -1,11 +1,12 @@ // Adapted from TanStack/db, MIT licensed; see contracts/NOTICE.md for source and changes. import { IR } from "@tanstack/db"; +import { eagerStorageTarget, type PersistedStorageTarget } from "@tanstack/db-sqlite-persistence-core"; import { afterEach, describe, expect, it } from "vite-plus/test"; import type { PersistenceAdapter, PersistencePullSinceResult } from "@tanstack/db-sqlite-persistence-core"; type Todo = { id: string; title: string; createdAt: string; score: number }; export type ContractAdapter = PersistenceAdapter & { - pullSince(collectionId: string, fromRowVersion: number): Promise; + pullSince(target: PersistedStorageTarget, fromRowVersion: number): Promise; }; export interface ContractHarness { adapter: ContractAdapter; @@ -69,7 +70,7 @@ export function runPersistenceContract( }, ]; - await adapter.applyCommittedTx(collectionId, { + await adapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: `seed-1`, term: 1, seq: 1, @@ -81,7 +82,7 @@ export function runPersistenceContract( })), }); - const filtered = await adapter.loadSubset(collectionId, { + const filtered = await adapter.loadSubset(eagerStorageTarget(collectionId), { where: new IR.Func(`and`, [ new IR.Func(`or`, [ new IR.Func(`like`, [new IR.PropRef([`title`]), new IR.Value(`%Task%`)]), @@ -115,7 +116,7 @@ export function runPersistenceContract( }, ]); - const withInEmpty = await adapter.loadSubset(collectionId, { + const withInEmpty = await adapter.loadSubset(eagerStorageTarget(collectionId), { where: new IR.Func(`in`, [new IR.PropRef([`id`]), new IR.Value([] as Array)]), }); expect(withInEmpty).toEqual([]); @@ -128,7 +129,7 @@ export function runPersistenceContract( const firstBigInt = BigInt(`9007199254740992`); const secondBigInt = BigInt(`9007199254740997`); - await typedAdapter.applyCommittedTx(collectionId, { + await typedAdapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: `seed-typed-values`, term: 1, seq: 1, @@ -159,14 +160,14 @@ export function runPersistenceContract( // Filter-only subset reads may legally be supersets; an indexed typed // predicate provides the exact candidate set exercised by this upstream case. - await adapter.ensureIndex(collectionId, `bigint`, { + await adapter.ensureIndex(eagerStorageTarget(collectionId), `bigint`, { expressionSql: [JSON.stringify(new IR.PropRef([`largeViewCount`]))], }); - await adapter.ensureIndex(collectionId, `date`, { + await adapter.ensureIndex(eagerStorageTarget(collectionId), `date`, { expressionSql: [JSON.stringify(new IR.PropRef([`createdAt`]))], }); - const bigintRows = await typedAdapter.loadSubset(collectionId, { + const bigintRows = await typedAdapter.loadSubset(eagerStorageTarget(collectionId), { where: new IR.Func(`gt`, [ new IR.PropRef([`largeViewCount`]), new IR.Value(BigInt(`9007199254740993`)), @@ -174,7 +175,7 @@ export function runPersistenceContract( }); expect(bigintRows.map((row) => row.key)).toEqual([`2`]); - const dateRows = await typedAdapter.loadSubset(collectionId, { + const dateRows = await typedAdapter.loadSubset(eagerStorageTarget(collectionId), { where: new IR.Func(`gt`, [ new IR.PropRef([`createdAt`]), new IR.Value(new Date(`2026-01-02T12:00:00.000Z`)), @@ -182,7 +183,7 @@ export function runPersistenceContract( }); expect(dateRows.map((row) => row.key)).toEqual([`2`]); - const restoredRows = await typedAdapter.loadSubset(collectionId, { + const restoredRows = await typedAdapter.loadSubset(eagerStorageTarget(collectionId), { where: new IR.Func(`eq`, [ new IR.Func(`date`, [new IR.PropRef([`createdAt`])]), new IR.Value(`2026-01-02`), @@ -197,7 +198,7 @@ export function runPersistenceContract( const { adapter } = registerContractHarness(); const collectionId = `todos`; - await adapter.applyCommittedTx(collectionId, { + await adapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: `tx-1`, term: 1, seq: 1, @@ -215,7 +216,7 @@ export function runPersistenceContract( }, ], }); - await adapter.applyCommittedTx(collectionId, { + await adapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: `tx-1-replay`, term: 1, seq: 1, @@ -234,12 +235,12 @@ export function runPersistenceContract( ], }); - const replay = await adapter.pullSince(collectionId, 0); + const replay = await adapter.pullSince(eagerStorageTarget(collectionId), 0); expect(replay.requiresFullReload).toBe(false); if (replay.requiresFullReload) throw new Error("Expected incremental replay"); expect(replay.deltas).toHaveLength(1); - await adapter.applyCommittedTx(collectionId, { + await adapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: `tx-2`, term: 1, seq: 2, @@ -258,7 +259,7 @@ export function runPersistenceContract( ], }); - const updated = await adapter.loadSubset(collectionId, { + const updated = await adapter.loadSubset(eagerStorageTarget(collectionId), { where: new IR.Func(`eq`, [new IR.PropRef([`id`]), new IR.Value(`1`)]), }); expect(updated).toEqual([ @@ -273,7 +274,7 @@ export function runPersistenceContract( }, ]); - await adapter.applyCommittedTx(collectionId, { + await adapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: `tx-3`, term: 1, seq: 3, @@ -292,10 +293,10 @@ export function runPersistenceContract( ], }); - const remainingRows = await adapter.loadSubset(collectionId, {}); + const remainingRows = await adapter.loadSubset(eagerStorageTarget(collectionId), {}); expect(remainingRows).toEqual([]); - const deletion = await adapter.pullSince(collectionId, 2); + const deletion = await adapter.pullSince(eagerStorageTarget(collectionId), 2); expect(deletion).toMatchObject({ latestRowVersion: 3, requiresFullReload: false, @@ -309,7 +310,7 @@ export function runPersistenceContract( const collectionId = `atomicity`; await expect( - adapter.applyCommittedTx(collectionId, { + adapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: `atomicity-1`, term: 1, seq: 1, @@ -341,10 +342,10 @@ export function runPersistenceContract( }) ).rejects.toThrow(Error); - const rows = await adapter.loadSubset(collectionId, {}); + const rows = await adapter.loadSubset(eagerStorageTarget(collectionId), {}); expect(rows).toEqual([]); - expect(await adapter.getStreamPosition?.(collectionId)).toMatchObject({ + expect(await adapter.getStreamPosition?.(eagerStorageTarget(collectionId))).toMatchObject({ latestRowVersion: 0, latestSeq: 0, }); @@ -354,7 +355,7 @@ export function runPersistenceContract( const { adapter, unserializable } = registerContractHarness(); const collectionId = `metadata-roundtrip`; - await adapter.applyCommittedTx(collectionId, { + await adapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: `metadata-1`, term: 1, seq: 1, @@ -394,7 +395,7 @@ export function runPersistenceContract( ], }); - const rows = await adapter.loadSubset(collectionId, {}); + const rows = await adapter.loadSubset(eagerStorageTarget(collectionId), {}); expect(rows).toEqual([ { key: `1`, @@ -414,7 +415,9 @@ export function runPersistenceContract( }, ]); - const collectionMetadata = await adapter.loadCollectionMetadata?.(collectionId); + const collectionMetadata = await adapter.loadCollectionMetadata?.( + eagerStorageTarget(collectionId) + ); expect(collectionMetadata).toEqual([ { key: `electric:resume`, @@ -429,7 +432,7 @@ export function runPersistenceContract( ]); await expect( - adapter.applyCommittedTx(collectionId, { + adapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: `metadata-2`, term: 1, seq: 2, @@ -458,11 +461,13 @@ export function runPersistenceContract( }) ).rejects.toThrow(Error); - const rowsAfterFailure = await adapter.loadSubset(collectionId, {}); + const rowsAfterFailure = await adapter.loadSubset(eagerStorageTarget(collectionId), {}); expect(rowsAfterFailure).toEqual(rows); - expect(await adapter.loadCollectionMetadata?.(collectionId)).toEqual(collectionMetadata); - expect(await adapter.getStreamPosition?.(collectionId)).toMatchObject({ + expect(await adapter.loadCollectionMetadata?.(eagerStorageTarget(collectionId))).toEqual( + collectionMetadata + ); + expect(await adapter.getStreamPosition?.(eagerStorageTarget(collectionId))).toMatchObject({ latestRowVersion: 1, latestSeq: 1, }); @@ -472,7 +477,7 @@ export function runPersistenceContract( const { adapter } = registerContractHarness(); const collectionId = `truncate-metadata-roundtrip`; - await adapter.applyCommittedTx(collectionId, { + await adapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: `seed-1`, term: 1, seq: 1, @@ -508,7 +513,7 @@ export function runPersistenceContract( ], }); - await adapter.applyCommittedTx(collectionId, { + await adapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: `truncate-2`, term: 1, seq: 2, @@ -542,7 +547,7 @@ export function runPersistenceContract( ], }); - expect(await adapter.loadSubset(collectionId, {})).toEqual([ + expect(await adapter.loadSubset(eagerStorageTarget(collectionId), {})).toEqual([ { key: `2`, value: { @@ -557,7 +562,7 @@ export function runPersistenceContract( }, ]); - expect(await adapter.loadCollectionMetadata?.(collectionId)).toEqual([ + expect(await adapter.loadCollectionMetadata?.(eagerStorageTarget(collectionId))).toEqual([ { key: `electric:resume`, value: { @@ -574,7 +579,7 @@ export function runPersistenceContract( }); const collectionId = `todos`; - await adapter.applyCommittedTx(collectionId, { + await adapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: `seed-pull`, term: 1, seq: 1, @@ -602,7 +607,7 @@ export function runPersistenceContract( }, ], }); - await adapter.applyCommittedTx(collectionId, { + await adapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: `seed-pull-2`, term: 1, seq: 2, @@ -621,7 +626,7 @@ export function runPersistenceContract( ], }); - const delta = await adapter.pullSince(collectionId, 1); + const delta = await adapter.pullSince(eagerStorageTarget(collectionId), 1); if (delta.requiresFullReload) { throw new Error(`Expected key-level delta, received full reload`); } @@ -638,7 +643,7 @@ export function runPersistenceContract( }, ]); - const fullReload = await adapter.pullSince(collectionId, 0); + const fullReload = await adapter.pullSince(eagerStorageTarget(collectionId), 0); expect(fullReload.requiresFullReload).toBe(true); }); @@ -646,7 +651,7 @@ export function runPersistenceContract( const { adapter } = registerContractHarness(); const collectionId = `scan-and-replay`; - await adapter.applyCommittedTx(collectionId, { + await adapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: `scan-seed-1`, term: 1, seq: 1, @@ -673,7 +678,7 @@ export function runPersistenceContract( ], }); - const scannedRows = await adapter.scanRows?.(collectionId, { + const scannedRows = await adapter.scanRows?.(eagerStorageTarget(collectionId), { metadataOnly: true, }); expect(scannedRows).toEqual([ @@ -695,7 +700,7 @@ export function runPersistenceContract( }, ]); - await adapter.applyCommittedTx(collectionId, { + await adapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: `scan-seed-2`, term: 1, seq: 2, @@ -726,7 +731,7 @@ export function runPersistenceContract( ], }); - const replayDelta = await adapter.pullSince(collectionId, 1); + const replayDelta = await adapter.pullSince(eagerStorageTarget(collectionId), 1); if (replayDelta.requiresFullReload) { throw new Error(`Expected replay delta, received full reload`); } @@ -768,7 +773,7 @@ export function runPersistenceContract( const { adapter } = registerContractHarness(); const collectionId = `todos`; - await adapter.applyCommittedTx(collectionId, { + await adapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: `seed-cursor`, term: 1, seq: 1, @@ -817,7 +822,7 @@ export function runPersistenceContract( ], }); - const rows = await adapter.loadSubset(collectionId, { + const rows = await adapter.loadSubset(eagerStorageTarget(collectionId), { orderBy: [ { expression: new IR.PropRef([`score`]), @@ -862,7 +867,7 @@ export function runPersistenceContract( const { adapter } = registerContractHarness(); const collectionId = `custom-collation-order-${caseName}`; - await adapter.applyCommittedTx(collectionId, { + await adapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: `seed-custom-collation`, term: 1, seq: 1, @@ -874,7 +879,7 @@ export function runPersistenceContract( })), }); - const rows = await adapter.loadSubset(collectionId, { + const rows = await adapter.loadSubset(eagerStorageTarget(collectionId), { orderBy: [ { expression: new IR.PropRef([`title`]), @@ -896,7 +901,7 @@ export function runPersistenceContract( const { adapter } = registerContractHarness(); const collectionId = `mixed-keys`; - await adapter.applyCommittedTx(collectionId, { + await adapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: `mixed-1`, term: 1, seq: 1, @@ -925,7 +930,7 @@ export function runPersistenceContract( ], }); - const rows = await adapter.loadSubset(collectionId, {}); + const rows = await adapter.loadSubset(eagerStorageTarget(collectionId), {}); expect(rows).toHaveLength(2); expect(rows.some((row) => row.key === 1)).toBe(true); expect(rows.some((row) => row.key === `1`)).toBe(true); diff --git a/packages/db-indexeddb-persistence/src/indexeddb-persistence.test.ts b/packages/db-indexeddb-persistence/src/indexeddb-persistence.test.ts index 7cecebf3..adc19913 100644 --- a/packages/db-indexeddb-persistence/src/indexeddb-persistence.test.ts +++ b/packages/db-indexeddb-persistence/src/indexeddb-persistence.test.ts @@ -1,5 +1,6 @@ -import "fake-indexeddb/auto"; import { createCollection } from "@tanstack/db"; +import "fake-indexeddb/auto"; +import { eagerStorageTarget } from "@tanstack/db-sqlite-persistence-core"; import { persistedCollectionOptions, SingleProcessCoordinator, @@ -76,13 +77,18 @@ describe("createIndexedDBPersistence", () => { const first = resolve({ collectionId: "first", mode: "sync-absent", schemaVersion: 2 }).adapter; const second = resolve({ collectionId: "second", mode: "sync-absent", schemaVersion: 7 }).adapter; try { - await Promise.all([first.loadResumeSnapshot("first"), second.loadResumeSnapshot("second")]); + await Promise.all([ + first.loadResumeSnapshot(eagerStorageTarget("first")), + second.loadResumeSnapshot(eagerStorageTarget("second")), + ]); expect(open).toHaveBeenCalledOnce(); (first as import("./IndexedDBPersistenceAdapter.js").IndexedDBPersistenceAdapter).close(); - await expect(first.loadResumeSnapshot("first")).rejects.toThrow("closed"); - await expect(second.loadResumeSnapshot("second")).resolves.toMatchObject({ rows: [] }); + await expect(first.loadResumeSnapshot(eagerStorageTarget("first"))).rejects.toThrow("closed"); + await expect(second.loadResumeSnapshot(eagerStorageTarget("second"))).resolves.toMatchObject({ + rows: [], + }); persistence.close(); - await expect(second.loadResumeSnapshot("second")).rejects.toThrow("closed"); + await expect(second.loadResumeSnapshot(eagerStorageTarget("second"))).rejects.toThrow("closed"); } finally { persistence.close(); open.mockRestore(); @@ -96,7 +102,7 @@ describe("createIndexedDBPersistence", () => { mode: "sync-absent", schemaVersion: 2, }); - const load = view.adapter.loadResumeSnapshot("pending"); + const load = view.adapter.loadResumeSnapshot(eagerStorageTarget("pending")); persistence.close(); await expect(load).rejects.toThrow("closed"); }); @@ -114,7 +120,7 @@ describe("createIndexedDBPersistence", () => { mode: "sync-absent", schemaVersion: 1, }).adapter; - await original.applyCommittedTx("tasks", { + await original.applyCommittedTx(eagerStorageTarget("tasks"), { txId: "seed", term: 1, seq: 1, @@ -122,8 +128,10 @@ describe("createIndexedDBPersistence", () => { mutations: [{ type: "insert", key: "saved", value: { id: "saved" } }], }); const next = resolve({ collectionId: "tasks", mode: "sync-absent", schemaVersion: 2 }).adapter; - await expect(next.loadResumeSnapshot("tasks")).rejects.toThrow("Local-only data was preserved"); - expect((await original.loadResumeSnapshot("tasks")).rows).toHaveLength(1); + await expect(next.loadResumeSnapshot(eagerStorageTarget("tasks"))).rejects.toThrow( + "Local-only data was preserved" + ); + expect((await original.loadResumeSnapshot(eagerStorageTarget("tasks"))).rows).toHaveLength(1); persistence.close(); } ); @@ -137,7 +145,7 @@ describe("createIndexedDBPersistence", () => { }); const resolve = persistence.resolvePersistenceForCollection!; const original = resolve({ collectionId: "tasks", mode, schemaVersion: 1 }).adapter; - await original.applyCommittedTx("tasks", { + await original.applyCommittedTx(eagerStorageTarget("tasks"), { txId: "seed", term: 1, seq: 1, @@ -145,7 +153,10 @@ describe("createIndexedDBPersistence", () => { mutations: [{ type: "insert", key: "saved", value: { id: "saved" } }], }); const next = resolve({ collectionId: "tasks", mode, schemaVersion: 2 }).adapter; - expect(await next.loadResumeSnapshot("tasks")).toMatchObject({ rows: [], resetEpoch: 1 }); + expect(await next.loadResumeSnapshot(eagerStorageTarget("tasks"))).toMatchObject({ + rows: [], + resetEpoch: 1, + }); persistence.close(); } ); diff --git a/packages/db-indexeddb-persistence/src/managedCacheContract.browser.test.ts b/packages/db-indexeddb-persistence/src/managedCacheContract.browser.test.ts new file mode 100644 index 00000000..05b5ff4d --- /dev/null +++ b/packages/db-indexeddb-persistence/src/managedCacheContract.browser.test.ts @@ -0,0 +1,3 @@ +import { indexedDBManagedCacheHarness } from "./contracts/indexedDBManagedCacheHarness.js"; +import { runManagedCacheContract } from "./contracts/managedCacheContract.js"; +runManagedCacheContract("managed cache / native IndexedDB", indexedDBManagedCacheHarness); diff --git a/packages/db-indexeddb-persistence/src/managedCacheContract.test.ts b/packages/db-indexeddb-persistence/src/managedCacheContract.test.ts new file mode 100644 index 00000000..09f4faf5 --- /dev/null +++ b/packages/db-indexeddb-persistence/src/managedCacheContract.test.ts @@ -0,0 +1,53 @@ +import "fake-indexeddb/auto"; +import { createPersistedTableName } from "@tanstack/db-sqlite-persistence-core"; +import { createNodeSQLitePersistence } from "@tanstack/node-db-sqlite-persistence"; +import Database from "better-sqlite3"; +import { indexedDBManagedCacheHarness } from "./contracts/indexedDBManagedCacheHarness.js"; +import { runManagedCacheContract, type ManagedAdapter } from "./contracts/managedCacheContract.js"; +import type { PersistedCacheGenerationClaim } from "@tanstack/db-sqlite-persistence-core"; + +runManagedCacheContract("managed cache / IndexedDB", indexedDBManagedCacheHarness); +runManagedCacheContract("managed cache / SQLite control", (options) => { + const database = new Database(":memory:"); + const create = (overrides = {}) => + createNodeSQLitePersistence({ database, ...options, ...overrides }).adapter as ManagedAdapter; + return { + adapter: create(), + reopen: create, + footprint: (id) => { + const count = (table: string, column?: string) => { + if (!database.prepare("SELECT name FROM sqlite_master WHERE name = ?").get(table)) return 0; + return ( + database + .prepare( + `SELECT COUNT(*) AS count FROM "${table}"${column ? ` WHERE ${column} = ?` : ""}` + ) + .get(...(column ? [id] : [])) as { count: number } + ).count; + }; + return Promise.resolve({ + rows: count(createPersistedTableName(id, "c")), + tombstones: count(createPersistedTableName(id, "t")), + transactions: count("applied_tx", "collection_id"), + collectionMetadata: count("collection_metadata", "collection_id"), + streams: count("collection_version", "collection_id"), + indexes: count("persisted_index_registry", "collection_id"), + generations: count("cache_generation", "physical_id"), + claims: count("cache_generation_claim", "physical_id"), + registry: count("collection_registry", "collection_id"), + expectedKeys: count("collection_expected_keys", "collection_id"), + }); + }, + claims: (logicalId) => + Promise.resolve( + database + .prepare( + "SELECT physical_id AS storageCollectionId, claim_id AS claimId, expires_at_ms AS expiresAtMs FROM cache_generation_claim WHERE logical_id = ?" + ) + .all(logicalId) as PersistedCacheGenerationClaim[] + ), + cleanup: () => { + database.close(); + }, + }; +}); diff --git a/packages/db-indexeddb-persistence/src/managedCacheRaces.browser.test.ts b/packages/db-indexeddb-persistence/src/managedCacheRaces.browser.test.ts new file mode 100644 index 00000000..a05c4c48 --- /dev/null +++ b/packages/db-indexeddb-persistence/src/managedCacheRaces.browser.test.ts @@ -0,0 +1,2 @@ +import { runManagedCacheRaces } from "./contracts/managedCacheRaces.js"; +runManagedCacheRaces("managed cache races / native IndexedDB"); diff --git a/packages/db-indexeddb-persistence/src/managedCacheRaces.test.ts b/packages/db-indexeddb-persistence/src/managedCacheRaces.test.ts new file mode 100644 index 00000000..49009f16 --- /dev/null +++ b/packages/db-indexeddb-persistence/src/managedCacheRaces.test.ts @@ -0,0 +1,3 @@ +import "fake-indexeddb/auto"; +import { runManagedCacheRaces } from "./contracts/managedCacheRaces.js"; +runManagedCacheRaces("managed cache races / IndexedDB"); diff --git a/packages/db-indexeddb-persistence/vite.config.ts b/packages/db-indexeddb-persistence/vite.config.ts index 8a6f5206..4ad09cfd 100644 --- a/packages/db-indexeddb-persistence/vite.config.ts +++ b/packages/db-indexeddb-persistence/vite.config.ts @@ -2,6 +2,15 @@ import { defineLibraryConfig } from "@party-stack/build-config"; import { playwright } from "vite-plus/test/browser-playwright"; export default defineLibraryConfig({ + pack: { + entry: [ + "src/**/*.{ts,tsx}", + "!src/**/*.d.ts", + "!src/**/*.test.{ts,tsx}", + "!src/**/*.bench.{ts,tsx}", + "!src/contracts/**", + ], + }, run: { tasks: { test: { command: "vp test --project unit" }, diff --git a/packages/durable-object-ontology/package.json b/packages/durable-object-ontology/package.json index 34214b44..cf9a72f1 100644 --- a/packages/durable-object-ontology/package.json +++ b/packages/durable-object-ontology/package.json @@ -22,7 +22,7 @@ "@cloudflare/vitest-plugin": "^1.4.0", "@cloudflare/workers-types": "^5.20260827.1", "@party-stack/build-config": "workspace:*", - "@tanstack/db": "0.12.1", + "@tanstack/db": "0.14.0", "esbuild": "^0.28.1", "typescript": "~7.0.2", "vite-plus": "catalog:", @@ -30,6 +30,6 @@ "wrangler": "^4.127.0" }, "peerDependencies": { - "@tanstack/db": "0.12.1" + "@tanstack/db": "0.14.0" } } diff --git a/packages/durable-object-ontology/vite.config.ts b/packages/durable-object-ontology/vite.config.ts index bebc1ed9..c668ed08 100644 --- a/packages/durable-object-ontology/vite.config.ts +++ b/packages/durable-object-ontology/vite.config.ts @@ -1,5 +1,5 @@ import { cloudflareTest } from "@cloudflare/vitest-plugin"; -import { defineLibraryConfig } from "@party-stack/build-config"; +import { defineLibraryConfig, type LibraryConfig } from "@party-stack/build-config"; export default defineLibraryConfig({ run: { @@ -16,11 +16,13 @@ export default defineLibraryConfig({ // https://github.com/cloudflare/workers-sdk/pull/16163 fsModuleCache: false, }, + // Keep the Cloudflare/Vitest plugin type at the shared Vite config boundary; + // comparing both recursive config types can exceed TypeScript's depth limit. plugins: [ cloudflareTest({ wrangler: { configPath: "./wrangler.jsonc", }, }), - ], + ] as LibraryConfig["plugins"], }); diff --git a/packages/expo-runtime/README.md b/packages/expo-runtime/README.md new file mode 100644 index 00000000..7f4c8063 --- /dev/null +++ b/packages/expo-runtime/README.md @@ -0,0 +1,16 @@ +# Expo runtime + +This runtime targets Expo SDK 57. Install `@party-stack/expo-runtime` and its Expo peer dependencies using SDK 57-compatible versions. Expo installs `expo/fetch` as the default native fetch starting in SDK 56; no separate `expo-fetch` package or fetch installer is needed. The runtime includes `expo-blob`. + +Import the required polyfill first in your application entry point, before importing application code or creating clients: + +```ts +import "@party-stack/expo-runtime/polyfill"; +import "expo-router/entry"; +``` + +Then use `createExpoRuntime` from `@party-stack/expo-runtime` as your installation's runtime provider. + +The polyfill replaces global `Blob` with Expo Blob and global `File` with a compatible class extending Expo Blob. It leaves Expo’s installed fetch unchanged. Import it once at startup; it is a separate entry point so importing the runtime alone does not replace Blob or File. Attachment uploads use Expo’s default fetch; do not opt back into React Native fetch with `EXPO_PUBLIC_USE_RN_FETCH`. + +Attachment storage requires blobs with `arrayBuffer()`, as provided by Expo Blob and Expo File. Blobs returned by other native libraries may still use React Native's older Blob implementation; convert those to compatible blobs before passing them to the runtime. Cache reads return `expo-file-system` File objects, which Expo fetch accepts as upload bodies. diff --git a/packages/expo-runtime/package.json b/packages/expo-runtime/package.json index 4b0d49be..a9a00f5b 100644 --- a/packages/expo-runtime/package.json +++ b/packages/expo-runtime/package.json @@ -5,31 +5,42 @@ "lib" ], "type": "module", + "sideEffects": [ + "./lib/polyfill.js" + ], "exports": { ".": { "types": "./lib/index.d.ts", "default": "./lib/index.js" + }, + "./polyfill": { + "types": "./lib/polyfill.d.ts", + "default": "./lib/polyfill.js" } }, "dependencies": { "@party-stack/coordination": "workspace:*", "@party-stack/runtime": "workspace:*", - "@tanstack/expo-db-sqlite-persistence": "0.2.30", - "expo-secure-store": "^57.0.1", - "expo-web-browser": "^57.0.2", + "@tanstack/expo-db-sqlite-persistence": "0.2.34", + "expo-blob": "^57.0.2", + "expo-crypto": "^57.0.4", + "expo-secure-store": "^57.0.4", + "expo-web-browser": "^57.0.3", "temporal-polyfill": "^0.3.0" }, "devDependencies": { "@party-stack/build-config": "workspace:*", "@react-native-community/netinfo": "^12.0.1", - "expo-file-system": "^55.0.19", - "expo-sqlite": "^55.0.15", + "expo": "^57.0.29", + "expo-file-system": "^57.0.7", + "expo-sqlite": "^57.0.4", "typescript": "~7.0.2", "vite-plus": "catalog:" }, "peerDependencies": { "@react-native-community/netinfo": "^12.0.1", - "expo-file-system": "^55.0.19", - "expo-sqlite": "^55.0.15" + "expo": "^57.0.29", + "expo-file-system": "^57.0.7", + "expo-sqlite": "^57.0.4" } } diff --git a/packages/expo-runtime/src/ExpoFileSystemBlobBytesStore.test.ts b/packages/expo-runtime/src/ExpoFileSystemBlobBytesStore.test.ts new file mode 100644 index 00000000..f63db59b --- /dev/null +++ b/packages/expo-runtime/src/ExpoFileSystemBlobBytesStore.test.ts @@ -0,0 +1,68 @@ +import { createHash } from "node:crypto"; +import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; +import { ExpoFileSystemBlobBytesStore } from "./ExpoFileSystemBlobBytesStore.js"; + +const state = vi.hoisted(() => ({ files: new Map(), created: vi.fn() })); +vi.mock("expo-crypto", () => ({ + CryptoDigestAlgorithm: { SHA256: "SHA-256" }, + digestStringAsync: (_algorithm: string, value: string) => + Promise.resolve(createHash("sha256").update(value).digest("hex")), +})); +vi.mock("expo-file-system", () => ({ + Paths: { document: "documents" }, + Directory: class { + exists = true; + create() {} + delete() { + state.files.clear(); + } + }, + File: class { + constructor( + _directory: unknown, + readonly name: string + ) {} + get exists() { + return state.files.has(this.name); + } + create() { + state.created(this.name); + } + write(bytes: Uint8Array) { + state.files.set(this.name, bytes); + } + delete() { + state.files.delete(this.name); + } + }, +})); + +describe("ExpoFileSystemBlobBytesStore", () => { + beforeEach(() => { + state.files.clear(); + vi.clearAllMocks(); + vi.unstubAllGlobals(); + }); + + it("writes standard Blob bytes under bounded hashed IDs", async () => { + const store = new ExpoFileSystemBlobBytesStore({ directoryName: "attachments" }); + const id = "../user/".repeat(500); + await store.write(id, new Blob(["hello"])); + expect(state.created).toHaveBeenCalledWith(createHash("sha256").update(id).digest("hex")); + expect([...state.files.values()][0]).toEqual(new TextEncoder().encode("hello")); + await expect(store.read(id)).resolves.toBeDefined(); + await store.delete(id); + await store.delete(id); + await expect(store.read(id)).rejects.toThrow("Blob bytes not found"); + }); + + it("propagates byte read failures before overwriting cached bytes", async () => { + const store = new ExpoFileSystemBlobBytesStore({ directoryName: "attachments" }); + await store.write("existing", new Blob(["original"])); + state.created.mockClear(); + const blob = { arrayBuffer: () => Promise.reject(new Error("byte read failed")) } as Blob; + await expect(store.write("existing", blob)).rejects.toThrow("byte read failed"); + expect(state.created).not.toHaveBeenCalled(); + expect([...state.files.values()][0]).toEqual(new TextEncoder().encode("original")); + }); +}); diff --git a/packages/expo-runtime/src/ExpoFileSystemBlobBytesStore.ts b/packages/expo-runtime/src/ExpoFileSystemBlobBytesStore.ts index 2127ee14..c81353e6 100644 --- a/packages/expo-runtime/src/ExpoFileSystemBlobBytesStore.ts +++ b/packages/expo-runtime/src/ExpoFileSystemBlobBytesStore.ts @@ -1,14 +1,11 @@ import { Directory, File, Paths } from "expo-file-system"; +import { hashStorageName } from "./hashStorageName.js"; import type { BlobBytesStore } from "@party-stack/runtime"; export interface ExpoFileSystemBlobBytesStoreOptions { directoryName: string; } -function encodeFileName(id: string): string { - return encodeURIComponent(id); -} - export class ExpoFileSystemBlobBytesStore implements BlobBytesStore { #directoryName: string; @@ -17,14 +14,15 @@ export class ExpoFileSystemBlobBytesStore implements BlobBytesStore { } async write(id: string, blob: Blob): Promise { - const file = this.#file(id); + const file = await this.#file(id); + const bytes = new Uint8Array(await blob.arrayBuffer()); file.create({ intermediates: true, overwrite: true }); - file.write(new Uint8Array(await blob.arrayBuffer())); + file.write(bytes); } read(id: string): Promise { - return Promise.resolve().then(() => { - const file = this.#file(id); + return Promise.resolve().then(async () => { + const file = await this.#file(id); if (!file.exists) { throw new Error(`Blob bytes not found for "${id}".`); } @@ -33,8 +31,8 @@ export class ExpoFileSystemBlobBytesStore implements BlobBytesStore { } delete(id: string): Promise { - return Promise.resolve().then(() => { - const file = this.#file(id); + return Promise.resolve().then(async () => { + const file = await this.#file(id); if (file.exists) file.delete(); }); } @@ -52,7 +50,7 @@ export class ExpoFileSystemBlobBytesStore implements BlobBytesStore { return directory; } - #file(id: string): File { - return new File(this.#directory(), encodeFileName(id)); + #file(id: string): Promise { + return hashStorageName(id).then((name) => new File(this.#directory(), name)); } } diff --git a/packages/expo-runtime/src/createExpoRuntime.test.ts b/packages/expo-runtime/src/createExpoRuntime.test.ts new file mode 100644 index 00000000..f359d97d --- /dev/null +++ b/packages/expo-runtime/src/createExpoRuntime.test.ts @@ -0,0 +1,87 @@ +import { createHash } from "node:crypto"; +import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; +import { createExpoRuntime } from "./createExpoRuntime.js"; + +const state = vi.hoisted(() => ({ + events: [] as string[], + close: vi.fn(), + delete: vi.fn(), + open: vi.fn(), + disconnect: vi.fn(), + clear: vi.fn(), + directory: vi.fn(), +})); +vi.mock("expo-crypto", () => ({ + CryptoDigestAlgorithm: { SHA256: "SHA-256" }, + digestStringAsync: (_algorithm: string, value: string) => + Promise.resolve(createHash("sha256").update(value).digest("hex")), +})); +vi.mock("expo-sqlite", () => ({ openDatabaseAsync: state.open, deleteDatabaseAsync: state.delete })); +vi.mock("@tanstack/expo-db-sqlite-persistence", () => ({ createExpoSQLitePersistence: () => ({}) })); +vi.mock("./ExpoNetworkConnectivity.js", () => ({ + ExpoNetworkConnectivity: { create: () => Promise.resolve({ close: state.disconnect }) }, +})); +vi.mock("./ExpoFileSystemBlobBytesStore.js", () => ({ + ExpoFileSystemBlobBytesStore: class { + constructor(options: unknown) { + state.directory(options); + } + clear = state.clear; + }, +})); +vi.mock("./ExpoSecretStore.js", () => ({ ExpoSecretStore: class {} })); +vi.mock("./createExpoBrowserAuthentication.js", () => ({ createExpoBrowserAuthentication: () => ({}) })); + +describe("createExpoRuntime", () => { + beforeEach(() => { + vi.clearAllMocks(); + state.events = []; + state.close.mockImplementation(async () => { + await Promise.resolve(); + state.events.push("closed"); + }); + state.delete.mockImplementation(() => { + state.events.push("deleted"); + return Promise.resolve(); + }); + state.open.mockResolvedValue({ closeAsync: state.close }); + }); + + it("hashes logical partition names into bounded filesystem names without ambiguous separators", async () => { + const owner = "../user@example.com/".repeat(100); + const runtime = await createExpoRuntime(owner, "namespace"); + expect(runtime.owner).toBe(owner); + const name = `party-stack-${createHash("sha256") + .update(JSON.stringify([owner, "namespace"])) + .digest("hex")}`; + expect(state.open).toHaveBeenCalledWith(`${name}.db`); + expect(state.directory).toHaveBeenCalledWith({ directoryName: `${name}-blobs` }); + await runtime.cleanup?.(); + const a = await createExpoRuntime("a:b", "c"); + const b = await createExpoRuntime("a", "b:c"); + expect(state.open.mock.calls[1]?.[0]).not.toBe(state.open.mock.calls[2]?.[0]); + await a.cleanup?.(); + await b.cleanup?.(); + }); + + it.each([false, true])( + "closes before deleting and makes cleanup/destroy idempotent (cleanup first: %s)", + async (cleanupFirst) => { + const runtime = await createExpoRuntime("owner", "namespace"); + if (cleanupFirst) await runtime.cleanup?.(); + await Promise.all([runtime.destroy?.(), runtime.cleanup?.(), runtime.destroy?.()]); + expect(state.close).toHaveBeenCalledTimes(1); + expect(state.disconnect).toHaveBeenCalledTimes(1); + expect(state.delete).toHaveBeenCalledTimes(1); + expect(state.clear).toHaveBeenCalledTimes(1); + expect(state.events).toEqual(["closed", "deleted"]); + } + ); + + it("does not delete the database if closing it fails", async () => { + state.close.mockRejectedValue(new Error("close failed")); + const runtime = await createExpoRuntime("owner", "namespace"); + await expect(runtime.destroy?.()).rejects.toThrow("close failed"); + expect(state.delete).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/expo-runtime/src/createExpoRuntime.ts b/packages/expo-runtime/src/createExpoRuntime.ts index 465cbd20..724e5e56 100644 --- a/packages/expo-runtime/src/createExpoRuntime.ts +++ b/packages/expo-runtime/src/createExpoRuntime.ts @@ -8,13 +8,15 @@ import { createExpoBrowserAuthentication } from "./createExpoBrowserAuthenticati import { ExpoFileSystemBlobBytesStore } from "./ExpoFileSystemBlobBytesStore.js"; import { ExpoNetworkConnectivity } from "./ExpoNetworkConnectivity.js"; import { ExpoSecretStore } from "./ExpoSecretStore.js"; +import { hashStorageName } from "./hashStorageName.js"; import type { RuntimeAdapter } from "@party-stack/runtime"; export const createExpoRuntime: (owner: string, namespace: string) => Promise = defineRuntime( async (owner, namespace) => { const name = `party-stack:${owner}:${namespace}`; + const storageName = `party-stack-${await hashStorageName(JSON.stringify([owner, namespace]))}`; const [database, connectivity] = await Promise.all([ - openDatabaseAsync(`${name}.db`), + openDatabaseAsync(`${storageName}.db`), ExpoNetworkConnectivity.create(), ]); const persistence = createExpoSQLitePersistence({ @@ -24,7 +26,7 @@ export const createExpoRuntime: (owner: string, namespace: string) => Promise Promise { - await Promise.all([deleteDatabaseAsync(`${name}.db`), blobBytes.clear()]); + await Promise.all([deleteDatabaseAsync(`${storageName}.db`), blobBytes.clear()]); }, cleanup: async () => { await coordination.close(); diff --git a/packages/expo-runtime/src/hashStorageName.ts b/packages/expo-runtime/src/hashStorageName.ts new file mode 100644 index 00000000..8f03f103 --- /dev/null +++ b/packages/expo-runtime/src/hashStorageName.ts @@ -0,0 +1,5 @@ +import { CryptoDigestAlgorithm, digestStringAsync } from "expo-crypto"; + +export function hashStorageName(value: string): Promise { + return digestStringAsync(CryptoDigestAlgorithm.SHA256, value); +} diff --git a/packages/expo-runtime/src/polyfill.test.ts b/packages/expo-runtime/src/polyfill.test.ts new file mode 100644 index 00000000..ac38549b --- /dev/null +++ b/packages/expo-runtime/src/polyfill.test.ts @@ -0,0 +1,69 @@ +import { afterEach, describe, expect, it, vi } from "vite-plus/test"; + +const { NativeBlob } = vi.hoisted(() => ({ NativeBlob: globalThis.Blob })); +vi.mock("expo-blob", () => ({ Blob: class ExpoBlob extends NativeBlob {} })); + +const originalBlob = globalThis.Blob; +const originalFile = globalThis.File; +const originalFetch = globalThis.fetch; +afterEach(() => { + globalThis.Blob = originalBlob; + globalThis.File = originalFile; + globalThis.fetch = originalFetch; + vi.resetModules(); + vi.restoreAllMocks(); +}); + +describe("Expo polyfill", () => { + it("installs Expo Blob and preserves Expo's installed fetch", async () => { + const { Blob } = await import("expo-blob"); + await import("./polyfill.js"); + expect(globalThis.Blob).toBe(Blob); + expect(globalThis.fetch).toBe(originalFetch); + }); + + it("constructs files from Expo blobs without changing their bytes", async () => { + await import("./polyfill.js"); + const blob = new Blob([new Uint8Array([0, 128, 255])]); + const file = new File([blob], "attachment.bin", { + type: "application/octet-stream", + lastModified: 1234, + }); + expect(file).toBeInstanceOf(Blob); + expect(file.name).toBe("attachment.bin"); + expect(file.lastModified).toBe(1234); + expect(file.type).toBe("application/octet-stream"); + expect(file.size).toBe(3); + expect(new Uint8Array(await file.arrayBuffer())).toEqual(new Uint8Array([0, 128, 255])); + expect(Object.prototype.toString.call(file)).toBe("[object File]"); + }); + + it("defaults file timestamps and supports blob slicing", async () => { + vi.spyOn(Date, "now").mockReturnValue(4321); + await import("./polyfill.js"); + const file = new File(["hello"], "hello.txt", { type: "text/plain" }); + expect(file.lastModified).toBe(4321); + expect(file.webkitRelativePath).toBe(""); + await expect(file.slice(1, 4).text()).resolves.toBe("ell"); + }); + + it("preserves a file's name through Expo FormData append and set", async () => { + const { installFormDataPatch } = (await import( + new URL("../node_modules/expo/src/winter/FormData.ts", import.meta.url).href + )) as typeof import("expo/build/winter/FormData.js"); + class ReactNativeFormData { + _parts: [string, string | Blob][] = []; + } + const FormData = installFormDataPatch(ReactNativeFormData as unknown as typeof globalThis.FormData); + await import("./polyfill.js"); + const file = new File(["attachment"], "attachment.txt", { type: "text/plain" }); + const form = new FormData(); + form.append("attachment", file); + expect(form.get("attachment")).toBe(file); + expect(file.name).toBe("attachment.txt"); + form.set("attachment", file); + expect(form.get("attachment")).toBe(file); + expect(file.name).toBe("attachment.txt"); + await expect(file.text()).resolves.toBe("attachment"); + }); +}); diff --git a/packages/expo-runtime/src/polyfill.ts b/packages/expo-runtime/src/polyfill.ts new file mode 100644 index 00000000..871bc9ac --- /dev/null +++ b/packages/expo-runtime/src/polyfill.ts @@ -0,0 +1,37 @@ +import { Blob } from "expo-blob"; + +// Expo declarations predate the current DOM Blob byte-array generics. +globalThis.Blob = Blob as unknown as typeof globalThis.Blob; + +// React Native File extends its own Blob class, so replace it alongside Blob. +class ExpoFile extends globalThis.Blob implements File { + readonly #name: string; + readonly #lastModified: number; + + constructor(parts: BlobPart[], name: string, options: FilePropertyBag = {}) { + super(parts, options); + this.#name = String(name); + this.#lastModified = options.lastModified ?? Date.now(); + // Expo FormData checks own descriptors before assigning a blob filename. + // An inherited getter alone would make that assignment throw. + Object.defineProperty(this, "name", { value: this.#name, enumerable: true }); + } + + get name(): string { + return this.#name; + } + + get lastModified(): number { + return this.#lastModified; + } + + get webkitRelativePath(): string { + return ""; + } + + get [Symbol.toStringTag](): string { + return "File"; + } +} + +globalThis.File = ExpoFile; diff --git a/packages/foundry-ontology/README.md b/packages/foundry-ontology/README.md index 07f7178f..e9efc021 100644 --- a/packages/foundry-ontology/README.md +++ b/packages/foundry-ontology/README.md @@ -44,3 +44,22 @@ are used when public metadata does not provide them. Open string one-of values are preserved as suggestions. Object-query and struct-field prefills are omitted until the expression model supports them, and failures from the private endpoint do not prevent public action metadata from loading. + +## Refreshing objects + +Foundry object collections expose an awaitable full-source refresh: + +```ts +await ontology.objects.Employee.utils.refresh?.(); +``` + +`refresh` is an optional adapter utility on the generic ontology collection API. +Foundry fetches a fresh snapshot and replaces cached rows in one sync transaction, +while keeping live subscriptions active and applying updates received during the +fetch. The promise waits for the commit and rejects on failure. Ontology +collections start their sync before refreshing; when using +`objectCollectionOptions` directly, preload the collection first. + +When edit history is unavailable, reconnection and action acknowledgement also +request a fresh snapshot. Acknowledgement waits for that snapshot's commit; it +cannot confirm a specific edit-history operation ID in this mode. diff --git a/packages/foundry-ontology/package.json b/packages/foundry-ontology/package.json index 9b63ae5a..11c1d446 100644 --- a/packages/foundry-ontology/package.json +++ b/packages/foundry-ontology/package.json @@ -47,8 +47,8 @@ }, "devDependencies": { "@party-stack/build-config": "workspace:*", - "@tanstack/db": "0.12.1", - "@tanstack/query-db-collection": "1.4.0", + "@tanstack/db": "0.14.0", + "@tanstack/query-db-collection": "1.4.4", "@types/node": "^24.10.1", "@types/react": "^19.3.0", "react": "^19.3.0", @@ -58,9 +58,9 @@ "vite-plus": "catalog:" }, "peerDependencies": { - "@tanstack/db": "0.12.1", + "@tanstack/db": "0.14.0", "@tanstack/query-core": "^5.102.8", - "@tanstack/query-db-collection": "1.4.0", + "@tanstack/query-db-collection": "1.4.4", "react": "^18.0.0 || ^19.0.0" } } diff --git a/packages/foundry-ontology/src/adapter/objectCollectionOptions.test.ts b/packages/foundry-ontology/src/adapter/objectCollectionOptions.test.ts index b29da9df..0088853b 100644 --- a/packages/foundry-ontology/src/adapter/objectCollectionOptions.test.ts +++ b/packages/foundry-ontology/src/adapter/objectCollectionOptions.test.ts @@ -184,6 +184,135 @@ describe("objectCollectionOptions", () => { }); }); + it("refreshes the full snapshot atomically and preserves the watcher", async () => { + mockState.search.mockResolvedValue({ + data: [{ employeeId: 2, name: "Fresh" }], + nextPageToken: undefined, + }); + const harness = createSyncHarness([{ employeeId: 1, name: "Stale" }]); + const watcher = mockState.subscribeCallback; + await harness.utils.refresh(); + expect(harness.syncedData.has(1)).toBe(false); + expect(harness.syncedData.get(2)).toEqual({ employeeId: 2, name: "Fresh" }); + expect(harness.truncate).toHaveBeenCalledTimes(1); + expect(harness.transactions.at(-1)?.writes).toEqual([{ type: "insert", primaryKey: 2 }]); + expect(mockState.subscribeCallback).toBe(watcher); + expect(mockState.unsubscribe).not.toHaveBeenCalled(); + harness.cleanup(); + }); + + it("propagates refresh errors without clearing cached rows, and allows retry", async () => { + mockState.search.mockRejectedValueOnce(new Error("snapshot failed")); + const harness = createSyncHarness([{ employeeId: 1, name: "Cached" }]); + await expect(harness.utils.refresh()).rejects.toThrow("snapshot failed"); + expect(harness.syncedData.get(1)).toMatchObject({ name: "Cached" }); + expect(harness.truncate).not.toHaveBeenCalled(); + mockState.search.mockResolvedValue({ data: [], nextPageToken: undefined }); + await harness.utils.refresh(); + expect(harness.syncedData.size).toBe(0); + harness.cleanup(); + await expect(harness.utils.refresh()).rejects.toThrow(); + }); + + it("waits for refresh commit and replays websocket updates received during the snapshot fetch", async () => { + let finishFetch!: (value: unknown) => void; + mockState.search.mockImplementation( + () => + new Promise((resolve) => { + finishFetch = resolve; + }) + ); + let finishCommit!: () => void; + const receipt = new Promise((resolve) => { + finishCommit = resolve; + }); + const harness = createSyncHarness([], { commitReceipt: () => receipt }); + const completed = vi.fn(); + const refresh = harness.utils.refresh().then(completed); + await vi.waitFor(() => expect(finishFetch).toBeDefined()); + mockState.subscribeCallback?.({ + type: "change", + updates: [{ type: "object", state: "ADDED_OR_UPDATED", object: { employeeId: 2, name: "Live" } }], + }); + finishFetch({ data: [{ employeeId: 2, name: "Snapshot" }], nextPageToken: undefined }); + await vi.waitFor(() => expect(harness.truncate).toHaveBeenCalledOnce()); + expect(completed).not.toHaveBeenCalled(); + finishCommit(); + await refresh; + expect(harness.syncedData.get(2)).toMatchObject({ name: "Live" }); + harness.cleanup(); + }); + + it("applies queued live updates even when the snapshot fetch fails", async () => { + let failFetch!: (error: Error) => void; + mockState.search.mockImplementation( + () => + new Promise((_resolve, reject) => { + failFetch = reject; + }) + ); + const harness = createSyncHarness([{ employeeId: 1, name: "Cached" }]); + const refresh = harness.utils.refresh(); + const rejected = expect(refresh).rejects.toThrow("snapshot failed"); + await vi.waitFor(() => expect(failFetch).toBeDefined()); + mockState.subscribeCallback?.({ + type: "change", + updates: [{ type: "object", state: "ADDED_OR_UPDATED", object: { employeeId: 1, name: "Live" } }], + }); + failFetch(new Error("snapshot failed")); + await rejected; + expect(harness.syncedData.get(1)).toMatchObject({ name: "Live" }); + expect(harness.truncate).not.toHaveBeenCalled(); + expect(mockState.unsubscribe).not.toHaveBeenCalled(); + harness.cleanup(); + }); + + it("rejects a refresh disposed while its snapshot is in flight", async () => { + let finishFetch!: (value: unknown) => void; + mockState.search.mockImplementation( + () => + new Promise((resolve) => { + finishFetch = resolve; + }) + ); + const harness = createSyncHarness([{ employeeId: 1 }]); + const refresh = harness.utils.refresh(); + const result = expect(refresh).rejects.toThrow("sync"); + await vi.waitFor(() => expect(finishFetch).toBeDefined()); + harness.cleanup(); + finishFetch({ data: [], nextPageToken: undefined }); + await result; + expect(harness.truncate).not.toHaveBeenCalled(); + }); + + it("acknowledges an operation by requesting and awaiting a new snapshot when history is unavailable", async () => { + const warn = vi.spyOn(console, "warn").mockImplementation(() => {}); + mockState.getEditsHistory.mockRejectedValue(new Error("History unavailable")); + mockState.search.mockResolvedValue({ data: [], nextPageToken: undefined }); + const harness = createSyncHarness(); + mockState.subscribeCallback?.({ type: "state", status: "open" }); + await vi.waitFor(() => expect(mockState.search).toHaveBeenCalledOnce()); + // Await the background refresh and any queued request before testing acknowledgement. + await harness.utils.refresh(); + mockState.search.mockClear(); + let finishFetch!: (value: unknown) => void; + mockState.search.mockImplementation( + () => + new Promise((resolve) => { + finishFetch = resolve; + }) + ); + const acknowledged = vi.fn(); + const operation = harness.utils.awaitOperationId("op-without-history").then(acknowledged); + await vi.waitFor(() => expect(finishFetch).toBeDefined()); + expect(acknowledged).not.toHaveBeenCalled(); + finishFetch({ data: [{ employeeId: 4, name: "Created" }], nextPageToken: undefined }); + await operation; + expect(harness.syncedData.get(4)).toMatchObject({ name: "Created" }); + harness.cleanup(); + warn.mockRestore(); + }); + it("constructs and cleans up the object-set watcher by default", () => { const harness = createSyncHarness(); @@ -1294,43 +1423,26 @@ describe("objectCollectionOptions", () => { expect(mockState.getEditsHistory).not.toHaveBeenCalled(); }); - it("resolves awaitOperationId after direct websocket sync observes an update", async () => { + it("resolves awaitOperationId after fallback snapshot commit without edit history", async () => { const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); mockState.getEditsHistory.mockRejectedValue(new Error("Edit history is not enabled")); - + mockState.search.mockResolvedValue({ + data: [{ employeeId: 13, name: "Direct Operation Employee" }], + nextPageToken: undefined, + }); let resolveCommit!: () => void; const commitApplied = new Promise((resolve) => { resolveCommit = resolve; }); - const harness = createSyncHarness([], { - commitReceipt: () => commitApplied, - }); - const operationPromise = harness.utils.awaitOperationId("op-12"); - - await vi.waitFor(() => { - expect(mockState.getEditsHistory).toHaveBeenCalledTimes(1); - }); - - mockState.subscribeCallback?.({ - type: "change", - updates: [ - { - type: "object", - state: "ADDED_OR_UPDATED", - object: { employeeId: 13, name: "Direct Operation Employee" }, - }, - ], - }); - - await expect(operationPromise).resolves.toBeUndefined(); - expect(harness.syncedData.get(13)).toEqual({ - employeeId: 13, - name: "Direct Operation Employee", - }); - expect(mockState.getEditsHistory).toHaveBeenCalledTimes(1); - + const harness = createSyncHarness([], { commitReceipt: () => commitApplied }); + const acknowledged = vi.fn(); + const operationPromise = harness.utils.awaitOperationId("op-12").then(acknowledged); + await vi.waitFor(() => expect(harness.truncate).toHaveBeenCalledOnce()); + expect(acknowledged).not.toHaveBeenCalled(); resolveCommit(); - await commitApplied; + await operationPromise; + expect(harness.syncedData.get(13)).toEqual({ employeeId: 13, name: "Direct Operation Employee" }); + expect(mockState.getEditsHistory).toHaveBeenCalledTimes(1); harness.cleanup(); warnSpy.mockRestore(); }); diff --git a/packages/foundry-ontology/src/adapter/objectCollectionOptions.ts b/packages/foundry-ontology/src/adapter/objectCollectionOptions.ts index d48bd3a6..eb1abc66 100644 --- a/packages/foundry-ontology/src/adapter/objectCollectionOptions.ts +++ b/packages/foundry-ontology/src/adapter/objectCollectionOptions.ts @@ -55,6 +55,7 @@ class ObjectCollectionSyncAbortedError extends Error { } export interface ObjectCollectionUtils extends UtilsRecord { + refresh: () => Promise; awaitOperationId: (operationId: string, timeout?: number) => Promise; } @@ -260,9 +261,18 @@ function createSyncConfig( ): { sync: SyncConfig, string | number>; utils: ObjectCollectionUtils } { const seenOperationIds = new Store>(new Set()); const syncDisposed = new Store(false); - const directWebsocketSyncVersion = new Store(0); + const fullSourceRefreshVersion = new Store(0); let editHistoryUnavailable = false; - let requestFullRefresh: (() => void) | undefined; + let requestFullRefresh: (() => Promise) | undefined; + const refresh = async (): Promise => { + if (!requestFullRefresh || syncDisposed.state) throw new ObjectCollectionSyncAbortedError(objectType); + await requestFullRefresh(); + }; + const refreshInBackground = () => { + void refresh().catch((error: unknown) => { + console.warn(`Failed full Foundry refresh for ${objectType}.`, error); + }); + }; let requestEditHistoryCatchUp: (() => void) | undefined; const awaitOperationId = async ( @@ -283,19 +293,16 @@ function createSyncConfig( if (!requestEditHistoryCatchUp) { // An on-demand collection has no local state to confirm until its sync starts. - // TODO: Track sync activity and edit-history availability separately. If sync is - // active but edit-history catch-up is unavailable, request and await a full refresh. return; } - const directSyncVersionAtStart = directWebsocketSyncVersion.state; - requestEditHistoryCatchUp(); - + const refreshVersionAtStart = fullSourceRefreshVersion.state; + const historyUnavailableAtStart = editHistoryUnavailable; return new Promise((resolve, reject) => { const cleanup = () => { clearTimeout(timeoutId); seenOperationIdsSubscription.unsubscribe(); - directWebsocketSyncVersionSubscription.unsubscribe(); + fullSourceRefreshVersionSubscription.unsubscribe(); disposedSubscription.unsubscribe(); }; @@ -309,8 +316,12 @@ function createSyncConfig( resolve(); } }); - const directWebsocketSyncVersionSubscription = directWebsocketSyncVersion.subscribe(() => { - if (editHistoryUnavailable && directWebsocketSyncVersion.state > directSyncVersionAtStart) { + const fullSourceRefreshVersionSubscription = fullSourceRefreshVersion.subscribe(() => { + if ( + !historyUnavailableAtStart && + editHistoryUnavailable && + fullSourceRefreshVersion.state > refreshVersionAtStart + ) { cleanup(); resolve(); } @@ -321,10 +332,22 @@ function createSyncConfig( reject(new ObjectCollectionSyncAbortedError(objectType)); } }); + if (editHistoryUnavailable) { + void refresh().then( + () => { + cleanup(); + resolve(); + }, + (error: unknown) => { + cleanup(); + reject(error); + } + ); + } else requestEditHistoryCatchUp?.(); }); }; - const utils: ObjectCollectionUtils = { awaitOperationId }; + const utils: ObjectCollectionUtils = { awaitOperationId, refresh }; const sync: SyncConfig, string | number> = { sync: (params) => { @@ -421,12 +444,10 @@ function createSyncConfig( string | number, { type: "upsert"; object: FoundryObject } | { type: "delete" } >(); - let observedObjectUpdate = false; for (const update of updates) { if (update.type !== "object") continue; - observedObjectUpdate = true; const object = update.object; const primaryKey = getWatcherObjectKey(object); @@ -459,10 +480,6 @@ function createSyncConfig( const receipt = transactionStarted ? commit() : true; - if (observedObjectUpdate) { - directWebsocketSyncVersion.setState((version) => version + 1); - } - if (receipt !== true) await receipt; }; @@ -483,7 +500,7 @@ function createSyncConfig( console.warn(`Failed to apply direct Foundry updates for ${objectType}.`, applyError); }); } - requestFullRefresh?.(); + refreshInBackground(); }; const catchUpFromEditHistory = async (): Promise => { @@ -585,7 +602,7 @@ function createSyncConfig( {}, decodeObject ); - if (disposed) return; + if (disposed) throw new ObjectCollectionSyncAbortedError(objectType); begin(); truncate(); @@ -597,35 +614,54 @@ function createSyncConfig( } const receipt = commit(); - if (pendingDirectWatcherUpdates.length > 0) { + if (receipt !== true) await receipt; + if (disposed) throw new ObjectCollectionSyncAbortedError(objectType); + while (pendingDirectWatcherUpdates.length > 0 && !disposed) { const updates = pendingDirectWatcherUpdates; pendingDirectWatcherUpdates = []; await applyDirectWatcherUpdates(updates); } - directWebsocketSyncVersion.setState((version) => version + 1); - - if (receipt !== true) await receipt; + if (disposed) throw new ObjectCollectionSyncAbortedError(objectType); + fullSourceRefreshVersion.setState((version) => version + 1); }; requestFullRefresh = () => { fullRefreshRequested = true; - if (fullRefreshTask) return; + if (fullRefreshTask) return fullRefreshTask; + const priorCatchUp = catchUpTask; fullRefreshTask = (async () => { + if (priorCatchUp) await priorCatchUp; + if (disposed) throw new ObjectCollectionSyncAbortedError(objectType); while (fullRefreshRequested && !disposed) { fullRefreshRequested = false; await refreshAllFromSource(); } })() - .catch((error: unknown) => { - console.warn(`Failed full Foundry refresh for ${objectType}.`, error); + .catch(async (error: unknown) => { + // A failed snapshot must not strand updates queued by the live watcher. + while (pendingDirectWatcherUpdates.length > 0 && !disposed) { + const updates = pendingDirectWatcherUpdates; + pendingDirectWatcherUpdates = []; + try { + await applyDirectWatcherUpdates(updates); + } catch (applyError: unknown) { + console.warn( + `Failed to apply queued Foundry updates for ${objectType}.`, + applyError + ); + break; + } + } + throw error; }) .finally(() => { fullRefreshTask = undefined; if (fullRefreshRequested && !disposed) { - requestFullRefresh?.(); + refreshInBackground(); } }); + return fullRefreshTask; }; requestEditHistoryCatchUp = () => { @@ -636,7 +672,9 @@ function createSyncConfig( catchUpRequested = true; if (catchUpTask) return; + const priorRefresh = fullRefreshTask; catchUpTask = (async () => { + if (priorRefresh) await priorRefresh; while (catchUpRequested && !disposed) { catchUpRequested = false; await catchUpFromEditHistory(); @@ -676,20 +714,16 @@ function createSyncConfig( ? getObjectSetWatcherManager(client).subscribe({ type: "base", objectType }, (message) => { switch (message.type) { case "change": { - if (editHistoryUnavailable) { - if (fullRefreshTask) { - pendingDirectWatcherUpdates.push(...message.updates); - } else { - void applyDirectWatcherUpdates(message.updates).catch( - (error: unknown) => { - console.warn( - `Failed to apply direct Foundry updates for ${objectType}.`, - error - ); - requestFullRefresh?.(); - } + if (fullRefreshTask) { + pendingDirectWatcherUpdates.push(...message.updates); + } else if (editHistoryUnavailable) { + void applyDirectWatcherUpdates(message.updates).catch((error: unknown) => { + console.warn( + `Failed to apply direct Foundry updates for ${objectType}.`, + error ); - } + refreshInBackground(); + }); } else { pendingDirectWatcherUpdates.push(...message.updates); requestEditHistoryCatchUp?.(); @@ -698,7 +732,7 @@ function createSyncConfig( } case "refresh": { if (editHistoryUnavailable) { - requestFullRefresh?.(); + refreshInBackground(); } else { requestEditHistoryCatchUp?.(); } @@ -707,7 +741,7 @@ function createSyncConfig( case "state": { if (message.status !== "open") break; if (editHistoryUnavailable) { - requestFullRefresh?.(); + refreshInBackground(); } else { requestEditHistoryCatchUp?.(); } diff --git a/packages/node-runtime/package.json b/packages/node-runtime/package.json index 2762d45f..d573b73a 100644 --- a/packages/node-runtime/package.json +++ b/packages/node-runtime/package.json @@ -15,7 +15,7 @@ "@hono/node-server": "^2.1.1", "@party-stack/coordination": "workspace:*", "@party-stack/runtime": "workspace:*", - "@tanstack/node-db-sqlite-persistence": "0.2.30", + "@tanstack/node-db-sqlite-persistence": "0.2.34", "better-sqlite3": "^12.9.0", "cross-keychain": "^1.1.0", "hono": "^4.13.5", @@ -24,7 +24,7 @@ }, "devDependencies": { "@party-stack/build-config": "workspace:*", - "@tanstack/db-sqlite-persistence-core": "0.4.5", + "@tanstack/db-sqlite-persistence-core": "0.4.9", "@types/better-sqlite3": "^7.6.13", "@types/node": "^24.10.1", "typescript": "~7.0.2", diff --git a/packages/node-runtime/src/createNodeRuntime.test.ts b/packages/node-runtime/src/createNodeRuntime.test.ts index d7f90f30..64bbad6f 100644 --- a/packages/node-runtime/src/createNodeRuntime.test.ts +++ b/packages/node-runtime/src/createNodeRuntime.test.ts @@ -2,6 +2,7 @@ import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { createLocalCollection } from "@party-stack/runtime"; +import { eagerStorageTarget } from "@tanstack/db-sqlite-persistence-core"; import { SQLiteCorePersistenceAdapter } from "@tanstack/db-sqlite-persistence-core"; import { Temporal } from "temporal-polyfill"; import { afterEach, describe, expect, it } from "vite-plus/test"; @@ -59,8 +60,8 @@ describe("createNodeRuntime", () => { await first.insert({ id: "one", instant, nested: { dates: [date] } }).isPersisted.promise; await first.cleanup(); const adapter = firstRuntime.persistence!.adapter; - const position = await adapter.loadResumeSnapshot(collectionId); - await adapter.applyCommittedTx(collectionId, { + const position = await adapter.loadResumeSnapshot(eagerStorageTarget(collectionId)); + await adapter.applyCommittedTx(eagerStorageTarget(collectionId), { txId: "temporal-metadata", term: position.latestTerm, seq: position.latestSeq + 1, @@ -83,7 +84,7 @@ describe("createNodeRuntime", () => { expect(second.get("one")?.nested.dates[0]).toBeInstanceOf(Temporal.PlainDate); expect(second.get("one")?.nested.dates[0]?.toString()).toBe(date.toString()); const adapter = secondRuntime.persistence!.adapter; - const snapshot = await adapter.loadResumeSnapshot(collectionId); + const snapshot = await adapter.loadResumeSnapshot(eagerStorageTarget(collectionId)); const rowMetadata = snapshot.rows[0]?.metadata as { date: unknown }; expect(rowMetadata.date).toBeInstanceOf(Temporal.PlainDate); expect(String(rowMetadata.date)).toBe(date.toString()); @@ -94,7 +95,7 @@ describe("createNodeRuntime", () => { expect(String(cursor.instant)).toBe(instant.toString()); if (!(adapter instanceof SQLiteCorePersistenceAdapter)) throw new Error("Expected SQLite persistence"); - const replay = await adapter.pullSince(collectionId, 0); + const replay = await adapter.pullSince(eagerStorageTarget(collectionId), 0); expect(replay.requiresFullReload).toBe(false); if (replay.requiresFullReload) throw new Error("Expected incremental replay"); { @@ -181,10 +182,10 @@ describe("createNodeRuntime", () => { schemaVersion: 3, }).adapter; await tasks.cleanup(); - await expect(incompatible.loadResumeSnapshot(tasks.id)).rejects.toThrow( + await expect(incompatible.loadResumeSnapshot(eagerStorageTarget(tasks.id))).rejects.toThrow( "Schema version mismatch" ); - expect((await current.loadResumeSnapshot(tasks.id)).rows).toHaveLength(1); + expect((await current.loadResumeSnapshot(eagerStorageTarget(tasks.id))).rows).toHaveLength(1); await settings.insert({ id: "other", title: "Still active" }).isPersisted.promise; const reopened = open("tasks", 2); try { diff --git a/packages/oauth/package.json b/packages/oauth/package.json index b6efb15c..bdf9a218 100644 --- a/packages/oauth/package.json +++ b/packages/oauth/package.json @@ -18,11 +18,11 @@ }, "devDependencies": { "@party-stack/build-config": "workspace:*", - "@tanstack/db": "0.12.1", + "@tanstack/db": "0.14.0", "typescript": "~7.0.2", "vite-plus": "catalog:" }, "peerDependencies": { - "@tanstack/db": "0.12.1" + "@tanstack/db": "0.14.0" } } diff --git a/packages/ontology-devtools/package.json b/packages/ontology-devtools/package.json index 9effa579..650ec5b4 100644 --- a/packages/ontology-devtools/package.json +++ b/packages/ontology-devtools/package.json @@ -25,8 +25,8 @@ "devDependencies": { "@party-stack/build-config": "workspace:*", "@tailwindcss/postcss": "^4.1.17", - "@tanstack/db": "0.12.1", - "@tanstack/react-db": "0.5.5", + "@tanstack/db": "0.14.0", + "@tanstack/react-db": "0.5.9", "@tanstack/react-devtools": "^0.10.9", "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", @@ -38,8 +38,8 @@ "vite-plus": "catalog:" }, "peerDependencies": { - "@tanstack/db": "0.12.1", - "@tanstack/react-db": "~0.5.5", + "@tanstack/db": "0.14.0", + "@tanstack/react-db": "0.5.9", "@tanstack/react-devtools": "^0.10.9", "react": "^19.2.0", "react-dom": "^19.2.0" diff --git a/packages/ontology/package.json b/packages/ontology/package.json index ed4211b8..6fecfec3 100644 --- a/packages/ontology/package.json +++ b/packages/ontology/package.json @@ -41,7 +41,7 @@ "@party-stack/icons": "workspace:*", "@party-stack/node-runtime": "workspace:*", "@party-stack/runtime": "workspace:*", - "@tanstack/db-sqlite-persistence-core": "0.4.5", + "@tanstack/db-sqlite-persistence-core": "0.4.9", "change-case": "^5.4.4", "commander": "^14.0.2", "effection": "^4.0.2", @@ -53,12 +53,12 @@ }, "devDependencies": { "@party-stack/build-config": "workspace:*", - "@tanstack/db": "0.12.1", + "@tanstack/db": "0.14.0", "tsx": "^4.20.6", "typescript": "~7.0.2", "vite-plus": "catalog:" }, "peerDependencies": { - "@tanstack/db": "0.12.1" + "@tanstack/db": "0.14.0" } } diff --git a/packages/ontology/src/live/OntologyBackendAdapter.ts b/packages/ontology/src/live/OntologyBackendAdapter.ts index 758336fc..7646bf99 100644 --- a/packages/ontology/src/live/OntologyBackendAdapter.ts +++ b/packages/ontology/src/live/OntologyBackendAdapter.ts @@ -3,10 +3,15 @@ import type { AttachmentTypeDef, OntologyIR } from "../ir/index.js"; import type { Uncertain } from "../utils/uncertain.js"; import type { ValidationIssue } from "../utils/validation.js"; import type { PartialAttachmentMetadata } from "./attachments/types.js"; -import type { Collection, CollectionConfig } from "@tanstack/db"; +import type { Collection, CollectionConfig, UtilsRecord } from "@tanstack/db"; + +export interface OntologyCollectionUtils extends UtilsRecord { + /** Fetch and atomically replace the source snapshot, when supported by the adapter. */ + refresh?: () => Promise; +} export type OntologyCollectionOptions = Omit< - CollectionConfig, string | number>, + CollectionConfig, string | number, never, OntologyCollectionUtils>, "getKey" >; diff --git a/packages/ontology/src/live/objects/createLiveOntologyObjectCollection.test.ts b/packages/ontology/src/live/objects/createLiveOntologyObjectCollection.test.ts index 6abe575c..3fb114b1 100644 --- a/packages/ontology/src/live/objects/createLiveOntologyObjectCollection.test.ts +++ b/packages/ontology/src/live/objects/createLiveOntologyObjectCollection.test.ts @@ -1,5 +1,6 @@ import { MemoryBlobBytesStore, SingleProcessCoordination } from "@party-stack/runtime"; import { createLiveQueryCollection } from "@tanstack/db"; +import { resolvePersistedStorageTarget } from "@tanstack/db-sqlite-persistence-core"; import { describe, expect, it, vi } from "vite-plus/test"; import { o } from "../../ir/index.js"; import { createLiveOntologyActions } from "../actions/createLiveOntologyActions.js"; @@ -78,44 +79,53 @@ function memoryPersistence( latestRowVersion: number; } >(); - const applyCommittedTx = vi.fn((collectionId: string, transaction: PersistedTx) => { - const rows = rowsFor(collectionId); - if (transaction.truncate) rows.clear(); - for (const mutation of transaction.mutations) { - if (mutation.type === "delete") { - rows.delete(String(mutation.key)); - } else { - rows.set(String(mutation.key), mutation.value); + const applyCommittedTx = vi.fn( + (target: Parameters[0], transaction: PersistedTx) => { + const { collectionId } = resolvePersistedStorageTarget(target); + const rows = rowsFor(collectionId); + if (transaction.truncate) rows.clear(); + for (const mutation of transaction.mutations) { + if (mutation.type === "delete") { + rows.delete(String(mutation.key)); + } else { + rows.set(String(mutation.key), mutation.value); + } } + positions.set(collectionId, { + latestTerm: transaction.term, + latestSeq: transaction.seq, + latestRowVersion: transaction.rowVersion, + }); + return Promise.resolve(); } - positions.set(collectionId, { - latestTerm: transaction.term, - latestSeq: transaction.seq, - latestRowVersion: transaction.rowVersion, - }); - return Promise.resolve(); - }); + ); return { adapter: { - loadResumeSnapshot: (collectionId) => + loadResumeSnapshot: (target) => Promise.resolve({ - rows: [...rowsFor(collectionId)].map(([key, value]) => ({ key, value })), + rows: [...rowsFor(resolvePersistedStorageTarget(target).collectionId)].map( + ([key, value]) => ({ key, value }) + ), collectionMetadata: [], - ...(positions.get(collectionId) ?? { latestTerm: 0, latestSeq: 0, latestRowVersion: 0 }), + ...(positions.get(resolvePersistedStorageTarget(target).collectionId) ?? { + latestTerm: 0, + latestSeq: 0, + latestRowVersion: 0, + }), resetEpoch: 0, }), - loadSubset: (collectionId) => + loadSubset: (target) => Promise.resolve( - [...rowsFor(collectionId)].map(([key, value]) => ({ + [...rowsFor(resolvePersistedStorageTarget(target).collectionId)].map(([key, value]) => ({ key, value, })) ), applyCommittedTx, ensureIndex: () => Promise.resolve(), - getStreamPosition: (collectionId) => + getStreamPosition: (target) => Promise.resolve( - positions.get(collectionId) ?? { + positions.get(resolvePersistedStorageTarget(target).collectionId) ?? { latestTerm: 0, latestSeq: 0, latestRowVersion: 0, @@ -140,6 +150,38 @@ function createOptions() { } describe("createLiveOntologyObjectCollection", () => { + it.each([false, true])( + "exposes optional adapter refresh through collections (persisted: %s)", + async (persistObjects) => { + const options = createOptions(); + const refresh = vi.fn(() => Promise.resolve()); + const adapter = backend(({ markReady }) => markReady()); + const getOptions = adapter.getCollectionOptions; + adapter.getCollectionOptions = (objectType) => ({ + ...getOptions(objectType), + utils: { refresh }, + }); + const collection = createLiveOntologyObjectCollection({ + ...options, + backendAdapter: adapter, + runtime: { + owner: options.owner, + namespace: options.ontologyId, + blobBytes: new MemoryBlobBytesStore(), + coordination: options.coordination, + persistence: memoryPersistence(), + }, + persistObjects, + }); + await collection.utils.refresh?.(); + expect(refresh).toHaveBeenCalledOnce(); + refresh.mockRejectedValueOnce(new Error("refresh failed")); + await expect(collection.utils.refresh?.()).rejects.toThrow("refresh failed"); + await collection.cleanup(); + await options.coordination.close(); + } + ); + it("fails fast when object persistence has no adapter", async () => { const options = createOptions(); @@ -236,16 +278,18 @@ describe("createLiveOntologyObjectCollection", () => { query: (q) => q.from({ task: collection }).orderBy(({ task }) => task.title, "asc"), startSync: true, }); + const loading = query.preload(); try { - await vi.waitFor(() => expect(loadSubset).toHaveBeenCalled()); await vi.waitFor(() => expect(query.toArray).toMatchObject([{ id: "persisted", title: "Cached task" }]) ); + await vi.waitFor(() => expect(loadSubset).toHaveBeenCalled()); expect(query.status).not.toBe("ready"); finishRemote(); await vi.waitFor(() => expect(query.status).toBe("ready")); } finally { finishRemote(); + await loading; await query.cleanup(); await collection.cleanup(); await options.coordination.close(); diff --git a/packages/ontology/src/live/objects/createLiveOntologyObjectCollection.ts b/packages/ontology/src/live/objects/createLiveOntologyObjectCollection.ts index 9144d5cc..651f5882 100644 --- a/packages/ontology/src/live/objects/createLiveOntologyObjectCollection.ts +++ b/packages/ontology/src/live/objects/createLiveOntologyObjectCollection.ts @@ -4,10 +4,14 @@ import { persistedCollectionOptions } from "@tanstack/db-sqlite-persistence-core import { decorateObjectAttachmentSources } from "../attachments/attachmentSources.js"; import { ontologyObjectCollectionId } from "./ontologyObjectCollectionId.js"; import type { OntologyIR } from "../../ir/index.js"; -import type { OntologyBackendAdapter } from "../OntologyBackendAdapter.js"; +import type { OntologyBackendAdapter, OntologyCollectionUtils } from "../OntologyBackendAdapter.js"; import type { OntologyObject } from "./OntologyObject.js"; -export type OntologyCollection = Collection; +export type OntologyCollection = Collection< + T, + string | number, + OntologyCollectionUtils +>; function decorateCollectionSync(opts: { ir: OntologyIR; @@ -52,8 +56,21 @@ export function createLiveOntologyObjectCollection(opts: { objectType: opts.objectType, collectionOptions: opts.backendAdapter.getCollectionOptions(opts.objectType.name), }); + let collection: OntologyCollection; + const sourceRefresh = collectionOptions.utils?.refresh; const options = { ...collectionOptions, + utils: { + ...collectionOptions.utils, + ...(sourceRefresh + ? { + refresh: async () => { + await collection.preload(); + await sourceRefresh(); + }, + } + : {}), + }, id: ontologyObjectCollectionId(opts.runtime.owner, opts.runtime.namespace, opts.objectType.name), defaultIndexType: BasicIndex, autoIndex: "eager" as const, @@ -61,7 +78,8 @@ export function createLiveOntologyObjectCollection(opts: { (object as Record)[opts.objectType.primaryKey] as string | number, }; if (!opts.persistObjects) { - return createCollection(options); + collection = createCollection(options); + return collection; } if (!opts.runtime.persistence) { throw new Error( @@ -69,11 +87,12 @@ export function createLiveOntologyObjectCollection(opts: { ); } - return createCollection( - persistedCollectionOptions({ + collection = createCollection( + persistedCollectionOptions({ ...options, schemaVersion: 1, persistence: createCollectionPersistence(opts.runtime.coordination, opts.runtime.persistence), }) ); + return collection; } diff --git a/packages/ontology/src/live/outbox/createOntologyOutbox.test.ts b/packages/ontology/src/live/outbox/createOntologyOutbox.test.ts index 9fa27f6c..4819f838 100644 --- a/packages/ontology/src/live/outbox/createOntologyOutbox.test.ts +++ b/packages/ontology/src/live/outbox/createOntologyOutbox.test.ts @@ -80,7 +80,7 @@ function memoryPersistenceAdapter(): PersistenceAdapter { })) ), applyCommittedTx: ( - _collectionId: string, + _target: Parameters[0], transaction: Parameters[1] ) => { if (transaction.truncate) rows.clear(); diff --git a/packages/remote-ontology/package.json b/packages/remote-ontology/package.json index 8139b9f6..37d69287 100644 --- a/packages/remote-ontology/package.json +++ b/packages/remote-ontology/package.json @@ -27,18 +27,18 @@ "@party-stack/ontology": "workspace:*", "@party-stack/runtime": "workspace:*", "@tanstack/query-core": "^5.102.8", - "@tanstack/query-db-collection": "1.4.0", + "@tanstack/query-db-collection": "1.4.4", "temporal-polyfill": "^0.3.0", "zod": "^4.4.3" }, "devDependencies": { "@party-stack/build-config": "workspace:*", - "@tanstack/db": "0.12.1", + "@tanstack/db": "0.14.0", "@types/node": "^24.10.1", "typescript": "~7.0.2", "vite-plus": "catalog:" }, "peerDependencies": { - "@tanstack/db": "0.12.1" + "@tanstack/db": "0.14.0" } } diff --git a/packages/remote-ontology/src/client.test.ts b/packages/remote-ontology/src/client.test.ts index f17cd3c3..7c267c6c 100644 --- a/packages/remote-ontology/src/client.test.ts +++ b/packages/remote-ontology/src/client.test.ts @@ -364,10 +364,8 @@ describe("createRemoteOntologyBackendAdapter.loadSubset", () => { const controllerA = new AbortController(); const controllerB = new AbortController(); - const requestA = collection._sync.loadSubset({ - limit: 1, - signal: controllerA.signal, - }); + const optionsA = { limit: 1, signal: controllerA.signal }; + const requestA = collection._sync.loadSubset(optionsA); const requestB = collection._sync.loadSubset({ limit: 1, signal: controllerB.signal, @@ -378,6 +376,8 @@ describe("createRemoteOntologyBackendAdapter.loadSubset", () => { expect(loadSubset.mock.calls[0]?.[1]?.signal).toBe(controllerA.signal); expect(loadSubset.mock.calls[1]?.[1]?.signal).toBe(controllerB.signal); + // Releasing demand ends the observer lease in current query collections. + collection._sync.unloadSubset(optionsA); controllerA.abort(new DOMException("Request A canceled.", "AbortError")); requests[1]!.resolve({ objectType: "Note", diff --git a/packages/runtime/package.json b/packages/runtime/package.json index bb104d20..4c1d0e3e 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -21,13 +21,13 @@ }, "devDependencies": { "@party-stack/build-config": "workspace:*", - "@tanstack/db": "0.12.1", - "@tanstack/db-sqlite-persistence-core": "0.4.5", + "@tanstack/db": "0.14.0", + "@tanstack/db-sqlite-persistence-core": "0.4.9", "typescript": "~7.0.2", "vite-plus": "catalog:" }, "peerDependencies": { - "@tanstack/db": "0.12.1", - "@tanstack/db-sqlite-persistence-core": "0.4.5" + "@tanstack/db": "0.14.0", + "@tanstack/db-sqlite-persistence-core": "0.4.9" } } diff --git a/packages/runtime/src/coordinator/createPersistedCollectionCoordinator.ts b/packages/runtime/src/coordinator/createPersistedCollectionCoordinator.ts index ea5edb86..7999053d 100644 --- a/packages/runtime/src/coordinator/createPersistedCollectionCoordinator.ts +++ b/packages/runtime/src/coordinator/createPersistedCollectionCoordinator.ts @@ -6,6 +6,8 @@ import { } from "@party-stack/coordination"; import { safeRandomUUID, + eagerStorageTarget, + managedStorageTarget, SingleProcessCoordinator, toTransportedLoadSubsetOptions, toPersistedCollectionDurabilityError, @@ -24,6 +26,7 @@ import type { ProtocolEnvelope, PullSinceResponse, ReplayableTxDelta, + PersistedStorageTarget, } from "@tanstack/db-sqlite-persistence-core"; const PERSISTENCE_SERVICE = "party-stack.persistence.v1"; @@ -54,6 +57,7 @@ interface ApplyCommittedTxInput { } interface EnsurePersistedIndexInput { + cacheGenerationClaimId?: string; schemaVersion?: number; collectionId: string; signature: string; @@ -61,6 +65,7 @@ interface EnsurePersistedIndexInput { } interface ApplyLocalMutationsInput { + cacheGenerationClaimId?: string; schemaVersion?: number; collectionId: string; rpcId: string; @@ -69,6 +74,7 @@ interface ApplyLocalMutationsInput { } interface PullSinceInput { + cacheGenerationClaimId?: string; schemaVersion?: number; collectionId: string; rpcId: string; @@ -98,7 +104,7 @@ interface PersistenceCoordinationService { type AdapterWithPullSince = PersistenceAdapter & { pullSince?: ( - collectionId: string, + target: PersistedStorageTarget, fromRowVersion: number ) => Promise< | { @@ -214,7 +220,13 @@ class CoordinationPersistenceShim implements PersistedCollectionCoordinator { }; } - setAdapterForCollection(collectionId: string, adapter: HydrationPersistenceAdapter): void { + setAdapterForCollection( + collectionId: string, + adapter: HydrationPersistenceAdapter, + cacheGenerationClaimId?: string + ): void { + if (cacheGenerationClaimId) this.claims.set(collectionId, cacheGenerationClaimId); + else this.claims.delete(collectionId); this.collectionAdapters.set(collectionId, adapter); this.positions.delete(collectionId); } @@ -234,6 +246,14 @@ class CoordinationPersistenceShim implements PersistedCollectionCoordinator { ? adapter.schemaVersion : undefined; } + private readonly claims = new Map(); + private storageTarget( + collectionId: string, + claimId = this.claims.get(collectionId) + ): PersistedStorageTarget { + if (claimId) this.claims.set(collectionId, claimId); + return claimId ? managedStorageTarget(collectionId, claimId) : eagerStorageTarget(collectionId); + } private readonly positions = new Map>(); private readonly appliedEnvelopes = new Map>(); private readonly relayedMessages = new Set(); @@ -291,7 +311,7 @@ class CoordinationPersistenceShim implements PersistedCollectionCoordinator { applyCommittedTx: (input) => this.applyCommittedTx(input), ensurePersistedIndex: (input) => this.adapterForCollection(input.collectionId, input.schemaVersion).ensureIndex( - input.collectionId, + this.storageTarget(input.collectionId, input.cacheGenerationClaimId), input.signature, input.spec ), @@ -346,7 +366,7 @@ class CoordinationPersistenceShim implements PersistedCollectionCoordinator { try { if (!active) return; const adapter = this.adapterForCollection(collectionId); - const current = await adapter.getStreamPosition?.(collectionId); + const current = await adapter.getStreamPosition?.(this.storageTarget(collectionId)); if (!active) return; if ( !force && @@ -408,6 +428,7 @@ class CoordinationPersistenceShim implements PersistedCollectionCoordinator { } else { this.collectionSubscriptions.delete(collectionId); this.collectionAdapters.delete(collectionId); + this.claims.delete(collectionId); this.positions.delete(collectionId); } } @@ -514,7 +535,7 @@ class CoordinationPersistenceShim implements PersistedCollectionCoordinator { ): Promise { try { await (scopedAdapter ?? this.adapterForCollection(collectionId, schemaVersion)).applyCommittedTx( - collectionId, + this.storageTarget(collectionId, tx.cacheGenerationClaimId), tx ); } catch (error) { @@ -536,13 +557,14 @@ class CoordinationPersistenceShim implements PersistedCollectionCoordinator { signature: string, spec: PersistedIndexSpec, scopedAdapter?: HydrationPersistenceAdapter, - localEnsureCompleted?: boolean + localEnsureCompleted?: boolean, + cacheGenerationClaimId?: string ): Promise { if (this.isLeader()) { return localEnsureCompleted ? Promise.resolve() : (scopedAdapter ?? this.adapterForCollection(collectionId)).ensureIndex( - collectionId, + this.storageTarget(collectionId, cacheGenerationClaimId), signature, spec ); @@ -551,6 +573,7 @@ class CoordinationPersistenceShim implements PersistedCollectionCoordinator { collectionId, signature, spec, + cacheGenerationClaimId, schemaVersion: this.schemaVersion(scopedAdapter ?? this.adapterForCollection(collectionId)) ?? this.schemaVersion(this.adapterForCollection(collectionId)), @@ -559,7 +582,8 @@ class CoordinationPersistenceShim implements PersistedCollectionCoordinator { async requestApplyLocalMutations( collectionId: string, - mutations: PersistedMutationEnvelope[] + mutations: PersistedMutationEnvelope[], + cacheGenerationClaimId?: string ): Promise { const envelopeId = safeRandomUUID(); const burst = this.resumeChecks.has(collectionId) @@ -582,6 +606,7 @@ class CoordinationPersistenceShim implements PersistedCollectionCoordinator { envelopeId, schemaVersion: this.schemaVersion(this.adapterForCollection(collectionId)), mutations, + cacheGenerationClaimId, }); } finally { burst?.pending.delete(envelopeId); @@ -600,13 +625,23 @@ class CoordinationPersistenceShim implements PersistedCollectionCoordinator { } } - pullSince(collectionId: string, fromRowVersion: number): Promise { - return this.service.methods.pullSince({ + pullSince( + collectionId: string, + fromRowVersion: number, + scopedAdapter?: HydrationPersistenceAdapter, + cacheGenerationClaimId?: string + ): Promise { + this.storageTarget(collectionId, cacheGenerationClaimId); + const input = { collectionId, rpcId: safeRandomUUID(), fromRowVersion, + cacheGenerationClaimId, schemaVersion: this.schemaVersion(this.adapterForCollection(collectionId)), - }); + }; + return scopedAdapter && this.isLeader() + ? this.handlePullSince(input, scopedAdapter) + : this.service.methods.pullSince(input); } private relayMessage(input: RelayMessageInput): Promise { @@ -635,8 +670,8 @@ class CoordinationPersistenceShim implements PersistedCollectionCoordinator { const adapter = this.adapterForCollection(collectionId); position = Promise.resolve( adapter.getStreamPosition - ? adapter.getStreamPosition(collectionId) - : adapter.loadResumeSnapshot(collectionId, { includeRows: false }) + ? adapter.getStreamPosition(this.storageTarget(collectionId)) + : adapter.loadResumeSnapshot(this.storageTarget(collectionId), { includeRows: false }) ).then((current) => ({ term: (current?.latestTerm ?? 0) + 1, seq: current?.latestSeq ?? 0, @@ -666,10 +701,12 @@ class CoordinationPersistenceShim implements PersistedCollectionCoordinator { }; } + this.storageTarget(input.collectionId, input.cacheGenerationClaimId); const position = await this.position(input.collectionId); const nextPosition = { ...position, seq: position.seq + 1, rowVersion: position.rowVersion + 1 }; const txId = input.envelopeId; - await adapter.applyCommittedTx(input.collectionId, { + await adapter.applyCommittedTx(this.storageTarget(input.collectionId, input.cacheGenerationClaimId), { + cacheGenerationClaimId: input.cacheGenerationClaimId, txId, term: nextPosition.term, seq: nextPosition.seq, @@ -738,13 +775,22 @@ class CoordinationPersistenceShim implements PersistedCollectionCoordinator { return response; } - private async handlePullSince(input: PullSinceInput): Promise { - const adapter = this.adapterForCollection(input.collectionId, input.schemaVersion); - const current = await adapter.getStreamPosition?.(input.collectionId); + private async handlePullSince( + input: PullSinceInput, + scopedAdapter?: HydrationPersistenceAdapter + ): Promise { + const adapter: AdapterWithPullSince = + scopedAdapter ?? this.adapterForCollection(input.collectionId, input.schemaVersion); + const current = await adapter.getStreamPosition?.( + this.storageTarget(input.collectionId, input.cacheGenerationClaimId) + ); const position = current ? { term: current.latestTerm, seq: current.latestSeq, rowVersion: current.latestRowVersion } : await this.position(input.collectionId); - const result = await adapter.pullSince?.(input.collectionId, input.fromRowVersion); + const result = await adapter.pullSince?.( + this.storageTarget(input.collectionId, input.cacheGenerationClaimId), + input.fromRowVersion + ); if (!result || result.requiresFullReload) { return { type: "rpc:pullSince:res", diff --git a/packages/runtime/src/utils/createLocalCollection.test.ts b/packages/runtime/src/utils/createLocalCollection.test.ts index 303e6257..8088d8f3 100644 --- a/packages/runtime/src/utils/createLocalCollection.test.ts +++ b/packages/runtime/src/utils/createLocalCollection.test.ts @@ -4,6 +4,7 @@ import { SharedWorkerCoordinationHost, } from "@party-stack/coordination/shared-worker"; import { queryOnce } from "@tanstack/db"; +import { eagerStorageTarget, resolvePersistedStorageTarget } from "@tanstack/db-sqlite-persistence-core"; import { SingleProcessCoordinator } from "@tanstack/db-sqlite-persistence-core"; import { describe, expect, it, vi } from "vite-plus/test"; import { createPersistedCollectionCoordinator } from "../coordinator/createPersistedCollectionCoordinator.js"; @@ -38,21 +39,32 @@ function memoryAdapter(): PersistenceAdapter & { return collection; }; return { - loadResumeSnapshot: (collectionId) => + loadResumeSnapshot: (target) => Promise.resolve({ - rows: [...rows(collectionId)].map(([key, value]) => ({ key, value })), + rows: [...rows(resolvePersistedStorageTarget(target).collectionId)].map(([key, value]) => ({ + key, + value, + })), collectionMetadata: [], - ...(positions.get(collectionId) ?? { latestTerm: 0, latestSeq: 0, latestRowVersion: 0 }), + ...(positions.get(resolvePersistedStorageTarget(target).collectionId) ?? { + latestTerm: 0, + latestSeq: 0, + latestRowVersion: 0, + }), resetEpoch: 0, }), - loadSubset: (collectionId) => + loadSubset: (target) => Promise.resolve( - [...rows(collectionId)].map(([key, value]) => ({ + [...rows(resolvePersistedStorageTarget(target).collectionId)].map(([key, value]) => ({ key, value, })) ), - applyCommittedTx: (collectionId: string, transaction: PersistedTx) => { + applyCommittedTx: ( + target: Parameters[0], + transaction: PersistedTx + ) => { + const { collectionId } = resolvePersistedStorageTarget(target); const collection = rows(collectionId); for (const mutation of transaction.mutations) { if (mutation.type === "delete") { @@ -69,9 +81,9 @@ function memoryAdapter(): PersistenceAdapter & { return Promise.resolve(); }, ensureIndex: () => Promise.resolve(), - getStreamPosition: (collectionId) => + getStreamPosition: (target) => Promise.resolve( - positions.get(collectionId) ?? { + positions.get(resolvePersistedStorageTarget(target).collectionId) ?? { latestTerm: 0, latestSeq: 0, latestRowVersion: 0, @@ -181,7 +193,7 @@ describe("createLocalCollection", () => { await collection.preload(); expect(loadResumeSnapshot).toHaveBeenCalledWith( - "party-stack:test-owner:persisted:persisted-items", + eagerStorageTarget("party-stack:test-owner:persisted:persisted-items"), expect.any(Object) ); expect(collection.get("one")).toMatchObject({ @@ -371,7 +383,7 @@ describe("createLocalCollection", () => { { mutationId: "two", type: "insert", key: "two", value: { id: "two" } }, ]); expect(apply).toHaveBeenLastCalledWith( - "client-0", + eagerStorageTarget("client-0"), expect.objectContaining({ seq: 2, rowVersion: 2 }) ); } finally { @@ -436,7 +448,7 @@ describe("createLocalCollection", () => { mutations: [{ type: "insert", key: "two", value: { id: "two", title: "From source" } }], }) ).resolves.toMatchObject({ ok: true, latestRowVersion: 2 }); - expect(await adapter.loadSubset(collectionId, {})).toEqual( + expect(await adapter.loadSubset(eagerStorageTarget(collectionId), {})).toEqual( expect.arrayContaining([{ key: "two", value: { id: "two", title: "From source" } }]) ); @@ -449,6 +461,41 @@ describe("createLocalCollection", () => { }); describe("collection-scoped persistence", () => { + it("preserves managed cache claims across writes, index requests, and pullSince", async () => { + const coordination = new SingleProcessCoordination({ scope: "managed-targets" }); + const adapter = memoryAdapter(); + const apply = vi.spyOn(adapter, "applyCommittedTx"); + const ensure = vi.spyOn(adapter, "ensureIndex"); + const position = vi.spyOn(adapter, "getStreamPosition"); + const coordinator = createPersistedCollectionCoordinator(coordination, adapter); + coordinator.setAdapterForCollection?.("generation", adapter, "claim"); + const target = { kind: "managed", storageCollectionId: "generation", claimId: "claim" }; + await coordinator.requestApplyCommittedTx("generation", { + txId: "tx", + term: 1, + seq: 1, + rowVersion: 1, + mutations: [], + cacheGenerationClaimId: "claim", + }); + expect(apply).toHaveBeenCalledWith( + target, + expect.objectContaining({ cacheGenerationClaimId: "claim" }) + ); + await coordinator.requestEnsurePersistedIndex( + "generation", + "index", + { expressionSql: [] }, + undefined, + false, + "claim" + ); + expect(ensure).toHaveBeenCalledWith(target, "index", { expressionSql: [] }); + await coordinator.pullSince?.("generation", 0, undefined, "claim"); + expect(position).toHaveBeenCalledWith(target); + await coordination.close(); + }); + it("forwards local mode and schema and routes writes through the scoped adapter", async () => { const coordination = new SingleProcessCoordination({ scope: "scoped-persistence" }); const scoped = { ...memoryAdapter(), schemaVersion: 2 }; @@ -475,7 +522,7 @@ describe("collection-scoped persistence", () => { }).runtime, }); await collection.preload(); - expect(registerAdapter).toHaveBeenCalledExactlyOnceWith(collection.id, scoped); + expect(registerAdapter).toHaveBeenCalledExactlyOnceWith(collection.id, scoped, undefined); expect(resolvePersistenceForCollection).toHaveBeenCalledWith({ collectionId: collection.id, mode: "sync-absent", @@ -490,7 +537,7 @@ describe("collection-scoped persistence", () => { rootPosition.mockClear(); await coordinator.pullSince!(collection.id, 0); expect(scopedPosition).not.toHaveBeenCalled(); - expect(rootPosition).toHaveBeenCalledWith(collection.id); + expect(rootPosition).toHaveBeenCalledWith(eagerStorageTarget(collection.id)); await coordination.close(); }); @@ -516,7 +563,7 @@ describe("collection-scoped persistence", () => { await collection.preload(); await collection.insert({ id: "one", title: "Selected" }).isPersisted.promise; expect(resolvePersistenceForMode).toHaveBeenCalledWith("sync-absent"); - expect(apply).toHaveBeenCalledWith(collection.id, expect.anything()); + expect(apply).toHaveBeenCalledWith(eagerStorageTarget(collection.id), expect.anything()); expect(rootApply).not.toHaveBeenCalled(); } finally { await collection.cleanup(); @@ -556,8 +603,8 @@ describe("collection-scoped persistence", () => { await Promise.all([tasks.preload(), settings.preload()]); await tasks.insert({ id: "one", title: "Task" }).isPersisted.promise; await settings.insert({ id: "one", title: "Setting" }).isPersisted.promise; - expect(tasksApply).toHaveBeenCalledWith(tasks.id, expect.anything()); - expect(settingsApply).toHaveBeenCalledWith(settings.id, expect.anything()); + expect(tasksApply).toHaveBeenCalledWith(eagerStorageTarget(tasks.id), expect.anything()); + expect(settingsApply).toHaveBeenCalledWith(eagerStorageTarget(settings.id), expect.anything()); expect(rootApply).not.toHaveBeenCalled(); await tasks.cleanup(); await settings.insert({ id: "two", title: "Still active" }).isPersisted.promise; @@ -587,7 +634,7 @@ describe("collection-scoped persistence", () => { }, ]); expect(apply).toHaveBeenCalledWith( - "items", + eagerStorageTarget("items"), expect.objectContaining({ mutations: [ { @@ -693,7 +740,7 @@ describe("collection-scoped persistence", () => { requiresFullReload: false, changedKeys: ["one"], }); - expect(pullSince).toHaveBeenCalledWith("items", 8); + expect(pullSince).toHaveBeenCalledWith(eagerStorageTarget("items"), 8); await coordination.close(); }); }); @@ -828,7 +875,7 @@ describe("persistence page resume", () => { await collection.insert({ id: "one", title: "Original" }).isPersisted.promise; load.mockClear(); // A durable adapter commit without its notification models a missed broadcast. - await adapter.applyCommittedTx(collection.id, { + await adapter.applyCommittedTx(eagerStorageTarget(collection.id), { txId: "missed", term: 1, seq: 2, diff --git a/packages/salesforce-ontology/package.json b/packages/salesforce-ontology/package.json index 29a2db25..403bce95 100644 --- a/packages/salesforce-ontology/package.json +++ b/packages/salesforce-ontology/package.json @@ -36,15 +36,15 @@ }, "devDependencies": { "@party-stack/build-config": "workspace:*", - "@tanstack/db": "0.12.1", + "@tanstack/db": "0.14.0", "@tanstack/query-core": "^5.102.8", - "@tanstack/query-db-collection": "1.4.0", + "@tanstack/query-db-collection": "1.4.4", "typescript": "~7.0.2", "vite-plus": "catalog:" }, "peerDependencies": { - "@tanstack/db": "0.12.1", + "@tanstack/db": "0.14.0", "@tanstack/query-core": "^5.102.8", - "@tanstack/query-db-collection": "1.4.0" + "@tanstack/query-db-collection": "1.4.4" } } diff --git a/packages/sqlite-ontology/package.json b/packages/sqlite-ontology/package.json index 2f45fb16..ae032960 100644 --- a/packages/sqlite-ontology/package.json +++ b/packages/sqlite-ontology/package.json @@ -21,14 +21,14 @@ }, "devDependencies": { "@party-stack/build-config": "workspace:*", - "@tanstack/db": "0.12.1", + "@tanstack/db": "0.14.0", "@types/node": "^24.10.1", "better-sqlite3": "^12.9.0", "typescript": "~7.0.2", "vite-plus": "catalog:" }, "peerDependencies": { - "@tanstack/db": "0.12.1", + "@tanstack/db": "0.14.0", "better-sqlite3": "^12.4.6" }, "peerDependenciesMeta": { diff --git a/packages/web-runtime/e2e/tab.ts b/packages/web-runtime/e2e/tab.ts index ee83f26e..41e61da1 100644 --- a/packages/web-runtime/e2e/tab.ts +++ b/packages/web-runtime/e2e/tab.ts @@ -1,6 +1,7 @@ import { createIndexedDBPersistence } from "@party-stack/db-indexeddb-persistence"; import { createLocalCollection } from "@party-stack/runtime"; import { createCollection, createLiveQueryCollection } from "@tanstack/db"; +import { eagerStorageTarget } from "@tanstack/db-sqlite-persistence-core"; import { persistedCollectionOptions } from "@tanstack/db-sqlite-persistence-core"; import { BroadcastCollectionCoordinator } from "@tanstack/db-sqlite-persistence-core/broadcast-coordinator"; import { createWebRuntime } from "../src/createWebRuntime.js"; @@ -64,10 +65,13 @@ Object.assign(window, { persistenceTest: { durable: async () => ({ position: await (upstreamPersistence ?? runtime.persistence)!.adapter.getStreamPosition?.( - collection.id + eagerStorageTarget(collection.id) ), keys: ( - await (upstreamPersistence ?? runtime.persistence)!.adapter.loadSubset(collection.id, {}) + await (upstreamPersistence ?? runtime.persistence)!.adapter.loadSubset( + eagerStorageTarget(collection.id), + {} + ) ).map((row) => row.key), }), rows: () => [...query.values()].map(({ id, title, score }) => ({ id, title, score })), diff --git a/packages/web-runtime/package.json b/packages/web-runtime/package.json index f73e0902..e84bc97f 100644 --- a/packages/web-runtime/package.json +++ b/packages/web-runtime/package.json @@ -22,8 +22,8 @@ }, "devDependencies": { "@party-stack/build-config": "workspace:*", - "@tanstack/db": "0.12.1", - "@tanstack/db-sqlite-persistence-core": "0.4.5", + "@tanstack/db": "0.14.0", + "@tanstack/db-sqlite-persistence-core": "0.4.9", "playwright": "^1.61.1", "typescript": "~7.0.2", "vite": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index db86c803..16541c00 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,9 +9,6 @@ catalogs: '@vitest/browser-playwright': specifier: 5.0.4 version: 5.0.4 - vite: - specifier: npm:@voidzero-dev/vite-plus-core@1.2.0 - version: 1.2.0 vite-plus: specifier: 1.2.0 version: 1.2.0 @@ -20,7 +17,7 @@ catalogs: version: 5.0.4 overrides: - vite@*: npm:@voidzero-dev/vite-plus-core@1.2.0 + vite: npm:@voidzero-dev/vite-plus-core@1.2.0 vitest@*: 5.0.4 importers: @@ -43,7 +40,7 @@ importers: specifier: ~7.0.2 version: 7.0.2 vite: - specifier: 'catalog:' + specifier: npm:@voidzero-dev/vite-plus-core@1.2.0 version: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' vite-plus: specifier: 'catalog:' @@ -131,7 +128,7 @@ importers: specifier: ~7.0.2 version: 7.0.2 vite: - specifier: 'catalog:' + specifier: npm:@voidzero-dev/vite-plus-core@1.2.0 version: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' vite-plus: specifier: 'catalog:' @@ -170,11 +167,11 @@ importers: specifier: workspace:* version: link:../../packages/web-runtime '@tanstack/db': - specifier: 0.12.1 - version: 0.12.1(typescript@7.0.2) + specifier: 0.14.0 + version: 0.14.0(typescript@7.0.2) '@tanstack/react-db': - specifier: 0.5.5 - version: 0.5.5(react@19.3.0)(typescript@7.0.2) + specifier: 0.5.9 + version: 0.5.9(react@19.3.0)(typescript@7.0.2) '@tanstack/react-devtools': specifier: ^0.10.9 version: 0.10.9(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(csstype@3.2.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.14) @@ -231,7 +228,7 @@ importers: specifier: ~7.0.2 version: 7.0.2 vite: - specifier: 'catalog:' + specifier: npm:@voidzero-dev/vite-plus-core@1.2.0 version: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' vite-plus: specifier: 'catalog:' @@ -249,17 +246,17 @@ importers: specifier: workspace:* version: link:../../packages/ontology '@tanstack/db': - specifier: 0.12.1 - version: 0.12.1(typescript@7.0.2) + specifier: 0.14.0 + version: 0.14.0(typescript@7.0.2) '@tanstack/query-core': specifier: ^5.102.8 version: 5.102.8 '@tanstack/query-db-collection': - specifier: 1.4.0 - version: 1.4.0(@tanstack/query-core@5.102.8)(typescript@7.0.2) + specifier: 1.4.4 + version: 1.4.4(@tanstack/query-core@5.102.8)(typescript@7.0.2) '@tanstack/react-db': - specifier: 0.5.5 - version: 0.5.5(react@19.3.0)(typescript@7.0.2) + specifier: 0.5.9 + version: 0.5.9(react@19.3.0)(typescript@7.0.2) '@tanstack/react-router': specifier: ^1.168.7 version: 1.168.7(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -295,7 +292,7 @@ importers: specifier: ~7.0.2 version: 7.0.2 vite: - specifier: 'catalog:' + specifier: npm:@voidzero-dev/vite-plus-core@1.2.0 version: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' vite-plus: specifier: 'catalog:' @@ -319,17 +316,17 @@ importers: specifier: workspace:* version: link:../../packages/web-runtime '@tanstack/db': - specifier: 0.12.1 - version: 0.12.1(typescript@7.0.2) + specifier: 0.14.0 + version: 0.14.0(typescript@7.0.2) '@tanstack/query-core': specifier: ^5.102.8 version: 5.102.8 '@tanstack/query-db-collection': - specifier: 1.4.0 - version: 1.4.0(@tanstack/query-core@5.102.8)(typescript@7.0.2) + specifier: 1.4.4 + version: 1.4.4(@tanstack/query-core@5.102.8)(typescript@7.0.2) '@tanstack/react-db': - specifier: 0.5.5 - version: 0.5.5(react@19.3.0)(typescript@7.0.2) + specifier: 0.5.9 + version: 0.5.9(react@19.3.0)(typescript@7.0.2) '@tanstack/react-devtools': specifier: ^0.10.9 version: 0.10.9(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(csstype@3.2.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.14) @@ -383,7 +380,7 @@ importers: specifier: ~7.0.2 version: 7.0.2 vite: - specifier: 'catalog:' + specifier: npm:@voidzero-dev/vite-plus-core@1.2.0 version: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' vite-plus: specifier: 'catalog:' @@ -410,11 +407,11 @@ importers: specifier: workspace:* version: link:../../packages/web-runtime '@tanstack/db': - specifier: 0.12.1 - version: 0.12.1(typescript@7.0.2) + specifier: 0.14.0 + version: 0.14.0(typescript@7.0.2) '@tanstack/react-db': - specifier: 0.5.5 - version: 0.5.5(react@19.3.0)(typescript@7.0.2) + specifier: 0.5.9 + version: 0.5.9(react@19.3.0)(typescript@7.0.2) '@tanstack/react-devtools': specifier: ^0.10.9 version: 0.10.9(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(csstype@3.2.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.14) @@ -453,7 +450,7 @@ importers: specifier: ~7.0.2 version: 7.0.2 vite: - specifier: 'catalog:' + specifier: npm:@voidzero-dev/vite-plus-core@1.2.0 version: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' vite-plus: specifier: 'catalog:' @@ -485,7 +482,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0) old/foundry-graphql: dependencies: @@ -531,7 +528,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) old/foundry-graphql-schema: dependencies: @@ -589,7 +586,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) old/foundry-graphql-server: dependencies: @@ -629,7 +626,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) old/osdk-react: dependencies: @@ -675,7 +672,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) old/osdk-react-todo-app: dependencies: @@ -735,7 +732,7 @@ importers: specifier: ~7.0.2 version: 7.0.2 vite: - specifier: 'catalog:' + specifier: npm:@voidzero-dev/vite-plus-core@1.2.0 version: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' vite-plus: specifier: 'catalog:' @@ -761,7 +758,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/better-auth: dependencies: @@ -776,17 +773,17 @@ importers: specifier: workspace:* version: link:../build-config '@tanstack/db': - specifier: 0.12.1 - version: 0.12.1(typescript@7.0.2) + specifier: 0.14.0 + version: 0.14.0(typescript@7.0.2) better-auth: specifier: ^1.7.1 - version: 1.7.1(@cloudflare/workers-types@5.20260827.1)(@tanstack/react-start@1.167.12(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(better-sqlite3@12.9.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.14)(vitest@5.0.4) + version: 1.7.1(@cloudflare/workers-types@5.20260827.1)(@tanstack/react-start@1.167.12(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)))(better-sqlite3@12.9.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.14)(vitest@5.0.4) typescript: specifier: ~7.0.2 version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/blobs: dependencies: @@ -810,14 +807,14 @@ importers: specifier: workspace:* version: link:../build-config '@tanstack/db': - specifier: 0.12.1 - version: 0.12.1(typescript@7.0.2) + specifier: 0.14.0 + version: 0.14.0(typescript@7.0.2) typescript: specifier: ~7.0.2 version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/build-config: devDependencies: @@ -829,7 +826,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/connections: dependencies: @@ -847,14 +844,14 @@ importers: specifier: workspace:* version: link:../build-config '@tanstack/db': - specifier: 0.12.1 - version: 0.12.1(typescript@7.0.2) + specifier: 0.14.0 + version: 0.14.0(typescript@7.0.2) typescript: specifier: ~7.0.2 version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/coordination: dependencies: @@ -873,13 +870,13 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/db-indexeddb-persistence: dependencies: '@tanstack/db-sqlite-persistence-core': - specifier: 0.4.5 - version: 0.4.5(typescript@7.0.2) + specifier: 0.4.9 + version: 0.4.9(typescript@7.0.2) idb: specifier: ^8.0.3 version: 8.0.3 @@ -891,17 +888,17 @@ importers: specifier: workspace:* version: link:../build-config '@tanstack/db': - specifier: 0.12.1 - version: 0.12.1(typescript@7.0.2) + specifier: 0.14.0 + version: 0.14.0(typescript@7.0.2) '@tanstack/node-db-sqlite-persistence': - specifier: 0.2.30 - version: 0.2.30(typescript@7.0.2) + specifier: 0.2.34 + version: 0.2.34(typescript@7.0.2) '@types/better-sqlite3': specifier: ^7.6.13 version: 7.6.13 '@vitest/browser-playwright': specifier: 'catalog:' - version: 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4) + version: 5.0.4(playwright@1.61.1)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) better-sqlite3: specifier: ^12.9.0 version: 12.9.0 @@ -916,10 +913,10 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) vitest: specifier: 'catalog:' - version: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0) + version: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) packages/durable-object-ontology: dependencies: @@ -940,8 +937,8 @@ importers: specifier: workspace:* version: link:../build-config '@tanstack/db': - specifier: 0.12.1 - version: 0.12.1(typescript@7.0.2) + specifier: 0.14.0 + version: 0.14.0(typescript@7.0.2) esbuild: specifier: ^0.28.1 version: 0.28.1 @@ -950,10 +947,10 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.1)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.1)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) vitest: specifier: 'catalog:' - version: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0) + version: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4)(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) wrangler: specifier: ^4.127.0 version: 4.127.0(@cloudflare/workers-types@5.20260827.1) @@ -968,7 +965,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/expo-runtime: dependencies: @@ -979,14 +976,20 @@ importers: specifier: workspace:* version: link:../runtime '@tanstack/expo-db-sqlite-persistence': - specifier: 0.2.30 - version: 0.2.30(expo-sqlite@55.0.15(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0))(typescript@7.0.2) + specifier: 0.2.34 + version: 0.2.34(expo-sqlite@57.0.4(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0))(typescript@7.0.2) + expo-blob: + specifier: ^57.0.2 + version: 57.0.2(expo@57.0.29) + expo-crypto: + specifier: ^57.0.4 + version: 57.0.4(expo@57.0.29) expo-secure-store: - specifier: ^57.0.1 - version: 57.0.1(expo@55.0.23) + specifier: ^57.0.4 + version: 57.0.4(expo@57.0.29) expo-web-browser: - specifier: ^57.0.2 - version: 57.0.2(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)) + specifier: ^57.0.3 + version: 57.0.3(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)) temporal-polyfill: specifier: ^0.3.0 version: 0.3.0 @@ -997,12 +1000,15 @@ importers: '@react-native-community/netinfo': specifier: ^12.0.1 version: 12.0.1(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0) + expo: + specifier: ^57.0.29 + version: 57.0.29(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) expo-file-system: - specifier: ^55.0.19 - version: 55.0.19(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)) + specifier: ^57.0.7 + version: 57.0.7(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)) expo-sqlite: - specifier: ^55.0.15 - version: 55.0.15(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0) + specifier: ^57.0.4 + version: 57.0.4(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0) typescript: specifier: ~7.0.2 version: 7.0.2 @@ -1033,7 +1039,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/foundry-object-set-watcher: dependencies: @@ -1079,7 +1085,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/foundry-ontology: dependencies: @@ -1151,11 +1157,11 @@ importers: specifier: workspace:* version: link:../build-config '@tanstack/db': - specifier: 0.12.1 - version: 0.12.1(typescript@7.0.2) + specifier: 0.14.0 + version: 0.14.0(typescript@7.0.2) '@tanstack/query-db-collection': - specifier: 1.4.0 - version: 1.4.0(@tanstack/query-core@5.102.8)(typescript@7.0.2) + specifier: 1.4.4 + version: 1.4.4(@tanstack/query-core@5.102.8)(typescript@7.0.2) '@types/node': specifier: ^24.10.1 version: 24.10.1 @@ -1176,7 +1182,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/icons: devDependencies: @@ -1188,7 +1194,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/icons-blueprint: dependencies: @@ -1216,7 +1222,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/icons-expo: dependencies: @@ -1250,7 +1256,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/icons-lucide: dependencies: @@ -1278,7 +1284,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/icons-salesforce-lightning: dependencies: @@ -1294,7 +1300,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/node-runtime: dependencies: @@ -1308,8 +1314,8 @@ importers: specifier: workspace:* version: link:../runtime '@tanstack/node-db-sqlite-persistence': - specifier: 0.2.30 - version: 0.2.30(typescript@7.0.2) + specifier: 0.2.34 + version: 0.2.34(typescript@7.0.2) better-sqlite3: specifier: ^12.9.0 version: 12.9.0 @@ -1330,8 +1336,8 @@ importers: specifier: workspace:* version: link:../build-config '@tanstack/db-sqlite-persistence-core': - specifier: 0.4.5 - version: 0.4.5(typescript@7.0.2) + specifier: 0.4.9 + version: 0.4.9(typescript@7.0.2) '@types/better-sqlite3': specifier: ^7.6.13 version: 7.6.13 @@ -1343,7 +1349,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/oauth: dependencies: @@ -1361,14 +1367,14 @@ importers: specifier: workspace:* version: link:../build-config '@tanstack/db': - specifier: 0.12.1 - version: 0.12.1(typescript@7.0.2) + specifier: 0.14.0 + version: 0.14.0(typescript@7.0.2) typescript: specifier: ~7.0.2 version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/ontology: dependencies: @@ -1400,8 +1406,8 @@ importers: specifier: workspace:* version: link:../runtime '@tanstack/db-sqlite-persistence-core': - specifier: 0.4.5 - version: 0.4.5(typescript@7.0.2) + specifier: 0.4.9 + version: 0.4.9(typescript@7.0.2) change-case: specifier: ^5.4.4 version: 5.4.4 @@ -1416,7 +1422,7 @@ importers: version: 2.6.1 oxfmt: specifier: ^0.72.0 - version: 0.72.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) + version: 0.72.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) temporal-polyfill: specifier: ^0.3.0 version: 0.3.0 @@ -1431,8 +1437,8 @@ importers: specifier: workspace:* version: link:../build-config '@tanstack/db': - specifier: 0.12.1 - version: 0.12.1(typescript@7.0.2) + specifier: 0.14.0 + version: 0.14.0(typescript@7.0.2) tsx: specifier: ^4.20.6 version: 4.20.6 @@ -1441,7 +1447,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/ontology-devtools: dependencies: @@ -1480,11 +1486,11 @@ importers: specifier: ^4.1.17 version: 4.1.17 '@tanstack/db': - specifier: 0.12.1 - version: 0.12.1(typescript@7.0.2) + specifier: 0.14.0 + version: 0.14.0(typescript@7.0.2) '@tanstack/react-db': - specifier: 0.5.5 - version: 0.5.5(react@19.3.0)(typescript@7.0.2) + specifier: 0.5.9 + version: 0.5.9(react@19.3.0)(typescript@7.0.2) '@tanstack/react-devtools': specifier: ^0.10.9 version: 0.10.9(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(csstype@3.2.3)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.14) @@ -1511,7 +1517,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/remote-ontology: dependencies: @@ -1525,8 +1531,8 @@ importers: specifier: ^5.102.8 version: 5.102.8 '@tanstack/query-db-collection': - specifier: 1.4.0 - version: 1.4.0(@tanstack/query-core@5.102.8)(typescript@7.0.2) + specifier: 1.4.4 + version: 1.4.4(@tanstack/query-core@5.102.8)(typescript@7.0.2) temporal-polyfill: specifier: ^0.3.0 version: 0.3.0 @@ -1538,8 +1544,8 @@ importers: specifier: workspace:* version: link:../build-config '@tanstack/db': - specifier: 0.12.1 - version: 0.12.1(typescript@7.0.2) + specifier: 0.14.0 + version: 0.14.0(typescript@7.0.2) '@types/node': specifier: ^24.10.1 version: 24.10.1 @@ -1548,7 +1554,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/runtime: dependencies: @@ -1563,17 +1569,17 @@ importers: specifier: workspace:* version: link:../build-config '@tanstack/db': - specifier: 0.12.1 - version: 0.12.1(typescript@7.0.2) + specifier: 0.14.0 + version: 0.14.0(typescript@7.0.2) '@tanstack/db-sqlite-persistence-core': - specifier: 0.4.5 - version: 0.4.5(typescript@7.0.2) + specifier: 0.4.9 + version: 0.4.9(typescript@7.0.2) typescript: specifier: ~7.0.2 version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/salesforce-client: dependencies: @@ -1595,7 +1601,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/salesforce-ontology: dependencies: @@ -1631,20 +1637,20 @@ importers: specifier: workspace:* version: link:../build-config '@tanstack/db': - specifier: 0.12.1 - version: 0.12.1(typescript@7.0.2) + specifier: 0.14.0 + version: 0.14.0(typescript@7.0.2) '@tanstack/query-core': specifier: ^5.102.8 version: 5.102.8 '@tanstack/query-db-collection': - specifier: 1.4.0 - version: 1.4.0(@tanstack/query-core@5.102.8)(typescript@7.0.2) + specifier: 1.4.4 + version: 1.4.4(@tanstack/query-core@5.102.8)(typescript@7.0.2) typescript: specifier: ~7.0.2 version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/sqlite-ontology: dependencies: @@ -1659,8 +1665,8 @@ importers: specifier: workspace:* version: link:../build-config '@tanstack/db': - specifier: 0.12.1 - version: 0.12.1(typescript@7.0.2) + specifier: 0.14.0 + version: 0.14.0(typescript@7.0.2) '@types/node': specifier: ^24.10.1 version: 24.10.1 @@ -1672,7 +1678,7 @@ importers: version: 7.0.2 vite-plus: specifier: 'catalog:' - version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + version: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) packages/tanstack-start-build-config: dependencies: @@ -1714,7 +1720,7 @@ importers: specifier: ~7.0.2 version: 7.0.2 vite: - specifier: 'catalog:' + specifier: npm:@voidzero-dev/vite-plus-core@1.2.0 version: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' vite-plus: specifier: 'catalog:' @@ -1761,11 +1767,11 @@ importers: specifier: workspace:* version: link:../build-config '@tanstack/db': - specifier: 0.12.1 - version: 0.12.1(typescript@7.0.2) + specifier: 0.14.0 + version: 0.14.0(typescript@7.0.2) '@tanstack/db-sqlite-persistence-core': - specifier: 0.4.5 - version: 0.4.5(typescript@7.0.2) + specifier: 0.4.9 + version: 0.4.9(typescript@7.0.2) playwright: specifier: ^1.61.1 version: 1.61.1 @@ -1773,7 +1779,7 @@ importers: specifier: ~7.0.2 version: 7.0.2 vite: - specifier: 'catalog:' + specifier: npm:@voidzero-dev/vite-plus-core@1.2.0 version: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' vite-plus: specifier: 'catalog:' @@ -3142,18 +3148,40 @@ packages: react-native: optional: true + '@expo/cli@57.0.30': + resolution: {integrity: sha512-xyPD5pkNvEojk9oI3xs2kR3JHSrkOPcCmuLnHkw4fcU/zSuqcoY3y6z/t/IIUlTpnuTFeTpDIiIvafBTO/mtKA==} + hasBin: true + peerDependencies: + expo: '*' + expo-router: '*' + react-native: '*' + peerDependenciesMeta: + expo-router: + optional: true + react-native: + optional: true + '@expo/code-signing-certificates@0.0.6': resolution: {integrity: sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==} '@expo/config-plugins@55.0.11': resolution: {integrity: sha512-85ZSmIK8rMfvYbG/2IHtwnykVdb6LI0ZavduM3ZwUMThyyVegYjVsO5Zek2ec8xzPhCmYX0ar5onnFEcwUDUlw==} + '@expo/config-plugins@57.0.10': + resolution: {integrity: sha512-XFcj01blofSohoOxibrPsrsFPt2gAigrcWE3s6Vg+rSHvP9QVEfnyfmJNVsuIVT0EjY/1rZVJ4SajNDR5cJjpA==} + '@expo/config-types@55.0.6': resolution: {integrity: sha512-S+GJKYoIjnWlert/9vXuTohaTsMbyOLSVxdIgPgoq3P4N1p4CWrfyZLnz6qRug8wYSO5fcYcS9mFleyEP8wRLg==} + '@expo/config-types@57.0.2': + resolution: {integrity: sha512-ewW08OonrcRIsRKIlFvvcmmafE5zemb1ocu3HkNwtVPyRtj2w42pZCAkMIROYpcVBaPnc3mDT9UZDzwXWC3i6g==} + '@expo/config@55.0.21': resolution: {integrity: sha512-yHmSSnBrm2ecB1DNsCrZZrScVnvXrtg3+PAMwI3ocxEtD9YK8yX5xV2xi2pmor9TYitAO+udFWxo+XDEGibznw==} + '@expo/config@57.0.10': + resolution: {integrity: sha512-YpexTqLWEXdbbXgHh7Zhpq+qA5diuvO46lb7TiY81JwqKLAl9zK31APY1D7jUFlDX56owje5RtK0o5bMmGIMUg==} + '@expo/devcert@1.2.1': resolution: {integrity: sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==} @@ -3168,6 +3196,17 @@ packages: react-native: optional: true + '@expo/devtools@57.0.1': + resolution: {integrity: sha512-GyUf+wFNkbttaX0jR7MZa9bm77U0IrLg6d2AjpxdyoXw/w4abHoXG0oFufwLMgP9zLTd5+Ct4X/ffNUTnlzZgg==} + peerDependencies: + react: '*' + react-native: '*' + peerDependenciesMeta: + react: + optional: true + react-native: + optional: true + '@expo/dom-webview@55.0.6': resolution: {integrity: sha512-ZNm8tiNEZysxrr36J0x4mOCGyJDcaIvL/3tMxBz0VJIJDcV19xjuJAhJQxHovu+jKx6s9tRyEAINa1mdrzV39g==} peerDependencies: @@ -3183,13 +3222,26 @@ packages: resolution: {integrity: sha512-M1NXeZCA1mkMkYOyIe7PlyRX0/jqFtMoJgyblnlq/vpCRfmueFT7RnGSQG8uEFDF5WHOFGijAQ3fogPh3/n5Ng==} engines: {node: '>=20.12.0'} + '@expo/expo-modules-macros-plugin@0.6.1': + resolution: {integrity: sha512-cpsLZE4rqkc1Y3eZTkxB98jrqY1YXgetmtxFt8q89jBRmk3quRuk1BZo+VcnCSObZardjg99r1k5xijEMONFGA==} + '@expo/fingerprint@0.16.7': resolution: {integrity: sha512-BH8sicYOqZ1iBMwCVEGIz6uTTfylosjc49FoMmCYIzKOiYdiVehsfoYBwyfxwWIiya1VMhm1gv0cgOP8fxHpDw==} hasBin: true + '@expo/fingerprint@0.20.14': + resolution: {integrity: sha512-7sihStGX1ZepJBNbYgeQY7cU2GQ+PSkSROe3g7JOQqnT6OOuX0rXKY0ZNoVnlB2Y/H5FWnnJ6QrPlK3fNg9+bQ==} + hasBin: true + + '@expo/image-utils@0.11.6': + resolution: {integrity: sha512-rmMpn7QGp6pUGRCQ3vvB9RP/vqq2q/PpToRh4dL/FbhSZOLZ1YTotpuLHf71FGOEfgKil/4PCd3hkWe/CCeiEg==} + '@expo/image-utils@0.8.17': resolution: {integrity: sha512-1eVrX6FFkPNl9DJNiw3Gjsf6PtG7HttbapEVvcROVrA9PCMikujOJWWHDYKGptmKoVIIVajFZE8UHGHUb3mPug==} + '@expo/inline-modules@0.1.8': + resolution: {integrity: sha512-5OCHzv/VHVuam9UT6/yeArtUnmkp5VdAH6k4JwdRzK/34ukzdjQK7B9n71fiswZKtm2MmZlPuy4hJo2Ao8CZNw==} + '@expo/json-file@10.0.16': resolution: {integrity: sha512-fcVkWEj+hLuP2yt5W0aw6LmDRqSPWDLUSxOMcmFeV+algmIF59sQVKCwB9btjQLd4V6x9N0pISkQEkBubUHrCw==} @@ -3202,6 +3254,9 @@ packages: '@expo/local-build-cache-provider@55.0.12': resolution: {integrity: sha512-Wqhe7ajt6lyIEQvqDC1zm0MQ1RqQLlM9awCepY9pz+tm9rvhuxGPZTSddWeD8k4kolinBlDbLDFnNi06XgaDWQ==} + '@expo/local-build-cache-provider@57.0.9': + resolution: {integrity: sha512-hox13/K/dqvKG7StLYs9nwyNR/mJ5ieAkNN8jTee6liMPyUQTfpzgticRyWnVxj49Fr8PpTaQab/ArYVEBJOcQ==} + '@expo/log-box@55.0.12': resolution: {integrity: sha512-f9ARS8J60cq3LLNdIqmUjYwyerBzVS5Ecp7KjIf3GOIPjW0571rkcwLz4/U18l/1DeSkSzIkYsNl2TC9oTdWaQ==} peerDependencies: @@ -3210,6 +3265,14 @@ packages: react: '*' react-native: '*' + '@expo/log-box@57.0.4': + resolution: {integrity: sha512-IxwS9s1L2muj8mj8AQSuiy7u8OFJdc02NRFo2me/Tj6DiaeG5SREqmpBE4rQpR2cadqSg5jl8Qab8Cjie616dg==} + peerDependencies: + '@expo/dom-webview': ^57.0.1 + expo: '*' + react: '*' + react-native: '*' + '@expo/metro-config@55.0.20': resolution: {integrity: sha512-dUv0simEyPbN2wbOjI+BdEZyXdghgCZD0+3rrA1WxXZN1lRofUx6g2+Nik2Qg61v/BXFrCTh8reYEzQPzHOhdQ==} peerDependencies: @@ -3218,9 +3281,23 @@ packages: expo: optional: true + '@expo/metro-config@57.0.13': + resolution: {integrity: sha512-r+YCwqh8FM5cymkC3MdYO32DqLZQ2wDWkGEnzwgwI40Bj355BvL4XoY0hGgao9vNHjYa7be7XxbVqtMTi76snw==} + peerDependencies: + expo: '*' + peerDependenciesMeta: + expo: + optional: true + + '@expo/metro-file-map@57.0.4': + resolution: {integrity: sha512-gLABIbU3SlxRVVCe56ZfYFbxjhdpAKomQYr6LIeDYf7rca8Pfvzv8FS39hH0+IcdpEHly2Jy0X2nd4/obd8Kig==} + '@expo/metro@55.1.2': resolution: {integrity: sha512-EOBoqqX4jD7JvHBHKDoek8SmWyAFPuJePyDSqIAhIjOCPAM67fVxd4+pCXJ6SDK1P9so55v2iTFBwCoeCmEj4Q==} + '@expo/metro@56.0.2': + resolution: {integrity: sha512-Ld5AeYMCCDa8bLeWhfuLbZFFjlV3f6ORqyPz2glGh6RltIngMuLf9BTC2yvHFjkKuGxL5SynijmA8xmNNWn5iA==} + '@expo/osascript@2.7.1': resolution: {integrity: sha512-Zn03EX6In7ts2lPUW2ESUSkEhEWQN1qqsiXjadtZMJOuZRkMiAg1ZQHuvz9DjByDWNJ2pBwAGyrts9lj9k389g==} engines: {node: '>=12'} @@ -3231,11 +3308,17 @@ packages: '@expo/plist@0.5.4': resolution: {integrity: sha512-Jqppj0FULNq6Zp5JtQrFICl8TtpMjwwUbxEcEC2T3z7m+TOrTQEHZXz3D3Ay7vhbmvD+VMgfWJ4ARclJXeN8Eg==} + '@expo/plist@0.8.1': + resolution: {integrity: sha512-3gTReGIUm0oRaMClsAJYxBnVPCl6fVpsl8HS+DTVxDhW4GyVyxg9E/Znm3BvcHtUJ51RJJI14pC1wvrNilCRHw==} + '@expo/prebuild-config@55.0.22': resolution: {integrity: sha512-2p1lE2hOCy9HUV9qxbEqmetxUgcBnVY8jR+j/SYRXgaBeHalyQ5d09Ol5XL36/HO2fViK8VmSnTbS7C3t8eFGg==} peerDependencies: expo: '*' + '@expo/prebuild-config@57.0.19': + resolution: {integrity: sha512-trBpySOP8Pv/xtMSb5ASAL5y8h6kbZTinakZ5cUbu0lX5iDpiB3Cg3ANYc3coriH7jpl3cJecG4e0PzUMced8g==} + '@expo/require-utils@55.0.8': resolution: {integrity: sha512-DDS5R67iz2SJwncbpcUMHtnsq31dSfllhaEncKxGlK0/ewgc2CaXUCkDA2AumfT7ubnSXiPkU9Gn8v2xCrvj+A==} peerDependencies: @@ -3244,6 +3327,14 @@ packages: typescript: optional: true + '@expo/require-utils@57.0.6': + resolution: {integrity: sha512-PKWfFdMgwWrlSP6NGplCi2nK0dEM5MeJ2Cj1ijr5VWTmZ+pKsJsJw2dWkBtlFDyxMY8eUjCPZ7hoqFEnbc0Pcw==} + peerDependencies: + typescript: ^5.0.0 || ^5.0.0-0 || ^6.0.0 || ^7.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@expo/router-server@55.0.19': resolution: {integrity: sha512-bm2CFp9eI9biqZXz7Vkske0LXHZaLyonWV6sTEWUaR8zJ0NdVnUxb1Vd0iCdhW8GtweOUfMK4z7gojd0kydxjA==} peerDependencies: @@ -3266,9 +3357,34 @@ packages: react-server-dom-webpack: optional: true + '@expo/router-server@57.0.12': + resolution: {integrity: sha512-afP8Lu+ozEP/6qvLuxeBKoBVSeU+e0zwk+iYBUM6fBxGRniJQyG1HqpHQFr0ZEocuVxRjmq9eoCnspFuuzDQoA==} + peerDependencies: + '@expo/metro-runtime': ^57.0.16 + expo: '*' + expo-constants: ^57.0.21 + expo-font: ^57.0.4 + expo-router: '*' + expo-server: ^57.0.3 + react: '*' + react-dom: '*' + react-server-dom-webpack: ~19.0.1 || ~19.1.2 || ~19.2.1 + peerDependenciesMeta: + '@expo/metro-runtime': + optional: true + expo-router: + optional: true + react-dom: + optional: true + react-server-dom-webpack: + optional: true + '@expo/schema-utils@55.0.5': resolution: {integrity: sha512-wdV4SzWJ/l+6y/r9vDaqPqXGyfxUD1igbX8rRbRBfkVXenAUY2qenq6HF4S0iJ0pjpVDNTANn5aQY7VV55hhsA==} + '@expo/schema-utils@57.0.2': + resolution: {integrity: sha512-fMu/jyN0l1Wzv7XkeWR4IYCx1M8ryui3FdBNGrWwbRgJ7EhxXxK8E2jxP2W3pbgUwUY0V3hG8+GyfCZwny+Lxw==} + '@expo/sdk-runtime-versions@1.0.0': resolution: {integrity: sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==} @@ -3289,6 +3405,11 @@ packages: '@expo/ws-tunnel@1.0.6': resolution: {integrity: sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q==} + '@expo/ws-tunnel@2.0.1': + resolution: {integrity: sha512-mljsRffbOHzmSimYQbQu9h9NR3alwCCwEEouRGFL4QRTt8dNvsNtKEPa/5jHmtDLDtsh4xXsWKwzjN9BTpeXHQ==} + peerDependencies: + ws: ^8.0.0 + '@expo/xcpretty@4.4.5': resolution: {integrity: sha512-J3eL4n4h5QTwfD0SIz8OIk6/+sOL/hFZAMacgCM07UNlxBQfJipEpIC2AQxvGkbYeStByJ0TVhQAJo+DeNgaSQ==} hasBin: true @@ -4701,6 +4822,10 @@ packages: resolution: {integrity: sha512-iRfhxsOePloy12TUzpL9dCwnxLi3eurg8+JurSozAmWh4HtmKu0IJHNjbEKk9htI5n4RlFom8LQ9vVcvgWOTbw==} engines: {node: '>= 20.19.4'} + '@react-native/babel-plugin-codegen@0.86.3': + resolution: {integrity: sha512-O6Xza4JBGPIU8J7YbKTyBoYL4thpy8jMW/oaLDWdAyOwYHKIjK47pAL5HUEbOe2bWz2PEKjbYRF2ApkJv1ottQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + '@react-native/babel-preset@0.83.10': resolution: {integrity: sha512-wTZWvs5cUQqr2qBAoJNKUy6IBH7wqPAJdxTC834S/7vBqtXypYHxDAFzYXkIh8pRTC86IBYxyc2IrBYHnB8O2g==} engines: {node: '>= 20.19.4'} @@ -4719,6 +4844,12 @@ packages: peerDependencies: '@babel/core': '*' + '@react-native/codegen@0.86.3': + resolution: {integrity: sha512-Ux4jHi0fh+bdtVEcL0gaPLbY56V+SvFUDl/8sRAE1jdb4k+o7fT/4Nc29yz4X+qfjstkSqObQTMBGhdzxH9JvA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + peerDependencies: + '@babel/core': '*' + '@react-native/community-cli-plugin@0.85.3': resolution: {integrity: sha512-fs85dmbIqNmtzEixDb0g+q6R3Vt4H9eAt8/inIZdDKfjN76+sUJA2r1nxODQ76bU23MrIbz8sI7KFBPaWk/zQw==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -4739,6 +4870,10 @@ packages: resolution: {integrity: sha512-uAu7rM5o/Np1zgp6fi5zM1sP1aB8DcS7DdOLcj/TkSutOAjkMqqd2lWt1/+3S7qXexRHVK5XcP+o3VXo4L/V0A==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + '@react-native/debugger-frontend@0.86.3': + resolution: {integrity: sha512-TQmeofQ0PcuylhhlleOeuzHYZfbrgm3gayXzowqUEzgRisTm1D40/J3ggqs7XkQi5HP5ZA3n8dHmKL9vIzPcsw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + '@react-native/debugger-shell@0.83.6': resolution: {integrity: sha512-684TJMBCU0l0ZjJWzrnK0HH+ERaM9KLyxyArE1k7BrP+gVl4X9GO0Pi94RoInOxvW/nyV65sOU6Ip1F3ygS0cg==} engines: {node: '>= 20.19.4'} @@ -4747,6 +4882,10 @@ packages: resolution: {integrity: sha512-/jRAaT9boiCttIcEwS02WPwYkUihqsjSaK/TMtHz05vT6uMgac9PaQt5kzBQLIABv5aEIa5gtrMmKVz49MjkjQ==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + '@react-native/debugger-shell@0.86.3': + resolution: {integrity: sha512-O4ds+J7xZfxkbih9T+cAGegBdvKSPKYJm/lDgC9CpEjFMkmzWTpVLU3Qsv9sqZuo58z+sGhIcJfPsCFFWHpqbQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + '@react-native/dev-middleware@0.83.6': resolution: {integrity: sha512-22xoddLTelpcVnF385SNH2hdP7X2av5pu7yRl/WnM5jBznbcl0+M9Ce94cj+WVeomsoUF/vlfuB0Ooy+RMlRiA==} engines: {node: '>= 20.19.4'} @@ -4755,6 +4894,10 @@ packages: resolution: {integrity: sha512-JYzBiT4A8w+KQt+dOD5v+ti+tDrGoPnsSTuApq3Ls4RB5sfWbDlYMyz3dbc8qBIHz9tv0sQ5+eOu6Xwqzr5AQA==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + '@react-native/dev-middleware@0.86.3': + resolution: {integrity: sha512-LiEPTqTg/63bYUnrPyHLfjTDCNhA/+CUqI1+DsA9tYyewtSbULd5awsva6SgE10I+2iMhgKXS3ymkhU/kSCrGA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + '@react-native/gradle-plugin@0.85.3': resolution: {integrity: sha512-39dY2j50Q1pntejzwt3XL7vwXtrj8jcIfHq6E+gyu3jzYxZJVvMkMutQ39vSg6zinIQOX36oQDhidXUbCXzgoA==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -4769,6 +4912,9 @@ packages: '@react-native/normalize-colors@0.85.3': resolution: {integrity: sha512-hj0PScZEhIbcOvQV5yMKX3ha4XEIOy/SVE1Rrpp0beW0dpNLOgSC7KDxGewmDnIHK9YdQUXGY9eMEfShUMIaZw==} + '@react-native/normalize-colors@0.86.3': + resolution: {integrity: sha512-Cv3CDkprb67GrzuaS9BGbBJC/6G4lIw3nyKOHRKTqTTum4bn37y5+R0Z04L8mcbQN85eEohNrRwb7IOM4j6uvg==} + '@react-native/virtualized-lists@0.85.3': resolution: {integrity: sha512-dsCjI//OIPEUJMyNHp4l7zNLVjCx7bcaRUceOCkU+IB17hkbtbGWvi7HjGFSzy7FJGmS/MOlcfpb72xXiy1Oig==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -5221,18 +5367,18 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 - '@tanstack/db-ivm@0.1.26': - resolution: {integrity: sha512-Td2HbR68RfpMPv4zyMNQk83z/5gTjGl/I1LA/07A26LXMEPGh+hTsNbfPSr5YPZhq6KXX9lDFjDzv3Qz3DjV5w==} + '@tanstack/db-ivm@0.2.0': + resolution: {integrity: sha512-AuMuGzVdWR5qzJPRYajmrUp95vMo0duvMZ0zBjMOvaI9rkh2GHZWCsJ/p7s81UN4Lep6HHJjahnYUF+XVL0+AQ==} peerDependencies: typescript: '>=4.7' - '@tanstack/db-sqlite-persistence-core@0.4.5': - resolution: {integrity: sha512-gRw41AdB9GlyUKuqNvuGU5wjatROQY2KvzEN1qnsFPw0aqPn7MHjg2WBr5TVy1yE+4n97dru62Yin9lu92r5BA==} + '@tanstack/db-sqlite-persistence-core@0.4.9': + resolution: {integrity: sha512-QKt0a3Y7eEMO78EcfU5Nc41GjxSA0i21UGPJFJt/GRof49M0Pq/FxxpGFTDZzWIvFiUfWwnyIGGyO1U2eQeEQQ==} peerDependencies: typescript: '>=4.7' - '@tanstack/db@0.12.1': - resolution: {integrity: sha512-nSLr/NUDdN/rnM66qZaBY0Xl5pKikv2T5aANSNyVN1/NY86Y8erhVRSh9Cm20t1tAZkikqJWfID23S5sejR82w==} + '@tanstack/db@0.14.0': + resolution: {integrity: sha512-9Qv82wvCbZgNL0hZn5G3nl90VRxVxLKop8lYMuRbN7ut/Uy5gx6dis1iDwrhTPadrPEDGzt1aJtZ6t+mtI6bQg==} peerDependencies: typescript: '>=4.7' @@ -5310,8 +5456,8 @@ packages: peerDependencies: solid-js: '>=1.9.7' - '@tanstack/expo-db-sqlite-persistence@0.2.30': - resolution: {integrity: sha512-JfUSNNIQGimBnUo+PQxiViPvkdiQr+mdL1ScmxTADOpvk3FS07VObvkdAy42PRJUKlNfvGmF04QELAFm1nWXIA==} + '@tanstack/expo-db-sqlite-persistence@0.2.34': + resolution: {integrity: sha512-RWfgzFDPLtFaFQwo2kOWtcQ7b+a+S3KZ08b61UIqaFCXdtIgqODHJKxYnWJtZeU61VxiYgwTIONu4YiO9LLqrw==} peerDependencies: expo-sqlite: ^55.0.10 || ^57.0.0 typescript: '>=4.7' @@ -5320,8 +5466,8 @@ packages: resolution: {integrity: sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg==} engines: {node: '>=20.19'} - '@tanstack/node-db-sqlite-persistence@0.2.30': - resolution: {integrity: sha512-AlwOqmjQTVzTb+ASLfshh8RVkyZgPFXJh124ZJTbIU1hi3hWt/+yOC+eg+ZG2nWkqrOnTrbjA9PXRRYYfhDtIA==} + '@tanstack/node-db-sqlite-persistence@0.2.34': + resolution: {integrity: sha512-8wf+CPI9SaxkQnpWBm/coWkmdhFJ5kE5b5pKpP/VMpIpXUuqKekhxycV7eZ4bTgyB/DmxB/GjHjjRScPJNiCDQ==} peerDependencies: typescript: '>=4.7' @@ -5335,14 +5481,14 @@ packages: '@tanstack/query-core@5.90.10': resolution: {integrity: sha512-EhZVFu9rl7GfRNuJLJ3Y7wtbTnENsvzp+YpcAV7kCYiXni1v8qZh++lpw4ch4rrwC0u/EZRnBHIehzCGzwXDSQ==} - '@tanstack/query-db-collection@1.4.0': - resolution: {integrity: sha512-/CfT2VCIJ0vMikfOWP5xE7/0jAcj56gbGT4QReoVMt5QIrVLSwrPYkplguD7Ixj79i7J0MWvubmB8GyaUUco7A==} + '@tanstack/query-db-collection@1.4.4': + resolution: {integrity: sha512-LpCNnxb6QrWk34uf/nPfwVYdkr7Qd2gW0LsGn2HeeqP2gKGyCTDVGWhUO5aGZziKjDLSRC7bBxYfIIfuTXf4kQ==} peerDependencies: - '@tanstack/query-core': ^5.0.0 + '@tanstack/query-core': ^5.90.15 typescript: '>=4.7' - '@tanstack/react-db@0.5.5': - resolution: {integrity: sha512-zR4cxeJQ8nlSNikrsl0nkG9tNmvW/t32nlMeRpex0AbbkfaW4BLQADpMvBxb9M3AhnW6fO8FGBnyvlrGRS6tgw==} + '@tanstack/react-db@0.5.9': + resolution: {integrity: sha512-xqH5mkcH3jprITTPSQqWGQzAJ3hANkQw7WSKJe4qHO8Szhw0vEhqJIqEzk7K2g8WzynKQ6VTZlrY5kUB3iSs1Q==} peerDependencies: react: '>=18.0.0' @@ -6047,6 +6193,11 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + agent-cli-detector@0.1.7: + resolution: {integrity: sha512-d8OWDVdZMgjhLUT9ZPgSv/BdFFF9pVuscC0JdUSz3bjwE15gcp6u/o0/JooM2yyAWC49KThFhXlgTXRa9B7yng==} + engines: {node: '>=18.18'} + hasBin: true + anser@1.4.10: resolution: {integrity: sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==} @@ -6183,6 +6334,9 @@ packages: babel-plugin-syntax-hermes-parser@0.33.3: resolution: {integrity: sha512-/Z9xYdaJ1lC0pT9do6TqCqhOSLfZ5Ot8D5za1p+feEfWYupCOfGbhhEXN9r2ZgJtDNUNRw/Z+T2CvAGKBqtqWA==} + babel-plugin-syntax-hermes-parser@0.36.1: + resolution: {integrity: sha512-ycduwJbvdvIMmVvlAZqGggS+pm5Eu4Bk9pcV9Sm2Z4PJNRVsKkv0g7vHj+LeuC1gHTeF67sJXFOq61IlqCa2hA==} + babel-plugin-transform-flow-enums@0.0.2: resolution: {integrity: sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==} @@ -6201,6 +6355,21 @@ packages: expo-widgets: optional: true + babel-preset-expo@57.0.15: + resolution: {integrity: sha512-GIJZeeBlDAO753yBaslD5wkElbB1//D0oRanc7ncahXDD0cBizIznJDNm1cKgVxcMaVZFacdozSa8jFHV1YJfQ==} + peerDependencies: + '@babel/runtime': ^7.20.0 + expo: '*' + expo-widgets: ^57.0.24 + react-refresh: '>=0.14.0 <1.0.0' + peerDependenciesMeta: + '@babel/runtime': + optional: true + expo: + optional: true + expo-widgets: + optional: true + bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -7003,24 +7172,47 @@ packages: react: '*' react-native: '*' + expo-asset@57.0.20: + resolution: {integrity: sha512-xm1p3Squ/dCzipISO1TE5+kDsIFEUc6DxEtRe2rRgPMDLPi6AumLlrNorFxetiP6zrvxK23xzVlNng/g7o/+aA==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + + expo-blob@57.0.2: + resolution: {integrity: sha512-xb4XF1mOD3KwVigFIDKu8IyMjv8ZgBe5n29LPfjyyDzMprzLO1l+HAS2l3h4XBMEiRgg/LFJFzQ4k2jVUpu0lg==} + peerDependencies: + expo: '*' + expo-constants@55.0.17: resolution: {integrity: sha512-t0SNWmXTjXPCHzjNsv1Okjaj8SpXQcUjDPtYDqvofDS+BhAsvlKNmwaaWXvduBjjmmmJWNH8q1/x9IWQ+upg8g==} peerDependencies: expo: '*' react-native: '*' - expo-file-system@55.0.19: - resolution: {integrity: sha512-c4smCbMqELLI3YQrGpw21MwZIREXM2e53vQD/+KWQcae1q+hgw8J2TroEqcQ/jVOtFpZYVvyVfgu4HDKNEKmNw==} + expo-constants@57.0.22: + resolution: {integrity: sha512-+mBjnzMWMKIrQa7BP7LTDRC0W7cI+q12/sZns4HzVQJGzgGoAdRJSTEKiObxIw/3EswKXlESkRob8upiL95Q4A==} peerDependencies: expo: '*' react-native: '*' + expo-crypto@57.0.4: + resolution: {integrity: sha512-LqMMs/N6wvwZyw4deVmcl3AgXgqMwCVQ9WyWf2eaWyrk99TXSKk5da3QspiLOOjnn5Qv9f8C50cDNuoyLThpdw==} + peerDependencies: + expo: '*' + expo-file-system@55.0.26: resolution: {integrity: sha512-KBOQWqxumCxJBN29Lu13QpccwAhcjsgDYCJSVVjfGydaK/UbEkH/1rT/WZmdfoZ4G8mUJlfRbWfnJtfHUXxsdw==} peerDependencies: expo: '*' react-native: '*' + expo-file-system@57.0.7: + resolution: {integrity: sha512-1vkZRadOsIputFDBcclqBa1pO3iXOO6iIsdqJipQWw4V5TSnWgmRTuOF7ro8IzrVue+PHbum++KCG3Tawem4cg==} + peerDependencies: + expo: '*' + react-native: '*' + expo-font@55.0.8: resolution: {integrity: sha512-WyP75pnKqhLNktYwDn3xKAUNt5rLihRDv8XWGhhz6VEhVqypixpT86NA3uGtiDTlM3gGjhrYCY7o7ypXgCUOZg==} peerDependencies: @@ -7028,16 +7220,33 @@ packages: react: '*' react-native: '*' + expo-font@57.0.4: + resolution: {integrity: sha512-7WOMC2oA6xCfOm8bbofqWn2bIqyWFm7YLEnpc/gA1MJH0Ll0TfT/3+CqRkoW2eNRWMS8sxrz5WukUmSvmNquHA==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + expo-keep-awake@55.0.8: resolution: {integrity: sha512-PfIpMfM+STOBwkR5XOE+yVtER86c44MD+W8QD8JxuO0sT9pF7Y1SJYakWlpvX8xsGA+bjKLxftm9403s9kQhKA==} peerDependencies: expo: '*' react: '*' + expo-keep-awake@57.0.2: + resolution: {integrity: sha512-GqgH746wtJImmsbyHmCQoi7cOslKuWQBbJHXr53S35Bpf5UvxCAztvnitUEd7D0QvFN2rvkOhylbZpgZZfBLlQ==} + peerDependencies: + expo: '*' + react: '*' + expo-modules-autolinking@55.0.21: resolution: {integrity: sha512-P9KsJgOwI7JVwxmGfRvcXkXO4LNRvHRdWmb4ukLmX15G/vZ7b6SM17yiYkPceWq1F5KeeZ11KFjEcl0y17xy7w==} hasBin: true + expo-modules-autolinking@57.0.16: + resolution: {integrity: sha512-OJh6rMMIIZ/dfwOk2FufJjjsUSw5Boy1Y5Tl+zbBmua/FcD03eEg097dkYIf3iBHVkQ6q9eR08qT88qxvnelow==} + hasBin: true + expo-modules-core@55.0.25: resolution: {integrity: sha512-yXpfg7aHLbuqoXocK34Vua6Aey5SCyqLygAsXAMbul9P8vfBjLpaOPiTJ5cLVF7Drfq8ownqVJO6qpGEtZ6GOw==} peerDependencies: @@ -7048,8 +7257,23 @@ packages: react-native-worklets: optional: true - expo-secure-store@57.0.1: - resolution: {integrity: sha512-tLa1VmSadOq19mA/dwkl99RbHyjLE0T1qqBYMY3/OsguZTI+rlrDy/DDJjupqlVtmr95hD7o1pYqx5aL+B4YMA==} + expo-modules-core@57.0.23: + resolution: {integrity: sha512-vZQoSlhqU6me5K4KCl42yXB0x3zqAiG2FAjzHmbuez77CslJJQ+82cmJoHC3tN5X8o/KxRArJejKNQIeciyoXA==} + peerDependencies: + react: '*' + react-native: '*' + react-native-worklets: ^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0 + peerDependenciesMeta: + react-native-worklets: + optional: true + + expo-modules-jsi@57.1.2: + resolution: {integrity: sha512-DYhBwO0fqcKIUma4jF16zRDP/m6ZbawEZbo4eX3gdekREIFqrrz8HKWXydgNUqOyOQcUkdrkZatxi2I9+V6E8g==} + peerDependencies: + react-native: '*' + + expo-secure-store@57.0.4: + resolution: {integrity: sha512-7zBA1RThURTXdP4w65kBzZNbcSsfArqVd+p+ETAnPRWXO8J296Qd0YaCVyRpJ0HSwlCnGdvOnqO+NgSreHsOhA==} peerDependencies: expo: '*' @@ -7057,8 +7281,12 @@ packages: resolution: {integrity: sha512-yuNHbxobUKCXWn9Z/H+lgYk6heXEFub6q1h8Yo9kCLxgL2afluWZ/YylbtxgHgmFwDVUmi6milQYfL+wwQMlzw==} engines: {node: '>=20.16.0'} - expo-sqlite@55.0.15: - resolution: {integrity: sha512-vxE5fs6l953QSIyievQ8TuSstj62eC7zUREjNzbUOwRWaHGGnhnlPJM1HLoTIv+oIt3+b1m7k2fmcDGkpK5t3w==} + expo-server@57.0.3: + resolution: {integrity: sha512-aK+LdKzauHSGmsOStZtyxdzv0zWssCkxTw3m4QuOhfDSJsZaMRTd9O41d8ixU/QfELTbaJ0oRNcF7JFV/7O9YQ==} + engines: {node: '>=20.16.0'} + + expo-sqlite@57.0.4: + resolution: {integrity: sha512-3N/bnDx4+kWEC9GABxKPLe/6RHTZJR/7DNT5tz/cYi8f7emign3ooavXnJn0+JPBA0JhHmsdDPrmC+CtT86Jzg==} peerDependencies: expo: '*' react: '*' @@ -7072,8 +7300,8 @@ packages: react: '*' react-native: '*' - expo-web-browser@57.0.2: - resolution: {integrity: sha512-3vl5kvd7PB48ub6PpNIJUuPxO8xVa6D8RnIgNba6SXRwqFprOfeEZgwTgtm41kz0AAtvMOztUVNEUkwrHKjqMQ==} + expo-web-browser@57.0.3: + resolution: {integrity: sha512-+ecvhyS/PdWkpv4ai6FF1Trd6Y/4kn/eQ3O86mie2ecU3w+I8bKZh13kppY/jw1uSL7yikt03L5W/iiHojrFwQ==} peerDependencies: expo: '*' react-native: '*' @@ -7095,6 +7323,29 @@ packages: react-native-webview: optional: true + expo@57.0.29: + resolution: {integrity: sha512-pJeJKps+jS2Q8+f14B12T6pJccTdRqej395Cqh2+isGziOUDggs1qvT4R9/UkE8WuRW3HYNAD0x0Mmnfw56FEQ==} + hasBin: true + peerDependencies: + '@expo/dom-webview': '*' + '@expo/metro-runtime': '*' + react: '*' + react-dom: '*' + react-native: '*' + react-native-web: '*' + react-native-webview: '*' + peerDependenciesMeta: + '@expo/dom-webview': + optional: true + '@expo/metro-runtime': + optional: true + react-dom: + optional: true + react-native-web: + optional: true + react-native-webview: + optional: true + exponential-backoff@3.1.3: resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} @@ -7447,6 +7698,12 @@ packages: hermes-estree@0.35.0: resolution: {integrity: sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==} + hermes-estree@0.36.0: + resolution: {integrity: sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w==} + + hermes-estree@0.36.1: + resolution: {integrity: sha512-guv1nQ6IJ7S83NRFPWc3SA7IBZrdNC9kapwOq6uXvF4wP+sDCgjzQbKPCoyYmoyZRzztF/n/c36l/rccCZSiCw==} + hermes-parser@0.32.0: resolution: {integrity: sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==} @@ -7459,6 +7716,12 @@ packages: hermes-parser@0.35.0: resolution: {integrity: sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==} + hermes-parser@0.36.0: + resolution: {integrity: sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w==} + + hermes-parser@0.36.1: + resolution: {integrity: sha512-GApNk4zLHi2UWoWZZkx7LNCOSzLSc5lB55pZ/PhK7ycFeg7u5LcF88p/WbpIi1XUDtE0MpHE3uRR3u3KB7TjSQ==} + hono@4.10.6: resolution: {integrity: sha512-BIdolzGpDO9MQ4nu3AUuDwHZZ+KViNm+EZ75Ae55eMXMqLVhDFqEMXxtUe9Qh8hjL+pIna/frs2j6Y2yD5Ua/g==} engines: {node: '>=16.9.0'} @@ -8131,6 +8394,10 @@ packages: resolution: {integrity: sha512-tnn0J5wzgTgTx2OJy3Cwr1y79bJz4eNgFQd+2HENOs5Vz6QOMnt05z7J+BedIo9wIbpEa0iN9U1nerxyvMRE9g==} engines: {node: '>=20.19.4'} + metro-babel-transformer@0.84.5: + resolution: {integrity: sha512-2WbHILKMiJUzfdjmGOQOqU1bWi9//gqiclc/tkk/AIsrrVw3efhZ1uhkOwMTxUEPOzqoo091H0olLmVZH5FHGQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-babel-transformer@0.84.6: resolution: {integrity: sha512-B1ozl6KxFHbQjXZ2U8WiTPWXylqiD3V4EXyV5r0fqETOrqIrJlBvbVTD82UxIN+B3Vpe4l8G1tb15ocd2RyOQA==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -8139,6 +8406,10 @@ packages: resolution: {integrity: sha512-I38PtcjT4crS5HY9UQ8i6z8S7tJ2WewtPGr/OwS6FcLKfy5T/1hlTaFw+wozUZkEtNpR6Gc0oJuvuKCbSoSN5A==} engines: {node: '>=20.19.4'} + metro-cache-key@0.84.5: + resolution: {integrity: sha512-3dPB2TnvGjjf0/9O7AXVQURKXuQNauTZE7WpTGTlR017Gh/B5y0m/2wcqxfveUguHSpu89KhVxCAlr2k/H7uhQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-cache-key@0.84.6: resolution: {integrity: sha512-6tyXt1BZ/3U183XV48oif7Nm65TE+pTo0Vs31u4FxMgqiYQR/SiSr3RoXn3pmy4pd280ZmLmct0x3jZpu0pV4A==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -8147,6 +8418,10 @@ packages: resolution: {integrity: sha512-aogMG5WbKzW5000otNjYrS9hIoORzkCI1faPJK+vxQLaf2BorJKBBFe/jl2Tfsi1mZUglS2EBuBt8B3JB7MYDQ==} engines: {node: '>=20.19.4'} + metro-cache@0.84.5: + resolution: {integrity: sha512-WHS0n2OxQqtwEjSeQFPePNrMvEFhmQcUQM9cRJMHByWoi/GMWFBEWOf7hVkAM/0KRutAXNbDlSu/cZB6CyxgQQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-cache@0.84.6: resolution: {integrity: sha512-KBJVpb02oKNO+jlWQ1Aax7cd11YP0MClDaMgx6C4MhyZ9g95J/dC1Bo6sCzXaU9L9h/w5cQfT5zDkGW5sOj99A==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -8155,6 +8430,10 @@ packages: resolution: {integrity: sha512-crNbNy+/B4tCne2+HjUshwvC57gNBQj+V9fFSy3lHH2RlKcLHib3Mil/SfTX8Pfh2fCY5pPuSu2OKa7YiadB+Q==} engines: {node: '>=20.19.4'} + metro-config@0.84.5: + resolution: {integrity: sha512-zie+uN6oohscowi2S7ByU+wUw6CrT4ZxW9uAbONOObSxx86RGmnIAmjXHLkfmcdYoY7jzOPEbqcI6oeVmqyBQA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-config@0.84.6: resolution: {integrity: sha512-cD2mLEofcuV26kxSV4hfV1Pbuh6igdUhYQriO8ZVeJ7HyZfq4/wqHV0gSR7fEO8NUp9odr60/Kzm5YakmghWwQ==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -8163,6 +8442,10 @@ packages: resolution: {integrity: sha512-NTyOUOQaQKvQgJG9VI2ymN6KTM7gHEqkVFhkPc9bK4BsHSTz/EaXuFBXtC+wtwINLeH9tbusL/jfsSmdEmuU7A==} engines: {node: '>=20.19.4'} + metro-core@0.84.5: + resolution: {integrity: sha512-xwm605hCi5Y6eJTTb8ZWo6pkUcoBEIyiQOfkZh5GwtDwUrP9SNhTQZhzJHrBCwwxlf3Ptl/pxWJgQ1rsNYMnrA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-core@0.84.6: resolution: {integrity: sha512-ZqYskM8+f3PFMosBDJd8DLqRvT1ltRGhWL/ZfTpBvf4SoadO30VVrdhAoHFVGz3G1CJpWa3fgZH7nnnX/ybllA==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -8171,6 +8454,10 @@ packages: resolution: {integrity: sha512-+W++EUuzEXIfWQEFTWQMVThzhWbnJL4gRNJ9WSHzIAM5pT7gsprUxo9+2hrfirURm/TLvrKwhq37oCECJcDSyQ==} engines: {node: '>=20.19.4'} + metro-file-map@0.84.5: + resolution: {integrity: sha512-mlm/JL8toSbSc2akpKIGmzvrVRSCgZ5vkbycI34oMLoOnLGuLyC8WTyVJ6P0hZG/usDaGwZSl/s9BCRriqjGJA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-file-map@0.84.6: resolution: {integrity: sha512-ov9VywWBsHPt54Zc7/DxWvqjTwxuZvhfeyHg8ZAMGKtf2PcH1wS3EnoBH7In53Rspk/eXVWAO5+avxunPn081w==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -8179,6 +8466,10 @@ packages: resolution: {integrity: sha512-7tU0J5/c7LZaZJwTlOb1xq0NepTFvGzRxigDhZOq4jSE6g0BRHmBqe7XvLH3WcRiJNGy+eshcZr34RpMjb6mmg==} engines: {node: '>=20.19.4'} + metro-minify-terser@0.84.5: + resolution: {integrity: sha512-BJoFwCEDsYnagPqarayInv2+diCDNDdLlaof/p6s9w4gh+gc9HXYM+pDvsKGKKUumpZswNF3Z/ftTMqKl/5IBg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-minify-terser@0.84.6: resolution: {integrity: sha512-SqhB/Kxrw0XfyW0k8mEscqX/CQopm6Fp3EpdSIPzvKfGgQp9bTMahg9PAdVsXUmIWwErbmFw1VqB+VFBOKksUw==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -8187,6 +8478,10 @@ packages: resolution: {integrity: sha512-piU0NVTI9i37YztDVF5rtn9uxP3NebVT0xZM9NKJ9z0jbigrctUQksi3NoYIeJMvL6Wn2dgAehpcmmnfn+gUwA==} engines: {node: '>=20.19.4'} + metro-resolver@0.84.5: + resolution: {integrity: sha512-VSSnepg1k6LyCwtb6eirWdAWlpKwBG8Rdtsr1mU38rMelFyWgh3/QuMSiZIZAIjwg/fsa8GhW5/FO54CAUPCEA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-resolver@0.84.6: resolution: {integrity: sha512-PplpGc/OCLxgKeLNsLQO/VHsYDIvkcGGr3zNT7UanHCGRqhxKv245o+naV8cRI4/pcLqyc+9j+J0AJbT3Kp5Sg==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -8195,6 +8490,10 @@ packages: resolution: {integrity: sha512-f7FfeM0pamq8vrvs8aO9KvIUabbUKe0WkHFpLt6Q9yIIIsORqNFwlgJeHGraOFPU7Cxqj5yLXkJu5bT1uwDXvw==} engines: {node: '>=20.19.4'} + metro-runtime@0.84.5: + resolution: {integrity: sha512-U1m2+d1Pr+JO2/iVXBB2OfXXityz7tqwIorxfrT15IEgaHvpJBq/OHiqnOWPKJbUl3JcxjcdviZZOKk85oK4Qg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-runtime@0.84.6: resolution: {integrity: sha512-47EYO/DOai0PA3GoAyDpyXOgHNMwbl/Z9dQlQineDLHkpD42xOwMrIx1crT0+n6F+aGoEyxZmeFwfMzaIqVqKQ==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -8203,6 +8502,10 @@ packages: resolution: {integrity: sha512-60Uor7bM+KsVewLkLCcZfkPFCbqjPDdoSmeBuT3+ye+ac80BuLWbbR8DpyxWpUqQeZPdaAT5ZqFgDIs8BNEcVA==} engines: {node: '>=20.19.4'} + metro-source-map@0.84.5: + resolution: {integrity: sha512-2BtV5L9uPc49F13Gn5wiP6bX/EncqzqTIk2VL/0F/96Vo0YEOjluT/qktQjFODfqGFsucwnh5mPEAl/2jVEfeg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-source-map@0.84.6: resolution: {integrity: sha512-4p/EplRbgNxhdqA6kAj1AmIq9qqYXYSAjgxuZ73rYA+p037xDDrxK8CyDKNfbn1k6aMuVt8LV0fpt7NHUTKiZw==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -8212,6 +8515,11 @@ packages: engines: {node: '>=20.19.4'} hasBin: true + metro-symbolicate@0.84.5: + resolution: {integrity: sha512-rQ40zYDAkaWBN9yvjUuAD0ZpzBMZSoKyGYXnb5JrfbKjun7fTvfoLHL3KXFYenBTYZkQtlp4cKSCv/1utxFyOw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + hasBin: true + metro-symbolicate@0.84.6: resolution: {integrity: sha512-wJFyyF5ysbVyoYVWGL6GwVMiNnGllwDU2gF096376fl0fc8IFUlADSyNlswj31K9og2sOilXe/FdyWFPczPUYg==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -8221,6 +8529,10 @@ packages: resolution: {integrity: sha512-9JRPkvi+m0QH2Y/w5RjCF9mHqOUNFpMFLDDLhKUjhcKESh8Wm3HKDdHXHVldTN1lTToCIabv81nF0zyJzKEJ5g==} engines: {node: '>=20.19.4'} + metro-transform-plugins@0.84.5: + resolution: {integrity: sha512-+InaSVGaOyt0DyRo4Y/zIdPI6CZwnbNho5LAL23tgmuGwv7fyfkF7kKfPjZcfxXBcoYdTLLFnCfCH/dHSiCqNg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-transform-plugins@0.84.6: resolution: {integrity: sha512-pROPMFaj25Y9+3LlLqpBRz/7B/2YPnfEQz/z3juYxkh3FGQ+3c/Qh4E+khPSTdwTw0M6LitLYHMqaHMbQmuBJw==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -8229,6 +8541,10 @@ packages: resolution: {integrity: sha512-Pa2hOfhUmWpI/dmkhsLq8uGyFHK2opoEK7j/YCiRtqNSe0YzzxYguXTNgg8AMiuZfc9LPcB1AhGENS10O5wcIw==} engines: {node: '>=20.19.4'} + metro-transform-worker@0.84.5: + resolution: {integrity: sha512-ui1Z8x4s5RL36gMmKLaMMO7O9NNDHNdthEZSCDQHAau3JcAsTaFOK6I+2q4I/kW5u8hSEjJk9L45TXSVJw6g1A==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + metro-transform-worker@0.84.6: resolution: {integrity: sha512-xLTVrOENaHR9DiuhnHS+zcle0rKsCy2CpXSIx7xD+zhJs+qVRNUyV7Z1zIt/0P/d0cSGVx846R+dIjHEudhcww==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -8238,6 +8554,11 @@ packages: engines: {node: '>=20.19.4'} hasBin: true + metro@0.84.5: + resolution: {integrity: sha512-r1liLkyFZMVSEMNjU1CJU5pRzs3NdkxHqXS60O25c0rCIqAR+cGk7rPydw/g0WAIKVXojIBIF45yYBPagJGcgw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + hasBin: true + metro@0.84.6: resolution: {integrity: sha512-ty/tx/imE5Ph2gvPU788Ckim6RAC2tyZxLz2hWRgC9U7/fGKV7qY/slADy0qHPEw9cpUgc4BfmV3oUsYwm7oeg==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -8534,6 +8855,10 @@ packages: resolution: {integrity: sha512-pk7el+eTOzfSKMAY4QBiiwKzegXn633JQj13y+pW5E5IdS+yV2CfDJ9Hf20K/LKy8nCrP9dAmd7XOk52OJxifA==} engines: {node: '>=20.19.4'} + ob1@0.84.5: + resolution: {integrity: sha512-aH9RkoZc7w/90HBamFxTw8ZLFr05wXS+iOnvmrgo53Ep8Pyrm5FieQSaPIVROkfFVQISeD/zo92fes26TOwe+A==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + ob1@0.84.6: resolution: {integrity: sha512-+s+6zjd0X68hfAcn92NsEPXnSYOF3O7exw/Fj4UfRTB+UL+Tdjdkzu5/4WquXlzqgvNm8FWuuW6/0Yd8S2rYxw==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} @@ -9157,6 +9482,11 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sandbox-cli-detector@0.2.0: + resolution: {integrity: sha512-4lyHX0ZU0AZKwjgZ1InxZAa3PNpyEb8rOQ+Zss1ReYmhNzW0Q+h1zE5nvniXN0HaAWZaZE1zgVNEirb0R7LmNg==} + engines: {node: '>=18.18'} + hasBin: true + sax@1.6.1: resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==} engines: {node: '>=11.0.0'} @@ -9327,9 +9657,6 @@ packages: solid-js@1.9.14: resolution: {integrity: sha512-sAEXC0Kk0S1EDg+8ysEWJDbYhA3RRoEjwuySUGlKIemeo0I5YZfOyumNjNs9Sv3y2nmhD+0rW66ag2HsMuQiGQ==} - sorted-btree@1.8.1: - resolution: {integrity: sha512-395+XIP+wqNn3USkFSrNz7G3Ss/MXlZEqesxvzCRFwL14h6e8LukDHdLBePn5pwbm5OQ9vGu8mDyz2lLDIqamQ==} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -9851,20 +10178,63 @@ packages: playwright: optional: true - vitefu@1.1.2: - resolution: {integrity: sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==} - peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0 - peerDependenciesMeta: - vite: - optional: true - - vitest@5.0.4: - resolution: {integrity: sha512-z0WxZRV2Sg48wF0FSgMq3TFG8JS38/Qr96vZVZdX6+Em9raQTvnXQvdTFJe2m3rfyJnEHeHQ/snceIdi8D1pVg==} - engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} + vite@8.3.4: + resolution: {integrity: sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - '@edge-runtime/vm': '*' + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.7.1 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.1.2: + resolution: {integrity: sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0 + peerDependenciesMeta: + vite: + optional: true + + vitest@5.0.4: + resolution: {integrity: sha512-z0WxZRV2Sg48wF0FSgMq3TFG8JS38/Qr96vZVZdX6+Em9raQTvnXQvdTFJe2m3rfyJnEHeHQ/snceIdi8D1pVg==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^22.0.0 || >=24.0.0 '@vitest/browser-playwright': 5.0.4 @@ -11065,7 +11435,7 @@ snapshots: cjs-module-lexer: 1.2.3 esbuild: 0.28.2 miniflare: 5.20261006.1-alpha(@types/node@24.10.1) - vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4)(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) wrangler: 4.149.0(@cloudflare/workers-types@5.20260827.1)(@types/node@24.10.1) zod: 4.6.5 transitivePeerDependencies: @@ -11522,7 +11892,84 @@ snapshots: terminal-link: 2.1.1 toqr: 0.1.1 wrap-ansi: 7.0.0 - ws: 8.21.3 + ws: 8.22.0 + zod: 3.25.76 + optionalDependencies: + react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) + transitivePeerDependencies: + - '@expo/dom-webview' + - '@expo/metro-runtime' + - bufferutil + - expo-constants + - expo-font + - react + - react-dom + - react-server-dom-webpack + - supports-color + - typescript + - utf-8-validate + + '@expo/cli@57.0.30(@expo/dom-webview@55.0.6)(expo-constants@57.0.22(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)))(expo-font@57.0.4(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0))(expo@57.0.29)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2)': + dependencies: + '@expo/code-signing-certificates': 0.0.6 + '@expo/config': 57.0.10(typescript@7.0.2) + '@expo/config-plugins': 57.0.10(typescript@7.0.2) + '@expo/devcert': 1.2.1 + '@expo/env': 2.4.3 + '@expo/image-utils': 0.11.6(typescript@7.0.2) + '@expo/inline-modules': 0.1.8(typescript@7.0.2) + '@expo/json-file': 11.0.1 + '@expo/log-box': 57.0.4(@expo/dom-webview@55.0.6)(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0) + '@expo/metro': 56.0.2 + '@expo/metro-config': 57.0.13(expo@57.0.29)(typescript@7.0.2) + '@expo/metro-file-map': 57.0.4 + '@expo/osascript': 2.7.1 + '@expo/package-manager': 1.13.1 + '@expo/plist': 0.8.1 + '@expo/prebuild-config': 57.0.19(typescript@7.0.2) + '@expo/require-utils': 57.0.6(typescript@7.0.2) + '@expo/router-server': 57.0.12(expo-constants@57.0.22(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)))(expo-font@57.0.4(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0))(expo-server@57.0.3)(expo@57.0.29)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@expo/schema-utils': 57.0.2 + '@expo/spawn-async': 1.8.0 + '@expo/ws-tunnel': 2.0.1(ws@8.22.0) + '@expo/xcpretty': 4.4.5 + '@react-native/dev-middleware': 0.86.3 + accepts: 1.3.8 + agent-cli-detector: 0.1.7 + arg: 5.0.2 + bplist-creator: 0.1.0 + bplist-parser: 0.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + compression: 1.8.2 + connect: 3.7.0 + debug: 4.4.3(supports-color@8.1.1) + dnssd-advertise: 1.1.6 + expo: 57.0.29(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + expo-server: 57.0.3 + fetch-nodeshim: 0.4.10 + getenv: 2.0.0 + glob: 13.0.6 + lan-network: 0.2.1 + multitars: 1.0.2 + node-forge: 1.4.0 + npm-package-arg: 11.0.3 + ora: 3.4.0 + picomatch: 4.0.7 + pretty-format: 29.7.0 + progress: 2.0.3 + prompts: 2.4.2 + resolve-from: 5.0.0 + sandbox-cli-detector: 0.2.0 + semver: 7.8.5 + send: 0.19.2 + slugify: 1.6.9 + stacktrace-parser: 0.1.11 + structured-headers: 0.4.1 + terminal-link: 2.1.1 + toqr: 0.1.1 + wrap-ansi: 7.0.0 + ws: 8.22.0 zod: 3.25.76 optionalDependencies: react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) @@ -11561,8 +12008,29 @@ snapshots: transitivePeerDependencies: - supports-color + '@expo/config-plugins@57.0.10(typescript@7.0.2)': + dependencies: + '@expo/config-types': 57.0.2 + '@expo/json-file': 11.0.1 + '@expo/plist': 0.8.1 + '@expo/require-utils': 57.0.6(typescript@7.0.2) + '@expo/sdk-runtime-versions': 1.0.0 + chalk: 4.1.2 + debug: 4.4.3(supports-color@8.1.1) + getenv: 2.0.0 + glob: 13.0.6 + semver: 7.8.5 + slugify: 1.6.9 + xcode: 3.0.1 + xml2js: 0.6.0 + transitivePeerDependencies: + - supports-color + - typescript + '@expo/config-types@55.0.6': {} + '@expo/config-types@57.0.2': {} + '@expo/config@55.0.21(typescript@7.0.2)': dependencies: '@expo/config-plugins': 55.0.11 @@ -11579,6 +12047,22 @@ snapshots: - supports-color - typescript + '@expo/config@57.0.10(typescript@7.0.2)': + dependencies: + '@expo/config-plugins': 57.0.10(typescript@7.0.2) + '@expo/config-types': 57.0.2 + '@expo/json-file': 11.0.1 + '@expo/require-utils': 57.0.6(typescript@7.0.2) + deepmerge: 4.3.1 + getenv: 2.0.0 + glob: 13.0.6 + resolve-workspace-root: 2.0.1 + semver: 7.8.5 + slugify: 1.6.9 + transitivePeerDependencies: + - supports-color + - typescript + '@expo/devcert@1.2.1': dependencies: '@expo/sudo-prompt': 9.3.2 @@ -11593,12 +12077,25 @@ snapshots: react: 19.3.0 react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) + '@expo/devtools@57.0.1(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)': + dependencies: + chalk: 4.1.2 + optionalDependencies: + react: 19.3.0 + react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) + '@expo/dom-webview@55.0.6(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)': dependencies: expo: 55.0.23(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) react: 19.3.0 react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) + '@expo/dom-webview@55.0.6(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)': + dependencies: + expo: 57.0.29(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + react: 19.3.0 + react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) + '@expo/env@2.1.3': dependencies: chalk: 4.1.2 @@ -11615,6 +12112,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@expo/expo-modules-macros-plugin@0.6.1': {} + '@expo/fingerprint@0.16.7': dependencies: '@expo/env': 2.4.3 @@ -11631,6 +12130,35 @@ snapshots: transitivePeerDependencies: - supports-color + '@expo/fingerprint@0.20.14': + dependencies: + '@expo/env': 2.4.3 + '@expo/spawn-async': 1.8.0 + arg: 5.0.2 + chalk: 4.1.2 + debug: 4.4.3(supports-color@8.1.1) + getenv: 2.0.0 + glob: 13.0.6 + ignore: 5.3.2 + minimatch: 10.2.6 + resolve-from: 5.0.0 + semver: 7.8.5 + transitivePeerDependencies: + - supports-color + + '@expo/image-utils@0.11.6(typescript@7.0.2)': + dependencies: + '@expo/require-utils': 57.0.6(typescript@7.0.2) + '@expo/spawn-async': 1.8.0 + chalk: 4.1.2 + getenv: 2.0.0 + jimp-compact: 0.16.1 + parse-png: 2.1.0 + semver: 7.8.5 + transitivePeerDependencies: + - supports-color + - typescript + '@expo/image-utils@0.8.17(typescript@7.0.2)': dependencies: '@expo/require-utils': 55.0.8(typescript@7.0.2) @@ -11644,6 +12172,13 @@ snapshots: - supports-color - typescript + '@expo/inline-modules@0.1.8(typescript@7.0.2)': + dependencies: + '@expo/config-plugins': 57.0.10(typescript@7.0.2) + transitivePeerDependencies: + - supports-color + - typescript + '@expo/json-file@10.0.16': dependencies: '@babel/code-frame': 7.10.4 @@ -11667,6 +12202,14 @@ snapshots: - supports-color - typescript + '@expo/local-build-cache-provider@57.0.9(typescript@7.0.2)': + dependencies: + '@expo/config': 57.0.10(typescript@7.0.2) + chalk: 4.1.2 + transitivePeerDependencies: + - supports-color + - typescript + '@expo/log-box@55.0.12(@expo/dom-webview@55.0.6)(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)': dependencies: '@expo/dom-webview': 55.0.6(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0) @@ -11676,6 +12219,15 @@ snapshots: react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) stacktrace-parser: 0.1.11 + '@expo/log-box@57.0.4(@expo/dom-webview@55.0.6)(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)': + dependencies: + '@expo/dom-webview': 55.0.6(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0) + anser: 1.4.10 + expo: 57.0.29(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + react: 19.3.0 + react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) + stacktrace-parser: 0.1.11 + '@expo/metro-config@55.0.20(expo@55.0.23)(typescript@7.0.2)': dependencies: '@babel/code-frame': 7.29.7 @@ -11705,6 +12257,49 @@ snapshots: - typescript - utf-8-validate + '@expo/metro-config@57.0.13(expo@57.0.29)(typescript@7.0.2)': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/generator': 7.29.8 + '@expo/config': 57.0.10(typescript@7.0.2) + '@expo/env': 2.4.3 + '@expo/json-file': 11.0.1 + '@expo/metro': 56.0.2 + '@expo/require-utils': 57.0.6(typescript@7.0.2) + '@expo/spawn-async': 1.8.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.6.0 + browserslist: 4.29.1 + chalk: 4.1.2 + debug: 4.4.3(supports-color@8.1.1) + getenv: 2.0.0 + glob: 13.0.6 + hermes-parser: 0.36.1 + jsc-safe-url: 0.2.4 + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.29 + resolve-from: 5.0.0 + optionalDependencies: + expo: 57.0.29(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + transitivePeerDependencies: + - bufferutil + - supports-color + - typescript + - utf-8-validate + + '@expo/metro-file-map@57.0.4': + dependencies: + debug: 4.4.3(supports-color@8.1.1) + fb-watchman: 2.0.2 + invariant: 2.2.4 + jest-worker: 29.7.0 + walker: 1.0.8 + transitivePeerDependencies: + - supports-color + '@expo/metro@55.1.2': dependencies: metro: 0.83.8 @@ -11726,6 +12321,27 @@ snapshots: - supports-color - utf-8-validate + '@expo/metro@56.0.2': + dependencies: + metro: 0.84.5 + metro-babel-transformer: 0.84.5 + metro-cache: 0.84.5 + metro-cache-key: 0.84.5 + metro-config: 0.84.5 + metro-core: 0.84.5 + metro-file-map: 0.84.5 + metro-minify-terser: 0.84.5 + metro-resolver: 0.84.5 + metro-runtime: 0.84.5 + metro-source-map: 0.84.5 + metro-symbolicate: 0.84.5 + metro-transform-plugins: 0.84.5 + metro-transform-worker: 0.84.5 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + '@expo/osascript@2.7.1': dependencies: '@expo/spawn-async': 1.8.0 @@ -11745,6 +12361,12 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 + '@expo/plist@0.8.1': + dependencies: + '@xmldom/xmldom': 0.8.15 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + '@expo/prebuild-config@55.0.22(expo@55.0.23)(typescript@7.0.2)': dependencies: '@expo/config': 55.0.21(typescript@7.0.2) @@ -11762,6 +12384,22 @@ snapshots: - supports-color - typescript + '@expo/prebuild-config@57.0.19(typescript@7.0.2)': + dependencies: + '@expo/config': 57.0.10(typescript@7.0.2) + '@expo/config-plugins': 57.0.10(typescript@7.0.2) + '@expo/config-types': 57.0.2 + '@expo/image-utils': 0.11.6(typescript@7.0.2) + '@expo/json-file': 11.0.1 + '@react-native/normalize-colors': 0.86.3 + debug: 4.4.3(supports-color@8.1.1) + expo-modules-autolinking: 57.0.16(typescript@7.0.2) + resolve-from: 5.0.0 + semver: 7.8.5 + transitivePeerDependencies: + - supports-color + - typescript + '@expo/require-utils@55.0.8(typescript@7.0.2)': dependencies: '@babel/code-frame': 7.29.7 @@ -11772,21 +12410,46 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/router-server@55.0.19(expo-constants@55.0.17(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)))(expo-font@55.0.8(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0))(expo-server@55.0.12)(expo@55.0.23)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@expo/require-utils@57.0.6(typescript@7.0.2)': dependencies: - debug: 4.4.3(supports-color@8.1.1) - expo: 55.0.23(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) - expo-constants: 55.0.17(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)) - expo-font: 55.0.8(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0) - expo-server: 55.0.12 - react: 19.3.0 - optionalDependencies: + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + optionalDependencies: + typescript: 7.0.2 + transitivePeerDependencies: + - supports-color + + '@expo/router-server@55.0.19(expo-constants@55.0.17(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)))(expo-font@55.0.8(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0))(expo-server@55.0.12)(expo@55.0.23)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + debug: 4.4.3(supports-color@8.1.1) + expo: 55.0.23(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + expo-constants: 55.0.17(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)) + expo-font: 55.0.8(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0) + expo-server: 55.0.12 + react: 19.3.0 + optionalDependencies: + react-dom: 19.3.0(react@19.3.0) + transitivePeerDependencies: + - supports-color + + '@expo/router-server@57.0.12(expo-constants@57.0.22(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)))(expo-font@57.0.4(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0))(expo-server@57.0.3)(expo@57.0.29)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + dependencies: + debug: 4.4.3(supports-color@8.1.1) + expo: 57.0.29(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + expo-constants: 57.0.22(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)) + expo-font: 57.0.4(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0) + expo-server: 57.0.3 + react: 19.3.0 + optionalDependencies: react-dom: 19.3.0(react@19.3.0) transitivePeerDependencies: - supports-color '@expo/schema-utils@55.0.5': {} + '@expo/schema-utils@57.0.2': {} + '@expo/sdk-runtime-versions@1.0.0': {} '@expo/spawn-async@1.8.0': @@ -11803,6 +12466,10 @@ snapshots: '@expo/ws-tunnel@1.0.6': {} + '@expo/ws-tunnel@2.0.1(ws@8.22.0)': + dependencies: + ws: 8.22.0 + '@expo/xcpretty@4.4.5': dependencies: '@babel/code-frame': 7.29.7 @@ -12943,6 +13610,14 @@ snapshots: - '@babel/core' - supports-color + '@react-native/babel-plugin-codegen@0.86.3(@babel/core@7.29.7)': + dependencies: + '@babel/traverse': 7.29.8 + '@react-native/codegen': 0.86.3(@babel/core@7.29.7) + transitivePeerDependencies: + - '@babel/core' + - supports-color + '@react-native/babel-preset@0.83.10(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -13013,6 +13688,16 @@ snapshots: tinyglobby: 0.2.17 yargs: 17.7.3 + '@react-native/codegen@0.86.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.9 + hermes-parser: 0.36.0 + invariant: 2.2.4 + nullthrows: 1.1.1 + tinyglobby: 0.2.17 + yargs: 17.7.3 + '@react-native/community-cli-plugin@0.85.3': dependencies: '@react-native/dev-middleware': 0.85.3 @@ -13031,6 +13716,8 @@ snapshots: '@react-native/debugger-frontend@0.85.3': {} + '@react-native/debugger-frontend@0.86.3': {} + '@react-native/debugger-shell@0.83.6': dependencies: cross-spawn: 7.0.6 @@ -13044,6 +13731,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@react-native/debugger-shell@0.86.3': + dependencies: + cross-spawn: 7.0.6 + debug: 4.4.3(supports-color@8.1.1) + fb-dotslash: 0.5.8 + transitivePeerDependencies: + - supports-color + '@react-native/dev-middleware@0.83.6': dependencies: '@isaacs/ttlcache': 1.4.1 @@ -13082,6 +13777,25 @@ snapshots: - supports-color - utf-8-validate + '@react-native/dev-middleware@0.86.3': + dependencies: + '@isaacs/ttlcache': 1.4.1 + '@react-native/debugger-frontend': 0.86.3 + '@react-native/debugger-shell': 0.86.3 + chrome-launcher: 0.15.2 + chromium-edge-launcher: 0.3.0 + connect: 3.7.0 + debug: 4.4.3(supports-color@8.1.1) + invariant: 2.2.4 + nullthrows: 1.1.1 + open: 7.4.2 + serve-static: 1.16.3 + ws: 7.5.13 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + '@react-native/gradle-plugin@0.85.3': {} '@react-native/js-polyfills@0.85.3': {} @@ -13090,6 +13804,8 @@ snapshots: '@react-native/normalize-colors@0.85.3': {} + '@react-native/normalize-colors@0.86.3': {} + '@react-native/virtualized-lists@0.85.3(@types/react@19.3.0)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)': dependencies: invariant: 2.2.4 @@ -13394,22 +14110,21 @@ snapshots: tailwindcss: 4.1.17 vite: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' - '@tanstack/db-ivm@0.1.26(typescript@7.0.2)': + '@tanstack/db-ivm@0.2.0(typescript@7.0.2)': dependencies: fractional-indexing: 3.4.0 - sorted-btree: 1.8.1 typescript: 7.0.2 - '@tanstack/db-sqlite-persistence-core@0.4.5(typescript@7.0.2)': + '@tanstack/db-sqlite-persistence-core@0.4.9(typescript@7.0.2)': dependencies: '@standard-schema/spec': 1.1.0 - '@tanstack/db': 0.12.1(typescript@7.0.2) + '@tanstack/db': 0.14.0(typescript@7.0.2) typescript: 7.0.2 - '@tanstack/db@0.12.1(typescript@7.0.2)': + '@tanstack/db@0.14.0(typescript@7.0.2)': dependencies: '@standard-schema/spec': 1.1.0 - '@tanstack/db-ivm': 0.1.26(typescript@7.0.2) + '@tanstack/db-ivm': 0.2.0(typescript@7.0.2) '@tanstack/pacer-lite': 0.2.2 typescript: 7.0.2 @@ -13488,17 +14203,17 @@ snapshots: - csstype - utf-8-validate - '@tanstack/expo-db-sqlite-persistence@0.2.30(expo-sqlite@55.0.15(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0))(typescript@7.0.2)': + '@tanstack/expo-db-sqlite-persistence@0.2.34(expo-sqlite@57.0.4(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0))(typescript@7.0.2)': dependencies: - '@tanstack/db-sqlite-persistence-core': 0.4.5(typescript@7.0.2) - expo-sqlite: 55.0.15(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0) + '@tanstack/db-sqlite-persistence-core': 0.4.9(typescript@7.0.2) + expo-sqlite: 57.0.4(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0) typescript: 7.0.2 '@tanstack/history@1.161.6': {} - '@tanstack/node-db-sqlite-persistence@0.2.30(typescript@7.0.2)': + '@tanstack/node-db-sqlite-persistence@0.2.34(typescript@7.0.2)': dependencies: - '@tanstack/db-sqlite-persistence-core': 0.4.5(typescript@7.0.2) + '@tanstack/db-sqlite-persistence-core': 0.4.9(typescript@7.0.2) better-sqlite3: 12.9.0 typescript: 7.0.2 @@ -13508,16 +14223,16 @@ snapshots: '@tanstack/query-core@5.90.10': {} - '@tanstack/query-db-collection@1.4.0(@tanstack/query-core@5.102.8)(typescript@7.0.2)': + '@tanstack/query-db-collection@1.4.4(@tanstack/query-core@5.102.8)(typescript@7.0.2)': dependencies: '@standard-schema/spec': 1.1.0 - '@tanstack/db': 0.12.1(typescript@7.0.2) + '@tanstack/db': 0.14.0(typescript@7.0.2) '@tanstack/query-core': 5.102.8 typescript: 7.0.2 - '@tanstack/react-db@0.5.5(react@19.3.0)(typescript@7.0.2)': + '@tanstack/react-db@0.5.9(react@19.3.0)(typescript@7.0.2)': dependencies: - '@tanstack/db': 0.12.1(typescript@7.0.2) + '@tanstack/db': 0.14.0(typescript@7.0.2) react: 19.3.0 transitivePeerDependencies: - typescript @@ -13589,6 +14304,27 @@ snapshots: - vite-plugin-solid - webpack + '@tanstack/react-start@1.167.12(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))': + dependencies: + '@tanstack/react-router': 1.168.7(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start-client': 1.166.22(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/react-start-server': 1.166.22(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + '@tanstack/router-utils': 1.161.6 + '@tanstack/start-client-core': 1.167.6 + '@tanstack/start-plugin-core': 1.167.13(@tanstack/react-router@1.168.7(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) + '@tanstack/start-server-core': 1.167.6 + pathe: 2.0.3 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + vite: 8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1) + transitivePeerDependencies: + - '@rsbuild/core' + - crossws + - supports-color + - vite-plugin-solid + - webpack + optional: true + '@tanstack/react-store@0.11.2(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@tanstack/store': 0.11.2 @@ -13652,6 +14388,28 @@ snapshots: transitivePeerDependencies: - supports-color + '@tanstack/router-plugin@1.167.8(@tanstack/react-router@1.168.7(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@tanstack/router-core': 1.168.6 + '@tanstack/router-generator': 1.166.21 + '@tanstack/router-utils': 1.161.6 + '@tanstack/virtual-file-routes': 1.161.7 + chokidar: 3.6.0 + unplugin: 2.3.11 + zod: 3.25.76 + optionalDependencies: + '@tanstack/react-router': 1.168.7(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + vite: 8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1) + transitivePeerDependencies: + - supports-color + optional: true + '@tanstack/router-utils@1.161.6': dependencies: '@babel/core': 7.29.7 @@ -13707,6 +14465,39 @@ snapshots: - vite-plugin-solid - webpack + '@tanstack/start-plugin-core@1.167.13(@tanstack/react-router@1.168.7(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/core': 7.29.7 + '@babel/types': 7.29.8 + '@rolldown/pluginutils': 1.0.0-beta.40 + '@tanstack/router-core': 1.168.6 + '@tanstack/router-generator': 1.166.21 + '@tanstack/router-plugin': 1.167.8(@tanstack/react-router@1.168.7(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) + '@tanstack/router-utils': 1.161.6 + '@tanstack/start-client-core': 1.167.6 + '@tanstack/start-server-core': 1.167.6 + cheerio: 1.2.0 + exsolve: 1.0.8 + pathe: 2.0.3 + picomatch: 4.0.7 + source-map: 0.7.6 + srvx: 0.11.22 + tinyglobby: 0.2.17 + ufo: 1.6.3 + vite: 8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1) + vitefu: 1.1.2(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) + xmlbuilder2: 4.0.3 + zod: 3.25.76 + transitivePeerDependencies: + - '@rsbuild/core' + - '@tanstack/react-router' + - crossws + - supports-color + - vite-plugin-solid + - webpack + optional: true + '@tanstack/start-server-core@1.167.6': dependencies: '@tanstack/history': 1.161.6 @@ -13947,13 +14738,13 @@ snapshots: '@rolldown/plugin-babel': 0.2.4(@babel/core@7.29.7)(@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7))(@babel/runtime@7.29.7)(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(rolldown@1.2.13) babel-plugin-react-compiler: 1.0.0 - '@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4)': + '@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4)': dependencies: - '@vitest/browser': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(vitest@5.0.4) - '@vitest/mocker': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) + '@vitest/browser': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/mocker': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) playwright: 1.61.1 tinyrainbow: 3.2.0 - vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0) transitivePeerDependencies: - bufferutil - msw @@ -13961,13 +14752,13 @@ snapshots: - vite optional: true - '@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4)': + '@vitest/browser-playwright@5.0.4(playwright@1.61.1)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4)': dependencies: - '@vitest/browser': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(vitest@5.0.4) - '@vitest/mocker': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) + '@vitest/browser': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/mocker': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) playwright: 1.61.1 tinyrainbow: 3.2.0 - vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4)(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) transitivePeerDependencies: - bufferutil - msw @@ -13975,13 +14766,13 @@ snapshots: - vite optional: true - '@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(playwright@1.61.1)(vitest@5.0.4)': + '@vitest/browser-playwright@5.0.4(playwright@1.61.1)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4)': dependencies: - '@vitest/browser': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(vitest@5.0.4) - '@vitest/mocker': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0)) + '@vitest/browser': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/mocker': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) playwright: 1.61.1 tinyrainbow: 3.2.0 - vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(jsdom@27.2.0) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) transitivePeerDependencies: - bufferutil - msw @@ -13989,13 +14780,27 @@ snapshots: - vite optional: true - '@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4)': + '@vitest/browser-playwright@5.0.4(playwright@1.61.1)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0))(vitest@5.0.4)': dependencies: - '@vitest/browser': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(vitest@5.0.4) - '@vitest/mocker': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) + '@vitest/browser': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0))(vitest@5.0.4) + '@vitest/mocker': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0)) playwright: 1.61.1 tinyrainbow: 3.2.0 - vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + optional: true + + '@vitest/browser-playwright@5.0.4(playwright@1.61.1)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4)': + dependencies: + '@vitest/browser': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/mocker': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) + playwright: 1.61.1 + tinyrainbow: 3.2.0 + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) transitivePeerDependencies: - bufferutil - msw @@ -14007,7 +14812,7 @@ snapshots: '@testing-library/dom': 10.4.2 '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.2) '@vitest/browser': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(vitest@5.0.4) - vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4)(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) transitivePeerDependencies: - bufferutil - msw @@ -14019,7 +14824,7 @@ snapshots: '@testing-library/dom': 10.4.2 '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.2) '@vitest/browser': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(vitest@5.0.4) - vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) transitivePeerDependencies: - bufferutil - msw @@ -14031,7 +14836,7 @@ snapshots: '@testing-library/dom': 10.4.2 '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.2) '@vitest/browser': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(vitest@5.0.4) - vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(jsdom@27.2.0) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0)) transitivePeerDependencies: - bufferutil - msw @@ -14050,6 +14855,58 @@ snapshots: - utf-8-validate - vite + '@vitest/browser-preview@5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4)': + dependencies: + '@testing-library/dom': 10.4.2 + '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.2) + '@vitest/browser': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4)(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + optional: true + + '@vitest/browser-preview@5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4)': + dependencies: + '@testing-library/dom': 10.4.2 + '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.2) + '@vitest/browser': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + optional: true + + '@vitest/browser-preview@5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0))(vitest@5.0.4)': + dependencies: + '@testing-library/dom': 10.4.2 + '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.2) + '@vitest/browser': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0))(vitest@5.0.4) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + optional: true + + '@vitest/browser-preview@5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4)': + dependencies: + '@testing-library/dom': 10.4.2 + '@testing-library/user-event': 14.6.7(@testing-library/dom@10.4.2) + '@vitest/browser': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + optional: true + '@vitest/browser@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(vitest@5.0.4)': dependencies: '@blazediff/core': 1.10.0 @@ -14060,7 +14917,7 @@ snapshots: pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.2.0 - vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4)(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) ws: 8.22.0 transitivePeerDependencies: - bufferutil @@ -14078,7 +14935,7 @@ snapshots: pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.2.0 - vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) ws: 8.22.0 transitivePeerDependencies: - bufferutil @@ -14096,7 +14953,7 @@ snapshots: pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.2.0 - vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(jsdom@27.2.0) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0)) ws: 8.22.0 transitivePeerDependencies: - bufferutil @@ -14122,32 +14979,107 @@ snapshots: - utf-8-validate - vite - '@vitest/mocker@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))': + '@vitest/browser@5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4)': dependencies: - '@jridgewell/trace-mapping': 0.3.31 - '@vitest/spy': 5.0.4 - estree-walker: 3.0.3 + '@blazediff/core': 1.10.0 + '@vitest/mocker': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) + '@vitest/ui': 5.0.4(vitest@5.0.4) + '@vitest/utils': 5.0.4 magic-string: 1.4.3 - optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.2.0 + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4)(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) + ws: 8.22.0 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + optional: true - '@vitest/mocker@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))': + '@vitest/browser@5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4)': dependencies: - '@jridgewell/trace-mapping': 0.3.31 - '@vitest/spy': 5.0.4 - estree-walker: 3.0.3 + '@blazediff/core': 1.10.0 + '@vitest/mocker': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) + '@vitest/ui': 5.0.4(vitest@5.0.4) + '@vitest/utils': 5.0.4 magic-string: 1.4.3 - optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.2.0 + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) + ws: 8.22.0 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + optional: true - '@vitest/mocker@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))': + '@vitest/browser@5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0))(vitest@5.0.4)': dependencies: - '@jridgewell/trace-mapping': 0.3.31 - '@vitest/spy': 5.0.4 - estree-walker: 3.0.3 + '@blazediff/core': 1.10.0 + '@vitest/mocker': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0)) + '@vitest/ui': 5.0.4(vitest@5.0.4) + '@vitest/utils': 5.0.4 magic-string: 1.4.3 - optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0)' + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.2.0 + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0)) + ws: 8.22.0 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + optional: true + + '@vitest/browser@5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4)': + dependencies: + '@blazediff/core': 1.10.0 + '@vitest/mocker': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) + '@vitest/ui': 5.0.4(vitest@5.0.4) + '@vitest/utils': 5.0.4 + magic-string: 1.4.3 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.2.0 + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) + ws: 8.22.0 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/mocker@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.4 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + vite: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' + + '@vitest/mocker@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.4 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + vite: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' + + '@vitest/mocker@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.4 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + vite: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0)' '@vitest/mocker@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))': dependencies: @@ -14158,6 +15090,42 @@ snapshots: optionalDependencies: vite: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' + '@vitest/mocker@5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.4 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + vite: 8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1) + + '@vitest/mocker@5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.4 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + vite: 8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1) + + '@vitest/mocker@5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.4 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + vite: 8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0) + + '@vitest/mocker@5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.4 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + vite: 8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1) + '@vitest/pretty-format@5.0.4': dependencies: tinyrainbow: 3.2.0 @@ -14405,6 +15373,8 @@ snapshots: agent-base@7.1.4: {} + agent-cli-detector@0.1.7: {} + anser@1.4.10: {} ansi-colors@4.1.3: {} @@ -14538,6 +15508,10 @@ snapshots: dependencies: hermes-parser: 0.33.3 + babel-plugin-syntax-hermes-parser@0.36.1: + dependencies: + hermes-parser: 0.36.1 + babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.29.7): dependencies: '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.7) @@ -14577,6 +15551,58 @@ snapshots: - '@babel/core' - supports-color + babel-preset-expo@57.0.15(@babel/core@7.29.7)(@babel/runtime@7.29.7)(expo@57.0.29)(react-refresh@0.14.2): + dependencies: + '@babel/generator': 7.29.8 + '@babel/helper-module-imports': 7.29.7 + '@babel/plugin-proposal-decorators': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-proposal-export-default-from': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-export-default-from': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-transform-async-generator-functions': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-async-to-generator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-block-scoping': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-class-static-block': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-export-namespace-from': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-logical-assignment-operators': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-object-rest-spread': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-optional-catch-binding': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-display-name': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-pure-annotations': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-runtime': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.29.9(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) + '@react-native/babel-plugin-codegen': 0.86.3(@babel/core@7.29.7) + babel-plugin-react-compiler: 1.0.0 + babel-plugin-react-native-web: 0.21.2 + babel-plugin-syntax-hermes-parser: 0.36.1 + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.7) + debug: 4.4.3(supports-color@8.1.1) + react-refresh: 0.14.2 + optionalDependencies: + '@babel/runtime': 7.29.7 + expo: 57.0.29(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + transitivePeerDependencies: + - '@babel/core' + - supports-color + bail@2.0.2: {} balanced-match@1.0.2: {} @@ -14646,6 +15672,36 @@ snapshots: - '@cloudflare/workers-types' - '@opentelemetry/api' + better-auth@1.7.1(@cloudflare/workers-types@5.20260827.1)(@tanstack/react-start@1.167.12(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)))(better-sqlite3@12.9.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(solid-js@1.9.14)(vitest@5.0.4): + dependencies: + '@better-auth/core': 1.7.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@5.20260827.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.5)(nanostores@1.5.2) + '@better-auth/drizzle-adapter': 1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@5.20260827.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.5)(nanostores@1.5.2))(@better-auth/utils@0.4.2) + '@better-auth/kysely-adapter': 1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@5.20260827.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.5)(nanostores@1.5.2))(@better-auth/utils@0.4.2)(kysely@0.29.5) + '@better-auth/memory-adapter': 1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@5.20260827.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.5)(nanostores@1.5.2))(@better-auth/utils@0.4.2) + '@better-auth/mongo-adapter': 1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@5.20260827.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.5)(nanostores@1.5.2))(@better-auth/utils@0.4.2) + '@better-auth/prisma-adapter': 1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@5.20260827.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.5)(nanostores@1.5.2))(@better-auth/utils@0.4.2) + '@better-auth/telemetry': 1.7.1(@better-auth/core@1.7.1(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@5.20260827.1)(better-call@1.4.0(zod@4.4.3))(jose@6.2.10)(kysely@0.29.5)(nanostores@1.5.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1) + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 + '@noble/ciphers': 2.3.0 + '@noble/hashes': 2.3.0 + better-call: 1.4.0(zod@4.4.3) + defu: 6.1.7 + jose: 6.2.10 + kysely: 0.29.5 + nanostores: 1.5.2 + zod: 4.4.3 + optionalDependencies: + '@tanstack/react-start': 1.167.12(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) + better-sqlite3: 12.9.0 + react: 19.3.0 + react-dom: 19.3.0(react@19.3.0) + solid-js: 1.9.14 + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) + transitivePeerDependencies: + - '@cloudflare/workers-types' + - '@opentelemetry/api' + better-call@1.4.0(zod@4.4.3): dependencies: '@better-auth/utils': 0.5.0 @@ -15471,6 +16527,21 @@ snapshots: - supports-color - typescript + expo-asset@57.0.20(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2): + dependencies: + '@expo/image-utils': 0.11.6(typescript@7.0.2) + expo: 57.0.29(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + expo-constants: 57.0.22(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)) + react: 19.3.0 + react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) + transitivePeerDependencies: + - supports-color + - typescript + + expo-blob@57.0.2(expo@57.0.29): + dependencies: + expo: 57.0.29(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + expo-constants@55.0.17(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)): dependencies: '@expo/env': 2.1.3 @@ -15479,16 +16550,28 @@ snapshots: transitivePeerDependencies: - supports-color - expo-file-system@55.0.19(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)): + expo-constants@57.0.22(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)): dependencies: - expo: 55.0.23(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + '@expo/env': 2.4.3 + expo: 57.0.29(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) + transitivePeerDependencies: + - supports-color + + expo-crypto@57.0.4(expo@57.0.29): + dependencies: + expo: 57.0.29(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) expo-file-system@55.0.26(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)): dependencies: expo: 55.0.23(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) + expo-file-system@57.0.7(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)): + dependencies: + expo: 57.0.29(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) + expo-font@55.0.8(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0): dependencies: expo: 55.0.23(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) @@ -15496,11 +16579,23 @@ snapshots: react: 19.3.0 react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) + expo-font@57.0.4(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0): + dependencies: + expo: 57.0.29(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + fontfaceobserver: 2.3.0 + react: 19.3.0 + react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) + expo-keep-awake@55.0.8(expo@55.0.23)(react@19.3.0): dependencies: expo: 55.0.23(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) react: 19.3.0 + expo-keep-awake@57.0.2(expo@57.0.29)(react@19.3.0): + dependencies: + expo: 57.0.29(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + react: 19.3.0 + expo-modules-autolinking@55.0.21(typescript@7.0.2): dependencies: '@expo/require-utils': 55.0.8(typescript@7.0.2) @@ -15511,22 +16606,46 @@ snapshots: - supports-color - typescript + expo-modules-autolinking@57.0.16(typescript@7.0.2): + dependencies: + '@expo/require-utils': 57.0.6(typescript@7.0.2) + '@expo/spawn-async': 1.8.0 + chalk: 4.1.2 + commander: 7.2.0 + transitivePeerDependencies: + - supports-color + - typescript + expo-modules-core@55.0.25(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0): dependencies: invariant: 2.2.4 react: 19.3.0 react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) - expo-secure-store@57.0.1(expo@55.0.23): + expo-modules-core@57.0.23(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0): dependencies: - expo: 55.0.23(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + '@expo/expo-modules-macros-plugin': 0.6.1 + expo-modules-jsi: 57.1.2(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)) + invariant: 2.2.4 + react: 19.3.0 + react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) + + expo-modules-jsi@57.1.2(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)): + dependencies: + react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) + + expo-secure-store@57.0.4(expo@57.0.29): + dependencies: + expo: 57.0.29(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) expo-server@55.0.12: {} - expo-sqlite@55.0.15(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0): + expo-server@57.0.3: {} + + expo-sqlite@57.0.4(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0): dependencies: await-lock: 2.2.2 - expo: 55.0.23(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + expo: 57.0.29(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) react: 19.3.0 react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) @@ -15539,9 +16658,9 @@ snapshots: react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) sf-symbols-typescript: 2.2.0 - expo-web-browser@57.0.2(expo@55.0.23)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)): + expo-web-browser@57.0.3(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)): dependencies: - expo: 55.0.23(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + expo: 57.0.29(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) expo@55.0.23(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2): @@ -15585,6 +16704,46 @@ snapshots: - typescript - utf-8-validate + expo@57.0.29(@babel/core@7.29.7)(@expo/dom-webview@55.0.6)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2): + dependencies: + '@babel/runtime': 7.29.7 + '@expo/cli': 57.0.30(@expo/dom-webview@55.0.6)(expo-constants@57.0.22(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)))(expo-font@57.0.4(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0))(expo@57.0.29)(react-dom@19.3.0(react@19.3.0))(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + '@expo/config': 57.0.10(typescript@7.0.2) + '@expo/config-plugins': 57.0.10(typescript@7.0.2) + '@expo/devtools': 57.0.1(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0) + '@expo/fingerprint': 0.20.14 + '@expo/local-build-cache-provider': 57.0.9(typescript@7.0.2) + '@expo/log-box': 57.0.4(@expo/dom-webview@55.0.6)(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0) + '@expo/metro': 56.0.2 + '@expo/metro-config': 57.0.13(expo@57.0.29)(typescript@7.0.2) + '@ungap/structured-clone': 1.4.0 + babel-preset-expo: 57.0.15(@babel/core@7.29.7)(@babel/runtime@7.29.7)(expo@57.0.29)(react-refresh@0.14.2) + expo-asset: 57.0.20(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0)(typescript@7.0.2) + expo-constants: 57.0.22(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)) + expo-file-system: 57.0.7(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0)) + expo-font: 57.0.4(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0) + expo-keep-awake: 57.0.2(expo@57.0.29)(react@19.3.0) + expo-modules-autolinking: 57.0.16(typescript@7.0.2) + expo-modules-core: 57.0.23(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0) + pretty-format: 29.7.0 + react: 19.3.0 + react-native: 0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0) + react-refresh: 0.14.2 + whatwg-url-minimum: 0.1.2 + optionalDependencies: + '@expo/dom-webview': 55.0.6(expo@57.0.29)(react-native@0.85.3(@babel/core@7.29.7)(@types/react@19.3.0)(react@19.3.0))(react@19.3.0) + react-dom: 19.3.0(react@19.3.0) + transitivePeerDependencies: + - '@babel/core' + - bufferutil + - expo-router + - expo-widgets + - react-native-worklets + - react-server-dom-webpack + - supports-color + - typescript + - utf-8-validate + exponential-backoff@3.1.3: {} exsolve@1.0.8: {} @@ -15950,6 +17109,10 @@ snapshots: hermes-estree@0.35.0: {} + hermes-estree@0.36.0: {} + + hermes-estree@0.36.1: {} + hermes-parser@0.32.0: dependencies: hermes-estree: 0.32.0 @@ -15966,6 +17129,14 @@ snapshots: dependencies: hermes-estree: 0.35.0 + hermes-parser@0.36.0: + dependencies: + hermes-estree: 0.36.0 + + hermes-parser@0.36.1: + dependencies: + hermes-estree: 0.36.1 + hono@4.10.6: {} hono@4.13.5: {} @@ -16307,7 +17478,7 @@ snapshots: whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 whatwg-url: 15.1.0 - ws: 8.21.3 + ws: 8.22.0 xml-name-validator: 5.0.0 transitivePeerDependencies: - bufferutil @@ -16683,6 +17854,16 @@ snapshots: transitivePeerDependencies: - supports-color + metro-babel-transformer@0.84.5: + dependencies: + '@babel/core': 7.29.7 + flow-enums-runtime: 0.0.6 + hermes-parser: 0.35.0 + metro-cache-key: 0.84.5 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + metro-babel-transformer@0.84.6: dependencies: '@babel/core': 7.29.7 @@ -16697,6 +17878,10 @@ snapshots: dependencies: flow-enums-runtime: 0.0.6 + metro-cache-key@0.84.5: + dependencies: + flow-enums-runtime: 0.0.6 + metro-cache-key@0.84.6: dependencies: flow-enums-runtime: 0.0.6 @@ -16710,6 +17895,15 @@ snapshots: transitivePeerDependencies: - supports-color + metro-cache@0.84.5: + dependencies: + exponential-backoff: 3.1.3 + flow-enums-runtime: 0.0.6 + https-proxy-agent: 7.0.6 + metro-core: 0.84.5 + transitivePeerDependencies: + - supports-color + metro-cache@0.84.6: dependencies: exponential-backoff: 3.1.3 @@ -16734,6 +17928,21 @@ snapshots: - supports-color - utf-8-validate + metro-config@0.84.5: + dependencies: + connect: 3.7.0 + flow-enums-runtime: 0.0.6 + jest-validate: 29.7.0 + metro: 0.84.5 + metro-cache: 0.84.5 + metro-core: 0.84.5 + metro-runtime: 0.84.5 + yaml: 2.9.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + metro-config@0.84.6: dependencies: connect: 3.7.0 @@ -16755,6 +17964,12 @@ snapshots: lodash.throttle: 4.1.1 metro-resolver: 0.83.8 + metro-core@0.84.5: + dependencies: + flow-enums-runtime: 0.0.6 + lodash.throttle: 4.1.1 + metro-resolver: 0.84.5 + metro-core@0.84.6: dependencies: flow-enums-runtime: 0.0.6 @@ -16775,7 +17990,21 @@ snapshots: transitivePeerDependencies: - supports-color - metro-file-map@0.84.6: + metro-file-map@0.84.5: + dependencies: + debug: 4.4.3(supports-color@8.1.1) + fb-watchman: 2.0.2 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + invariant: 2.2.4 + jest-worker: 29.7.0 + micromatch: 4.0.8 + nullthrows: 1.1.1 + walker: 1.0.8 + transitivePeerDependencies: + - supports-color + + metro-file-map@0.84.6: dependencies: debug: 4.4.3(supports-color@8.1.1) fb-watchman: 2.0.2 @@ -16794,6 +18023,11 @@ snapshots: flow-enums-runtime: 0.0.6 terser: 5.51.2 + metro-minify-terser@0.84.5: + dependencies: + flow-enums-runtime: 0.0.6 + terser: 5.51.2 + metro-minify-terser@0.84.6: dependencies: flow-enums-runtime: 0.0.6 @@ -16803,6 +18037,10 @@ snapshots: dependencies: flow-enums-runtime: 0.0.6 + metro-resolver@0.84.5: + dependencies: + flow-enums-runtime: 0.0.6 + metro-resolver@0.84.6: dependencies: flow-enums-runtime: 0.0.6 @@ -16812,6 +18050,11 @@ snapshots: '@babel/runtime': 7.29.7 flow-enums-runtime: 0.0.6 + metro-runtime@0.84.5: + dependencies: + '@babel/runtime': 7.29.7 + flow-enums-runtime: 0.0.6 + metro-runtime@0.84.6: dependencies: '@babel/runtime': 7.29.7 @@ -16831,6 +18074,20 @@ snapshots: transitivePeerDependencies: - supports-color + metro-source-map@0.84.5: + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-symbolicate: 0.84.5 + nullthrows: 1.1.1 + ob1: 0.84.5 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + metro-source-map@0.84.6: dependencies: '@babel/traverse': 7.29.8 @@ -16856,6 +18113,17 @@ snapshots: transitivePeerDependencies: - supports-color + metro-symbolicate@0.84.5: + dependencies: + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-source-map: 0.84.5 + nullthrows: 1.1.1 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + metro-symbolicate@0.84.6: dependencies: flow-enums-runtime: 0.0.6 @@ -16878,6 +18146,17 @@ snapshots: transitivePeerDependencies: - supports-color + metro-transform-plugins@0.84.5: + dependencies: + '@babel/core': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + flow-enums-runtime: 0.0.6 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + metro-transform-plugins@0.84.6: dependencies: '@babel/core': 7.29.7 @@ -16909,6 +18188,26 @@ snapshots: - supports-color - utf-8-validate + metro-transform-worker@0.84.5: + dependencies: + '@babel/core': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/parser': 7.29.9 + '@babel/types': 7.29.8 + flow-enums-runtime: 0.0.6 + metro: 0.84.5 + metro-babel-transformer: 0.84.5 + metro-cache: 0.84.5 + metro-cache-key: 0.84.5 + metro-minify-terser: 0.84.5 + metro-source-map: 0.84.5 + metro-transform-plugins: 0.84.5 + nullthrows: 1.1.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + metro-transform-worker@0.84.6: dependencies: '@babel/core': 7.29.7 @@ -16974,6 +18273,51 @@ snapshots: - supports-color - utf-8-validate + metro@0.84.5: + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/parser': 7.29.9 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + accepts: 2.0.0 + ci-info: 2.0.0 + connect: 3.7.0 + debug: 4.4.3(supports-color@8.1.1) + error-stack-parser: 2.1.4 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + hermes-parser: 0.35.0 + invariant: 2.2.4 + jest-worker: 29.7.0 + jsc-safe-url: 0.2.4 + lodash.throttle: 4.1.1 + metro-babel-transformer: 0.84.5 + metro-cache: 0.84.5 + metro-cache-key: 0.84.5 + metro-config: 0.84.5 + metro-core: 0.84.5 + metro-file-map: 0.84.5 + metro-resolver: 0.84.5 + metro-runtime: 0.84.5 + metro-source-map: 0.84.5 + metro-symbolicate: 0.84.5 + metro-transform-plugins: 0.84.5 + metro-transform-worker: 0.84.5 + mime-types: 3.0.2 + nullthrows: 1.1.1 + serialize-error: 2.1.0 + source-map: 0.5.7 + throat: 5.0.0 + ws: 7.5.13 + yargs: 17.7.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + metro@0.84.6: dependencies: '@babel/code-frame': 7.29.7 @@ -17383,6 +18727,10 @@ snapshots: dependencies: flow-enums-runtime: 0.0.6 + ob1@0.84.5: + dependencies: + flow-enums-runtime: 0.0.6 + ob1@0.84.6: dependencies: flow-enums-runtime: 0.0.6 @@ -17494,7 +18842,7 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 - oxfmt@0.72.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)): + oxfmt@0.72.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)): dependencies: tinypool: 2.2.0 optionalDependencies: @@ -17517,9 +18865,9 @@ snapshots: '@oxfmt/binding-win32-arm64-msvc': 0.72.0 '@oxfmt/binding-win32-ia32-msvc': 0.72.0 '@oxfmt/binding-win32-x64-msvc': 0.72.0 - vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) - oxfmt@0.73.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.1)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)): + oxfmt@0.73.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)): dependencies: tinypool: 2.2.0 optionalDependencies: @@ -17542,9 +18890,9 @@ snapshots: '@oxfmt/binding-win32-arm64-msvc': 0.73.0 '@oxfmt/binding-win32-ia32-msvc': 0.73.0 '@oxfmt/binding-win32-x64-msvc': 0.73.0 - vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.1)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) - oxfmt@0.73.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)): + oxfmt@0.73.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.1)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)): dependencies: tinypool: 2.2.0 optionalDependencies: @@ -17567,9 +18915,9 @@ snapshots: '@oxfmt/binding-win32-arm64-msvc': 0.73.0 '@oxfmt/binding-win32-ia32-msvc': 0.73.0 '@oxfmt/binding-win32-x64-msvc': 0.73.0 - vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.1)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) - oxfmt@0.73.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0)): + oxfmt@0.73.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)): dependencies: tinypool: 2.2.0 optionalDependencies: @@ -17592,9 +18940,9 @@ snapshots: '@oxfmt/binding-win32-arm64-msvc': 0.73.0 '@oxfmt/binding-win32-ia32-msvc': 0.73.0 '@oxfmt/binding-win32-x64-msvc': 0.73.0 - vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0) + vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) - oxfmt@0.73.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)): + oxfmt@0.73.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0)): dependencies: tinypool: 2.2.0 optionalDependencies: @@ -17617,7 +18965,32 @@ snapshots: '@oxfmt/binding-win32-arm64-msvc': 0.73.0 '@oxfmt/binding-win32-ia32-msvc': 0.73.0 '@oxfmt/binding-win32-x64-msvc': 0.73.0 - vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0) + + oxfmt@0.73.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)): + dependencies: + tinypool: 2.2.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.73.0 + '@oxfmt/binding-android-arm64': 0.73.0 + '@oxfmt/binding-darwin-arm64': 0.73.0 + '@oxfmt/binding-darwin-x64': 0.73.0 + '@oxfmt/binding-freebsd-x64': 0.73.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.73.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.73.0 + '@oxfmt/binding-linux-arm64-gnu': 0.73.0 + '@oxfmt/binding-linux-arm64-musl': 0.73.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.73.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.73.0 + '@oxfmt/binding-linux-riscv64-musl': 0.73.0 + '@oxfmt/binding-linux-s390x-gnu': 0.73.0 + '@oxfmt/binding-linux-x64-gnu': 0.73.0 + '@oxfmt/binding-linux-x64-musl': 0.73.0 + '@oxfmt/binding-openharmony-arm64': 0.73.0 + '@oxfmt/binding-win32-arm64-msvc': 0.73.0 + '@oxfmt/binding-win32-ia32-msvc': 0.73.0 + '@oxfmt/binding-win32-x64-msvc': 0.73.0 + vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) oxlint-tsgolint@7.0.2004: optionalDependencies: @@ -17628,7 +19001,7 @@ snapshots: '@oxlint-tsgolint/win32-arm64': 7.0.2004 '@oxlint-tsgolint/win32-x64': 7.0.2004 - oxlint@1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.1)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)): + oxlint@1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)): optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.88.0 '@oxlint/binding-android-arm64': 1.88.0 @@ -17650,9 +19023,9 @@ snapshots: '@oxlint/binding-win32-ia32-msvc': 1.88.0 '@oxlint/binding-win32-x64-msvc': 1.88.0 oxlint-tsgolint: 7.0.2004 - vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.1)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) - oxlint@1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)): + oxlint@1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.1)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)): optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.88.0 '@oxlint/binding-android-arm64': 1.88.0 @@ -17674,9 +19047,9 @@ snapshots: '@oxlint/binding-win32-ia32-msvc': 1.88.0 '@oxlint/binding-win32-x64-msvc': 1.88.0 oxlint-tsgolint: 7.0.2004 - vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.1)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) - oxlint@1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0)): + oxlint@1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)): optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.88.0 '@oxlint/binding-android-arm64': 1.88.0 @@ -17698,9 +19071,9 @@ snapshots: '@oxlint/binding-win32-ia32-msvc': 1.88.0 '@oxlint/binding-win32-x64-msvc': 1.88.0 oxlint-tsgolint: 7.0.2004 - vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0) + vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) - oxlint@1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)): + oxlint@1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0)): optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.88.0 '@oxlint/binding-android-arm64': 1.88.0 @@ -17722,7 +19095,31 @@ snapshots: '@oxlint/binding-win32-ia32-msvc': 1.88.0 '@oxlint/binding-win32-x64-msvc': 1.88.0 oxlint-tsgolint: 7.0.2004 - vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) + vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0) + + oxlint@1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.88.0 + '@oxlint/binding-android-arm64': 1.88.0 + '@oxlint/binding-darwin-arm64': 1.88.0 + '@oxlint/binding-darwin-x64': 1.88.0 + '@oxlint/binding-freebsd-x64': 1.88.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.88.0 + '@oxlint/binding-linux-arm-musleabihf': 1.88.0 + '@oxlint/binding-linux-arm64-gnu': 1.88.0 + '@oxlint/binding-linux-arm64-musl': 1.88.0 + '@oxlint/binding-linux-ppc64-gnu': 1.88.0 + '@oxlint/binding-linux-riscv64-gnu': 1.88.0 + '@oxlint/binding-linux-riscv64-musl': 1.88.0 + '@oxlint/binding-linux-s390x-gnu': 1.88.0 + '@oxlint/binding-linux-x64-gnu': 1.88.0 + '@oxlint/binding-linux-x64-musl': 1.88.0 + '@oxlint/binding-openharmony-arm64': 1.88.0 + '@oxlint/binding-win32-arm64-msvc': 1.88.0 + '@oxlint/binding-win32-ia32-msvc': 1.88.0 + '@oxlint/binding-win32-x64-msvc': 1.88.0 + oxlint-tsgolint: 7.0.2004 + vite-plus: 1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1) p-defer@4.0.1: {} @@ -18314,6 +19711,8 @@ snapshots: safer-buffer@2.1.2: {} + sandbox-cli-detector@0.2.0: {} + sax@1.6.1: {} saxes@6.0.0: @@ -18596,8 +19995,6 @@ snapshots: seroval: 1.5.6 seroval-plugins: 1.5.6(seroval@1.5.6) - sorted-btree@1.8.1: {} - source-map-js@1.2.1: {} source-map-js@1.2.2: {} @@ -19143,7 +20540,69 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.1)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1): + vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1): + dependencies: + '@oxc-project/types': 0.154.0 + '@oxlint/plugins': 1.88.0 + '@vitest/browser': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/browser-preview': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/mocker': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) + '@vitest/pretty-format': 5.0.4 + '@vitest/snapshot': 5.0.4 + '@vitest/spy': 5.0.4 + '@vitest/utils': 5.0.4 + oxfmt: 0.73.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) + oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) + oxlint-tsgolint: 7.0.2004 + vite: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0) + optionalDependencies: + '@vitest/browser-playwright': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4) + '@voidzero-dev/vite-plus-darwin-arm64': 1.2.0 + '@voidzero-dev/vite-plus-darwin-x64': 1.2.0 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.2.0 + '@voidzero-dev/vite-plus-linux-arm64-musl': 1.2.0 + '@voidzero-dev/vite-plus-linux-x64-gnu': 1.2.0 + '@voidzero-dev/vite-plus-linux-x64-musl': 1.2.0 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 1.2.0 + '@voidzero-dev/vite-plus-win32-x64-msvc': 1.2.0 + playwright: 1.61.1 + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@astrojs/compiler-rs' + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@types/node' + - '@typescript/native-preview' + - '@vitejs/devtools' + - '@vitest/coverage-istanbul' + - '@vitest/coverage-v8' + - '@vitest/ui' + - '@volar/typescript' + - '@vue/language-core' + - bufferutil + - esbuild + - happy-dom + - jiti + - jsdom + - less + - msw + - publint + - sass + - sass-embedded + - stylus + - sugarss + - svelte + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - utf-8-validate + - vue-tsc + - yaml + + vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.1)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1): dependencies: '@oxc-project/types': 0.154.0 '@oxlint/plugins': 1.88.0 @@ -19154,13 +20613,13 @@ snapshots: '@vitest/snapshot': 5.0.4 '@vitest/spy': 5.0.4 '@vitest/utils': 5.0.4 - oxfmt: 0.73.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.1)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) - oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.1)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) + oxfmt: 0.73.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.1)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) + oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.1)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) oxlint-tsgolint: 7.0.2004 vite: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' - vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0) optionalDependencies: - '@vitest/browser-playwright': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4) + '@vitest/browser-playwright': 5.0.4(playwright@1.61.1)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) '@voidzero-dev/vite-plus-darwin-arm64': 1.2.0 '@voidzero-dev/vite-plus-darwin-x64': 1.2.0 '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.2.0 @@ -19205,7 +20664,7 @@ snapshots: - vue-tsc - yaml - vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1): + vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1): dependencies: '@oxc-project/types': 0.154.0 '@oxlint/plugins': 1.88.0 @@ -19216,13 +20675,13 @@ snapshots: '@vitest/snapshot': 5.0.4 '@vitest/spy': 5.0.4 '@vitest/utils': 5.0.4 - oxfmt: 0.73.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) - oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) + oxfmt: 0.73.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) + oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.6.1)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) oxlint-tsgolint: 7.0.2004 vite: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' - vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0) optionalDependencies: - '@vitest/browser-playwright': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4) + '@vitest/browser-playwright': 5.0.4(playwright@1.61.1)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) '@voidzero-dev/vite-plus-darwin-arm64': 1.2.0 '@voidzero-dev/vite-plus-darwin-x64': 1.2.0 '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.2.0 @@ -19267,7 +20726,7 @@ snapshots: - vue-tsc - yaml - vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0): + vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0): dependencies: '@oxc-project/types': 0.154.0 '@oxlint/plugins': 1.88.0 @@ -19278,13 +20737,13 @@ snapshots: '@vitest/snapshot': 5.0.4 '@vitest/spy': 5.0.4 '@vitest/utils': 5.0.4 - oxfmt: 0.73.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0)) - oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0)) + oxfmt: 0.73.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0)) + oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0)) oxlint-tsgolint: 7.0.2004 vite: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0)' - vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(jsdom@27.2.0) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(jsdom@27.2.0) optionalDependencies: - '@vitest/browser-playwright': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(playwright@1.61.1)(vitest@5.0.4) + '@vitest/browser-playwright': 5.0.4(playwright@1.61.1)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0))(vitest@5.0.4) '@voidzero-dev/vite-plus-darwin-arm64': 1.2.0 '@voidzero-dev/vite-plus-darwin-x64': 1.2.0 '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.2.0 @@ -19329,7 +20788,7 @@ snapshots: - vue-tsc - yaml - vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1): + vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1): dependencies: '@oxc-project/types': 0.154.0 '@oxlint/plugins': 1.88.0 @@ -19340,13 +20799,13 @@ snapshots: '@vitest/snapshot': 5.0.4 '@vitest/spy': 5.0.4 '@vitest/utils': 5.0.4 - oxfmt: 0.73.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) - oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) + oxfmt: 0.73.0(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) + oxlint: 1.88.0(oxlint-tsgolint@7.0.2004)(vite-plus@1.2.0(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(esbuild@0.28.2)(jiti@2.7.0)(jsdom@27.2.0)(playwright@1.61.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) oxlint-tsgolint: 7.0.2004 vite: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' - vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0) + vitest: 5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0) optionalDependencies: - '@vitest/browser-playwright': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4) + '@vitest/browser-playwright': 5.0.4(playwright@1.61.1)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) '@voidzero-dev/vite-plus-darwin-arm64': 1.2.0 '@voidzero-dev/vite-plus-darwin-x64': 1.2.0 '@voidzero-dev/vite-plus-linux-arm64-gnu': 1.2.0 @@ -19391,11 +20850,105 @@ snapshots: - vue-tsc - yaml + vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.29 + rolldown: 1.2.13 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.10.1 + esbuild: 0.28.1 + fsevents: 2.3.3 + jiti: 2.7.0 + terser: 5.51.2 + tsx: 4.20.6 + yaml: 2.9.1 + + vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.29 + rolldown: 1.2.13 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.10.1 + esbuild: 0.28.2 + fsevents: 2.3.3 + jiti: 2.6.1 + terser: 5.51.2 + tsx: 4.20.6 + yaml: 2.9.1 + + vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.29 + rolldown: 1.2.13 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.10.1 + esbuild: 0.28.2 + fsevents: 2.3.3 + jiti: 2.7.0 + terser: 5.51.2 + tsx: 4.20.6 + yaml: 2.9.0 + + vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.29 + rolldown: 1.2.13 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.10.1 + esbuild: 0.28.2 + fsevents: 2.3.3 + jiti: 2.7.0 + terser: 5.51.2 + tsx: 4.20.6 + yaml: 2.9.1 + vitefu@1.1.2(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)): optionalDependencies: vite: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' - vitest@5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0): + vitefu@1.1.2(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)): + optionalDependencies: + vite: 8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1) + optional: true + + vitest@5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) + chai: 6.3.0 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.3 + obug: 3.0.0 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 6.2.2 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: '@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)' + why-is-node-running: 3.2.1 + optionalDependencies: + '@types/node': 24.10.1 + '@vitest/browser-playwright': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4) + '@vitest/browser-preview': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/ui': 5.0.4(vitest@5.0.4) + jsdom: 27.2.0 + transitivePeerDependencies: + - msw + + vitest@5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0): dependencies: '@types/chai': 5.2.3 '@vitest/mocker': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) @@ -19413,14 +20966,14 @@ snapshots: why-is-node-running: 3.2.1 optionalDependencies: '@types/node': 24.10.1 - '@vitest/browser-playwright': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4) - '@vitest/browser-preview': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/browser-playwright': 5.0.4(playwright@1.61.1)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/browser-preview': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) '@vitest/ui': 5.0.4(vitest@5.0.4) jsdom: 27.2.0 transitivePeerDependencies: - msw - vitest@5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0): + vitest@5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0): dependencies: '@types/chai': 5.2.3 '@vitest/mocker': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) @@ -19438,14 +20991,14 @@ snapshots: why-is-node-running: 3.2.1 optionalDependencies: '@types/node': 24.10.1 - '@vitest/browser-playwright': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4) - '@vitest/browser-preview': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/browser-playwright': 5.0.4(playwright@1.61.1)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/browser-preview': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) '@vitest/ui': 5.0.4(vitest@5.0.4) jsdom: 27.2.0 transitivePeerDependencies: - msw - vitest@5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(jsdom@27.2.0): + vitest@5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(jsdom@27.2.0): dependencies: '@types/chai': 5.2.3 '@vitest/mocker': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0)) @@ -19463,14 +21016,14 @@ snapshots: why-is-node-running: 3.2.1 optionalDependencies: '@types/node': 24.10.1 - '@vitest/browser-playwright': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(playwright@1.61.1)(vitest@5.0.4) - '@vitest/browser-preview': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.0))(vitest@5.0.4) + '@vitest/browser-playwright': 5.0.4(playwright@1.61.1)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0))(vitest@5.0.4) + '@vitest/browser-preview': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0))(vitest@5.0.4) '@vitest/ui': 5.0.4(vitest@5.0.4) jsdom: 27.2.0 transitivePeerDependencies: - msw - vitest@5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4))(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0): + vitest@5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(jsdom@27.2.0): dependencies: '@types/chai': 5.2.3 '@vitest/mocker': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1)) @@ -19488,8 +21041,108 @@ snapshots: why-is-node-running: 3.2.1 optionalDependencies: '@types/node': 24.10.1 - '@vitest/browser-playwright': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(playwright@1.61.1)(vitest@5.0.4) - '@vitest/browser-preview': 5.0.4(@voidzero-dev/vite-plus-core@1.2.0(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(typescript@7.0.2)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/browser-playwright': 5.0.4(playwright@1.61.1)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/browser-preview': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/ui': 5.0.4(vitest@5.0.4) + jsdom: 27.2.0 + transitivePeerDependencies: + - msw + + vitest@5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) + chai: 6.3.0 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.3 + obug: 3.0.0 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 6.2.2 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: 8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1) + why-is-node-running: 3.2.1 + optionalDependencies: + '@types/node': 24.10.1 + '@vitest/browser-playwright': 5.0.4(playwright@1.61.1)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/browser-preview': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.6.1)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/ui': 5.0.4(vitest@5.0.4) + jsdom: 27.2.0 + transitivePeerDependencies: + - msw + + vitest@5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0)) + chai: 6.3.0 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.3 + obug: 3.0.0 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 6.2.2 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: 8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0) + why-is-node-running: 3.2.1 + optionalDependencies: + '@types/node': 24.10.1 + '@vitest/browser-playwright': 5.0.4(playwright@1.61.1)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0))(vitest@5.0.4) + '@vitest/browser-preview': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.0))(vitest@5.0.4) + '@vitest/ui': 5.0.4(vitest@5.0.4) + jsdom: 27.2.0 + transitivePeerDependencies: + - msw + + vitest@5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4(vitest@5.0.4))(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) + chai: 6.3.0 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.3 + obug: 3.0.0 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 6.2.2 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: 8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1) + why-is-node-running: 3.2.1 + optionalDependencies: + '@types/node': 24.10.1 + '@vitest/browser-playwright': 5.0.4(playwright@1.61.1)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/browser-preview': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/ui': 5.0.4(vitest@5.0.4) + jsdom: 27.2.0 + transitivePeerDependencies: + - msw + + vitest@5.0.4(@types/node@24.10.1)(@vitest/browser-playwright@5.0.4)(@vitest/browser-preview@5.0.4)(@vitest/ui@5.0.4)(jsdom@27.2.0)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1)) + chai: 6.3.0 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.3 + obug: 3.0.0 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 6.2.2 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: 8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1) + why-is-node-running: 3.2.1 + optionalDependencies: + '@types/node': 24.10.1 + '@vitest/browser-playwright': 5.0.4(playwright@1.61.1)(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) + '@vitest/browser-preview': 5.0.4(vite@8.3.4(@types/node@24.10.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.51.2)(tsx@4.20.6)(yaml@2.9.1))(vitest@5.0.4) '@vitest/ui': 5.0.4(vitest@5.0.4) jsdom: 27.2.0 transitivePeerDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 42807ea4..521e47d5 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -12,7 +12,7 @@ catalog: # Apply toolchain versions to transitive dependencies as well. # https://viteplus.dev/guide/migrate-rules#vite-and-overrides overrides: - "vite@*": "npm:@voidzero-dev/vite-plus-core@1.2.0" + "vite": "npm:@voidzero-dev/vite-plus-core@1.2.0" "vitest@*": "5.0.4" # Plugins may still declare peer ranges for upstream Vite or older Vitest. # https://pnpm.io/settings/peer-dependencies#peerdependencyrulesallowany