@loradb/lora-node 0.15.0 → 0.17.0
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 +134 -6
- package/dist/decode.d.ts.map +1 -1
- package/dist/decode.js +20 -8
- package/dist/decode.js.map +1 -1
- package/dist/index.d.ts +128 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +486 -17
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +61 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -1
- package/package.json +10 -6
- package/ts/native.d.ts +64 -1
package/dist/index.js
CHANGED
|
@@ -31,6 +31,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
31
31
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
32
32
|
};
|
|
33
33
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.Transaction = void 0;
|
|
34
35
|
exports.createDatabase = createDatabase;
|
|
35
36
|
exports.openWalDatabase = openWalDatabase;
|
|
36
37
|
const node_buffer_1 = require("node:buffer");
|
|
@@ -46,10 +47,20 @@ __exportStar(require("./types.js"), exports);
|
|
|
46
47
|
class NativeRowStream {
|
|
47
48
|
#inner;
|
|
48
49
|
#streamId;
|
|
50
|
+
#signal;
|
|
51
|
+
#release;
|
|
49
52
|
#closed = false;
|
|
50
|
-
constructor(inner, streamId) {
|
|
53
|
+
constructor(inner, streamId, signal, release) {
|
|
51
54
|
this.#inner = inner;
|
|
52
55
|
this.#streamId = streamId;
|
|
56
|
+
this.#signal = signal;
|
|
57
|
+
this.#release = release;
|
|
58
|
+
}
|
|
59
|
+
#finish() {
|
|
60
|
+
this.#closed = true;
|
|
61
|
+
const release = this.#release;
|
|
62
|
+
this.#release = undefined;
|
|
63
|
+
release?.();
|
|
53
64
|
}
|
|
54
65
|
[Symbol.asyncIterator]() {
|
|
55
66
|
return this;
|
|
@@ -69,13 +80,15 @@ class NativeRowStream {
|
|
|
69
80
|
try {
|
|
70
81
|
const row = this.#inner.streamNext(this.#streamId);
|
|
71
82
|
if (row === null) {
|
|
72
|
-
this.#
|
|
83
|
+
this.#finish();
|
|
73
84
|
return { done: true, value: undefined };
|
|
74
85
|
}
|
|
75
86
|
return { done: false, value: row };
|
|
76
87
|
}
|
|
77
88
|
catch (err) {
|
|
78
|
-
this.#
|
|
89
|
+
this.#finish();
|
|
90
|
+
if (this.#signal?.aborted)
|
|
91
|
+
throw this.#signal.reason;
|
|
79
92
|
throw (0, types_js_1.wrapError)(err);
|
|
80
93
|
}
|
|
81
94
|
}
|
|
@@ -96,7 +109,7 @@ class NativeRowStream {
|
|
|
96
109
|
close() {
|
|
97
110
|
if (this.#closed)
|
|
98
111
|
return;
|
|
99
|
-
this.#
|
|
112
|
+
this.#finish();
|
|
100
113
|
try {
|
|
101
114
|
this.#inner.streamClose(this.#streamId);
|
|
102
115
|
}
|
|
@@ -105,6 +118,189 @@ class NativeRowStream {
|
|
|
105
118
|
}
|
|
106
119
|
}
|
|
107
120
|
}
|
|
121
|
+
const CHANGES_POLL_MAX = 256;
|
|
122
|
+
class NativeChangeFeed {
|
|
123
|
+
#inner;
|
|
124
|
+
#signal;
|
|
125
|
+
#onDone;
|
|
126
|
+
#opened;
|
|
127
|
+
ready;
|
|
128
|
+
#id;
|
|
129
|
+
#buffer = [];
|
|
130
|
+
#done = false;
|
|
131
|
+
#error = undefined;
|
|
132
|
+
#lastLsn;
|
|
133
|
+
#wakeSeq = 0;
|
|
134
|
+
#wake;
|
|
135
|
+
#queue = Promise.resolve();
|
|
136
|
+
#onAbort;
|
|
137
|
+
constructor(inner, options, onDone) {
|
|
138
|
+
this.#inner = inner;
|
|
139
|
+
this.#signal = options?.signal;
|
|
140
|
+
this.#onDone = onDone;
|
|
141
|
+
const fromLsn = normalizeLsn(options?.fromLsn);
|
|
142
|
+
this.#lastLsn = fromLsn ?? undefined;
|
|
143
|
+
const bufferSize = normalizeBufferSize(options?.bufferSize);
|
|
144
|
+
if (this.#signal?.aborted) {
|
|
145
|
+
this.#done = true;
|
|
146
|
+
this.#error = this.#signal.reason;
|
|
147
|
+
this.#opened = Promise.reject(this.#signal.reason);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// Subscribes synchronously when capture is already on, so writes
|
|
151
|
+
// issued after `changes()` returns are never missed.
|
|
152
|
+
let opening;
|
|
153
|
+
try {
|
|
154
|
+
opening = inner.openChanges(fromLsn, bufferSize, () => this.#onWake());
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
opening = Promise.reject(err);
|
|
158
|
+
}
|
|
159
|
+
this.#opened = opening.then((id) => {
|
|
160
|
+
this.#id = id;
|
|
161
|
+
if (this.#done)
|
|
162
|
+
this.#closeNative();
|
|
163
|
+
return id;
|
|
164
|
+
});
|
|
165
|
+
if (this.#signal) {
|
|
166
|
+
const signal = this.#signal;
|
|
167
|
+
this.#onAbort = () => {
|
|
168
|
+
this.#buffer = [];
|
|
169
|
+
this.#end(signal.reason);
|
|
170
|
+
};
|
|
171
|
+
signal.addEventListener("abort", this.#onAbort, { once: true });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
this.ready = this.#opened.then(() => undefined, (err) => {
|
|
175
|
+
throw (0, types_js_1.wrapError)(err);
|
|
176
|
+
});
|
|
177
|
+
// `ready` is optional to await; its rejection also reaches `next()`.
|
|
178
|
+
this.ready.catch(() => undefined);
|
|
179
|
+
}
|
|
180
|
+
get lastLsn() {
|
|
181
|
+
return this.#lastLsn;
|
|
182
|
+
}
|
|
183
|
+
[Symbol.asyncIterator]() {
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
next() {
|
|
187
|
+
// Serialize concurrent `next()` calls so batches stay in order.
|
|
188
|
+
const result = this.#queue.then(() => this.#next());
|
|
189
|
+
this.#queue = result.catch(() => undefined);
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
async return() {
|
|
193
|
+
this.close();
|
|
194
|
+
return { done: true, value: undefined };
|
|
195
|
+
}
|
|
196
|
+
close() {
|
|
197
|
+
this.#buffer = [];
|
|
198
|
+
this.#end(undefined);
|
|
199
|
+
}
|
|
200
|
+
/** Called by `dispose()`: the native side already dropped the feed. */
|
|
201
|
+
disposed() {
|
|
202
|
+
this.#id = undefined;
|
|
203
|
+
this.#end(undefined);
|
|
204
|
+
}
|
|
205
|
+
async #next() {
|
|
206
|
+
for (;;) {
|
|
207
|
+
const buffered = this.#buffer.shift();
|
|
208
|
+
if (buffered) {
|
|
209
|
+
this.#lastLsn = buffered.lsn;
|
|
210
|
+
return { done: false, value: buffered };
|
|
211
|
+
}
|
|
212
|
+
if (this.#done) {
|
|
213
|
+
const err = this.#error;
|
|
214
|
+
this.#error = undefined;
|
|
215
|
+
if (err !== undefined)
|
|
216
|
+
throw err;
|
|
217
|
+
return { done: true, value: undefined };
|
|
218
|
+
}
|
|
219
|
+
let id;
|
|
220
|
+
try {
|
|
221
|
+
id = await this.#opened;
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
this.#end((0, types_js_1.wrapError)(err));
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (this.#done)
|
|
228
|
+
continue;
|
|
229
|
+
const seq = this.#wakeSeq;
|
|
230
|
+
let polled;
|
|
231
|
+
try {
|
|
232
|
+
polled = await this.#inner.changesPoll(id, CHANGES_POLL_MAX);
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
this.#end(this.#done ? undefined : (0, types_js_1.wrapError)(err));
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
this.#buffer.push(...polled.batches);
|
|
239
|
+
if (polled.closed) {
|
|
240
|
+
this.#end(undefined);
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (polled.batches.length === 0 && seq === this.#wakeSeq && !this.#done) {
|
|
244
|
+
await new Promise((resolve) => {
|
|
245
|
+
this.#wake = resolve;
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
#onWake() {
|
|
251
|
+
this.#wakeSeq += 1;
|
|
252
|
+
const wake = this.#wake;
|
|
253
|
+
this.#wake = undefined;
|
|
254
|
+
wake?.();
|
|
255
|
+
}
|
|
256
|
+
#end(error) {
|
|
257
|
+
if (this.#done) {
|
|
258
|
+
if (error !== undefined && this.#error === undefined)
|
|
259
|
+
this.#error = error;
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
this.#done = true;
|
|
263
|
+
this.#error = error;
|
|
264
|
+
if (this.#onAbort && this.#signal) {
|
|
265
|
+
this.#signal.removeEventListener("abort", this.#onAbort);
|
|
266
|
+
this.#onAbort = undefined;
|
|
267
|
+
}
|
|
268
|
+
this.#closeNative();
|
|
269
|
+
this.#onDone(this);
|
|
270
|
+
this.#onWake();
|
|
271
|
+
}
|
|
272
|
+
#closeNative() {
|
|
273
|
+
const id = this.#id;
|
|
274
|
+
if (id === undefined)
|
|
275
|
+
return;
|
|
276
|
+
this.#id = undefined;
|
|
277
|
+
try {
|
|
278
|
+
this.#inner.changesClose(id);
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
// Already gone (database disposed).
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function normalizeLsn(lsn) {
|
|
286
|
+
if (lsn === undefined)
|
|
287
|
+
return null;
|
|
288
|
+
const value = typeof lsn === "bigint" ? Number(lsn) : lsn;
|
|
289
|
+
if (!Number.isSafeInteger(value) ||
|
|
290
|
+
value < 0 ||
|
|
291
|
+
(typeof lsn === "bigint" && BigInt(value) !== lsn)) {
|
|
292
|
+
throw new types_js_1.LoraError(`\`fromLsn\` must be a non-negative safe integer, got ${String(lsn)}`, "LORA_INVALID_PARAMS");
|
|
293
|
+
}
|
|
294
|
+
return value;
|
|
295
|
+
}
|
|
296
|
+
function normalizeBufferSize(size) {
|
|
297
|
+
if (size === undefined)
|
|
298
|
+
return null;
|
|
299
|
+
if (!Number.isInteger(size) || size < 1 || size > 0xffff_ffff) {
|
|
300
|
+
throw new types_js_1.LoraError(`\`bufferSize\` must be a positive integer, got ${String(size)}`, "LORA_INVALID_PARAMS");
|
|
301
|
+
}
|
|
302
|
+
return size;
|
|
303
|
+
}
|
|
108
304
|
function isFetchUrl(url) {
|
|
109
305
|
return (url.protocol === "http:" ||
|
|
110
306
|
url.protocol === "https:" ||
|
|
@@ -253,6 +449,7 @@ function normalizeSnapshotLoadOptions(options) {
|
|
|
253
449
|
*/
|
|
254
450
|
class DatabaseImpl {
|
|
255
451
|
#inner;
|
|
452
|
+
#feeds = new Set();
|
|
256
453
|
constructor(inner) {
|
|
257
454
|
this.#inner = inner;
|
|
258
455
|
}
|
|
@@ -262,15 +459,25 @@ class DatabaseImpl {
|
|
|
262
459
|
* with a narrowed `code` from the `LoraErrorCode` union (e.g.
|
|
263
460
|
* `LORA_PARSE`, `LORA_INVALID_PARAMS`, `LORA_INTERNAL`).
|
|
264
461
|
*/
|
|
265
|
-
async execute(query, params) {
|
|
462
|
+
async execute(query, params, options) {
|
|
266
463
|
try {
|
|
267
|
-
const
|
|
464
|
+
const encoded = encodeParams(params);
|
|
465
|
+
const buf = await this.#limited(options, (timeoutMs, token) => this.#inner.execute(query, encoded, timeoutMs, token));
|
|
268
466
|
return (0, decode_js_1.decodeResult)(buf);
|
|
269
467
|
}
|
|
270
468
|
catch (err) {
|
|
271
469
|
throw (0, types_js_1.wrapError)(err);
|
|
272
470
|
}
|
|
273
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* Run `start` under `options`. Without a signal the timeout goes to the
|
|
474
|
+
* native call directly. With one, a native cancellation token carries
|
|
475
|
+
* the timeout, `abort` cancels the query server-side, and a cancelled
|
|
476
|
+
* call rejects with the signal's reason.
|
|
477
|
+
*/
|
|
478
|
+
#limited(options, start) {
|
|
479
|
+
return runLimited(this.#inner, options, start);
|
|
480
|
+
}
|
|
274
481
|
/**
|
|
275
482
|
* Compile a query and return its execution plan without running it.
|
|
276
483
|
*
|
|
@@ -281,7 +488,7 @@ class DatabaseImpl {
|
|
|
281
488
|
*/
|
|
282
489
|
async explain(query, params) {
|
|
283
490
|
try {
|
|
284
|
-
return (await this.#inner.explain(query, params
|
|
491
|
+
return (await this.#inner.explain(query, encodeParams(params)));
|
|
285
492
|
}
|
|
286
493
|
catch (err) {
|
|
287
494
|
throw (0, types_js_1.wrapError)(err);
|
|
@@ -298,7 +505,7 @@ class DatabaseImpl {
|
|
|
298
505
|
*/
|
|
299
506
|
async profile(query, params) {
|
|
300
507
|
try {
|
|
301
|
-
return (await this.#inner.profile(query, params
|
|
508
|
+
return (await this.#inner.profile(query, encodeParams(params)));
|
|
302
509
|
}
|
|
303
510
|
catch (err) {
|
|
304
511
|
throw (0, types_js_1.wrapError)(err);
|
|
@@ -311,18 +518,39 @@ class DatabaseImpl {
|
|
|
311
518
|
* Rows are materialized by the native `execute()` promise today, then
|
|
312
519
|
* yielded one at a time to JS consumers.
|
|
313
520
|
*/
|
|
314
|
-
stream(query, params) {
|
|
521
|
+
stream(query, params, options) {
|
|
315
522
|
try {
|
|
316
|
-
const
|
|
317
|
-
|
|
523
|
+
const encoded = encodeParams(params);
|
|
524
|
+
const timeoutMs = normalizeTimeout(options?.timeoutMs);
|
|
525
|
+
const signal = options?.signal;
|
|
526
|
+
if (signal == null) {
|
|
527
|
+
const streamId = this.#inner.openStream(query, encoded, timeoutMs, null);
|
|
528
|
+
return new NativeRowStream(this.#inner, streamId);
|
|
529
|
+
}
|
|
530
|
+
signal.throwIfAborted();
|
|
531
|
+
const token = this.#inner.createCancelToken(timeoutMs);
|
|
532
|
+
const onAbort = () => this.#inner.cancelQuery(token);
|
|
533
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
534
|
+
const release = () => {
|
|
535
|
+
signal.removeEventListener("abort", onAbort);
|
|
536
|
+
this.#inner.releaseCancelToken(token);
|
|
537
|
+
};
|
|
538
|
+
try {
|
|
539
|
+
const streamId = this.#inner.openStream(query, encoded, null, token);
|
|
540
|
+
return new NativeRowStream(this.#inner, streamId, signal, release);
|
|
541
|
+
}
|
|
542
|
+
catch (err) {
|
|
543
|
+
release();
|
|
544
|
+
throw err;
|
|
545
|
+
}
|
|
318
546
|
}
|
|
319
547
|
catch (err) {
|
|
320
548
|
throw (0, types_js_1.wrapError)(err);
|
|
321
549
|
}
|
|
322
550
|
}
|
|
323
551
|
/** Alias for `stream()`, useful when naming the row-level API explicitly. */
|
|
324
|
-
rows(query, params) {
|
|
325
|
-
return this.stream(query, params);
|
|
552
|
+
rows(query, params, options) {
|
|
553
|
+
return this.stream(query, params, options);
|
|
326
554
|
}
|
|
327
555
|
/**
|
|
328
556
|
* Execute a statement batch inside one native transaction.
|
|
@@ -331,9 +559,32 @@ class DatabaseImpl {
|
|
|
331
559
|
* native transaction is dropped before commit and all prior writes in the
|
|
332
560
|
* batch are rolled back.
|
|
333
561
|
*/
|
|
334
|
-
|
|
562
|
+
/**
|
|
563
|
+
* Begin an interactive transaction: run statements, decide in JS, then
|
|
564
|
+
* `commit()` or `rollback()`.
|
|
565
|
+
*
|
|
566
|
+
* A `read_write` transaction holds the database's writer lock from
|
|
567
|
+
* `begin()` until it finishes, so no other write can interleave with it
|
|
568
|
+
* (other writers wait). Reads outside the transaction keep seeing the
|
|
569
|
+
* last committed state. A statement that fails rolls the transaction
|
|
570
|
+
* back. An unfinished transaction rolls back when the handle is disposed
|
|
571
|
+
* (`await using tx = await db.begin()`), when the database is disposed,
|
|
572
|
+
* or when the process exits. Keep transactions short: while one is open,
|
|
573
|
+
* writers queue behind it.
|
|
574
|
+
*/
|
|
575
|
+
async begin(mode = "read_write") {
|
|
335
576
|
try {
|
|
336
|
-
const
|
|
577
|
+
const id = await this.#inner.beginTransaction(mode);
|
|
578
|
+
return new Transaction(this.#inner, id);
|
|
579
|
+
}
|
|
580
|
+
catch (err) {
|
|
581
|
+
throw (0, types_js_1.wrapError)(err);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
async transaction(statements, mode = "read_write", options) {
|
|
585
|
+
try {
|
|
586
|
+
const encoded = statements.map((st) => st.params == null ? st : { ...st, params: encodeParams(st.params) });
|
|
587
|
+
const buffers = await this.#limited(options, (timeoutMs, token) => this.#inner.transaction(encoded, mode, timeoutMs, token));
|
|
337
588
|
return buffers.map((buf) => (0, decode_js_1.decodeResult)(buf));
|
|
338
589
|
}
|
|
339
590
|
catch (err) {
|
|
@@ -396,6 +647,34 @@ class DatabaseImpl {
|
|
|
396
647
|
catch (err) {
|
|
397
648
|
throw (0, types_js_1.wrapError)(err);
|
|
398
649
|
}
|
|
650
|
+
finally {
|
|
651
|
+
for (const feed of [...this.#feeds])
|
|
652
|
+
feed.disposed();
|
|
653
|
+
this.#feeds.clear();
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Subscribe to committed changes: one `LoraChangeBatch` per committed
|
|
658
|
+
* write (auto-commit query, transaction, streamed write, `clear()`,
|
|
659
|
+
* snapshot restore), in commit order. Rolled-back work never appears.
|
|
660
|
+
*
|
|
661
|
+
* ```ts
|
|
662
|
+
* for await (const batch of db.changes({ fromLsn: saved })) {
|
|
663
|
+
* for (const change of batch.changes) handle(change);
|
|
664
|
+
* saved = batch.lsn;
|
|
665
|
+
* }
|
|
666
|
+
* ```
|
|
667
|
+
*
|
|
668
|
+
* Writers never wait for the feed. A consumer that falls more than
|
|
669
|
+
* `bufferSize` batches behind gets `LORA_CHANGES_LAGGED` after the
|
|
670
|
+
* buffered batches and should resume with `fromLsn: feed.lastLsn`.
|
|
671
|
+
* `dispose()` ends every feed. An open feed does not keep the process
|
|
672
|
+
* alive on its own.
|
|
673
|
+
*/
|
|
674
|
+
changes(options) {
|
|
675
|
+
const feed = new NativeChangeFeed(this.#inner, options, (done) => this.#feeds.delete(done));
|
|
676
|
+
this.#feeds.add(feed);
|
|
677
|
+
return feed;
|
|
399
678
|
}
|
|
400
679
|
/**
|
|
401
680
|
* Save the graph as a snapshot.
|
|
@@ -460,6 +739,127 @@ class DatabaseImpl {
|
|
|
460
739
|
}
|
|
461
740
|
}
|
|
462
741
|
}
|
|
742
|
+
/**
|
|
743
|
+
* An open interactive transaction from `Database.begin()`.
|
|
744
|
+
*/
|
|
745
|
+
class Transaction {
|
|
746
|
+
#inner;
|
|
747
|
+
#id;
|
|
748
|
+
#open = true;
|
|
749
|
+
/** @internal */
|
|
750
|
+
constructor(inner, id) {
|
|
751
|
+
this.#inner = inner;
|
|
752
|
+
this.#id = id;
|
|
753
|
+
}
|
|
754
|
+
/** Whether the transaction can still run statements. */
|
|
755
|
+
get isOpen() {
|
|
756
|
+
return this.#open;
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Run one statement inside the transaction. It sees the transaction's
|
|
760
|
+
* own uncommitted writes. If it fails, the transaction is rolled back.
|
|
761
|
+
*/
|
|
762
|
+
async execute(query, params, options) {
|
|
763
|
+
this.#assertOpen();
|
|
764
|
+
try {
|
|
765
|
+
const encoded = encodeParams(params);
|
|
766
|
+
const buf = await runLimited(this.#inner, options, (timeoutMs, token) => this.#inner.txExecute(this.#id, query, encoded, timeoutMs, token));
|
|
767
|
+
return (0, decode_js_1.decodeResult)(buf);
|
|
768
|
+
}
|
|
769
|
+
catch (err) {
|
|
770
|
+
this.#open = false;
|
|
771
|
+
throw (0, types_js_1.wrapError)(err);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Run several statements inside the transaction in one native call and
|
|
776
|
+
* return their results in order. Each statement sees the writes of the
|
|
777
|
+
* ones before it. This saves the per-call round trip of calling
|
|
778
|
+
* `execute()` once per statement.
|
|
779
|
+
*
|
|
780
|
+
* The batch stops at the first failing statement: the transaction is
|
|
781
|
+
* rolled back (as with a failed `execute()`) and the promise rejects
|
|
782
|
+
* with that statement's error, whose message ends with
|
|
783
|
+
* `(statement i of n)`. `options.timeoutMs` and `options.signal` bound
|
|
784
|
+
* the whole batch.
|
|
785
|
+
*/
|
|
786
|
+
async executeMany(statements, options) {
|
|
787
|
+
this.#assertOpen();
|
|
788
|
+
let encoded;
|
|
789
|
+
try {
|
|
790
|
+
encoded = statements.map((st) => st.params == null ? st : { ...st, params: encodeParams(st.params) });
|
|
791
|
+
}
|
|
792
|
+
catch (err) {
|
|
793
|
+
// Invalid params: nothing ran, the transaction stays open.
|
|
794
|
+
throw (0, types_js_1.wrapError)(err);
|
|
795
|
+
}
|
|
796
|
+
try {
|
|
797
|
+
const buffers = await runLimited(this.#inner, options, (timeoutMs, token) => this.#inner.txExecuteMany(this.#id, encoded, timeoutMs, token));
|
|
798
|
+
return buffers.map((buf) => (0, decode_js_1.decodeResult)(buf));
|
|
799
|
+
}
|
|
800
|
+
catch (err) {
|
|
801
|
+
this.#open = false;
|
|
802
|
+
throw (0, types_js_1.wrapError)(err);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
/** Commit every write made in the transaction and release the writer lock. */
|
|
806
|
+
async commit() {
|
|
807
|
+
await this.#finish(true);
|
|
808
|
+
}
|
|
809
|
+
/** Discard every write made in the transaction and release the writer lock. */
|
|
810
|
+
async rollback() {
|
|
811
|
+
await this.#finish(false);
|
|
812
|
+
}
|
|
813
|
+
/** Roll back if still open. Lets `await using tx = await db.begin()` clean up. */
|
|
814
|
+
async [Symbol.asyncDispose]() {
|
|
815
|
+
if (this.#open)
|
|
816
|
+
await this.rollback();
|
|
817
|
+
}
|
|
818
|
+
async #finish(commit) {
|
|
819
|
+
this.#assertOpen();
|
|
820
|
+
this.#open = false;
|
|
821
|
+
try {
|
|
822
|
+
await this.#inner.txFinish(this.#id, commit);
|
|
823
|
+
}
|
|
824
|
+
catch (err) {
|
|
825
|
+
throw (0, types_js_1.wrapError)(err);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
#assertOpen() {
|
|
829
|
+
if (!this.#open) {
|
|
830
|
+
throw new types_js_1.LoraError("transaction is no longer open (already committed, rolled back, or failed)", "LORA_TRANSACTION");
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
exports.Transaction = Transaction;
|
|
835
|
+
/**
|
|
836
|
+
* Run `start` under `options`. Without a signal the timeout goes to the
|
|
837
|
+
* native call directly. With one, a native cancellation token carries the
|
|
838
|
+
* timeout, `abort` cancels the query server-side, and a cancelled call
|
|
839
|
+
* rejects with the signal's reason.
|
|
840
|
+
*/
|
|
841
|
+
async function runLimited(inner, options, start) {
|
|
842
|
+
const timeoutMs = normalizeTimeout(options?.timeoutMs);
|
|
843
|
+
const signal = options?.signal;
|
|
844
|
+
if (signal == null)
|
|
845
|
+
return start(timeoutMs, null);
|
|
846
|
+
signal.throwIfAborted();
|
|
847
|
+
const token = inner.createCancelToken(timeoutMs);
|
|
848
|
+
const onAbort = () => inner.cancelQuery(token);
|
|
849
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
850
|
+
try {
|
|
851
|
+
return await start(null, token);
|
|
852
|
+
}
|
|
853
|
+
catch (err) {
|
|
854
|
+
if (signal.aborted)
|
|
855
|
+
throw signal.reason;
|
|
856
|
+
throw err;
|
|
857
|
+
}
|
|
858
|
+
finally {
|
|
859
|
+
signal.removeEventListener("abort", onAbort);
|
|
860
|
+
inner.releaseCancelToken(token);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
463
863
|
/**
|
|
464
864
|
* Create and initialize a new LoraDB instance.
|
|
465
865
|
*
|
|
@@ -522,8 +922,8 @@ async function createDatabase(databaseName, options = {}) {
|
|
|
522
922
|
const syncMode = options.syncMode ?? null;
|
|
523
923
|
const groupSyncIntervalMs = options.groupSyncIntervalMs ?? null;
|
|
524
924
|
return new DatabaseImpl(databaseName == null
|
|
525
|
-
? new NativeDatabase()
|
|
526
|
-
: new NativeDatabase(databaseName, options.databaseDir ?? null, syncMode, groupSyncIntervalMs, null, null, null, null, null));
|
|
925
|
+
? new NativeDatabase(null, null, null, null, null, null, null, null, null, options.queryTimeoutMs ?? null)
|
|
926
|
+
: new NativeDatabase(databaseName, options.databaseDir ?? null, syncMode, groupSyncIntervalMs, null, null, null, null, null, options.queryTimeoutMs ?? null));
|
|
527
927
|
}
|
|
528
928
|
catch (err) {
|
|
529
929
|
throw (0, types_js_1.wrapError)(err);
|
|
@@ -550,4 +950,73 @@ async function openWalDatabase(options) {
|
|
|
550
950
|
throw (0, types_js_1.wrapError)(err);
|
|
551
951
|
}
|
|
552
952
|
}
|
|
953
|
+
/**
|
|
954
|
+
* Prepare params for the native boundary. Params cross as JSON-shaped
|
|
955
|
+
* values, which cannot carry a `bigint`, so every bigint is rewritten to
|
|
956
|
+
* `{ kind: "integer", value: "<decimal>" }` and decoded exactly on the
|
|
957
|
+
* Rust side. Returns the input unchanged (no copy) when it holds no
|
|
958
|
+
* bigint, which is the common case.
|
|
959
|
+
*/
|
|
960
|
+
function encodeParams(params) {
|
|
961
|
+
if (params == null)
|
|
962
|
+
return null;
|
|
963
|
+
return needsEncoding(params) ? encodeBigInts(params) : params;
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* An integer-valued `number` past 2^53 has usually already been rounded
|
|
967
|
+
* by JavaScript (an id or timestamp parsed from JSON, say). Storing it
|
|
968
|
+
* would make that corruption permanent, so refuse it and point at bigint.
|
|
969
|
+
* Magnitudes of 2^63 and up cannot be 64-bit integers and stay floats.
|
|
970
|
+
*/
|
|
971
|
+
function checkNumber(value) {
|
|
972
|
+
if (Number.isInteger(value) &&
|
|
973
|
+
!Number.isSafeInteger(value) &&
|
|
974
|
+
Math.abs(value) < 2 ** 63) {
|
|
975
|
+
throw new types_js_1.LoraError(`integer ${value} is outside the JavaScript safe-integer range and may already be rounded; pass it as a bigint`, "LORA_INVALID_PARAMS");
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
function needsEncoding(value) {
|
|
979
|
+
if (typeof value === "bigint")
|
|
980
|
+
return true;
|
|
981
|
+
if (typeof value === "number") {
|
|
982
|
+
checkNumber(value);
|
|
983
|
+
return false;
|
|
984
|
+
}
|
|
985
|
+
if (value === null || typeof value !== "object")
|
|
986
|
+
return false;
|
|
987
|
+
if (ArrayBuffer.isView(value))
|
|
988
|
+
return false;
|
|
989
|
+
if (Array.isArray(value)) {
|
|
990
|
+
let found = false;
|
|
991
|
+
for (const item of value)
|
|
992
|
+
found = needsEncoding(item) || found;
|
|
993
|
+
return found;
|
|
994
|
+
}
|
|
995
|
+
let found = false;
|
|
996
|
+
for (const key in value) {
|
|
997
|
+
found = needsEncoding(value[key]) || found;
|
|
998
|
+
}
|
|
999
|
+
return found;
|
|
1000
|
+
}
|
|
1001
|
+
function encodeBigInts(value) {
|
|
1002
|
+
if (typeof value === "bigint")
|
|
1003
|
+
return { kind: "integer", value: value.toString() };
|
|
1004
|
+
if (value === null || typeof value !== "object" || ArrayBuffer.isView(value))
|
|
1005
|
+
return value;
|
|
1006
|
+
if (Array.isArray(value))
|
|
1007
|
+
return value.map(encodeBigInts);
|
|
1008
|
+
const out = {};
|
|
1009
|
+
for (const key in value)
|
|
1010
|
+
out[key] = encodeBigInts(value[key]);
|
|
1011
|
+
return out;
|
|
1012
|
+
}
|
|
1013
|
+
function normalizeTimeout(timeoutMs) {
|
|
1014
|
+
if (timeoutMs === undefined)
|
|
1015
|
+
return null;
|
|
1016
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
|
|
1017
|
+
throw new types_js_1.LoraError(`timeoutMs must be a non-negative finite number, got ${timeoutMs}`, "LORA_INVALID_PARAMS");
|
|
1018
|
+
}
|
|
1019
|
+
// The native side takes whole milliseconds as u32; 0 disables.
|
|
1020
|
+
return Math.min(Math.ceil(timeoutMs), 0xffff_ffff);
|
|
1021
|
+
}
|
|
553
1022
|
//# sourceMappingURL=index.js.map
|