From fb302ba01f7faebc800b2170839ba039c864aff5 Mon Sep 17 00:00:00 2001 From: Kevin De Porre Date: Wed, 17 Jun 2026 15:45:52 +0200 Subject: [PATCH 1/7] fix(db): use safe randomUUID helper for non-secure browser contexts (#1541) --- .changeset/safe-random-uuid.md | 9 ++++ 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 | 71 +++++++++++++++++++++++++ 9 files changed, 140 insertions(+), 7 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..acac661be5 --- /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/db/src/collection/index.ts b/packages/db/src/collection/index.ts index e51eb998d6..f5b87fb75c 100644 --- a/packages/db/src/collection/index.ts +++ b/packages/db/src/collection/index.ts @@ -1,3 +1,4 @@ +import { randomUUID } 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 = randomUUID() } // 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..ab9a33c837 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 { randomUUID } 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: randomUUID(), 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: randomUUID(), 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: randomUUID(), 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..0321f9aef5 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 { randomUUID } 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..169c2691ca 100644 --- a/packages/db/src/local-only.ts +++ b/packages/db/src/local-only.ts @@ -1,3 +1,4 @@ +import { randomUUID } 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 ?? randomUUID() // 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..c886032eee 100644 --- a/packages/db/src/local-storage.ts +++ b/packages/db/src/local-storage.ts @@ -1,3 +1,4 @@ +import { randomUUID } 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 randomUUID() } /** diff --git a/packages/db/src/transactions.ts b/packages/db/src/transactions.ts index 84e2bb0d5d..d7137490e5 100644 --- a/packages/db/src/transactions.ts +++ b/packages/db/src/transactions.ts @@ -1,4 +1,5 @@ import { createDeferred } from './deferred' +import { randomUUID } 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 ?? randomUUID() 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..d6912e8e32 --- /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 randomUUID(): 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..9fb3dc4115 --- /dev/null +++ b/packages/db/tests/uuid.test.ts @@ -0,0 +1,71 @@ +import { afterEach, describe, expect, it, vi } from "vitest" +import { randomUUID } 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(`randomUUID 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 = randomUUID() + 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 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 = randomUUID() + 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 = randomUUID() + 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(() => randomUUID()).toThrow(/No secure random number generator/) + }) +}) From b960c25d76061548a4ddad9f8fa94c5bca3c2b31 Mon Sep 17 00:00:00 2001 From: Kevin De Porre Date: Wed, 17 Jun 2026 15:46:29 +0200 Subject: [PATCH 2/7] fix(db-sqlite-persistence-core): use safe randomUUID helper (#1541) --- packages/db-sqlite-persistence-core/src/index.ts | 2 ++ .../db-sqlite-persistence-core/src/persisted.ts | 16 ++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/db-sqlite-persistence-core/src/index.ts b/packages/db-sqlite-persistence-core/src/index.ts index 921b92f3b0..b581dae997 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 { randomUUID } 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..7a3466bb98 100644 --- a/packages/db-sqlite-persistence-core/src/persisted.ts +++ b/packages/db-sqlite-persistence-core/src/persisted.ts @@ -1,4 +1,4 @@ -import { compileSingleRowExpression, toBooleanPredicate } from '@tanstack/db' +import { compileSingleRowExpression, randomUUID, toBooleanPredicate } from '@tanstack/db' import { InvalidPersistedCollectionConfigError, InvalidPersistedCollectionCoordinatorError, @@ -440,7 +440,7 @@ type SyncControlFns = { export class SingleProcessCoordinator implements PersistedCollectionCoordinator { private readonly nodeId: string - constructor(nodeId: string = crypto.randomUUID()) { + constructor(nodeId: string = randomUUID()) { this.nodeId = nodeId } @@ -467,7 +467,7 @@ export class SingleProcessCoordinator implements PersistedCollectionCoordinator public pullSince(): Promise { return Promise.resolve({ type: `rpc:pullSince:res`, - rpcId: crypto.randomUUID(), + rpcId: randomUUID(), ok: true, latestTerm: 1, latestSeq: 0, @@ -1387,7 +1387,7 @@ class PersistedCollectionRuntime< this.createTxCommittedPayload({ term: streamPosition.term, seq: streamPosition.seq, - txId: crypto.randomUUID(), + txId: randomUUID(), latestRowVersion: streamPosition.rowVersion, changedRows: [], deletedKeys: [], @@ -1427,7 +1427,7 @@ class PersistedCollectionRuntime< streamPosition: { term: number; seq: number; rowVersion: number }, ): PersistedTx { return { - txId: crypto.randomUUID(), + txId: randomUUID(), term: streamPosition.term, seq: streamPosition.seq, rowVersion: streamPosition.rowVersion, @@ -1471,7 +1471,7 @@ class PersistedCollectionRuntime< streamPosition: { term: number; seq: number; rowVersion: number }, ): PersistedTx { return { - txId: crypto.randomUUID(), + txId: randomUUID(), term: streamPosition.term, seq: streamPosition.seq, rowVersion: streamPosition.rowVersion, @@ -2607,7 +2607,7 @@ export function persistedCollectionOptions< const { schemaVersion, ...syncOptions } = options const collectionId = - syncOptions.id ?? `persisted-collection:${crypto.randomUUID()}` + syncOptions.id ?? `persisted-collection:${randomUUID()}` const persistence = resolvePersistenceForCollection( syncOptions.persistence, { @@ -2635,7 +2635,7 @@ export function persistedCollectionOptions< const { schemaVersion, ...localOnlyOptions } = options const collectionId = - localOnlyOptions.id ?? `persisted-collection:${crypto.randomUUID()}` + localOnlyOptions.id ?? `persisted-collection:${randomUUID()}` const persistence = resolvePersistenceForCollection( localOnlyOptions.persistence, { From ae7c3a33430520d0a24509a7d25d107e5a0ef82c Mon Sep 17 00:00:00 2001 From: Kevin De Porre Date: Wed, 17 Jun 2026 15:47:49 +0200 Subject: [PATCH 3/7] fix(browser-db-sqlite-persistence): use safe randomUUID helper (#1541) --- .../src/browser-coordinator.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/browser-db-sqlite-persistence/src/browser-coordinator.ts b/packages/browser-db-sqlite-persistence/src/browser-coordinator.ts index 5b518ea387..295fc69f67 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 { randomUUID } 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 = randomUUID() 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: randomUUID(), options, }) @@ -233,7 +234,7 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina error?: string }>(collectionId, { type: `rpc:ensurePersistedIndex:req`, - rpcId: crypto.randomUUID(), + rpcId: randomUUID(), 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: randomUUID(), + envelopeId: randomUUID(), mutations, }) } return this.sendRPC(collectionId, { type: `rpc:applyLocalMutations:req`, - rpcId: crypto.randomUUID(), - envelopeId: crypto.randomUUID(), + rpcId: randomUUID(), + envelopeId: randomUUID(), 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: randomUUID(), fromRowVersion, }) } return this.sendRPC(collectionId, { type: `rpc:pullSince:req`, - rpcId: crypto.randomUUID(), + rpcId: randomUUID(), fromRowVersion, }) } @@ -663,7 +664,7 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina // Build and apply the persisted transaction const tx = { - txId: crypto.randomUUID(), + txId: randomUUID(), term, seq, rowVersion, From e298d90979d7b363b58d410b46446ee6e9e0d2ba Mon Sep 17 00:00:00 2001 From: Kevin De Porre Date: Wed, 17 Jun 2026 15:47:49 +0200 Subject: [PATCH 4/7] fix(electron-db-sqlite-persistence): use safe randomUUID helper (#1541) --- .../src/electron-coordinator.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/electron-db-sqlite-persistence/src/electron-coordinator.ts b/packages/electron-db-sqlite-persistence/src/electron-coordinator.ts index ea9735e709..42708f417b 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 { randomUUID } 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 = randomUUID() 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: randomUUID(), options, }) @@ -233,7 +234,7 @@ export class ElectronCollectionCoordinator implements PersistedCollectionCoordin error?: string }>(collectionId, { type: `rpc:ensurePersistedIndex:req`, - rpcId: crypto.randomUUID(), + rpcId: randomUUID(), 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: randomUUID(), + envelopeId: randomUUID(), mutations, }) } return this.sendRPC(collectionId, { type: `rpc:applyLocalMutations:req`, - rpcId: crypto.randomUUID(), - envelopeId: crypto.randomUUID(), + rpcId: randomUUID(), + envelopeId: randomUUID(), 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: randomUUID(), fromRowVersion, }) } return this.sendRPC(collectionId, { type: `rpc:pullSince:req`, - rpcId: crypto.randomUUID(), + rpcId: randomUUID(), fromRowVersion, }) } @@ -663,7 +664,7 @@ export class ElectronCollectionCoordinator implements PersistedCollectionCoordin // Build and apply the persisted transaction const tx = { - txId: crypto.randomUUID(), + txId: randomUUID(), term, seq, rowVersion, From e7d3e36916c12861b5f101983b4305d0ea2437e4 Mon Sep 17 00:00:00 2001 From: Kevin De Porre Date: Wed, 17 Jun 2026 15:48:55 +0200 Subject: [PATCH 5/7] fix(offline-transactions): use safe randomUUID helper (#1541) --- packages/offline-transactions/src/OfflineExecutor.ts | 6 +++--- packages/offline-transactions/src/api/OfflineTransaction.ts | 6 +++--- .../src/coordination/BroadcastChannelLeader.ts | 3 ++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/offline-transactions/src/OfflineExecutor.ts b/packages/offline-transactions/src/OfflineExecutor.ts index 8f443277c2..3fe1bee1dd 100644 --- a/packages/offline-transactions/src/OfflineExecutor.ts +++ b/packages/offline-transactions/src/OfflineExecutor.ts @@ -1,5 +1,5 @@ // Storage adapters -import { createOptimisticAction, createTransaction } from '@tanstack/db' +import { createOptimisticAction, createTransaction, randomUUID } from '@tanstack/db' import { IndexedDBAdapter } from './storage/IndexedDBAdapter' import { LocalStorageAdapter } from './storage/LocalStorageAdapter' @@ -367,7 +367,7 @@ export class OfflineExecutor { mutationFn: (params) => mutationFn({ ...params, - idempotencyKey: options.idempotencyKey || crypto.randomUUID(), + idempotencyKey: options.idempotencyKey || randomUUID(), }), metadata: options.metadata, }) @@ -399,7 +399,7 @@ export class OfflineExecutor { mutationFn({ ...vars, ...params, - idempotencyKey: crypto.randomUUID(), + idempotencyKey: randomUUID(), }), onMutate: options.onMutate, }) diff --git a/packages/offline-transactions/src/api/OfflineTransaction.ts b/packages/offline-transactions/src/api/OfflineTransaction.ts index 32c96fd26a..f96ae80807 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, randomUUID } 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 = randomUUID() this.mutationFnName = options.mutationFnName this.autoCommit = options.autoCommit ?? true - this.idempotencyKey = options.idempotencyKey ?? crypto.randomUUID() + this.idempotencyKey = options.idempotencyKey ?? randomUUID() 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..1173ae772b 100644 --- a/packages/offline-transactions/src/coordination/BroadcastChannelLeader.ts +++ b/packages/offline-transactions/src/coordination/BroadcastChannelLeader.ts @@ -1,3 +1,4 @@ +import { randomUUID } 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 = randomUUID() this.setupChannel() } From a3fec51aa6c08beb57e4ecdd83ab187484cb4bc8 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 13:50:23 +0000 Subject: [PATCH 6/7] ci: apply automated fixes --- .changeset/safe-random-uuid.md | 10 +++++----- packages/db-sqlite-persistence-core/src/persisted.ts | 6 +++++- packages/db/src/collection/index.ts | 2 +- packages/db/src/local-only.ts | 2 +- packages/db/src/local-storage.ts | 2 +- packages/db/src/utils/uuid.ts | 2 +- packages/db/tests/uuid.test.ts | 4 ++-- packages/offline-transactions/src/OfflineExecutor.ts | 6 +++++- 8 files changed, 21 insertions(+), 13 deletions(-) diff --git a/.changeset/safe-random-uuid.md b/.changeset/safe-random-uuid.md index acac661be5..a10264d535 100644 --- a/.changeset/safe-random-uuid.md +++ b/.changeset/safe-random-uuid.md @@ -1,9 +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 +'@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/db-sqlite-persistence-core/src/persisted.ts b/packages/db-sqlite-persistence-core/src/persisted.ts index 7a3466bb98..ba3c890824 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, randomUUID, toBooleanPredicate } from '@tanstack/db' +import { + compileSingleRowExpression, + randomUUID, + toBooleanPredicate, +} from '@tanstack/db' import { InvalidPersistedCollectionConfigError, InvalidPersistedCollectionCoordinatorError, diff --git a/packages/db/src/collection/index.ts b/packages/db/src/collection/index.ts index f5b87fb75c..416ab8791d 100644 --- a/packages/db/src/collection/index.ts +++ b/packages/db/src/collection/index.ts @@ -1,4 +1,4 @@ -import { randomUUID } from "../utils/uuid" +import { randomUUID } from '../utils/uuid' import { CollectionConfigurationError, CollectionRequiresConfigError, diff --git a/packages/db/src/local-only.ts b/packages/db/src/local-only.ts index 169c2691ca..43ee15728e 100644 --- a/packages/db/src/local-only.ts +++ b/packages/db/src/local-only.ts @@ -1,4 +1,4 @@ -import { randomUUID } from "./utils/uuid" +import { randomUUID } from './utils/uuid' import type { BaseCollectionConfig, CollectionConfig, diff --git a/packages/db/src/local-storage.ts b/packages/db/src/local-storage.ts index c886032eee..3a9bde82d7 100644 --- a/packages/db/src/local-storage.ts +++ b/packages/db/src/local-storage.ts @@ -1,4 +1,4 @@ -import { randomUUID } from "./utils/uuid" +import { randomUUID } from './utils/uuid' import { InvalidStorageDataFormatError, InvalidStorageObjectFormatError, diff --git a/packages/db/src/utils/uuid.ts b/packages/db/src/utils/uuid.ts index d6912e8e32..fc988090a3 100644 --- a/packages/db/src/utils/uuid.ts +++ b/packages/db/src/utils/uuid.ts @@ -40,6 +40,6 @@ export function randomUUID(): string { } throw new Error( - `No secure random number generator available: neither crypto.randomUUID nor crypto.getRandomValues is defined in this environment.` + `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 index 9fb3dc4115..d8c007d3ca 100644 --- a/packages/db/tests/uuid.test.ts +++ b/packages/db/tests/uuid.test.ts @@ -1,5 +1,5 @@ -import { afterEach, describe, expect, it, vi } from "vitest" -import { randomUUID } from "../src/utils/uuid" +import { afterEach, describe, expect, it, vi } from 'vitest' +import { randomUUID } 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}$/ diff --git a/packages/offline-transactions/src/OfflineExecutor.ts b/packages/offline-transactions/src/OfflineExecutor.ts index 3fe1bee1dd..3fa875ccab 100644 --- a/packages/offline-transactions/src/OfflineExecutor.ts +++ b/packages/offline-transactions/src/OfflineExecutor.ts @@ -1,5 +1,9 @@ // Storage adapters -import { createOptimisticAction, createTransaction, randomUUID } from '@tanstack/db' +import { + createOptimisticAction, + createTransaction, + randomUUID, +} from '@tanstack/db' import { IndexedDBAdapter } from './storage/IndexedDBAdapter' import { LocalStorageAdapter } from './storage/LocalStorageAdapter' From 713842e9e7e5dd5f687b50035a5409800e32e187 Mon Sep 17 00:00:00 2001 From: Kevin De Porre Date: Wed, 17 Jun 2026 16:01:06 +0200 Subject: [PATCH 7/7] refactor: rename randomUUID helper to safeRandomUUID and add crypto-undefined test --- .../src/browser-coordinator.ts | 22 +++++++++---------- .../db-sqlite-persistence-core/src/index.ts | 2 +- .../src/persisted.ts | 16 +++++++------- packages/db/src/collection/index.ts | 4 ++-- packages/db/src/collection/mutations.ts | 8 +++---- packages/db/src/index.ts | 2 +- packages/db/src/local-only.ts | 4 ++-- packages/db/src/local-storage.ts | 4 ++-- packages/db/src/transactions.ts | 4 ++-- packages/db/src/utils/uuid.ts | 2 +- packages/db/tests/uuid.test.ts | 19 ++++++++++------ .../src/electron-coordinator.ts | 22 +++++++++---------- .../src/OfflineExecutor.ts | 6 ++--- .../src/api/OfflineTransaction.ts | 6 ++--- .../coordination/BroadcastChannelLeader.ts | 4 ++-- 15 files changed, 65 insertions(+), 60 deletions(-) diff --git a/packages/browser-db-sqlite-persistence/src/browser-coordinator.ts b/packages/browser-db-sqlite-persistence/src/browser-coordinator.ts index 295fc69f67..1babddc5a7 100644 --- a/packages/browser-db-sqlite-persistence/src/browser-coordinator.ts +++ b/packages/browser-db-sqlite-persistence/src/browser-coordinator.ts @@ -1,4 +1,4 @@ -import { randomUUID } from '@tanstack/db-sqlite-persistence-core' +import { safeRandomUUID } from '@tanstack/db-sqlite-persistence-core' import type { ApplyLocalMutationsResponse, PersistedCollectionCoordinator, @@ -119,7 +119,7 @@ export type BrowserCollectionCoordinatorOptions = { // --------------------------------------------------------------------------- export class BrowserCollectionCoordinator implements PersistedCollectionCoordinator { - private readonly nodeId = randomUUID() + private readonly nodeId = safeRandomUUID() private readonly dbName: string private adapter: AdapterWithPullSince | null private readonly channel: BroadcastChannel @@ -206,7 +206,7 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina error?: string }>(collectionId, { type: `rpc:ensureRemoteSubset:req`, - rpcId: randomUUID(), + rpcId: safeRandomUUID(), options, }) @@ -234,7 +234,7 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina error?: string }>(collectionId, { type: `rpc:ensurePersistedIndex:req`, - rpcId: randomUUID(), + rpcId: safeRandomUUID(), signature, spec, }) @@ -253,16 +253,16 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina if (this.isLeader(collectionId)) { return this.handleApplyLocalMutations(collectionId, { type: `rpc:applyLocalMutations:req`, - rpcId: randomUUID(), - envelopeId: randomUUID(), + rpcId: safeRandomUUID(), + envelopeId: safeRandomUUID(), mutations, }) } return this.sendRPC(collectionId, { type: `rpc:applyLocalMutations:req`, - rpcId: randomUUID(), - envelopeId: randomUUID(), + rpcId: safeRandomUUID(), + envelopeId: safeRandomUUID(), mutations, }) } @@ -274,14 +274,14 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina if (this.isLeader(collectionId)) { return this.handlePullSince(collectionId, { type: `rpc:pullSince:req`, - rpcId: randomUUID(), + rpcId: safeRandomUUID(), fromRowVersion, }) } return this.sendRPC(collectionId, { type: `rpc:pullSince:req`, - rpcId: randomUUID(), + rpcId: safeRandomUUID(), fromRowVersion, }) } @@ -664,7 +664,7 @@ export class BrowserCollectionCoordinator implements PersistedCollectionCoordina // Build and apply the persisted transaction const tx = { - txId: 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 b581dae997..9e2bb9faae 100644 --- a/packages/db-sqlite-persistence-core/src/index.ts +++ b/packages/db-sqlite-persistence-core/src/index.ts @@ -2,4 +2,4 @@ export * from './persisted' export * from './errors' export * from './sqlite-core-adapter' // Re-export for use in non-secure browser contexts (see #1541) -export { randomUUID } from '@tanstack/db' +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 ba3c890824..12cf8319c3 100644 --- a/packages/db-sqlite-persistence-core/src/persisted.ts +++ b/packages/db-sqlite-persistence-core/src/persisted.ts @@ -1,6 +1,6 @@ import { compileSingleRowExpression, - randomUUID, + safeRandomUUID, toBooleanPredicate, } from '@tanstack/db' import { @@ -444,7 +444,7 @@ type SyncControlFns = { export class SingleProcessCoordinator implements PersistedCollectionCoordinator { private readonly nodeId: string - constructor(nodeId: string = randomUUID()) { + constructor(nodeId: string = safeRandomUUID()) { this.nodeId = nodeId } @@ -471,7 +471,7 @@ export class SingleProcessCoordinator implements PersistedCollectionCoordinator public pullSince(): Promise { return Promise.resolve({ type: `rpc:pullSince:res`, - rpcId: randomUUID(), + rpcId: safeRandomUUID(), ok: true, latestTerm: 1, latestSeq: 0, @@ -1391,7 +1391,7 @@ class PersistedCollectionRuntime< this.createTxCommittedPayload({ term: streamPosition.term, seq: streamPosition.seq, - txId: randomUUID(), + txId: safeRandomUUID(), latestRowVersion: streamPosition.rowVersion, changedRows: [], deletedKeys: [], @@ -1431,7 +1431,7 @@ class PersistedCollectionRuntime< streamPosition: { term: number; seq: number; rowVersion: number }, ): PersistedTx { return { - txId: randomUUID(), + txId: safeRandomUUID(), term: streamPosition.term, seq: streamPosition.seq, rowVersion: streamPosition.rowVersion, @@ -1475,7 +1475,7 @@ class PersistedCollectionRuntime< streamPosition: { term: number; seq: number; rowVersion: number }, ): PersistedTx { return { - txId: randomUUID(), + txId: safeRandomUUID(), term: streamPosition.term, seq: streamPosition.seq, rowVersion: streamPosition.rowVersion, @@ -2611,7 +2611,7 @@ export function persistedCollectionOptions< const { schemaVersion, ...syncOptions } = options const collectionId = - syncOptions.id ?? `persisted-collection:${randomUUID()}` + syncOptions.id ?? `persisted-collection:${safeRandomUUID()}` const persistence = resolvePersistenceForCollection( syncOptions.persistence, { @@ -2639,7 +2639,7 @@ export function persistedCollectionOptions< const { schemaVersion, ...localOnlyOptions } = options const collectionId = - localOnlyOptions.id ?? `persisted-collection:${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 416ab8791d..137fd5f595 100644 --- a/packages/db/src/collection/index.ts +++ b/packages/db/src/collection/index.ts @@ -1,4 +1,4 @@ -import { randomUUID } from '../utils/uuid' +import { safeRandomUUID } from '../utils/uuid' import { CollectionConfigurationError, CollectionRequiresConfigError, @@ -330,7 +330,7 @@ export class CollectionImpl< if (config.id) { this.id = config.id } else { - this.id = 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 ab9a33c837..abfb6693eb 100644 --- a/packages/db/src/collection/mutations.ts +++ b/packages/db/src/collection/mutations.ts @@ -1,5 +1,5 @@ import { withArrayChangeTracking, withChangeTracking } from '../proxy' -import { randomUUID } from '../utils/uuid' +import { safeRandomUUID } from '../utils/uuid' import { createTransaction, getActiveTransaction } from '../transactions' import { DeleteKeyNotFoundError, @@ -194,7 +194,7 @@ export class CollectionMutationsManager< const globalKey = this.generateGlobalKey(key, item) const mutation: PendingMutation = { - mutationId: randomUUID(), + mutationId: safeRandomUUID(), original: {}, modified: validatedData, // Pick the values from validatedData based on what's passed in - this is for cases @@ -367,7 +367,7 @@ export class CollectionMutationsManager< const globalKey = this.generateGlobalKey(modifiedItemId, modifiedItem) return { - mutationId: randomUUID(), + mutationId: safeRandomUUID(), original: originalItem, modified: modifiedItem, // Pick the values from modifiedItem based on what's passed in - this is for cases @@ -498,7 +498,7 @@ export class CollectionMutationsManager< `delete`, CollectionImpl > = { - mutationId: 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 0321f9aef5..347a3119b5 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -81,7 +81,7 @@ export { } from './query/effect.js' // UUID helper (safe in non-secure browser contexts, see #1541) -export { randomUUID } from './utils/uuid.js' +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' diff --git a/packages/db/src/local-only.ts b/packages/db/src/local-only.ts index 43ee15728e..afcf3c9a76 100644 --- a/packages/db/src/local-only.ts +++ b/packages/db/src/local-only.ts @@ -1,4 +1,4 @@ -import { randomUUID } from './utils/uuid' +import { safeRandomUUID } from './utils/uuid' import type { BaseCollectionConfig, CollectionConfig, @@ -183,7 +183,7 @@ export function localOnlyCollectionOptions< const { initialData, onInsert, onUpdate, onDelete, id, ...restConfig } = config - const collectionId = id ?? 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 3a9bde82d7..05ad388d7c 100644 --- a/packages/db/src/local-storage.ts +++ b/packages/db/src/local-storage.ts @@ -1,4 +1,4 @@ -import { randomUUID } from './utils/uuid' +import { safeRandomUUID } from './utils/uuid' import { InvalidStorageDataFormatError, InvalidStorageObjectFormatError, @@ -150,7 +150,7 @@ function validateJsonSerializable( * @returns A unique identifier string for tracking data versions */ function generateUuid(): string { - return randomUUID() + return safeRandomUUID() } /** diff --git a/packages/db/src/transactions.ts b/packages/db/src/transactions.ts index d7137490e5..fe2f61c0fd 100644 --- a/packages/db/src/transactions.ts +++ b/packages/db/src/transactions.ts @@ -1,5 +1,5 @@ import { createDeferred } from './deferred' -import { randomUUID } from './utils/uuid' +import { safeRandomUUID } from './utils/uuid' import './duplicate-instance-check' import { MissingMutationFunctionError, @@ -225,7 +225,7 @@ class Transaction> { if (typeof config.mutationFn === `undefined`) { throw new MissingMutationFunctionError() } - this.id = config.id ?? 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 index fc988090a3..45875a459f 100644 --- a/packages/db/src/utils/uuid.ts +++ b/packages/db/src/utils/uuid.ts @@ -8,7 +8,7 @@ * * See https://github.com/TanStack/db/issues/1541. */ -export function randomUUID(): string { +export function safeRandomUUID(): string { const c: Crypto | undefined = typeof globalThis !== `undefined` ? (globalThis as any).crypto : undefined diff --git a/packages/db/tests/uuid.test.ts b/packages/db/tests/uuid.test.ts index d8c007d3ca..f6cf577e70 100644 --- a/packages/db/tests/uuid.test.ts +++ b/packages/db/tests/uuid.test.ts @@ -1,10 +1,10 @@ import { afterEach, describe, expect, it, vi } from 'vitest' -import { randomUUID } from '../src/utils/uuid' +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(`randomUUID helper`, () => { +describe(`safeRandomUUID helper`, () => { afterEach(() => { vi.restoreAllMocks() vi.unstubAllGlobals() @@ -14,13 +14,13 @@ describe(`randomUUID helper`, () => { const spy = vi .spyOn(globalThis.crypto, `randomUUID`) .mockReturnValue(`11111111-2222-4333-8444-555555555555`) - const id = randomUUID() + 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 randomUUID is unavailable + // Simulate a non-secure browser context where crypto.randomUUID is unavailable // but getRandomValues remains. vi.stubGlobal(`crypto`, { randomUUID: undefined, @@ -32,7 +32,7 @@ describe(`randomUUID helper`, () => { }, }) - const id = randomUUID() + const id = safeRandomUUID() expect(id).toMatch(UUID_V4_REGEX) // Verify version nibble == 4 and variant nibble in [8,9,a,b] @@ -57,7 +57,7 @@ describe(`randomUUID helper`, () => { const seen = new Set() for (let i = 0; i < 200; i++) { - const id = randomUUID() + const id = safeRandomUUID() expect(id).toMatch(UUID_V4_REGEX) seen.add(id) } @@ -66,6 +66,11 @@ describe(`randomUUID helper`, () => { it(`throws when neither crypto.randomUUID nor crypto.getRandomValues is available`, () => { vi.stubGlobal(`crypto`, {}) - expect(() => randomUUID()).toThrow(/No secure random number generator/) + 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 42708f417b..a4c6bb7fe8 100644 --- a/packages/electron-db-sqlite-persistence/src/electron-coordinator.ts +++ b/packages/electron-db-sqlite-persistence/src/electron-coordinator.ts @@ -1,4 +1,4 @@ -import { randomUUID } from '@tanstack/db-sqlite-persistence-core' +import { safeRandomUUID } from '@tanstack/db-sqlite-persistence-core' import type { ApplyLocalMutationsResponse, PersistedCollectionCoordinator, @@ -119,7 +119,7 @@ export type ElectronCollectionCoordinatorOptions = { // --------------------------------------------------------------------------- export class ElectronCollectionCoordinator implements PersistedCollectionCoordinator { - private readonly nodeId = randomUUID() + private readonly nodeId = safeRandomUUID() private readonly dbName: string private adapter: AdapterWithPullSince | null private readonly channel: BroadcastChannel @@ -206,7 +206,7 @@ export class ElectronCollectionCoordinator implements PersistedCollectionCoordin error?: string }>(collectionId, { type: `rpc:ensureRemoteSubset:req`, - rpcId: randomUUID(), + rpcId: safeRandomUUID(), options, }) @@ -234,7 +234,7 @@ export class ElectronCollectionCoordinator implements PersistedCollectionCoordin error?: string }>(collectionId, { type: `rpc:ensurePersistedIndex:req`, - rpcId: randomUUID(), + rpcId: safeRandomUUID(), signature, spec, }) @@ -253,16 +253,16 @@ export class ElectronCollectionCoordinator implements PersistedCollectionCoordin if (this.isLeader(collectionId)) { return this.handleApplyLocalMutations(collectionId, { type: `rpc:applyLocalMutations:req`, - rpcId: randomUUID(), - envelopeId: randomUUID(), + rpcId: safeRandomUUID(), + envelopeId: safeRandomUUID(), mutations, }) } return this.sendRPC(collectionId, { type: `rpc:applyLocalMutations:req`, - rpcId: randomUUID(), - envelopeId: randomUUID(), + rpcId: safeRandomUUID(), + envelopeId: safeRandomUUID(), mutations, }) } @@ -274,14 +274,14 @@ export class ElectronCollectionCoordinator implements PersistedCollectionCoordin if (this.isLeader(collectionId)) { return this.handlePullSince(collectionId, { type: `rpc:pullSince:req`, - rpcId: randomUUID(), + rpcId: safeRandomUUID(), fromRowVersion, }) } return this.sendRPC(collectionId, { type: `rpc:pullSince:req`, - rpcId: randomUUID(), + rpcId: safeRandomUUID(), fromRowVersion, }) } @@ -664,7 +664,7 @@ export class ElectronCollectionCoordinator implements PersistedCollectionCoordin // Build and apply the persisted transaction const tx = { - txId: randomUUID(), + txId: safeRandomUUID(), term, seq, rowVersion, diff --git a/packages/offline-transactions/src/OfflineExecutor.ts b/packages/offline-transactions/src/OfflineExecutor.ts index 3fa875ccab..a6140cfebc 100644 --- a/packages/offline-transactions/src/OfflineExecutor.ts +++ b/packages/offline-transactions/src/OfflineExecutor.ts @@ -2,7 +2,7 @@ import { createOptimisticAction, createTransaction, - randomUUID, + safeRandomUUID, } from '@tanstack/db' import { IndexedDBAdapter } from './storage/IndexedDBAdapter' import { LocalStorageAdapter } from './storage/LocalStorageAdapter' @@ -371,7 +371,7 @@ export class OfflineExecutor { mutationFn: (params) => mutationFn({ ...params, - idempotencyKey: options.idempotencyKey || randomUUID(), + idempotencyKey: options.idempotencyKey || safeRandomUUID(), }), metadata: options.metadata, }) @@ -403,7 +403,7 @@ export class OfflineExecutor { mutationFn({ ...vars, ...params, - idempotencyKey: 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 f96ae80807..af13484a75 100644 --- a/packages/offline-transactions/src/api/OfflineTransaction.ts +++ b/packages/offline-transactions/src/api/OfflineTransaction.ts @@ -1,4 +1,4 @@ -import { createTransaction, randomUUID } 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 = randomUUID() + this.offlineId = safeRandomUUID() this.mutationFnName = options.mutationFnName this.autoCommit = options.autoCommit ?? true - this.idempotencyKey = options.idempotencyKey ?? 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 1173ae772b..7f50d06459 100644 --- a/packages/offline-transactions/src/coordination/BroadcastChannelLeader.ts +++ b/packages/offline-transactions/src/coordination/BroadcastChannelLeader.ts @@ -1,4 +1,4 @@ -import { randomUUID } from '@tanstack/db' +import { safeRandomUUID } from '@tanstack/db' import { BaseLeaderElection } from './LeaderElection' interface LeaderMessage { @@ -20,7 +20,7 @@ export class BroadcastChannelLeader extends BaseLeaderElection { constructor(channelName = `offline-executor-leader`) { super() this.channelName = channelName - this.tabId = randomUUID() + this.tabId = safeRandomUUID() this.setupChannel() }