From 00389a47b258ad58fc3a03c5cc6f66957b9bd2d1 Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 17 Jun 2026 22:42:33 +0200 Subject: [PATCH 01/11] fix: safe randomUUID helper for non-secure browser contexts (#1593) * fix(db): use safe randomUUID helper for non-secure browser contexts (#1541) * fix(db-sqlite-persistence-core): use safe randomUUID helper (#1541) * fix(browser-db-sqlite-persistence): use safe randomUUID helper (#1541) * fix(electron-db-sqlite-persistence): use safe randomUUID helper (#1541) * fix(offline-transactions): use safe randomUUID helper (#1541) * ci: apply automated fixes * refactor: rename randomUUID helper to safeRandomUUID and add crypto-undefined test --------- Co-authored-by: Kevin De Porre Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .changeset/safe-random-uuid.md | 9 +++ .../src/browser-coordinator.ts | 21 ++--- .../db-sqlite-persistence-core/src/index.ts | 2 + .../src/persisted.ts | 20 +++-- packages/db/src/collection/index.ts | 3 +- packages/db/src/collection/mutations.ts | 7 +- packages/db/src/index.ts | 3 + packages/db/src/local-only.ts | 3 +- packages/db/src/local-storage.ts | 3 +- packages/db/src/transactions.ts | 3 +- packages/db/src/utils/uuid.ts | 45 +++++++++++ packages/db/tests/uuid.test.ts | 76 +++++++++++++++++++ .../src/electron-coordinator.ts | 21 ++--- .../src/OfflineExecutor.ts | 10 ++- .../src/api/OfflineTransaction.ts | 6 +- .../coordination/BroadcastChannelLeader.ts | 3 +- 16 files changed, 193 insertions(+), 42 deletions(-) create mode 100644 .changeset/safe-random-uuid.md create mode 100644 packages/db/src/utils/uuid.ts create mode 100644 packages/db/tests/uuid.test.ts diff --git a/.changeset/safe-random-uuid.md b/.changeset/safe-random-uuid.md new file mode 100644 index 0000000000..a10264d535 --- /dev/null +++ b/.changeset/safe-random-uuid.md @@ -0,0 +1,9 @@ +--- +'@tanstack/db': patch +'@tanstack/browser-db-sqlite-persistence': patch +'@tanstack/offline-transactions': patch +'@tanstack/db-sqlite-persistence-core': patch +'@tanstack/electron-db-sqlite-persistence': patch +--- + +Use a safe `randomUUID` helper that falls back to `crypto.getRandomValues` when `crypto.randomUUID` is unavailable (non-secure browser contexts such as dev servers reached via a LAN IP over HTTP). Fixes #1541. diff --git a/packages/browser-db-sqlite-persistence/src/browser-coordinator.ts b/packages/browser-db-sqlite-persistence/src/browser-coordinator.ts index 5b518ea387..1babddc5a7 100644 --- a/packages/browser-db-sqlite-persistence/src/browser-coordinator.ts +++ b/packages/browser-db-sqlite-persistence/src/browser-coordinator.ts @@ -1,3 +1,4 @@ +import { safeRandomUUID } from '@tanstack/db-sqlite-persistence-core' import type { ApplyLocalMutationsResponse, PersistedCollectionCoordinator, @@ -118,7 +119,7 @@ export type BrowserCollectionCoordinatorOptions = { // --------------------------------------------------------------------------- export class BrowserCollectionCoordinator implements PersistedCollectionCoordinator { - private readonly nodeId = crypto.randomUUID() + private readonly nodeId = safeRandomUUID() private readonly dbName: string private adapter: AdapterWithPullSince | null private readonly channel: BroadcastChannel @@ -205,7 +206,7 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina error?: string }>(collectionId, { type: `rpc:ensureRemoteSubset:req`, - rpcId: crypto.randomUUID(), + rpcId: safeRandomUUID(), options, }) @@ -233,7 +234,7 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina error?: string }>(collectionId, { type: `rpc:ensurePersistedIndex:req`, - rpcId: crypto.randomUUID(), + rpcId: safeRandomUUID(), signature, spec, }) @@ -252,16 +253,16 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina if (this.isLeader(collectionId)) { return this.handleApplyLocalMutations(collectionId, { type: `rpc:applyLocalMutations:req`, - rpcId: crypto.randomUUID(), - envelopeId: crypto.randomUUID(), + rpcId: safeRandomUUID(), + envelopeId: safeRandomUUID(), mutations, }) } return this.sendRPC(collectionId, { type: `rpc:applyLocalMutations:req`, - rpcId: crypto.randomUUID(), - envelopeId: crypto.randomUUID(), + rpcId: safeRandomUUID(), + envelopeId: safeRandomUUID(), mutations, }) } @@ -273,14 +274,14 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina if (this.isLeader(collectionId)) { return this.handlePullSince(collectionId, { type: `rpc:pullSince:req`, - rpcId: crypto.randomUUID(), + rpcId: safeRandomUUID(), fromRowVersion, }) } return this.sendRPC(collectionId, { type: `rpc:pullSince:req`, - rpcId: crypto.randomUUID(), + rpcId: safeRandomUUID(), fromRowVersion, }) } @@ -663,7 +664,7 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina // Build and apply the persisted transaction const tx = { - txId: crypto.randomUUID(), + txId: safeRandomUUID(), term, seq, rowVersion, diff --git a/packages/db-sqlite-persistence-core/src/index.ts b/packages/db-sqlite-persistence-core/src/index.ts index 921b92f3b0..9e2bb9faae 100644 --- a/packages/db-sqlite-persistence-core/src/index.ts +++ b/packages/db-sqlite-persistence-core/src/index.ts @@ -1,3 +1,5 @@ export * from './persisted' export * from './errors' export * from './sqlite-core-adapter' +// Re-export for use in non-secure browser contexts (see #1541) +export { safeRandomUUID } from '@tanstack/db' diff --git a/packages/db-sqlite-persistence-core/src/persisted.ts b/packages/db-sqlite-persistence-core/src/persisted.ts index 7c20e96883..12cf8319c3 100644 --- a/packages/db-sqlite-persistence-core/src/persisted.ts +++ b/packages/db-sqlite-persistence-core/src/persisted.ts @@ -1,4 +1,8 @@ -import { compileSingleRowExpression, toBooleanPredicate } from '@tanstack/db' +import { + compileSingleRowExpression, + safeRandomUUID, + toBooleanPredicate, +} from '@tanstack/db' import { InvalidPersistedCollectionConfigError, InvalidPersistedCollectionCoordinatorError, @@ -440,7 +444,7 @@ type SyncControlFns = { export class SingleProcessCoordinator implements PersistedCollectionCoordinator { private readonly nodeId: string - constructor(nodeId: string = crypto.randomUUID()) { + constructor(nodeId: string = safeRandomUUID()) { this.nodeId = nodeId } @@ -467,7 +471,7 @@ export class SingleProcessCoordinator implements PersistedCollectionCoordinator public pullSince(): Promise { return Promise.resolve({ type: `rpc:pullSince:res`, - rpcId: crypto.randomUUID(), + rpcId: safeRandomUUID(), ok: true, latestTerm: 1, latestSeq: 0, @@ -1387,7 +1391,7 @@ class PersistedCollectionRuntime< this.createTxCommittedPayload({ term: streamPosition.term, seq: streamPosition.seq, - txId: crypto.randomUUID(), + txId: safeRandomUUID(), latestRowVersion: streamPosition.rowVersion, changedRows: [], deletedKeys: [], @@ -1427,7 +1431,7 @@ class PersistedCollectionRuntime< streamPosition: { term: number; seq: number; rowVersion: number }, ): PersistedTx { return { - txId: crypto.randomUUID(), + txId: safeRandomUUID(), term: streamPosition.term, seq: streamPosition.seq, rowVersion: streamPosition.rowVersion, @@ -1471,7 +1475,7 @@ class PersistedCollectionRuntime< streamPosition: { term: number; seq: number; rowVersion: number }, ): PersistedTx { return { - txId: crypto.randomUUID(), + txId: safeRandomUUID(), term: streamPosition.term, seq: streamPosition.seq, rowVersion: streamPosition.rowVersion, @@ -2607,7 +2611,7 @@ export function persistedCollectionOptions< const { schemaVersion, ...syncOptions } = options const collectionId = - syncOptions.id ?? `persisted-collection:${crypto.randomUUID()}` + syncOptions.id ?? `persisted-collection:${safeRandomUUID()}` const persistence = resolvePersistenceForCollection( syncOptions.persistence, { @@ -2635,7 +2639,7 @@ export function persistedCollectionOptions< const { schemaVersion, ...localOnlyOptions } = options const collectionId = - localOnlyOptions.id ?? `persisted-collection:${crypto.randomUUID()}` + localOnlyOptions.id ?? `persisted-collection:${safeRandomUUID()}` const persistence = resolvePersistenceForCollection( localOnlyOptions.persistence, { diff --git a/packages/db/src/collection/index.ts b/packages/db/src/collection/index.ts index e51eb998d6..137fd5f595 100644 --- a/packages/db/src/collection/index.ts +++ b/packages/db/src/collection/index.ts @@ -1,3 +1,4 @@ +import { safeRandomUUID } from '../utils/uuid' import { CollectionConfigurationError, CollectionRequiresConfigError, @@ -329,7 +330,7 @@ export class CollectionImpl< if (config.id) { this.id = config.id } else { - this.id = crypto.randomUUID() + this.id = safeRandomUUID() } // Set default values for optional config properties diff --git a/packages/db/src/collection/mutations.ts b/packages/db/src/collection/mutations.ts index 765e409ef6..abfb6693eb 100644 --- a/packages/db/src/collection/mutations.ts +++ b/packages/db/src/collection/mutations.ts @@ -1,4 +1,5 @@ import { withArrayChangeTracking, withChangeTracking } from '../proxy' +import { safeRandomUUID } from '../utils/uuid' import { createTransaction, getActiveTransaction } from '../transactions' import { DeleteKeyNotFoundError, @@ -193,7 +194,7 @@ export class CollectionMutationsManager< const globalKey = this.generateGlobalKey(key, item) const mutation: PendingMutation = { - mutationId: crypto.randomUUID(), + mutationId: safeRandomUUID(), original: {}, modified: validatedData, // Pick the values from validatedData based on what's passed in - this is for cases @@ -366,7 +367,7 @@ export class CollectionMutationsManager< const globalKey = this.generateGlobalKey(modifiedItemId, modifiedItem) return { - mutationId: crypto.randomUUID(), + mutationId: safeRandomUUID(), original: originalItem, modified: modifiedItem, // Pick the values from modifiedItem based on what's passed in - this is for cases @@ -497,7 +498,7 @@ export class CollectionMutationsManager< `delete`, CollectionImpl > = { - mutationId: crypto.randomUUID(), + mutationId: safeRandomUUID(), original: this.state.get(key)!, modified: this.state.get(key)!, changes: this.state.get(key)!, diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index ec1e229665..347a3119b5 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -80,6 +80,9 @@ export { type EffectQueryInput, } from './query/effect.js' +// UUID helper (safe in non-secure browser contexts, see #1541) +export { safeRandomUUID } from './utils/uuid.js' + // Re-export some stuff explicitly to ensure the type & value is exported export type { Collection } from './collection/index.js' export { IR } diff --git a/packages/db/src/local-only.ts b/packages/db/src/local-only.ts index d3a0a7f2ca..afcf3c9a76 100644 --- a/packages/db/src/local-only.ts +++ b/packages/db/src/local-only.ts @@ -1,3 +1,4 @@ +import { safeRandomUUID } from './utils/uuid' import type { BaseCollectionConfig, CollectionConfig, @@ -182,7 +183,7 @@ export function localOnlyCollectionOptions< const { initialData, onInsert, onUpdate, onDelete, id, ...restConfig } = config - const collectionId = id ?? crypto.randomUUID() + const collectionId = id ?? safeRandomUUID() // Create the sync configuration with transaction confirmation capability const syncResult = createLocalOnlySync(initialData) diff --git a/packages/db/src/local-storage.ts b/packages/db/src/local-storage.ts index 3060b7ec61..05ad388d7c 100644 --- a/packages/db/src/local-storage.ts +++ b/packages/db/src/local-storage.ts @@ -1,3 +1,4 @@ +import { safeRandomUUID } from './utils/uuid' import { InvalidStorageDataFormatError, InvalidStorageObjectFormatError, @@ -149,7 +150,7 @@ function validateJsonSerializable( * @returns A unique identifier string for tracking data versions */ function generateUuid(): string { - return crypto.randomUUID() + return safeRandomUUID() } /** diff --git a/packages/db/src/transactions.ts b/packages/db/src/transactions.ts index 84e2bb0d5d..fe2f61c0fd 100644 --- a/packages/db/src/transactions.ts +++ b/packages/db/src/transactions.ts @@ -1,4 +1,5 @@ import { createDeferred } from './deferred' +import { safeRandomUUID } from './utils/uuid' import './duplicate-instance-check' import { MissingMutationFunctionError, @@ -224,7 +225,7 @@ class Transaction> { if (typeof config.mutationFn === `undefined`) { throw new MissingMutationFunctionError() } - this.id = config.id ?? crypto.randomUUID() + this.id = config.id ?? safeRandomUUID() this.mutationFn = config.mutationFn this.state = `pending` this.mutations = [] diff --git a/packages/db/src/utils/uuid.ts b/packages/db/src/utils/uuid.ts new file mode 100644 index 0000000000..45875a459f --- /dev/null +++ b/packages/db/src/utils/uuid.ts @@ -0,0 +1,45 @@ +/** + * Returns a RFC 4122 version 4 UUID. + * + * Prefers `crypto.randomUUID()` when available. In non-secure browser contexts + * (e.g. a dev server accessed via a LAN IP over HTTP) `crypto.randomUUID` is + * `undefined`, so this falls back to building a UUIDv4 from + * `crypto.getRandomValues`. Throws if neither API is available. + * + * See https://github.com/TanStack/db/issues/1541. + */ +export function safeRandomUUID(): string { + const c: Crypto | undefined = + typeof globalThis !== `undefined` ? (globalThis as any).crypto : undefined + + if (c && typeof c.randomUUID === `function`) { + return c.randomUUID() + } + + if (c && typeof c.getRandomValues === `function`) { + const bytes = c.getRandomValues(new Uint8Array(16)) + // Per RFC 4122 §4.4: set version (4) and variant (10xx) bits. + bytes[6] = (bytes[6]! & 0x0f) | 0x40 + bytes[8] = (bytes[8]! & 0x3f) | 0x80 + + const hex: Array = [] + for (let i = 0; i < 16; i++) { + hex.push(bytes[i]!.toString(16).padStart(2, `0`)) + } + return ( + hex.slice(0, 4).join(``) + + `-` + + hex.slice(4, 6).join(``) + + `-` + + hex.slice(6, 8).join(``) + + `-` + + hex.slice(8, 10).join(``) + + `-` + + hex.slice(10, 16).join(``) + ) + } + + throw new Error( + `No secure random number generator available: neither crypto.randomUUID nor crypto.getRandomValues is defined in this environment.`, + ) +} diff --git a/packages/db/tests/uuid.test.ts b/packages/db/tests/uuid.test.ts new file mode 100644 index 0000000000..f6cf577e70 --- /dev/null +++ b/packages/db/tests/uuid.test.ts @@ -0,0 +1,76 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { safeRandomUUID } from '../src/utils/uuid' + +const UUID_V4_REGEX = + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/ + +describe(`safeRandomUUID helper`, () => { + afterEach(() => { + vi.restoreAllMocks() + vi.unstubAllGlobals() + }) + + it(`delegates to crypto.randomUUID when available`, () => { + const spy = vi + .spyOn(globalThis.crypto, `randomUUID`) + .mockReturnValue(`11111111-2222-4333-8444-555555555555`) + const id = safeRandomUUID() + expect(spy).toHaveBeenCalledTimes(1) + expect(id).toBe(`11111111-2222-4333-8444-555555555555`) + }) + + it(`falls back to getRandomValues when crypto.randomUUID is undefined (non-secure context)`, () => { + // Simulate a non-secure browser context where crypto.randomUUID is unavailable + // but getRandomValues remains. + vi.stubGlobal(`crypto`, { + randomUUID: undefined, + getRandomValues: (arr: Uint8Array) => { + // Deterministic-ish fill so we can verify version/variant bits land + // exactly where they should. + for (let i = 0; i < arr.length; i++) arr[i] = 0xff + return arr + }, + }) + + const id = safeRandomUUID() + expect(id).toMatch(UUID_V4_REGEX) + + // Verify version nibble == 4 and variant nibble in [8,9,a,b] + const versionChar = id[14] + const variantChar = id[19] + expect(versionChar).toBe(`4`) + expect([`8`, `9`, `a`, `b`]).toContain(variantChar) + + // With all bytes 0xff, expect ffffffff-ffff-4fff-bfff-ffffffffffff + expect(id).toBe(`ffffffff-ffff-4fff-bfff-ffffffffffff`) + }) + + it(`produces unique, well-formed UUIDs via the fallback path across many calls`, () => { + vi.stubGlobal(`crypto`, { + randomUUID: undefined, + getRandomValues: (arr: Uint8Array) => { + for (let i = 0; i < arr.length; i++) + arr[i] = Math.floor(Math.random() * 256) + return arr + }, + }) + + const seen = new Set() + for (let i = 0; i < 200; i++) { + const id = safeRandomUUID() + expect(id).toMatch(UUID_V4_REGEX) + seen.add(id) + } + expect(seen.size).toBe(200) + }) + + it(`throws when neither crypto.randomUUID nor crypto.getRandomValues is available`, () => { + vi.stubGlobal(`crypto`, {}) + expect(() => safeRandomUUID()).toThrow(/No secure random number generator/) + }) + + it(`throws when globalThis.crypto is undefined`, () => { + vi.stubGlobal(`crypto`, undefined) + expect(() => safeRandomUUID()).toThrow(/No secure random number generator/) + }) +}) diff --git a/packages/electron-db-sqlite-persistence/src/electron-coordinator.ts b/packages/electron-db-sqlite-persistence/src/electron-coordinator.ts index ea9735e709..a4c6bb7fe8 100644 --- a/packages/electron-db-sqlite-persistence/src/electron-coordinator.ts +++ b/packages/electron-db-sqlite-persistence/src/electron-coordinator.ts @@ -1,3 +1,4 @@ +import { safeRandomUUID } from '@tanstack/db-sqlite-persistence-core' import type { ApplyLocalMutationsResponse, PersistedCollectionCoordinator, @@ -118,7 +119,7 @@ export type ElectronCollectionCoordinatorOptions = { // --------------------------------------------------------------------------- export class ElectronCollectionCoordinator implements PersistedCollectionCoordinator { - private readonly nodeId = crypto.randomUUID() + private readonly nodeId = safeRandomUUID() private readonly dbName: string private adapter: AdapterWithPullSince | null private readonly channel: BroadcastChannel @@ -205,7 +206,7 @@ export class ElectronCollectionCoordinator implements PersistedCollectionCoordin error?: string }>(collectionId, { type: `rpc:ensureRemoteSubset:req`, - rpcId: crypto.randomUUID(), + rpcId: safeRandomUUID(), options, }) @@ -233,7 +234,7 @@ export class ElectronCollectionCoordinator implements PersistedCollectionCoordin error?: string }>(collectionId, { type: `rpc:ensurePersistedIndex:req`, - rpcId: crypto.randomUUID(), + rpcId: safeRandomUUID(), signature, spec, }) @@ -252,16 +253,16 @@ export class ElectronCollectionCoordinator implements PersistedCollectionCoordin if (this.isLeader(collectionId)) { return this.handleApplyLocalMutations(collectionId, { type: `rpc:applyLocalMutations:req`, - rpcId: crypto.randomUUID(), - envelopeId: crypto.randomUUID(), + rpcId: safeRandomUUID(), + envelopeId: safeRandomUUID(), mutations, }) } return this.sendRPC(collectionId, { type: `rpc:applyLocalMutations:req`, - rpcId: crypto.randomUUID(), - envelopeId: crypto.randomUUID(), + rpcId: safeRandomUUID(), + envelopeId: safeRandomUUID(), mutations, }) } @@ -273,14 +274,14 @@ export class ElectronCollectionCoordinator implements PersistedCollectionCoordin if (this.isLeader(collectionId)) { return this.handlePullSince(collectionId, { type: `rpc:pullSince:req`, - rpcId: crypto.randomUUID(), + rpcId: safeRandomUUID(), fromRowVersion, }) } return this.sendRPC(collectionId, { type: `rpc:pullSince:req`, - rpcId: crypto.randomUUID(), + rpcId: safeRandomUUID(), fromRowVersion, }) } @@ -663,7 +664,7 @@ export class ElectronCollectionCoordinator implements PersistedCollectionCoordin // Build and apply the persisted transaction const tx = { - txId: crypto.randomUUID(), + txId: safeRandomUUID(), term, seq, rowVersion, diff --git a/packages/offline-transactions/src/OfflineExecutor.ts b/packages/offline-transactions/src/OfflineExecutor.ts index 8f443277c2..a6140cfebc 100644 --- a/packages/offline-transactions/src/OfflineExecutor.ts +++ b/packages/offline-transactions/src/OfflineExecutor.ts @@ -1,5 +1,9 @@ // Storage adapters -import { createOptimisticAction, createTransaction } from '@tanstack/db' +import { + createOptimisticAction, + createTransaction, + safeRandomUUID, +} from '@tanstack/db' import { IndexedDBAdapter } from './storage/IndexedDBAdapter' import { LocalStorageAdapter } from './storage/LocalStorageAdapter' @@ -367,7 +371,7 @@ export class OfflineExecutor { mutationFn: (params) => mutationFn({ ...params, - idempotencyKey: options.idempotencyKey || crypto.randomUUID(), + idempotencyKey: options.idempotencyKey || safeRandomUUID(), }), metadata: options.metadata, }) @@ -399,7 +403,7 @@ export class OfflineExecutor { mutationFn({ ...vars, ...params, - idempotencyKey: crypto.randomUUID(), + idempotencyKey: safeRandomUUID(), }), onMutate: options.onMutate, }) diff --git a/packages/offline-transactions/src/api/OfflineTransaction.ts b/packages/offline-transactions/src/api/OfflineTransaction.ts index 32c96fd26a..af13484a75 100644 --- a/packages/offline-transactions/src/api/OfflineTransaction.ts +++ b/packages/offline-transactions/src/api/OfflineTransaction.ts @@ -1,4 +1,4 @@ -import { createTransaction } from '@tanstack/db' +import { createTransaction, safeRandomUUID } from '@tanstack/db' import { NonRetriableError } from '../types' import type { PendingMutation, Transaction } from '@tanstack/db' import type { @@ -23,10 +23,10 @@ export class OfflineTransaction { persistTransaction: (tx: OfflineTransactionType) => Promise, executor: any, ) { - this.offlineId = crypto.randomUUID() + this.offlineId = safeRandomUUID() this.mutationFnName = options.mutationFnName this.autoCommit = options.autoCommit ?? true - this.idempotencyKey = options.idempotencyKey ?? crypto.randomUUID() + this.idempotencyKey = options.idempotencyKey ?? safeRandomUUID() this.metadata = options.metadata ?? {} this.persistTransaction = persistTransaction this.executor = executor diff --git a/packages/offline-transactions/src/coordination/BroadcastChannelLeader.ts b/packages/offline-transactions/src/coordination/BroadcastChannelLeader.ts index ce11a8abc3..7f50d06459 100644 --- a/packages/offline-transactions/src/coordination/BroadcastChannelLeader.ts +++ b/packages/offline-transactions/src/coordination/BroadcastChannelLeader.ts @@ -1,3 +1,4 @@ +import { safeRandomUUID } from '@tanstack/db' import { BaseLeaderElection } from './LeaderElection' interface LeaderMessage { @@ -19,7 +20,7 @@ export class BroadcastChannelLeader extends BaseLeaderElection { constructor(channelName = `offline-executor-leader`) { super() this.channelName = channelName - this.tabId = crypto.randomUUID() + this.tabId = safeRandomUUID() this.setupChannel() } From 3124c9e49e831a0a012295d4e82a55cfda95f848 Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Fri, 12 Jun 2026 16:01:44 +0200 Subject: [PATCH 02/11] Added attachments support. --- packages/powersync-db-collection/package.json | 4 +- .../src/attachments.ts | 181 ++++++++++++++++++ pnpm-lock.yaml | 23 ++- 3 files changed, 197 insertions(+), 11 deletions(-) create mode 100644 packages/powersync-db-collection/src/attachments.ts diff --git a/packages/powersync-db-collection/package.json b/packages/powersync-db-collection/package.json index 93444c4f26..66f2af4ae5 100644 --- a/packages/powersync-db-collection/package.json +++ b/packages/powersync-db-collection/package.json @@ -59,10 +59,10 @@ "p-defer": "^4.0.1" }, "peerDependencies": { - "@powersync/common": "^1.41.0" + "@powersync/common": "^1.54.0" }, "devDependencies": { - "@powersync/common": "1.49.0", + "@powersync/common": "1.54.0", "@powersync/node": "0.18.1", "@types/debug": "^4.1.12", "@vitest/coverage-istanbul": "^3.2.4", diff --git a/packages/powersync-db-collection/src/attachments.ts b/packages/powersync-db-collection/src/attachments.ts new file mode 100644 index 0000000000..9359abfc9e --- /dev/null +++ b/packages/powersync-db-collection/src/attachments.ts @@ -0,0 +1,181 @@ +import { + AttachmentQueue, + AttachmentState, + AttachmentTable, + Schema, +} from '@powersync/common' +import { createTransaction } from '@tanstack/db' +import { PowerSyncTransactor } from './PowerSyncTransactor' + +import type { + AbstractPowerSyncDatabase, + AttachmentData, + AttachmentErrorHandler, + ILogger, + LocalStorageAdapter, + RemoteStorageAdapter, + WatchedAttachmentItem, +} from '@powersync/common' +import type { Collection } from '@tanstack/db' + +type AttachmentQueueRow = (typeof _tmpSchema)['types']['attachments'] + +/** + * This extends the default AttachmentQueue constructor params + * FIXME(powersync) we should export this type from the common SDK. + */ +type TanStackDBAttachmentQueueOptions = { + db: AbstractPowerSyncDatabase + /** + * For TanStack, we want access to the synced TanStackDB collection. + * In order to have the same relational data be set in a single transaction. + * This also allows for joining both TanStackDB collections. + */ + attachmentsCollection: Collection + remoteStorage: RemoteStorageAdapter + localStorage: LocalStorageAdapter + watchAttachments: ( + onUpdate: (attachment: Array) => Promise, + signal: AbortSignal, + ) => void + tableName?: string + logger?: ILogger + syncIntervalMs?: number + syncThrottleDuration?: number + downloadAttachments?: boolean + archivedCacheLimit?: number + errorHandler?: AttachmentErrorHandler +} + +interface SaveFileTanStackOptions { + data: AttachmentData + fileExtension: string + mediaType?: string + metaData?: string + id?: string + /** + * Note that this is called inside a synchronous TanStackDB transaction, + * any mutations made to other collections will be in the same transaction. + */ + updateHook?: (attachment: AttachmentQueueRow) => Promise +} + +interface DeleteFileTanStackOptions { + id: string + updateHook?: (attachment: AttachmentQueueRow) => Promise +} + +const _tmpSchema = new Schema({ + attachments: new AttachmentTable(), +}) + +/** + * A custom extension of the PowerSyncAttachmentQueue for TanStackDB. + */ +export class TanStackDBAttachmentQueue extends AttachmentQueue { + readonly powersync: AbstractPowerSyncDatabase + readonly collection: Collection + + constructor(params: TanStackDBAttachmentQueueOptions) { + super(params) + this.powersync = params.db + this.collection = params.attachmentsCollection + } + + /** + * Saves a file to local storage and queues it for upload to remote storage. + * + * Exposes an `updateHook` option which is called inside a TanStackDB transaction, + * relational associations with the provided attachment ID should be made in this hook. + */ + async saveFileTanStack({ + data, + fileExtension, + mediaType, + metaData, + id, + updateHook, + }: SaveFileTanStackOptions): Promise { + const resolvedId = id ?? (await this.generateAttachmentId()) + const filename = `${resolvedId}.${fileExtension}` + const localUri = this.localStorage.getLocalUri(filename) + const size = await this.localStorage.saveFile(localUri, data) + + const attachment: AttachmentQueueRow = { + id: resolvedId, + filename, + media_type: mediaType ?? null, + local_uri: localUri, + state: AttachmentState.QUEUED_UPLOAD, + has_synced: 0, + size, + timestamp: new Date().getTime(), + meta_data: metaData ?? null, + } + + /** + * We use the attachmentService lock to prevent attachment queue race conditions — specifically, + * it stops the watcher from treating a newly inserted attachment record as one that needs + * to be downloaded. + * */ + await this.withAttachmentContext(async (ctx) => { + const tanStackDBTransaction = createTransaction({ + autoCommit: false, + mutationFn: async ({ transaction }) => { + await new PowerSyncTransactor({ + database: ctx.db, + }).applyTransaction(transaction) + }, + }) + + tanStackDBTransaction.mutate(() => { + this.collection.insert(attachment) + // allow the user to associate values in this transaction + updateHook?.(attachment) + }) + + await tanStackDBTransaction.commit() + }) + + return attachment + } + + /** + * Queues a file for deletion from local and remote storage. + * + * Exposes an `updateHook` option which is called inside a TanStackDB transaction, + * relational associations with the provided attachment ID should be cleaned up in this hook. + */ + async deleteFileTanStack({ + id, + updateHook, + }: DeleteFileTanStackOptions): Promise { + await this.withAttachmentContext(async (ctx) => { + const tanStackDBTransaction = createTransaction({ + autoCommit: false, + mutationFn: async ({ transaction }) => { + await new PowerSyncTransactor({ + database: ctx.db, + }).applyTransaction(transaction) + }, + }) + + tanStackDBTransaction.mutate(() => { + const attachment = this.collection.get(id) + if (!attachment) { + throw new Error(`Attachment with id ${id} not found`) + } + + this.collection.update(id, (draft) => { + draft.state = AttachmentState.QUEUED_DELETE + draft.has_synced = 0 + }) + + // allow the user to associate values in this transaction + updateHook?.(attachment) + }) + + await tanStackDBTransaction.commit() + }) + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad174b46b5..c4562916df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1338,11 +1338,11 @@ importers: version: 4.0.1 devDependencies: '@powersync/common': - specifier: 1.49.0 - version: 1.49.0 + specifier: 1.54.0 + version: 1.54.0 '@powersync/node': specifier: 0.18.1 - version: 0.18.1(@powersync/common@1.49.0)(better-sqlite3@12.8.0) + version: 0.18.1(@powersync/common@1.54.0)(better-sqlite3@12.8.0) '@types/debug': specifier: ^4.1.12 version: 4.1.12 @@ -4831,8 +4831,8 @@ packages: '@poppinss/exception@1.2.3': resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} - '@powersync/common@1.49.0': - resolution: {integrity: sha512-g6uonubvtmtyx8hS/G5trg9LsBvzHY3tAKHiV7SIQV3Xyz9ONM6NNnjDMP2vcLZVmsOSi8x/QJZmy/ig1YtBMg==} + '@powersync/common@1.54.0': + resolution: {integrity: sha512-/gzitw4iQL4UI7ILf7TUzCy/cfbDJGU3/aiN/ciaLtDd2Uts3wYARVKclSW0OJhPPisKCX0E8Ev/iZGQPbTgDA==} '@powersync/node@0.18.1': resolution: {integrity: sha512-fcTICgs61CAEb39xiC7pedYsPgbjUInJ/47dr7RIdnEHpAgjWH8bW95/b70qK1fQUANy9lKBBF3PcmfswVgfCw==} @@ -9372,6 +9372,9 @@ packages: js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} + js-logger@1.6.1: + resolution: {integrity: sha512-yTgMCPXVjhmg28CuUH8CKjU+cIKL/G+zTu4Fn4lQxs8mRFH/03QTNvEFngcxfg/gRDiQAOoyCKmMTOm9ayOzXA==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -16480,14 +16483,14 @@ snapshots: '@poppinss/exception@1.2.3': {} - '@powersync/common@1.49.0': + '@powersync/common@1.54.0': dependencies: - async-mutex: 0.5.0 event-iterator: 2.0.0 + js-logger: 1.6.1 - '@powersync/node@0.18.1(@powersync/common@1.49.0)(better-sqlite3@12.8.0)': + '@powersync/node@0.18.1(@powersync/common@1.54.0)(better-sqlite3@12.8.0)': dependencies: - '@powersync/common': 1.49.0 + '@powersync/common': 1.54.0 async-mutex: 0.5.0 bson: 6.10.4 comlink: 4.4.2 @@ -22133,6 +22136,8 @@ snapshots: js-base64@3.7.8: {} + js-logger@1.6.1: {} + js-tokens@4.0.0: {} js-tokens@9.0.1: {} From d18ab196cd1b1c320b990e47fde5c8044d82082d Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Thu, 18 Jun 2026 16:13:35 +0200 Subject: [PATCH 03/11] Types and tests. --- packages/powersync-db-collection/package.json | 4 +- .../src/attachments.ts | 36 +- packages/powersync-db-collection/src/index.ts | 1 + .../tests/attachments.test.ts | 401 ++++++++++++++++++ pnpm-lock.yaml | 22 +- 5 files changed, 419 insertions(+), 45 deletions(-) create mode 100644 packages/powersync-db-collection/tests/attachments.test.ts diff --git a/packages/powersync-db-collection/package.json b/packages/powersync-db-collection/package.json index 66f2af4ae5..9374371824 100644 --- a/packages/powersync-db-collection/package.json +++ b/packages/powersync-db-collection/package.json @@ -59,10 +59,10 @@ "p-defer": "^4.0.1" }, "peerDependencies": { - "@powersync/common": "^1.54.0" + "@powersync/common": "^1.55.0" }, "devDependencies": { - "@powersync/common": "1.54.0", + "@powersync/common": "1.55.0", "@powersync/node": "0.18.1", "@types/debug": "^4.1.12", "@vitest/coverage-istanbul": "^3.2.4", diff --git a/packages/powersync-db-collection/src/attachments.ts b/packages/powersync-db-collection/src/attachments.ts index 9359abfc9e..c3c9d8f677 100644 --- a/packages/powersync-db-collection/src/attachments.ts +++ b/packages/powersync-db-collection/src/attachments.ts @@ -10,44 +10,22 @@ import { PowerSyncTransactor } from './PowerSyncTransactor' import type { AbstractPowerSyncDatabase, AttachmentData, - AttachmentErrorHandler, - ILogger, - LocalStorageAdapter, - RemoteStorageAdapter, - WatchedAttachmentItem, + AttachmentQueueOptions, } from '@powersync/common' import type { Collection } from '@tanstack/db' -type AttachmentQueueRow = (typeof _tmpSchema)['types']['attachments'] +export type AttachmentQueueRow = (typeof _tmpSchema)['types']['attachments'] -/** - * This extends the default AttachmentQueue constructor params - * FIXME(powersync) we should export this type from the common SDK. - */ -type TanStackDBAttachmentQueueOptions = { - db: AbstractPowerSyncDatabase +export type TanStackDBAttachmentQueueOptions = AttachmentQueueOptions & { /** * For TanStack, we want access to the synced TanStackDB collection. * In order to have the same relational data be set in a single transaction. * This also allows for joining both TanStackDB collections. */ - attachmentsCollection: Collection - remoteStorage: RemoteStorageAdapter - localStorage: LocalStorageAdapter - watchAttachments: ( - onUpdate: (attachment: Array) => Promise, - signal: AbortSignal, - ) => void - tableName?: string - logger?: ILogger - syncIntervalMs?: number - syncThrottleDuration?: number - downloadAttachments?: boolean - archivedCacheLimit?: number - errorHandler?: AttachmentErrorHandler + attachmentsCollection: Collection } -interface SaveFileTanStackOptions { +export interface SaveFileTanStackOptions { data: AttachmentData fileExtension: string mediaType?: string @@ -60,7 +38,7 @@ interface SaveFileTanStackOptions { updateHook?: (attachment: AttachmentQueueRow) => Promise } -interface DeleteFileTanStackOptions { +export interface DeleteFileTanStackOptions { id: string updateHook?: (attachment: AttachmentQueueRow) => Promise } @@ -74,7 +52,7 @@ const _tmpSchema = new Schema({ */ export class TanStackDBAttachmentQueue extends AttachmentQueue { readonly powersync: AbstractPowerSyncDatabase - readonly collection: Collection + readonly collection: Collection constructor(params: TanStackDBAttachmentQueueOptions) { super(params) diff --git a/packages/powersync-db-collection/src/index.ts b/packages/powersync-db-collection/src/index.ts index f8d0928056..f96a7a0ee4 100644 --- a/packages/powersync-db-collection/src/index.ts +++ b/packages/powersync-db-collection/src/index.ts @@ -1,3 +1,4 @@ +export * from './attachments' export * from './definitions' export * from './powersync' export * from './PowerSyncTransactor' diff --git a/packages/powersync-db-collection/tests/attachments.test.ts b/packages/powersync-db-collection/tests/attachments.test.ts new file mode 100644 index 0000000000..411c7cd753 --- /dev/null +++ b/packages/powersync-db-collection/tests/attachments.test.ts @@ -0,0 +1,401 @@ +import { randomUUID } from 'node:crypto' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { + AttachmentState, + AttachmentTable, + Schema, + Table, + column, +} from '@powersync/common' +import { NodeFileSystemAdapter, PowerSyncDatabase } from '@powersync/node' +import { + createCollection, + isNull, + liveQueryCollectionOptions, + not, +} from '@tanstack/db' +import { describe, expect, it, onTestFinished, vi } from 'vitest' +import { powerSyncCollectionOptions } from '../src' +import { TanStackDBAttachmentQueue } from '../src/attachments' +import { TEST_DATABASE_IMPLEMENTATION } from './test-db-implementation' +import type { + AttachmentErrorHandler, + RemoteStorageAdapter, + WatchedAttachmentItem, +} from '@powersync/common' + +// A minimal valid 1x1 pixel JPEG used as the remote payload for downloads. +const MOCK_JPEG_U8A = [ + 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xff, 0xd9, +] +const createMockJpegBuffer = (): ArrayBuffer => + new Uint8Array(MOCK_JPEG_U8A).buffer + +const SYNC_INTERVAL_MS = 300 +const WAIT_TIMEOUT = 8000 + +const APP_SCHEMA = new Schema({ + users: new Table({ + name: column.text, + email: column.text, + photo_id: column.text, + }), + attachments: new AttachmentTable(), +}) + +type WatchAttachments = ( + onUpdate: (attachments: Array) => Promise, + signal: AbortSignal, +) => void + +const describePowerSync = TEST_DATABASE_IMPLEMENTATION + ? describe + : describe.skip + +describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { + async function setup() { + const db = new PowerSyncDatabase({ + database: { + dbFilename: `attachments-test-${randomUUID()}.sqlite`, + dbLocation: tmpdir(), + implementation: TEST_DATABASE_IMPLEMENTATION, + }, + schema: APP_SCHEMA, + }) + await db.disconnectAndClear() + + const localStorage = new NodeFileSystemAdapter( + join(tmpdir(), `ps-attachments-${randomUUID()}`), + ) + await localStorage.initialize() + + const uploadFile = vi.fn(() => + Promise.resolve(), + ) + const downloadFile = vi.fn(() => + Promise.resolve(createMockJpegBuffer()), + ) + const deleteFile = vi.fn(() => + Promise.resolve(), + ) + const remoteStorage: RemoteStorageAdapter = { + uploadFile, + downloadFile, + deleteFile, + } + + const attachmentsCollection = createCollection( + powerSyncCollectionOptions({ + database: db, + table: APP_SCHEMA.props.attachments, + }), + ) + const usersCollection = createCollection( + powerSyncCollectionOptions({ + database: db, + table: APP_SCHEMA.props.users, + }), + ) + await Promise.all([ + attachmentsCollection.stateWhenReady(), + usersCollection.stateWhenReady(), + ]) + + onTestFinished(async () => { + attachmentsCollection.cleanup() + usersCollection.cleanup() + await db.disconnectAndClear() + await db.close() + await localStorage.clear().catch(() => {}) + }) + + function createQueue( + overrides: { + watchAttachments?: WatchAttachments + archivedCacheLimit?: number + errorHandler?: AttachmentErrorHandler + remoteStorage?: RemoteStorageAdapter + } = {}, + ) { + const queue = new TanStackDBAttachmentQueue({ + db, + attachmentsCollection, + remoteStorage: overrides.remoteStorage ?? remoteStorage, + localStorage, + watchAttachments: overrides.watchAttachments ?? watchPhotoIds, + syncIntervalMs: SYNC_INTERVAL_MS, + archivedCacheLimit: overrides.archivedCacheLimit ?? 0, + errorHandler: overrides.errorHandler, + }) + onTestFinished(() => queue.stopSync()) + return queue + } + + // Reports every photo_id referenced by the users collection as a watched + // attachment. This mirrors how an application links its domain model to the + // attachment queue using a TanStack DB live query rather than a raw SQL + // watch: the `photo_id IS NOT NULL` filter lives in the query, and each + // change re-emits the full set of referenced ids. + const watchPhotoIdsWith = ( + toItem: (photoId: string) => WatchedAttachmentItem, + ): WatchAttachments => { + return async (onUpdate, signal) => { + const livePhotoIds = createCollection( + liveQueryCollectionOptions({ + query: (q) => + q + .from({ user: usersCollection }) + .where(({ user }) => not(isNull(user.photo_id))) + .select(({ user }) => ({ photo_id: user.photo_id })), + }), + ) + + const emit = () => + void onUpdate( + livePhotoIds.toArray + .map((row) => row.photo_id) + .filter((photoId): photoId is string => photoId != null) + .map(toItem), + ) + + // Emit the current snapshot once ready, then on every change. + await livePhotoIds.stateWhenReady() + emit() + const subscription = livePhotoIds.subscribeChanges(() => emit()) + + signal.addEventListener(`abort`, () => { + subscription.unsubscribe() + livePhotoIds.cleanup() + }) + } + } + + const watchPhotoIds = watchPhotoIdsWith((id) => ({ + id, + fileExtension: `jpg`, + })) + + return { + db, + localStorage, + remoteStorage, + uploadFile, + downloadFile, + deleteFile, + attachmentsCollection, + usersCollection, + createQueue, + watchPhotoIds, + watchPhotoIdsWith, + } + } + + /** Waits until the attachment with `id` reaches the expected state. */ + function waitForState( + collection: { get: (id: string) => TRow | undefined }, + id: string, + state: AttachmentState, + ): Promise { + return vi.waitFor( + () => { + const attachment = collection.get(id) + expect( + (attachment as { state?: AttachmentState } | undefined)?.state, + ).toBe(state) + return attachment! + }, + { timeout: WAIT_TIMEOUT, interval: 50 }, + ) + } + + describe(`saveFileTanStack`, () => { + it(`writes the local file and inserts a QUEUED_UPLOAD row into the collection`, async () => { + const { createQueue, attachmentsCollection, localStorage } = await setup() + const queue = createQueue() + + const data = new Uint8Array(123).fill(42).buffer + const record = await queue.saveFileTanStack({ + data, + fileExtension: `jpg`, + mediaType: `image/jpeg`, + }) + + expect(record.size).toBe(123) + expect(record.state).toBe(AttachmentState.QUEUED_UPLOAD) + expect(record.media_type).toBe(`image/jpeg`) + expect(record.filename).toBe(`${record.id}.jpg`) + expect(record.has_synced).toBe(0) + + // The file should exist on disk at the returned local_uri. + expect(await localStorage.fileExists(record.local_uri)).toBe(true) + + // The row should be reflected in the collection once it syncs back. + await waitForState( + attachmentsCollection, + record.id, + AttachmentState.QUEUED_UPLOAD, + ) + }) + + it(`commits the updateHook mutation atomically with the attachment row`, async () => { + const { createQueue, attachmentsCollection, usersCollection } = + await setup() + const queue = createQueue() + + const userId = randomUUID() + const record = await queue.saveFileTanStack({ + data: createMockJpegBuffer(), + fileExtension: `jpg`, + updateHook: async (attachment) => { + usersCollection.insert({ + id: userId, + name: `steven`, + email: `steven@journeyapps.com`, + photo_id: attachment.id, + }) + }, + }) + + // Both the attachment and the linked user row should appear together. + await waitForState( + attachmentsCollection, + record.id, + AttachmentState.QUEUED_UPLOAD, + ) + await vi.waitFor( + () => { + const user = usersCollection.get(userId) + expect(user?.photo_id).toBe(record.id) + }, + { timeout: WAIT_TIMEOUT, interval: 50 }, + ) + }) + + it(`uploads the saved file and transitions it to SYNCED`, async () => { + const { + createQueue, + attachmentsCollection, + usersCollection, + uploadFile, + } = await setup() + const queue = createQueue() + await queue.startSync() + + const userId = randomUUID() + const record = await queue.saveFileTanStack({ + data: createMockJpegBuffer(), + fileExtension: `jpg`, + updateHook: async (attachment) => { + usersCollection.insert({ + id: userId, + name: `steven`, + email: `steven@journeyapps.com`, + photo_id: attachment.id, + }) + }, + }) + + await waitForState( + attachmentsCollection, + record.id, + AttachmentState.SYNCED, + ) + + expect(uploadFile).toHaveBeenCalled() + const [, uploadedAttachment] = uploadFile.mock.calls[0]! + expect(uploadedAttachment.id).toBe(record.id) + }) + + it(`honours a caller-supplied id`, async () => { + const { createQueue } = await setup() + const queue = createQueue() + + const id = `my-custom-id` + const record = await queue.saveFileTanStack({ + id, + data: createMockJpegBuffer(), + fileExtension: `png`, + }) + + expect(record.id).toBe(id) + expect(record.filename).toBe(`${id}.png`) + }) + }) + + describe(`deleteFileTanStack`, () => { + it(`queues an existing attachment for deletion and removes the local file`, async () => { + const { + createQueue, + attachmentsCollection, + usersCollection, + localStorage, + } = await setup() + const queue = createQueue() + await queue.startSync() + + const userId = randomUUID() + const record = await queue.saveFileTanStack({ + data: createMockJpegBuffer(), + fileExtension: `jpg`, + updateHook: async (attachment) => { + usersCollection.insert({ + id: userId, + name: `steven`, + email: `steven@journeyapps.com`, + photo_id: attachment.id, + }) + }, + }) + + await waitForState( + attachmentsCollection, + record.id, + AttachmentState.SYNCED, + ) + + await queue.deleteFileTanStack({ + id: record.id, + updateHook: async (attachment) => { + usersCollection.update(userId, (draft) => { + if (draft.photo_id === attachment.id) { + draft.photo_id = null + } + }) + }, + }) + + // It should immediately be marked for deletion (and no longer synced). + const queued = attachmentsCollection.get(record.id) + expect(queued?.state).toBe(AttachmentState.QUEUED_DELETE) + expect(queued?.has_synced).toBe(0) + + // The user reference should have been cleared in the same transaction. + expect(usersCollection.get(userId)?.photo_id).toBeNull() + + // Eventually the row and the local file are removed. + await vi.waitFor( + () => expect(attachmentsCollection.get(record.id)).toBeUndefined(), + { timeout: WAIT_TIMEOUT, interval: 50 }, + ) + expect(await localStorage.fileExists(record.local_uri)).toBe(false) + }) + + it(`throws for an unknown id and commits nothing`, async () => { + const { createQueue, attachmentsCollection, usersCollection } = + await setup() + const queue = createQueue() + + const hook = vi.fn() + await expect( + queue.deleteFileTanStack({ id: `does-not-exist`, updateHook: hook }), + ).rejects.toThrow(/not found/i) + + // The failing transaction must not have run the hook or touched state. + expect(hook).not.toHaveBeenCalled() + expect(attachmentsCollection.get(`does-not-exist`)).toBeUndefined() + expect(usersCollection.size).toBe(0) + }) + }) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c4562916df..3852ece3ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1338,11 +1338,11 @@ importers: version: 4.0.1 devDependencies: '@powersync/common': - specifier: 1.54.0 - version: 1.54.0 + specifier: 1.55.0 + version: 1.55.0 '@powersync/node': specifier: 0.18.1 - version: 0.18.1(@powersync/common@1.54.0)(better-sqlite3@12.8.0) + version: 0.18.1(@powersync/common@1.55.0)(better-sqlite3@12.8.0) '@types/debug': specifier: ^4.1.12 version: 4.1.12 @@ -4831,8 +4831,8 @@ packages: '@poppinss/exception@1.2.3': resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} - '@powersync/common@1.54.0': - resolution: {integrity: sha512-/gzitw4iQL4UI7ILf7TUzCy/cfbDJGU3/aiN/ciaLtDd2Uts3wYARVKclSW0OJhPPisKCX0E8Ev/iZGQPbTgDA==} + '@powersync/common@1.55.0': + resolution: {integrity: sha512-c9K2Gac9wOB4ijVnQT388g7Yeuh6VrfJZFXaL6Rag9Fp7F8JzAYcrWaULLTdMnzm7VW6b6XT5M2ip5yLS8oSBg==} '@powersync/node@0.18.1': resolution: {integrity: sha512-fcTICgs61CAEb39xiC7pedYsPgbjUInJ/47dr7RIdnEHpAgjWH8bW95/b70qK1fQUANy9lKBBF3PcmfswVgfCw==} @@ -8173,9 +8173,6 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - event-iterator@2.0.0: - resolution: {integrity: sha512-KGft0ldl31BZVV//jj+IAIGCxkvvUkkON+ScH6zfoX+l+omX6001ggyRSpI0Io2Hlro0ThXotswCtfzS8UkIiQ==} - event-reduce-js@5.2.7: resolution: {integrity: sha512-Vi6aIiAmakzx81JAwhw8L988aSX5a3ZqqVjHyZa9xFU6P4oT1IotoDreWtjNlS+fvEnASvyIQT565nmkOtns/Q==} engines: {node: '>=16'} @@ -16483,14 +16480,13 @@ snapshots: '@poppinss/exception@1.2.3': {} - '@powersync/common@1.54.0': + '@powersync/common@1.55.0': dependencies: - event-iterator: 2.0.0 js-logger: 1.6.1 - '@powersync/node@0.18.1(@powersync/common@1.54.0)(better-sqlite3@12.8.0)': + '@powersync/node@0.18.1(@powersync/common@1.55.0)(better-sqlite3@12.8.0)': dependencies: - '@powersync/common': 1.54.0 + '@powersync/common': 1.55.0 async-mutex: 0.5.0 bson: 6.10.4 comlink: 4.4.2 @@ -20580,8 +20576,6 @@ snapshots: etag@1.8.1: {} - event-iterator@2.0.0: {} - event-reduce-js@5.2.7: dependencies: array-push-at-sort-position: 4.0.1 From 2147345236ceee6e73d9fc6c0cdc2385833199fc Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Thu, 18 Jun 2026 11:42:39 -0600 Subject: [PATCH 04/11] Export and document subtract, multiply, divide math functions (#1151) * feat(db): add subtract, multiply, divide math functions Add missing math functions that were implemented in evaluators but not exported. These enable computed columns in orderBy for ranking algorithms like HN-style scoring that balances recency and rating. - Add subtract(a, b) function - Add multiply(a, b) function - Add divide(a, b) function (with null on divide-by-zero) - Export from query/index.ts - Add to operators list - Add comprehensive tests including orderBy usage * docs: document subtract, multiply, divide math functions - Add documentation for new math functions in live-queries.md - Include example of computed columns in orderBy for ranking algorithms - Add changeset for the new minor feature * ci: apply automated fixes * fix: align math function return types * ci: apply automated fixes * docs: clarify ranking snapshot semantics --------- Co-authored-by: Claude Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .changeset/add-math-functions.md | 26 +++++ docs/guides/live-queries.md | 48 ++++++++++ packages/db/src/query/builder/functions.ts | 68 ++++++++------ packages/db/src/query/index.ts | 3 + .../db/tests/query/builder/functions.test.ts | 94 ++++++++++++++++++- 5 files changed, 211 insertions(+), 28 deletions(-) create mode 100644 .changeset/add-math-functions.md diff --git a/.changeset/add-math-functions.md b/.changeset/add-math-functions.md new file mode 100644 index 0000000000..c4e70d0c5d --- /dev/null +++ b/.changeset/add-math-functions.md @@ -0,0 +1,26 @@ +--- +'@tanstack/db': patch +--- + +Add `subtract`, `multiply`, and `divide` math functions for computed columns + +These functions enable complex calculations in `select` and `orderBy` clauses, such as ranking algorithms that combine multiple factors (e.g., HN-style scoring that balances recency and rating). + +```ts +import { subtract, multiply, divide } from '@tanstack/db' + +// Example: Sort by computed ranking score +const ranked = createLiveQueryCollection((q) => + q + .from({ r: recipesCollection }) + .orderBy( + ({ r }) => + subtract(multiply(r.rating, r.timesMade), divide(r.ageInMs, 86400000)), + 'desc', + ), +) +``` + +- `subtract(a, b)` - Subtraction +- `multiply(a, b)` - Multiplication +- `divide(a, b)` - Division (returns `null` on divide-by-zero) diff --git a/docs/guides/live-queries.md b/docs/guides/live-queries.md index 0780871a3b..988628261a 100644 --- a/docs/guides/live-queries.md +++ b/docs/guides/live-queries.md @@ -2551,6 +2551,54 @@ Add two numbers: add(user.salary, user.bonus) ``` +#### `subtract(left, right)` +Subtract two numbers: +```ts +subtract(user.salary, user.deductions) +``` + +#### `multiply(left, right)` +Multiply two numbers: +```ts +multiply(item.price, item.quantity) +``` + +#### `divide(left, right)` +Divide two numbers (returns `null` on divide-by-zero): +```ts +divide(order.total, order.itemCount) +``` + +#### Computed Columns in orderBy + +You can use math functions directly in `orderBy` to sort by computed values. This is useful for ranking algorithms that combine multiple factors: + +```ts +import { subtract, multiply, divide } from '@tanstack/db' + +// HN-style ranking: balance rating with recency +// Date.now() is captured when this query is created. Recreate the query if +// you need the recency score to advance as time passes. +const rankedRecipes = createLiveQueryCollection((q) => + q + .from({ r: recipesCollection }) + .orderBy( + ({ r }) => + subtract( + multiply(r.rating, r.timesMade), // weighted rating + divide( + subtract(Date.now(), r.lastMadeAt), // time since last made + 3600000 * 24 // convert ms to days + ) + ), + 'desc' + ) + .limit(20) +) +``` + +> **Note:** When using computed expressions in `orderBy` with `limit()`, lazy loading optimization is skipped (all matching data is loaded first, then sorted). For large collections where this matters, consider pre-computing the ranking score as a stored field. + ### Utility Functions #### `coalesce(...values)` diff --git a/packages/db/src/query/builder/functions.ts b/packages/db/src/query/builder/functions.ts index 47e190162a..84c26e5ca4 100644 --- a/packages/db/src/query/builder/functions.ts +++ b/packages/db/src/query/builder/functions.ts @@ -125,31 +125,12 @@ type MapToNumber = T extends string | Array ? null : T -// Helper type for binary numeric operations (combines nullability of both operands) -type BinaryNumericReturnType = - ExtractType extends infer U1 - ? ExtractType extends infer U2 - ? U1 extends number - ? U2 extends number - ? BasicExpression - : U2 extends number | undefined - ? BasicExpression - : U2 extends number | null - ? BasicExpression - : BasicExpression - : U1 extends number | undefined - ? U2 extends number - ? BasicExpression - : U2 extends number | undefined - ? BasicExpression - : BasicExpression - : U1 extends number | null - ? U2 extends number - ? BasicExpression - : BasicExpression - : BasicExpression - : BasicExpression - : BasicExpression +// Helper type for binary numeric operations. +// Runtime coalesces nullish operands to 0 for these operations, so nullable +// operands don't make the result nullable. +type BinaryNumericReturnType = BasicExpression + +type DivideReturnType = BasicExpression // Operators @@ -620,11 +601,41 @@ export function caseWhen(...args: Array): any { export function add( left: T1, right: T2, -): BinaryNumericReturnType { +): BinaryNumericReturnType { return new Func(`add`, [ toExpression(left), toExpression(right), - ]) as BinaryNumericReturnType + ]) as BinaryNumericReturnType +} + +export function subtract( + left: T1, + right: T2, +): BinaryNumericReturnType { + return new Func(`subtract`, [ + toExpression(left), + toExpression(right), + ]) as BinaryNumericReturnType +} + +export function multiply( + left: T1, + right: T2, +): BinaryNumericReturnType { + return new Func(`multiply`, [ + toExpression(left), + toExpression(right), + ]) as BinaryNumericReturnType +} + +export function divide( + left: T1, + right: T2, +): DivideReturnType { + return new Func(`divide`, [ + toExpression(left), + toExpression(right), + ]) as DivideReturnType } // Aggregates @@ -690,6 +701,9 @@ export const operators = [ `concat`, // Numeric functions `add`, + `subtract`, + `multiply`, + `divide`, // Utility functions `coalesce`, `caseWhen`, diff --git a/packages/db/src/query/index.ts b/packages/db/src/query/index.ts index e23475bc25..8cda812b3f 100644 --- a/packages/db/src/query/index.ts +++ b/packages/db/src/query/index.ts @@ -59,6 +59,9 @@ export { coalesce, caseWhen, add, + subtract, + multiply, + divide, // Aggregates count, avg, diff --git a/packages/db/tests/query/builder/functions.test.ts b/packages/db/tests/query/builder/functions.test.ts index cd40ce2e4c..e4d284960b 100644 --- a/packages/db/tests/query/builder/functions.test.ts +++ b/packages/db/tests/query/builder/functions.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from 'vitest' +import { describe, expect, expectTypeOf, it } from 'vitest' import { CollectionImpl } from '../../../src/collection/index.js' import { Query, getQueryIR } from '../../../src/query/builder/index.js' import { @@ -12,6 +12,7 @@ import { coalesce, concat, count, + divide, eq, gt, gte, @@ -23,12 +24,16 @@ import { lte, max, min, + multiply, not, or, + subtract, sum, toArray, upper, } from '../../../src/query/builder/functions.js' +import { compileSingleRowExpression } from '../../../src/query/compiler/evaluators.js' +import type { BasicExpression } from '../../../src/query/ir.js' // Test schema interface Employee { @@ -324,5 +329,92 @@ describe(`QueryBuilder Functions`, () => { const select = builtQuery.select! expect((select.salary_plus_bonus as any).name).toBe(`add`) }) + + it(`subtract function works`, () => { + const query = new Query() + .from({ employees: employeesCollection }) + .select(({ employees }) => ({ + id: employees.id, + salary_minus_tax: subtract(employees.salary, 5000), + })) + + const builtQuery = getQueryIR(query) + const select = builtQuery.select! + expect((select.salary_minus_tax as any).name).toBe(`subtract`) + }) + + it(`multiply function works`, () => { + const query = new Query() + .from({ employees: employeesCollection }) + .select(({ employees }) => ({ + id: employees.id, + double_salary: multiply(employees.salary, 2), + })) + + const builtQuery = getQueryIR(query) + const select = builtQuery.select! + expect((select.double_salary as any).name).toBe(`multiply`) + }) + + it(`divide function works`, () => { + const query = new Query() + .from({ employees: employeesCollection }) + .select(({ employees }) => ({ + id: employees.id, + monthly_salary: divide(employees.salary, 12), + })) + + const builtQuery = getQueryIR(query) + const select = builtQuery.select! + expect((select.monthly_salary as any).name).toBe(`divide`) + }) + + it(`math functions can be combined for complex calculations`, () => { + const query = new Query() + .from({ employees: employeesCollection }) + .select(({ employees }) => ({ + id: employees.id, + // (salary * 1.1) - 500 = 10% raise minus deductions + adjusted_salary: subtract(multiply(employees.salary, 1.1), 500), + })) + + const builtQuery = getQueryIR(query) + const select = builtQuery.select! + expect((select.adjusted_salary as any).name).toBe(`subtract`) + }) + + it(`RED review: nullish operands are coalesced to 0 at runtime but widen types`, () => { + const subtractExpression = subtract(10, null) + expectTypeOf(subtractExpression).toEqualTypeOf>() + + const subtractResult = compileSingleRowExpression(subtractExpression)({}) + expect(subtractResult).toBe(10) + }) + + it(`RED review: divide can return null for non-null operand types`, () => { + const divideExpression = divide(10, 0) + expectTypeOf(divideExpression).toEqualTypeOf< + BasicExpression + >() + + const divideResult = compileSingleRowExpression(divideExpression)({}) + expect(divideResult).toBeNull() + }) + + it(`math functions can be used in orderBy`, () => { + const query = new Query() + .from({ employees: employeesCollection }) + .orderBy(({ employees }) => multiply(employees.salary, 2), `desc`) + .select(({ employees }) => ({ + id: employees.id, + salary: employees.salary, + })) + + const builtQuery = getQueryIR(query) + expect(builtQuery.orderBy).toBeDefined() + expect(builtQuery.orderBy).toHaveLength(1) + expect((builtQuery.orderBy![0]!.expression as any).name).toBe(`multiply`) + expect(builtQuery.orderBy![0]!.compareOptions.direction).toBe(`desc`) + }) }) }) From 9a5e8e5445435c59ede20d3080d09b95355a67dd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:31:28 -0600 Subject: [PATCH 05/11] ci: Version Packages (#1596) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/add-math-functions.md | 26 ------- .changeset/safe-random-uuid.md | 9 --- examples/angular/todos/package.json | 4 +- examples/electron/offline-first/package.json | 10 +-- .../offline-transactions/package.json | 10 +-- .../react-native/shopping-list/package.json | 10 +-- .../react/offline-transactions/package.json | 10 +-- .../react/paced-mutations-demo/package.json | 4 +- examples/react/projects/package.json | 4 +- examples/react/todo/package.json | 8 +-- examples/solid/todo/package.json | 8 +-- packages/angular-db/CHANGELOG.md | 7 ++ packages/angular-db/package.json | 2 +- .../CHANGELOG.md | 9 +++ .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../db-sqlite-persistence-core/CHANGELOG.md | 9 +++ .../db-sqlite-persistence-core/package.json | 2 +- packages/db/CHANGELOG.md | 32 +++++++++ packages/db/package.json | 2 +- packages/electric-db-collection/CHANGELOG.md | 7 ++ packages/electric-db-collection/package.json | 2 +- .../CHANGELOG.md | 9 +++ .../package.json | 2 +- .../expo-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/expo-runtime-app/CHANGELOG.md | 8 +++ .../e2e/expo-runtime-app/package.json | 2 +- .../expo-db-sqlite-persistence/package.json | 2 +- .../node-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../node-db-sqlite-persistence/package.json | 2 +- packages/offline-transactions/CHANGELOG.md | 9 +++ packages/offline-transactions/package.json | 2 +- packages/powersync-db-collection/CHANGELOG.md | 7 ++ packages/powersync-db-collection/package.json | 2 +- packages/query-db-collection/CHANGELOG.md | 7 ++ packages/query-db-collection/package.json | 2 +- packages/react-db/CHANGELOG.md | 7 ++ packages/react-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- packages/rxdb-db-collection/CHANGELOG.md | 7 ++ packages/rxdb-db-collection/package.json | 2 +- packages/solid-db/CHANGELOG.md | 7 ++ packages/solid-db/package.json | 2 +- packages/svelte-db/CHANGELOG.md | 7 ++ packages/svelte-db/package.json | 2 +- .../tauri-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../tauri-db-sqlite-persistence/package.json | 2 +- packages/trailbase-db-collection/CHANGELOG.md | 7 ++ packages/trailbase-db-collection/package.json | 2 +- packages/vue-db/CHANGELOG.md | 7 ++ packages/vue-db/package.json | 2 +- pnpm-lock.yaml | 68 +++++++++---------- 60 files changed, 296 insertions(+), 127 deletions(-) delete mode 100644 .changeset/add-math-functions.md delete mode 100644 .changeset/safe-random-uuid.md diff --git a/.changeset/add-math-functions.md b/.changeset/add-math-functions.md deleted file mode 100644 index c4e70d0c5d..0000000000 --- a/.changeset/add-math-functions.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -'@tanstack/db': patch ---- - -Add `subtract`, `multiply`, and `divide` math functions for computed columns - -These functions enable complex calculations in `select` and `orderBy` clauses, such as ranking algorithms that combine multiple factors (e.g., HN-style scoring that balances recency and rating). - -```ts -import { subtract, multiply, divide } from '@tanstack/db' - -// Example: Sort by computed ranking score -const ranked = createLiveQueryCollection((q) => - q - .from({ r: recipesCollection }) - .orderBy( - ({ r }) => - subtract(multiply(r.rating, r.timesMade), divide(r.ageInMs, 86400000)), - 'desc', - ), -) -``` - -- `subtract(a, b)` - Subtraction -- `multiply(a, b)` - Multiplication -- `divide(a, b)` - Division (returns `null` on divide-by-zero) diff --git a/.changeset/safe-random-uuid.md b/.changeset/safe-random-uuid.md deleted file mode 100644 index a10264d535..0000000000 --- a/.changeset/safe-random-uuid.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@tanstack/db': patch -'@tanstack/browser-db-sqlite-persistence': patch -'@tanstack/offline-transactions': patch -'@tanstack/db-sqlite-persistence-core': patch -'@tanstack/electron-db-sqlite-persistence': patch ---- - -Use a safe `randomUUID` helper that falls back to `crypto.getRandomValues` when `crypto.randomUUID` is unavailable (non-secure browser contexts such as dev servers reached via a LAN IP over HTTP). Fixes #1541. diff --git a/examples/angular/todos/package.json b/examples/angular/todos/package.json index fd4d3ac679..b4c64bfacc 100644 --- a/examples/angular/todos/package.json +++ b/examples/angular/todos/package.json @@ -28,8 +28,8 @@ "@angular/forms": "^20.3.16", "@angular/platform-browser": "^20.3.16", "@angular/router": "^20.3.16", - "@tanstack/angular-db": "^0.1.68", - "@tanstack/db": "^0.6.8", + "@tanstack/angular-db": "^0.1.69", + "@tanstack/db": "^0.6.9", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "~0.15.0" diff --git a/examples/electron/offline-first/package.json b/examples/electron/offline-first/package.json index b15ad88932..ebe72a04c5 100644 --- a/examples/electron/offline-first/package.json +++ b/examples/electron/offline-first/package.json @@ -13,11 +13,11 @@ "postinstall": "prebuild-install --runtime electron --target 40.2.1 --arch arm64 || echo 'prebuild-install failed, try: npx @electron/rebuild'" }, "dependencies": { - "@tanstack/electron-db-sqlite-persistence": "^0.1.12", - "@tanstack/node-db-sqlite-persistence": "^0.2.0", - "@tanstack/offline-transactions": "^1.0.33", - "@tanstack/query-db-collection": "^1.0.40", - "@tanstack/react-db": "^0.1.86", + "@tanstack/electron-db-sqlite-persistence": "^0.1.13", + "@tanstack/node-db-sqlite-persistence": "^0.2.1", + "@tanstack/offline-transactions": "^1.0.34", + "@tanstack/query-db-collection": "^1.0.41", + "@tanstack/react-db": "^0.1.87", "@tanstack/react-query": "^5.90.20", "better-sqlite3": "^12.6.2", "react": "^19.2.4", diff --git a/examples/react-native/offline-transactions/package.json b/examples/react-native/offline-transactions/package.json index 6274e7353c..82b07e3c31 100644 --- a/examples/react-native/offline-transactions/package.json +++ b/examples/react-native/offline-transactions/package.json @@ -15,11 +15,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.8", - "@tanstack/offline-transactions": "^1.0.33", - "@tanstack/query-db-collection": "^1.0.40", - "@tanstack/react-db": "^0.1.86", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.0", + "@tanstack/db": "^0.6.9", + "@tanstack/offline-transactions": "^1.0.34", + "@tanstack/query-db-collection": "^1.0.41", + "@tanstack/react-db": "^0.1.87", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.1", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react-native/shopping-list/package.json b/examples/react-native/shopping-list/package.json index be14aed669..8db8659ca3 100644 --- a/examples/react-native/shopping-list/package.json +++ b/examples/react-native/shopping-list/package.json @@ -18,11 +18,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.8", - "@tanstack/electric-db-collection": "^0.3.6", - "@tanstack/offline-transactions": "^1.0.33", - "@tanstack/react-db": "^0.1.86", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.0", + "@tanstack/db": "^0.6.9", + "@tanstack/electric-db-collection": "^0.3.7", + "@tanstack/offline-transactions": "^1.0.34", + "@tanstack/react-db": "^0.1.87", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.1", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react/offline-transactions/package.json b/examples/react/offline-transactions/package.json index 05ce1caea1..eb798b2b86 100644 --- a/examples/react/offline-transactions/package.json +++ b/examples/react/offline-transactions/package.json @@ -8,11 +8,11 @@ "build": "vite build && tsc --noEmit" }, "dependencies": { - "@tanstack/browser-db-sqlite-persistence": "^0.2.0", - "@tanstack/db": "^0.6.8", - "@tanstack/offline-transactions": "^1.0.33", - "@tanstack/query-db-collection": "^1.0.40", - "@tanstack/react-db": "^0.1.86", + "@tanstack/browser-db-sqlite-persistence": "^0.2.1", + "@tanstack/db": "^0.6.9", + "@tanstack/offline-transactions": "^1.0.34", + "@tanstack/query-db-collection": "^1.0.41", + "@tanstack/react-db": "^0.1.87", "@tanstack/react-query": "^5.90.20", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", diff --git a/examples/react/paced-mutations-demo/package.json b/examples/react/paced-mutations-demo/package.json index 02a929269c..290ed30adc 100644 --- a/examples/react/paced-mutations-demo/package.json +++ b/examples/react/paced-mutations-demo/package.json @@ -9,8 +9,8 @@ "preview": "vite preview" }, "dependencies": { - "@tanstack/db": "^0.6.8", - "@tanstack/react-db": "^0.1.86", + "@tanstack/db": "^0.6.9", + "@tanstack/react-db": "^0.1.87", "mitt": "^3.0.1", "react": "^19.2.4", "react-dom": "^19.2.4" diff --git a/examples/react/projects/package.json b/examples/react/projects/package.json index 1f0178c52e..2f68958d4d 100644 --- a/examples/react/projects/package.json +++ b/examples/react/projects/package.json @@ -17,8 +17,8 @@ "dependencies": { "@tailwindcss/vite": "^4.1.18", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.40", - "@tanstack/react-db": "^0.1.86", + "@tanstack/query-db-collection": "^1.0.41", + "@tanstack/react-db": "^0.1.87", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", "@tanstack/react-router-with-query": "^1.130.17", diff --git a/examples/react/todo/package.json b/examples/react/todo/package.json index a656ae38ed..9145e3ff5f 100644 --- a/examples/react/todo/package.json +++ b/examples/react/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.1.25", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.6", + "@tanstack/electric-db-collection": "^0.3.7", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.40", - "@tanstack/react-db": "^0.1.86", + "@tanstack/query-db-collection": "^1.0.41", + "@tanstack/react-db": "^0.1.87", "@tanstack/react-router": "^1.159.5", "@tanstack/react-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.86", + "@tanstack/trailbase-db-collection": "^0.1.87", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/examples/solid/todo/package.json b/examples/solid/todo/package.json index 215c98e075..efdec9ece8 100644 --- a/examples/solid/todo/package.json +++ b/examples/solid/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.0.35", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.6", + "@tanstack/electric-db-collection": "^0.3.7", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.40", - "@tanstack/solid-db": "^0.2.22", + "@tanstack/query-db-collection": "^1.0.41", + "@tanstack/solid-db": "^0.2.23", "@tanstack/solid-router": "^1.159.5", "@tanstack/solid-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.86", + "@tanstack/trailbase-db-collection": "^0.1.87", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/packages/angular-db/CHANGELOG.md b/packages/angular-db/CHANGELOG.md index 10a322326a..cc5e8b4e34 100644 --- a/packages/angular-db/CHANGELOG.md +++ b/packages/angular-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/angular-db +## 0.1.69 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.1.68 ### Patch Changes diff --git a/packages/angular-db/package.json b/packages/angular-db/package.json index e8ee4b5620..3e7a47e0c9 100644 --- a/packages/angular-db/package.json +++ b/packages/angular-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/angular-db", - "version": "0.1.68", + "version": "0.1.69", "description": "Angular integration for @tanstack/db", "author": "Ethan McDaniel", "license": "MIT", diff --git a/packages/browser-db-sqlite-persistence/CHANGELOG.md b/packages/browser-db-sqlite-persistence/CHANGELOG.md index fafcb0bb1f..5dcf408756 100644 --- a/packages/browser-db-sqlite-persistence/CHANGELOG.md +++ b/packages/browser-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,14 @@ # @tanstack/browser-db-sqlite-persistence +## 0.2.1 + +### Patch Changes + +- Use a safe `randomUUID` helper that falls back to `crypto.getRandomValues` when `crypto.randomUUID` is unavailable (non-secure browser contexts such as dev servers reached via a LAN IP over HTTP). Fixes #1541. ([#1593](https://github.com/TanStack/db/pull/1593)) + +- Updated dependencies [[`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db-sqlite-persistence-core@0.2.1 + ## 0.2.0 ### Minor Changes diff --git a/packages/browser-db-sqlite-persistence/package.json b/packages/browser-db-sqlite-persistence/package.json index 8e94ebedf9..30065efe96 100644 --- a/packages/browser-db-sqlite-persistence/package.json +++ b/packages/browser-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/browser-db-sqlite-persistence", - "version": "0.2.0", + "version": "0.2.1", "description": "Browser wa-sqlite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md index 127512e3aa..2c238a9a83 100644 --- a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/capacitor-db-sqlite-persistence +## 0.2.1 + +### Patch Changes + +- Updated dependencies [[`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db-sqlite-persistence-core@0.2.1 + ## 0.2.0 ### Minor Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md index 118201f804..fa47e3e79f 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/capacitor-db-sqlite-persistence-e2e-app +## 0.0.13 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + - @tanstack/capacitor-db-sqlite-persistence@0.2.1 + ## 0.0.12 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json index 7c8cacdf76..90872c4797 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.12", + "version": "0.0.13", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/capacitor-db-sqlite-persistence/package.json b/packages/capacitor-db-sqlite-persistence/package.json index 8ea6d338f6..2ccf6f8fbc 100644 --- a/packages/capacitor-db-sqlite-persistence/package.json +++ b/packages/capacitor-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence", - "version": "0.2.0", + "version": "0.2.1", "description": "Capacitor SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md index 87f4d18c83..f0b263dcf8 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/cloudflare-durable-objects-db-sqlite-persistence +## 0.2.1 + +### Patch Changes + +- Updated dependencies [[`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db-sqlite-persistence-core@0.2.1 + ## 0.2.0 ### Minor Changes diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json index 257183d06e..4394bb569a 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/cloudflare-durable-objects-db-sqlite-persistence", - "version": "0.2.0", + "version": "0.2.1", "description": "Cloudflare Durable Object SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db-sqlite-persistence-core/CHANGELOG.md b/packages/db-sqlite-persistence-core/CHANGELOG.md index e1721d2e3a..68b5463537 100644 --- a/packages/db-sqlite-persistence-core/CHANGELOG.md +++ b/packages/db-sqlite-persistence-core/CHANGELOG.md @@ -1,5 +1,14 @@ # @tanstack/db-sqlite-persistence-core +## 0.2.1 + +### Patch Changes + +- Use a safe `randomUUID` helper that falls back to `crypto.getRandomValues` when `crypto.randomUUID` is unavailable (non-secure browser contexts such as dev servers reached via a LAN IP over HTTP). Fixes #1541. ([#1593](https://github.com/TanStack/db/pull/1593)) + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.2.0 ### Minor Changes diff --git a/packages/db-sqlite-persistence-core/package.json b/packages/db-sqlite-persistence-core/package.json index ddcd5847d0..076abc5a81 100644 --- a/packages/db-sqlite-persistence-core/package.json +++ b/packages/db-sqlite-persistence-core/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db-sqlite-persistence-core", - "version": "0.2.0", + "version": "0.2.1", "description": "SQLite persisted collection core for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db/CHANGELOG.md b/packages/db/CHANGELOG.md index d59f3991e6..2c8ff69a7f 100644 --- a/packages/db/CHANGELOG.md +++ b/packages/db/CHANGELOG.md @@ -1,5 +1,37 @@ # @tanstack/db +## 0.6.9 + +### Patch Changes + +- Add `subtract`, `multiply`, and `divide` math functions for computed columns ([#1151](https://github.com/TanStack/db/pull/1151)) + + These functions enable complex calculations in `select` and `orderBy` clauses, such as ranking algorithms that combine multiple factors (e.g., HN-style scoring that balances recency and rating). + + ```ts + import { subtract, multiply, divide } from '@tanstack/db' + + // Example: Sort by computed ranking score + const ranked = createLiveQueryCollection((q) => + q + .from({ r: recipesCollection }) + .orderBy( + ({ r }) => + subtract( + multiply(r.rating, r.timesMade), + divide(r.ageInMs, 86400000), + ), + 'desc', + ), + ) + ``` + + - `subtract(a, b)` - Subtraction + - `multiply(a, b)` - Multiplication + - `divide(a, b)` - Division (returns `null` on divide-by-zero) + +- Use a safe `randomUUID` helper that falls back to `crypto.getRandomValues` when `crypto.randomUUID` is unavailable (non-secure browser contexts such as dev servers reached via a LAN IP over HTTP). Fixes #1541. ([#1593](https://github.com/TanStack/db/pull/1593)) + ## 0.6.8 ### Patch Changes diff --git a/packages/db/package.json b/packages/db/package.json index 218beb8072..7939c67c55 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db", - "version": "0.6.8", + "version": "0.6.9", "description": "A reactive client store for building super fast apps on sync", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electric-db-collection/CHANGELOG.md b/packages/electric-db-collection/CHANGELOG.md index d8e0ee03db..3c670daf9d 100644 --- a/packages/electric-db-collection/CHANGELOG.md +++ b/packages/electric-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electric-db-collection +## 0.3.7 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.3.6 ### Patch Changes diff --git a/packages/electric-db-collection/package.json b/packages/electric-db-collection/package.json index a265310672..db931807d7 100644 --- a/packages/electric-db-collection/package.json +++ b/packages/electric-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electric-db-collection", - "version": "0.3.6", + "version": "0.3.7", "description": "ElectricSQL collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electron-db-sqlite-persistence/CHANGELOG.md b/packages/electron-db-sqlite-persistence/CHANGELOG.md index 9a74f0ce27..73de7a2e6d 100644 --- a/packages/electron-db-sqlite-persistence/CHANGELOG.md +++ b/packages/electron-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,14 @@ # @tanstack/electron-db-sqlite-persistence +## 0.1.13 + +### Patch Changes + +- Use a safe `randomUUID` helper that falls back to `crypto.getRandomValues` when `crypto.randomUUID` is unavailable (non-secure browser contexts such as dev servers reached via a LAN IP over HTTP). Fixes #1541. ([#1593](https://github.com/TanStack/db/pull/1593)) + +- Updated dependencies [[`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db-sqlite-persistence-core@0.2.1 + ## 0.1.12 ### Patch Changes diff --git a/packages/electron-db-sqlite-persistence/package.json b/packages/electron-db-sqlite-persistence/package.json index 18f38372f7..b342835f7b 100644 --- a/packages/electron-db-sqlite-persistence/package.json +++ b/packages/electron-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electron-db-sqlite-persistence", - "version": "0.1.12", + "version": "0.1.13", "description": "Electron SQLite persisted collection bridge for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/expo-db-sqlite-persistence/CHANGELOG.md b/packages/expo-db-sqlite-persistence/CHANGELOG.md index b1cded9a2c..fcab9b256e 100644 --- a/packages/expo-db-sqlite-persistence/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/expo-db-sqlite-persistence +## 0.2.1 + +### Patch Changes + +- Updated dependencies [[`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db-sqlite-persistence-core@0.2.1 + ## 0.2.0 ### Minor Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md index 935e723540..4750ae2ba7 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/expo-db-sqlite-persistence-e2e-app +## 0.0.13 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + - @tanstack/expo-db-sqlite-persistence@0.2.1 + ## 0.0.12 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json index 8922e1a5c6..01e45379d0 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/expo-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.12", + "version": "0.0.13", "main": "index.js", "scripts": { "start": "expo start", diff --git a/packages/expo-db-sqlite-persistence/package.json b/packages/expo-db-sqlite-persistence/package.json index 7ff8f3554c..54eab71556 100644 --- a/packages/expo-db-sqlite-persistence/package.json +++ b/packages/expo-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/expo-db-sqlite-persistence", - "version": "0.2.0", + "version": "0.2.1", "description": "Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/node-db-sqlite-persistence/CHANGELOG.md b/packages/node-db-sqlite-persistence/CHANGELOG.md index b427f31a5e..fbae0befa1 100644 --- a/packages/node-db-sqlite-persistence/CHANGELOG.md +++ b/packages/node-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/node-db-sqlite-persistence +## 0.2.1 + +### Patch Changes + +- Updated dependencies [[`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db-sqlite-persistence-core@0.2.1 + ## 0.2.0 ### Minor Changes diff --git a/packages/node-db-sqlite-persistence/package.json b/packages/node-db-sqlite-persistence/package.json index 4391f7b071..386f79f795 100644 --- a/packages/node-db-sqlite-persistence/package.json +++ b/packages/node-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/node-db-sqlite-persistence", - "version": "0.2.0", + "version": "0.2.1", "description": "Node SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/offline-transactions/CHANGELOG.md b/packages/offline-transactions/CHANGELOG.md index 2057952ea4..4973473334 100644 --- a/packages/offline-transactions/CHANGELOG.md +++ b/packages/offline-transactions/CHANGELOG.md @@ -1,5 +1,14 @@ # @tanstack/offline-transactions +## 1.0.34 + +### Patch Changes + +- Use a safe `randomUUID` helper that falls back to `crypto.getRandomValues` when `crypto.randomUUID` is unavailable (non-secure browser contexts such as dev servers reached via a LAN IP over HTTP). Fixes #1541. ([#1593](https://github.com/TanStack/db/pull/1593)) + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 1.0.33 ### Patch Changes diff --git a/packages/offline-transactions/package.json b/packages/offline-transactions/package.json index 872a47b924..1020be51bc 100644 --- a/packages/offline-transactions/package.json +++ b/packages/offline-transactions/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/offline-transactions", - "version": "1.0.33", + "version": "1.0.34", "description": "Offline-first transaction capabilities for TanStack DB", "author": "TanStack", "license": "MIT", diff --git a/packages/powersync-db-collection/CHANGELOG.md b/packages/powersync-db-collection/CHANGELOG.md index b5447bdea9..b24bb05e72 100644 --- a/packages/powersync-db-collection/CHANGELOG.md +++ b/packages/powersync-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/powersync-db-collection +## 0.1.47 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.1.46 ### Patch Changes diff --git a/packages/powersync-db-collection/package.json b/packages/powersync-db-collection/package.json index 93444c4f26..a13faca7f0 100644 --- a/packages/powersync-db-collection/package.json +++ b/packages/powersync-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/powersync-db-collection", - "version": "0.1.46", + "version": "0.1.47", "description": "PowerSync collection for TanStack DB", "author": "POWERSYNC", "license": "MIT", diff --git a/packages/query-db-collection/CHANGELOG.md b/packages/query-db-collection/CHANGELOG.md index c64cc6fe15..58727ae34b 100644 --- a/packages/query-db-collection/CHANGELOG.md +++ b/packages/query-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/query-db-collection +## 1.0.41 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 1.0.40 ### Patch Changes diff --git a/packages/query-db-collection/package.json b/packages/query-db-collection/package.json index f3599c7a5e..9a0b654121 100644 --- a/packages/query-db-collection/package.json +++ b/packages/query-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/query-db-collection", - "version": "1.0.40", + "version": "1.0.41", "description": "TanStack Query collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-db/CHANGELOG.md b/packages/react-db/CHANGELOG.md index d8d077ea50..e12c46b786 100644 --- a/packages/react-db/CHANGELOG.md +++ b/packages/react-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 0.1.87 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.1.86 ### Patch Changes diff --git a/packages/react-db/package.json b/packages/react-db/package.json index 65f6f2f315..2ac29a8fa4 100644 --- a/packages/react-db/package.json +++ b/packages/react-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-db", - "version": "0.1.86", + "version": "0.1.87", "description": "React integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-native-db-sqlite-persistence/CHANGELOG.md b/packages/react-native-db-sqlite-persistence/CHANGELOG.md index 4f72cb892d..bd6a90668c 100644 --- a/packages/react-native-db-sqlite-persistence/CHANGELOG.md +++ b/packages/react-native-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-native-db-sqlite-persistence +## 0.2.1 + +### Patch Changes + +- Updated dependencies [[`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db-sqlite-persistence-core@0.2.1 + ## 0.2.0 ### Minor Changes diff --git a/packages/react-native-db-sqlite-persistence/package.json b/packages/react-native-db-sqlite-persistence/package.json index 36fb14352c..b59c3b5b49 100644 --- a/packages/react-native-db-sqlite-persistence/package.json +++ b/packages/react-native-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-native-db-sqlite-persistence", - "version": "0.2.0", + "version": "0.2.1", "description": "React Native and Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/rxdb-db-collection/CHANGELOG.md b/packages/rxdb-db-collection/CHANGELOG.md index 2e6f39e24f..dcd6edd405 100644 --- a/packages/rxdb-db-collection/CHANGELOG.md +++ b/packages/rxdb-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/rxdb-db-collection +## 0.1.75 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.1.74 ### Patch Changes diff --git a/packages/rxdb-db-collection/package.json b/packages/rxdb-db-collection/package.json index edd6a3474b..06a259ecfb 100644 --- a/packages/rxdb-db-collection/package.json +++ b/packages/rxdb-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/rxdb-db-collection", - "version": "0.1.74", + "version": "0.1.75", "description": "Reactive, Offline-First adapter for TanStack DB using RxDB. Sync, Replication and Local-First support.", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/solid-db/CHANGELOG.md b/packages/solid-db/CHANGELOG.md index 28f10b3eff..63d736f91c 100644 --- a/packages/solid-db/CHANGELOG.md +++ b/packages/solid-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 0.2.23 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.2.22 ### Patch Changes diff --git a/packages/solid-db/package.json b/packages/solid-db/package.json index 50debdc474..d5d65a6bfc 100644 --- a/packages/solid-db/package.json +++ b/packages/solid-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/solid-db", - "version": "0.2.22", + "version": "0.2.23", "description": "Solid integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/svelte-db/CHANGELOG.md b/packages/svelte-db/CHANGELOG.md index a740729e17..388208fbc7 100644 --- a/packages/svelte-db/CHANGELOG.md +++ b/packages/svelte-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/svelte-db +## 0.1.86 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.1.85 ### Patch Changes diff --git a/packages/svelte-db/package.json b/packages/svelte-db/package.json index 79cfab6a16..e7da54fe70 100644 --- a/packages/svelte-db/package.json +++ b/packages/svelte-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/svelte-db", - "version": "0.1.85", + "version": "0.1.86", "description": "Svelte integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/tauri-db-sqlite-persistence/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/CHANGELOG.md index 25f620e40b..47726fa7ad 100644 --- a/packages/tauri-db-sqlite-persistence/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/tauri-db-sqlite-persistence +## 0.2.1 + +### Patch Changes + +- Updated dependencies [[`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db-sqlite-persistence-core@0.2.1 + ## 0.2.0 ### Minor Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md index 254ae4ded8..2279f773b7 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/tauri-db-sqlite-persistence-e2e-app +## 0.0.13 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + - @tanstack/tauri-db-sqlite-persistence@0.2.1 + ## 0.0.12 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/package.json b/packages/tauri-db-sqlite-persistence/e2e/app/package.json index 795d1406cd..6d568d4c41 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/package.json +++ b/packages/tauri-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/tauri-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.12", + "version": "0.0.13", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/tauri-db-sqlite-persistence/package.json b/packages/tauri-db-sqlite-persistence/package.json index 796c62efa3..e00a0f4ac0 100644 --- a/packages/tauri-db-sqlite-persistence/package.json +++ b/packages/tauri-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/tauri-db-sqlite-persistence", - "version": "0.2.0", + "version": "0.2.1", "description": "Tauri SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/trailbase-db-collection/CHANGELOG.md b/packages/trailbase-db-collection/CHANGELOG.md index da6baa3e67..c7f70e587b 100644 --- a/packages/trailbase-db-collection/CHANGELOG.md +++ b/packages/trailbase-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/trailbase-db-collection +## 0.1.87 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.1.86 ### Patch Changes diff --git a/packages/trailbase-db-collection/package.json b/packages/trailbase-db-collection/package.json index 01ac435b6b..5c90b7e0a3 100644 --- a/packages/trailbase-db-collection/package.json +++ b/packages/trailbase-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/trailbase-db-collection", - "version": "0.1.86", + "version": "0.1.87", "description": "TrailBase collection for TanStack DB", "author": "Sebastian Jeltsch", "license": "MIT", diff --git a/packages/vue-db/CHANGELOG.md b/packages/vue-db/CHANGELOG.md index 7a4bebe4eb..3c156d70e5 100644 --- a/packages/vue-db/CHANGELOG.md +++ b/packages/vue-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/vue-db +## 0.0.120 + +### Patch Changes + +- Updated dependencies [[`2147345`](https://github.com/TanStack/db/commit/2147345236ceee6e73d9fc6c0cdc2385833199fc), [`00389a4`](https://github.com/TanStack/db/commit/00389a47b258ad58fc3a03c5cc6f66957b9bd2d1)]: + - @tanstack/db@0.6.9 + ## 0.0.119 ### Patch Changes diff --git a/packages/vue-db/package.json b/packages/vue-db/package.json index f35759d05d..a70789c358 100644 --- a/packages/vue-db/package.json +++ b/packages/vue-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/vue-db", - "version": "0.0.119", + "version": "0.0.120", "description": "Vue integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad174b46b5..38387194dd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,10 +148,10 @@ importers: specifier: ^20.3.16 version: 20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@tanstack/angular-db': - specifier: ^0.1.68 + specifier: ^0.1.69 version: link:../../../packages/angular-db '@tanstack/db': - specifier: ^0.6.8 + specifier: ^0.6.9 version: link:../../../packages/db rxjs: specifier: ^7.8.2 @@ -209,19 +209,19 @@ importers: examples/electron/offline-first: dependencies: '@tanstack/electron-db-sqlite-persistence': - specifier: ^0.1.12 + specifier: ^0.1.13 version: link:../../../packages/electron-db-sqlite-persistence '@tanstack/node-db-sqlite-persistence': - specifier: ^0.2.0 + specifier: ^0.2.1 version: link:../../../packages/node-db-sqlite-persistence '@tanstack/offline-transactions': - specifier: ^1.0.33 + specifier: ^1.0.34 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.40 + specifier: ^1.0.41 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.86 + specifier: ^0.1.87 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -300,19 +300,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.8 + specifier: ^0.6.9 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.33 + specifier: ^1.0.34 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.40 + specifier: ^1.0.41 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.86 + specifier: ^0.1.87 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.0 + specifier: ^0.2.1 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -397,19 +397,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.8 + specifier: ^0.6.9 version: link:../../../packages/db '@tanstack/electric-db-collection': - specifier: ^0.3.6 + specifier: ^0.3.7 version: link:../../../packages/electric-db-collection '@tanstack/offline-transactions': - specifier: ^1.0.33 + specifier: ^1.0.34 version: link:../../../packages/offline-transactions '@tanstack/react-db': - specifier: ^0.1.86 + specifier: ^0.1.87 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.0 + specifier: ^0.2.1 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -482,19 +482,19 @@ importers: examples/react/offline-transactions: dependencies: '@tanstack/browser-db-sqlite-persistence': - specifier: ^0.2.0 + specifier: ^0.2.1 version: link:../../../packages/browser-db-sqlite-persistence '@tanstack/db': - specifier: ^0.6.8 + specifier: ^0.6.9 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.33 + specifier: ^1.0.34 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.40 + specifier: ^1.0.41 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.86 + specifier: ^0.1.87 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -552,10 +552,10 @@ importers: examples/react/paced-mutations-demo: dependencies: '@tanstack/db': - specifier: ^0.6.8 + specifier: ^0.6.9 version: link:../../../packages/db '@tanstack/react-db': - specifier: ^0.1.86 + specifier: ^0.1.87 version: link:../../../packages/react-db mitt: specifier: ^3.0.1 @@ -592,10 +592,10 @@ importers: specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.40 + specifier: ^1.0.41 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.86 + specifier: ^0.1.87 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -725,16 +725,16 @@ importers: examples/react/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.6 + specifier: ^0.3.7 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.40 + specifier: ^1.0.41 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.86 + specifier: ^0.1.87 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -743,7 +743,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.86 + specifier: ^0.1.87 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 @@ -846,16 +846,16 @@ importers: examples/solid/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.6 + specifier: ^0.3.7 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.40 + specifier: ^1.0.41 version: link:../../../packages/query-db-collection '@tanstack/solid-db': - specifier: ^0.2.22 + specifier: ^0.2.23 version: link:../../../packages/solid-db '@tanstack/solid-router': specifier: ^1.159.5 @@ -864,7 +864,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(solid-js@1.9.11)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.86 + specifier: ^0.1.87 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 From 91c24c6df4579bcca320227a9da4e222ccc42215 Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Tue, 23 Jun 2026 13:49:32 +0200 Subject: [PATCH 06/11] Docs. --- docs/collections/powersync-collection.md | 167 ++++++++++++++++++ .../src/attachments.ts | 4 + 2 files changed, 171 insertions(+) diff --git a/docs/collections/powersync-collection.md b/docs/collections/powersync-collection.md index c8ddbabbbe..b484c5bb92 100644 --- a/docs/collections/powersync-collection.md +++ b/docs/collections/powersync-collection.md @@ -1099,4 +1099,171 @@ const liveQuery = createLiveQueryCollection({ completed: todo.completed, })), }) +``` + +## Attachments + +`@tanstack/powersync-db-collection` ships `TanStackDBAttachmentQueue`, an [`AttachmentQueue`](https://docs.powersync.com/usage/use-case-examples/attachments-files) whose file operations commit inside a TanStack DB collection transaction. This lets you create (or delete) an attachment and mutate a related collection row (for example, setting `lists.photo_id`) atomically in a single transaction, instead of issuing two independent writes. + +The queue extends PowerSync's `AttachmentQueue`, so the generic concepts are unchanged and documented once in the SDK. + +> This section only covers what is specific to the TanStack DB integration. For storage adapters (local and remote), the `AttachmentTable` schema primitive, error-handling/retry semantics, and the `startSync()` / `stopSync()` lifecycle, see the [PowerSync attachments documentation](https://docs.powersync.com/usage/use-case-examples/attachments-files). + +### Prerequisites + +These are standard PowerSync attachment requirements. See the SDK attachments docs for details. + +- An `AttachmentTable` in your schema: + + ```ts + import { AttachmentTable, Schema } from "@powersync/web" + + const APP_SCHEMA = new Schema({ + // ...your tables + attachments: new AttachmentTable(), + }) + ``` + +- A local storage adapter (such as `IndexDBFileSystemStorageAdapter` on web) and a remote storage adapter (an implementation of the SDK's `RemoteStorageAdapter`, for example backed by Supabase Storage). Both are generic to all attachment users. See the SDK docs for the available adapters and the remote-adapter contract. + +### 1. Create the attachments collection + +This is the piece that makes the integration TanStack-aware: a normal PowerSync collection over the attachments table. The queue reads and writes attachment records through it. + +```ts +import { createCollection } from "@tanstack/react-db" +import { powerSyncCollectionOptions } from "@tanstack/powersync-db-collection" + +const attachmentsCollection = createCollection( + powerSyncCollectionOptions({ + database: db, + table: APP_SCHEMA.props.attachments, + }) +) +``` + +### 2. Construct the queue + +Pass your collection as `attachmentsCollection` alongside the standard `AttachmentQueue` options. Only `attachmentsCollection` and `watchAttachments` (below) are specific to this package; `db`, `localStorage`, `remoteStorage`, and `errorHandler` are the usual SDK options. + +```ts +import { TanStackDBAttachmentQueue } from "@tanstack/powersync-db-collection" + +const attachmentQueue = new TanStackDBAttachmentQueue({ + db, + attachmentsCollection, // TanStack DB collection over your AttachmentTable + localStorage, // SDK local storage adapter + remoteStorage, // your RemoteStorageAdapter (see SDK docs) + watchAttachments, // see step 3 + errorHandler, // standard AttachmentQueue error handler (see SDK docs) +}) +``` + +Start and stop syncing with the standard `attachmentQueue.startSync()` / `attachmentQueue.stopSync()` lifecycle (see SDK docs), typically inside a React effect or provider. + +### 3. Tell the queue which attachments exist (`watchAttachments`) + +`watchAttachments` reports the set of attachment IDs your data currently references, so the queue knows what to download and what to archive. With TanStack DB you drive it from a live query: emit the initial state, then re-emit the complete set on every change, and clean up on abort. + +```ts +import { + createCollection, + isNull, + liveQueryCollectionOptions, + not, +} from "@tanstack/db" +import { WatchedAttachmentItem } from "@powersync/web" + +const watchAttachments = async (onUpdate, abortSignal) => { + // Every row in your data model that references an attachment. + const livePhotoIds = createCollection( + liveQueryCollectionOptions({ + query: (q) => + q + .from({ document: listsCollection }) + .where(({ document }) => not(isNull(document.photo_id))) + .select(({ document }) => ({ photo_id: document.photo_id })), + }) + ) + + const mapper = (item) => + ({ + id: item.photo_id, + fileExtension: "jpg", + }) satisfies WatchedAttachmentItem + + // 1. Report the initial set of referenced attachment IDs. + const initialState = await livePhotoIds.stateWhenReady() + onUpdate(Array.from(initialState.values()).map(mapper)) + + // 2. Re-emit the whole set on every change (the queue expects the holistic state). + livePhotoIds.subscribeChanges(() => { + onUpdate(livePhotoIds.map(mapper)) + }) + + // 3. Clean up when sync stops. + abortSignal.addEventListener("abort", () => livePhotoIds.cleanup(), { + once: true, + }) +} +``` + +> A `watchAttachmentsFromQuery(...)` convenience helper that collapses this boilerplate into a single call is planned. Until then, use the pattern above. + +### 4. Save an attachment atomically with related data + +`saveFileTanStack` writes the file, inserts the attachment record into your collection, and runs your `updateHook` mutations in the same transaction. Use the hook to insert or update the row that references the new attachment, so both land together or not at all. + +```ts +await attachmentQueue.saveFileTanStack({ + data, // file bytes (ArrayBuffer / base64, per your local adapter) + fileExtension: "jpg", + updateHook: async (attachmentRecord) => { + // Runs in the same transaction as the attachment insert. + listsCollection.insert({ + id: crypto.randomUUID(), + name, + created_at: new Date(), + owner_id: userID, + photo_id: attachmentRecord.id, // associate the row with the attachment + }) + }, +}) +``` + +### 5. Delete an attachment and detach it from the row + +`deleteFileTanStack` queues the file for deletion and runs your `updateHook` in the same transaction. Clear the foreign key so the row and the attachment stay consistent. + +```ts +await attachmentQueue.deleteFileTanStack({ + id: photo_id, + updateHook: async () => { + listsCollection.update(listId, (draft) => { + draft.photo_id = null + }) + }, +}) +``` + +### 6. Display attachments via a live-query join + +Join your attachments collection into a live query to read the local URI (the locally cached file path) alongside your domain rows: + +```ts +import { eq } from "@tanstack/db" + +const { data } = useLiveQuery((q) => + q + .from({ lists: listsCollection }) + .leftJoin({ attachment: attachmentsCollection }, ({ lists, attachment }) => + eq(lists.photo_id, attachment.id) + ) + .select(({ lists, attachment }) => ({ + id: lists.id, + name: lists.name, + photo_id: lists.photo_id, + attachment_local_uri: attachment?.local_uri, + })) +) ``` \ No newline at end of file diff --git a/packages/powersync-db-collection/src/attachments.ts b/packages/powersync-db-collection/src/attachments.ts index c3c9d8f677..cbda937226 100644 --- a/packages/powersync-db-collection/src/attachments.ts +++ b/packages/powersync-db-collection/src/attachments.ts @@ -40,6 +40,10 @@ export interface SaveFileTanStackOptions { export interface DeleteFileTanStackOptions { id: string + /** * + * Note that this is called inside a synchronous TanStackDB transaction, + * any mutations made to other collections will be in the same transaction. + */ updateHook?: (attachment: AttachmentQueueRow) => Promise } From 3770d3b3e87966ec55775cae46e0ea762a136a4c Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Tue, 23 Jun 2026 14:01:10 +0200 Subject: [PATCH 07/11] changeset. --- .changeset/curly-planets-lead.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/curly-planets-lead.md diff --git a/.changeset/curly-planets-lead.md b/.changeset/curly-planets-lead.md new file mode 100644 index 0000000000..891ffbb522 --- /dev/null +++ b/.changeset/curly-planets-lead.md @@ -0,0 +1,6 @@ +--- +'@tanstack/powersync-db-collection': patch +--- + +Add attachments support via `TanStackDBAttachmentQueue`. This extends the PowerSync SDK's `AttachmentQueue` and backs it with +a TanStack DB collection, so file uploads/deletes are managed atomically alongside the relational data. From 307fdf80f522a39a50e316316b3b75ba27fd5e84 Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 23 Jun 2026 18:58:41 +0200 Subject: [PATCH 08/11] fix: preload hangs forever after cleanup (#1576) (#1606) * test: cover re-preloading a live query after cleanup Add a regression test asserting that a live query loads its data again when it is preloaded after the live query and its source collection were cleaned up (e.g. when switching data sets at runtime). Co-Authored-By: Claude Opus 4.8 (1M context) * fix: reset live query error state on sync restart so preload recovers after cleanup (#1576) A source collection that is cleaned up while a live query depends on it pushes the live query into an error state and latches `isInErrorState`. That flag was never reset, so when sync restarted via preload() after cleanup (e.g. switching profiles without a page refresh), updateLiveQueryStatus() returned early, markReady() was never called and the preload promise hung forever. Reset `isInErrorState` at the start of each sync session so the live query can become ready again. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- .changeset/preload-after-cleanup-1576.md | 7 +++++ .../query/live/collection-config-builder.ts | 2 ++ .../tests/query/live-query-collection.test.ts | 27 +++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 .changeset/preload-after-cleanup-1576.md diff --git a/.changeset/preload-after-cleanup-1576.md b/.changeset/preload-after-cleanup-1576.md new file mode 100644 index 0000000000..132aeaf929 --- /dev/null +++ b/.changeset/preload-after-cleanup-1576.md @@ -0,0 +1,7 @@ +--- +'@tanstack/db': patch +--- + +Fix live query `preload()` hanging forever after a source collection was cleaned up (#1576) + +When a source collection is cleaned up while a live query depends on it, the live query transitions to an error state and latches an internal `isInErrorState` flag. That flag was never reset, so restarting sync (e.g. calling `preload()` again after cleanup when switching profiles) left the live query unable to become ready and the returned promise never resolved. The flag is now cleared at the start of each sync session so the live query can recover. diff --git a/packages/db/src/query/live/collection-config-builder.ts b/packages/db/src/query/live/collection-config-builder.ts index b40e6e431a..8faa55265f 100644 --- a/packages/db/src/query/live/collection-config-builder.ts +++ b/packages/db/src/query/live/collection-config-builder.ts @@ -596,6 +596,8 @@ export class CollectionConfigBuilder< private syncFn(config: SyncMethods) { // Store reference to the live query collection for error state transitions this.liveQueryCollection = config.collection + // Reset error state from any previous sync session so a restarted sync can become ready again. + this.isInErrorState = false // Store config and syncState as instance properties for the duration of this sync session this.currentSyncConfig = config diff --git a/packages/db/tests/query/live-query-collection.test.ts b/packages/db/tests/query/live-query-collection.test.ts index 8b2b609016..29d10cc27d 100644 --- a/packages/db/tests/query/live-query-collection.test.ts +++ b/packages/db/tests/query/live-query-collection.test.ts @@ -594,6 +594,33 @@ describe(`createLiveQueryCollection`, () => { finalSubscription.unsubscribe() }) + it(`loads its data again when preloaded after the live query and its source collection were cleaned up`, async () => { + const activeUsers = createLiveQueryCollection({ + query: (q) => + q + .from({ user: usersCollection }) + .where(({ user }) => eq(user.active, true)), + }) + + await activeUsers.preload() + expect(activeUsers.status).toBe(`ready`) + expect(activeUsers.size).toBe(2) + + // Tear down the source collection and the live query, e.g. when switching + // to a different data set at runtime. Cleaning up a source collection puts + // the dependent live query into an error state. + await usersCollection.cleanup() + expect(activeUsers.status).toBe(`error`) + + await activeUsers.cleanup() + expect(activeUsers.status).toBe(`cleaned-up`) + + // Preloading again restarts sync and resolves once the data is loaded. + await activeUsers.preload() + expect(activeUsers.status).toBe(`ready`) + expect(activeUsers.size).toBe(2) + }) + it(`should handle temporal values correctly in live queries`, async () => { // Define a type with temporal values type Task = { From 45617c45b761ee32d6cacc1a4ee9e653a27680dd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 23 Jun 2026 13:07:45 -0600 Subject: [PATCH 09/11] ci: Version Packages (#1612) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .changeset/preload-after-cleanup-1576.md | 7 -- examples/angular/todos/package.json | 4 +- examples/electron/offline-first/package.json | 10 +-- .../offline-transactions/package.json | 10 +-- .../react-native/shopping-list/package.json | 10 +-- .../react/offline-transactions/package.json | 10 +-- .../react/paced-mutations-demo/package.json | 4 +- examples/react/projects/package.json | 4 +- examples/react/todo/package.json | 8 +-- examples/solid/todo/package.json | 8 +-- packages/angular-db/CHANGELOG.md | 7 ++ packages/angular-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../db-sqlite-persistence-core/CHANGELOG.md | 7 ++ .../db-sqlite-persistence-core/package.json | 2 +- packages/db/CHANGELOG.md | 8 +++ packages/db/package.json | 2 +- packages/electric-db-collection/CHANGELOG.md | 7 ++ packages/electric-db-collection/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- .../expo-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/expo-runtime-app/CHANGELOG.md | 8 +++ .../e2e/expo-runtime-app/package.json | 2 +- .../expo-db-sqlite-persistence/package.json | 2 +- .../node-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../node-db-sqlite-persistence/package.json | 2 +- packages/offline-transactions/CHANGELOG.md | 7 ++ packages/offline-transactions/package.json | 2 +- packages/powersync-db-collection/CHANGELOG.md | 7 ++ packages/powersync-db-collection/package.json | 2 +- packages/query-db-collection/CHANGELOG.md | 7 ++ packages/query-db-collection/package.json | 2 +- packages/react-db/CHANGELOG.md | 7 ++ packages/react-db/package.json | 2 +- .../CHANGELOG.md | 7 ++ .../package.json | 2 +- packages/rxdb-db-collection/CHANGELOG.md | 7 ++ packages/rxdb-db-collection/package.json | 2 +- packages/solid-db/CHANGELOG.md | 7 ++ packages/solid-db/package.json | 2 +- packages/svelte-db/CHANGELOG.md | 7 ++ packages/svelte-db/package.json | 2 +- .../tauri-db-sqlite-persistence/CHANGELOG.md | 7 ++ .../e2e/app/CHANGELOG.md | 8 +++ .../e2e/app/package.json | 2 +- .../tauri-db-sqlite-persistence/package.json | 2 +- packages/trailbase-db-collection/CHANGELOG.md | 7 ++ packages/trailbase-db-collection/package.json | 2 +- packages/vue-db/CHANGELOG.md | 7 ++ packages/vue-db/package.json | 2 +- pnpm-lock.yaml | 68 +++++++++---------- 59 files changed, 264 insertions(+), 99 deletions(-) delete mode 100644 .changeset/preload-after-cleanup-1576.md diff --git a/.changeset/preload-after-cleanup-1576.md b/.changeset/preload-after-cleanup-1576.md deleted file mode 100644 index 132aeaf929..0000000000 --- a/.changeset/preload-after-cleanup-1576.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@tanstack/db': patch ---- - -Fix live query `preload()` hanging forever after a source collection was cleaned up (#1576) - -When a source collection is cleaned up while a live query depends on it, the live query transitions to an error state and latches an internal `isInErrorState` flag. That flag was never reset, so restarting sync (e.g. calling `preload()` again after cleanup when switching profiles) left the live query unable to become ready and the returned promise never resolved. The flag is now cleared at the start of each sync session so the live query can recover. diff --git a/examples/angular/todos/package.json b/examples/angular/todos/package.json index b4c64bfacc..7829800350 100644 --- a/examples/angular/todos/package.json +++ b/examples/angular/todos/package.json @@ -28,8 +28,8 @@ "@angular/forms": "^20.3.16", "@angular/platform-browser": "^20.3.16", "@angular/router": "^20.3.16", - "@tanstack/angular-db": "^0.1.69", - "@tanstack/db": "^0.6.9", + "@tanstack/angular-db": "^0.1.70", + "@tanstack/db": "^0.6.10", "rxjs": "^7.8.2", "tslib": "^2.8.1", "zone.js": "~0.15.0" diff --git a/examples/electron/offline-first/package.json b/examples/electron/offline-first/package.json index ebe72a04c5..2ba2aea097 100644 --- a/examples/electron/offline-first/package.json +++ b/examples/electron/offline-first/package.json @@ -13,11 +13,11 @@ "postinstall": "prebuild-install --runtime electron --target 40.2.1 --arch arm64 || echo 'prebuild-install failed, try: npx @electron/rebuild'" }, "dependencies": { - "@tanstack/electron-db-sqlite-persistence": "^0.1.13", - "@tanstack/node-db-sqlite-persistence": "^0.2.1", - "@tanstack/offline-transactions": "^1.0.34", - "@tanstack/query-db-collection": "^1.0.41", - "@tanstack/react-db": "^0.1.87", + "@tanstack/electron-db-sqlite-persistence": "^0.1.14", + "@tanstack/node-db-sqlite-persistence": "^0.2.2", + "@tanstack/offline-transactions": "^1.0.35", + "@tanstack/query-db-collection": "^1.0.42", + "@tanstack/react-db": "^0.1.88", "@tanstack/react-query": "^5.90.20", "better-sqlite3": "^12.6.2", "react": "^19.2.4", diff --git a/examples/react-native/offline-transactions/package.json b/examples/react-native/offline-transactions/package.json index 82b07e3c31..a8ec7a0635 100644 --- a/examples/react-native/offline-transactions/package.json +++ b/examples/react-native/offline-transactions/package.json @@ -15,11 +15,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.9", - "@tanstack/offline-transactions": "^1.0.34", - "@tanstack/query-db-collection": "^1.0.41", - "@tanstack/react-db": "^0.1.87", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.1", + "@tanstack/db": "^0.6.10", + "@tanstack/offline-transactions": "^1.0.35", + "@tanstack/query-db-collection": "^1.0.42", + "@tanstack/react-db": "^0.1.88", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.2", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react-native/shopping-list/package.json b/examples/react-native/shopping-list/package.json index 8db8659ca3..8b4fee8599 100644 --- a/examples/react-native/shopping-list/package.json +++ b/examples/react-native/shopping-list/package.json @@ -18,11 +18,11 @@ "@op-engineering/op-sqlite": "^15.2.5", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/netinfo": "11.4.1", - "@tanstack/db": "^0.6.9", - "@tanstack/electric-db-collection": "^0.3.7", - "@tanstack/offline-transactions": "^1.0.34", - "@tanstack/react-db": "^0.1.87", - "@tanstack/react-native-db-sqlite-persistence": "^0.2.1", + "@tanstack/db": "^0.6.10", + "@tanstack/electric-db-collection": "^0.3.8", + "@tanstack/offline-transactions": "^1.0.35", + "@tanstack/react-db": "^0.1.88", + "@tanstack/react-native-db-sqlite-persistence": "^0.2.2", "@tanstack/react-query": "^5.90.20", "expo": "~53.0.26", "expo-constants": "~17.1.0", diff --git a/examples/react/offline-transactions/package.json b/examples/react/offline-transactions/package.json index eb798b2b86..212d0c0a7a 100644 --- a/examples/react/offline-transactions/package.json +++ b/examples/react/offline-transactions/package.json @@ -8,11 +8,11 @@ "build": "vite build && tsc --noEmit" }, "dependencies": { - "@tanstack/browser-db-sqlite-persistence": "^0.2.1", - "@tanstack/db": "^0.6.9", - "@tanstack/offline-transactions": "^1.0.34", - "@tanstack/query-db-collection": "^1.0.41", - "@tanstack/react-db": "^0.1.87", + "@tanstack/browser-db-sqlite-persistence": "^0.2.2", + "@tanstack/db": "^0.6.10", + "@tanstack/offline-transactions": "^1.0.35", + "@tanstack/query-db-collection": "^1.0.42", + "@tanstack/react-db": "^0.1.88", "@tanstack/react-query": "^5.90.20", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", diff --git a/examples/react/paced-mutations-demo/package.json b/examples/react/paced-mutations-demo/package.json index 290ed30adc..c9ea092585 100644 --- a/examples/react/paced-mutations-demo/package.json +++ b/examples/react/paced-mutations-demo/package.json @@ -9,8 +9,8 @@ "preview": "vite preview" }, "dependencies": { - "@tanstack/db": "^0.6.9", - "@tanstack/react-db": "^0.1.87", + "@tanstack/db": "^0.6.10", + "@tanstack/react-db": "^0.1.88", "mitt": "^3.0.1", "react": "^19.2.4", "react-dom": "^19.2.4" diff --git a/examples/react/projects/package.json b/examples/react/projects/package.json index 2f68958d4d..837e2f371b 100644 --- a/examples/react/projects/package.json +++ b/examples/react/projects/package.json @@ -17,8 +17,8 @@ "dependencies": { "@tailwindcss/vite": "^4.1.18", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.41", - "@tanstack/react-db": "^0.1.87", + "@tanstack/query-db-collection": "^1.0.42", + "@tanstack/react-db": "^0.1.88", "@tanstack/react-router": "^1.159.5", "@tanstack/react-router-devtools": "^1.159.5", "@tanstack/react-router-with-query": "^1.130.17", diff --git a/examples/react/todo/package.json b/examples/react/todo/package.json index 9145e3ff5f..dc7365451e 100644 --- a/examples/react/todo/package.json +++ b/examples/react/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.1.25", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.7", + "@tanstack/electric-db-collection": "^0.3.8", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.41", - "@tanstack/react-db": "^0.1.87", + "@tanstack/query-db-collection": "^1.0.42", + "@tanstack/react-db": "^0.1.88", "@tanstack/react-router": "^1.159.5", "@tanstack/react-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.87", + "@tanstack/trailbase-db-collection": "^0.1.88", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/examples/solid/todo/package.json b/examples/solid/todo/package.json index efdec9ece8..e19558c2e6 100644 --- a/examples/solid/todo/package.json +++ b/examples/solid/todo/package.json @@ -3,13 +3,13 @@ "private": true, "version": "0.0.35", "dependencies": { - "@tanstack/electric-db-collection": "^0.3.7", + "@tanstack/electric-db-collection": "^0.3.8", "@tanstack/query-core": "^5.90.20", - "@tanstack/query-db-collection": "^1.0.41", - "@tanstack/solid-db": "^0.2.23", + "@tanstack/query-db-collection": "^1.0.42", + "@tanstack/solid-db": "^0.2.24", "@tanstack/solid-router": "^1.159.5", "@tanstack/solid-start": "^1.159.5", - "@tanstack/trailbase-db-collection": "^0.1.87", + "@tanstack/trailbase-db-collection": "^0.1.88", "cors": "^2.8.6", "drizzle-orm": "^0.45.1", "drizzle-zod": "^0.8.3", diff --git a/packages/angular-db/CHANGELOG.md b/packages/angular-db/CHANGELOG.md index cc5e8b4e34..09961d8f45 100644 --- a/packages/angular-db/CHANGELOG.md +++ b/packages/angular-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/angular-db +## 0.1.70 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.1.69 ### Patch Changes diff --git a/packages/angular-db/package.json b/packages/angular-db/package.json index 3e7a47e0c9..509fd65f04 100644 --- a/packages/angular-db/package.json +++ b/packages/angular-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/angular-db", - "version": "0.1.69", + "version": "0.1.70", "description": "Angular integration for @tanstack/db", "author": "Ethan McDaniel", "license": "MIT", diff --git a/packages/browser-db-sqlite-persistence/CHANGELOG.md b/packages/browser-db-sqlite-persistence/CHANGELOG.md index 5dcf408756..a7963d8ac7 100644 --- a/packages/browser-db-sqlite-persistence/CHANGELOG.md +++ b/packages/browser-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/browser-db-sqlite-persistence +## 0.2.2 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.2 + ## 0.2.1 ### Patch Changes diff --git a/packages/browser-db-sqlite-persistence/package.json b/packages/browser-db-sqlite-persistence/package.json index 30065efe96..b57349c6c8 100644 --- a/packages/browser-db-sqlite-persistence/package.json +++ b/packages/browser-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/browser-db-sqlite-persistence", - "version": "0.2.1", + "version": "0.2.2", "description": "Browser wa-sqlite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md index 2c238a9a83..2c19dc8950 100644 --- a/packages/capacitor-db-sqlite-persistence/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/capacitor-db-sqlite-persistence +## 0.2.2 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.2 + ## 0.2.1 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md index fa47e3e79f..f9809c5dec 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/capacitor-db-sqlite-persistence-e2e-app +## 0.0.14 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + - @tanstack/capacitor-db-sqlite-persistence@0.2.2 + ## 0.0.13 ### Patch Changes diff --git a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json index 90872c4797..f180809b89 100644 --- a/packages/capacitor-db-sqlite-persistence/e2e/app/package.json +++ b/packages/capacitor-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.13", + "version": "0.0.14", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/capacitor-db-sqlite-persistence/package.json b/packages/capacitor-db-sqlite-persistence/package.json index 2ccf6f8fbc..171b52bf5b 100644 --- a/packages/capacitor-db-sqlite-persistence/package.json +++ b/packages/capacitor-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/capacitor-db-sqlite-persistence", - "version": "0.2.1", + "version": "0.2.2", "description": "Capacitor SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md index f0b263dcf8..3aacab134c 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/cloudflare-durable-objects-db-sqlite-persistence +## 0.2.2 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.2 + ## 0.2.1 ### Patch Changes diff --git a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json index 4394bb569a..500030ec02 100644 --- a/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json +++ b/packages/cloudflare-durable-objects-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/cloudflare-durable-objects-db-sqlite-persistence", - "version": "0.2.1", + "version": "0.2.2", "description": "Cloudflare Durable Object SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db-sqlite-persistence-core/CHANGELOG.md b/packages/db-sqlite-persistence-core/CHANGELOG.md index 68b5463537..3a1c664490 100644 --- a/packages/db-sqlite-persistence-core/CHANGELOG.md +++ b/packages/db-sqlite-persistence-core/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/db-sqlite-persistence-core +## 0.2.2 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.2.1 ### Patch Changes diff --git a/packages/db-sqlite-persistence-core/package.json b/packages/db-sqlite-persistence-core/package.json index 076abc5a81..26cdd105c4 100644 --- a/packages/db-sqlite-persistence-core/package.json +++ b/packages/db-sqlite-persistence-core/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db-sqlite-persistence-core", - "version": "0.2.1", + "version": "0.2.2", "description": "SQLite persisted collection core for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/db/CHANGELOG.md b/packages/db/CHANGELOG.md index 2c8ff69a7f..a6574da80f 100644 --- a/packages/db/CHANGELOG.md +++ b/packages/db/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/db +## 0.6.10 + +### Patch Changes + +- Fix live query `preload()` hanging forever after a source collection was cleaned up (#1576) ([#1606](https://github.com/TanStack/db/pull/1606)) + + When a source collection is cleaned up while a live query depends on it, the live query transitions to an error state and latches an internal `isInErrorState` flag. That flag was never reset, so restarting sync (e.g. calling `preload()` again after cleanup when switching profiles) left the live query unable to become ready and the returned promise never resolved. The flag is now cleared at the start of each sync session so the live query can recover. + ## 0.6.9 ### Patch Changes diff --git a/packages/db/package.json b/packages/db/package.json index 7939c67c55..03d84b67ea 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/db", - "version": "0.6.9", + "version": "0.6.10", "description": "A reactive client store for building super fast apps on sync", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electric-db-collection/CHANGELOG.md b/packages/electric-db-collection/CHANGELOG.md index 3c670daf9d..7d02c6ec29 100644 --- a/packages/electric-db-collection/CHANGELOG.md +++ b/packages/electric-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electric-db-collection +## 0.3.8 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.3.7 ### Patch Changes diff --git a/packages/electric-db-collection/package.json b/packages/electric-db-collection/package.json index db931807d7..9c03109450 100644 --- a/packages/electric-db-collection/package.json +++ b/packages/electric-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electric-db-collection", - "version": "0.3.7", + "version": "0.3.8", "description": "ElectricSQL collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/electron-db-sqlite-persistence/CHANGELOG.md b/packages/electron-db-sqlite-persistence/CHANGELOG.md index 73de7a2e6d..7bac5e1b85 100644 --- a/packages/electron-db-sqlite-persistence/CHANGELOG.md +++ b/packages/electron-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/electron-db-sqlite-persistence +## 0.1.14 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.2 + ## 0.1.13 ### Patch Changes diff --git a/packages/electron-db-sqlite-persistence/package.json b/packages/electron-db-sqlite-persistence/package.json index b342835f7b..80d7b57dd5 100644 --- a/packages/electron-db-sqlite-persistence/package.json +++ b/packages/electron-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/electron-db-sqlite-persistence", - "version": "0.1.13", + "version": "0.1.14", "description": "Electron SQLite persisted collection bridge for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/expo-db-sqlite-persistence/CHANGELOG.md b/packages/expo-db-sqlite-persistence/CHANGELOG.md index fcab9b256e..68fa4658c9 100644 --- a/packages/expo-db-sqlite-persistence/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/expo-db-sqlite-persistence +## 0.2.2 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.2 + ## 0.2.1 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md index 4750ae2ba7..80f5d686f6 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/expo-db-sqlite-persistence-e2e-app +## 0.0.14 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + - @tanstack/expo-db-sqlite-persistence@0.2.2 + ## 0.0.13 ### Patch Changes diff --git a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json index 01e45379d0..fb7d4c2fe2 100644 --- a/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json +++ b/packages/expo-db-sqlite-persistence/e2e/expo-runtime-app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/expo-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.13", + "version": "0.0.14", "main": "index.js", "scripts": { "start": "expo start", diff --git a/packages/expo-db-sqlite-persistence/package.json b/packages/expo-db-sqlite-persistence/package.json index 54eab71556..9e71f67794 100644 --- a/packages/expo-db-sqlite-persistence/package.json +++ b/packages/expo-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/expo-db-sqlite-persistence", - "version": "0.2.1", + "version": "0.2.2", "description": "Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/node-db-sqlite-persistence/CHANGELOG.md b/packages/node-db-sqlite-persistence/CHANGELOG.md index fbae0befa1..f685d49021 100644 --- a/packages/node-db-sqlite-persistence/CHANGELOG.md +++ b/packages/node-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/node-db-sqlite-persistence +## 0.2.2 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.2 + ## 0.2.1 ### Patch Changes diff --git a/packages/node-db-sqlite-persistence/package.json b/packages/node-db-sqlite-persistence/package.json index 386f79f795..ce5eefc2e9 100644 --- a/packages/node-db-sqlite-persistence/package.json +++ b/packages/node-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/node-db-sqlite-persistence", - "version": "0.2.1", + "version": "0.2.2", "description": "Node SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/offline-transactions/CHANGELOG.md b/packages/offline-transactions/CHANGELOG.md index 4973473334..dbb4255300 100644 --- a/packages/offline-transactions/CHANGELOG.md +++ b/packages/offline-transactions/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/offline-transactions +## 1.0.35 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 1.0.34 ### Patch Changes diff --git a/packages/offline-transactions/package.json b/packages/offline-transactions/package.json index 1020be51bc..c336d46380 100644 --- a/packages/offline-transactions/package.json +++ b/packages/offline-transactions/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/offline-transactions", - "version": "1.0.34", + "version": "1.0.35", "description": "Offline-first transaction capabilities for TanStack DB", "author": "TanStack", "license": "MIT", diff --git a/packages/powersync-db-collection/CHANGELOG.md b/packages/powersync-db-collection/CHANGELOG.md index b24bb05e72..a5c28761ad 100644 --- a/packages/powersync-db-collection/CHANGELOG.md +++ b/packages/powersync-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/powersync-db-collection +## 0.1.48 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.1.47 ### Patch Changes diff --git a/packages/powersync-db-collection/package.json b/packages/powersync-db-collection/package.json index a13faca7f0..39466dc011 100644 --- a/packages/powersync-db-collection/package.json +++ b/packages/powersync-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/powersync-db-collection", - "version": "0.1.47", + "version": "0.1.48", "description": "PowerSync collection for TanStack DB", "author": "POWERSYNC", "license": "MIT", diff --git a/packages/query-db-collection/CHANGELOG.md b/packages/query-db-collection/CHANGELOG.md index 58727ae34b..3e868617c0 100644 --- a/packages/query-db-collection/CHANGELOG.md +++ b/packages/query-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/query-db-collection +## 1.0.42 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 1.0.41 ### Patch Changes diff --git a/packages/query-db-collection/package.json b/packages/query-db-collection/package.json index 9a0b654121..fc58d3dbc8 100644 --- a/packages/query-db-collection/package.json +++ b/packages/query-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/query-db-collection", - "version": "1.0.41", + "version": "1.0.42", "description": "TanStack Query collection for TanStack DB", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-db/CHANGELOG.md b/packages/react-db/CHANGELOG.md index e12c46b786..2322d676c9 100644 --- a/packages/react-db/CHANGELOG.md +++ b/packages/react-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 0.1.88 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.1.87 ### Patch Changes diff --git a/packages/react-db/package.json b/packages/react-db/package.json index 2ac29a8fa4..7b7ac675c3 100644 --- a/packages/react-db/package.json +++ b/packages/react-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-db", - "version": "0.1.87", + "version": "0.1.88", "description": "React integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/react-native-db-sqlite-persistence/CHANGELOG.md b/packages/react-native-db-sqlite-persistence/CHANGELOG.md index bd6a90668c..7029e671b8 100644 --- a/packages/react-native-db-sqlite-persistence/CHANGELOG.md +++ b/packages/react-native-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-native-db-sqlite-persistence +## 0.2.2 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.2 + ## 0.2.1 ### Patch Changes diff --git a/packages/react-native-db-sqlite-persistence/package.json b/packages/react-native-db-sqlite-persistence/package.json index b59c3b5b49..f42bfa63ce 100644 --- a/packages/react-native-db-sqlite-persistence/package.json +++ b/packages/react-native-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-native-db-sqlite-persistence", - "version": "0.2.1", + "version": "0.2.2", "description": "React Native and Expo SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/rxdb-db-collection/CHANGELOG.md b/packages/rxdb-db-collection/CHANGELOG.md index dcd6edd405..21cac83f26 100644 --- a/packages/rxdb-db-collection/CHANGELOG.md +++ b/packages/rxdb-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/rxdb-db-collection +## 0.1.76 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.1.75 ### Patch Changes diff --git a/packages/rxdb-db-collection/package.json b/packages/rxdb-db-collection/package.json index 06a259ecfb..98530d438c 100644 --- a/packages/rxdb-db-collection/package.json +++ b/packages/rxdb-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/rxdb-db-collection", - "version": "0.1.75", + "version": "0.1.76", "description": "Reactive, Offline-First adapter for TanStack DB using RxDB. Sync, Replication and Local-First support.", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/solid-db/CHANGELOG.md b/packages/solid-db/CHANGELOG.md index 63d736f91c..a9ef5cc2f4 100644 --- a/packages/solid-db/CHANGELOG.md +++ b/packages/solid-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/react-db +## 0.2.24 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.2.23 ### Patch Changes diff --git a/packages/solid-db/package.json b/packages/solid-db/package.json index d5d65a6bfc..dbaa187d57 100644 --- a/packages/solid-db/package.json +++ b/packages/solid-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/solid-db", - "version": "0.2.23", + "version": "0.2.24", "description": "Solid integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/svelte-db/CHANGELOG.md b/packages/svelte-db/CHANGELOG.md index 388208fbc7..45333b0511 100644 --- a/packages/svelte-db/CHANGELOG.md +++ b/packages/svelte-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/svelte-db +## 0.1.87 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.1.86 ### Patch Changes diff --git a/packages/svelte-db/package.json b/packages/svelte-db/package.json index e7da54fe70..c3c7b292ae 100644 --- a/packages/svelte-db/package.json +++ b/packages/svelte-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/svelte-db", - "version": "0.1.86", + "version": "0.1.87", "description": "Svelte integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/packages/tauri-db-sqlite-persistence/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/CHANGELOG.md index 47726fa7ad..97893f5f26 100644 --- a/packages/tauri-db-sqlite-persistence/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/tauri-db-sqlite-persistence +## 0.2.2 + +### Patch Changes + +- Updated dependencies []: + - @tanstack/db-sqlite-persistence-core@0.2.2 + ## 0.2.1 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md index 2279f773b7..287bbd79a3 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md +++ b/packages/tauri-db-sqlite-persistence/e2e/app/CHANGELOG.md @@ -1,5 +1,13 @@ # @tanstack/tauri-db-sqlite-persistence-e2e-app +## 0.0.14 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + - @tanstack/tauri-db-sqlite-persistence@0.2.2 + ## 0.0.13 ### Patch Changes diff --git a/packages/tauri-db-sqlite-persistence/e2e/app/package.json b/packages/tauri-db-sqlite-persistence/e2e/app/package.json index 6d568d4c41..a5c17377ed 100644 --- a/packages/tauri-db-sqlite-persistence/e2e/app/package.json +++ b/packages/tauri-db-sqlite-persistence/e2e/app/package.json @@ -1,7 +1,7 @@ { "name": "@tanstack/tauri-db-sqlite-persistence-e2e-app", "private": true, - "version": "0.0.13", + "version": "0.0.14", "type": "module", "scripts": { "build": "vite build", diff --git a/packages/tauri-db-sqlite-persistence/package.json b/packages/tauri-db-sqlite-persistence/package.json index e00a0f4ac0..1be0c86c24 100644 --- a/packages/tauri-db-sqlite-persistence/package.json +++ b/packages/tauri-db-sqlite-persistence/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/tauri-db-sqlite-persistence", - "version": "0.2.1", + "version": "0.2.2", "description": "Tauri SQLite persisted collection adapter for TanStack DB", "author": "TanStack Team", "license": "MIT", diff --git a/packages/trailbase-db-collection/CHANGELOG.md b/packages/trailbase-db-collection/CHANGELOG.md index c7f70e587b..f631d325cd 100644 --- a/packages/trailbase-db-collection/CHANGELOG.md +++ b/packages/trailbase-db-collection/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/trailbase-db-collection +## 0.1.88 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.1.87 ### Patch Changes diff --git a/packages/trailbase-db-collection/package.json b/packages/trailbase-db-collection/package.json index 5c90b7e0a3..7403bb4587 100644 --- a/packages/trailbase-db-collection/package.json +++ b/packages/trailbase-db-collection/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/trailbase-db-collection", - "version": "0.1.87", + "version": "0.1.88", "description": "TrailBase collection for TanStack DB", "author": "Sebastian Jeltsch", "license": "MIT", diff --git a/packages/vue-db/CHANGELOG.md b/packages/vue-db/CHANGELOG.md index 3c156d70e5..2e559852d4 100644 --- a/packages/vue-db/CHANGELOG.md +++ b/packages/vue-db/CHANGELOG.md @@ -1,5 +1,12 @@ # @tanstack/vue-db +## 0.0.121 + +### Patch Changes + +- Updated dependencies [[`307fdf8`](https://github.com/TanStack/db/commit/307fdf80f522a39a50e316316b3b75ba27fd5e84)]: + - @tanstack/db@0.6.10 + ## 0.0.120 ### Patch Changes diff --git a/packages/vue-db/package.json b/packages/vue-db/package.json index a70789c358..fbec37e9ab 100644 --- a/packages/vue-db/package.json +++ b/packages/vue-db/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/vue-db", - "version": "0.0.120", + "version": "0.0.121", "description": "Vue integration for @tanstack/db", "author": "Kyle Mathews", "license": "MIT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 38387194dd..525d66dab4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -148,10 +148,10 @@ importers: specifier: ^20.3.16 version: 20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.3.16(@angular/common@20.3.16(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.17(@angular/compiler@20.3.16)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@tanstack/angular-db': - specifier: ^0.1.69 + specifier: ^0.1.70 version: link:../../../packages/angular-db '@tanstack/db': - specifier: ^0.6.9 + specifier: ^0.6.10 version: link:../../../packages/db rxjs: specifier: ^7.8.2 @@ -209,19 +209,19 @@ importers: examples/electron/offline-first: dependencies: '@tanstack/electron-db-sqlite-persistence': - specifier: ^0.1.13 + specifier: ^0.1.14 version: link:../../../packages/electron-db-sqlite-persistence '@tanstack/node-db-sqlite-persistence': - specifier: ^0.2.1 + specifier: ^0.2.2 version: link:../../../packages/node-db-sqlite-persistence '@tanstack/offline-transactions': - specifier: ^1.0.34 + specifier: ^1.0.35 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.41 + specifier: ^1.0.42 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.87 + specifier: ^0.1.88 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -300,19 +300,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.9 + specifier: ^0.6.10 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.34 + specifier: ^1.0.35 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.41 + specifier: ^1.0.42 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.87 + specifier: ^0.1.88 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.1 + specifier: ^0.2.2 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -397,19 +397,19 @@ importers: specifier: 11.4.1 version: 11.4.1(react-native@0.79.6(@babel/core@7.29.0)(@types/react@19.2.13)(react@19.0.0)) '@tanstack/db': - specifier: ^0.6.9 + specifier: ^0.6.10 version: link:../../../packages/db '@tanstack/electric-db-collection': - specifier: ^0.3.7 + specifier: ^0.3.8 version: link:../../../packages/electric-db-collection '@tanstack/offline-transactions': - specifier: ^1.0.34 + specifier: ^1.0.35 version: link:../../../packages/offline-transactions '@tanstack/react-db': - specifier: ^0.1.87 + specifier: ^0.1.88 version: link:../../../packages/react-db '@tanstack/react-native-db-sqlite-persistence': - specifier: ^0.2.1 + specifier: ^0.2.2 version: link:../../../packages/react-native-db-sqlite-persistence '@tanstack/react-query': specifier: ^5.90.20 @@ -482,19 +482,19 @@ importers: examples/react/offline-transactions: dependencies: '@tanstack/browser-db-sqlite-persistence': - specifier: ^0.2.1 + specifier: ^0.2.2 version: link:../../../packages/browser-db-sqlite-persistence '@tanstack/db': - specifier: ^0.6.9 + specifier: ^0.6.10 version: link:../../../packages/db '@tanstack/offline-transactions': - specifier: ^1.0.34 + specifier: ^1.0.35 version: link:../../../packages/offline-transactions '@tanstack/query-db-collection': - specifier: ^1.0.41 + specifier: ^1.0.42 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.87 + specifier: ^0.1.88 version: link:../../../packages/react-db '@tanstack/react-query': specifier: ^5.90.20 @@ -552,10 +552,10 @@ importers: examples/react/paced-mutations-demo: dependencies: '@tanstack/db': - specifier: ^0.6.9 + specifier: ^0.6.10 version: link:../../../packages/db '@tanstack/react-db': - specifier: ^0.1.87 + specifier: ^0.1.88 version: link:../../../packages/react-db mitt: specifier: ^3.0.1 @@ -592,10 +592,10 @@ importers: specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.41 + specifier: ^1.0.42 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.87 + specifier: ^0.1.88 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -725,16 +725,16 @@ importers: examples/react/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.7 + specifier: ^0.3.8 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.41 + specifier: ^1.0.42 version: link:../../../packages/query-db-collection '@tanstack/react-db': - specifier: ^0.1.87 + specifier: ^0.1.88 version: link:../../../packages/react-db '@tanstack/react-router': specifier: ^1.159.5 @@ -743,7 +743,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.87 + specifier: ^0.1.88 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 @@ -846,16 +846,16 @@ importers: examples/solid/todo: dependencies: '@tanstack/electric-db-collection': - specifier: ^0.3.7 + specifier: ^0.3.8 version: link:../../../packages/electric-db-collection '@tanstack/query-core': specifier: ^5.90.20 version: 5.90.20 '@tanstack/query-db-collection': - specifier: ^1.0.41 + specifier: ^1.0.42 version: link:../../../packages/query-db-collection '@tanstack/solid-db': - specifier: ^0.2.23 + specifier: ^0.2.24 version: link:../../../packages/solid-db '@tanstack/solid-router': specifier: ^1.159.5 @@ -864,7 +864,7 @@ importers: specifier: ^1.159.5 version: 1.159.5(@tanstack/react-router@1.159.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(solid-js@1.9.11)(vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)))(vite@7.3.2(@types/node@25.2.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass@1.90.0)(terser@5.44.0)(tsx@4.21.0)(yaml@2.8.1)) '@tanstack/trailbase-db-collection': - specifier: ^0.1.87 + specifier: ^0.1.88 version: link:../../../packages/trailbase-db-collection cors: specifier: ^2.8.6 From 38af8c59b7766ebb1b9f53941c9916dd503508ea Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Wed, 24 Jun 2026 10:45:59 +0200 Subject: [PATCH 10/11] Rename saveFileTanStack => save, deleteFIleTanStack => delete. --- docs/collections/powersync-collection.md | 8 ++++---- .../powersync-db-collection/src/attachments.ts | 13 +++++-------- .../tests/attachments.test.ts | 18 +++++++++--------- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/docs/collections/powersync-collection.md b/docs/collections/powersync-collection.md index b484c5bb92..8799f96aa8 100644 --- a/docs/collections/powersync-collection.md +++ b/docs/collections/powersync-collection.md @@ -1212,10 +1212,10 @@ const watchAttachments = async (onUpdate, abortSignal) => { ### 4. Save an attachment atomically with related data -`saveFileTanStack` writes the file, inserts the attachment record into your collection, and runs your `updateHook` mutations in the same transaction. Use the hook to insert or update the row that references the new attachment, so both land together or not at all. +`save` writes the file, inserts the attachment record into your collection, and runs your `updateHook` mutations in the same transaction. Use the hook to insert or update the row that references the new attachment, so both land together or not at all. ```ts -await attachmentQueue.saveFileTanStack({ +await attachmentQueue.save({ data, // file bytes (ArrayBuffer / base64, per your local adapter) fileExtension: "jpg", updateHook: async (attachmentRecord) => { @@ -1233,10 +1233,10 @@ await attachmentQueue.saveFileTanStack({ ### 5. Delete an attachment and detach it from the row -`deleteFileTanStack` queues the file for deletion and runs your `updateHook` in the same transaction. Clear the foreign key so the row and the attachment stay consistent. +`delete` queues the file for deletion and runs your `updateHook` in the same transaction. Clear the foreign key so the row and the attachment stay consistent. ```ts -await attachmentQueue.deleteFileTanStack({ +await attachmentQueue.delete({ id: photo_id, updateHook: async () => { listsCollection.update(listId, (draft) => { diff --git a/packages/powersync-db-collection/src/attachments.ts b/packages/powersync-db-collection/src/attachments.ts index cbda937226..f70f6494ec 100644 --- a/packages/powersync-db-collection/src/attachments.ts +++ b/packages/powersync-db-collection/src/attachments.ts @@ -25,7 +25,7 @@ export type TanStackDBAttachmentQueueOptions = AttachmentQueueOptions & { attachmentsCollection: Collection } -export interface SaveFileTanStackOptions { +export interface SaveOptions { data: AttachmentData fileExtension: string mediaType?: string @@ -38,7 +38,7 @@ export interface SaveFileTanStackOptions { updateHook?: (attachment: AttachmentQueueRow) => Promise } -export interface DeleteFileTanStackOptions { +export interface DeleteOptions { id: string /** * * Note that this is called inside a synchronous TanStackDB transaction, @@ -70,14 +70,14 @@ export class TanStackDBAttachmentQueue extends AttachmentQueue { * Exposes an `updateHook` option which is called inside a TanStackDB transaction, * relational associations with the provided attachment ID should be made in this hook. */ - async saveFileTanStack({ + async save({ data, fileExtension, mediaType, metaData, id, updateHook, - }: SaveFileTanStackOptions): Promise { + }: SaveOptions): Promise { const resolvedId = id ?? (await this.generateAttachmentId()) const filename = `${resolvedId}.${fileExtension}` const localUri = this.localStorage.getLocalUri(filename) @@ -128,10 +128,7 @@ export class TanStackDBAttachmentQueue extends AttachmentQueue { * Exposes an `updateHook` option which is called inside a TanStackDB transaction, * relational associations with the provided attachment ID should be cleaned up in this hook. */ - async deleteFileTanStack({ - id, - updateHook, - }: DeleteFileTanStackOptions): Promise { + async delete({ id, updateHook }: DeleteOptions): Promise { await this.withAttachmentContext(async (ctx) => { const tanStackDBTransaction = createTransaction({ autoCommit: false, diff --git a/packages/powersync-db-collection/tests/attachments.test.ts b/packages/powersync-db-collection/tests/attachments.test.ts index 411c7cd753..e13ea3dbf9 100644 --- a/packages/powersync-db-collection/tests/attachments.test.ts +++ b/packages/powersync-db-collection/tests/attachments.test.ts @@ -210,13 +210,13 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { ) } - describe(`saveFileTanStack`, () => { + describe(`save`, () => { it(`writes the local file and inserts a QUEUED_UPLOAD row into the collection`, async () => { const { createQueue, attachmentsCollection, localStorage } = await setup() const queue = createQueue() const data = new Uint8Array(123).fill(42).buffer - const record = await queue.saveFileTanStack({ + const record = await queue.save({ data, fileExtension: `jpg`, mediaType: `image/jpeg`, @@ -245,7 +245,7 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { const queue = createQueue() const userId = randomUUID() - const record = await queue.saveFileTanStack({ + const record = await queue.save({ data: createMockJpegBuffer(), fileExtension: `jpg`, updateHook: async (attachment) => { @@ -284,7 +284,7 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { await queue.startSync() const userId = randomUUID() - const record = await queue.saveFileTanStack({ + const record = await queue.save({ data: createMockJpegBuffer(), fileExtension: `jpg`, updateHook: async (attachment) => { @@ -313,7 +313,7 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { const queue = createQueue() const id = `my-custom-id` - const record = await queue.saveFileTanStack({ + const record = await queue.save({ id, data: createMockJpegBuffer(), fileExtension: `png`, @@ -324,7 +324,7 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { }) }) - describe(`deleteFileTanStack`, () => { + describe(`delete file`, () => { it(`queues an existing attachment for deletion and removes the local file`, async () => { const { createQueue, @@ -336,7 +336,7 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { await queue.startSync() const userId = randomUUID() - const record = await queue.saveFileTanStack({ + const record = await queue.save({ data: createMockJpegBuffer(), fileExtension: `jpg`, updateHook: async (attachment) => { @@ -355,7 +355,7 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { AttachmentState.SYNCED, ) - await queue.deleteFileTanStack({ + await queue.delete({ id: record.id, updateHook: async (attachment) => { usersCollection.update(userId, (draft) => { @@ -389,7 +389,7 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { const hook = vi.fn() await expect( - queue.deleteFileTanStack({ id: `does-not-exist`, updateHook: hook }), + queue.delete({ id: `does-not-exist`, updateHook: hook }), ).rejects.toThrow(/not found/i) // The failing transaction must not have run the hook or touched state. From d82ffc2b031e989143f0dde3ad630a0c7ab579a4 Mon Sep 17 00:00:00 2001 From: Christiaan Landman Date: Wed, 24 Jun 2026 16:04:29 +0200 Subject: [PATCH 11/11] Made `updateHook` synchronous and updated `AttachmentQueueRow` typing. --- packages/powersync-db-collection/package.json | 6 ++-- .../src/attachments.ts | 28 +++++++--------- .../tests/attachments.test.ts | 4 +-- pnpm-lock.yaml | 33 +++++++------------ 4 files changed, 29 insertions(+), 42 deletions(-) diff --git a/packages/powersync-db-collection/package.json b/packages/powersync-db-collection/package.json index 9374371824..fde2ae3828 100644 --- a/packages/powersync-db-collection/package.json +++ b/packages/powersync-db-collection/package.json @@ -59,11 +59,11 @@ "p-defer": "^4.0.1" }, "peerDependencies": { - "@powersync/common": "^1.55.0" + "@powersync/common": "^1.57.0" }, "devDependencies": { - "@powersync/common": "1.55.0", - "@powersync/node": "0.18.1", + "@powersync/common": "1.57.0", + "@powersync/node": "0.19.2", "@types/debug": "^4.1.12", "@vitest/coverage-istanbul": "^3.2.4", "better-sqlite3": "^12.6.2" diff --git a/packages/powersync-db-collection/src/attachments.ts b/packages/powersync-db-collection/src/attachments.ts index f70f6494ec..d3c48fb1d8 100644 --- a/packages/powersync-db-collection/src/attachments.ts +++ b/packages/powersync-db-collection/src/attachments.ts @@ -1,8 +1,6 @@ import { AttachmentQueue, - AttachmentState, - AttachmentTable, - Schema, + AttachmentState } from '@powersync/common' import { createTransaction } from '@tanstack/db' import { PowerSyncTransactor } from './PowerSyncTransactor' @@ -11,10 +9,10 @@ import type { AbstractPowerSyncDatabase, AttachmentData, AttachmentQueueOptions, -} from '@powersync/common' -import type { Collection } from '@tanstack/db' -export type AttachmentQueueRow = (typeof _tmpSchema)['types']['attachments'] + AttachmentTable} from '@powersync/common' +import type { Collection } from '@tanstack/db' +import type { OptionalExtractedTable } from './helpers' export type TanStackDBAttachmentQueueOptions = AttachmentQueueOptions & { /** @@ -32,24 +30,22 @@ export interface SaveOptions { metaData?: string id?: string /** - * Note that this is called inside a synchronous TanStackDB transaction, - * any mutations made to other collections will be in the same transaction. + * Called within the same TanStackDB transaction as the attachment write, + * so any mutations made to other collections are committed atomically with it. */ - updateHook?: (attachment: AttachmentQueueRow) => Promise + updateHook?: (attachment: AttachmentQueueRow) => void } export interface DeleteOptions { id: string - /** * - * Note that this is called inside a synchronous TanStackDB transaction, - * any mutations made to other collections will be in the same transaction. + /** + * Called within the same TanStackDB transaction as the attachment write, + * so any mutations made to other collections are committed atomically with it. */ - updateHook?: (attachment: AttachmentQueueRow) => Promise + updateHook?: (attachment: AttachmentQueueRow) => void } -const _tmpSchema = new Schema({ - attachments: new AttachmentTable(), -}) +export type AttachmentQueueRow = OptionalExtractedTable /** * A custom extension of the PowerSyncAttachmentQueue for TanStackDB. diff --git a/packages/powersync-db-collection/tests/attachments.test.ts b/packages/powersync-db-collection/tests/attachments.test.ts index e13ea3dbf9..c5925e234f 100644 --- a/packages/powersync-db-collection/tests/attachments.test.ts +++ b/packages/powersync-db-collection/tests/attachments.test.ts @@ -229,7 +229,7 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { expect(record.has_synced).toBe(0) // The file should exist on disk at the returned local_uri. - expect(await localStorage.fileExists(record.local_uri)).toBe(true) + expect(await localStorage.fileExists(record.local_uri!)).toBe(true) // The row should be reflected in the collection once it syncs back. await waitForState( @@ -379,7 +379,7 @@ describePowerSync(`PowerSync AttachmentQueue (TanStackDB)`, () => { () => expect(attachmentsCollection.get(record.id)).toBeUndefined(), { timeout: WAIT_TIMEOUT, interval: 50 }, ) - expect(await localStorage.fileExists(record.local_uri)).toBe(false) + expect(await localStorage.fileExists(record.local_uri!)).toBe(false) }) it(`throws for an unknown id and commits nothing`, async () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3852ece3ed..6e61ea7bdd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1338,11 +1338,11 @@ importers: version: 4.0.1 devDependencies: '@powersync/common': - specifier: 1.55.0 - version: 1.55.0 + specifier: 1.57.0 + version: 1.57.0 '@powersync/node': - specifier: 0.18.1 - version: 0.18.1(@powersync/common@1.55.0)(better-sqlite3@12.8.0) + specifier: 0.19.2 + version: 0.19.2(@powersync/common@1.57.0)(better-sqlite3@12.8.0) '@types/debug': specifier: ^4.1.12 version: 4.1.12 @@ -4831,13 +4831,13 @@ packages: '@poppinss/exception@1.2.3': resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} - '@powersync/common@1.55.0': - resolution: {integrity: sha512-c9K2Gac9wOB4ijVnQT388g7Yeuh6VrfJZFXaL6Rag9Fp7F8JzAYcrWaULLTdMnzm7VW6b6XT5M2ip5yLS8oSBg==} + '@powersync/common@1.57.0': + resolution: {integrity: sha512-uYccCxK5mwahELRouY3YY584TZgjFU8wPPKZQQ6sAOUoMikV8D/+v+UYsNI280MKMnhFqLkxk4TPZIG7ArIzTQ==} - '@powersync/node@0.18.1': - resolution: {integrity: sha512-fcTICgs61CAEb39xiC7pedYsPgbjUInJ/47dr7RIdnEHpAgjWH8bW95/b70qK1fQUANy9lKBBF3PcmfswVgfCw==} + '@powersync/node@0.19.2': + resolution: {integrity: sha512-lF7v/rkiLujAojn7Vjgvs1AibhL5zlEQVYO0iCUGoE1S1Hw7lxfUvAa1mTneKWCEmj0EC9yQBHkPUyBDZXVdLA==} peerDependencies: - '@powersync/common': ^1.49.0 + '@powersync/common': ^1.57.0 better-sqlite3: 12.x peerDependenciesMeta: better-sqlite3: @@ -6799,9 +6799,6 @@ packages: async-limiter@1.0.1: resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} - async-mutex@0.5.0: - resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==} - asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -16480,15 +16477,13 @@ snapshots: '@poppinss/exception@1.2.3': {} - '@powersync/common@1.55.0': + '@powersync/common@1.57.0': dependencies: js-logger: 1.6.1 - '@powersync/node@0.18.1(@powersync/common@1.55.0)(better-sqlite3@12.8.0)': + '@powersync/node@0.19.2(@powersync/common@1.57.0)(better-sqlite3@12.8.0)': dependencies: - '@powersync/common': 1.55.0 - async-mutex: 0.5.0 - bson: 6.10.4 + '@powersync/common': 1.57.0 comlink: 4.4.2 undici: 7.24.4 optionalDependencies: @@ -18956,10 +18951,6 @@ snapshots: async-limiter@1.0.1: {} - async-mutex@0.5.0: - dependencies: - tslib: 2.8.1 - asynckit@0.4.0: {} at-least-node@1.0.0: {}