@remit/drizzle-service 0.0.99 → 0.0.100
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/package.json +1 -1
- package/src/batch.sqlite.test.ts +78 -0
- package/src/batch.ts +45 -0
- package/src/databases.ts +70 -0
- package/src/db.ts +1 -4
- package/src/generated-enum.test.ts +8 -0
- package/src/generated-id.test.ts +19 -0
- package/src/id.test.ts +0 -8
- package/src/id.ts +0 -6
- package/src/index.ts +16 -6
- package/src/repair/address-display-name.sqlite.test.ts +3 -5
- package/src/repair/address-display-name.ts +8 -3
- package/src/repair/junk-only-address.sqlite.test.ts +8 -36
- package/src/repair/stranded-sent-outbox.sqlite.test.ts +2 -9
- package/src/repos/account.conformance.sqlite.test.ts +1 -1
- package/src/repos/calendar-default.sqlite.test.ts +6 -6
- package/src/repos/calendar-put.sqlite.test.ts +40 -39
- package/src/repos/calendar-store.ts +273 -0
- package/src/repos/cascade-delete.sqlite.test.ts +7 -9
- package/src/repos/cascade-delete.test.ts +6 -13
- package/src/repos/cascade-delete.ts +127 -97
- package/src/repos/envelope.test.ts +1 -17
- package/src/repos/envelope.ts +83 -67
- package/src/repos/filter.test.ts +22 -2
- package/src/repos/filter.ts +1 -5
- package/src/repos/i4-account-config.test.ts +2 -2
- package/src/repos/i4-account-config.ts +5 -2
- package/src/repos/i4-account.test.ts +6 -2
- package/src/repos/i4-account.ts +12 -56
- package/src/repos/i4-address-junk-move.sqlite.test.ts +1 -1
- package/src/repos/i4-address-merge-flags.sqlite.test.ts +1 -2
- package/src/repos/i4-address.ts +58 -90
- package/src/repos/i4-mailbox-special-use.sqlite.test.ts +6 -2
- package/src/repos/i4-mailbox-special-use.ts +9 -6
- package/src/repos/i4-mailbox.sqlite.test.ts +12 -14
- package/src/repos/i4-mailbox.test.ts +17 -62
- package/src/repos/i4-mailbox.ts +121 -94
- package/src/repos/i4-organize-job-request.ts +0 -4
- package/src/repos/i4-outbox-attachment.ts +62 -33
- package/src/repos/mappers.ts +0 -16
- package/src/repos/message.sqlite.test.ts +32 -106
- package/src/repos/message.test.ts +6 -167
- package/src/repos/message.ts +216 -228
- package/src/repos/sync-warning.ts +76 -0
- package/src/repos/thread-message-body-muted.sqlite.test.ts +5 -10
- package/src/repos/thread-message-field-terms.sqlite.test.ts +5 -1
- package/src/repos/thread-message.sqlite.test.ts +2 -1
- package/src/repos/thread-message.test.ts +17 -4
- package/src/repos/thread-message.ts +502 -285
- package/src/repos/thread-search-predicates.ts +24 -26
- package/src/schema/message-data.ts +4 -8
- package/src/schema/sync-warning.ts +3 -0
- package/src/schema-sets/app.ts +17 -0
- package/src/schema-sets/mail.ts +32 -0
- package/src/schema-sets/search.ts +5 -0
- package/src/schema-version-accounting.sqlite.test.ts +56 -43
- package/src/schema.ts +3 -4
- package/src/search/thread-message-search.sqlite.test.ts +131 -0
- package/src/search/thread-message-search.ts +271 -0
- package/src/sqlite-client.ts +5 -7
- package/src/sqlite-repositories.ts +75 -0
- package/src/test-db-sqlite.ts +5 -13
- package/src/test-shipped-sqlite-schema.ts +46 -45
- package/src/test-sqlite.ts +35 -11
- package/src/vps-migrations-drift.sqlite.test.ts +81 -15
- package/src/account-granted-scopes-migration.sqlite.test.ts +0 -66
- package/src/account-synced-services-migration.sqlite.test.ts +0 -56
- package/src/mailbox-path-dedupe.sqlite.test.ts +0 -462
- package/src/mailbox-sync-status-backfill.sqlite.test.ts +0 -118
- package/src/outbox-created-cleanup.sqlite.test.ts +0 -83
- package/src/repair/duplicate-mailbox-mail.ts +0 -98
- package/src/repair/thread-message-category-contract.ts +0 -505
- package/src/repair/thread-message-category.sqlite.test.ts +0 -100
- package/src/repair/thread-message-category.ts +0 -415
- package/src/repos/calendar-migrations.sqlite.test.ts +0 -345
- package/src/repos/calendar-unit-of-work.ts +0 -31
- package/src/repos/config-import-atomicity.sqlite.test.ts +0 -255
- package/src/repos/filter-anchor-transaction.test.ts +0 -136
- package/src/repos/filter-anchor-transaction.ts +0 -45
- package/src/repos/filter-anchor.test.ts +0 -131
- package/src/repos/filter-anchor.ts +0 -105
- package/src/repos/i4-mailbox-special-use.structure.test.ts +0 -27
- package/src/repos/message-placement-backfill.sqlite.test.ts +0 -190
- package/src/repos/search-index-shape.test.ts +0 -82
- package/src/repos/search-index-shape.ts +0 -56
- package/src/repos/serialized-writes.sqlite.test.ts +0 -199
- package/src/repos/unit-of-work.sqlite.test.ts +0 -138
- package/src/repos/unit-of-work.test.ts +0 -109
- package/src/repos/unit-of-work.ts +0 -31
- package/src/schema/outbox.ts +0 -46
- package/src/schema-full-sqlite.ts +0 -13
- package/src/tx.sqlite.test.ts +0 -83
- package/src/tx.ts +0 -233
package/src/tx.sqlite.test.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { after, before, describe, test } from "node:test";
|
|
6
|
-
import Database from "better-sqlite3";
|
|
7
|
-
import { sql } from "drizzle-orm";
|
|
8
|
-
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
9
|
-
import type { Db } from "./db.js";
|
|
10
|
-
import { runInTransaction } from "./tx.js";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* A write set that reads before it writes, against the file four processes
|
|
14
|
-
* share (RFC 036 D3).
|
|
15
|
-
*
|
|
16
|
-
* SQLite gives a deferred transaction its write lock at its first write, and by
|
|
17
|
-
* then a unit that read first is holding a snapshot. Another process committing
|
|
18
|
-
* in that gap makes the upgrade impossible, and SQLite refuses it at once —
|
|
19
|
-
* `busy_timeout` waits for a lock, not for a snapshot that is already stale. The
|
|
20
|
-
* unit then dies with "database is locked" however long that timeout is, which
|
|
21
|
-
* is what answered a calendar write 500 while the imap-worker was syncing.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
const PRAGMAS = [
|
|
25
|
-
"journal_mode = WAL",
|
|
26
|
-
"busy_timeout = 5000",
|
|
27
|
-
"synchronous = NORMAL",
|
|
28
|
-
"foreign_keys = ON",
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
let directory: string;
|
|
32
|
-
let sqlite: Database.Database;
|
|
33
|
-
let other: Database.Database;
|
|
34
|
-
let db: Db<Record<string, never>>;
|
|
35
|
-
|
|
36
|
-
before(() => {
|
|
37
|
-
directory = mkdtempSync(join(tmpdir(), "remit-tx-"));
|
|
38
|
-
const path = join(directory, "remit.db");
|
|
39
|
-
|
|
40
|
-
sqlite = new Database(path);
|
|
41
|
-
for (const pragma of PRAGMAS) sqlite.pragma(pragma);
|
|
42
|
-
sqlite.exec("CREATE TABLE row (id INTEGER PRIMARY KEY, v TEXT NOT NULL)");
|
|
43
|
-
sqlite.exec("INSERT INTO row (id, v) VALUES (1, 'read'), (2, 'other')");
|
|
44
|
-
|
|
45
|
-
// The other writer, as a second connection. It refuses instead of waiting,
|
|
46
|
-
// so the run finishes in its own time whichever of the two holds the lock.
|
|
47
|
-
other = new Database(path);
|
|
48
|
-
other.pragma("busy_timeout = 0");
|
|
49
|
-
|
|
50
|
-
db = drizzle(sqlite) as unknown as Db<Record<string, never>>;
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
after(() => {
|
|
54
|
-
other.close();
|
|
55
|
-
sqlite.close();
|
|
56
|
-
rmSync(directory, { recursive: true, force: true });
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe("a top-level sqlite unit that reads before it writes", () => {
|
|
60
|
-
test("commits through another connection's write landing between the two", async () => {
|
|
61
|
-
const outcome = await runInTransaction(db, async (tx) => {
|
|
62
|
-
await tx.get(sql`SELECT v FROM row WHERE id = 1`);
|
|
63
|
-
|
|
64
|
-
// The other process, arriving in the gap an async write set leaves
|
|
65
|
-
// open. Whether its own write lands is not the claim here — only that
|
|
66
|
-
// this unit can still finish the one it came to make.
|
|
67
|
-
try {
|
|
68
|
-
other.prepare("UPDATE row SET v = 'moved' WHERE id = 2").run();
|
|
69
|
-
} catch {
|
|
70
|
-
// Refused because this unit holds the write lock, which is the
|
|
71
|
-
// outcome that keeps the unit below alive.
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
await tx.run(sql`UPDATE row SET v = 'written' WHERE id = 1`);
|
|
75
|
-
return "committed";
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
assert.equal(outcome, "committed");
|
|
79
|
-
assert.deepEqual(sqlite.prepare("SELECT v FROM row WHERE id = 1").get(), {
|
|
80
|
-
v: "written",
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
});
|
package/src/tx.ts
DELETED
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { type SQL, sql } from "drizzle-orm";
|
|
4
|
-
import type { Db } from "./db.js";
|
|
5
|
-
|
|
6
|
-
// Runs a write set in one transaction.
|
|
7
|
-
//
|
|
8
|
-
// better-sqlite3's native transaction runner rejects a callback that returns a
|
|
9
|
-
// promise, and the repos' write sets are async, so drizzle's own
|
|
10
|
-
// `db.transaction()` cannot be used. A top-level unit is bracketed with
|
|
11
|
-
// `BEGIN IMMEDIATE` / `COMMIT` instead, and a nested one with a SAVEPOINT.
|
|
12
|
-
//
|
|
13
|
-
// IMMEDIATE, not the deferred transaction a bare `BEGIN` or `SAVEPOINT` opens.
|
|
14
|
-
// A deferred transaction takes its write lock at its first write, and a unit
|
|
15
|
-
// that reads before it writes (every unit that checks a row before updating it)
|
|
16
|
-
// has by then taken a read snapshot. If another process commits in between,
|
|
17
|
-
// SQLite refuses the upgrade with SQLITE_BUSY_SNAPSHOT — and that refusal is
|
|
18
|
-
// immediate, because `busy_timeout` cannot wait out a snapshot that is already
|
|
19
|
-
// stale. Four processes write this file (RFC 036 D3), so the gap is real: it
|
|
20
|
-
// answered a calendar write 500 while the imap-worker was syncing. Taking the
|
|
21
|
-
// write lock at BEGIN closes it — an IMMEDIATE that meets another writer waits
|
|
22
|
-
// out the `busy_timeout` and then proceeds, which is what that timeout is for.
|
|
23
|
-
//
|
|
24
|
-
// All writers on this backend share one better-sqlite3 connection (RFC 036 D3),
|
|
25
|
-
// and every query runs synchronously, but an async callback still yields the
|
|
26
|
-
// event loop at each `await`. Two concurrent top-level transactions would
|
|
27
|
-
// therefore interleave their SAVEPOINT/RELEASE on the shared connection and
|
|
28
|
-
// corrupt each other's boundaries — and the existing callers do run concurrently
|
|
29
|
-
// (message-sync's `unitOfWork.transaction` under pMap). So top-level sqlite
|
|
30
|
-
// transactions are serialized behind an async queue: one runs start-to-finish
|
|
31
|
-
// before the next begins, which is exactly the global write serialization D3
|
|
32
|
-
// accepts at single-box scale. Nested calls (a repo opening its own transaction
|
|
33
|
-
// while a unit-of-work already holds one) are detected via AsyncLocalStorage and
|
|
34
|
-
// take a nested savepoint on the same open transaction instead of re-queuing,
|
|
35
|
-
// which would deadlock.
|
|
36
|
-
|
|
37
|
-
const inSqliteTx = new AsyncLocalStorage<true>();
|
|
38
|
-
|
|
39
|
-
let sqliteQueue: Promise<unknown> = Promise.resolve();
|
|
40
|
-
|
|
41
|
-
function serializeSqlite<T>(task: () => Promise<T>): Promise<T> {
|
|
42
|
-
const result = sqliteQueue.then(task, task);
|
|
43
|
-
// Keep the queue alive regardless of this task's outcome.
|
|
44
|
-
sqliteQueue = result.then(
|
|
45
|
-
() => undefined,
|
|
46
|
-
() => undefined,
|
|
47
|
-
);
|
|
48
|
-
return result;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function runSqliteSavepoint<TSchema extends Record<string, unknown>, T>(
|
|
52
|
-
db: Db<TSchema>,
|
|
53
|
-
fn: (tx: Db<TSchema>) => Promise<T>,
|
|
54
|
-
): Promise<T> {
|
|
55
|
-
const runner = db as unknown as { run: (query: SQL) => unknown };
|
|
56
|
-
const name = `sp_${randomUUID().replace(/-/g, "")}`;
|
|
57
|
-
runner.run(sql.raw(`SAVEPOINT ${name}`));
|
|
58
|
-
try {
|
|
59
|
-
const result = await fn(db);
|
|
60
|
-
runner.run(sql.raw(`RELEASE SAVEPOINT ${name}`));
|
|
61
|
-
return result;
|
|
62
|
-
} catch (error) {
|
|
63
|
-
try {
|
|
64
|
-
runner.run(sql.raw(`ROLLBACK TO SAVEPOINT ${name}`));
|
|
65
|
-
runner.run(sql.raw(`RELEASE SAVEPOINT ${name}`));
|
|
66
|
-
} catch {
|
|
67
|
-
// A failed rollback must not mask the error that caused it; surface
|
|
68
|
-
// the original below.
|
|
69
|
-
}
|
|
70
|
-
throw error;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function runSqliteImmediate<TSchema extends Record<string, unknown>, T>(
|
|
75
|
-
db: Db<TSchema>,
|
|
76
|
-
fn: (tx: Db<TSchema>) => Promise<T>,
|
|
77
|
-
): Promise<T> {
|
|
78
|
-
const runner = db as unknown as { run: (query: SQL) => unknown };
|
|
79
|
-
runner.run(sql.raw("BEGIN IMMEDIATE"));
|
|
80
|
-
try {
|
|
81
|
-
const result = await fn(db);
|
|
82
|
-
runner.run(sql.raw("COMMIT"));
|
|
83
|
-
return result;
|
|
84
|
-
} catch (error) {
|
|
85
|
-
try {
|
|
86
|
-
runner.run(sql.raw("ROLLBACK"));
|
|
87
|
-
} catch {
|
|
88
|
-
// A failed rollback must not mask the error that caused it; surface
|
|
89
|
-
// the original below.
|
|
90
|
-
}
|
|
91
|
-
throw error;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export async function runInTransaction<
|
|
96
|
-
TSchema extends Record<string, unknown>,
|
|
97
|
-
T,
|
|
98
|
-
>(db: Db<TSchema>, fn: (tx: Db<TSchema>) => Promise<T>): Promise<T> {
|
|
99
|
-
if (inSqliteTx.getStore()) {
|
|
100
|
-
// Already inside a top-level transaction on this connection — nest with a
|
|
101
|
-
// savepoint, do not re-queue.
|
|
102
|
-
return runSqliteSavepoint(db, fn);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return serializeSqlite(() =>
|
|
106
|
-
inSqliteTx.run(true, () => runSqliteImmediate(db, fn)),
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Every write on the shared better-sqlite3 connection must run start-to-finish
|
|
111
|
-
// with the connection in autocommit — never during the `await` gap of a
|
|
112
|
-
// top-level `runInTransaction` unit, where a plain single statement would join
|
|
113
|
-
// the open SAVEPOINT and be lost on its rollback (the uncommitted-read /
|
|
114
|
-
// rollback hazard from RFC 036 D3, named on PR #1310). A single statement runs
|
|
115
|
-
// synchronously, so the hazard only exists when it lands inside another unit's
|
|
116
|
-
// open savepoint; routing it through the same async queue that serializes
|
|
117
|
-
// transactions guarantees no savepoint is open when it executes.
|
|
118
|
-
//
|
|
119
|
-
// Inside a top-level unit (ALS flag set), the savepoint already holds the queue
|
|
120
|
-
// slot — running directly is correct and re-queuing would deadlock.
|
|
121
|
-
function runSqliteWrite<T>(execute: () => T | Promise<T>): Promise<T> {
|
|
122
|
-
if (inSqliteTx.getStore()) return Promise.resolve(execute());
|
|
123
|
-
return serializeSqlite(async () => execute());
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const SQLITE_WRITE_METHODS = new Set(["insert", "update", "delete"]);
|
|
127
|
-
|
|
128
|
-
// The methods that actually run a drizzle write builder's query. Every one of
|
|
129
|
-
// them must run under the write queue, not just the `await` (`then`) path — a
|
|
130
|
-
// repo (now or later) could execute a write via any of these and must not
|
|
131
|
-
// bypass serialization. `values` is an executor only on a runnable (a builder
|
|
132
|
-
// with `execute` — including a `.prepare()`d query); on the pre-`.values(rows)`
|
|
133
|
-
// insert builder it is the chain method, which the runnable gate below leaves
|
|
134
|
-
// to the pass-through path.
|
|
135
|
-
const SQLITE_EXECUTORS = new Set(["execute", "run", "all", "get", "values"]);
|
|
136
|
-
|
|
137
|
-
// A drizzle write builder is identified by its `execute` method. A Promise (what
|
|
138
|
-
// the executors above return) has `then` but no `execute`, so it is never
|
|
139
|
-
// mistaken for a builder and re-wrapped — re-wrapping a Promise would make its
|
|
140
|
-
// intercepted `then` call the non-existent `promise.execute()`.
|
|
141
|
-
const isWriteBuilder = (value: unknown): value is { execute: () => unknown } =>
|
|
142
|
-
typeof value === "object" &&
|
|
143
|
-
value !== null &&
|
|
144
|
-
typeof (value as { execute?: unknown }).execute === "function";
|
|
145
|
-
|
|
146
|
-
const runBuilder = (builder: object): Promise<unknown> =>
|
|
147
|
-
runSqliteWrite(() => (builder as { execute: () => unknown }).execute());
|
|
148
|
-
|
|
149
|
-
// Wrap a drizzle insert/update/delete builder so every terminal — the `await`
|
|
150
|
-
// (`then`/`catch`/`finally`) and the explicit executors (`execute`/`run`/`all`/
|
|
151
|
-
// `get`/`values`) — runs the query through `runSqliteWrite` instead of inline.
|
|
152
|
-
// Terminals exist only on a runnable (`execute` present), so interception is
|
|
153
|
-
// gated on that: on a pre-`.values(rows)` insert builder nothing is
|
|
154
|
-
// intercepted and the chain passes through untouched. The chain
|
|
155
|
-
// methods (`.values().returning().onConflictDoUpdate()`, `.set()`, `.where()`)
|
|
156
|
-
// return builders, so each chained result is re-wrapped until a terminal fires.
|
|
157
|
-
// Reads never reach here — only the write entry points below.
|
|
158
|
-
function wrapWriteBuilder<B extends object>(builder: B): B {
|
|
159
|
-
return new Proxy(builder, {
|
|
160
|
-
get(target, prop, receiver) {
|
|
161
|
-
if (isWriteBuilder(target)) {
|
|
162
|
-
if (prop === "then") {
|
|
163
|
-
return (
|
|
164
|
-
onFulfilled?: ((value: unknown) => unknown) | null,
|
|
165
|
-
onRejected?: ((reason: unknown) => unknown) | null,
|
|
166
|
-
) => runBuilder(target).then(onFulfilled, onRejected);
|
|
167
|
-
}
|
|
168
|
-
if (prop === "catch") {
|
|
169
|
-
return (onRejected?: ((reason: unknown) => unknown) | null) =>
|
|
170
|
-
runBuilder(target).catch(onRejected);
|
|
171
|
-
}
|
|
172
|
-
if (prop === "finally") {
|
|
173
|
-
return (onFinally?: (() => void) | null) =>
|
|
174
|
-
runBuilder(target).finally(onFinally);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
if (
|
|
178
|
-
typeof prop === "string" &&
|
|
179
|
-
SQLITE_EXECUTORS.has(prop) &&
|
|
180
|
-
isWriteBuilder(target)
|
|
181
|
-
) {
|
|
182
|
-
return (...args: unknown[]) =>
|
|
183
|
-
runSqliteWrite(() =>
|
|
184
|
-
(target as unknown as Record<string, (...a: unknown[]) => unknown>)[
|
|
185
|
-
prop
|
|
186
|
-
](...args),
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
const value = Reflect.get(target, prop, receiver);
|
|
190
|
-
if (typeof value === "function") {
|
|
191
|
-
return (...args: unknown[]) => {
|
|
192
|
-
const result = (value as (...a: unknown[]) => unknown).apply(
|
|
193
|
-
target,
|
|
194
|
-
args,
|
|
195
|
-
);
|
|
196
|
-
return result === target || isWriteBuilder(result)
|
|
197
|
-
? wrapWriteBuilder(result as object)
|
|
198
|
-
: result;
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
return value;
|
|
202
|
-
},
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Route every repo write through the write queue (RFC 036 D3). The handle a repo
|
|
207
|
-
// holds intercepts `insert`/`update`/`delete` and serializes the resulting
|
|
208
|
-
// query, so no repo insert/update/delete can land inside another unit's open
|
|
209
|
-
// SAVEPOINT. Deliberate escape hatches pass straight through: `run` (the
|
|
210
|
-
// savepoint DDL, already inside a serialized unit), `transaction`, and reads —
|
|
211
|
-
// reads are not serialized, so a read issued during another unit's open
|
|
212
|
-
// transaction can still observe uncommitted rows; the wrapper closes the
|
|
213
|
-
// write-side rollback hazard, not read isolation.
|
|
214
|
-
export function serializeSqliteWrites<TDb extends Db<Record<string, unknown>>>(
|
|
215
|
-
db: TDb,
|
|
216
|
-
): TDb {
|
|
217
|
-
return new Proxy(db, {
|
|
218
|
-
get(target, prop, receiver) {
|
|
219
|
-
const value = Reflect.get(target, prop, receiver);
|
|
220
|
-
if (
|
|
221
|
-
typeof prop === "string" &&
|
|
222
|
-
SQLITE_WRITE_METHODS.has(prop) &&
|
|
223
|
-
typeof value === "function"
|
|
224
|
-
) {
|
|
225
|
-
return (...args: unknown[]) =>
|
|
226
|
-
wrapWriteBuilder(
|
|
227
|
-
(value as (...a: unknown[]) => object).apply(target, args),
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
return typeof value === "function" ? value.bind(target) : value;
|
|
231
|
-
},
|
|
232
|
-
}) as TDb;
|
|
233
|
-
}
|