@tanstack/node-db-sqlite-persistence 0.2.23 → 0.2.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/cjs/node-driver.cjs +24 -11
- package/dist/cjs/node-driver.cjs.map +1 -1
- package/dist/cjs/node-driver.d.cts +1 -3
- package/dist/esm/node-driver.d.ts +1 -3
- package/dist/esm/node-driver.js +24 -11
- package/dist/esm/node-driver.js.map +1 -1
- package/package.json +3 -2
- package/src/node-driver.ts +39 -12
package/README.md
CHANGED
package/dist/cjs/node-driver.cjs
CHANGED
|
@@ -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":"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"}
|
|
@@ -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
|
|
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
|
|
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>>;
|
package/dist/esm/node-driver.js
CHANGED
|
@@ -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":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/node-db-sqlite-persistence",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.26",
|
|
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.
|
|
44
|
+
"@tanstack/db-sqlite-persistence-core": "0.4.1"
|
|
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
|
}
|
package/src/node-driver.ts
CHANGED
|
@@ -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
|
|
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
|
)
|