Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/fix-sqlite-expression-index-planning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@tanstack/db': patch
'@tanstack/db-sqlite-persistence-core': patch
---

Preserve explicit source aliases without changing legacy property paths. Compile SQLite expression-index queries consistently, rebuild affected stale physical indexes, and reject BigInts outside SQLite's signed 64-bit range.
12 changes: 12 additions & 0 deletions docs/contributing/oracle-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ comment and the current API/architecture contract before extending its model.
| Electric and TrailBase | [Electric histories](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/tests/electric-oracle.property.test.ts), [recovery histories](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/tests/electric-recovery-oracle.test.ts), [held resume snapshots](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/tests/electric-resume-snapshot-races.test.ts), [PostgreSQL semantics](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/e2e/sql-predicate-semantics.e2e.test.ts), [TrailBase contract](https://github.com/TanStack/db/blob/main/packages/trailbase-db-collection/tests/ORACLE.md) | Installed SDK delivery/framing, independent predicates, exact subscription arguments, restart/reset lineage, held certification and durability races, source-order publication before durability, and late errors. The queued-presence property runs identical fixed/random generators plus isolated seed-and-path replay across insert, update, delete, and truncate callbacks. The recovery fixtures use a mocked ShapeStream; they do not establish live Electric-service framing or native persistence-host behavior. |
| PowerSync | [tests](https://github.com/TanStack/db/tree/main/packages/powersync-db-collection/tests), `tests/correctness-oracle.test.ts` | Applied receipt positions crossed with held peers, native SQLite/SDK and cleanup evidence. Run the focused owner with the package's `test:oracles` command. A timeout mutant proves a progress failure, not every value assertion. |
| SQLite persistence and native hosts | [persisted histories](https://github.com/TanStack/db/blob/main/packages/db-sqlite-persistence-core/tests/persisted.test.ts), [reset/resume histories](https://github.com/TanStack/db/blob/main/packages/db-sqlite-persistence-core/tests/sqlite-core-adapter.test.ts), [dual-adapter resume snapshots](https://github.com/TanStack/db/blob/main/packages/db-sqlite-persistence-core/tests/sqlite-resume-snapshot.test.ts), [Browser composed-owner histories](https://github.com/TanStack/db/blob/main/packages/browser-db-sqlite-persistence/tests/per-collection-coordinator-oracle.test.ts), [Browser coordinator RPC](https://github.com/TanStack/db/blob/main/packages/browser-db-sqlite-persistence/tests/browser-coordinator.test.ts), [driver contracts](https://github.com/TanStack/db/blob/main/packages/db-sqlite-persistence-core/tests/contracts/sqlite-driver-contract.ts), [Node shared-handle scheduling](https://github.com/TanStack/db/blob/main/packages/node-db-sqlite-persistence/tests/node-driver.test.ts), [OP-SQLite shared-handle scheduling](https://github.com/TanStack/db/blob/main/packages/react-native-db-sqlite-persistence/tests/op-sqlite-driver.test.ts), [browser OPFS lifecycle](https://github.com/TanStack/db/blob/main/packages/browser-db-sqlite-persistence/tests/opfs-page-lifecycle-oracle.test.ts), [worker diagnostics](https://github.com/TanStack/db/blob/main/packages/browser-db-sqlite-persistence/tests/opfs-worker-diagnostics-oracle.test.ts), [Electron IPC and composed owner](https://github.com/TanStack/db/blob/main/packages/electron-db-sqlite-persistence/tests/electron-ipc.test.ts), [113-law manifest](https://github.com/TanStack/db/blob/main/packages/db-collection-e2e/src/fixtures/persisted-conformance-manifest.ts) | Core cache/remote rejection/peer/reopen histories, atomic reset/resume lineage, key-set evidence, dual-adapter races, and exact driver results. Browser composes public source commits with per-collection elected-owner routing and covers the complete committed-transaction wire partition through deterministic Node transport seams. Remote-subset histories distinguish logical demand, physical acquisitions, exact acquisition leases, and released replay tombstones. Electron composes source commits with a per-collection renderer owner, IPC adapter, real SQLite, and reopen checks. Same-handle Node and OP-SQLite tests cover transaction admission. Controlled OPFS page/worker histories cover ownership and diagnostic-cause retention. The reset/resume owners use sqlite3 CLI and in-memory node:sqlite seams; they do not prove multi-process WAL, mobile/Tauri, or other native-device execution. Distinct database handles rely on SQLite lock admission rather than one in-process queue. React Native hosts without async-context propagation must use the transaction driver supplied to the callback for nested work. Fake workers and synthetic page events do not prove native handle release or real bfcache admission. The Browser composed seams are not real multi-context/OPFS-worker execution; the Electron harness is not an actual Electron process unless its explicit runtime-bridge mode runs. An ownerless elected node suppresses core routing, while a follower may route demand to the elected owner; host coordinators retry only classified transport or admission failures while demand remains retained. The manifest excludes progressive and move suites; registration and shim runs are not device execution. |
| SQLite expression-index planning | [Node expression-index oracle](https://github.com/TanStack/db/blob/main/packages/node-db-sqlite-persistence/tests/expression-index-oracle.test.ts) | RFC #1659 invariant 8 owns identical persisted-index and runtime-expression shapes. Independent expected keys are checked against direct captured SQL, adapter results, and named-index plans. Generated BigInts use SQLite's signed range; one fixed case checks legacy oversized-value reads. Other limits: bounded unqualified JSON paths/scalars, Node BetterSQLite, and no null, arbitrary raw SQL, or native-host planning. Run the package's `test:oracles` campaign. |
| Offline execution | [scheduler](https://github.com/TanStack/db/blob/main/packages/offline-transactions/tests/KeyScheduler.property.test.ts), [leadership](https://github.com/TanStack/db/blob/main/packages/offline-transactions/tests/leadership-replay.property.test.ts), [settlement](https://github.com/TanStack/db/blob/main/packages/offline-transactions/tests/transaction-settlement.property.test.ts), [serialization](https://github.com/TanStack/db/blob/main/packages/offline-transactions/tests/transaction-serializer.property.test.ts) | Declarative FIFO eligibility, per-transaction outcomes, durable state and typed wire trees. Issued work may finish after ownership loss, but new work must not start. Exactly-once network execution is not promised. |
| Frameworks | [React conformance](https://github.com/TanStack/db/blob/main/packages/react-db/tests/conformance.test.tsx), [React pagination](https://github.com/TanStack/db/blob/main/packages/react-db/tests/infinite-query-conformance.test.tsx), [shared suites](https://github.com/TanStack/db/tree/main/packages/db-collection-e2e/src/suites) | Exact exposed rows/pages and each framework's own lifecycle cuts. A React witness does not prove Vue/Solid/Angular/Svelte scheduling. Preserve their receiving registrations. |
| Structural values and ordered primitives | [hash values](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/hash.property.test.ts), [hash graphs](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/hash-graph.property.test.ts), [mixed hash graphs](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/hash-mixed-graph.property.test.ts), [hash retry](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/hash-failure-retry.property.test.ts), [comparison](https://github.com/TanStack/db/blob/main/packages/db/tests/comparison.property.test.ts), [deep equality](https://github.com/TanStack/db/blob/main/packages/db/tests/utils.property.test.ts), [cursor](https://github.com/TanStack/db/blob/main/packages/db/tests/cursor.property.test.ts), [indexes](https://github.com/TanStack/db/blob/main/packages/db/tests/index-update.property.test.ts), [query identity](https://github.com/TanStack/db/blob/main/packages/db/tests/query/identity-output-shape-oracle.test.ts), [LIKE semantics](https://github.com/TanStack/db/blob/main/packages/db/tests/query/compiler/evaluators.test.ts) | Independent flat values, graph topology, algebraic laws, Map/group/sort recomputation, expression denotation, LIKE wildcard refinement, and compiled output bags. The LIKE owner covers boolean string matching and bounded work, not nullish three-valued logic or a general Unicode collation contract. Hash collision freedom is not promised. Unsupported composite cursors reject. |
Expand Down Expand Up @@ -159,6 +160,8 @@ pnpm --filter @tanstack/db-ivm build
pnpm --filter @tanstack/db build
pnpm --filter @tanstack/db test:oracles
pnpm --filter @tanstack/powersync-db-collection test:oracles
pnpm --filter @tanstack/db-sqlite-persistence-core build
pnpm --filter @tanstack/node-db-sqlite-persistence test:oracles
# Service-dependent: requires Electric and PostgreSQL to be running.
pnpm --filter @tanstack/electric-db-collection test:e2e
pnpm run typecheck:tests
Expand All @@ -184,6 +187,15 @@ target execution, not reproduction of a particular bug. Local IVM and offline
properties have separate environment variables; inspect their test headers.
Do not assume the core multiplier reaches them.

The Node expression-index oracle runs matching fixed-seed and seedless-random
campaigns by default. Supplying both replay values selects only the requested
seed and shrink path:

```sh
TANSTACK_DB_WS5A_SEED=1659005 TANSTACK_DB_WS5A_PATH=0 \
pnpm --filter @tanstack/node-db-sqlite-persistence test:oracles
```

Stress runs need an explicit file list, run budget, seed policy, runtime, exit
status and cost. For long synchronous campaigns, yield **between complete
histories**, never between an action and its synchronous observation. In this
Expand Down
9 changes: 9 additions & 0 deletions packages/db-sqlite-persistence-core/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,15 @@ export class InvalidPersistedStorageKeyEncodingError extends InvalidPersistedCol
}
}

export class SQLiteBigIntOutOfRangeError extends PersistedCollectionCoreError {
constructor(value: bigint, minimum: bigint, maximum: bigint) {
super(
`SQLite BigInt value ${value} is outside the signed 64-bit range [${minimum}, ${maximum}]`,
)
this.name = `SQLiteBigIntOutOfRangeError`
}
}

export class PersistenceUnavailableError extends PersistedCollectionCoreError {
constructor(details?: string) {
super(
Expand Down
1 change: 1 addition & 0 deletions packages/db-sqlite-persistence-core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './persisted'
export * from './errors'
export * from './sqlite-core-adapter'
export * from './sqlite-value'
export * from './remote-subset-wire'
// Re-export for use in non-secure browser contexts (see #1541)
export { safeRandomUUID } from '@tanstack/db'
3 changes: 2 additions & 1 deletion packages/db-sqlite-persistence-core/src/persisted.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
PersistedCollectionDurabilityError,
toPersistedCollectionDurabilityError,
} from './errors'
import { serializeSQLiteBigInt } from './sqlite-value'
import {
toProcessLocalLoadSubsetOptions,
toTransportedLoadSubsetOptions,
Expand Down Expand Up @@ -980,7 +981,7 @@ function toStableSerializable(value: unknown): unknown {
case `boolean`:
return value
case `bigint`:
return value.toString()
return serializeSQLiteBigInt(value)
case `function`:
case `symbol`:
case `undefined`:
Expand Down
27 changes: 22 additions & 5 deletions packages/db-sqlite-persistence-core/src/remote-subset-wire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export type RemoteSubsetWireValue =
| RemoteSubsetWireRecord

export type RemoteSubsetWireExpression =
| { type: `ref`; path: Array<string> }
| { type: `ref`; path: Array<string>; sourceAlias?: string }
| { type: `val`; value: RemoteSubsetWireValue }
| {
type: `func`
Expand Down Expand Up @@ -232,18 +232,35 @@ function projectExpression(
switch (type.value) {
case `ref`: {
assertExpressionPrototype(object, path, IR.PropRef.prototype)
assertAllowedProperties(object, path, [`type`, `path`])
const projected = {
assertAllowedProperties(object, path, [`type`, `path`, `sourceAlias`])
const projected: Extract<RemoteSubsetWireExpression, { type: `ref` }> = {
type: `ref`,
path: [] as Array<string>,
} satisfies RemoteSubsetWireExpression
path: [],
}
state.expressions.set(object, projected)
const sourcePath = readRequiredDataProperty(
object,
`path`,
`${path}.path`,
)
projected.path = projectStringArray(sourcePath, `${path}.path`, state)
const sourceAlias = readDataProperty(
object,
`sourceAlias`,
`${path}.sourceAlias`,
)
if (sourceAlias.present) {
if (
typeof sourceAlias.value !== `string` ||
projected.path[0] !== sourceAlias.value
) {
throw new RemoteSubsetWireValueError(
`${path}.sourceAlias`,
`source alias must match the first path segment`,
)
}
projected.sourceAlias = sourceAlias.value
}
return projected
}
case `val`: {
Expand Down
Loading
Loading