mirror of
https://github.com/earendil-works/pi.git
synced 2026-10-02 00:35:27 +08:00
refactor(durable): drop AsyncLocalStorage misuse detection from Node SQLite adapter
This commit is contained in:
@@ -21,7 +21,8 @@ export interface SqliteExecutor {
|
||||
* All operations are asynchronous so adapters may execute outside the harness runtime.
|
||||
*
|
||||
* `transaction` passes the callback a transaction handle. All work in the transaction
|
||||
* must use that handle; the handle is invalid after the callback settles. Adapters must
|
||||
* must use that handle; calling the database itself from inside the callback waits for the
|
||||
* transaction and never completes. The handle is invalid after the callback settles. Adapters must
|
||||
* queue unrelated operations and other transactions until the transaction finishes. The
|
||||
* returned promise settles after commit or rollback.
|
||||
*
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
import { mkdir } from "node:fs/promises";
|
||||
import { dirname } from "node:path";
|
||||
import type { StatementSync } from "node:sqlite";
|
||||
@@ -97,8 +96,6 @@ class NodeSqliteTransaction extends NodeSqliteExecutor {
|
||||
/** `SqliteDatabase` adapter backed by Node's built-in `node:sqlite`. */
|
||||
export class NodeSqliteDatabase extends NodeSqliteExecutor implements SqliteDatabase {
|
||||
private readonly access = new SerialOperationQueue();
|
||||
/** Detects database calls from inside a transaction callback, which would otherwise wait for that transaction forever. */
|
||||
private readonly transactionScope = new AsyncLocalStorage<TransactionScope>();
|
||||
private closed = false;
|
||||
|
||||
constructor(database: DatabaseSync) {
|
||||
@@ -106,16 +103,11 @@ export class NodeSqliteDatabase extends NodeSqliteExecutor implements SqliteData
|
||||
}
|
||||
|
||||
transaction<T>(callback: (transaction: SqliteExecutor) => Promise<T>): Promise<T> {
|
||||
if (this.insideTransaction()) {
|
||||
return Promise.reject(new Error("Nested SQLite transactions are not supported"));
|
||||
}
|
||||
return this.access.run(async () => {
|
||||
this.database.exec("BEGIN IMMEDIATE");
|
||||
const scope = { active: true };
|
||||
try {
|
||||
const result = await this.transactionScope.run(scope, () =>
|
||||
callback(new NodeSqliteTransaction(this.database, this.statements, scope)),
|
||||
);
|
||||
const result = await callback(new NodeSqliteTransaction(this.database, this.statements, scope));
|
||||
scope.active = false;
|
||||
this.database.exec("COMMIT");
|
||||
return result;
|
||||
@@ -132,9 +124,6 @@ export class NodeSqliteDatabase extends NodeSqliteExecutor implements SqliteData
|
||||
}
|
||||
|
||||
close(): Promise<void> {
|
||||
if (this.insideTransaction()) {
|
||||
return Promise.reject(new Error("Cannot close SQLite during an active transaction"));
|
||||
}
|
||||
return this.access.run(() => {
|
||||
if (this.closed) return;
|
||||
this.closed = true;
|
||||
@@ -148,15 +137,8 @@ export class NodeSqliteDatabase extends NodeSqliteExecutor implements SqliteData
|
||||
}
|
||||
|
||||
protected runOperation<T>(operation: () => T): Promise<T> {
|
||||
if (this.insideTransaction()) {
|
||||
return Promise.reject(new Error("Use the transaction handle inside a transaction callback"));
|
||||
}
|
||||
return this.access.run(operation);
|
||||
}
|
||||
|
||||
private insideTransaction(): boolean {
|
||||
return this.transactionScope.getStore()?.active === true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Open and configure a Node-backed SQLite database facade. */
|
||||
|
||||
@@ -193,21 +193,6 @@ describe("portable SQLite facade settlement", () => {
|
||||
await database.close();
|
||||
});
|
||||
|
||||
it("rejects database operations from inside a transaction callback instead of waiting forever", async () => {
|
||||
const database = await openNodeSqliteDatabase(":memory:");
|
||||
await database.exec("CREATE TABLE misuse_probe (value INTEGER)");
|
||||
const misuse = "Use the transaction handle inside a transaction callback";
|
||||
await expect(database.transaction(() => database.exec("SELECT 1"))).rejects.toThrow(misuse);
|
||||
await expect(database.transaction(() => database.all("SELECT value FROM misuse_probe"))).rejects.toThrow(misuse);
|
||||
await expect(database.transaction(() => database.transaction(async () => undefined))).rejects.toThrow(
|
||||
"Nested SQLite transactions are not supported",
|
||||
);
|
||||
await expect(database.transaction(() => database.close())).rejects.toThrow(
|
||||
"Cannot close SQLite during an active transaction",
|
||||
);
|
||||
await database.close();
|
||||
});
|
||||
|
||||
it("rejects a transaction handle used after its transaction settles", async () => {
|
||||
const database = await openNodeSqliteDatabase(":memory:");
|
||||
await database.exec("CREATE TABLE stale_probe (value INTEGER)");
|
||||
|
||||
Reference in New Issue
Block a user