@jarenjs/db 0.49.2 → 0.66.1
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/ARCHITECTURE.md +420 -71
- package/README.md +711 -79
- package/docs/HOSTS.md +269 -0
- package/docs/JOBS-FORMAT.md +309 -45
- package/docs/LIVE-FORMAT.md +156 -19
- package/docs/MIGRATION-FORMAT.md +247 -40
- package/docs/MODEL-FORMAT.md +968 -86
- package/package.json +21 -8
- package/schemas/jaren-migration.draft-07.schema.json +73 -0
- package/schemas/jaren-migration.schema.json +73 -0
- package/schemas/jaren-model.draft-07.schema.json +224 -162
- package/schemas/jaren-model.schema.json +224 -162
- package/src/algebra.js +227 -9
- package/src/backup.js +161 -0
- package/src/cancellation.js +48 -0
- package/src/capture.js +255 -44
- package/src/cli.js +337 -50
- package/src/cursor.js +411 -0
- package/src/dag-job.js +154 -21
- package/src/ddl.js +125 -11
- package/src/dialect.js +267 -112
- package/src/dialects/expression-read.js +158 -0
- package/src/dialects/postgres.js +618 -0
- package/src/dialects/rtree-ddl.js +129 -0
- package/src/dialects/sqlite.js +245 -12
- package/src/document-files.js +311 -0
- package/src/document-steps.js +422 -0
- package/src/documents.js +335 -0
- package/src/driver.js +503 -69
- package/src/drivers/bun.js +37 -1
- package/src/drivers/indexeddb-snapshot.js +149 -0
- package/src/drivers/node-pool.js +11 -0
- package/src/drivers/node-worker-endpoint.js +105 -0
- package/src/drivers/node-worker.js +204 -0
- package/src/drivers/node.js +41 -7
- package/src/drivers/postgres.js +331 -0
- package/src/drivers/wasm-oo1.js +97 -0
- package/src/drivers/wasm-session.js +67 -0
- package/src/drivers/wasm.js +18 -83
- package/src/drivers/worker-pool.js +183 -0
- package/src/drivers/worker-protocol.js +79 -0
- package/src/drivers/worker-queue.js +60 -0
- package/src/emit-model.js +14 -0
- package/src/emit.js +349 -51
- package/src/entity.js +102 -59
- package/src/errors.js +430 -2
- package/src/expression.js +284 -0
- package/src/graph.js +64 -8
- package/src/index.js +48 -19
- package/src/introspect.js +583 -0
- package/src/jobs.js +870 -99
- package/src/json-bytes.js +58 -0
- package/src/live-time.js +12 -3
- package/src/live.js +11 -1
- package/src/maintenance.js +175 -0
- package/src/migrate.js +606 -333
- package/src/model.js +241 -8
- package/src/plan.js +1238 -160
- package/src/pragmas.js +314 -0
- package/src/profile.js +151 -3
- package/src/query.js +1748 -312
- package/src/residual.js +17 -0
- package/src/series.js +12 -4
- package/src/store.js +1672 -276
- package/src/tracker.js +367 -68
- package/src/udf.js +88 -7
- package/types/index.d.ts +1246 -32
- package/types/node-pool.d.ts +28 -0
- package/types/node-worker.d.ts +54 -0
- package/types/node.d.ts +72 -3
- package/types/postgres.d.ts +46 -0
- package/types/typed.d.ts +81 -3
- package/types/wasm.d.ts +21 -0
- package/dist/types/algebra.d.ts +0 -230
- package/dist/types/app.d.ts +0 -49
- package/dist/types/capture.d.ts +0 -85
- package/dist/types/cli.d.ts +0 -2
- package/dist/types/dag-job.d.ts +0 -40
- package/dist/types/ddl.d.ts +0 -229
- package/dist/types/derive.d.ts +0 -250
- package/dist/types/dialect.d.ts +0 -154
- package/dist/types/dialects/sqlite.d.ts +0 -9
- package/dist/types/driver.d.ts +0 -110
- package/dist/types/drivers/bun.d.ts +0 -47
- package/dist/types/drivers/node.d.ts +0 -37
- package/dist/types/drivers/wasm.d.ts +0 -65
- package/dist/types/emit-model.d.ts +0 -44
- package/dist/types/emit.d.ts +0 -75
- package/dist/types/entity.d.ts +0 -23
- package/dist/types/errors.d.ts +0 -170
- package/dist/types/graph.d.ts +0 -28
- package/dist/types/index.d.ts +0 -37
- package/dist/types/jobs.d.ts +0 -140
- package/dist/types/knn.d.ts +0 -69
- package/dist/types/live-time.d.ts +0 -141
- package/dist/types/live.d.ts +0 -64
- package/dist/types/migrate.d.ts +0 -170
- package/dist/types/model.d.ts +0 -36
- package/dist/types/patch-sql.d.ts +0 -37
- package/dist/types/plan.d.ts +0 -142
- package/dist/types/profile.d.ts +0 -80
- package/dist/types/query.d.ts +0 -112
- package/dist/types/residual.d.ts +0 -64
- package/dist/types/series.d.ts +0 -227
- package/dist/types/store.d.ts +0 -60
- package/dist/types/tracker.d.ts +0 -43
- package/dist/types/typed.d.ts +0 -15
- package/dist/types/types.d.ts +0 -26
- package/dist/types/udf.d.ts +0 -75
- package/dist/types/window.d.ts +0 -52
package/src/driver.js
CHANGED
|
@@ -19,20 +19,28 @@
|
|
|
19
19
|
* specifiers. `open()` is where "this driver does not exist here"
|
|
20
20
|
* becomes the coded `JD0003` instead of a module-load crash.
|
|
21
21
|
*
|
|
22
|
-
* `capabilities` is read once at open —
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
22
|
+
* `capabilities` is read once at open by a PROBE — the SQLite one by
|
|
23
|
+
* default, another engine's through `options.probe` — and is the single
|
|
24
|
+
* source of truth for feature gating; never a `typeof` sniff at a call
|
|
25
|
+
* site. {@link baseCapabilities} gives every slot the conservative
|
|
26
|
+
* answer, so a probe that says nothing about a feature says `false`
|
|
27
|
+
* rather than `undefined`. Two slots are deliberately EMPTY on every
|
|
28
|
+
* driver this package ships: `statementTimeout` (SQLite has no
|
|
29
|
+
* interrupt or progress handler to build one on, and a PostgreSQL
|
|
30
|
+
* server-side timeout is not the same promise as the store's
|
|
31
|
+
* `AbortSignal`) and `rowEstimates` (SQLite's query plan is prose, not
|
|
32
|
+
* numbers). They exist so a driver that has the facts can fill
|
|
29
33
|
* them without a contract change; pretending SQLite has them is the
|
|
30
|
-
* silent degradation this suite refuses.
|
|
34
|
+
* silent degradation this suite refuses. A third, `lazyIteration`, is
|
|
35
|
+
* probed rather than declared: whether the binding's statements carry
|
|
36
|
+
* a native row iterator, which is what lets a cursor classify itself
|
|
37
|
+
* honestly — a binding without one gets `iterate` composed over `all()`
|
|
38
|
+
* here, and the cursor over it says it buffers.
|
|
31
39
|
*/
|
|
32
40
|
|
|
33
41
|
import { isThenable, chain, toPromise } from '@jarenjs/core/function';
|
|
34
42
|
|
|
35
|
-
import { DbCompileError } from './errors.js';
|
|
43
|
+
import { DbCompileError, DbRuntimeError, TransactionFailure } from './errors.js';
|
|
36
44
|
|
|
37
45
|
/** The minimum SQLite the store accepts, asserted at open. */
|
|
38
46
|
export const SQLITE_FLOOR = '3.45.0';
|
|
@@ -46,6 +54,19 @@ export const SQLITE_FLOOR = '3.45.0';
|
|
|
46
54
|
*/
|
|
47
55
|
export const DEFAULT_QUEUE_TIMEOUT = 5000;
|
|
48
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Why a queued caller gave up. It is always a coded refusal — a caller
|
|
59
|
+
* has to be able to tell "you were cancelled before you ran" from every
|
|
60
|
+
* other failure — and the host's own `reason` rides along as the cause,
|
|
61
|
+
* so a cancellation that meant something specific still says it.
|
|
62
|
+
* @param {AbortSignal} [signal]
|
|
63
|
+
*/
|
|
64
|
+
function abortReason(signal) {
|
|
65
|
+
return new DbRuntimeError('JD2064',
|
|
66
|
+
'the call was aborted while it waited for the open transaction to settle; '
|
|
67
|
+
+ 'it ran no statement', { cause: signal?.reason });
|
|
68
|
+
}
|
|
69
|
+
|
|
49
70
|
// the sync-capable-async helpers are `@jarenjs/core/function`'s (one
|
|
50
71
|
// implementation in the suite); re-exported here because every store
|
|
51
72
|
// module and the public `@jarenjs/db` surface reach them through this seam
|
|
@@ -96,18 +117,103 @@ export function lazyOpen(specifier, reason, use, args) {
|
|
|
96
117
|
* @returns {{ run: Function, get: Function, all: Function,
|
|
97
118
|
* iterate: Function }}
|
|
98
119
|
*/
|
|
99
|
-
export function wrapStatement(statement) {
|
|
120
|
+
export function wrapStatement(statement, guard = undefined, active = undefined) {
|
|
121
|
+
const before = guard ?? (() => {});
|
|
122
|
+
const track = (source) => {
|
|
123
|
+
if (active === undefined) return source;
|
|
124
|
+
let done = false;
|
|
125
|
+
const iterator = {
|
|
126
|
+
next: () => {
|
|
127
|
+
before();
|
|
128
|
+
if (done) return { done: true, value: undefined };
|
|
129
|
+
return chain(source.next(), (step) => {
|
|
130
|
+
if (step.done) { done = true; active.delete(iterator); }
|
|
131
|
+
return step;
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
return: (value) => {
|
|
135
|
+
if (done) return { done: true, value };
|
|
136
|
+
done = true;
|
|
137
|
+
active.delete(iterator);
|
|
138
|
+
return source.return?.(value) ?? { done: true, value };
|
|
139
|
+
},
|
|
140
|
+
[Symbol.iterator]: () => iterator,
|
|
141
|
+
[Symbol.asyncIterator]: () => iterator,
|
|
142
|
+
};
|
|
143
|
+
active.add(iterator);
|
|
144
|
+
return iterator;
|
|
145
|
+
};
|
|
100
146
|
return {
|
|
101
|
-
run: (params = []) => statement.run(params),
|
|
102
|
-
get: (params = []) => statement.get(params),
|
|
103
|
-
all: (params = []) => statement.all(params),
|
|
147
|
+
run: (params = []) => { before(); return statement.run(params); },
|
|
148
|
+
get: (params = []) => { before(); return statement.get(params); },
|
|
149
|
+
all: (params = []) => { before(); return statement.all(params); },
|
|
104
150
|
iterate: typeof statement.iterate === 'function'
|
|
105
|
-
? (params = []) => /** @type {Function} */ (statement.iterate)(params)
|
|
106
|
-
: (params = []) => chain(statement.all(params),
|
|
107
|
-
(rows) => rows[Symbol.iterator]()),
|
|
151
|
+
? (params = []) => { before(); return chain(/** @type {Function} */ (statement.iterate)(params), track); }
|
|
152
|
+
: (params = []) => { before(); return chain(statement.all(params),
|
|
153
|
+
(rows) => track(rows[Symbol.iterator]())); },
|
|
108
154
|
};
|
|
109
155
|
}
|
|
110
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Run a driver call and map its failure — thrown OR rejected — through
|
|
159
|
+
* `wrap`. A `try/catch` around a value-or-promise call saw only the
|
|
160
|
+
* synchronous throw: on an asynchronous driver the failure arrived as a
|
|
161
|
+
* rejection nothing handled, so a rejected write resolved as a success
|
|
162
|
+
* and surfaced later as an unhandled rejection.
|
|
163
|
+
* @template T
|
|
164
|
+
* @param {() => T | Promise<T>} call
|
|
165
|
+
* @param {(error: any) => Error} wrap
|
|
166
|
+
* @returns {T | Promise<T>}
|
|
167
|
+
*/
|
|
168
|
+
export function attempt(call, wrap) {
|
|
169
|
+
let out;
|
|
170
|
+
try {
|
|
171
|
+
out = call();
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
throw wrap(error);
|
|
175
|
+
}
|
|
176
|
+
return isThenable(out)
|
|
177
|
+
? /** @type {Promise<T>} */ (out).then(undefined, (error) => { throw wrap(error); })
|
|
178
|
+
: out;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Settle a transaction only after its commit succeeds. Deferred constraints
|
|
183
|
+
* can refuse COMMIT or RELEASE after the body returned successfully; that
|
|
184
|
+
* failure owes the same rollback as a failing body.
|
|
185
|
+
* @param {() => any} body
|
|
186
|
+
* @param {() => any} commit
|
|
187
|
+
* @param {() => any} rollback
|
|
188
|
+
* @returns {any}
|
|
189
|
+
*/
|
|
190
|
+
function settleTransaction(body, commit, rollback) {
|
|
191
|
+
const fail = (error) => chain(attempt(rollback, (cleanupError) =>
|
|
192
|
+
// Losing one remote generation also loses its rollback channel.
|
|
193
|
+
// That is one failure, not two independent transaction defects.
|
|
194
|
+
error === cleanupError || error?.code === 'JD2090' && cleanupError?.code === 'JD2090'
|
|
195
|
+
&& error.generation === cleanupError.generation
|
|
196
|
+
? error : new TransactionFailure(error, cleanupError)), () => { throw error; });
|
|
197
|
+
const succeed = (value) => {
|
|
198
|
+
let committed;
|
|
199
|
+
try {
|
|
200
|
+
committed = commit();
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
return fail(error);
|
|
204
|
+
}
|
|
205
|
+
return isThenable(committed) ? committed.then(() => value, fail) : value;
|
|
206
|
+
};
|
|
207
|
+
let out;
|
|
208
|
+
try {
|
|
209
|
+
out = body();
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
return fail(error);
|
|
213
|
+
}
|
|
214
|
+
return isThenable(out) ? out.then(succeed, fail) : succeed(out);
|
|
215
|
+
}
|
|
216
|
+
|
|
111
217
|
/**
|
|
112
218
|
* Finish a raw binding into the connection contract: probe the library
|
|
113
219
|
* once, assert the version floor, freeze the capability table, and
|
|
@@ -115,23 +221,118 @@ export function wrapStatement(statement) {
|
|
|
115
221
|
*
|
|
116
222
|
* The raw shape a binding supplies:
|
|
117
223
|
* `{ exec(sql), prepare(sql) -> { run, get, all, iterate? }, close(),
|
|
118
|
-
* registerFunction?, registerAggregate?, session? }` — every
|
|
119
|
-
* value-or-promise.
|
|
224
|
+
* registerFunction?, registerAggregate?, session?, backup? }` — every
|
|
225
|
+
* method value-or-promise. `backup` is the online-backup primitive
|
|
226
|
+
* triple `{ copy(path, { rate, progress }), rename(from, to),
|
|
227
|
+
* remove(path) }` a binding with a platform backup API and a file
|
|
228
|
+
* system supplies; the store's `backupTo` is built on it and never
|
|
229
|
+
* touches a builtin itself.
|
|
120
230
|
*
|
|
121
231
|
* @param {any} raw
|
|
122
232
|
* @param {{ dialect: any, synchronous?: boolean, queueTimeout?: number,
|
|
233
|
+
* probe?: (raw: any, dialect: any, declared: any) => any,
|
|
123
234
|
* declared?: { sessions?: boolean, userFunctions?: boolean,
|
|
124
235
|
* deterministicIndexableFunctions?: boolean,
|
|
125
|
-
* aggregateFunctions?: boolean
|
|
236
|
+
* aggregateFunctions?: boolean,
|
|
237
|
+
* pragmas?: readonly string[],
|
|
238
|
+
* maintenance?: Record<string, boolean>,
|
|
239
|
+
* backup?: boolean } }} options - `declared.pragmas`
|
|
240
|
+
* names the configuration pragmas the binding can apply (the store's
|
|
241
|
+
* closed set, by option name); the store refuses a request outside it
|
|
242
|
+
* and reads every declared one back after open. `declared.maintenance`
|
|
243
|
+
* narrows the maintenance operations (`checkpoint`, `integrityCheck`,
|
|
244
|
+
* `foreignKeyCheck`, `optimize`): every SQLite library runs them, so
|
|
245
|
+
* an operation is available unless the binding declares it `false`
|
|
126
246
|
* @returns {any} a Connection, or a promise of one
|
|
127
247
|
*/
|
|
128
248
|
export function openConnection(raw, options) {
|
|
129
249
|
const { dialect } = options;
|
|
130
250
|
const synchronous = options.synchronous === true;
|
|
131
|
-
const
|
|
251
|
+
const probe = options.probe ?? sqliteProbe;
|
|
252
|
+
// The binding has already acquired its handle. A failed probe must
|
|
253
|
+
// release it here, before a store can take ownership of the connection.
|
|
254
|
+
/** @param {any} failure */
|
|
255
|
+
const failClosed = (failure) => {
|
|
256
|
+
/** @param {any} closeError */
|
|
257
|
+
const both = (closeError) => new AggregateError([failure, closeError],
|
|
258
|
+
'the connection failed to open, and closing its handle failed too');
|
|
259
|
+
const closed = attempt(() => raw.close(), both);
|
|
260
|
+
return chain(closed, () => { throw failure; });
|
|
261
|
+
};
|
|
262
|
+
let opened;
|
|
263
|
+
try {
|
|
264
|
+
opened = chain(probe(raw, dialect, options.declared ?? {}), (capabilities) =>
|
|
265
|
+
finishConnection(raw, dialect, synchronous, capabilities,
|
|
266
|
+
options.queueTimeout ?? DEFAULT_QUEUE_TIMEOUT));
|
|
267
|
+
}
|
|
268
|
+
catch (failure) {
|
|
269
|
+
return failClosed(failure);
|
|
270
|
+
}
|
|
271
|
+
return isThenable(opened) ? opened.then(undefined, failClosed) : opened;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* The capability answers every connection carries, each defaulted to
|
|
276
|
+
* the conservative one. A probe fills in what its engine and binding
|
|
277
|
+
* actually supply; a slot it says nothing about reads `false` rather
|
|
278
|
+
* than `undefined`, so a feature gate is never a `typeof` sniff at a
|
|
279
|
+
* call site.
|
|
280
|
+
* @returns {Record<string, any>}
|
|
281
|
+
*/
|
|
282
|
+
export function baseCapabilities() {
|
|
283
|
+
return {
|
|
284
|
+
version: '',
|
|
285
|
+
jsonb: false,
|
|
286
|
+
generatedColumns: false,
|
|
287
|
+
returning: false,
|
|
288
|
+
upsert: false,
|
|
289
|
+
savepoints: false,
|
|
290
|
+
rtree: false,
|
|
291
|
+
fts: false,
|
|
292
|
+
sessions: false,
|
|
293
|
+
sessionReason: null,
|
|
294
|
+
worker: false,
|
|
295
|
+
pooling: false,
|
|
296
|
+
poolReaders: 0,
|
|
297
|
+
poolWriters: 0,
|
|
298
|
+
userFunctions: false,
|
|
299
|
+
deterministicIndexableFunctions: false,
|
|
300
|
+
aggregateFunctions: false,
|
|
301
|
+
configurablePragmas: Object.freeze([]),
|
|
302
|
+
backup: false,
|
|
303
|
+
maintenance: Object.freeze({
|
|
304
|
+
checkpoint: false, integrityCheck: false, foreignKeyCheck: false, optimize: false,
|
|
305
|
+
}),
|
|
306
|
+
alterTableFull: false,
|
|
307
|
+
statementTimeout: false,
|
|
308
|
+
rowEstimates: false,
|
|
309
|
+
lazyIteration: false,
|
|
310
|
+
// the job queue, the change ledger and the live registry are built
|
|
311
|
+
// on SQLite's own spellings; a store on another engine reports them
|
|
312
|
+
// absent by name rather than failing at the first statement
|
|
313
|
+
jobs: false,
|
|
314
|
+
changeCapture: false,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* The SQLite probe: the library's version report and compile options,
|
|
320
|
+
* plus what the binding declares it can do. It is the DEFAULT probe
|
|
321
|
+
* because every binding this package ships is a SQLite one; a driver
|
|
322
|
+
* for another engine passes its own through `options.probe`, and the
|
|
323
|
+
* version floor asserted here goes with it.
|
|
324
|
+
* @param {any} raw
|
|
325
|
+
* @param {any} dialect
|
|
326
|
+
* @param {Record<string, any>} declared
|
|
327
|
+
* @returns {any} value-or-promise of the frozen capability table
|
|
328
|
+
*/
|
|
329
|
+
export function sqliteProbe(raw, dialect, declared) {
|
|
132
330
|
return chain(raw.prepare(dialect.introspect.version()), (versionStatement) =>
|
|
133
331
|
chain(versionStatement.get([]), (versionRow) => {
|
|
134
332
|
const version = String(versionRow.version);
|
|
333
|
+
// the binding's statements either carry a lazy iterator or they
|
|
334
|
+
// do not; the probe statement is one of them
|
|
335
|
+
const lazyIteration = typeof versionStatement.iterate === 'function';
|
|
135
336
|
if (compareVersions(version, SQLITE_FLOOR) < 0) {
|
|
136
337
|
throw new DbCompileError('JD0001',
|
|
137
338
|
`the SQLite library is ${version}, below the supported floor ${SQLITE_FLOOR}`);
|
|
@@ -139,7 +340,8 @@ export function openConnection(raw, options) {
|
|
|
139
340
|
return chain(raw.prepare(dialect.introspect.compileOptions()), (optionsStatement) =>
|
|
140
341
|
chain(optionsStatement.all([]), (rows) => {
|
|
141
342
|
const compiled = new Set(rows.map((row) => String(row.name)));
|
|
142
|
-
|
|
343
|
+
return Object.freeze({
|
|
344
|
+
...baseCapabilities(),
|
|
143
345
|
version,
|
|
144
346
|
// guaranteed by the version floor
|
|
145
347
|
jsonb: true,
|
|
@@ -155,6 +357,7 @@ export function openConnection(raw, options) {
|
|
|
155
357
|
sessions: declared.sessions === true
|
|
156
358
|
&& typeof raw.session === 'function'
|
|
157
359
|
&& compiled.has('ENABLE_SESSION'),
|
|
360
|
+
sessionReason: raw.sessionReason ?? null,
|
|
158
361
|
userFunctions: declared.userFunctions === true
|
|
159
362
|
&& typeof raw.registerFunction === 'function',
|
|
160
363
|
deterministicIndexableFunctions:
|
|
@@ -166,15 +369,40 @@ export function openConnection(raw, options) {
|
|
|
166
369
|
// method AND declare it.
|
|
167
370
|
aggregateFunctions: declared.aggregateFunctions === true
|
|
168
371
|
&& typeof raw.registerAggregate === 'function',
|
|
372
|
+
// the configuration pragmas the binding applies, by the
|
|
373
|
+
// store's option name — a request outside this list is a
|
|
374
|
+
// coded refusal at open, never a silently skipped member
|
|
375
|
+
configurablePragmas: Object.freeze([...(declared.pragmas ?? [])]),
|
|
376
|
+
// the online backup: the binding must supply the primitive
|
|
377
|
+
// triple AND declare it — the platform API and the file
|
|
378
|
+
// system it needs are the binding's, not the library's
|
|
379
|
+
backup: declared.backup === true && raw.backup !== null && typeof raw.backup === 'object'
|
|
380
|
+
&& typeof raw.backup.copy === 'function' && typeof raw.backup.rename === 'function'
|
|
381
|
+
&& typeof raw.backup.remove === 'function',
|
|
382
|
+
// the maintenance pragmas are the library's, not the
|
|
383
|
+
// binding's: available unless declared absent, and the
|
|
384
|
+
// store's report says so per operation
|
|
385
|
+
maintenance: Object.freeze({
|
|
386
|
+
checkpoint: declared.maintenance?.checkpoint !== false,
|
|
387
|
+
integrityCheck: declared.maintenance?.integrityCheck !== false,
|
|
388
|
+
foreignKeyCheck: declared.maintenance?.foreignKeyCheck !== false,
|
|
389
|
+
optimize: declared.maintenance?.optimize !== false,
|
|
390
|
+
}),
|
|
169
391
|
// structural SQLite limits — stated, not worked around
|
|
170
392
|
alterTableFull: false,
|
|
171
393
|
// the slots every SQLite driver leaves EMPTY (no
|
|
172
394
|
// interrupt, no progress handler, no estimate API)
|
|
173
395
|
statementTimeout: false,
|
|
174
396
|
rowEstimates: false,
|
|
397
|
+
// whether a cursor can pull one row at a time, or the driver
|
|
398
|
+
// materialises the result on the first pull (declared, so
|
|
399
|
+
// the cursor's own report is honest about it)
|
|
400
|
+
lazyIteration,
|
|
401
|
+
// the job queue and the change ledger are SQLite spellings,
|
|
402
|
+
// and every SQLite binding runs them
|
|
403
|
+
jobs: true,
|
|
404
|
+
changeCapture: true,
|
|
175
405
|
});
|
|
176
|
-
return finishConnection(raw, dialect, synchronous, capabilities,
|
|
177
|
-
options.queueTimeout ?? DEFAULT_QUEUE_TIMEOUT);
|
|
178
406
|
}));
|
|
179
407
|
}));
|
|
180
408
|
}
|
|
@@ -203,14 +431,26 @@ export function openConnection(raw, options) {
|
|
|
203
431
|
* @param {any} dialect
|
|
204
432
|
* @param {boolean} synchronous
|
|
205
433
|
* @param {Readonly<Record<string, any>>} capabilities
|
|
434
|
+
* @param {number} queueTimeout
|
|
206
435
|
* @returns {any}
|
|
207
436
|
*/
|
|
208
|
-
function finishConnection(raw, dialect, synchronous, capabilities, queueTimeout) {
|
|
437
|
+
export function finishConnection(raw, dialect, synchronous, capabilities, queueTimeout) {
|
|
438
|
+
const activeIterators = new Set();
|
|
209
439
|
/** Savepoint names are never reused, so a stale name can never be
|
|
210
440
|
* mistaken for a live one in an error or a log. */
|
|
211
441
|
let savepointSeq = 0;
|
|
212
442
|
/** Whether a top-level transaction currently owns the connection. */
|
|
213
443
|
let owned = false;
|
|
444
|
+
/**
|
|
445
|
+
* Whether a transaction BLOCK is open on this connection right now.
|
|
446
|
+
*
|
|
447
|
+
* Only an engine whose `SAVEPOINT` does not start a transaction needs
|
|
448
|
+
* the answer, and it needs it for one decision: a checkpoint asked
|
|
449
|
+
* for outside a block has to open the block first, because there is
|
|
450
|
+
* nothing for it to be a checkpoint OF. On SQLite a bare `SAVEPOINT`
|
|
451
|
+
* IS the block, and the flag is never read.
|
|
452
|
+
*/
|
|
453
|
+
let inBlock = false;
|
|
214
454
|
/**
|
|
215
455
|
* Whether an owning callback is on the stack RIGHT NOW — set around the
|
|
216
456
|
* synchronous extent of every transaction body, cleared the moment it
|
|
@@ -223,6 +463,17 @@ function finishConnection(raw, dialect, synchronous, capabilities, queueTimeout)
|
|
|
223
463
|
let onStack = false;
|
|
224
464
|
/** @type {Array<() => void>} FIFO of work waiting for the owner. */
|
|
225
465
|
const waiting = [];
|
|
466
|
+
/** Set by `close()`: every later call is refused by name rather than
|
|
467
|
+
* leaking the binding's own error (or, on a build that tolerates it,
|
|
468
|
+
* running against a closed handle). */
|
|
469
|
+
let closed = false;
|
|
470
|
+
let closeResult;
|
|
471
|
+
const requireOpen = () => {
|
|
472
|
+
if (closed) {
|
|
473
|
+
throw new DbRuntimeError('JD2063',
|
|
474
|
+
'the store is closed — a call after close() has no connection to run on');
|
|
475
|
+
}
|
|
476
|
+
};
|
|
226
477
|
|
|
227
478
|
/** Hand the connection to the next waiter, in arrival order. */
|
|
228
479
|
const release = () => {
|
|
@@ -242,28 +493,46 @@ function finishConnection(raw, dialect, synchronous, capabilities, queueTimeout)
|
|
|
242
493
|
* instead of the scope it was handed (never — it is waiting for itself).
|
|
243
494
|
* The bound turns the second case from a silent hang into a coded error
|
|
244
495
|
* that names the fix, the same trade SQLite's own busy timeout makes.
|
|
496
|
+
* A caller that gives up while queued — an aborted signal — is taken
|
|
497
|
+
* off the queue and rejected with its own reason, and `work` never
|
|
498
|
+
* runs at all. That is the difference between cancelling a request
|
|
499
|
+
* and cancelling its effect.
|
|
245
500
|
* @param {() => any} work
|
|
246
501
|
* @param {string} what - what is waiting, for the timeout message
|
|
502
|
+
* @param {AbortSignal} [signal] - abandons the wait when it aborts
|
|
247
503
|
* @returns {any} value-or-promise
|
|
248
504
|
*/
|
|
249
|
-
const whenFree = (work, what) => {
|
|
505
|
+
const whenFree = (work, what, signal) => {
|
|
506
|
+
if (signal?.aborted === true) return Promise.reject(abortReason(signal));
|
|
250
507
|
if (!owned) return work();
|
|
251
508
|
return new Promise((resolve, reject) => {
|
|
252
509
|
let done = false;
|
|
253
|
-
|
|
510
|
+
/** Leave the queue without running: the turn passes to the next
|
|
511
|
+
* waiter when the owner releases, exactly as a timeout's does. */
|
|
512
|
+
const abandon = (error) => {
|
|
254
513
|
done = true;
|
|
255
514
|
const index = waiting.indexOf(run);
|
|
256
515
|
if (index >= 0) waiting.splice(index, 1);
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
516
|
+
// a wait that ends by timeout owes the signal its listener back
|
|
517
|
+
signal?.removeEventListener('abort', cancelled);
|
|
518
|
+
reject(error);
|
|
519
|
+
};
|
|
520
|
+
const timer = setTimeout(() => abandon(new DbCompileError('JD0012',
|
|
521
|
+
`${what} waited ${queueTimeout}ms for the open transaction to settle. `
|
|
522
|
+
+ 'A transaction owns its connection until it commits; work that belongs '
|
|
523
|
+
+ 'INSIDE it must go through the store or client the callback received '
|
|
524
|
+
+ '(tx.collection / tx.entity / tx.entities / tx.transaction), not the '
|
|
525
|
+
+ 'outer one — that request waits for itself.')),
|
|
526
|
+
queueTimeout);
|
|
527
|
+
const cancelled = () => {
|
|
528
|
+
clearTimeout(timer);
|
|
529
|
+
abandon(abortReason(signal));
|
|
530
|
+
};
|
|
531
|
+
signal?.addEventListener('abort', cancelled, { once: true });
|
|
264
532
|
const run = () => {
|
|
265
533
|
if (done) { release(); return; } // already rejected: pass the turn on
|
|
266
534
|
clearTimeout(timer);
|
|
535
|
+
signal?.removeEventListener('abort', cancelled);
|
|
267
536
|
done = true;
|
|
268
537
|
let out;
|
|
269
538
|
try {
|
|
@@ -279,33 +548,136 @@ function finishConnection(raw, dialect, synchronous, capabilities, queueTimeout)
|
|
|
279
548
|
});
|
|
280
549
|
};
|
|
281
550
|
|
|
282
|
-
/**
|
|
283
|
-
*
|
|
551
|
+
/**
|
|
552
|
+
* Hold the connection for `fn`'s whole extent without opening a
|
|
553
|
+
* savepoint: what an UNRELATED caller needs so its statements cannot
|
|
554
|
+
* fall inside a transaction it is not part of. A store-level read or
|
|
555
|
+
* write is exactly that caller — it waits for the owner, owns the
|
|
556
|
+
* connection while it runs, and hands it on.
|
|
557
|
+
*
|
|
558
|
+
* `fn` receives a scope for the same reason a transaction callback
|
|
559
|
+
* does: work that must nest inside this one (a membership attach, a
|
|
560
|
+
* unit of work's own transaction) says so through the scope rather
|
|
561
|
+
* than queueing behind the caller it is part of.
|
|
284
562
|
* @param {(scope: any) => any} fn
|
|
563
|
+
* @param {string} [what] - what is waiting, for the timeout message
|
|
564
|
+
* @param {AbortSignal} [signal]
|
|
285
565
|
*/
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
return chain(raw.exec(dialect.tx.savepoint(name)), () => {
|
|
294
|
-
let out;
|
|
566
|
+
const exclusively = (fn, what, signal) => {
|
|
567
|
+
requireOpen();
|
|
568
|
+
// a synchronous extent inside an owning callback cannot interleave
|
|
569
|
+
// with anything, so there is nothing to wait for
|
|
570
|
+
if (onStack) return fn(scopeFor());
|
|
571
|
+
return whenFree(() => {
|
|
572
|
+
owned = true;
|
|
295
573
|
const wasOnStack = onStack;
|
|
296
574
|
onStack = true;
|
|
575
|
+
let out;
|
|
297
576
|
try {
|
|
298
577
|
out = fn(scopeFor());
|
|
299
578
|
}
|
|
300
579
|
catch (error) {
|
|
301
580
|
onStack = wasOnStack;
|
|
302
|
-
|
|
581
|
+
release();
|
|
582
|
+
throw error;
|
|
583
|
+
}
|
|
584
|
+
onStack = wasOnStack;
|
|
585
|
+
if (!isThenable(out)) {
|
|
586
|
+
release();
|
|
587
|
+
return out;
|
|
303
588
|
}
|
|
304
|
-
|
|
305
|
-
|
|
589
|
+
return out.then(
|
|
590
|
+
(value) => { release(); return value; },
|
|
591
|
+
(error) => { release(); throw error; });
|
|
592
|
+
}, what ?? 'a store-level call', signal);
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* The ONE checkpoint primitive both savepoint kinds are built on: a
|
|
597
|
+
* structured `transaction()` nesting opens one and settles it around
|
|
598
|
+
* its callback, and a scope's manual `savepoint()` opens one the
|
|
599
|
+
* caller settles by name. Every checkpoint wears a generated
|
|
600
|
+
* monotonic identifier from the same sequence, so the two kinds share
|
|
601
|
+
* one engine stack and no caller-supplied label ever reaches SQL.
|
|
602
|
+
* The handle is opaque: the store's label bookkeeping lives above it.
|
|
603
|
+
* @returns {any} value-or-promise of `{ name }`
|
|
604
|
+
*/
|
|
605
|
+
const openCheckpoint = () => {
|
|
606
|
+
const name = `jaren_sp_${savepointSeq++}`;
|
|
607
|
+
return chain(raw.exec(dialect.tx.savepoint(name)),
|
|
608
|
+
() => Object.freeze({ name }));
|
|
609
|
+
};
|
|
610
|
+
/** `ROLLBACK TO` a checkpoint: the engine keeps the target active and
|
|
611
|
+
* discards every savepoint opened after it.
|
|
612
|
+
* @param {{ name: string }} checkpoint */
|
|
613
|
+
const rollbackToCheckpoint = (checkpoint) =>
|
|
614
|
+
raw.exec(dialect.tx.rollbackTo(checkpoint.name));
|
|
615
|
+
/** `RELEASE` a checkpoint: the engine removes the target and every
|
|
616
|
+
* savepoint opened after it, keeping their rows.
|
|
617
|
+
* @param {{ name: string }} checkpoint */
|
|
618
|
+
const releaseCheckpoint = (checkpoint) =>
|
|
619
|
+
raw.exec(dialect.tx.release(checkpoint.name));
|
|
620
|
+
|
|
621
|
+
/** Only the synchronous extent of the callback may implicitly nest. */
|
|
622
|
+
const callBody = (fn) => {
|
|
623
|
+
const wasOnStack = onStack;
|
|
624
|
+
onStack = true;
|
|
625
|
+
try {
|
|
626
|
+
return fn(scopeFor());
|
|
627
|
+
}
|
|
628
|
+
finally {
|
|
629
|
+
onStack = wasOnStack;
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Own the connection inside a transaction BLOCK: `BEGIN` (or `BEGIN
|
|
635
|
+
* IMMEDIATE`) around `fn`, committed or rolled back as a whole.
|
|
636
|
+
*
|
|
637
|
+
* The `immediate` mode takes the write lock up front. A body
|
|
638
|
+
* that reads before it writes — a claim: read the record, decide,
|
|
639
|
+
* insert — otherwise meets the read→write upgrade `SQLITE_BUSY` the
|
|
640
|
+
* busy handler cannot retry when another connection commits in
|
|
641
|
+
* between; taking the lock first makes that wait an ordinary busy
|
|
642
|
+
* wait the timeout covers. Nesting inside it is savepoints, as always.
|
|
643
|
+
* @param {(scope: any) => any} fn
|
|
644
|
+
*/
|
|
645
|
+
const blockAround = (fn, mode) => {
|
|
646
|
+
const begin = mode === 'immediate' ? dialect.tx.beginImmediate : dialect.tx.begin;
|
|
647
|
+
return chain(raw.exec(begin), () => {
|
|
648
|
+
let out;
|
|
649
|
+
const wasInBlock = inBlock;
|
|
650
|
+
inBlock = true;
|
|
651
|
+
const restore = (value) => { inBlock = wasInBlock; return value; };
|
|
652
|
+
try {
|
|
653
|
+
out = settleTransaction(() => callBody(fn),
|
|
654
|
+
() => raw.exec(dialect.tx.commit), () => raw.exec(dialect.tx.rollback));
|
|
655
|
+
}
|
|
656
|
+
catch (error) {
|
|
657
|
+
restore(undefined);
|
|
658
|
+
throw error;
|
|
659
|
+
}
|
|
660
|
+
return isThenable(out)
|
|
661
|
+
? out.then(restore, (error) => { restore(undefined); throw error; })
|
|
662
|
+
: restore(out);
|
|
306
663
|
});
|
|
307
664
|
};
|
|
308
665
|
|
|
666
|
+
/** Open one savepoint around `fn`, at whatever depth we are. `fn`
|
|
667
|
+
* receives the scope so nested work can name itself.
|
|
668
|
+
* @param {(scope: any) => any} fn
|
|
669
|
+
*/
|
|
670
|
+
const savepointAround = (fn) => {
|
|
671
|
+
// an engine that refuses a savepoint outside a transaction gets the
|
|
672
|
+
// block it needs; on every other one this is the same statement it
|
|
673
|
+
// always was
|
|
674
|
+
if (!inBlock && dialect.capabilities.savepointStartsTransaction !== true)
|
|
675
|
+
return blockAround(fn, 'deferred');
|
|
676
|
+
return chain(openCheckpoint(), (checkpoint) => settleTransaction(() => callBody(fn),
|
|
677
|
+
() => releaseCheckpoint(checkpoint),
|
|
678
|
+
() => chain(rollbackToCheckpoint(checkpoint), () => releaseCheckpoint(checkpoint))));
|
|
679
|
+
};
|
|
680
|
+
|
|
309
681
|
/** The scope handed to a transaction callback: the owner's direct
|
|
310
682
|
* access to the connection, plus nesting. Deliberately narrow —
|
|
311
683
|
* registering a function or opening a change session belongs to store
|
|
@@ -315,12 +687,26 @@ function finishConnection(raw, dialect, synchronous, capabilities, queueTimeout)
|
|
|
315
687
|
capabilities,
|
|
316
688
|
dialect,
|
|
317
689
|
/** @param {string} sql */
|
|
318
|
-
exec: (sql) => raw.exec(sql),
|
|
690
|
+
exec: (sql) => { requireOpen(); return raw.exec(sql); },
|
|
319
691
|
/** @param {string} sql */
|
|
320
|
-
prepare: (sql) => chain(raw.prepare(sql), wrapStatement),
|
|
692
|
+
prepare: (sql, metadata) => { requireOpen(); return chain(raw.prepare(sql, metadata), (s) => wrapStatement(s, requireOpen, activeIterators)); },
|
|
321
693
|
/** A nested savepoint inside this transaction.
|
|
322
694
|
* @param {(scope: any) => any} fn */
|
|
323
695
|
transaction: (fn) => savepointAround(fn),
|
|
696
|
+
/** A MANUAL checkpoint at the current depth, settled by the caller
|
|
697
|
+
* through {@link rollbackTo}/{@link release} rather than around a
|
|
698
|
+
* callback. It is the same primitive structured nesting uses — one
|
|
699
|
+
* generated-identifier stack — so the two kinds cannot cross-release
|
|
700
|
+
* each other by name, and no caller-supplied label reaches SQL. */
|
|
701
|
+
savepoint: () => { requireOpen(); return openCheckpoint(); },
|
|
702
|
+
/** `ROLLBACK TO` a manual checkpoint: the target stays active; every
|
|
703
|
+
* savepoint opened after it is discarded with its rows.
|
|
704
|
+
* @param {{ name: string }} checkpoint */
|
|
705
|
+
rollbackTo: (checkpoint) => { requireOpen(); return rollbackToCheckpoint(checkpoint); },
|
|
706
|
+
/** `RELEASE` a manual checkpoint: the target and every savepoint
|
|
707
|
+
* opened after it are removed; their rows remain.
|
|
708
|
+
* @param {{ name: string }} checkpoint */
|
|
709
|
+
release: (checkpoint) => { requireOpen(); return releaseCheckpoint(checkpoint); },
|
|
324
710
|
});
|
|
325
711
|
|
|
326
712
|
return Object.freeze({
|
|
@@ -328,22 +714,27 @@ function finishConnection(raw, dialect, synchronous, capabilities, queueTimeout)
|
|
|
328
714
|
capabilities,
|
|
329
715
|
dialect,
|
|
330
716
|
/** @param {string} sql */
|
|
331
|
-
// NOT gated
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
// two
|
|
339
|
-
//
|
|
340
|
-
|
|
341
|
-
exec: (sql) => raw.exec(sql),
|
|
717
|
+
// NOT gated, because a SQLite connection has no per-statement
|
|
718
|
+
// transaction scope: a statement issued here joins whatever is open.
|
|
719
|
+
// That is right for work INSIDE the transaction, which is why the
|
|
720
|
+
// scope exposes the same two methods. An unrelated caller must not
|
|
721
|
+
// reach them — it takes `exclusively` instead, which holds the
|
|
722
|
+
// connection for its own extent, and the store's handles do exactly
|
|
723
|
+
// that (MODEL-FORMAT §5.1). What the gate guarantees on top is that
|
|
724
|
+
// two TRANSACTIONS never interleave, which is what made commits
|
|
725
|
+
// report failure.
|
|
726
|
+
exec: (sql) => { requireOpen(); return raw.exec(sql); },
|
|
342
727
|
/** @param {string} sql */
|
|
343
|
-
prepare: (sql) => chain(raw.prepare(sql), wrapStatement),
|
|
728
|
+
prepare: (sql, metadata) => { requireOpen(); return chain(raw.prepare(sql, metadata), (s) => wrapStatement(s, requireOpen, activeIterators)); },
|
|
729
|
+
/** Whether a transaction issued NOW would have to queue: an owner
|
|
730
|
+
* holds the connection and no owning callback is on the stack (a
|
|
731
|
+
* synchronous call from inside the callback nests instead). What a
|
|
732
|
+
* synchronous surface must know before it would hand back a Promise. */
|
|
733
|
+
get mustQueue() { return owned && !onStack; },
|
|
344
734
|
/**
|
|
345
735
|
* A transaction. `fn`'s value is returned; a throw rolls back exactly
|
|
346
|
-
* this level and rethrows.
|
|
736
|
+
* this level and rethrows. A refused COMMIT or RELEASE also rolls
|
|
737
|
+
* back before the next owner runs. No implicit retry.
|
|
347
738
|
*
|
|
348
739
|
* Two shapes, decided here rather than by the caller:
|
|
349
740
|
*
|
|
@@ -361,14 +752,26 @@ function finishConnection(raw, dialect, synchronous, capabilities, queueTimeout)
|
|
|
361
752
|
* queues behind the transaction the caller is part of, and
|
|
362
753
|
* {@link DEFAULT_QUEUE_TIMEOUT} turns that into `JD0012`.
|
|
363
754
|
* @param {(scope: any) => any} fn
|
|
755
|
+
* @param {AbortSignal} [signal] - abandons a QUEUED transaction; a
|
|
756
|
+
* transaction that has already taken the connection runs on
|
|
757
|
+
* @param {'deferred' | 'immediate'} [mode] - `'immediate'` takes the
|
|
758
|
+
* write lock up front (a top-level transaction only; a nested call
|
|
759
|
+
* is a savepoint whichever mode the root chose)
|
|
364
760
|
*/
|
|
365
|
-
transaction(fn) {
|
|
761
|
+
transaction(fn, signal, mode = 'deferred') {
|
|
762
|
+
requireOpen();
|
|
366
763
|
if (onStack) return savepointAround(fn);
|
|
367
764
|
return whenFree(() => {
|
|
368
765
|
owned = true;
|
|
369
766
|
let out;
|
|
370
767
|
try {
|
|
371
|
-
|
|
768
|
+
// A top-level transaction is one CHECKPOINT where a savepoint
|
|
769
|
+
// opens a transaction of its own, and a BLOCK where it does
|
|
770
|
+
// not: an engine that refuses `SAVEPOINT` outside a
|
|
771
|
+
// transaction has to be told one is starting.
|
|
772
|
+
out = mode === 'immediate' || dialect.capabilities.savepointStartsTransaction !== true
|
|
773
|
+
? blockAround(fn, mode)
|
|
774
|
+
: savepointAround(fn);
|
|
372
775
|
}
|
|
373
776
|
catch (error) {
|
|
374
777
|
release();
|
|
@@ -381,17 +784,48 @@ function finishConnection(raw, dialect, synchronous, capabilities, queueTimeout)
|
|
|
381
784
|
return out.then(
|
|
382
785
|
(value) => { release(); return value; },
|
|
383
786
|
(error) => { release(); throw error; });
|
|
384
|
-
}, 'a transaction');
|
|
787
|
+
}, 'a transaction', signal);
|
|
788
|
+
},
|
|
789
|
+
/** Hold the connection for one unrelated caller's whole extent,
|
|
790
|
+
* without a savepoint: what a store-level read or write takes so it
|
|
791
|
+
* cannot fall inside a transaction it is not part of. */
|
|
792
|
+
exclusively,
|
|
793
|
+
// idempotent: the second close is a no-op on every driver, not a
|
|
794
|
+
// raw error on one and a resolved promise on another
|
|
795
|
+
close: () => {
|
|
796
|
+
if (closed) return closeResult;
|
|
797
|
+
closed = true;
|
|
798
|
+
// Remote hosts own cursor cleanup within their bounded shutdown.
|
|
799
|
+
// Waiting for a row here would postpone that deadline indefinitely.
|
|
800
|
+
if (raw.closeDrainsIterators === true) {
|
|
801
|
+
activeIterators.clear();
|
|
802
|
+
return closeResult = raw.close();
|
|
803
|
+
}
|
|
804
|
+
const pending = [];
|
|
805
|
+
for (const iterator of activeIterators) {
|
|
806
|
+
try { pending.push(iterator.return()); }
|
|
807
|
+
catch (error) { pending.push(Promise.reject(error)); }
|
|
808
|
+
}
|
|
809
|
+
if (pending.some(isThenable)) return closeResult = Promise.allSettled(pending).then(() => raw.close());
|
|
810
|
+
return closeResult = raw.close();
|
|
385
811
|
},
|
|
386
|
-
close: () => raw.close(),
|
|
387
812
|
registerFunction: typeof raw.registerFunction === 'function'
|
|
388
|
-
? (name, functionOptions, fn) => raw.registerFunction(name, functionOptions, fn)
|
|
813
|
+
? (name, functionOptions, fn) => { requireOpen(); return raw.registerFunction(name, functionOptions, fn); }
|
|
389
814
|
: null,
|
|
390
815
|
registerAggregate: typeof raw.registerAggregate === 'function'
|
|
391
|
-
? (name, spec) => raw.registerAggregate(name, spec)
|
|
816
|
+
? (name, spec) => { requireOpen(); return raw.registerAggregate(name, spec); }
|
|
392
817
|
: null,
|
|
393
818
|
session: typeof raw.session === 'function'
|
|
394
|
-
? (table) => raw.session(table)
|
|
819
|
+
? (table) => { requireOpen(); return raw.session(table); }
|
|
820
|
+
: null,
|
|
821
|
+
// the backup primitives, each refused by name once the store is
|
|
822
|
+
// closed exactly as a statement is
|
|
823
|
+
backup: capabilities.backup === true
|
|
824
|
+
? Object.freeze({
|
|
825
|
+
copy: (path, options) => { requireOpen(); return raw.backup.copy(path, options); },
|
|
826
|
+
rename: (from, to) => { requireOpen(); return raw.backup.rename(from, to); },
|
|
827
|
+
remove: (path) => { requireOpen(); return raw.backup.remove(path); },
|
|
828
|
+
})
|
|
395
829
|
: null,
|
|
396
830
|
});
|
|
397
831
|
}
|