@tanstack/db-sqlite-persistence-core 0.4.7 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +32 -3
  2. package/dist/cjs/broadcast-coordinator.cjs +282 -43
  3. package/dist/cjs/broadcast-coordinator.cjs.map +1 -1
  4. package/dist/cjs/broadcast-coordinator.d.cts +25 -8
  5. package/dist/cjs/committed-tx-anchor.cjs +9 -0
  6. package/dist/cjs/committed-tx-anchor.cjs.map +1 -0
  7. package/dist/cjs/committed-tx-anchor.d.cts +1 -0
  8. package/dist/cjs/index.cjs +4 -0
  9. package/dist/cjs/index.cjs.map +1 -1
  10. package/dist/cjs/persisted.cjs +1599 -313
  11. package/dist/cjs/persisted.cjs.map +1 -1
  12. package/dist/cjs/persisted.d.cts +106 -13
  13. package/dist/cjs/remote-subset-wire.cjs +11 -0
  14. package/dist/cjs/remote-subset-wire.cjs.map +1 -1
  15. package/dist/cjs/remote-subset-wire.d.cts +1 -0
  16. package/dist/cjs/sqlite-core-adapter.cjs +705 -64
  17. package/dist/cjs/sqlite-core-adapter.cjs.map +1 -1
  18. package/dist/cjs/sqlite-core-adapter.d.cts +53 -10
  19. package/dist/esm/broadcast-coordinator.d.ts +25 -8
  20. package/dist/esm/broadcast-coordinator.js +283 -44
  21. package/dist/esm/broadcast-coordinator.js.map +1 -1
  22. package/dist/esm/committed-tx-anchor.d.ts +1 -0
  23. package/dist/esm/committed-tx-anchor.js +9 -0
  24. package/dist/esm/committed-tx-anchor.js.map +1 -0
  25. package/dist/esm/index.js +6 -2
  26. package/dist/esm/persisted.d.ts +106 -13
  27. package/dist/esm/persisted.js +1601 -315
  28. package/dist/esm/persisted.js.map +1 -1
  29. package/dist/esm/remote-subset-wire.d.ts +1 -0
  30. package/dist/esm/remote-subset-wire.js +11 -0
  31. package/dist/esm/remote-subset-wire.js.map +1 -1
  32. package/dist/esm/sqlite-core-adapter.d.ts +53 -10
  33. package/dist/esm/sqlite-core-adapter.js +708 -67
  34. package/dist/esm/sqlite-core-adapter.js.map +1 -1
  35. package/package.json +2 -2
  36. package/src/broadcast-coordinator.ts +415 -62
  37. package/src/committed-tx-anchor.ts +13 -0
  38. package/src/persisted.ts +2283 -370
  39. package/src/remote-subset-wire.ts +12 -0
  40. package/src/sqlite-core-adapter.ts +891 -68
package/dist/esm/index.js CHANGED
@@ -1,12 +1,13 @@
1
- import { SQLITE_DRIVER_SHARED_LOGICAL_SCHEDULING_KEY, SingleProcessCoordinator, createPersistedTableName, decodePersistedStorageKey, encodePersistedStorageKey, forwardSQLiteDriverSharedLogicalScheduling, persistedCollectionOptions, validatePersistedCollectionCoordinator } from "./persisted.js";
1
+ import { SQLITE_DRIVER_SHARED_LOGICAL_SCHEDULING_KEY, SingleProcessCoordinator, createPersistedTableName, decodePersistedStorageKey, eagerStorageTarget, encodePersistedStorageKey, forwardSQLiteDriverSharedLogicalScheduling, managedStorageTarget, persistedCollectionOptions, resolvePersistedStorageTarget, validatePersistedCollectionCoordinator } from "./persisted.js";
2
2
  import { DuplicateRemoteSubsetOwnerError, IndeterminateCommitError, InvalidPersistedCollectionConfigError, InvalidPersistedCollectionCoordinatorError, InvalidPersistedStorageKeyEncodingError, InvalidPersistedStorageKeyError, InvalidPersistenceAdapterError, InvalidSyncConfigError, PersistedCollectionCoreError, PersistedCollectionDurabilityError, PersistenceUnavailableError, RetryableRemoteSubsetAcquisitionError, SQLiteBigIntOutOfRangeError, toPersistedCollectionDurabilityError } from "./errors.js";
