@tanstack/cloudflare-durable-objects-db-sqlite-persistence 0.2.23 → 0.2.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Thin SQLite persistence for Cloudflare Durable Objects.
4
4
 
5
+ Start with the [SQLite Persistence guide](../../docs/guides/sqlite-persistence.md) for Collection setup and lifecycle behavior.
6
+
5
7
  ## Public API
6
8
 
7
9
  - `createCloudflareDOSQLitePersistence(...)`
@@ -36,6 +36,7 @@ function toRowArray(result, sql) {
36
36
  }
37
37
  class CloudflareDOSQLiteDriver {
38
38
  constructor(options) {
39
+ this.maxBoundParameters = 100;
39
40
  this.queue = Promise.resolve();
40
41
  this.nextSavepointId = 1;
41
42
  const resolvedStorage = `storage` in options ? options.storage : {
@@ -1 +1 @@
1
- {"version":3,"file":"do-driver.cjs","sources":["../../src/do-driver.ts"],"sourcesContent":["import { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core'\nimport type { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core'\n\ntype DurableObjectSqlRow = Record<string, unknown>\n\ntype DurableObjectSqlCursorLike = Iterable<DurableObjectSqlRow> & {\n toArray?: () => Array<DurableObjectSqlRow>\n}\n\nexport type DurableObjectSqlStorageLike = {\n exec: (\n sql: string,\n ...params: ReadonlyArray<unknown>\n ) => DurableObjectSqlCursorLike | ReadonlyArray<DurableObjectSqlRow> | null\n}\n\nexport type DurableObjectTransactionExecutor = <T>(\n fn: () => Promise<T>,\n) => Promise<T>\n\nexport type DurableObjectStorageLike = {\n sql: DurableObjectSqlStorageLike\n transaction?: DurableObjectTransactionExecutor\n}\n\ntype CloudflareDOProvidedSqlOptions = {\n sql: DurableObjectSqlStorageLike\n transaction?: DurableObjectTransactionExecutor\n}\n\ntype CloudflareDOProvidedStorageOptions = {\n storage: DurableObjectStorageLike\n}\n\nexport type CloudflareDOSQLiteDriverOptions =\n | CloudflareDOProvidedSqlOptions\n | CloudflareDOProvidedStorageOptions\n\nfunction assertTransactionCallbackHasDriverArg(\n fn: (transactionDriver: SQLiteDriver) => Promise<unknown>,\n): void {\n if (fn.length > 0) {\n return\n }\n\n throw new InvalidPersistedCollectionConfigError(\n `SQLiteDriver.transaction callback must accept the transaction driver argument`,\n )\n}\n\nfunction isIterableRecord(\n value: unknown,\n): value is Iterable<DurableObjectSqlRow> {\n if (!value || typeof value !== `object`) {\n return false\n }\n\n const iterator = (value as { [Symbol.iterator]?: unknown })[Symbol.iterator]\n return typeof iterator === `function`\n}\n\nfunction toRowArray<T>(\n result: ReturnType<DurableObjectSqlStorageLike[`exec`]>,\n sql: string,\n): ReadonlyArray<T> {\n if (result == null) {\n return []\n }\n\n if (Array.isArray(result)) {\n return result as ReadonlyArray<T>\n }\n\n const cursorResult = result as DurableObjectSqlCursorLike\n if (typeof cursorResult.toArray === `function`) {\n return cursorResult.toArray() as ReadonlyArray<T>\n }\n\n if (isIterableRecord(cursorResult)) {\n return Array.from(cursorResult as Iterable<T>)\n }\n\n throw new InvalidPersistedCollectionConfigError(\n `Unsupported Durable Object SQL result shape for query \"${sql}\"`,\n )\n}\n\nexport class CloudflareDOSQLiteDriver implements SQLiteDriver {\n private readonly sqlStorage: DurableObjectSqlStorageLike\n private readonly storage: DurableObjectStorageLike\n private readonly transactionExecutor: DurableObjectTransactionExecutor | null\n private queue: Promise<void> = Promise.resolve()\n private nextSavepointId = 1\n\n constructor(options: CloudflareDOSQLiteDriverOptions) {\n const resolvedStorage: DurableObjectStorageLike =\n `storage` in options\n ? options.storage\n : {\n sql: options.sql,\n ...(typeof options.transaction === `function`\n ? { transaction: options.transaction }\n : {}),\n }\n const resolvedSqlStorage = resolvedStorage.sql\n if (typeof resolvedSqlStorage.exec !== `function`) {\n throw new InvalidPersistedCollectionConfigError(\n `Cloudflare DO SQL driver requires a sql.exec function`,\n )\n }\n this.storage = resolvedStorage\n this.sqlStorage = resolvedSqlStorage\n if (typeof resolvedStorage.transaction === `function`) {\n const transactionMethod = resolvedStorage.transaction\n this.transactionExecutor = <T>(fn: () => Promise<T>) =>\n Promise.resolve(\n transactionMethod.call(resolvedStorage, fn) as Promise<T> | T,\n )\n } else {\n this.transactionExecutor = null\n }\n }\n\n async exec(sql: string): Promise<void> {\n await this.enqueue(() => {\n this.execute(sql)\n })\n }\n\n async query<T>(\n sql: string,\n params: ReadonlyArray<unknown> = [],\n ): Promise<ReadonlyArray<T>> {\n return this.enqueue(() => this.executeQuery<T>(sql, params))\n }\n\n async run(sql: string, params: ReadonlyArray<unknown> = []): Promise<void> {\n await this.enqueue(() => {\n this.execute(sql, params)\n })\n }\n\n async transaction<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n assertTransactionCallbackHasDriverArg(fn)\n\n return this.enqueue(async () => {\n const transactionDriver = this.createTransactionDriver()\n if (this.transactionExecutor) {\n return this.transactionExecutor(() => fn(transactionDriver))\n }\n\n this.execute(`BEGIN IMMEDIATE`)\n try {\n const result = await fn(transactionDriver)\n this.execute(`COMMIT`)\n return result\n } catch (error) {\n try {\n this.execute(`ROLLBACK`)\n } catch {\n // Keep the original transaction error as the primary failure.\n }\n throw error\n }\n })\n }\n\n async transactionWithDriver<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n return this.transaction(fn)\n }\n\n getStorage(): DurableObjectStorageLike {\n return this.storage\n }\n\n private execute(sql: string, params: ReadonlyArray<unknown> = []): unknown {\n return this.sqlStorage.exec(sql, ...params)\n }\n\n private executeQuery<T>(\n sql: string,\n params: ReadonlyArray<unknown>,\n ): ReadonlyArray<T> {\n const result = this.execute(sql, params)\n return toRowArray<T>(\n result as ReturnType<DurableObjectSqlStorageLike[`exec`]>,\n sql,\n )\n }\n\n private enqueue<T>(operation: () => Promise<T> | T): Promise<T> {\n const queuedOperation = this.queue.then(operation, operation)\n this.queue = queuedOperation.then(\n () => undefined,\n () => undefined,\n )\n return queuedOperation\n }\n\n private createTransactionDriver(): SQLiteDriver {\n const transactionDriver: SQLiteDriver = {\n exec: (sql) => {\n this.execute(sql)\n return Promise.resolve()\n },\n query: <T>(\n sql: string,\n params: ReadonlyArray<unknown> = [],\n ): Promise<ReadonlyArray<T>> =>\n Promise.resolve(this.executeQuery<T>(sql, params)),\n run: (sql, params = []) => {\n this.execute(sql, params)\n return Promise.resolve()\n },\n transaction: <T>(\n fn: (nestedDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> => {\n assertTransactionCallbackHasDriverArg(fn)\n return this.runNestedTransaction(transactionDriver, fn)\n },\n transactionWithDriver: <T>(\n fn: (nestedDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> => this.runNestedTransaction(transactionDriver, fn),\n }\n\n return transactionDriver\n }\n\n private async runNestedTransaction<T>(\n transactionDriver: SQLiteDriver,\n fn: (nestedDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n if (this.transactionExecutor) {\n throw new InvalidPersistedCollectionConfigError(\n `Nested SQL savepoints are not supported when using Durable Object transaction API`,\n )\n }\n\n const savepointName = `tsdb_sp_${this.nextSavepointId}`\n this.nextSavepointId++\n this.execute(`SAVEPOINT ${savepointName}`)\n\n try {\n const result = await fn(transactionDriver)\n this.execute(`RELEASE SAVEPOINT ${savepointName}`)\n return result\n } catch (error) {\n this.execute(`ROLLBACK TO SAVEPOINT ${savepointName}`)\n this.execute(`RELEASE SAVEPOINT ${savepointName}`)\n throw error\n }\n }\n}\n\nexport function createCloudflareDOSQLiteDriver(\n options: CloudflareDOSQLiteDriverOptions,\n): CloudflareDOSQLiteDriver {\n return new CloudflareDOSQLiteDriver(options)\n}\n"],"names":["InvalidPersistedCollectionConfigError"],"mappings":"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"}
1
+ {"version":3,"file":"do-driver.cjs","sources":["../../src/do-driver.ts"],"sourcesContent":["import { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core'\nimport type { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core'\n\ntype DurableObjectSqlRow = Record<string, unknown>\n\ntype DurableObjectSqlCursorLike = Iterable<DurableObjectSqlRow> & {\n toArray?: () => Array<DurableObjectSqlRow>\n}\n\nexport type DurableObjectSqlStorageLike = {\n exec: (\n sql: string,\n ...params: ReadonlyArray<unknown>\n ) => DurableObjectSqlCursorLike | ReadonlyArray<DurableObjectSqlRow> | null\n}\n\nexport type DurableObjectTransactionExecutor = <T>(\n fn: () => Promise<T>,\n) => Promise<T>\n\nexport type DurableObjectStorageLike = {\n sql: DurableObjectSqlStorageLike\n transaction?: DurableObjectTransactionExecutor\n}\n\ntype CloudflareDOProvidedSqlOptions = {\n sql: DurableObjectSqlStorageLike\n transaction?: DurableObjectTransactionExecutor\n}\n\ntype CloudflareDOProvidedStorageOptions = {\n storage: DurableObjectStorageLike\n}\n\nexport type CloudflareDOSQLiteDriverOptions =\n | CloudflareDOProvidedSqlOptions\n | CloudflareDOProvidedStorageOptions\n\nfunction assertTransactionCallbackHasDriverArg(\n fn: (transactionDriver: SQLiteDriver) => Promise<unknown>,\n): void {\n if (fn.length > 0) {\n return\n }\n\n throw new InvalidPersistedCollectionConfigError(\n `SQLiteDriver.transaction callback must accept the transaction driver argument`,\n )\n}\n\nfunction isIterableRecord(\n value: unknown,\n): value is Iterable<DurableObjectSqlRow> {\n if (!value || typeof value !== `object`) {\n return false\n }\n\n const iterator = (value as { [Symbol.iterator]?: unknown })[Symbol.iterator]\n return typeof iterator === `function`\n}\n\nfunction toRowArray<T>(\n result: ReturnType<DurableObjectSqlStorageLike[`exec`]>,\n sql: string,\n): ReadonlyArray<T> {\n if (result == null) {\n return []\n }\n\n if (Array.isArray(result)) {\n return result as ReadonlyArray<T>\n }\n\n const cursorResult = result as DurableObjectSqlCursorLike\n if (typeof cursorResult.toArray === `function`) {\n return cursorResult.toArray() as ReadonlyArray<T>\n }\n\n if (isIterableRecord(cursorResult)) {\n return Array.from(cursorResult as Iterable<T>)\n }\n\n throw new InvalidPersistedCollectionConfigError(\n `Unsupported Durable Object SQL result shape for query \"${sql}\"`,\n )\n}\n\nexport class CloudflareDOSQLiteDriver implements SQLiteDriver {\n // https://developers.cloudflare.com/durable-objects/platform/limits/\n // Durable Object SQL storage permits at most 100 bound parameters per query.\n readonly maxBoundParameters = 100\n private readonly sqlStorage: DurableObjectSqlStorageLike\n private readonly storage: DurableObjectStorageLike\n private readonly transactionExecutor: DurableObjectTransactionExecutor | null\n private queue: Promise<void> = Promise.resolve()\n private nextSavepointId = 1\n\n constructor(options: CloudflareDOSQLiteDriverOptions) {\n const resolvedStorage: DurableObjectStorageLike =\n `storage` in options\n ? options.storage\n : {\n sql: options.sql,\n ...(typeof options.transaction === `function`\n ? { transaction: options.transaction }\n : {}),\n }\n const resolvedSqlStorage = resolvedStorage.sql\n if (typeof resolvedSqlStorage.exec !== `function`) {\n throw new InvalidPersistedCollectionConfigError(\n `Cloudflare DO SQL driver requires a sql.exec function`,\n )\n }\n this.storage = resolvedStorage\n this.sqlStorage = resolvedSqlStorage\n if (typeof resolvedStorage.transaction === `function`) {\n const transactionMethod = resolvedStorage.transaction\n this.transactionExecutor = <T>(fn: () => Promise<T>) =>\n Promise.resolve(\n transactionMethod.call(resolvedStorage, fn) as Promise<T> | T,\n )\n } else {\n this.transactionExecutor = null\n }\n }\n\n async exec(sql: string): Promise<void> {\n await this.enqueue(() => {\n this.execute(sql)\n })\n }\n\n async query<T>(\n sql: string,\n params: ReadonlyArray<unknown> = [],\n ): Promise<ReadonlyArray<T>> {\n return this.enqueue(() => this.executeQuery<T>(sql, params))\n }\n\n async run(sql: string, params: ReadonlyArray<unknown> = []): Promise<void> {\n await this.enqueue(() => {\n this.execute(sql, params)\n })\n }\n\n async transaction<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n assertTransactionCallbackHasDriverArg(fn)\n\n return this.enqueue(async () => {\n const transactionDriver = this.createTransactionDriver()\n if (this.transactionExecutor) {\n return this.transactionExecutor(() => fn(transactionDriver))\n }\n\n this.execute(`BEGIN IMMEDIATE`)\n try {\n const result = await fn(transactionDriver)\n this.execute(`COMMIT`)\n return result\n } catch (error) {\n try {\n this.execute(`ROLLBACK`)\n } catch {\n // Keep the original transaction error as the primary failure.\n }\n throw error\n }\n })\n }\n\n async transactionWithDriver<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n return this.transaction(fn)\n }\n\n getStorage(): DurableObjectStorageLike {\n return this.storage\n }\n\n private execute(sql: string, params: ReadonlyArray<unknown> = []): unknown {\n return this.sqlStorage.exec(sql, ...params)\n }\n\n private executeQuery<T>(\n sql: string,\n params: ReadonlyArray<unknown>,\n ): ReadonlyArray<T> {\n const result = this.execute(sql, params)\n return toRowArray<T>(\n result as ReturnType<DurableObjectSqlStorageLike[`exec`]>,\n sql,\n )\n }\n\n private enqueue<T>(operation: () => Promise<T> | T): Promise<T> {\n const queuedOperation = this.queue.then(operation, operation)\n this.queue = queuedOperation.then(\n () => undefined,\n () => undefined,\n )\n return queuedOperation\n }\n\n private createTransactionDriver(): SQLiteDriver {\n const transactionDriver: SQLiteDriver = {\n exec: (sql) => {\n this.execute(sql)\n return Promise.resolve()\n },\n query: <T>(\n sql: string,\n params: ReadonlyArray<unknown> = [],\n ): Promise<ReadonlyArray<T>> =>\n Promise.resolve(this.executeQuery<T>(sql, params)),\n run: (sql, params = []) => {\n this.execute(sql, params)\n return Promise.resolve()\n },\n transaction: <T>(\n fn: (nestedDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> => {\n assertTransactionCallbackHasDriverArg(fn)\n return this.runNestedTransaction(transactionDriver, fn)\n },\n transactionWithDriver: <T>(\n fn: (nestedDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> => this.runNestedTransaction(transactionDriver, fn),\n }\n\n return transactionDriver\n }\n\n private async runNestedTransaction<T>(\n transactionDriver: SQLiteDriver,\n fn: (nestedDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n if (this.transactionExecutor) {\n throw new InvalidPersistedCollectionConfigError(\n `Nested SQL savepoints are not supported when using Durable Object transaction API`,\n )\n }\n\n const savepointName = `tsdb_sp_${this.nextSavepointId}`\n this.nextSavepointId++\n this.execute(`SAVEPOINT ${savepointName}`)\n\n try {\n const result = await fn(transactionDriver)\n this.execute(`RELEASE SAVEPOINT ${savepointName}`)\n return result\n } catch (error) {\n this.execute(`ROLLBACK TO SAVEPOINT ${savepointName}`)\n this.execute(`RELEASE SAVEPOINT ${savepointName}`)\n throw error\n }\n }\n}\n\nexport function createCloudflareDOSQLiteDriver(\n options: CloudflareDOSQLiteDriverOptions,\n): CloudflareDOSQLiteDriver {\n return new CloudflareDOSQLiteDriver(options)\n}\n"],"names":["InvalidPersistedCollectionConfigError"],"mappings":"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"}
@@ -20,6 +20,7 @@ type CloudflareDOProvidedStorageOptions = {
20
20
  };
21
21
  export type CloudflareDOSQLiteDriverOptions = CloudflareDOProvidedSqlOptions | CloudflareDOProvidedStorageOptions;
22
22
  export declare class CloudflareDOSQLiteDriver implements SQLiteDriver {
23
+ readonly maxBoundParameters = 100;
23
24
  private readonly sqlStorage;
24
25
  private readonly storage;
25
26
  private readonly transactionExecutor;
@@ -20,6 +20,7 @@ type CloudflareDOProvidedStorageOptions = {
20
20
  };
21
21
  export type CloudflareDOSQLiteDriverOptions = CloudflareDOProvidedSqlOptions | CloudflareDOProvidedStorageOptions;
22
22
  export declare class CloudflareDOSQLiteDriver implements SQLiteDriver {
23
+ readonly maxBoundParameters = 100;
23
24
  private readonly sqlStorage;
24
25
  private readonly storage;
25
26
  private readonly transactionExecutor;
@@ -34,6 +34,7 @@ function toRowArray(result, sql) {
34
34
  }
35
35
  class CloudflareDOSQLiteDriver {
36
36
  constructor(options) {
37
+ this.maxBoundParameters = 100;
37
38
  this.queue = Promise.resolve();
38
39
  this.nextSavepointId = 1;
39
40
  const resolvedStorage = `storage` in options ? options.storage : {
@@ -1 +1 @@
1
- {"version":3,"file":"do-driver.js","sources":["../../src/do-driver.ts"],"sourcesContent":["import { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core'\nimport type { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core'\n\ntype DurableObjectSqlRow = Record<string, unknown>\n\ntype DurableObjectSqlCursorLike = Iterable<DurableObjectSqlRow> & {\n toArray?: () => Array<DurableObjectSqlRow>\n}\n\nexport type DurableObjectSqlStorageLike = {\n exec: (\n sql: string,\n ...params: ReadonlyArray<unknown>\n ) => DurableObjectSqlCursorLike | ReadonlyArray<DurableObjectSqlRow> | null\n}\n\nexport type DurableObjectTransactionExecutor = <T>(\n fn: () => Promise<T>,\n) => Promise<T>\n\nexport type DurableObjectStorageLike = {\n sql: DurableObjectSqlStorageLike\n transaction?: DurableObjectTransactionExecutor\n}\n\ntype CloudflareDOProvidedSqlOptions = {\n sql: DurableObjectSqlStorageLike\n transaction?: DurableObjectTransactionExecutor\n}\n\ntype CloudflareDOProvidedStorageOptions = {\n storage: DurableObjectStorageLike\n}\n\nexport type CloudflareDOSQLiteDriverOptions =\n | CloudflareDOProvidedSqlOptions\n | CloudflareDOProvidedStorageOptions\n\nfunction assertTransactionCallbackHasDriverArg(\n fn: (transactionDriver: SQLiteDriver) => Promise<unknown>,\n): void {\n if (fn.length > 0) {\n return\n }\n\n throw new InvalidPersistedCollectionConfigError(\n `SQLiteDriver.transaction callback must accept the transaction driver argument`,\n )\n}\n\nfunction isIterableRecord(\n value: unknown,\n): value is Iterable<DurableObjectSqlRow> {\n if (!value || typeof value !== `object`) {\n return false\n }\n\n const iterator = (value as { [Symbol.iterator]?: unknown })[Symbol.iterator]\n return typeof iterator === `function`\n}\n\nfunction toRowArray<T>(\n result: ReturnType<DurableObjectSqlStorageLike[`exec`]>,\n sql: string,\n): ReadonlyArray<T> {\n if (result == null) {\n return []\n }\n\n if (Array.isArray(result)) {\n return result as ReadonlyArray<T>\n }\n\n const cursorResult = result as DurableObjectSqlCursorLike\n if (typeof cursorResult.toArray === `function`) {\n return cursorResult.toArray() as ReadonlyArray<T>\n }\n\n if (isIterableRecord(cursorResult)) {\n return Array.from(cursorResult as Iterable<T>)\n }\n\n throw new InvalidPersistedCollectionConfigError(\n `Unsupported Durable Object SQL result shape for query \"${sql}\"`,\n )\n}\n\nexport class CloudflareDOSQLiteDriver implements SQLiteDriver {\n private readonly sqlStorage: DurableObjectSqlStorageLike\n private readonly storage: DurableObjectStorageLike\n private readonly transactionExecutor: DurableObjectTransactionExecutor | null\n private queue: Promise<void> = Promise.resolve()\n private nextSavepointId = 1\n\n constructor(options: CloudflareDOSQLiteDriverOptions) {\n const resolvedStorage: DurableObjectStorageLike =\n `storage` in options\n ? options.storage\n : {\n sql: options.sql,\n ...(typeof options.transaction === `function`\n ? { transaction: options.transaction }\n : {}),\n }\n const resolvedSqlStorage = resolvedStorage.sql\n if (typeof resolvedSqlStorage.exec !== `function`) {\n throw new InvalidPersistedCollectionConfigError(\n `Cloudflare DO SQL driver requires a sql.exec function`,\n )\n }\n this.storage = resolvedStorage\n this.sqlStorage = resolvedSqlStorage\n if (typeof resolvedStorage.transaction === `function`) {\n const transactionMethod = resolvedStorage.transaction\n this.transactionExecutor = <T>(fn: () => Promise<T>) =>\n Promise.resolve(\n transactionMethod.call(resolvedStorage, fn) as Promise<T> | T,\n )\n } else {\n this.transactionExecutor = null\n }\n }\n\n async exec(sql: string): Promise<void> {\n await this.enqueue(() => {\n this.execute(sql)\n })\n }\n\n async query<T>(\n sql: string,\n params: ReadonlyArray<unknown> = [],\n ): Promise<ReadonlyArray<T>> {\n return this.enqueue(() => this.executeQuery<T>(sql, params))\n }\n\n async run(sql: string, params: ReadonlyArray<unknown> = []): Promise<void> {\n await this.enqueue(() => {\n this.execute(sql, params)\n })\n }\n\n async transaction<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n assertTransactionCallbackHasDriverArg(fn)\n\n return this.enqueue(async () => {\n const transactionDriver = this.createTransactionDriver()\n if (this.transactionExecutor) {\n return this.transactionExecutor(() => fn(transactionDriver))\n }\n\n this.execute(`BEGIN IMMEDIATE`)\n try {\n const result = await fn(transactionDriver)\n this.execute(`COMMIT`)\n return result\n } catch (error) {\n try {\n this.execute(`ROLLBACK`)\n } catch {\n // Keep the original transaction error as the primary failure.\n }\n throw error\n }\n })\n }\n\n async transactionWithDriver<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n return this.transaction(fn)\n }\n\n getStorage(): DurableObjectStorageLike {\n return this.storage\n }\n\n private execute(sql: string, params: ReadonlyArray<unknown> = []): unknown {\n return this.sqlStorage.exec(sql, ...params)\n }\n\n private executeQuery<T>(\n sql: string,\n params: ReadonlyArray<unknown>,\n ): ReadonlyArray<T> {\n const result = this.execute(sql, params)\n return toRowArray<T>(\n result as ReturnType<DurableObjectSqlStorageLike[`exec`]>,\n sql,\n )\n }\n\n private enqueue<T>(operation: () => Promise<T> | T): Promise<T> {\n const queuedOperation = this.queue.then(operation, operation)\n this.queue = queuedOperation.then(\n () => undefined,\n () => undefined,\n )\n return queuedOperation\n }\n\n private createTransactionDriver(): SQLiteDriver {\n const transactionDriver: SQLiteDriver = {\n exec: (sql) => {\n this.execute(sql)\n return Promise.resolve()\n },\n query: <T>(\n sql: string,\n params: ReadonlyArray<unknown> = [],\n ): Promise<ReadonlyArray<T>> =>\n Promise.resolve(this.executeQuery<T>(sql, params)),\n run: (sql, params = []) => {\n this.execute(sql, params)\n return Promise.resolve()\n },\n transaction: <T>(\n fn: (nestedDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> => {\n assertTransactionCallbackHasDriverArg(fn)\n return this.runNestedTransaction(transactionDriver, fn)\n },\n transactionWithDriver: <T>(\n fn: (nestedDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> => this.runNestedTransaction(transactionDriver, fn),\n }\n\n return transactionDriver\n }\n\n private async runNestedTransaction<T>(\n transactionDriver: SQLiteDriver,\n fn: (nestedDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n if (this.transactionExecutor) {\n throw new InvalidPersistedCollectionConfigError(\n `Nested SQL savepoints are not supported when using Durable Object transaction API`,\n )\n }\n\n const savepointName = `tsdb_sp_${this.nextSavepointId}`\n this.nextSavepointId++\n this.execute(`SAVEPOINT ${savepointName}`)\n\n try {\n const result = await fn(transactionDriver)\n this.execute(`RELEASE SAVEPOINT ${savepointName}`)\n return result\n } catch (error) {\n this.execute(`ROLLBACK TO SAVEPOINT ${savepointName}`)\n this.execute(`RELEASE SAVEPOINT ${savepointName}`)\n throw error\n }\n }\n}\n\nexport function createCloudflareDOSQLiteDriver(\n options: CloudflareDOSQLiteDriverOptions,\n): CloudflareDOSQLiteDriver {\n return new CloudflareDOSQLiteDriver(options)\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"do-driver.js","sources":["../../src/do-driver.ts"],"sourcesContent":["import { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core'\nimport type { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core'\n\ntype DurableObjectSqlRow = Record<string, unknown>\n\ntype DurableObjectSqlCursorLike = Iterable<DurableObjectSqlRow> & {\n toArray?: () => Array<DurableObjectSqlRow>\n}\n\nexport type DurableObjectSqlStorageLike = {\n exec: (\n sql: string,\n ...params: ReadonlyArray<unknown>\n ) => DurableObjectSqlCursorLike | ReadonlyArray<DurableObjectSqlRow> | null\n}\n\nexport type DurableObjectTransactionExecutor = <T>(\n fn: () => Promise<T>,\n) => Promise<T>\n\nexport type DurableObjectStorageLike = {\n sql: DurableObjectSqlStorageLike\n transaction?: DurableObjectTransactionExecutor\n}\n\ntype CloudflareDOProvidedSqlOptions = {\n sql: DurableObjectSqlStorageLike\n transaction?: DurableObjectTransactionExecutor\n}\n\ntype CloudflareDOProvidedStorageOptions = {\n storage: DurableObjectStorageLike\n}\n\nexport type CloudflareDOSQLiteDriverOptions =\n | CloudflareDOProvidedSqlOptions\n | CloudflareDOProvidedStorageOptions\n\nfunction assertTransactionCallbackHasDriverArg(\n fn: (transactionDriver: SQLiteDriver) => Promise<unknown>,\n): void {\n if (fn.length > 0) {\n return\n }\n\n throw new InvalidPersistedCollectionConfigError(\n `SQLiteDriver.transaction callback must accept the transaction driver argument`,\n )\n}\n\nfunction isIterableRecord(\n value: unknown,\n): value is Iterable<DurableObjectSqlRow> {\n if (!value || typeof value !== `object`) {\n return false\n }\n\n const iterator = (value as { [Symbol.iterator]?: unknown })[Symbol.iterator]\n return typeof iterator === `function`\n}\n\nfunction toRowArray<T>(\n result: ReturnType<DurableObjectSqlStorageLike[`exec`]>,\n sql: string,\n): ReadonlyArray<T> {\n if (result == null) {\n return []\n }\n\n if (Array.isArray(result)) {\n return result as ReadonlyArray<T>\n }\n\n const cursorResult = result as DurableObjectSqlCursorLike\n if (typeof cursorResult.toArray === `function`) {\n return cursorResult.toArray() as ReadonlyArray<T>\n }\n\n if (isIterableRecord(cursorResult)) {\n return Array.from(cursorResult as Iterable<T>)\n }\n\n throw new InvalidPersistedCollectionConfigError(\n `Unsupported Durable Object SQL result shape for query \"${sql}\"`,\n )\n}\n\nexport class CloudflareDOSQLiteDriver implements SQLiteDriver {\n // https://developers.cloudflare.com/durable-objects/platform/limits/\n // Durable Object SQL storage permits at most 100 bound parameters per query.\n readonly maxBoundParameters = 100\n private readonly sqlStorage: DurableObjectSqlStorageLike\n private readonly storage: DurableObjectStorageLike\n private readonly transactionExecutor: DurableObjectTransactionExecutor | null\n private queue: Promise<void> = Promise.resolve()\n private nextSavepointId = 1\n\n constructor(options: CloudflareDOSQLiteDriverOptions) {\n const resolvedStorage: DurableObjectStorageLike =\n `storage` in options\n ? options.storage\n : {\n sql: options.sql,\n ...(typeof options.transaction === `function`\n ? { transaction: options.transaction }\n : {}),\n }\n const resolvedSqlStorage = resolvedStorage.sql\n if (typeof resolvedSqlStorage.exec !== `function`) {\n throw new InvalidPersistedCollectionConfigError(\n `Cloudflare DO SQL driver requires a sql.exec function`,\n )\n }\n this.storage = resolvedStorage\n this.sqlStorage = resolvedSqlStorage\n if (typeof resolvedStorage.transaction === `function`) {\n const transactionMethod = resolvedStorage.transaction\n this.transactionExecutor = <T>(fn: () => Promise<T>) =>\n Promise.resolve(\n transactionMethod.call(resolvedStorage, fn) as Promise<T> | T,\n )\n } else {\n this.transactionExecutor = null\n }\n }\n\n async exec(sql: string): Promise<void> {\n await this.enqueue(() => {\n this.execute(sql)\n })\n }\n\n async query<T>(\n sql: string,\n params: ReadonlyArray<unknown> = [],\n ): Promise<ReadonlyArray<T>> {\n return this.enqueue(() => this.executeQuery<T>(sql, params))\n }\n\n async run(sql: string, params: ReadonlyArray<unknown> = []): Promise<void> {\n await this.enqueue(() => {\n this.execute(sql, params)\n })\n }\n\n async transaction<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n assertTransactionCallbackHasDriverArg(fn)\n\n return this.enqueue(async () => {\n const transactionDriver = this.createTransactionDriver()\n if (this.transactionExecutor) {\n return this.transactionExecutor(() => fn(transactionDriver))\n }\n\n this.execute(`BEGIN IMMEDIATE`)\n try {\n const result = await fn(transactionDriver)\n this.execute(`COMMIT`)\n return result\n } catch (error) {\n try {\n this.execute(`ROLLBACK`)\n } catch {\n // Keep the original transaction error as the primary failure.\n }\n throw error\n }\n })\n }\n\n async transactionWithDriver<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n return this.transaction(fn)\n }\n\n getStorage(): DurableObjectStorageLike {\n return this.storage\n }\n\n private execute(sql: string, params: ReadonlyArray<unknown> = []): unknown {\n return this.sqlStorage.exec(sql, ...params)\n }\n\n private executeQuery<T>(\n sql: string,\n params: ReadonlyArray<unknown>,\n ): ReadonlyArray<T> {\n const result = this.execute(sql, params)\n return toRowArray<T>(\n result as ReturnType<DurableObjectSqlStorageLike[`exec`]>,\n sql,\n )\n }\n\n private enqueue<T>(operation: () => Promise<T> | T): Promise<T> {\n const queuedOperation = this.queue.then(operation, operation)\n this.queue = queuedOperation.then(\n () => undefined,\n () => undefined,\n )\n return queuedOperation\n }\n\n private createTransactionDriver(): SQLiteDriver {\n const transactionDriver: SQLiteDriver = {\n exec: (sql) => {\n this.execute(sql)\n return Promise.resolve()\n },\n query: <T>(\n sql: string,\n params: ReadonlyArray<unknown> = [],\n ): Promise<ReadonlyArray<T>> =>\n Promise.resolve(this.executeQuery<T>(sql, params)),\n run: (sql, params = []) => {\n this.execute(sql, params)\n return Promise.resolve()\n },\n transaction: <T>(\n fn: (nestedDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> => {\n assertTransactionCallbackHasDriverArg(fn)\n return this.runNestedTransaction(transactionDriver, fn)\n },\n transactionWithDriver: <T>(\n fn: (nestedDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> => this.runNestedTransaction(transactionDriver, fn),\n }\n\n return transactionDriver\n }\n\n private async runNestedTransaction<T>(\n transactionDriver: SQLiteDriver,\n fn: (nestedDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n if (this.transactionExecutor) {\n throw new InvalidPersistedCollectionConfigError(\n `Nested SQL savepoints are not supported when using Durable Object transaction API`,\n )\n }\n\n const savepointName = `tsdb_sp_${this.nextSavepointId}`\n this.nextSavepointId++\n this.execute(`SAVEPOINT ${savepointName}`)\n\n try {\n const result = await fn(transactionDriver)\n this.execute(`RELEASE SAVEPOINT ${savepointName}`)\n return result\n } catch (error) {\n this.execute(`ROLLBACK TO SAVEPOINT ${savepointName}`)\n this.execute(`RELEASE SAVEPOINT ${savepointName}`)\n throw error\n }\n }\n}\n\nexport function createCloudflareDOSQLiteDriver(\n options: CloudflareDOSQLiteDriverOptions,\n): CloudflareDOSQLiteDriver {\n return new CloudflareDOSQLiteDriver(options)\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/cloudflare-durable-objects-db-sqlite-persistence",
3
- "version": "0.2.23",
3
+ "version": "0.2.26",
4
4
  "description": "Cloudflare Durable Object SQLite persisted collection adapter for TanStack DB",
5
5
  "author": "TanStack Team",
6
6
  "license": "MIT",
@@ -40,7 +40,7 @@
40
40
  "src"
41
41
  ],
42
42
  "dependencies": {
43
- "@tanstack/db-sqlite-persistence-core": "0.2.23"
43
+ "@tanstack/db-sqlite-persistence-core": "0.4.1"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "typescript": ">=4.7"
package/src/do-driver.ts CHANGED
@@ -86,6 +86,9 @@ function toRowArray<T>(
86
86
  }
87
87
 
88
88
  export class CloudflareDOSQLiteDriver implements SQLiteDriver {
89
+ // https://developers.cloudflare.com/durable-objects/platform/limits/
90
+ // Durable Object SQL storage permits at most 100 bound parameters per query.
91
+ readonly maxBoundParameters = 100
89
92
  private readonly sqlStorage: DurableObjectSqlStorageLike
90
93
  private readonly storage: DurableObjectStorageLike
91
94
  private readonly transactionExecutor: DurableObjectTransactionExecutor | null