@tanstack/node-db-sqlite-persistence 0.2.21 → 0.2.25

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 Node SQLite persistence for TanStack DB.
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
  - `createNodeSQLitePersistence(...)`
@@ -9,6 +9,20 @@ const DEFAULT_PRAGMAS = [
9
9
  `foreign_keys = ON`
10
10
  ];
11
11
  const INVALID_PRAGMA_PATTERN = /(;|--|\/\*)/;
12
+ const databaseExecutionStates = /* @__PURE__ */ new WeakMap();
13
+ function getDatabaseExecutionState(database) {
14
+ const existing = databaseExecutionStates.get(database);
15
+ if (existing) {
16
+ return existing;
17
+ }
18
+ const state = {
19
+ transactionContext: new node_async_hooks.AsyncLocalStorage(),
20
+ queue: Promise.resolve(),
21
+ nextSavepointId: 1
22
+ };
23
+ databaseExecutionStates.set(database, state);
24
+ return state;
25
+ }
12
26
  function assertTransactionCallbackHasDriverArg(fn) {
13
27
  if (fn.length > 0) {
14
28
  return;
@@ -22,12 +36,10 @@ function hasExistingDatabase(options) {
22
36
  }
23
37
  class BetterSqlite3SQLiteDriver {
24
38
  constructor(options) {
25
- this.transactionContext = new node_async_hooks.AsyncLocalStorage();
26
- this.queue = Promise.resolve();
27
- this.nextSavepointId = 1;
28
39
  if (hasExistingDatabase(options)) {
29
40
  this.database = options.database;
30
41
  this.ownsDatabase = false;
42
+ this.executionState = getDatabaseExecutionState(this.database);
31
43
  this.applyPragmas(options.pragmas ?? DEFAULT_PRAGMAS);
32
44
  return;
33
45
  }
@@ -38,6 +50,7 @@ class BetterSqlite3SQLiteDriver {
38
50
  }
39
51
  this.database = new BetterSqlite3(options.filename, options.options);
40
52
  this.ownsDatabase = true;
53
+ this.executionState = getDatabaseExecutionState(this.database);
41
54
  this.applyPragmas(options.pragmas ?? DEFAULT_PRAGMAS);
42
55
  }
43
56
  async exec(sql) {
@@ -72,7 +85,7 @@ class BetterSqlite3SQLiteDriver {
72
85
  return this.enqueue(async () => {
73
86
  this.database.exec(`BEGIN IMMEDIATE`);
74
87
  try {
75
- const result = await this.transactionContext.run(
88
+ const result = await this.executionState.transactionContext.run(
76
89
  { depth: 1 },
77
90
  async () => fn(this)
78
91
  );
@@ -114,7 +127,7 @@ class BetterSqlite3SQLiteDriver {
114
127
  }
115
128
  }
116
129
  isInsideTransaction() {
117
- return this.transactionContext.getStore() !== void 0;
130
+ return this.executionState.transactionContext.getStore() !== void 0;
118
131
  }
119
132
  executeQuery(sql, params) {
120
133
  const statement = this.database.prepare(sql);
@@ -132,23 +145,23 @@ class BetterSqlite3SQLiteDriver {
132
145
  statement.run(...params);
133
146
  }
134
147
  enqueue(operation) {
135
- const queuedOperation = this.queue.then(operation, operation);
136
- this.queue = queuedOperation.then(
148
+ const queuedOperation = this.executionState.queue.then(operation, operation);
149
+ this.executionState.queue = queuedOperation.then(
137
150
  () => void 0,
138
151
  () => void 0
139
152
  );
140
153
  return queuedOperation;
141
154
  }
142
155
  async runNestedTransaction(fn) {
143
- const context = this.transactionContext.getStore();
156
+ const context = this.executionState.transactionContext.getStore();
144
157
  if (!context) {
145
158
  return fn(this);
146
159
  }
147
- const savepointName = `tsdb_sp_${this.nextSavepointId}`;
148
- this.nextSavepointId++;
160
+ const savepointName = `tsdb_sp_${this.executionState.nextSavepointId}`;
161
+ this.executionState.nextSavepointId++;
149
162
  this.database.exec(`SAVEPOINT ${savepointName}`);
150
163
  try {
151
- const result = await this.transactionContext.run(
164
+ const result = await this.executionState.transactionContext.run(
152
165
  { depth: context.depth + 1 },
153
166
  async () => fn(this)
154
167
  );
@@ -1 +1 @@
1
- {"version":3,"file":"node-driver.cjs","sources":["../../src/node-driver.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport BetterSqlite3 from 'better-sqlite3'\nimport { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core'\nimport type { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core'\n\nconst DEFAULT_PRAGMAS = [\n `journal_mode = WAL`,\n `synchronous = NORMAL`,\n `foreign_keys = ON`,\n] as const\n\nconst INVALID_PRAGMA_PATTERN = /(;|--|\\/\\*)/\n\nexport type BetterSqlite3Database = InstanceType<typeof BetterSqlite3>\nexport type BetterSqlite3OpenOptions = ConstructorParameters<\n typeof BetterSqlite3\n>[1]\n\ntype BetterSqlite3ExistingDatabaseOptions = {\n database: BetterSqlite3Database\n pragmas?: ReadonlyArray<string>\n}\n\ntype BetterSqlite3OpenFileOptions = {\n filename: string\n options?: BetterSqlite3OpenOptions\n pragmas?: ReadonlyArray<string>\n}\n\nexport type BetterSqlite3DriverOptions =\n | BetterSqlite3ExistingDatabaseOptions\n | BetterSqlite3OpenFileOptions\n\ntype TransactionContext = {\n depth: number\n}\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 hasExistingDatabase(\n options: BetterSqlite3DriverOptions,\n): options is BetterSqlite3ExistingDatabaseOptions {\n return `database` in options\n}\n\nexport class BetterSqlite3SQLiteDriver implements SQLiteDriver {\n private readonly database: BetterSqlite3Database\n private readonly ownsDatabase: boolean\n private readonly transactionContext =\n new AsyncLocalStorage<TransactionContext>()\n private queue: Promise<void> = Promise.resolve()\n private nextSavepointId = 1\n\n constructor(options: BetterSqlite3DriverOptions) {\n if (hasExistingDatabase(options)) {\n this.database = options.database\n this.ownsDatabase = false\n this.applyPragmas(options.pragmas ?? DEFAULT_PRAGMAS)\n return\n }\n\n if (options.filename.trim().length === 0) {\n throw new InvalidPersistedCollectionConfigError(\n `Node SQLite driver filename cannot be empty`,\n )\n }\n\n this.database = new BetterSqlite3(options.filename, options.options)\n this.ownsDatabase = true\n this.applyPragmas(options.pragmas ?? DEFAULT_PRAGMAS)\n }\n\n async exec(sql: string): Promise<void> {\n if (this.isInsideTransaction()) {\n this.database.exec(sql)\n return\n }\n\n await this.enqueue(() => {\n this.database.exec(sql)\n })\n }\n\n async query<T>(\n sql: string,\n params: ReadonlyArray<unknown> = [],\n ): Promise<ReadonlyArray<T>> {\n if (this.isInsideTransaction()) {\n return this.executeQuery<T>(sql, params)\n }\n\n return this.enqueue(() => this.executeQuery<T>(sql, params))\n }\n\n async run(sql: string, params: ReadonlyArray<unknown> = []): Promise<void> {\n if (this.isInsideTransaction()) {\n this.executeRun(sql, params)\n return\n }\n\n await this.enqueue(() => {\n this.executeRun(sql, params)\n })\n }\n\n async transaction<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n assertTransactionCallbackHasDriverArg(fn)\n\n if (this.isInsideTransaction()) {\n return this.runNestedTransaction(fn)\n }\n\n return this.enqueue(async () => {\n this.database.exec(`BEGIN IMMEDIATE`)\n try {\n const result = await this.transactionContext.run(\n { depth: 1 },\n async () => fn(this),\n )\n this.database.exec(`COMMIT`)\n return result\n } catch (error) {\n try {\n this.database.exec(`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 close(): void {\n if (!this.ownsDatabase) {\n return\n }\n\n this.database.close()\n }\n\n getDatabase(): BetterSqlite3Database {\n return this.database\n }\n\n private applyPragmas(pragmas: ReadonlyArray<string>): void {\n for (const pragma of pragmas) {\n const trimmedPragma = pragma.trim()\n if (trimmedPragma.length === 0) {\n continue\n }\n\n if (INVALID_PRAGMA_PATTERN.test(trimmedPragma)) {\n throw new InvalidPersistedCollectionConfigError(\n `Invalid SQLite PRAGMA: \"${pragma}\"`,\n )\n }\n\n this.database.exec(`PRAGMA ${trimmedPragma}`)\n }\n }\n\n private isInsideTransaction(): boolean {\n return this.transactionContext.getStore() !== undefined\n }\n\n private executeQuery<T>(\n sql: string,\n params: ReadonlyArray<unknown>,\n ): ReadonlyArray<T> {\n const statement = this.database.prepare(sql)\n if (params.length === 0) {\n return statement.all() as ReadonlyArray<T>\n }\n\n return statement.all(...params) as ReadonlyArray<T>\n }\n\n private executeRun(sql: string, params: ReadonlyArray<unknown>): void {\n const statement = this.database.prepare(sql)\n if (params.length === 0) {\n statement.run()\n return\n }\n\n statement.run(...params)\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 async runNestedTransaction<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n const context = this.transactionContext.getStore()\n if (!context) {\n return fn(this)\n }\n\n const savepointName = `tsdb_sp_${this.nextSavepointId}`\n this.nextSavepointId++\n this.database.exec(`SAVEPOINT ${savepointName}`)\n\n try {\n const result = await this.transactionContext.run(\n { depth: context.depth + 1 },\n async () => fn(this),\n )\n this.database.exec(`RELEASE SAVEPOINT ${savepointName}`)\n return result\n } catch (error) {\n this.database.exec(`ROLLBACK TO SAVEPOINT ${savepointName}`)\n this.database.exec(`RELEASE SAVEPOINT ${savepointName}`)\n throw error\n }\n }\n}\n\nexport function createBetterSqlite3Driver(\n options: BetterSqlite3DriverOptions,\n): BetterSqlite3SQLiteDriver {\n return new BetterSqlite3SQLiteDriver(options)\n}\n"],"names":["InvalidPersistedCollectionConfigError","AsyncLocalStorage"],"mappings":"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"}
1
+ {"version":3,"file":"node-driver.cjs","sources":["../../src/node-driver.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport BetterSqlite3 from 'better-sqlite3'\nimport { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core'\nimport type { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core'\n\nconst DEFAULT_PRAGMAS = [\n `journal_mode = WAL`,\n `synchronous = NORMAL`,\n `foreign_keys = ON`,\n] as const\n\nconst INVALID_PRAGMA_PATTERN = /(;|--|\\/\\*)/\n\nexport type BetterSqlite3Database = InstanceType<typeof BetterSqlite3>\nexport type BetterSqlite3OpenOptions = ConstructorParameters<\n typeof BetterSqlite3\n>[1]\n\ntype BetterSqlite3ExistingDatabaseOptions = {\n database: BetterSqlite3Database\n pragmas?: ReadonlyArray<string>\n}\n\ntype BetterSqlite3OpenFileOptions = {\n filename: string\n options?: BetterSqlite3OpenOptions\n pragmas?: ReadonlyArray<string>\n}\n\nexport type BetterSqlite3DriverOptions =\n | BetterSqlite3ExistingDatabaseOptions\n | BetterSqlite3OpenFileOptions\n\ntype TransactionContext = {\n depth: number\n}\n\ntype DatabaseExecutionState = {\n transactionContext: AsyncLocalStorage<TransactionContext>\n queue: Promise<void>\n nextSavepointId: number\n}\n\nconst databaseExecutionStates = new WeakMap<\n BetterSqlite3Database,\n DatabaseExecutionState\n>()\n\nfunction getDatabaseExecutionState(\n database: BetterSqlite3Database,\n): DatabaseExecutionState {\n const existing = databaseExecutionStates.get(database)\n if (existing) {\n return existing\n }\n\n const state: DatabaseExecutionState = {\n transactionContext: new AsyncLocalStorage<TransactionContext>(),\n queue: Promise.resolve(),\n nextSavepointId: 1,\n }\n databaseExecutionStates.set(database, state)\n return state\n}\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 hasExistingDatabase(\n options: BetterSqlite3DriverOptions,\n): options is BetterSqlite3ExistingDatabaseOptions {\n return `database` in options\n}\n\nexport class BetterSqlite3SQLiteDriver implements SQLiteDriver {\n private readonly database: BetterSqlite3Database\n private readonly ownsDatabase: boolean\n private readonly executionState: DatabaseExecutionState\n\n constructor(options: BetterSqlite3DriverOptions) {\n if (hasExistingDatabase(options)) {\n this.database = options.database\n this.ownsDatabase = false\n this.executionState = getDatabaseExecutionState(this.database)\n this.applyPragmas(options.pragmas ?? DEFAULT_PRAGMAS)\n return\n }\n\n if (options.filename.trim().length === 0) {\n throw new InvalidPersistedCollectionConfigError(\n `Node SQLite driver filename cannot be empty`,\n )\n }\n\n this.database = new BetterSqlite3(options.filename, options.options)\n this.ownsDatabase = true\n this.executionState = getDatabaseExecutionState(this.database)\n this.applyPragmas(options.pragmas ?? DEFAULT_PRAGMAS)\n }\n\n async exec(sql: string): Promise<void> {\n if (this.isInsideTransaction()) {\n this.database.exec(sql)\n return\n }\n\n await this.enqueue(() => {\n this.database.exec(sql)\n })\n }\n\n async query<T>(\n sql: string,\n params: ReadonlyArray<unknown> = [],\n ): Promise<ReadonlyArray<T>> {\n if (this.isInsideTransaction()) {\n return this.executeQuery<T>(sql, params)\n }\n\n return this.enqueue(() => this.executeQuery<T>(sql, params))\n }\n\n async run(sql: string, params: ReadonlyArray<unknown> = []): Promise<void> {\n if (this.isInsideTransaction()) {\n this.executeRun(sql, params)\n return\n }\n\n await this.enqueue(() => {\n this.executeRun(sql, params)\n })\n }\n\n async transaction<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n assertTransactionCallbackHasDriverArg(fn)\n\n if (this.isInsideTransaction()) {\n return this.runNestedTransaction(fn)\n }\n\n return this.enqueue(async () => {\n this.database.exec(`BEGIN IMMEDIATE`)\n try {\n const result = await this.executionState.transactionContext.run(\n { depth: 1 },\n async () => fn(this),\n )\n this.database.exec(`COMMIT`)\n return result\n } catch (error) {\n try {\n this.database.exec(`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 close(): void {\n if (!this.ownsDatabase) {\n return\n }\n\n this.database.close()\n }\n\n getDatabase(): BetterSqlite3Database {\n return this.database\n }\n\n private applyPragmas(pragmas: ReadonlyArray<string>): void {\n for (const pragma of pragmas) {\n const trimmedPragma = pragma.trim()\n if (trimmedPragma.length === 0) {\n continue\n }\n\n if (INVALID_PRAGMA_PATTERN.test(trimmedPragma)) {\n throw new InvalidPersistedCollectionConfigError(\n `Invalid SQLite PRAGMA: \"${pragma}\"`,\n )\n }\n\n this.database.exec(`PRAGMA ${trimmedPragma}`)\n }\n }\n\n private isInsideTransaction(): boolean {\n return this.executionState.transactionContext.getStore() !== undefined\n }\n\n private executeQuery<T>(\n sql: string,\n params: ReadonlyArray<unknown>,\n ): ReadonlyArray<T> {\n const statement = this.database.prepare(sql)\n if (params.length === 0) {\n return statement.all() as ReadonlyArray<T>\n }\n\n return statement.all(...params) as ReadonlyArray<T>\n }\n\n private executeRun(sql: string, params: ReadonlyArray<unknown>): void {\n const statement = this.database.prepare(sql)\n if (params.length === 0) {\n statement.run()\n return\n }\n\n statement.run(...params)\n }\n\n private enqueue<T>(operation: () => Promise<T> | T): Promise<T> {\n const queuedOperation = this.executionState.queue.then(operation, operation)\n this.executionState.queue = queuedOperation.then(\n () => undefined,\n () => undefined,\n )\n return queuedOperation\n }\n\n private async runNestedTransaction<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n const context = this.executionState.transactionContext.getStore()\n if (!context) {\n return fn(this)\n }\n\n const savepointName = `tsdb_sp_${this.executionState.nextSavepointId}`\n this.executionState.nextSavepointId++\n this.database.exec(`SAVEPOINT ${savepointName}`)\n\n try {\n const result = await this.executionState.transactionContext.run(\n { depth: context.depth + 1 },\n async () => fn(this),\n )\n this.database.exec(`RELEASE SAVEPOINT ${savepointName}`)\n return result\n } catch (error) {\n this.database.exec(`ROLLBACK TO SAVEPOINT ${savepointName}`)\n this.database.exec(`RELEASE SAVEPOINT ${savepointName}`)\n throw error\n }\n }\n}\n\nexport function createBetterSqlite3Driver(\n options: BetterSqlite3DriverOptions,\n): BetterSqlite3SQLiteDriver {\n return new BetterSqlite3SQLiteDriver(options)\n}\n"],"names":["AsyncLocalStorage","InvalidPersistedCollectionConfigError"],"mappings":";;;;;AAKA,MAAM,kBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,yBAAyB;AAgC/B,MAAM,8CAA8B,QAAA;AAKpC,SAAS,0BACP,UACwB;AACxB,QAAM,WAAW,wBAAwB,IAAI,QAAQ;AACrD,MAAI,UAAU;AACZ,WAAO;AAAA,EACT;AAEA,QAAM,QAAgC;AAAA,IACpC,oBAAoB,IAAIA,iBAAAA,kBAAA;AAAA,IACxB,OAAO,QAAQ,QAAA;AAAA,IACf,iBAAiB;AAAA,EAAA;AAEnB,0BAAwB,IAAI,UAAU,KAAK;AAC3C,SAAO;AACT;AAEA,SAAS,sCACP,IACM;AACN,MAAI,GAAG,SAAS,GAAG;AACjB;AAAA,EACF;AAEA,QAAM,IAAIC,wBAAAA;AAAAA,IACR;AAAA,EAAA;AAEJ;AAEA,SAAS,oBACP,SACiD;AACjD,SAAO,cAAc;AACvB;AAEO,MAAM,0BAAkD;AAAA,EAK7D,YAAY,SAAqC;AAC/C,QAAI,oBAAoB,OAAO,GAAG;AAChC,WAAK,WAAW,QAAQ;AACxB,WAAK,eAAe;AACpB,WAAK,iBAAiB,0BAA0B,KAAK,QAAQ;AAC7D,WAAK,aAAa,QAAQ,WAAW,eAAe;AACpD;AAAA,IACF;AAEA,QAAI,QAAQ,SAAS,KAAA,EAAO,WAAW,GAAG;AACxC,YAAM,IAAIA,wBAAAA;AAAAA,QACR;AAAA,MAAA;AAAA,IAEJ;AAEA,SAAK,WAAW,IAAI,cAAc,QAAQ,UAAU,QAAQ,OAAO;AACnE,SAAK,eAAe;AACpB,SAAK,iBAAiB,0BAA0B,KAAK,QAAQ;AAC7D,SAAK,aAAa,QAAQ,WAAW,eAAe;AAAA,EACtD;AAAA,EAEA,MAAM,KAAK,KAA4B;AACrC,QAAI,KAAK,uBAAuB;AAC9B,WAAK,SAAS,KAAK,GAAG;AACtB;AAAA,IACF;AAEA,UAAM,KAAK,QAAQ,MAAM;AACvB,WAAK,SAAS,KAAK,GAAG;AAAA,IACxB,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MACJ,KACA,SAAiC,IACN;AAC3B,QAAI,KAAK,uBAAuB;AAC9B,aAAO,KAAK,aAAgB,KAAK,MAAM;AAAA,IACzC;AAEA,WAAO,KAAK,QAAQ,MAAM,KAAK,aAAgB,KAAK,MAAM,CAAC;AAAA,EAC7D;AAAA,EAEA,MAAM,IAAI,KAAa,SAAiC,IAAmB;AACzE,QAAI,KAAK,uBAAuB;AAC9B,WAAK,WAAW,KAAK,MAAM;AAC3B;AAAA,IACF;AAEA,UAAM,KAAK,QAAQ,MAAM;AACvB,WAAK,WAAW,KAAK,MAAM;AAAA,IAC7B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,YACJ,IACY;AACZ,0CAAsC,EAAE;AAExC,QAAI,KAAK,uBAAuB;AAC9B,aAAO,KAAK,qBAAqB,EAAE;AAAA,IACrC;AAEA,WAAO,KAAK,QAAQ,YAAY;AAC9B,WAAK,SAAS,KAAK,iBAAiB;AACpC,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,eAAe,mBAAmB;AAAA,UAC1D,EAAE,OAAO,EAAA;AAAA,UACT,YAAY,GAAG,IAAI;AAAA,QAAA;AAErB,aAAK,SAAS,KAAK,QAAQ;AAC3B,eAAO;AAAA,MACT,SAAS,OAAO;AACd,YAAI;AACF,eAAK,SAAS,KAAK,UAAU;AAAA,QAC/B,QAAQ;AAAA,QAER;AACA,cAAM;AAAA,MACR;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,sBACJ,IACY;AACZ,WAAO,KAAK,YAAY,EAAE;AAAA,EAC5B;AAAA,EAEA,QAAc;AACZ,QAAI,CAAC,KAAK,cAAc;AACtB;AAAA,IACF;AAEA,SAAK,SAAS,MAAA;AAAA,EAChB;AAAA,EAEA,cAAqC;AACnC,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,aAAa,SAAsC;AACzD,eAAW,UAAU,SAAS;AAC5B,YAAM,gBAAgB,OAAO,KAAA;AAC7B,UAAI,cAAc,WAAW,GAAG;AAC9B;AAAA,MACF;AAEA,UAAI,uBAAuB,KAAK,aAAa,GAAG;AAC9C,cAAM,IAAIA,wBAAAA;AAAAA,UACR,2BAA2B,MAAM;AAAA,QAAA;AAAA,MAErC;AAEA,WAAK,SAAS,KAAK,UAAU,aAAa,EAAE;AAAA,IAC9C;AAAA,EACF;AAAA,EAEQ,sBAA+B;AACrC,WAAO,KAAK,eAAe,mBAAmB,SAAA,MAAe;AAAA,EAC/D;AAAA,EAEQ,aACN,KACA,QACkB;AAClB,UAAM,YAAY,KAAK,SAAS,QAAQ,GAAG;AAC3C,QAAI,OAAO,WAAW,GAAG;AACvB,aAAO,UAAU,IAAA;AAAA,IACnB;AAEA,WAAO,UAAU,IAAI,GAAG,MAAM;AAAA,EAChC;AAAA,EAEQ,WAAW,KAAa,QAAsC;AACpE,UAAM,YAAY,KAAK,SAAS,QAAQ,GAAG;AAC3C,QAAI,OAAO,WAAW,GAAG;AACvB,gBAAU,IAAA;AACV;AAAA,IACF;AAEA,cAAU,IAAI,GAAG,MAAM;AAAA,EACzB;AAAA,EAEQ,QAAW,WAA6C;AAC9D,UAAM,kBAAkB,KAAK,eAAe,MAAM,KAAK,WAAW,SAAS;AAC3E,SAAK,eAAe,QAAQ,gBAAgB;AAAA,MAC1C,MAAM;AAAA,MACN,MAAM;AAAA,IAAA;AAER,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,qBACZ,IACY;AACZ,UAAM,UAAU,KAAK,eAAe,mBAAmB,SAAA;AACvD,QAAI,CAAC,SAAS;AACZ,aAAO,GAAG,IAAI;AAAA,IAChB;AAEA,UAAM,gBAAgB,WAAW,KAAK,eAAe,eAAe;AACpE,SAAK,eAAe;AACpB,SAAK,SAAS,KAAK,aAAa,aAAa,EAAE;AAE/C,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,eAAe,mBAAmB;AAAA,QAC1D,EAAE,OAAO,QAAQ,QAAQ,EAAA;AAAA,QACzB,YAAY,GAAG,IAAI;AAAA,MAAA;AAErB,WAAK,SAAS,KAAK,qBAAqB,aAAa,EAAE;AACvD,aAAO;AAAA,IACT,SAAS,OAAO;AACd,WAAK,SAAS,KAAK,yBAAyB,aAAa,EAAE;AAC3D,WAAK,SAAS,KAAK,qBAAqB,aAAa,EAAE;AACvD,YAAM;AAAA,IACR;AAAA,EACF;AACF;;"}
@@ -15,9 +15,7 @@ export type BetterSqlite3DriverOptions = BetterSqlite3ExistingDatabaseOptions |
15
15
  export declare class BetterSqlite3SQLiteDriver implements SQLiteDriver {
16
16
  private readonly database;
17
17
  private readonly ownsDatabase;
18
- private readonly transactionContext;
19
- private queue;
20
- private nextSavepointId;
18
+ private readonly executionState;
21
19
  constructor(options: BetterSqlite3DriverOptions);
22
20
  exec(sql: string): Promise<void>;
23
21
  query<T>(sql: string, params?: ReadonlyArray<unknown>): Promise<ReadonlyArray<T>>;
@@ -15,9 +15,7 @@ export type BetterSqlite3DriverOptions = BetterSqlite3ExistingDatabaseOptions |
15
15
  export declare class BetterSqlite3SQLiteDriver implements SQLiteDriver {
16
16
  private readonly database;
17
17
  private readonly ownsDatabase;
18
- private readonly transactionContext;
19
- private queue;
20
- private nextSavepointId;
18
+ private readonly executionState;
21
19
  constructor(options: BetterSqlite3DriverOptions);
22
20
  exec(sql: string): Promise<void>;
23
21
  query<T>(sql: string, params?: ReadonlyArray<unknown>): Promise<ReadonlyArray<T>>;
@@ -7,6 +7,20 @@ const DEFAULT_PRAGMAS = [
7
7
  `foreign_keys = ON`
8
8
  ];
9
9
  const INVALID_PRAGMA_PATTERN = /(;|--|\/\*)/;
10
+ const databaseExecutionStates = /* @__PURE__ */ new WeakMap();
11
+ function getDatabaseExecutionState(database) {
12
+ const existing = databaseExecutionStates.get(database);
13
+ if (existing) {
14
+ return existing;
15
+ }
16
+ const state = {
17
+ transactionContext: new AsyncLocalStorage(),
18
+ queue: Promise.resolve(),
19
+ nextSavepointId: 1
20
+ };
21
+ databaseExecutionStates.set(database, state);
22
+ return state;
23
+ }
10
24
  function assertTransactionCallbackHasDriverArg(fn) {
11
25
  if (fn.length > 0) {
12
26
  return;
@@ -20,12 +34,10 @@ function hasExistingDatabase(options) {
20
34
  }
21
35
  class BetterSqlite3SQLiteDriver {
22
36
  constructor(options) {
23
- this.transactionContext = new AsyncLocalStorage();
24
- this.queue = Promise.resolve();
25
- this.nextSavepointId = 1;
26
37
  if (hasExistingDatabase(options)) {
27
38
  this.database = options.database;
28
39
  this.ownsDatabase = false;
40
+ this.executionState = getDatabaseExecutionState(this.database);
29
41
  this.applyPragmas(options.pragmas ?? DEFAULT_PRAGMAS);
30
42
  return;
31
43
  }
@@ -36,6 +48,7 @@ class BetterSqlite3SQLiteDriver {
36
48
  }
37
49
  this.database = new BetterSqlite3(options.filename, options.options);
38
50
  this.ownsDatabase = true;
51
+ this.executionState = getDatabaseExecutionState(this.database);
39
52
  this.applyPragmas(options.pragmas ?? DEFAULT_PRAGMAS);
40
53
  }
41
54
  async exec(sql) {
@@ -70,7 +83,7 @@ class BetterSqlite3SQLiteDriver {
70
83
  return this.enqueue(async () => {
71
84
  this.database.exec(`BEGIN IMMEDIATE`);
72
85
  try {
73
- const result = await this.transactionContext.run(
86
+ const result = await this.executionState.transactionContext.run(
74
87
  { depth: 1 },
75
88
  async () => fn(this)
76
89
  );
@@ -112,7 +125,7 @@ class BetterSqlite3SQLiteDriver {
112
125
  }
113
126
  }
114
127
  isInsideTransaction() {
115
- return this.transactionContext.getStore() !== void 0;
128
+ return this.executionState.transactionContext.getStore() !== void 0;
116
129
  }
117
130
  executeQuery(sql, params) {
118
131
  const statement = this.database.prepare(sql);
@@ -130,23 +143,23 @@ class BetterSqlite3SQLiteDriver {
130
143
  statement.run(...params);
131
144
  }
132
145
  enqueue(operation) {
133
- const queuedOperation = this.queue.then(operation, operation);
134
- this.queue = queuedOperation.then(
146
+ const queuedOperation = this.executionState.queue.then(operation, operation);
147
+ this.executionState.queue = queuedOperation.then(
135
148
  () => void 0,
136
149
  () => void 0
137
150
  );
138
151
  return queuedOperation;
139
152
  }
140
153
  async runNestedTransaction(fn) {
141
- const context = this.transactionContext.getStore();
154
+ const context = this.executionState.transactionContext.getStore();
142
155
  if (!context) {
143
156
  return fn(this);
144
157
  }
145
- const savepointName = `tsdb_sp_${this.nextSavepointId}`;
146
- this.nextSavepointId++;
158
+ const savepointName = `tsdb_sp_${this.executionState.nextSavepointId}`;
159
+ this.executionState.nextSavepointId++;
147
160
  this.database.exec(`SAVEPOINT ${savepointName}`);
148
161
  try {
149
- const result = await this.transactionContext.run(
162
+ const result = await this.executionState.transactionContext.run(
150
163
  { depth: context.depth + 1 },
151
164
  async () => fn(this)
152
165
  );
@@ -1 +1 @@
1
- {"version":3,"file":"node-driver.js","sources":["../../src/node-driver.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport BetterSqlite3 from 'better-sqlite3'\nimport { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core'\nimport type { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core'\n\nconst DEFAULT_PRAGMAS = [\n `journal_mode = WAL`,\n `synchronous = NORMAL`,\n `foreign_keys = ON`,\n] as const\n\nconst INVALID_PRAGMA_PATTERN = /(;|--|\\/\\*)/\n\nexport type BetterSqlite3Database = InstanceType<typeof BetterSqlite3>\nexport type BetterSqlite3OpenOptions = ConstructorParameters<\n typeof BetterSqlite3\n>[1]\n\ntype BetterSqlite3ExistingDatabaseOptions = {\n database: BetterSqlite3Database\n pragmas?: ReadonlyArray<string>\n}\n\ntype BetterSqlite3OpenFileOptions = {\n filename: string\n options?: BetterSqlite3OpenOptions\n pragmas?: ReadonlyArray<string>\n}\n\nexport type BetterSqlite3DriverOptions =\n | BetterSqlite3ExistingDatabaseOptions\n | BetterSqlite3OpenFileOptions\n\ntype TransactionContext = {\n depth: number\n}\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 hasExistingDatabase(\n options: BetterSqlite3DriverOptions,\n): options is BetterSqlite3ExistingDatabaseOptions {\n return `database` in options\n}\n\nexport class BetterSqlite3SQLiteDriver implements SQLiteDriver {\n private readonly database: BetterSqlite3Database\n private readonly ownsDatabase: boolean\n private readonly transactionContext =\n new AsyncLocalStorage<TransactionContext>()\n private queue: Promise<void> = Promise.resolve()\n private nextSavepointId = 1\n\n constructor(options: BetterSqlite3DriverOptions) {\n if (hasExistingDatabase(options)) {\n this.database = options.database\n this.ownsDatabase = false\n this.applyPragmas(options.pragmas ?? DEFAULT_PRAGMAS)\n return\n }\n\n if (options.filename.trim().length === 0) {\n throw new InvalidPersistedCollectionConfigError(\n `Node SQLite driver filename cannot be empty`,\n )\n }\n\n this.database = new BetterSqlite3(options.filename, options.options)\n this.ownsDatabase = true\n this.applyPragmas(options.pragmas ?? DEFAULT_PRAGMAS)\n }\n\n async exec(sql: string): Promise<void> {\n if (this.isInsideTransaction()) {\n this.database.exec(sql)\n return\n }\n\n await this.enqueue(() => {\n this.database.exec(sql)\n })\n }\n\n async query<T>(\n sql: string,\n params: ReadonlyArray<unknown> = [],\n ): Promise<ReadonlyArray<T>> {\n if (this.isInsideTransaction()) {\n return this.executeQuery<T>(sql, params)\n }\n\n return this.enqueue(() => this.executeQuery<T>(sql, params))\n }\n\n async run(sql: string, params: ReadonlyArray<unknown> = []): Promise<void> {\n if (this.isInsideTransaction()) {\n this.executeRun(sql, params)\n return\n }\n\n await this.enqueue(() => {\n this.executeRun(sql, params)\n })\n }\n\n async transaction<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n assertTransactionCallbackHasDriverArg(fn)\n\n if (this.isInsideTransaction()) {\n return this.runNestedTransaction(fn)\n }\n\n return this.enqueue(async () => {\n this.database.exec(`BEGIN IMMEDIATE`)\n try {\n const result = await this.transactionContext.run(\n { depth: 1 },\n async () => fn(this),\n )\n this.database.exec(`COMMIT`)\n return result\n } catch (error) {\n try {\n this.database.exec(`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 close(): void {\n if (!this.ownsDatabase) {\n return\n }\n\n this.database.close()\n }\n\n getDatabase(): BetterSqlite3Database {\n return this.database\n }\n\n private applyPragmas(pragmas: ReadonlyArray<string>): void {\n for (const pragma of pragmas) {\n const trimmedPragma = pragma.trim()\n if (trimmedPragma.length === 0) {\n continue\n }\n\n if (INVALID_PRAGMA_PATTERN.test(trimmedPragma)) {\n throw new InvalidPersistedCollectionConfigError(\n `Invalid SQLite PRAGMA: \"${pragma}\"`,\n )\n }\n\n this.database.exec(`PRAGMA ${trimmedPragma}`)\n }\n }\n\n private isInsideTransaction(): boolean {\n return this.transactionContext.getStore() !== undefined\n }\n\n private executeQuery<T>(\n sql: string,\n params: ReadonlyArray<unknown>,\n ): ReadonlyArray<T> {\n const statement = this.database.prepare(sql)\n if (params.length === 0) {\n return statement.all() as ReadonlyArray<T>\n }\n\n return statement.all(...params) as ReadonlyArray<T>\n }\n\n private executeRun(sql: string, params: ReadonlyArray<unknown>): void {\n const statement = this.database.prepare(sql)\n if (params.length === 0) {\n statement.run()\n return\n }\n\n statement.run(...params)\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 async runNestedTransaction<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n const context = this.transactionContext.getStore()\n if (!context) {\n return fn(this)\n }\n\n const savepointName = `tsdb_sp_${this.nextSavepointId}`\n this.nextSavepointId++\n this.database.exec(`SAVEPOINT ${savepointName}`)\n\n try {\n const result = await this.transactionContext.run(\n { depth: context.depth + 1 },\n async () => fn(this),\n )\n this.database.exec(`RELEASE SAVEPOINT ${savepointName}`)\n return result\n } catch (error) {\n this.database.exec(`ROLLBACK TO SAVEPOINT ${savepointName}`)\n this.database.exec(`RELEASE SAVEPOINT ${savepointName}`)\n throw error\n }\n }\n}\n\nexport function createBetterSqlite3Driver(\n options: BetterSqlite3DriverOptions,\n): BetterSqlite3SQLiteDriver {\n return new BetterSqlite3SQLiteDriver(options)\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"node-driver.js","sources":["../../src/node-driver.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport BetterSqlite3 from 'better-sqlite3'\nimport { InvalidPersistedCollectionConfigError } from '@tanstack/db-sqlite-persistence-core'\nimport type { SQLiteDriver } from '@tanstack/db-sqlite-persistence-core'\n\nconst DEFAULT_PRAGMAS = [\n `journal_mode = WAL`,\n `synchronous = NORMAL`,\n `foreign_keys = ON`,\n] as const\n\nconst INVALID_PRAGMA_PATTERN = /(;|--|\\/\\*)/\n\nexport type BetterSqlite3Database = InstanceType<typeof BetterSqlite3>\nexport type BetterSqlite3OpenOptions = ConstructorParameters<\n typeof BetterSqlite3\n>[1]\n\ntype BetterSqlite3ExistingDatabaseOptions = {\n database: BetterSqlite3Database\n pragmas?: ReadonlyArray<string>\n}\n\ntype BetterSqlite3OpenFileOptions = {\n filename: string\n options?: BetterSqlite3OpenOptions\n pragmas?: ReadonlyArray<string>\n}\n\nexport type BetterSqlite3DriverOptions =\n | BetterSqlite3ExistingDatabaseOptions\n | BetterSqlite3OpenFileOptions\n\ntype TransactionContext = {\n depth: number\n}\n\ntype DatabaseExecutionState = {\n transactionContext: AsyncLocalStorage<TransactionContext>\n queue: Promise<void>\n nextSavepointId: number\n}\n\nconst databaseExecutionStates = new WeakMap<\n BetterSqlite3Database,\n DatabaseExecutionState\n>()\n\nfunction getDatabaseExecutionState(\n database: BetterSqlite3Database,\n): DatabaseExecutionState {\n const existing = databaseExecutionStates.get(database)\n if (existing) {\n return existing\n }\n\n const state: DatabaseExecutionState = {\n transactionContext: new AsyncLocalStorage<TransactionContext>(),\n queue: Promise.resolve(),\n nextSavepointId: 1,\n }\n databaseExecutionStates.set(database, state)\n return state\n}\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 hasExistingDatabase(\n options: BetterSqlite3DriverOptions,\n): options is BetterSqlite3ExistingDatabaseOptions {\n return `database` in options\n}\n\nexport class BetterSqlite3SQLiteDriver implements SQLiteDriver {\n private readonly database: BetterSqlite3Database\n private readonly ownsDatabase: boolean\n private readonly executionState: DatabaseExecutionState\n\n constructor(options: BetterSqlite3DriverOptions) {\n if (hasExistingDatabase(options)) {\n this.database = options.database\n this.ownsDatabase = false\n this.executionState = getDatabaseExecutionState(this.database)\n this.applyPragmas(options.pragmas ?? DEFAULT_PRAGMAS)\n return\n }\n\n if (options.filename.trim().length === 0) {\n throw new InvalidPersistedCollectionConfigError(\n `Node SQLite driver filename cannot be empty`,\n )\n }\n\n this.database = new BetterSqlite3(options.filename, options.options)\n this.ownsDatabase = true\n this.executionState = getDatabaseExecutionState(this.database)\n this.applyPragmas(options.pragmas ?? DEFAULT_PRAGMAS)\n }\n\n async exec(sql: string): Promise<void> {\n if (this.isInsideTransaction()) {\n this.database.exec(sql)\n return\n }\n\n await this.enqueue(() => {\n this.database.exec(sql)\n })\n }\n\n async query<T>(\n sql: string,\n params: ReadonlyArray<unknown> = [],\n ): Promise<ReadonlyArray<T>> {\n if (this.isInsideTransaction()) {\n return this.executeQuery<T>(sql, params)\n }\n\n return this.enqueue(() => this.executeQuery<T>(sql, params))\n }\n\n async run(sql: string, params: ReadonlyArray<unknown> = []): Promise<void> {\n if (this.isInsideTransaction()) {\n this.executeRun(sql, params)\n return\n }\n\n await this.enqueue(() => {\n this.executeRun(sql, params)\n })\n }\n\n async transaction<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n assertTransactionCallbackHasDriverArg(fn)\n\n if (this.isInsideTransaction()) {\n return this.runNestedTransaction(fn)\n }\n\n return this.enqueue(async () => {\n this.database.exec(`BEGIN IMMEDIATE`)\n try {\n const result = await this.executionState.transactionContext.run(\n { depth: 1 },\n async () => fn(this),\n )\n this.database.exec(`COMMIT`)\n return result\n } catch (error) {\n try {\n this.database.exec(`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 close(): void {\n if (!this.ownsDatabase) {\n return\n }\n\n this.database.close()\n }\n\n getDatabase(): BetterSqlite3Database {\n return this.database\n }\n\n private applyPragmas(pragmas: ReadonlyArray<string>): void {\n for (const pragma of pragmas) {\n const trimmedPragma = pragma.trim()\n if (trimmedPragma.length === 0) {\n continue\n }\n\n if (INVALID_PRAGMA_PATTERN.test(trimmedPragma)) {\n throw new InvalidPersistedCollectionConfigError(\n `Invalid SQLite PRAGMA: \"${pragma}\"`,\n )\n }\n\n this.database.exec(`PRAGMA ${trimmedPragma}`)\n }\n }\n\n private isInsideTransaction(): boolean {\n return this.executionState.transactionContext.getStore() !== undefined\n }\n\n private executeQuery<T>(\n sql: string,\n params: ReadonlyArray<unknown>,\n ): ReadonlyArray<T> {\n const statement = this.database.prepare(sql)\n if (params.length === 0) {\n return statement.all() as ReadonlyArray<T>\n }\n\n return statement.all(...params) as ReadonlyArray<T>\n }\n\n private executeRun(sql: string, params: ReadonlyArray<unknown>): void {\n const statement = this.database.prepare(sql)\n if (params.length === 0) {\n statement.run()\n return\n }\n\n statement.run(...params)\n }\n\n private enqueue<T>(operation: () => Promise<T> | T): Promise<T> {\n const queuedOperation = this.executionState.queue.then(operation, operation)\n this.executionState.queue = queuedOperation.then(\n () => undefined,\n () => undefined,\n )\n return queuedOperation\n }\n\n private async runNestedTransaction<T>(\n fn: (transactionDriver: SQLiteDriver) => Promise<T>,\n ): Promise<T> {\n const context = this.executionState.transactionContext.getStore()\n if (!context) {\n return fn(this)\n }\n\n const savepointName = `tsdb_sp_${this.executionState.nextSavepointId}`\n this.executionState.nextSavepointId++\n this.database.exec(`SAVEPOINT ${savepointName}`)\n\n try {\n const result = await this.executionState.transactionContext.run(\n { depth: context.depth + 1 },\n async () => fn(this),\n )\n this.database.exec(`RELEASE SAVEPOINT ${savepointName}`)\n return result\n } catch (error) {\n this.database.exec(`ROLLBACK TO SAVEPOINT ${savepointName}`)\n this.database.exec(`RELEASE SAVEPOINT ${savepointName}`)\n throw error\n }\n }\n}\n\nexport function createBetterSqlite3Driver(\n options: BetterSqlite3DriverOptions,\n): BetterSqlite3SQLiteDriver {\n return new BetterSqlite3SQLiteDriver(options)\n}\n"],"names":[],"mappings":";;;AAKA,MAAM,kBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,yBAAyB;AAgC/B,MAAM,8CAA8B,QAAA;AAKpC,SAAS,0BACP,UACwB;AACxB,QAAM,WAAW,wBAAwB,IAAI,QAAQ;AACrD,MAAI,UAAU;AACZ,WAAO;AAAA,EACT;AAEA,QAAM,QAAgC;AAAA,IACpC,oBAAoB,IAAI,kBAAA;AAAA,IACxB,OAAO,QAAQ,QAAA;AAAA,IACf,iBAAiB;AAAA,EAAA;AAEnB,0BAAwB,IAAI,UAAU,KAAK;AAC3C,SAAO;AACT;AAEA,SAAS,sCACP,IACM;AACN,MAAI,GAAG,SAAS,GAAG;AACjB;AAAA,EACF;AAEA,QAAM,IAAI;AAAA,IACR;AAAA,EAAA;AAEJ;AAEA,SAAS,oBACP,SACiD;AACjD,SAAO,cAAc;AACvB;AAEO,MAAM,0BAAkD;AAAA,EAK7D,YAAY,SAAqC;AAC/C,QAAI,oBAAoB,OAAO,GAAG;AAChC,WAAK,WAAW,QAAQ;AACxB,WAAK,eAAe;AACpB,WAAK,iBAAiB,0BAA0B,KAAK,QAAQ;AAC7D,WAAK,aAAa,QAAQ,WAAW,eAAe;AACpD;AAAA,IACF;AAEA,QAAI,QAAQ,SAAS,KAAA,EAAO,WAAW,GAAG;AACxC,YAAM,IAAI;AAAA,QACR;AAAA,MAAA;AAAA,IAEJ;AAEA,SAAK,WAAW,IAAI,cAAc,QAAQ,UAAU,QAAQ,OAAO;AACnE,SAAK,eAAe;AACpB,SAAK,iBAAiB,0BAA0B,KAAK,QAAQ;AAC7D,SAAK,aAAa,QAAQ,WAAW,eAAe;AAAA,EACtD;AAAA,EAEA,MAAM,KAAK,KAA4B;AACrC,QAAI,KAAK,uBAAuB;AAC9B,WAAK,SAAS,KAAK,GAAG;AACtB;AAAA,IACF;AAEA,UAAM,KAAK,QAAQ,MAAM;AACvB,WAAK,SAAS,KAAK,GAAG;AAAA,IACxB,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MACJ,KACA,SAAiC,IACN;AAC3B,QAAI,KAAK,uBAAuB;AAC9B,aAAO,KAAK,aAAgB,KAAK,MAAM;AAAA,IACzC;AAEA,WAAO,KAAK,QAAQ,MAAM,KAAK,aAAgB,KAAK,MAAM,CAAC;AAAA,EAC7D;AAAA,EAEA,MAAM,IAAI,KAAa,SAAiC,IAAmB;AACzE,QAAI,KAAK,uBAAuB;AAC9B,WAAK,WAAW,KAAK,MAAM;AAC3B;AAAA,IACF;AAEA,UAAM,KAAK,QAAQ,MAAM;AACvB,WAAK,WAAW,KAAK,MAAM;AAAA,IAC7B,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,YACJ,IACY;AACZ,0CAAsC,EAAE;AAExC,QAAI,KAAK,uBAAuB;AAC9B,aAAO,KAAK,qBAAqB,EAAE;AAAA,IACrC;AAEA,WAAO,KAAK,QAAQ,YAAY;AAC9B,WAAK,SAAS,KAAK,iBAAiB;AACpC,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,eAAe,mBAAmB;AAAA,UAC1D,EAAE,OAAO,EAAA;AAAA,UACT,YAAY,GAAG,IAAI;AAAA,QAAA;AAErB,aAAK,SAAS,KAAK,QAAQ;AAC3B,eAAO;AAAA,MACT,SAAS,OAAO;AACd,YAAI;AACF,eAAK,SAAS,KAAK,UAAU;AAAA,QAC/B,QAAQ;AAAA,QAER;AACA,cAAM;AAAA,MACR;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,sBACJ,IACY;AACZ,WAAO,KAAK,YAAY,EAAE;AAAA,EAC5B;AAAA,EAEA,QAAc;AACZ,QAAI,CAAC,KAAK,cAAc;AACtB;AAAA,IACF;AAEA,SAAK,SAAS,MAAA;AAAA,EAChB;AAAA,EAEA,cAAqC;AACnC,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,aAAa,SAAsC;AACzD,eAAW,UAAU,SAAS;AAC5B,YAAM,gBAAgB,OAAO,KAAA;AAC7B,UAAI,cAAc,WAAW,GAAG;AAC9B;AAAA,MACF;AAEA,UAAI,uBAAuB,KAAK,aAAa,GAAG;AAC9C,cAAM,IAAI;AAAA,UACR,2BAA2B,MAAM;AAAA,QAAA;AAAA,MAErC;AAEA,WAAK,SAAS,KAAK,UAAU,aAAa,EAAE;AAAA,IAC9C;AAAA,EACF;AAAA,EAEQ,sBAA+B;AACrC,WAAO,KAAK,eAAe,mBAAmB,SAAA,MAAe;AAAA,EAC/D;AAAA,EAEQ,aACN,KACA,QACkB;AAClB,UAAM,YAAY,KAAK,SAAS,QAAQ,GAAG;AAC3C,QAAI,OAAO,WAAW,GAAG;AACvB,aAAO,UAAU,IAAA;AAAA,IACnB;AAEA,WAAO,UAAU,IAAI,GAAG,MAAM;AAAA,EAChC;AAAA,EAEQ,WAAW,KAAa,QAAsC;AACpE,UAAM,YAAY,KAAK,SAAS,QAAQ,GAAG;AAC3C,QAAI,OAAO,WAAW,GAAG;AACvB,gBAAU,IAAA;AACV;AAAA,IACF;AAEA,cAAU,IAAI,GAAG,MAAM;AAAA,EACzB;AAAA,EAEQ,QAAW,WAA6C;AAC9D,UAAM,kBAAkB,KAAK,eAAe,MAAM,KAAK,WAAW,SAAS;AAC3E,SAAK,eAAe,QAAQ,gBAAgB;AAAA,MAC1C,MAAM;AAAA,MACN,MAAM;AAAA,IAAA;AAER,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,qBACZ,IACY;AACZ,UAAM,UAAU,KAAK,eAAe,mBAAmB,SAAA;AACvD,QAAI,CAAC,SAAS;AACZ,aAAO,GAAG,IAAI;AAAA,IAChB;AAEA,UAAM,gBAAgB,WAAW,KAAK,eAAe,eAAe;AACpE,SAAK,eAAe;AACpB,SAAK,SAAS,KAAK,aAAa,aAAa,EAAE;AAE/C,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,eAAe,mBAAmB;AAAA,QAC1D,EAAE,OAAO,QAAQ,QAAQ,EAAA;AAAA,QACzB,YAAY,GAAG,IAAI;AAAA,MAAA;AAErB,WAAK,SAAS,KAAK,qBAAqB,aAAa,EAAE;AACvD,aAAO;AAAA,IACT,SAAS,OAAO;AACd,WAAK,SAAS,KAAK,yBAAyB,aAAa,EAAE;AAC3D,WAAK,SAAS,KAAK,qBAAqB,aAAa,EAAE;AACvD,YAAM;AAAA,IACR;AAAA,EACF;AACF;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/node-db-sqlite-persistence",
3
- "version": "0.2.21",
3
+ "version": "0.2.25",
4
4
  "description": "Node SQLite persisted collection adapter for TanStack DB",
5
5
  "author": "TanStack Team",
6
6
  "license": "MIT",
@@ -41,7 +41,7 @@
41
41
  ],
42
42
  "dependencies": {
43
43
  "better-sqlite3": "^12.6.2",
44
- "@tanstack/db-sqlite-persistence-core": "0.2.21"
44
+ "@tanstack/db-sqlite-persistence-core": "0.4.0"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "typescript": ">=4.7"
@@ -55,6 +55,7 @@
55
55
  "dev": "vite build --watch",
56
56
  "lint": "eslint . --fix",
57
57
  "test": "vitest --run",
58
+ "test:oracles": "vitest --run tests/expression-index-oracle.test.ts",
58
59
  "test:e2e": "pnpm --filter @tanstack/db-ivm build && pnpm --filter @tanstack/db build && pnpm --filter @tanstack/db-sqlite-persistence-core build && pnpm --filter @tanstack/node-db-sqlite-persistence build && vitest --config vitest.e2e.config.ts --run"
59
60
  }
60
61
  }
@@ -35,6 +35,34 @@ type TransactionContext = {
35
35
  depth: number
36
36
  }
37
37
 
38
+ type DatabaseExecutionState = {
39
+ transactionContext: AsyncLocalStorage<TransactionContext>
40
+ queue: Promise<void>
41
+ nextSavepointId: number
42
+ }
43
+
44
+ const databaseExecutionStates = new WeakMap<
45
+ BetterSqlite3Database,
46
+ DatabaseExecutionState
47
+ >()
48
+
49
+ function getDatabaseExecutionState(
50
+ database: BetterSqlite3Database,
51
+ ): DatabaseExecutionState {
52
+ const existing = databaseExecutionStates.get(database)
53
+ if (existing) {
54
+ return existing
55
+ }
56
+
57
+ const state: DatabaseExecutionState = {
58
+ transactionContext: new AsyncLocalStorage<TransactionContext>(),
59
+ queue: Promise.resolve(),
60
+ nextSavepointId: 1,
61
+ }
62
+ databaseExecutionStates.set(database, state)
63
+ return state
64
+ }
65
+
38
66
  function assertTransactionCallbackHasDriverArg(
39
67
  fn: (transactionDriver: SQLiteDriver) => Promise<unknown>,
40
68
  ): void {
@@ -56,15 +84,13 @@ function hasExistingDatabase(
56
84
  export class BetterSqlite3SQLiteDriver implements SQLiteDriver {
57
85
  private readonly database: BetterSqlite3Database
58
86
  private readonly ownsDatabase: boolean
59
- private readonly transactionContext =
60
- new AsyncLocalStorage<TransactionContext>()
61
- private queue: Promise<void> = Promise.resolve()
62
- private nextSavepointId = 1
87
+ private readonly executionState: DatabaseExecutionState
63
88
 
64
89
  constructor(options: BetterSqlite3DriverOptions) {
65
90
  if (hasExistingDatabase(options)) {
66
91
  this.database = options.database
67
92
  this.ownsDatabase = false
93
+ this.executionState = getDatabaseExecutionState(this.database)
68
94
  this.applyPragmas(options.pragmas ?? DEFAULT_PRAGMAS)
69
95
  return
70
96
  }
@@ -77,6 +103,7 @@ export class BetterSqlite3SQLiteDriver implements SQLiteDriver {
77
103
 
78
104
  this.database = new BetterSqlite3(options.filename, options.options)
79
105
  this.ownsDatabase = true
106
+ this.executionState = getDatabaseExecutionState(this.database)
80
107
  this.applyPragmas(options.pragmas ?? DEFAULT_PRAGMAS)
81
108
  }
82
109
 
@@ -125,7 +152,7 @@ export class BetterSqlite3SQLiteDriver implements SQLiteDriver {
125
152
  return this.enqueue(async () => {
126
153
  this.database.exec(`BEGIN IMMEDIATE`)
127
154
  try {
128
- const result = await this.transactionContext.run(
155
+ const result = await this.executionState.transactionContext.run(
129
156
  { depth: 1 },
130
157
  async () => fn(this),
131
158
  )
@@ -178,7 +205,7 @@ export class BetterSqlite3SQLiteDriver implements SQLiteDriver {
178
205
  }
179
206
 
180
207
  private isInsideTransaction(): boolean {
181
- return this.transactionContext.getStore() !== undefined
208
+ return this.executionState.transactionContext.getStore() !== undefined
182
209
  }
183
210
 
184
211
  private executeQuery<T>(
@@ -204,8 +231,8 @@ export class BetterSqlite3SQLiteDriver implements SQLiteDriver {
204
231
  }
205
232
 
206
233
  private enqueue<T>(operation: () => Promise<T> | T): Promise<T> {
207
- const queuedOperation = this.queue.then(operation, operation)
208
- this.queue = queuedOperation.then(
234
+ const queuedOperation = this.executionState.queue.then(operation, operation)
235
+ this.executionState.queue = queuedOperation.then(
209
236
  () => undefined,
210
237
  () => undefined,
211
238
  )
@@ -215,17 +242,17 @@ export class BetterSqlite3SQLiteDriver implements SQLiteDriver {
215
242
  private async runNestedTransaction<T>(
216
243
  fn: (transactionDriver: SQLiteDriver) => Promise<T>,
217
244
  ): Promise<T> {
218
- const context = this.transactionContext.getStore()
245
+ const context = this.executionState.transactionContext.getStore()
219
246
  if (!context) {
220
247
  return fn(this)
221
248
  }
222
249
 
223
- const savepointName = `tsdb_sp_${this.nextSavepointId}`
224
- this.nextSavepointId++
250
+ const savepointName = `tsdb_sp_${this.executionState.nextSavepointId}`
251
+ this.executionState.nextSavepointId++
225
252
  this.database.exec(`SAVEPOINT ${savepointName}`)
226
253
 
227
254
  try {
228
- const result = await this.transactionContext.run(
255
+ const result = await this.executionState.transactionContext.run(
229
256
  { depth: context.depth + 1 },
230
257
  async () => fn(this),
231
258
  )