3
- import { DEFAULT_APPLIED_TX_PRUNE_MAX_AGE_SECONDS, DEFAULT_APPLIED_TX_PRUNE_MAX_ROWS, SQLiteCorePersistenceAdapter, createSQLiteCorePersistenceAdapter } from "./sqlite-core-adapter.js";
3
+ import { DEFAULT_APPLIED_TX_PRUNE_MAX_AGE_SECONDS, DEFAULT_APPLIED_TX_PRUNE_MAX_ROWS, DEFAULT_CACHE_GENERATION_CLAIM_TTL_MS, SQLiteCorePersistenceAdapter, createSQLiteCorePersistenceAdapter } from "./sqlite-core-adapter.js";
4
4
  import { PERSISTED_TYPE_TAG, PERSISTED_VALUE_TAG, SQLITE_BIGINT_MAX, SQLITE_BIGINT_MIN, assertSQLiteBigIntInRange, reviveSQLiteTemporal, serializeSQLiteBigInt, serializeSQLiteTemporal, sqliteTemporalIdentity, sqliteTemporalKind } from "./sqlite-value.js";
5
5
  import { RemoteSubsetWireValueError, toProcessLocalLoadSubsetOptions, toTransportedLoadSubsetOptions, validateRemoteSubsetOptions } from "./remote-subset-wire.js";
6
6
  import { safeRandomUUID } from "@tanstack/db";
