@tanstack/cloudflare-durable-objects-db-sqlite-persistence 0.1.2

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 ADDED
@@ -0,0 +1,48 @@
1
+ # @tanstack/cloudflare-durable-objects-db-sqlite-persistence
2
+
3
+ Thin SQLite persistence for Cloudflare Durable Objects.
4
+
5
+ ## Public API
6
+
7
+ - `createCloudflareDOSQLitePersistence(...)`
8
+ - `persistedCollectionOptions(...)` (re-exported from core)
9
+
10
+ ## Quick start
11
+
12
+ ```ts
13
+ import { createCollection } from '@tanstack/db'
14
+ import {
15
+ createCloudflareDOSQLitePersistence,
16
+ persistedCollectionOptions,
17
+ } from '@tanstack/cloudflare-durable-objects-db-sqlite-persistence'
18
+
19
+ type Todo = {
20
+ id: string
21
+ title: string
22
+ completed: boolean
23
+ }
24
+
25
+ export class TodosObject extends DurableObject {
26
+ persistence = createCloudflareDOSQLitePersistence({
27
+ // Pass full storage to use native DO transaction support.
28
+ storage: this.ctx.storage,
29
+ })
30
+
31
+ todos = createCollection(
32
+ persistedCollectionOptions<Todo, string>({
33
+ id: `todos`,
34
+ getKey: (todo) => todo.id,
35
+ persistence: this.persistence,
36
+ schemaVersion: 1, // Per-collection schema version
37
+ }),
38
+ )
39
+ }
40
+ ```
41
+
42
+ ## Notes
43
+
44
+ - One shared persistence instance can serve multiple collections.
45
+ - Mode defaults are inferred from collection usage:
46
+ - sync config present => `sync-present-reset`
47
+ - no sync config => `sync-absent-error`
48
+ - You can still override with `schemaMismatchPolicy` if needed.
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const dbSqlitePersistenceCore = require("@tanstack/db-sqlite-persistence-core");
4
+ function assertTransactionCallbackHasDriverArg(fn) {
5
+ if (fn.length > 0) {
6
+ return;
7
+ }
8
+ throw new dbSqlitePersistenceCore.InvalidPersistedCollectionConfigError(
9
+ `SQLiteDriver.transaction callback must accept the transaction driver argument`
10
+ );
11
+ }
12
+ function isIterableRecord(value) {
13
+ if (!value || typeof value !== `object`) {
14
+ return false;
15
+ }
16
+ const iterator = value[Symbol.iterator];
17
+ return typeof iterator === `function`;
18
+ }
19
+ function toRowArray(result, sql) {
20
+ if (result == null) {
21
+ return [];
22
+ }
23
+ if (Array.isArray(result)) {
24
+ return result;
25
+ }
26
+ const cursorResult = result;
27
+ if (typeof cursorResult.toArray === `function`) {
28
+ return cursorResult.toArray();
29
+ }
30
+ if (isIterableRecord(cursorResult)) {
31
+ return Array.from(cursorResult);
32
+ }
33
+ throw new dbSqlitePersistenceCore.InvalidPersistedCollectionConfigError(
34
+ `Unsupported Durable Object SQL result shape for query "${sql}"`
35
+ );
36
+ }
37
+ class CloudflareDOSQLiteDriver {
38
+ constructor(options) {
39
+ this.queue = Promise.resolve();
40
+ this.nextSavepointId = 1;
41
+ const resolvedStorage = `storage` in options ? options.storage : {
42
+ sql: options.sql,
43
+ ...typeof options.transaction === `function` ? { transaction: options.transaction } : {}
44
+ };
45
+ const resolvedSqlStorage = resolvedStorage.sql;
46
+ if (typeof resolvedSqlStorage.exec !== `function`) {
47
+ throw new dbSqlitePersistenceCore.InvalidPersistedCollectionConfigError(
48
+ `Cloudflare DO SQL driver requires a sql.exec function`
49
+ );
50
+ }
51
+ this.storage = resolvedStorage;
52
+ this.sqlStorage = resolvedSqlStorage;
53
+ if (typeof resolvedStorage.transaction === `function`) {
54
+ const transactionMethod = resolvedStorage.transaction;
55
+ this.transactionExecutor = (fn) => Promise.resolve(
56
+ transactionMethod.call(resolvedStorage, fn)
57
+ );
58
+ } else {
59
+ this.transactionExecutor = null;
60
+ }
61
+ }
62
+ async exec(sql) {
63
+ await this.enqueue(() => {
64
+ this.execute(sql);
65
+ });
66
+ }
67
+ async query(sql, params = []) {
68
+ return this.enqueue(() => this.executeQuery(sql, params));
69
+ }
70
+ async run(sql, params = []) {
71
+ await this.enqueue(() => {
72
+ this.execute(sql, params);
73
+ });
74
+ }
75
+ async transaction(fn) {
76
+ assertTransactionCallbackHasDriverArg(fn);
77
+ return this.enqueue(async () => {
78
+ const transactionDriver = this.createTransactionDriver();
79
+ if (this.transactionExecutor) {
80
+ return this.transactionExecutor(() => fn(transactionDriver));
81
+ }
82
+ this.execute(`BEGIN IMMEDIATE`);
83
+ try {
84
+ const result = await fn(transactionDriver);
85
+ this.execute(`COMMIT`);
86
+ return result;
87
+ } catch (error) {
88
+ try {
89
+ this.execute(`ROLLBACK`);
90
+ } catch {
91
+ }
92
+ throw error;
93
+ }
94
+ });
95
+ }
96
+ async transactionWithDriver(fn) {
97
+ return this.transaction(fn);
98
+ }
99
+ getStorage() {
100
+ return this.storage;
101
+ }
102
+ execute(sql, params = []) {
103
+ return this.sqlStorage.exec(sql, ...params);
104
+ }
105
+ executeQuery(sql, params) {
106
+ const result = this.execute(sql, params);
107
+ return toRowArray(
108
+ result,
109
+ sql
110
+ );
111
+ }
112
+ enqueue(operation) {
113
+ const queuedOperation = this.queue.then(operation, operation);
114
+ this.queue = queuedOperation.then(
115
+ () => void 0,
116
+ () => void 0
117
+ );
118
+ return queuedOperation;
119
+ }
120
+ createTransactionDriver() {
121
+ const transactionDriver = {
122
+ exec: (sql) => {
123
+ this.execute(sql);
124
+ return Promise.resolve();
125
+ },
126
+ query: (sql, params = []) => Promise.resolve(this.executeQuery(sql, params)),
127
+ run: (sql, params = []) => {
128
+ this.execute(sql, params);
129
+ return Promise.resolve();
130
+ },
131
+ transaction: (fn) => {
132
+ assertTransactionCallbackHasDriverArg(fn);
133
+ return this.runNestedTransaction(transactionDriver, fn);
134
+ },
135
+ transactionWithDriver: (fn) => this.runNestedTransaction(transactionDriver, fn)
136
+ };
137
+ return transactionDriver;
138
+ }
139
+ async runNestedTransaction(transactionDriver, fn) {
140
+ if (this.transactionExecutor) {
141
+ throw new dbSqlitePersistenceCore.InvalidPersistedCollectionConfigError(
142
+ `Nested SQL savepoints are not supported when using Durable Object transaction API`
143
+ );
144
+ }
145
+ const savepointName = `tsdb_sp_${this.nextSavepointId}`;
146
+ this.nextSavepointId++;
147
+ this.execute(`SAVEPOINT ${savepointName}`);
148
+ try {
149
+ const result = await fn(transactionDriver);
150
+ this.execute(`RELEASE SAVEPOINT ${savepointName}`);
151
+ return result;
152
+ } catch (error) {
153
+ this.execute(`ROLLBACK TO SAVEPOINT ${savepointName}`);
154
+ this.execute(`RELEASE SAVEPOINT ${savepointName}`);
155
+ throw error;
156
+ }
157
+ }
158
+ }
159
+ exports.CloudflareDOSQLiteDriver = CloudflareDOSQLiteDriver;
160
+ //# sourceMappingURL=do-driver.cjs.map
@@ -0,0 +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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"}
@@ -0,0 +1,42 @@
1
+ import { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core';
2
+ type DurableObjectSqlRow = Record<string, unknown>;
3
+ type DurableObjectSqlCursorLike = Iterable<DurableObjectSqlRow> & {
4
+ toArray?: () => Array<DurableObjectSqlRow>;
5
+ };
6
+ export type DurableObjectSqlStorageLike = {
7
+ exec: (sql: string, ...params: ReadonlyArray<unknown>) => DurableObjectSqlCursorLike | ReadonlyArray<DurableObjectSqlRow> | null;
8
+ };
9
+ export type DurableObjectTransactionExecutor = <T>(fn: () => Promise<T>) => Promise<T>;
10
+ export type DurableObjectStorageLike = {
11
+ sql: DurableObjectSqlStorageLike;
12
+ transaction?: DurableObjectTransactionExecutor;
13
+ };
14
+ type CloudflareDOProvidedSqlOptions = {
15
+ sql: DurableObjectSqlStorageLike;
16
+ transaction?: DurableObjectTransactionExecutor;
17
+ };
18
+ type CloudflareDOProvidedStorageOptions = {
19
+ storage: DurableObjectStorageLike;
20
+ };
21
+ export type CloudflareDOSQLiteDriverOptions = CloudflareDOProvidedSqlOptions | CloudflareDOProvidedStorageOptions;
22
+ export declare class CloudflareDOSQLiteDriver implements SQLiteDriver {
23
+ private readonly sqlStorage;
24
+ private readonly storage;
25
+ private readonly transactionExecutor;
26
+ private queue;
27
+ private nextSavepointId;
28
+ constructor(options: CloudflareDOSQLiteDriverOptions);
29
+ exec(sql: string): Promise<void>;
30
+ query<T>(sql: string, params?: ReadonlyArray<unknown>): Promise<ReadonlyArray<T>>;
31
+ run(sql: string, params?: ReadonlyArray<unknown>): Promise<void>;
32
+ transaction<T>(fn: (transactionDriver: SQLiteDriver) => Promise<T>): Promise<T>;
33
+ transactionWithDriver<T>(fn: (transactionDriver: SQLiteDriver) => Promise<T>): Promise<T>;
34
+ getStorage(): DurableObjectStorageLike;
35
+ private execute;
36
+ private executeQuery;
37
+ private enqueue;
38
+ private createTransactionDriver;
39
+ private runNestedTransaction;
40
+ }
41
+ export declare function createCloudflareDOSQLiteDriver(options: CloudflareDOSQLiteDriverOptions): CloudflareDOSQLiteDriver;
42
+ export {};
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const dbSqlitePersistenceCore = require("@tanstack/db-sqlite-persistence-core");
4
+ const doDriver = require("./do-driver.cjs");
5
+ function normalizeSchemaMismatchPolicy(policy) {
6
+ if (policy === `throw`) {
7
+ return `sync-absent-error`;
8
+ }
9
+ return policy;
10
+ }
11
+ function resolveSchemaMismatchPolicy(explicitPolicy, mode) {
12
+ if (explicitPolicy) {
13
+ return normalizeSchemaMismatchPolicy(explicitPolicy);
14
+ }
15
+ return mode === `sync-present` ? `sync-present-reset` : `sync-absent-error`;
16
+ }
17
+ function createAdapterCacheKey(schemaMismatchPolicy, schemaVersion) {
18
+ const schemaVersionKey = schemaVersion === void 0 ? `schema:default` : `schema:${schemaVersion}`;
19
+ return `${schemaMismatchPolicy}|${schemaVersionKey}`;
20
+ }
21
+ function resolveSQLiteDriver(options) {
22
+ return new doDriver.CloudflareDOSQLiteDriver({
23
+ storage: options.storage
24
+ });
25
+ }
26
+ function resolveAdapterBaseOptions(options) {
27
+ return {
28
+ appliedTxPruneMaxRows: options.appliedTxPruneMaxRows,
29
+ appliedTxPruneMaxAgeSeconds: options.appliedTxPruneMaxAgeSeconds,
30
+ pullSinceReloadThreshold: options.pullSinceReloadThreshold
31
+ };
32
+ }
33
+ function createCloudflareDOSQLitePersistence(options) {
34
+ const { coordinator, schemaMismatchPolicy } = options;
35
+ const driver = resolveSQLiteDriver(options);
36
+ const adapterBaseOptions = resolveAdapterBaseOptions(options);
37
+ const resolvedCoordinator = coordinator ?? new dbSqlitePersistenceCore.SingleProcessCoordinator();
38
+ const adapterCache = /* @__PURE__ */ new Map();
39
+ const getAdapterForCollection = (mode, schemaVersion) => {
40
+ const resolvedSchemaMismatchPolicy = resolveSchemaMismatchPolicy(
41
+ schemaMismatchPolicy,
42
+ mode
43
+ );
44
+ const cacheKey = createAdapterCacheKey(
45
+ resolvedSchemaMismatchPolicy,
46
+ schemaVersion
47
+ );
48
+ const cachedAdapter = adapterCache.get(cacheKey);
49
+ if (cachedAdapter) {
50
+ return cachedAdapter;
51
+ }
52
+ const adapter = dbSqlitePersistenceCore.createSQLiteCorePersistenceAdapter({
53
+ ...adapterBaseOptions,
54
+ driver,
55
+ schemaMismatchPolicy: resolvedSchemaMismatchPolicy,
56
+ ...schemaVersion === void 0 ? {} : { schemaVersion }
57
+ });
58
+ adapterCache.set(cacheKey, adapter);
59
+ return adapter;
60
+ };
61
+ const createCollectionPersistence = (mode, schemaVersion) => ({
62
+ adapter: getAdapterForCollection(
63
+ mode,
64
+ schemaVersion
65
+ ),
66
+ coordinator: resolvedCoordinator
67
+ });
68
+ const defaultPersistence = createCollectionPersistence(
69
+ `sync-absent`,
70
+ void 0
71
+ );
72
+ return {
73
+ ...defaultPersistence,
74
+ resolvePersistenceForCollection: ({ mode, schemaVersion }) => createCollectionPersistence(mode, schemaVersion),
75
+ // Backward compatible fallback for older callers.
76
+ resolvePersistenceForMode: (mode) => createCollectionPersistence(mode, void 0)
77
+ };
78
+ }
79
+ exports.createCloudflareDOSQLitePersistence = createCloudflareDOSQLitePersistence;
80
+ //# sourceMappingURL=do-persistence.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"do-persistence.cjs","sources":["../../src/do-persistence.ts"],"sourcesContent":["import {\n SingleProcessCoordinator,\n createSQLiteCorePersistenceAdapter,\n} from '@tanstack/db-sqlite-persistence-core'\nimport { CloudflareDOSQLiteDriver } from './do-driver'\nimport type {\n PersistedCollectionCoordinator,\n PersistedCollectionMode,\n PersistedCollectionPersistence,\n SQLiteCoreAdapterOptions,\n SQLiteDriver,\n} from '@tanstack/db-sqlite-persistence-core'\nimport type { DurableObjectStorageLike } from './do-driver'\n\nexport type { DurableObjectStorageLike } from './do-driver'\n\ntype CloudflareDOCoreSchemaMismatchPolicy =\n | `sync-present-reset`\n | `sync-absent-error`\n | `reset`\n\nexport type CloudflareDOSchemaMismatchPolicy =\n | CloudflareDOCoreSchemaMismatchPolicy\n | `throw`\n\ntype CloudflareDOSQLitePersistenceBaseOptions = Omit<\n SQLiteCoreAdapterOptions,\n `driver` | `schemaVersion` | `schemaMismatchPolicy`\n> & {\n storage: DurableObjectStorageLike\n coordinator?: PersistedCollectionCoordinator\n schemaMismatchPolicy?: CloudflareDOSchemaMismatchPolicy\n}\n\nexport type CloudflareDOSQLitePersistenceOptions =\n CloudflareDOSQLitePersistenceBaseOptions\n\nfunction normalizeSchemaMismatchPolicy(\n policy: CloudflareDOSchemaMismatchPolicy,\n): CloudflareDOCoreSchemaMismatchPolicy {\n if (policy === `throw`) {\n return `sync-absent-error`\n }\n return policy\n}\n\nfunction resolveSchemaMismatchPolicy(\n explicitPolicy: CloudflareDOSchemaMismatchPolicy | undefined,\n mode: PersistedCollectionMode,\n): CloudflareDOCoreSchemaMismatchPolicy {\n if (explicitPolicy) {\n return normalizeSchemaMismatchPolicy(explicitPolicy)\n }\n\n return mode === `sync-present` ? `sync-present-reset` : `sync-absent-error`\n}\n\nfunction createAdapterCacheKey(\n schemaMismatchPolicy: CloudflareDOCoreSchemaMismatchPolicy,\n schemaVersion: number | undefined,\n): string {\n const schemaVersionKey =\n schemaVersion === undefined ? `schema:default` : `schema:${schemaVersion}`\n return `${schemaMismatchPolicy}|${schemaVersionKey}`\n}\n\nfunction resolveSQLiteDriver(\n options: CloudflareDOSQLitePersistenceOptions,\n): SQLiteDriver {\n return new CloudflareDOSQLiteDriver({\n storage: options.storage,\n })\n}\n\nfunction resolveAdapterBaseOptions(\n options: CloudflareDOSQLitePersistenceOptions,\n): Omit<\n SQLiteCoreAdapterOptions,\n `driver` | `schemaVersion` | `schemaMismatchPolicy`\n> {\n return {\n appliedTxPruneMaxRows: options.appliedTxPruneMaxRows,\n appliedTxPruneMaxAgeSeconds: options.appliedTxPruneMaxAgeSeconds,\n pullSinceReloadThreshold: options.pullSinceReloadThreshold,\n }\n}\n\n/**\n * Creates a shared Durable Object SQLite persistence instance that can be reused\n * by many collections in a single Durable Object storage.\n */\nexport function createCloudflareDOSQLitePersistence<\n T extends object,\n TKey extends string | number = string | number,\n>(\n options: CloudflareDOSQLitePersistenceOptions,\n): PersistedCollectionPersistence<T, TKey> {\n const { coordinator, schemaMismatchPolicy } = options\n const driver = resolveSQLiteDriver(options)\n const adapterBaseOptions = resolveAdapterBaseOptions(options)\n const resolvedCoordinator = coordinator ?? new SingleProcessCoordinator()\n const adapterCache = new Map<\n string,\n ReturnType<\n typeof createSQLiteCorePersistenceAdapter<\n Record<string, unknown>,\n string | number\n >\n >\n >()\n\n const getAdapterForCollection = (\n mode: PersistedCollectionMode,\n schemaVersion: number | undefined,\n ) => {\n const resolvedSchemaMismatchPolicy = resolveSchemaMismatchPolicy(\n schemaMismatchPolicy,\n mode,\n )\n const cacheKey = createAdapterCacheKey(\n resolvedSchemaMismatchPolicy,\n schemaVersion,\n )\n const cachedAdapter = adapterCache.get(cacheKey)\n if (cachedAdapter) {\n return cachedAdapter\n }\n\n const adapter = createSQLiteCorePersistenceAdapter<\n Record<string, unknown>,\n string | number\n >({\n ...adapterBaseOptions,\n driver,\n schemaMismatchPolicy: resolvedSchemaMismatchPolicy,\n ...(schemaVersion === undefined ? {} : { schemaVersion }),\n })\n adapterCache.set(cacheKey, adapter)\n return adapter\n }\n\n const createCollectionPersistence = (\n mode: PersistedCollectionMode,\n schemaVersion: number | undefined,\n ): PersistedCollectionPersistence<T, TKey> => ({\n adapter: getAdapterForCollection(\n mode,\n schemaVersion,\n ) as unknown as PersistedCollectionPersistence<T, TKey>[`adapter`],\n coordinator: resolvedCoordinator,\n })\n\n const defaultPersistence = createCollectionPersistence(\n `sync-absent`,\n undefined,\n )\n\n return {\n ...defaultPersistence,\n resolvePersistenceForCollection: ({ mode, schemaVersion }) =>\n createCollectionPersistence(mode, schemaVersion),\n // Backward compatible fallback for older callers.\n resolvePersistenceForMode: (mode) =>\n createCollectionPersistence(mode, undefined),\n }\n}\n"],"names":["CloudflareDOSQLiteDriver","SingleProcessCoordinator","createSQLiteCorePersistenceAdapter"],"mappings":";;;;AAqCA,SAAS,8BACP,QACsC;AACtC,MAAI,WAAW,SAAS;AACtB,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,4BACP,gBACA,MACsC;AACtC,MAAI,gBAAgB;AAClB,WAAO,8BAA8B,cAAc;AAAA,EACrD;AAEA,SAAO,SAAS,iBAAiB,uBAAuB;AAC1D;AAEA,SAAS,sBACP,sBACA,eACQ;AACR,QAAM,mBACJ,kBAAkB,SAAY,mBAAmB,UAAU,aAAa;AAC1E,SAAO,GAAG,oBAAoB,IAAI,gBAAgB;AACpD;AAEA,SAAS,oBACP,SACc;AACd,SAAO,IAAIA,SAAAA,yBAAyB;AAAA,IAClC,SAAS,QAAQ;AAAA,EAAA,CAClB;AACH;AAEA,SAAS,0BACP,SAIA;AACA,SAAO;AAAA,IACL,uBAAuB,QAAQ;AAAA,IAC/B,6BAA6B,QAAQ;AAAA,IACrC,0BAA0B,QAAQ;AAAA,EAAA;AAEtC;AAMO,SAAS,oCAId,SACyC;AACzC,QAAM,EAAE,aAAa,qBAAA,IAAyB;AAC9C,QAAM,SAAS,oBAAoB,OAAO;AAC1C,QAAM,qBAAqB,0BAA0B,OAAO;AAC5D,QAAM,sBAAsB,eAAe,IAAIC,iDAAA;AAC/C,QAAM,mCAAmB,IAAA;AAUzB,QAAM,0BAA0B,CAC9B,MACA,kBACG;AACH,UAAM,+BAA+B;AAAA,MACnC;AAAA,MACA;AAAA,IAAA;AAEF,UAAM,WAAW;AAAA,MACf;AAAA,MACA;AAAA,IAAA;AAEF,UAAM,gBAAgB,aAAa,IAAI,QAAQ;AAC/C,QAAI,eAAe;AACjB,aAAO;AAAA,IACT;AAEA,UAAM,UAAUC,wBAAAA,mCAGd;AAAA,MACA,GAAG;AAAA,MACH;AAAA,MACA,sBAAsB;AAAA,MACtB,GAAI,kBAAkB,SAAY,CAAA,IAAK,EAAE,cAAA;AAAA,IAAc,CACxD;AACD,iBAAa,IAAI,UAAU,OAAO;AAClC,WAAO;AAAA,EACT;AAEA,QAAM,8BAA8B,CAClC,MACA,mBAC6C;AAAA,IAC7C,SAAS;AAAA,MACP;AAAA,MACA;AAAA,IAAA;AAAA,IAEF,aAAa;AAAA,EAAA;AAGf,QAAM,qBAAqB;AAAA,IACzB;AAAA,IACA;AAAA,EAAA;AAGF,SAAO;AAAA,IACL,GAAG;AAAA,IACH,iCAAiC,CAAC,EAAE,MAAM,oBACxC,4BAA4B,MAAM,aAAa;AAAA;AAAA,IAEjD,2BAA2B,CAAC,SAC1B,4BAA4B,MAAM,MAAS;AAAA,EAAA;AAEjD;;"}
@@ -0,0 +1,16 @@
1
+ import { PersistedCollectionCoordinator, PersistedCollectionPersistence, SQLiteCoreAdapterOptions } from '@tanstack/db-sqlite-persistence-core';
2
+ import { DurableObjectStorageLike } from './do-driver.cjs';
3
+ export type { DurableObjectStorageLike } from './do-driver.cjs';
4
+ type CloudflareDOCoreSchemaMismatchPolicy = `sync-present-reset` | `sync-absent-error` | `reset`;
5
+ export type CloudflareDOSchemaMismatchPolicy = CloudflareDOCoreSchemaMismatchPolicy | `throw`;
6
+ type CloudflareDOSQLitePersistenceBaseOptions = Omit<SQLiteCoreAdapterOptions, `driver` | `schemaVersion` | `schemaMismatchPolicy`> & {
7
+ storage: DurableObjectStorageLike;
8
+ coordinator?: PersistedCollectionCoordinator;
9
+ schemaMismatchPolicy?: CloudflareDOSchemaMismatchPolicy;
10
+ };
11
+ export type CloudflareDOSQLitePersistenceOptions = CloudflareDOSQLitePersistenceBaseOptions;
12
+ /**
13
+ * Creates a shared Durable Object SQLite persistence instance that can be reused
14
+ * by many collections in a single Durable Object storage.
15
+ */
16
+ export declare function createCloudflareDOSQLitePersistence<T extends object, TKey extends string | number = string | number>(options: CloudflareDOSQLitePersistenceOptions): PersistedCollectionPersistence<T, TKey>;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const doPersistence = require("./do-persistence.cjs");
4
+ const dbSqlitePersistenceCore = require("@tanstack/db-sqlite-persistence-core");
5
+ exports.createCloudflareDOSQLitePersistence = doPersistence.createCloudflareDOSQLitePersistence;
6
+ Object.defineProperty(exports, "persistedCollectionOptions", {
7
+ enumerable: true,
8
+ get: () => dbSqlitePersistenceCore.persistedCollectionOptions
9
+ });
10
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}
@@ -0,0 +1,4 @@
1
+ export { createCloudflareDOSQLitePersistence } from './do-persistence.cjs';
2
+ export type { CloudflareDOSchemaMismatchPolicy, CloudflareDOSQLitePersistenceOptions, DurableObjectStorageLike, } from './do-persistence.cjs';
3
+ export { persistedCollectionOptions } from '@tanstack/db-sqlite-persistence-core';
4
+ export type { PersistedCollectionCoordinator, PersistedCollectionPersistence, } from '@tanstack/db-sqlite-persistence-core';
@@ -0,0 +1,42 @@
1
+ import { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core';
2
+ type DurableObjectSqlRow = Record<string, unknown>;
3
+ type DurableObjectSqlCursorLike = Iterable<DurableObjectSqlRow> & {
4
+ toArray?: () => Array<DurableObjectSqlRow>;
5
+ };
6
+ export type DurableObjectSqlStorageLike = {
7
+ exec: (sql: string, ...params: ReadonlyArray<unknown>) => DurableObjectSqlCursorLike | ReadonlyArray<DurableObjectSqlRow> | null;
8
+ };
9
+ export type DurableObjectTransactionExecutor = <T>(fn: () => Promise<T>) => Promise<T>;
10
+ export type DurableObjectStorageLike = {
11
+ sql: DurableObjectSqlStorageLike;
12
+ transaction?: DurableObjectTransactionExecutor;
13
+ };
14
+ type CloudflareDOProvidedSqlOptions = {
15
+ sql: DurableObjectSqlStorageLike;
16
+ transaction?: DurableObjectTransactionExecutor;
17
+ };
18
+ type CloudflareDOProvidedStorageOptions = {
19
+ storage: DurableObjectStorageLike;
20
+ };
21
+ export type CloudflareDOSQLiteDriverOptions = CloudflareDOProvidedSqlOptions | CloudflareDOProvidedStorageOptions;
22
+ export declare class CloudflareDOSQLiteDriver implements SQLiteDriver {
23
+ private readonly sqlStorage;
24
+ private readonly storage;
25
+ private readonly transactionExecutor;
26
+ private queue;
27
+ private nextSavepointId;
28
+ constructor(options: CloudflareDOSQLiteDriverOptions);
29
+ exec(sql: string): Promise<void>;
30
+ query<T>(sql: string, params?: ReadonlyArray<unknown>): Promise<ReadonlyArray<T>>;
31
+ run(sql: string, params?: ReadonlyArray<unknown>): Promise<void>;
32
+ transaction<T>(fn: (transactionDriver: SQLiteDriver) => Promise<T>): Promise<T>;
33
+ transactionWithDriver<T>(fn: (transactionDriver: SQLiteDriver) => Promise<T>): Promise<T>;
34
+ getStorage(): DurableObjectStorageLike;
35
+ private execute;
36
+ private executeQuery;
37
+ private enqueue;
38
+ private createTransactionDriver;
39
+ private runNestedTransaction;
40
+ }
41
+ export declare function createCloudflareDOSQLiteDriver(options: CloudflareDOSQLiteDriverOptions): CloudflareDOSQLiteDriver;
42
+ export {};
@@ -0,0 +1,160 @@
1
+ import { InvalidPersistedCollectionConfigError } from "@tanstack/db-sqlite-persistence-core";
2
+ function assertTransactionCallbackHasDriverArg(fn) {
3
+ if (fn.length > 0) {
4
+ return;
5
+ }
6
+ throw new InvalidPersistedCollectionConfigError(
7
+ `SQLiteDriver.transaction callback must accept the transaction driver argument`
8
+ );
9
+ }
10
+ function isIterableRecord(value) {
11
+ if (!value || typeof value !== `object`) {
12
+ return false;
13
+ }
14
+ const iterator = value[Symbol.iterator];
15
+ return typeof iterator === `function`;
16
+ }
17
+ function toRowArray(result, sql) {
18
+ if (result == null) {
19
+ return [];
20
+ }
21
+ if (Array.isArray(result)) {
22
+ return result;
23
+ }
24
+ const cursorResult = result;
25
+ if (typeof cursorResult.toArray === `function`) {
26
+ return cursorResult.toArray();
27
+ }
28
+ if (isIterableRecord(cursorResult)) {
29
+ return Array.from(cursorResult);
30
+ }
31
+ throw new InvalidPersistedCollectionConfigError(
32
+ `Unsupported Durable Object SQL result shape for query "${sql}"`
33
+ );
34
+ }
35
+ class CloudflareDOSQLiteDriver {
36
+ constructor(options) {
37
+ this.queue = Promise.resolve();
38
+ this.nextSavepointId = 1;
39
+ const resolvedStorage = `storage` in options ? options.storage : {
40
+ sql: options.sql,
41
+ ...typeof options.transaction === `function` ? { transaction: options.transaction } : {}
42
+ };
43
+ const resolvedSqlStorage = resolvedStorage.sql;
44
+ if (typeof resolvedSqlStorage.exec !== `function`) {
45
+ throw new InvalidPersistedCollectionConfigError(
46
+ `Cloudflare DO SQL driver requires a sql.exec function`
47
+ );
48
+ }
49
+ this.storage = resolvedStorage;
50
+ this.sqlStorage = resolvedSqlStorage;
51
+ if (typeof resolvedStorage.transaction === `function`) {
52
+ const transactionMethod = resolvedStorage.transaction;
53
+ this.transactionExecutor = (fn) => Promise.resolve(
54
+ transactionMethod.call(resolvedStorage, fn)
55
+ );
56
+ } else {
57
+ this.transactionExecutor = null;
58
+ }
59
+ }
60
+ async exec(sql) {
61
+ await this.enqueue(() => {
62
+ this.execute(sql);
63
+ });
64
+ }
65
+ async query(sql, params = []) {
66
+ return this.enqueue(() => this.executeQuery(sql, params));
67
+ }
68
+ async run(sql, params = []) {
69
+ await this.enqueue(() => {
70
+ this.execute(sql, params);
71
+ });
72
+ }
73
+ async transaction(fn) {
74
+ assertTransactionCallbackHasDriverArg(fn);
75
+ return this.enqueue(async () => {
76
+ const transactionDriver = this.createTransactionDriver();
77
+ if (this.transactionExecutor) {
78
+ return this.transactionExecutor(() => fn(transactionDriver));
79
+ }
80
+ this.execute(`BEGIN IMMEDIATE`);
81
+ try {
82
+ const result = await fn(transactionDriver);
83
+ this.execute(`COMMIT`);
84
+ return result;
85
+ } catch (error) {
86
+ try {
87
+ this.execute(`ROLLBACK`);
88
+ } catch {
89
+ }
90
+ throw error;
91
+ }
92
+ });
93
+ }
94
+ async transactionWithDriver(fn) {
95
+ return this.transaction(fn);
96
+ }
97
+ getStorage() {
98
+ return this.storage;
99
+ }
100
+ execute(sql, params = []) {
101
+ return this.sqlStorage.exec(sql, ...params);
102
+ }
103
+ executeQuery(sql, params) {
104
+ const result = this.execute(sql, params);
105
+ return toRowArray(
106
+ result,
107
+ sql
108
+ );
109
+ }
110
+ enqueue(operation) {
111
+ const queuedOperation = this.queue.then(operation, operation);
112
+ this.queue = queuedOperation.then(
113
+ () => void 0,
114
+ () => void 0
115
+ );
116
+ return queuedOperation;
117
+ }
118
+ createTransactionDriver() {
119
+ const transactionDriver = {
120
+ exec: (sql) => {
121
+ this.execute(sql);
122
+ return Promise.resolve();
123
+ },
124
+ query: (sql, params = []) => Promise.resolve(this.executeQuery(sql, params)),
125
+ run: (sql, params = []) => {
126
+ this.execute(sql, params);
127
+ return Promise.resolve();
128
+ },
129
+ transaction: (fn) => {
130
+ assertTransactionCallbackHasDriverArg(fn);
131
+ return this.runNestedTransaction(transactionDriver, fn);
132
+ },
133
+ transactionWithDriver: (fn) => this.runNestedTransaction(transactionDriver, fn)
134
+ };
135
+ return transactionDriver;
136
+ }
137
+ async runNestedTransaction(transactionDriver, fn) {
138
+ if (this.transactionExecutor) {
139
+ throw new InvalidPersistedCollectionConfigError(
140
+ `Nested SQL savepoints are not supported when using Durable Object transaction API`
141
+ );
142
+ }
143
+ const savepointName = `tsdb_sp_${this.nextSavepointId}`;
144
+ this.nextSavepointId++;
145
+ this.execute(`SAVEPOINT ${savepointName}`);
146
+ try {
147
+ const result = await fn(transactionDriver);
148
+ this.execute(`RELEASE SAVEPOINT ${savepointName}`);
149
+ return result;
150
+ } catch (error) {
151
+ this.execute(`ROLLBACK TO SAVEPOINT ${savepointName}`);
152
+ this.execute(`RELEASE SAVEPOINT ${savepointName}`);
153
+ throw error;
154
+ }
155
+ }
156
+ }
157
+ export {
158
+ CloudflareDOSQLiteDriver
159
+ };
160
+ //# sourceMappingURL=do-driver.js.map
@@ -0,0 +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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"}
@@ -0,0 +1,16 @@
1
+ import { PersistedCollectionCoordinator, PersistedCollectionPersistence, SQLiteCoreAdapterOptions } from '@tanstack/db-sqlite-persistence-core';
2
+ import { DurableObjectStorageLike } from './do-driver.js';
3
+ export type { DurableObjectStorageLike } from './do-driver.js';
4
+ type CloudflareDOCoreSchemaMismatchPolicy = `sync-present-reset` | `sync-absent-error` | `reset`;
5
+ export type CloudflareDOSchemaMismatchPolicy = CloudflareDOCoreSchemaMismatchPolicy | `throw`;
6
+ type CloudflareDOSQLitePersistenceBaseOptions = Omit<SQLiteCoreAdapterOptions, `driver` | `schemaVersion` | `schemaMismatchPolicy`> & {
7
+ storage: DurableObjectStorageLike;
8
+ coordinator?: PersistedCollectionCoordinator;
9
+ schemaMismatchPolicy?: CloudflareDOSchemaMismatchPolicy;
10
+ };
11
+ export type CloudflareDOSQLitePersistenceOptions = CloudflareDOSQLitePersistenceBaseOptions;
12
+ /**
13
+ * Creates a shared Durable Object SQLite persistence instance that can be reused
14
+ * by many collections in a single Durable Object storage.
15
+ */
16
+ export declare function createCloudflareDOSQLitePersistence<T extends object, TKey extends string | number = string | number>(options: CloudflareDOSQLitePersistenceOptions): PersistedCollectionPersistence<T, TKey>;
@@ -0,0 +1,80 @@
1
+ import { SingleProcessCoordinator, createSQLiteCorePersistenceAdapter } from "@tanstack/db-sqlite-persistence-core";
2
+ import { CloudflareDOSQLiteDriver } from "./do-driver.js";
3
+ function normalizeSchemaMismatchPolicy(policy) {
4
+ if (policy === `throw`) {
5
+ return `sync-absent-error`;
6
+ }
7
+ return policy;
8
+ }
9
+ function resolveSchemaMismatchPolicy(explicitPolicy, mode) {
10
+ if (explicitPolicy) {
11
+ return normalizeSchemaMismatchPolicy(explicitPolicy);
12
+ }
13
+ return mode === `sync-present` ? `sync-present-reset` : `sync-absent-error`;
14
+ }
15
+ function createAdapterCacheKey(schemaMismatchPolicy, schemaVersion) {
16
+ const schemaVersionKey = schemaVersion === void 0 ? `schema:default` : `schema:${schemaVersion}`;
17
+ return `${schemaMismatchPolicy}|${schemaVersionKey}`;
18
+ }
19
+ function resolveSQLiteDriver(options) {
20
+ return new CloudflareDOSQLiteDriver({
21
+ storage: options.storage
22
+ });
23
+ }
24
+ function resolveAdapterBaseOptions(options) {
25
+ return {
26
+ appliedTxPruneMaxRows: options.appliedTxPruneMaxRows,
27
+ appliedTxPruneMaxAgeSeconds: options.appliedTxPruneMaxAgeSeconds,
28
+ pullSinceReloadThreshold: options.pullSinceReloadThreshold
29
+ };
30
+ }
31
+ function createCloudflareDOSQLitePersistence(options) {
32
+ const { coordinator, schemaMismatchPolicy } = options;
33
+ const driver = resolveSQLiteDriver(options);
34
+ const adapterBaseOptions = resolveAdapterBaseOptions(options);
35
+ const resolvedCoordinator = coordinator ?? new SingleProcessCoordinator();
36
+ const adapterCache = /* @__PURE__ */ new Map();
37
+ const getAdapterForCollection = (mode, schemaVersion) => {
38
+ const resolvedSchemaMismatchPolicy = resolveSchemaMismatchPolicy(
39
+ schemaMismatchPolicy,
40
+ mode
41
+ );
42
+ const cacheKey = createAdapterCacheKey(
43
+ resolvedSchemaMismatchPolicy,
44
+ schemaVersion
45
+ );
46
+ const cachedAdapter = adapterCache.get(cacheKey);
47
+ if (cachedAdapter) {
48
+ return cachedAdapter;
49
+ }
50
+ const adapter = createSQLiteCorePersistenceAdapter({
51
+ ...adapterBaseOptions,
52
+ driver,
53
+ schemaMismatchPolicy: resolvedSchemaMismatchPolicy,
54
+ ...schemaVersion === void 0 ? {} : { schemaVersion }
55
+ });
56
+ adapterCache.set(cacheKey, adapter);
57
+ return adapter;
58
+ };
59
+ const createCollectionPersistence = (mode, schemaVersion) => ({
60
+ adapter: getAdapterForCollection(
61
+ mode,
62
+ schemaVersion
63
+ ),
64
+ coordinator: resolvedCoordinator
65
+ });
66
+ const defaultPersistence = createCollectionPersistence(
67
+ `sync-absent`,
68
+ void 0
69
+ );
70
+ return {
71
+ ...defaultPersistence,
72
+ resolvePersistenceForCollection: ({ mode, schemaVersion }) => createCollectionPersistence(mode, schemaVersion),
73
+ // Backward compatible fallback for older callers.
74
+ resolvePersistenceForMode: (mode) => createCollectionPersistence(mode, void 0)
75
+ };
76
+ }
77
+ export {
78
+ createCloudflareDOSQLitePersistence
79
+ };
80
+ //# sourceMappingURL=do-persistence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"do-persistence.js","sources":["../../src/do-persistence.ts"],"sourcesContent":["import {\n SingleProcessCoordinator,\n createSQLiteCorePersistenceAdapter,\n} from '@tanstack/db-sqlite-persistence-core'\nimport { CloudflareDOSQLiteDriver } from './do-driver'\nimport type {\n PersistedCollectionCoordinator,\n PersistedCollectionMode,\n PersistedCollectionPersistence,\n SQLiteCoreAdapterOptions,\n SQLiteDriver,\n} from '@tanstack/db-sqlite-persistence-core'\nimport type { DurableObjectStorageLike } from './do-driver'\n\nexport type { DurableObjectStorageLike } from './do-driver'\n\ntype CloudflareDOCoreSchemaMismatchPolicy =\n | `sync-present-reset`\n | `sync-absent-error`\n | `reset`\n\nexport type CloudflareDOSchemaMismatchPolicy =\n | CloudflareDOCoreSchemaMismatchPolicy\n | `throw`\n\ntype CloudflareDOSQLitePersistenceBaseOptions = Omit<\n SQLiteCoreAdapterOptions,\n `driver` | `schemaVersion` | `schemaMismatchPolicy`\n> & {\n storage: DurableObjectStorageLike\n coordinator?: PersistedCollectionCoordinator\n schemaMismatchPolicy?: CloudflareDOSchemaMismatchPolicy\n}\n\nexport type CloudflareDOSQLitePersistenceOptions =\n CloudflareDOSQLitePersistenceBaseOptions\n\nfunction normalizeSchemaMismatchPolicy(\n policy: CloudflareDOSchemaMismatchPolicy,\n): CloudflareDOCoreSchemaMismatchPolicy {\n if (policy === `throw`) {\n return `sync-absent-error`\n }\n return policy\n}\n\nfunction resolveSchemaMismatchPolicy(\n explicitPolicy: CloudflareDOSchemaMismatchPolicy | undefined,\n mode: PersistedCollectionMode,\n): CloudflareDOCoreSchemaMismatchPolicy {\n if (explicitPolicy) {\n return normalizeSchemaMismatchPolicy(explicitPolicy)\n }\n\n return mode === `sync-present` ? `sync-present-reset` : `sync-absent-error`\n}\n\nfunction createAdapterCacheKey(\n schemaMismatchPolicy: CloudflareDOCoreSchemaMismatchPolicy,\n schemaVersion: number | undefined,\n): string {\n const schemaVersionKey =\n schemaVersion === undefined ? `schema:default` : `schema:${schemaVersion}`\n return `${schemaMismatchPolicy}|${schemaVersionKey}`\n}\n\nfunction resolveSQLiteDriver(\n options: CloudflareDOSQLitePersistenceOptions,\n): SQLiteDriver {\n return new CloudflareDOSQLiteDriver({\n storage: options.storage,\n })\n}\n\nfunction resolveAdapterBaseOptions(\n options: CloudflareDOSQLitePersistenceOptions,\n): Omit<\n SQLiteCoreAdapterOptions,\n `driver` | `schemaVersion` | `schemaMismatchPolicy`\n> {\n return {\n appliedTxPruneMaxRows: options.appliedTxPruneMaxRows,\n appliedTxPruneMaxAgeSeconds: options.appliedTxPruneMaxAgeSeconds,\n pullSinceReloadThreshold: options.pullSinceReloadThreshold,\n }\n}\n\n/**\n * Creates a shared Durable Object SQLite persistence instance that can be reused\n * by many collections in a single Durable Object storage.\n */\nexport function createCloudflareDOSQLitePersistence<\n T extends object,\n TKey extends string | number = string | number,\n>(\n options: CloudflareDOSQLitePersistenceOptions,\n): PersistedCollectionPersistence<T, TKey> {\n const { coordinator, schemaMismatchPolicy } = options\n const driver = resolveSQLiteDriver(options)\n const adapterBaseOptions = resolveAdapterBaseOptions(options)\n const resolvedCoordinator = coordinator ?? new SingleProcessCoordinator()\n const adapterCache = new Map<\n string,\n ReturnType<\n typeof createSQLiteCorePersistenceAdapter<\n Record<string, unknown>,\n string | number\n >\n >\n >()\n\n const getAdapterForCollection = (\n mode: PersistedCollectionMode,\n schemaVersion: number | undefined,\n ) => {\n const resolvedSchemaMismatchPolicy = resolveSchemaMismatchPolicy(\n schemaMismatchPolicy,\n mode,\n )\n const cacheKey = createAdapterCacheKey(\n resolvedSchemaMismatchPolicy,\n schemaVersion,\n )\n const cachedAdapter = adapterCache.get(cacheKey)\n if (cachedAdapter) {\n return cachedAdapter\n }\n\n const adapter = createSQLiteCorePersistenceAdapter<\n Record<string, unknown>,\n string | number\n >({\n ...adapterBaseOptions,\n driver,\n schemaMismatchPolicy: resolvedSchemaMismatchPolicy,\n ...(schemaVersion === undefined ? {} : { schemaVersion }),\n })\n adapterCache.set(cacheKey, adapter)\n return adapter\n }\n\n const createCollectionPersistence = (\n mode: PersistedCollectionMode,\n schemaVersion: number | undefined,\n ): PersistedCollectionPersistence<T, TKey> => ({\n adapter: getAdapterForCollection(\n mode,\n schemaVersion,\n ) as unknown as PersistedCollectionPersistence<T, TKey>[`adapter`],\n coordinator: resolvedCoordinator,\n })\n\n const defaultPersistence = createCollectionPersistence(\n `sync-absent`,\n undefined,\n )\n\n return {\n ...defaultPersistence,\n resolvePersistenceForCollection: ({ mode, schemaVersion }) =>\n createCollectionPersistence(mode, schemaVersion),\n // Backward compatible fallback for older callers.\n resolvePersistenceForMode: (mode) =>\n createCollectionPersistence(mode, undefined),\n }\n}\n"],"names":[],"mappings":";;AAqCA,SAAS,8BACP,QACsC;AACtC,MAAI,WAAW,SAAS;AACtB,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,4BACP,gBACA,MACsC;AACtC,MAAI,gBAAgB;AAClB,WAAO,8BAA8B,cAAc;AAAA,EACrD;AAEA,SAAO,SAAS,iBAAiB,uBAAuB;AAC1D;AAEA,SAAS,sBACP,sBACA,eACQ;AACR,QAAM,mBACJ,kBAAkB,SAAY,mBAAmB,UAAU,aAAa;AAC1E,SAAO,GAAG,oBAAoB,IAAI,gBAAgB;AACpD;AAEA,SAAS,oBACP,SACc;AACd,SAAO,IAAI,yBAAyB;AAAA,IAClC,SAAS,QAAQ;AAAA,EAAA,CAClB;AACH;AAEA,SAAS,0BACP,SAIA;AACA,SAAO;AAAA,IACL,uBAAuB,QAAQ;AAAA,IAC/B,6BAA6B,QAAQ;AAAA,IACrC,0BAA0B,QAAQ;AAAA,EAAA;AAEtC;AAMO,SAAS,oCAId,SACyC;AACzC,QAAM,EAAE,aAAa,qBAAA,IAAyB;AAC9C,QAAM,SAAS,oBAAoB,OAAO;AAC1C,QAAM,qBAAqB,0BAA0B,OAAO;AAC5D,QAAM,sBAAsB,eAAe,IAAI,yBAAA;AAC/C,QAAM,mCAAmB,IAAA;AAUzB,QAAM,0BAA0B,CAC9B,MACA,kBACG;AACH,UAAM,+BAA+B;AAAA,MACnC;AAAA,MACA;AAAA,IAAA;AAEF,UAAM,WAAW;AAAA,MACf;AAAA,MACA;AAAA,IAAA;AAEF,UAAM,gBAAgB,aAAa,IAAI,QAAQ;AAC/C,QAAI,eAAe;AACjB,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,mCAGd;AAAA,MACA,GAAG;AAAA,MACH;AAAA,MACA,sBAAsB;AAAA,MACtB,GAAI,kBAAkB,SAAY,CAAA,IAAK,EAAE,cAAA;AAAA,IAAc,CACxD;AACD,iBAAa,IAAI,UAAU,OAAO;AAClC,WAAO;AAAA,EACT;AAEA,QAAM,8BAA8B,CAClC,MACA,mBAC6C;AAAA,IAC7C,SAAS;AAAA,MACP;AAAA,MACA;AAAA,IAAA;AAAA,IAEF,aAAa;AAAA,EAAA;AAGf,QAAM,qBAAqB;AAAA,IACzB;AAAA,IACA;AAAA,EAAA;AAGF,SAAO;AAAA,IACL,GAAG;AAAA,IACH,iCAAiC,CAAC,EAAE,MAAM,oBACxC,4BAA4B,MAAM,aAAa;AAAA;AAAA,IAEjD,2BAA2B,CAAC,SAC1B,4BAA4B,MAAM,MAAS;AAAA,EAAA;AAEjD;"}
@@ -0,0 +1,4 @@
1
+ export { createCloudflareDOSQLitePersistence } from './do-persistence.js';
2
+ export type { CloudflareDOSchemaMismatchPolicy, CloudflareDOSQLitePersistenceOptions, DurableObjectStorageLike, } from './do-persistence.js';
3
+ export { persistedCollectionOptions } from '@tanstack/db-sqlite-persistence-core';
4
+ export type { PersistedCollectionCoordinator, PersistedCollectionPersistence, } from '@tanstack/db-sqlite-persistence-core';
@@ -0,0 +1,7 @@
1
+ import { createCloudflareDOSQLitePersistence } from "./do-persistence.js";
2
+ import { persistedCollectionOptions } from "@tanstack/db-sqlite-persistence-core";
3
+ export {
4
+ createCloudflareDOSQLitePersistence,
5
+ persistedCollectionOptions
6
+ };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@tanstack/cloudflare-durable-objects-db-sqlite-persistence",
3
+ "version": "0.1.2",
4
+ "description": "Cloudflare Durable Object SQLite persisted collection adapter for TanStack DB",
5
+ "author": "TanStack Team",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/TanStack/db.git",
10
+ "directory": "packages/cloudflare-durable-objects-db-sqlite-persistence"
11
+ },
12
+ "homepage": "https://tanstack.com/db",
13
+ "keywords": [
14
+ "sqlite",
15
+ "cloudflare",
16
+ "durable-objects",
17
+ "persistence",
18
+ "typescript"
19
+ ],
20
+ "scripts": {
21
+ "build": "vite build",
22
+ "dev": "vite build --watch",
23
+ "lint": "eslint . --fix",
24
+ "test": "vitest --run",
25
+ "test:e2e": "pnpm --filter @tanstack/db-ivm build && pnpm --filter @tanstack/db build && pnpm --filter @tanstack/db-sqlite-persistence-core build && pnpm --filter @tanstack/cloudflare-durable-objects-db-sqlite-persistence build && vitest --config vitest.e2e.config.ts --run"
26
+ },
27
+ "type": "module",
28
+ "main": "dist/cjs/index.cjs",
29
+ "module": "dist/esm/index.js",
30
+ "types": "dist/esm/index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "import": {
34
+ "types": "./dist/esm/index.d.ts",
35
+ "default": "./dist/esm/index.js"
36
+ },
37
+ "require": {
38
+ "types": "./dist/cjs/index.d.cts",
39
+ "default": "./dist/cjs/index.cjs"
40
+ }
41
+ },
42
+ "./package.json": "./package.json"
43
+ },
44
+ "sideEffects": false,
45
+ "files": [
46
+ "dist",
47
+ "src"
48
+ ],
49
+ "dependencies": {
50
+ "@tanstack/db-sqlite-persistence-core": "workspace:*"
51
+ },
52
+ "peerDependencies": {
53
+ "typescript": ">=4.7"
54
+ },
55
+ "devDependencies": {
56
+ "@types/better-sqlite3": "^7.6.13",
57
+ "@vitest/coverage-istanbul": "^3.2.4",
58
+ "better-sqlite3": "^12.6.2",
59
+ "wrangler": "^4.64.0"
60
+ }
61
+ }
@@ -0,0 +1,263 @@
1
+ import { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core'
2
+ import type { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core'
3
+
4
+ type DurableObjectSqlRow = Record<string, unknown>
5
+
6
+ type DurableObjectSqlCursorLike = Iterable<DurableObjectSqlRow> & {
7
+ toArray?: () => Array<DurableObjectSqlRow>
8
+ }
9
+
10
+ export type DurableObjectSqlStorageLike = {
11
+ exec: (
12
+ sql: string,
13
+ ...params: ReadonlyArray<unknown>
14
+ ) => DurableObjectSqlCursorLike | ReadonlyArray<DurableObjectSqlRow> | null
15
+ }
16
+
17
+ export type DurableObjectTransactionExecutor = <T>(
18
+ fn: () => Promise<T>,
19
+ ) => Promise<T>
20
+
21
+ export type DurableObjectStorageLike = {
22
+ sql: DurableObjectSqlStorageLike
23
+ transaction?: DurableObjectTransactionExecutor
24
+ }
25
+
26
+ type CloudflareDOProvidedSqlOptions = {
27
+ sql: DurableObjectSqlStorageLike
28
+ transaction?: DurableObjectTransactionExecutor
29
+ }
30
+
31
+ type CloudflareDOProvidedStorageOptions = {
32
+ storage: DurableObjectStorageLike
33
+ }
34
+
35
+ export type CloudflareDOSQLiteDriverOptions =
36
+ | CloudflareDOProvidedSqlOptions
37
+ | CloudflareDOProvidedStorageOptions
38
+
39
+ function assertTransactionCallbackHasDriverArg(
40
+ fn: (transactionDriver: SQLiteDriver) => Promise<unknown>,
41
+ ): void {
42
+ if (fn.length > 0) {
43
+ return
44
+ }
45
+
46
+ throw new InvalidPersistedCollectionConfigError(
47
+ `SQLiteDriver.transaction callback must accept the transaction driver argument`,
48
+ )
49
+ }
50
+
51
+ function isIterableRecord(
52
+ value: unknown,
53
+ ): value is Iterable<DurableObjectSqlRow> {
54
+ if (!value || typeof value !== `object`) {
55
+ return false
56
+ }
57
+
58
+ const iterator = (value as { [Symbol.iterator]?: unknown })[Symbol.iterator]
59
+ return typeof iterator === `function`
60
+ }
61
+
62
+ function toRowArray<T>(
63
+ result: ReturnType<DurableObjectSqlStorageLike[`exec`]>,
64
+ sql: string,
65
+ ): ReadonlyArray<T> {
66
+ if (result == null) {
67
+ return []
68
+ }
69
+
70
+ if (Array.isArray(result)) {
71
+ return result as ReadonlyArray<T>
72
+ }
73
+
74
+ const cursorResult = result as DurableObjectSqlCursorLike
75
+ if (typeof cursorResult.toArray === `function`) {
76
+ return cursorResult.toArray() as ReadonlyArray<T>
77
+ }
78
+
79
+ if (isIterableRecord(cursorResult)) {
80
+ return Array.from(cursorResult as Iterable<T>)
81
+ }
82
+
83
+ throw new InvalidPersistedCollectionConfigError(
84
+ `Unsupported Durable Object SQL result shape for query "${sql}"`,
85
+ )
86
+ }
87
+
88
+ export class CloudflareDOSQLiteDriver implements SQLiteDriver {
89
+ private readonly sqlStorage: DurableObjectSqlStorageLike
90
+ private readonly storage: DurableObjectStorageLike
91
+ private readonly transactionExecutor: DurableObjectTransactionExecutor | null
92
+ private queue: Promise<void> = Promise.resolve()
93
+ private nextSavepointId = 1
94
+
95
+ constructor(options: CloudflareDOSQLiteDriverOptions) {
96
+ const resolvedStorage: DurableObjectStorageLike =
97
+ `storage` in options
98
+ ? options.storage
99
+ : {
100
+ sql: options.sql,
101
+ ...(typeof options.transaction === `function`
102
+ ? { transaction: options.transaction }
103
+ : {}),
104
+ }
105
+ const resolvedSqlStorage = resolvedStorage.sql
106
+ if (typeof resolvedSqlStorage.exec !== `function`) {
107
+ throw new InvalidPersistedCollectionConfigError(
108
+ `Cloudflare DO SQL driver requires a sql.exec function`,
109
+ )
110
+ }
111
+ this.storage = resolvedStorage
112
+ this.sqlStorage = resolvedSqlStorage
113
+ if (typeof resolvedStorage.transaction === `function`) {
114
+ const transactionMethod = resolvedStorage.transaction
115
+ this.transactionExecutor = <T>(fn: () => Promise<T>) =>
116
+ Promise.resolve(
117
+ transactionMethod.call(resolvedStorage, fn) as Promise<T> | T,
118
+ )
119
+ } else {
120
+ this.transactionExecutor = null
121
+ }
122
+ }
123
+
124
+ async exec(sql: string): Promise<void> {
125
+ await this.enqueue(() => {
126
+ this.execute(sql)
127
+ })
128
+ }
129
+
130
+ async query<T>(
131
+ sql: string,
132
+ params: ReadonlyArray<unknown> = [],
133
+ ): Promise<ReadonlyArray<T>> {
134
+ return this.enqueue(() => this.executeQuery<T>(sql, params))
135
+ }
136
+
137
+ async run(sql: string, params: ReadonlyArray<unknown> = []): Promise<void> {
138
+ await this.enqueue(() => {
139
+ this.execute(sql, params)
140
+ })
141
+ }
142
+
143
+ async transaction<T>(
144
+ fn: (transactionDriver: SQLiteDriver) => Promise<T>,
145
+ ): Promise<T> {
146
+ assertTransactionCallbackHasDriverArg(fn)
147
+
148
+ return this.enqueue(async () => {
149
+ const transactionDriver = this.createTransactionDriver()
150
+ if (this.transactionExecutor) {
151
+ return this.transactionExecutor(() => fn(transactionDriver))
152
+ }
153
+
154
+ this.execute(`BEGIN IMMEDIATE`)
155
+ try {
156
+ const result = await fn(transactionDriver)
157
+ this.execute(`COMMIT`)
158
+ return result
159
+ } catch (error) {
160
+ try {
161
+ this.execute(`ROLLBACK`)
162
+ } catch {
163
+ // Keep the original transaction error as the primary failure.
164
+ }
165
+ throw error
166
+ }
167
+ })
168
+ }
169
+
170
+ async transactionWithDriver<T>(
171
+ fn: (transactionDriver: SQLiteDriver) => Promise<T>,
172
+ ): Promise<T> {
173
+ return this.transaction(fn)
174
+ }
175
+
176
+ getStorage(): DurableObjectStorageLike {
177
+ return this.storage
178
+ }
179
+
180
+ private execute(sql: string, params: ReadonlyArray<unknown> = []): unknown {
181
+ return this.sqlStorage.exec(sql, ...params)
182
+ }
183
+
184
+ private executeQuery<T>(
185
+ sql: string,
186
+ params: ReadonlyArray<unknown>,
187
+ ): ReadonlyArray<T> {
188
+ const result = this.execute(sql, params)
189
+ return toRowArray<T>(
190
+ result as ReturnType<DurableObjectSqlStorageLike[`exec`]>,
191
+ sql,
192
+ )
193
+ }
194
+
195
+ private enqueue<T>(operation: () => Promise<T> | T): Promise<T> {
196
+ const queuedOperation = this.queue.then(operation, operation)
197
+ this.queue = queuedOperation.then(
198
+ () => undefined,
199
+ () => undefined,
200
+ )
201
+ return queuedOperation
202
+ }
203
+
204
+ private createTransactionDriver(): SQLiteDriver {
205
+ const transactionDriver: SQLiteDriver = {
206
+ exec: (sql) => {
207
+ this.execute(sql)
208
+ return Promise.resolve()
209
+ },
210
+ query: <T>(
211
+ sql: string,
212
+ params: ReadonlyArray<unknown> = [],
213
+ ): Promise<ReadonlyArray<T>> =>
214
+ Promise.resolve(this.executeQuery<T>(sql, params)),
215
+ run: (sql, params = []) => {
216
+ this.execute(sql, params)
217
+ return Promise.resolve()
218
+ },
219
+ transaction: <T>(
220
+ fn: (nestedDriver: SQLiteDriver) => Promise<T>,
221
+ ): Promise<T> => {
222
+ assertTransactionCallbackHasDriverArg(fn)
223
+ return this.runNestedTransaction(transactionDriver, fn)
224
+ },
225
+ transactionWithDriver: <T>(
226
+ fn: (nestedDriver: SQLiteDriver) => Promise<T>,
227
+ ): Promise<T> => this.runNestedTransaction(transactionDriver, fn),
228
+ }
229
+
230
+ return transactionDriver
231
+ }
232
+
233
+ private async runNestedTransaction<T>(
234
+ transactionDriver: SQLiteDriver,
235
+ fn: (nestedDriver: SQLiteDriver) => Promise<T>,
236
+ ): Promise<T> {
237
+ if (this.transactionExecutor) {
238
+ throw new InvalidPersistedCollectionConfigError(
239
+ `Nested SQL savepoints are not supported when using Durable Object transaction API`,
240
+ )
241
+ }
242
+
243
+ const savepointName = `tsdb_sp_${this.nextSavepointId}`
244
+ this.nextSavepointId++
245
+ this.execute(`SAVEPOINT ${savepointName}`)
246
+
247
+ try {
248
+ const result = await fn(transactionDriver)
249
+ this.execute(`RELEASE SAVEPOINT ${savepointName}`)
250
+ return result
251
+ } catch (error) {
252
+ this.execute(`ROLLBACK TO SAVEPOINT ${savepointName}`)
253
+ this.execute(`RELEASE SAVEPOINT ${savepointName}`)
254
+ throw error
255
+ }
256
+ }
257
+ }
258
+
259
+ export function createCloudflareDOSQLiteDriver(
260
+ options: CloudflareDOSQLiteDriverOptions,
261
+ ): CloudflareDOSQLiteDriver {
262
+ return new CloudflareDOSQLiteDriver(options)
263
+ }
@@ -0,0 +1,166 @@
1
+ import {
2
+ SingleProcessCoordinator,
3
+ createSQLiteCorePersistenceAdapter,
4
+ } from '@tanstack/db-sqlite-persistence-core'
5
+ import { CloudflareDOSQLiteDriver } from './do-driver'
6
+ import type {
7
+ PersistedCollectionCoordinator,
8
+ PersistedCollectionMode,
9
+ PersistedCollectionPersistence,
10
+ SQLiteCoreAdapterOptions,
11
+ SQLiteDriver,
12
+ } from '@tanstack/db-sqlite-persistence-core'
13
+ import type { DurableObjectStorageLike } from './do-driver'
14
+
15
+ export type { DurableObjectStorageLike } from './do-driver'
16
+
17
+ type CloudflareDOCoreSchemaMismatchPolicy =
18
+ | `sync-present-reset`
19
+ | `sync-absent-error`
20
+ | `reset`
21
+
22
+ export type CloudflareDOSchemaMismatchPolicy =
23
+ | CloudflareDOCoreSchemaMismatchPolicy
24
+ | `throw`
25
+
26
+ type CloudflareDOSQLitePersistenceBaseOptions = Omit<
27
+ SQLiteCoreAdapterOptions,
28
+ `driver` | `schemaVersion` | `schemaMismatchPolicy`
29
+ > & {
30
+ storage: DurableObjectStorageLike
31
+ coordinator?: PersistedCollectionCoordinator
32
+ schemaMismatchPolicy?: CloudflareDOSchemaMismatchPolicy
33
+ }
34
+
35
+ export type CloudflareDOSQLitePersistenceOptions =
36
+ CloudflareDOSQLitePersistenceBaseOptions
37
+
38
+ function normalizeSchemaMismatchPolicy(
39
+ policy: CloudflareDOSchemaMismatchPolicy,
40
+ ): CloudflareDOCoreSchemaMismatchPolicy {
41
+ if (policy === `throw`) {
42
+ return `sync-absent-error`
43
+ }
44
+ return policy
45
+ }
46
+
47
+ function resolveSchemaMismatchPolicy(
48
+ explicitPolicy: CloudflareDOSchemaMismatchPolicy | undefined,
49
+ mode: PersistedCollectionMode,
50
+ ): CloudflareDOCoreSchemaMismatchPolicy {
51
+ if (explicitPolicy) {
52
+ return normalizeSchemaMismatchPolicy(explicitPolicy)
53
+ }
54
+
55
+ return mode === `sync-present` ? `sync-present-reset` : `sync-absent-error`
56
+ }
57
+
58
+ function createAdapterCacheKey(
59
+ schemaMismatchPolicy: CloudflareDOCoreSchemaMismatchPolicy,
60
+ schemaVersion: number | undefined,
61
+ ): string {
62
+ const schemaVersionKey =
63
+ schemaVersion === undefined ? `schema:default` : `schema:${schemaVersion}`
64
+ return `${schemaMismatchPolicy}|${schemaVersionKey}`
65
+ }
66
+
67
+ function resolveSQLiteDriver(
68
+ options: CloudflareDOSQLitePersistenceOptions,
69
+ ): SQLiteDriver {
70
+ return new CloudflareDOSQLiteDriver({
71
+ storage: options.storage,
72
+ })
73
+ }
74
+
75
+ function resolveAdapterBaseOptions(
76
+ options: CloudflareDOSQLitePersistenceOptions,
77
+ ): Omit<
78
+ SQLiteCoreAdapterOptions,
79
+ `driver` | `schemaVersion` | `schemaMismatchPolicy`
80
+ > {
81
+ return {
82
+ appliedTxPruneMaxRows: options.appliedTxPruneMaxRows,
83
+ appliedTxPruneMaxAgeSeconds: options.appliedTxPruneMaxAgeSeconds,
84
+ pullSinceReloadThreshold: options.pullSinceReloadThreshold,
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Creates a shared Durable Object SQLite persistence instance that can be reused
90
+ * by many collections in a single Durable Object storage.
91
+ */
92
+ export function createCloudflareDOSQLitePersistence<
93
+ T extends object,
94
+ TKey extends string | number = string | number,
95
+ >(
96
+ options: CloudflareDOSQLitePersistenceOptions,
97
+ ): PersistedCollectionPersistence<T, TKey> {
98
+ const { coordinator, schemaMismatchPolicy } = options
99
+ const driver = resolveSQLiteDriver(options)
100
+ const adapterBaseOptions = resolveAdapterBaseOptions(options)
101
+ const resolvedCoordinator = coordinator ?? new SingleProcessCoordinator()
102
+ const adapterCache = new Map<
103
+ string,
104
+ ReturnType<
105
+ typeof createSQLiteCorePersistenceAdapter<
106
+ Record<string, unknown>,
107
+ string | number
108
+ >
109
+ >
110
+ >()
111
+
112
+ const getAdapterForCollection = (
113
+ mode: PersistedCollectionMode,
114
+ schemaVersion: number | undefined,
115
+ ) => {
116
+ const resolvedSchemaMismatchPolicy = resolveSchemaMismatchPolicy(
117
+ schemaMismatchPolicy,
118
+ mode,
119
+ )
120
+ const cacheKey = createAdapterCacheKey(
121
+ resolvedSchemaMismatchPolicy,
122
+ schemaVersion,
123
+ )
124
+ const cachedAdapter = adapterCache.get(cacheKey)
125
+ if (cachedAdapter) {
126
+ return cachedAdapter
127
+ }
128
+
129
+ const adapter = createSQLiteCorePersistenceAdapter<
130
+ Record<string, unknown>,
131
+ string | number
132
+ >({
133
+ ...adapterBaseOptions,
134
+ driver,
135
+ schemaMismatchPolicy: resolvedSchemaMismatchPolicy,
136
+ ...(schemaVersion === undefined ? {} : { schemaVersion }),
137
+ })
138
+ adapterCache.set(cacheKey, adapter)
139
+ return adapter
140
+ }
141
+
142
+ const createCollectionPersistence = (
143
+ mode: PersistedCollectionMode,
144
+ schemaVersion: number | undefined,
145
+ ): PersistedCollectionPersistence<T, TKey> => ({
146
+ adapter: getAdapterForCollection(
147
+ mode,
148
+ schemaVersion,
149
+ ) as unknown as PersistedCollectionPersistence<T, TKey>[`adapter`],
150
+ coordinator: resolvedCoordinator,
151
+ })
152
+
153
+ const defaultPersistence = createCollectionPersistence(
154
+ `sync-absent`,
155
+ undefined,
156
+ )
157
+
158
+ return {
159
+ ...defaultPersistence,
160
+ resolvePersistenceForCollection: ({ mode, schemaVersion }) =>
161
+ createCollectionPersistence(mode, schemaVersion),
162
+ // Backward compatible fallback for older callers.
163
+ resolvePersistenceForMode: (mode) =>
164
+ createCollectionPersistence(mode, undefined),
165
+ }
166
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export { createCloudflareDOSQLitePersistence } from './do-persistence'
2
+ export type {
3
+ CloudflareDOSchemaMismatchPolicy,
4
+ CloudflareDOSQLitePersistenceOptions,
5
+ DurableObjectStorageLike,
6
+ } from './do-persistence'
7
+ export { persistedCollectionOptions } from '@tanstack/db-sqlite-persistence-core'
8
+ export type {
9
+ PersistedCollectionCoordinator,
10
+ PersistedCollectionPersistence,
11
+ } from '@tanstack/db-sqlite-persistence-core'