7
7
  export {
8
8
  DEFAULT_APPLIED_TX_PRUNE_MAX_AGE_SECONDS,
9
9
  DEFAULT_APPLIED_TX_PRUNE_MAX_ROWS,
10
+ DEFAULT_CACHE_GENERATION_CLAIM_TTL_MS,
10
11
  DuplicateRemoteSubsetOwnerError,
11
12
  IndeterminateCommitError,
12
13
  InvalidPersistedCollectionConfigError,
@@ -32,9 +33,12 @@ export {
32
33
  createPersistedTableName,
33
34
  createSQLiteCorePersistenceAdapter,
34
35
  decodePersistedStorageKey,
36
+ eagerStorageTarget,
35
37
  encodePersistedStorageKey,
36
38
  forwardSQLiteDriverSharedLogicalScheduling,
39
+ managedStorageTarget,
37
40
  persistedCollectionOptions,
41
+ resolvePersistedStorageTarget,
38
42
  reviveSQLiteTemporal,
39
43
  safeRandomUUID,
40
44
  serializeSQLiteBigInt,
@@ -49,6 +49,7 @@ export type TxCommitted = {
49
49
  latestRowVersion: number;
50
50
  } & ({
51
51
  requiresFullReload: true;
52
+ reconciliationReload?: true;
52
53
  } | {
53
54
  requiresFullReload: false;
54
55
  changedRows: Array<{
@@ -149,6 +150,41 @@ export type ApplyCommittedTxResponse = {
149
150
  sourceCode?: string | number;
150
151
  path?: string | ReadonlyArray<string | number>;
151
152
  };
153
+ /** The durable version read before a source transaction enters coordination. */
154
+ export type CommittedTxAnchor = {
155
+ latestRowVersion: number;
156
+ resetEpoch: number;
157
+ };
158
+ export type ReconcileCommittedTxResponse = {
159
+ type: `rpc:reconcileCommittedTx:res`;
160
+ rpcId: string;
161
+ ok: true;
162
+ alreadyApplied: boolean;
163
+ committed: {
164
+ term: number;
165
+ seq: number;
166
+ rowVersion: number;
167
+ };
168
+ } | {
169
+ type: `rpc:reconcileCommittedTx:res`;
170
+ rpcId: string;
171
+ ok: false;
172
+ code: `NOT_LEADER` | `UNDETERMINED` | `PERSISTENCE_ERROR`;
173
+ error: string;
174
+ sourceCode?: string | number;
175
+ path?: string | ReadonlyArray<string | number>;
176
+ };
177
+ export type ReconciledCommittedTx = {
178
+ kind: `already-applied` | `applied-now`;
179
+ committed: {
180
+ term: number;
181
+ seq: number;
182
+ rowVersion: number;
183
+ };
184
+ latestRowVersion: number;
185
+ } | {
186
+ kind: `unknown`;
187
+ };
152
188
  export type PullSinceRequest = {
153
189
  type: `rpc:pullSince:req`;
154
190
  rpcId: string;
@@ -223,6 +259,27 @@ export type PersistedScannedRow<T extends object = Record<string, unknown>, TKey
223
259
  };
224
260
  export type PersistedRowScanOptions = SyncPersistenceScanOptions;
225
261
  export type PersistedKeySetEvidence = SyncPersistenceKeySetEvidence;
262
+ export type PersistedCacheGenerationClaim = {
263
+ storageCollectionId: string;
264
+ claimId: string;
265
+ /** Present when the adapter expires dormant claims. */
266
+ expiresAtMs?: number;
267
+ };
268
+ /** An eager Collection name or a claim-bound persisted cache generation. */
269
+ export type PersistedStorageTarget = {
270
+ kind: `eager`;
271
+ collectionId: string;
272
+ } | {
273
+ kind: `managed`;
274
+ storageCollectionId: string;
275
+ claimId: string;
276
+ };
277
+ export declare function eagerStorageTarget(collectionId: string): PersistedStorageTarget;
278
+ export declare function managedStorageTarget(storageCollectionId: string, claimId: string): PersistedStorageTarget;
279
+ export declare function resolvePersistedStorageTarget(target: PersistedStorageTarget, legacyClaimId?: string): {
280
+ collectionId: string;
281
+ claimId?: string;
282
+ };
226
283
  export type PersistencePullSinceResult = {
227
284
  latestRowVersion: number;
228
285
  requiresFullReload: true;
@@ -238,6 +295,8 @@ export type PersistedTx<T extends object = Record<string, unknown>, TKey extends
238
295
  term: number;
239
296
  seq: number;
240
297
  rowVersion: number;
298
+ /** Claim authorizing this transaction's persisted cache generation. */
299
+ cacheGenerationClaimId?: string;
241
300
  truncate?: boolean;
242
301
  mutations: Array<{
243
302
  type: `insert`;
@@ -272,16 +331,18 @@ export type PersistedTx<T extends object = Record<string, unknown>, TKey extends
272
331
  */
273
332
  export declare const SQLITE_DRIVER_SHARED_LOGICAL_SCHEDULING_KEY: unique symbol;
274
333
  export interface PersistenceAdapter {
275
- loadSubset: (collectionId: string, options: LoadSubsetOptions, ctx?: {
334
+ loadSubset: (target: PersistedStorageTarget, options: LoadSubsetOptions, ctx?: {
276
335
  requiredIndexSignatures?: ReadonlyArray<string>;
336
+ cacheGenerationClaimId?: string;
277
337
  }) => Promise<Array<{
278
338
  key: string | number;
279
339
  value: Record<string, unknown>;
280
340
  metadata?: unknown;
281
341
  }>>;
282
- loadResumeSnapshot: (collectionId: string, ctx?: {
342
+ loadResumeSnapshot: (target: PersistedStorageTarget, ctx?: {
283
343
  requiredIndexSignatures?: ReadonlyArray<string>;
284
344
  includeRows?: boolean;
345
+ cacheGenerationClaimId?: string;
285
346
  }) => Promise<{
286
347
  rows: Array<{
287
348
  key: string | number;
@@ -298,15 +359,43 @@ export interface PersistenceAdapter {
298
359
  latestRowVersion: number;
299
360
  resetEpoch: number;
300
361
  }>;
301
- applyCommittedTx: (collectionId: string, tx: PersistedTx) => Promise<void>;
302
- loadCollectionMetadata?: (collectionId: string) => Promise<Array<{
362
+ applyCommittedTx: (target: PersistedStorageTarget, tx: PersistedTx) => Promise<void>;
363
+ claimCacheGeneration?: (collectionId: string) => Promise<PersistedCacheGenerationClaim>;
364
+ rotateCacheGeneration?: (collectionId: string, claimId: string, resetMetadata?: {
365
+ key: string;
366
+ value: unknown;
367
+ }, expectedStorageCollectionId?: string) => Promise<PersistedCacheGenerationClaim>;
368
+ releaseCacheGenerationClaim?: (claimId: string) => Promise<void>;
369
+ renewCacheGenerationClaim?: (storageCollectionId: string, claimId: string) => Promise<number | undefined>;
370
+ getCacheGenerationNow?: () => number;
371
+ /** Atomically certify or apply one source transaction after a lost response. */
372
+ reconcileCommittedTx?: (target: PersistedStorageTarget, tx: PersistedTx, anchor: CommittedTxAnchor) => Promise<ReconciledCommittedTx>;
373
+ loadCollectionMetadata?: (target: PersistedStorageTarget, ctx?: {
374
+ cacheGenerationClaimId?: string;
375
+ }) => Promise<Array<{
303
376
  key: string;
304
377
  value: unknown;
305
378
  }>>;
306
- scanRows?: (collectionId: string, options?: PersistedRowScanOptions) => Promise<Array<PersistedScannedRow>>;
307
- ensureIndex: (collectionId: string, signature: string, spec: PersistedIndexSpec) => Promise<void>;
308
- markIndexRemoved?: (collectionId: string, signature: string) => Promise<void>;
309
- getStreamPosition?: (collectionId: string) => Promise<{
379
+ scanRows?: (target: PersistedStorageTarget, options?: PersistedRowScanOptions, ctx?: {
380
+ cacheGenerationClaimId?: string;
381
+ }) => Promise<Array<PersistedScannedRow>>;
382
+ ensureIndex: (target: PersistedStorageTarget, signature: string, spec: PersistedIndexSpec, ctx?: {
383
+ cacheGenerationClaimId?: string;
384
+ }) => Promise<void>;
385
+ markIndexRemoved?: (target: PersistedStorageTarget, signature: string, ctx?: {
386
+ cacheGenerationClaimId?: string;
387
+ }) => Promise<void>;
388
+ getStreamPosition?: (target: PersistedStorageTarget, ctx?: {
389
+ cacheGenerationClaimId?: string;
390
+ }) => Promise<{
391
+ latestTerm: number;
392
+ latestSeq: number;
393
+ latestRowVersion: number;
394
+ }>;
395
+ /** Reserve a new durable election term before announcing Browser leadership. */
396
+ reserveLeadershipTerm?: (target: PersistedStorageTarget, observedTerm: number, ctx?: {
397
+ cacheGenerationClaimId?: string;
398
+ }) => Promise<{
310
399
  latestTerm: number;
311
400
  latestSeq: number;
312
401
  latestRowVersion: number;
@@ -323,7 +412,9 @@ export interface PersistenceAdapter {
323
412
  isHydrationScopeScheduled?: () => boolean;
324
413
  }
325
414
  export type HydrationPersistenceAdapter = PersistenceAdapter & {
326
- pullSince?: (collectionId: string, fromRowVersion: number) => Promise<PersistencePullSinceResult>;
415
+ pullSince?: (target: PersistedStorageTarget, fromRowVersion: number, ctx?: {
416
+ cacheGenerationClaimId?: string;
417
+ }) => Promise<PersistencePullSinceResult>;
327
418
  };
328
419
  export type { RemoteSubsetOwner } from './remote-subset-owner.js';
329
420
  export interface SQLiteDriver {
@@ -343,7 +434,7 @@ export interface SQLiteDriver {
343
434
  export declare function forwardSQLiteDriverSharedLogicalScheduling<TDriver extends SQLiteDriver>(source: SQLiteDriver, wrapper: TDriver): TDriver;
344
435
  export interface PersistedCollectionCoordinator {
345
436
  getNodeId: () => string;
346
- setAdapterForCollection?: (collectionId: string, adapter: PersistenceAdapter) => void;
437
+ setAdapterForCollection?: (collectionId: string, adapter: PersistenceAdapter, cacheGenerationClaimId?: string) => void;
347
438
  subscribe: (collectionId: string, onMessage: (message: ProtocolEnvelope<unknown>) => void) => () => void;
348
439
  publish: (collectionId: string, message: ProtocolEnvelope<unknown>) => void;
349
440
  isLeader: (collectionId: string) => boolean;
@@ -356,8 +447,8 @@ export interface PersistedCollectionCoordinator {
356
447
  * and is never serialized to a follower. `localEnsureCompleted` lets the
357
448
  * built-in leader avoid repeating successful local work.
358
449
  */
359
- requestEnsurePersistedIndex: (collectionId: string, signature: string, spec: PersistedIndexSpec, scopedAdapter?: HydrationPersistenceAdapter, localEnsureCompleted?: boolean) => Promise<void>;
360
- requestApplyLocalMutations?: (collectionId: string, mutations: Array<PersistedMutationEnvelope>) => Promise<ApplyLocalMutationsResponse>;
450
+ requestEnsurePersistedIndex: (collectionId: string, signature: string, spec: PersistedIndexSpec, scopedAdapter?: HydrationPersistenceAdapter, localEnsureCompleted?: boolean, cacheGenerationClaimId?: string) => Promise<void>;
451
+ requestApplyLocalMutations?: (collectionId: string, mutations: Array<PersistedMutationEnvelope>, cacheGenerationClaimId?: string) => Promise<ApplyLocalMutationsResponse>;
361
452
  /**
362
453
  * Applies a committed transaction through the supplied adapter when present.
363
454
  * Wrappers must forward this leader-local loan: scheduling it again can wait
@@ -365,8 +456,10 @@ export interface PersistedCollectionCoordinator {
365
456
  * loan after the call; ordinary work uses the registered collection adapter.
366
457
  */
367
458
  requestApplyCommittedTx: (collectionId: string, tx: PersistedTx, scopedAdapter?: HydrationPersistenceAdapter) => Promise<ApplyCommittedTxResponse>;
459
+ /** Available only when the elected adapter can certify the durable outcome. */
460
+ reconcileCommittedTx?: (collectionId: string, tx: PersistedTx, anchor: CommittedTxAnchor, scopedAdapter?: HydrationPersistenceAdapter) => Promise<ReconcileCommittedTxResponse>;
368
461
  /** The scoped adapter is leader-local and is never serialized to a follower. */
369
- pullSince?: (collectionId: string, fromRowVersion: number, scopedAdapter?: HydrationPersistenceAdapter) => Promise<PullSinceResponse>;
462
+ pullSince?: (collectionId: string, fromRowVersion: number, scopedAdapter?: HydrationPersistenceAdapter, cacheGenerationClaimId?: string) => Promise<PullSinceResponse>;
370
463
  }
371
464
  export interface PersistedCollectionPersistence {
372
465
  adapter: PersistenceAdapter;