@cutils/sqlite 0.0.0-stage → 0.1.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.
@@ -0,0 +1,761 @@
1
+ // @cutils/sqlite — embedded SQLite for chttp/scriptc apps.
2
+ //
3
+ // better-sqlite3 ergonomics, narrowed to what the scriptc subset lowers:
4
+ // `prepare().get/all/run/bind()`, `exec`, `pragma`, `transaction()`,
5
+ // columns metadata, `using` via `[Symbol.dispose]()`. Row typing and
6
+ // validation flow through the Standard-Schema seam: `prepare(sql, schema)`
7
+ // returns `Stmt<InferOutput<typeof schema>>`; the `*Raw` family opts out.
8
+ //
9
+ // Subset notes: non-generic `*Core` classes + `interface X extends XCore` /
10
+ // `const X = XCore` aliases (generic CLASSES degrade to methodless shells,
11
+ // generic METHODS are fine and are what make the interface assignable);
12
+ // `| null` unions only except the documented `get()` divergence; no
13
+ // `?.`/`??`, no Map/Set, no `for...in`, no object/method shorthand.
14
+ import { SqliteError, sqliteCodeName } from "./errors.js";
15
+ import { validateRow } from "./validate.js";
16
+ import { Scratch } from "./internal/buffer.js";
17
+ import { utf8Decode } from "./internal/utf8.js";
18
+ import { nextSavepoint } from "./internal/savepoint.js";
19
+ import { ffiBegin, ffiBindBlob, ffiBindCount, ffiBindDbl, ffiBindIndex, ffiBindInt, ffiBindNull, ffiBindText, ffiChanges, ffiClearBindings, ffiClose, ffiColumnBlob, ffiColumnCount, ffiColumnDbl, ffiColumnInt64, ffiColumnMeta, ffiColumnName, ffiColumnText, ffiColumnType, ffiCommit, ffiErrcode, ffiErrmsg, ffiExec, ffiExpandedSql, ffiFinalize, ffiInTransaction, ffiLastInsertRowid, ffiOpen, ffiPrepare, ffiReset, ffiRollback, ffiSavepoint, ffiStep, ffiStmtReadonly, } from "./internal/ffi.js";
20
+ export { StandardSchemaV1, StdProps } from "./types.js";
21
+ export { SqliteError };
22
+ // SQLITE_OPEN_* flags.
23
+ const OPEN_READONLY = 1;
24
+ const OPEN_READWRITE = 2;
25
+ const OPEN_CREATE = 4;
26
+ // sqlite3_column_type values.
27
+ const COL_INTEGER = 1;
28
+ const COL_FLOAT = 2;
29
+ const COL_TEXT = 3;
30
+ const COL_BLOB = 4;
31
+ // shim_savepoint op codes.
32
+ const SP_SAVE = 0;
33
+ const SP_RELEASE = 1;
34
+ const SP_ROLLBACK = 2;
35
+ const STEP_ROW = 0;
36
+ const STEP_DONE = 101;
37
+ const ERR_CLOSED = "database connection is not open";
38
+ const ERR_FINALIZED = "statement is finalized";
39
+ class StmtCore {
40
+ _db;
41
+ _stmt;
42
+ _source;
43
+ _schema;
44
+ _reader;
45
+ _readonly;
46
+ _finalized;
47
+ constructor(db, stmt, source, schema) {
48
+ this._db = db;
49
+ this._stmt = stmt;
50
+ this._source = source;
51
+ this._schema = schema;
52
+ this._reader = ffiColumnCount(stmt) > 0;
53
+ this._readonly = ffiStmtReadonly(stmt) === 1;
54
+ this._finalized = false;
55
+ }
56
+ get source() {
57
+ return this._source;
58
+ }
59
+ get reader() {
60
+ return this._reader;
61
+ }
62
+ get readonly() {
63
+ return this._readonly;
64
+ }
65
+ get database() {
66
+ return this._db;
67
+ }
68
+ get schema() {
69
+ return this._schema;
70
+ }
71
+ _checkLive() {
72
+ if (this._finalized) {
73
+ throw new SqliteError("SQLITE_MISUSE", ERR_FINALIZED);
74
+ }
75
+ }
76
+ _bindValue(idx, v) {
77
+ let rc;
78
+ if (v === null) {
79
+ rc = ffiBindNull(this._stmt, idx);
80
+ }
81
+ else if (typeof v === "bigint") {
82
+ rc = ffiBindInt(this._stmt, idx, v);
83
+ }
84
+ else if (typeof v === "number") {
85
+ if (Number.isInteger(v) && v <= 9007199254740991 && v >= -9007199254740991) {
86
+ rc = ffiBindInt(this._stmt, idx, BigInt(v));
87
+ }
88
+ else {
89
+ rc = ffiBindDbl(this._stmt, idx, v);
90
+ }
91
+ }
92
+ else if (typeof v === "boolean") {
93
+ rc = ffiBindInt(this._stmt, idx, v ? BigInt(1) : BigInt(0));
94
+ }
95
+ else if (typeof v === "string") {
96
+ rc = ffiBindText(this._stmt, idx, v);
97
+ }
98
+ else if (typeof v === "undefined") {
99
+ // Schema-validated records may carry optional keys as `undefined`
100
+ // (valibot `optional()`); bind NULL, better-sqlite3 semantics.
101
+ rc = ffiBindNull(this._stmt, idx);
102
+ }
103
+ else {
104
+ rc = ffiBindBlob(this._stmt, idx, v);
105
+ }
106
+ if (rc !== 0) {
107
+ this._db._throwDb(rc, "bind failed");
108
+ }
109
+ }
110
+ _namedIndex(key) {
111
+ // Named params may be keyed with or without their sigil: "title",
112
+ // ":title", "@title", "$title" all resolve against :title/@title/$title
113
+ // in the prepared SQL.
114
+ let idx = ffiBindIndex(this._stmt, key);
115
+ if (idx !== 0) {
116
+ return idx;
117
+ }
118
+ let bare = key;
119
+ const c0 = key.charAt(0);
120
+ if (c0 === ":" || c0 === "@" || c0 === "$") {
121
+ bare = key.slice(1);
122
+ idx = ffiBindIndex(this._stmt, bare);
123
+ if (idx !== 0) {
124
+ return idx;
125
+ }
126
+ }
127
+ idx = ffiBindIndex(this._stmt, ":" + bare);
128
+ if (idx !== 0) {
129
+ return idx;
130
+ }
131
+ idx = ffiBindIndex(this._stmt, "@" + bare);
132
+ if (idx !== 0) {
133
+ return idx;
134
+ }
135
+ return ffiBindIndex(this._stmt, "$" + bare);
136
+ }
137
+ _bindParams(params) {
138
+ // Always reset before stepping; an omitted or EMPTY params argument
139
+ // keeps whatever bindings are currently set (the `stmt.bind()`
140
+ // permanent-bind path) — a subset-clean stand-in for better-sqlite3's
141
+ // zero-argument call convention.
142
+ ffiReset(this._stmt);
143
+ if (typeof params === "undefined") {
144
+ return;
145
+ }
146
+ if (Array.isArray(params)) {
147
+ if (params.length === 0) {
148
+ return;
149
+ }
150
+ ffiClearBindings(this._stmt);
151
+ const count = ffiBindCount(this._stmt);
152
+ if (params.length > count) {
153
+ throw new SqliteError("BIND_PARAM", "too many bind parameters: got " +
154
+ String(params.length) +
155
+ ", statement takes " +
156
+ String(count));
157
+ }
158
+ for (let i = 0; i < params.length; i++) {
159
+ this._bindValue(i + 1, params[i]);
160
+ }
161
+ return;
162
+ }
163
+ if (typeof params !== "object" || params === null) {
164
+ throw new SqliteError("BIND_PARAM", "bind parameters must be a tuple or a record");
165
+ }
166
+ const rec = params;
167
+ const keys = Object.keys(rec);
168
+ if (keys.length === 0) {
169
+ return;
170
+ }
171
+ ffiClearBindings(this._stmt);
172
+ for (let i = 0; i < keys.length; i++) {
173
+ const key = keys[i];
174
+ const idx = this._namedIndex(key);
175
+ if (idx === 0) {
176
+ throw new SqliteError("BIND_PARAM", "no bind parameter named " + key);
177
+ }
178
+ this._bindValue(idx, rec[key]);
179
+ }
180
+ }
181
+ _columnName(idx) {
182
+ const n = ffiColumnName(this._stmt, idx, this._db._scratch.buf);
183
+ if (n < 0) {
184
+ return "";
185
+ }
186
+ if (n > this._db._scratch.buf.length) {
187
+ this._db._scratch.ensure(n);
188
+ const m = ffiColumnName(this._stmt, idx, this._db._scratch.buf);
189
+ if (m < 0) {
190
+ return "";
191
+ }
192
+ return utf8Decode(this._db._scratch.buf, m);
193
+ }
194
+ return utf8Decode(this._db._scratch.buf, n);
195
+ }
196
+ _columnMeta(idx, which) {
197
+ const n = ffiColumnMeta(this._stmt, idx, which, this._db._scratch.buf);
198
+ if (n < 0) {
199
+ return null;
200
+ }
201
+ if (n > this._db._scratch.buf.length) {
202
+ this._db._scratch.ensure(n);
203
+ const m = ffiColumnMeta(this._stmt, idx, which, this._db._scratch.buf);
204
+ if (m < 0) {
205
+ return null;
206
+ }
207
+ return utf8Decode(this._db._scratch.buf, m);
208
+ }
209
+ return utf8Decode(this._db._scratch.buf, n);
210
+ }
211
+ _columnValue(idx) {
212
+ const t = ffiColumnType(this._stmt, idx);
213
+ if (t === COL_INTEGER) {
214
+ const b = ffiColumnInt64(this._stmt, idx);
215
+ const n = Number(b);
216
+ if (Number.isSafeInteger(n)) {
217
+ return n;
218
+ }
219
+ return b;
220
+ }
221
+ if (t === COL_FLOAT) {
222
+ return ffiColumnDbl(this._stmt, idx);
223
+ }
224
+ if (t === COL_TEXT) {
225
+ const n = ffiColumnText(this._stmt, idx, this._db._scratch.buf);
226
+ if (n < 0) {
227
+ return "";
228
+ }
229
+ if (n > this._db._scratch.buf.length) {
230
+ this._db._scratch.ensure(n);
231
+ const m = ffiColumnText(this._stmt, idx, this._db._scratch.buf);
232
+ if (m < 0) {
233
+ return "";
234
+ }
235
+ return utf8Decode(this._db._scratch.buf, m);
236
+ }
237
+ return utf8Decode(this._db._scratch.buf, n);
238
+ }
239
+ if (t === COL_BLOB) {
240
+ const n = ffiColumnBlob(this._stmt, idx, this._db._scratch.buf);
241
+ if (n < 0) {
242
+ return new Uint8Array(0);
243
+ }
244
+ if (n > this._db._scratch.buf.length) {
245
+ this._db._scratch.ensure(n);
246
+ const m = ffiColumnBlob(this._stmt, idx, this._db._scratch.buf);
247
+ if (m < 0) {
248
+ return new Uint8Array(0);
249
+ }
250
+ return this._db._scratch.copyOut(m);
251
+ }
252
+ return this._db._scratch.copyOut(n);
253
+ }
254
+ return null;
255
+ }
256
+ _readRow() {
257
+ const ncols = ffiColumnCount(this._stmt);
258
+ const row = {};
259
+ for (let i = 0; i < ncols; i++) {
260
+ row[this._columnName(i)] = this._columnValue(i);
261
+ }
262
+ return row;
263
+ }
264
+ _validateOut(row) {
265
+ if (this._schema === null) {
266
+ return row;
267
+ }
268
+ return validateRow(this._schema, row);
269
+ }
270
+ _stepOnce() {
271
+ const rc = ffiStep(this._stmt);
272
+ if (rc === STEP_ROW) {
273
+ const row = this._readRow();
274
+ ffiReset(this._stmt);
275
+ return this._validateOut(row);
276
+ }
277
+ ffiReset(this._stmt);
278
+ if (rc === STEP_DONE) {
279
+ return null;
280
+ }
281
+ this._db._throwDb(rc, "step failed");
282
+ return null;
283
+ }
284
+ _allRows() {
285
+ const out = [];
286
+ for (;;) {
287
+ const rc = ffiStep(this._stmt);
288
+ if (rc === STEP_ROW) {
289
+ out.push(this._validateOut(this._readRow()));
290
+ continue;
291
+ }
292
+ ffiReset(this._stmt);
293
+ if (rc === STEP_DONE) {
294
+ return out;
295
+ }
296
+ this._db._throwDb(rc, "step failed");
297
+ return out;
298
+ }
299
+ }
300
+ _drain() {
301
+ for (;;) {
302
+ const rc = ffiStep(this._stmt);
303
+ if (rc === STEP_ROW) {
304
+ continue;
305
+ }
306
+ ffiReset(this._stmt);
307
+ if (rc === STEP_DONE) {
308
+ return;
309
+ }
310
+ this._db._throwDb(rc, "step failed");
311
+ return;
312
+ }
313
+ }
314
+ _runResult() {
315
+ return {
316
+ changes: ffiChanges(this._db._db),
317
+ lastInsertRowid: Number(ffiLastInsertRowid(this._db._db)),
318
+ };
319
+ }
320
+ run(params) {
321
+ this._checkLive();
322
+ let bound = params;
323
+ if (this._schema !== null && typeof params !== "undefined") {
324
+ bound = validateRow(this._schema, params);
325
+ }
326
+ this._bindParams(bound);
327
+ this._drain();
328
+ return this._runResult();
329
+ }
330
+ runRaw(params) {
331
+ this._checkLive();
332
+ this._bindParams(params);
333
+ this._drain();
334
+ return this._runResult();
335
+ }
336
+ get(params) {
337
+ this._checkLive();
338
+ this._bindParams(params);
339
+ const row = this._stepOnce();
340
+ if (row === null) {
341
+ return undefined;
342
+ }
343
+ return row;
344
+ }
345
+ getOrNull(params) {
346
+ this._checkLive();
347
+ this._bindParams(params);
348
+ return this._stepOnce();
349
+ }
350
+ getRaw(params) {
351
+ this._checkLive();
352
+ this._bindParams(params);
353
+ const rc = ffiStep(this._stmt);
354
+ if (rc === STEP_ROW) {
355
+ const row = this._readRow();
356
+ ffiReset(this._stmt);
357
+ return row;
358
+ }
359
+ ffiReset(this._stmt);
360
+ if (rc === STEP_DONE) {
361
+ return null;
362
+ }
363
+ this._db._throwDb(rc, "step failed");
364
+ return null;
365
+ }
366
+ all(params) {
367
+ this._checkLive();
368
+ this._bindParams(params);
369
+ return this._allRows();
370
+ }
371
+ allRaw(params) {
372
+ this._checkLive();
373
+ this._bindParams(params);
374
+ const out = [];
375
+ for (;;) {
376
+ const rc = ffiStep(this._stmt);
377
+ if (rc === STEP_ROW) {
378
+ out.push(this._readRow());
379
+ continue;
380
+ }
381
+ ffiReset(this._stmt);
382
+ if (rc === STEP_DONE) {
383
+ return out;
384
+ }
385
+ this._db._throwDb(rc, "step failed");
386
+ return out;
387
+ }
388
+ }
389
+ bind(params) {
390
+ this._checkLive();
391
+ if (typeof params === "undefined") {
392
+ return this;
393
+ }
394
+ if (Array.isArray(params)) {
395
+ const count = ffiBindCount(this._stmt);
396
+ if (params.length > count) {
397
+ throw new SqliteError("BIND_PARAM", "too many bind parameters");
398
+ }
399
+ for (let i = 0; i < params.length; i++) {
400
+ this._bindValue(i + 1, params[i]);
401
+ }
402
+ return this;
403
+ }
404
+ if (typeof params !== "object" || params === null) {
405
+ throw new SqliteError("BIND_PARAM", "bind parameters must be a tuple or a record");
406
+ }
407
+ const rec = params;
408
+ const keys = Object.keys(rec);
409
+ for (let i = 0; i < keys.length; i++) {
410
+ const key = keys[i];
411
+ const idx = this._namedIndex(key);
412
+ if (idx === 0) {
413
+ throw new SqliteError("BIND_PARAM", "no bind parameter named " + key);
414
+ }
415
+ this._bindValue(idx, rec[key]);
416
+ }
417
+ return this;
418
+ }
419
+ columns() {
420
+ this._checkLive();
421
+ const ncols = ffiColumnCount(this._stmt);
422
+ const out = [];
423
+ for (let i = 0; i < ncols; i++) {
424
+ out.push({
425
+ name: this._columnName(i),
426
+ column: this._columnMeta(i, 0),
427
+ table: this._columnMeta(i, 1),
428
+ database: this._columnMeta(i, 2),
429
+ type: this._columnMeta(i, 3),
430
+ });
431
+ }
432
+ return out;
433
+ }
434
+ toString() {
435
+ const n = ffiExpandedSql(this._stmt, this._db._scratch.buf);
436
+ if (n < 0) {
437
+ return this._source;
438
+ }
439
+ if (n > this._db._scratch.buf.length) {
440
+ this._db._scratch.ensure(n);
441
+ const m = ffiExpandedSql(this._stmt, this._db._scratch.buf);
442
+ if (m < 0) {
443
+ return this._source;
444
+ }
445
+ return utf8Decode(this._db._scratch.buf, m);
446
+ }
447
+ return utf8Decode(this._db._scratch.buf, n);
448
+ }
449
+ finalize() {
450
+ if (this._finalized) {
451
+ return;
452
+ }
453
+ this._finalized = true;
454
+ ffiFinalize(this._stmt);
455
+ this._db._untrack(this);
456
+ }
457
+ [Symbol.dispose]() {
458
+ this.finalize();
459
+ }
460
+ }
461
+ export const Stmt = StmtCore;
462
+ export class Database {
463
+ _db;
464
+ _path;
465
+ _memory;
466
+ _readonly;
467
+ _closed;
468
+ _scratch;
469
+ _stmts;
470
+ _txs;
471
+ constructor(handle, path, memory, readonly_) {
472
+ this._db = handle;
473
+ this._path = path;
474
+ this._memory = memory;
475
+ this._readonly = readonly_;
476
+ this._closed = false;
477
+ this._scratch = new Scratch();
478
+ this._stmts = [];
479
+ this._txs = [];
480
+ }
481
+ static open(path, opts) {
482
+ let flags = OPEN_READWRITE;
483
+ let ro = false;
484
+ let mustExist = false;
485
+ if (typeof opts !== "undefined") {
486
+ if (opts.readonly === true) {
487
+ ro = true;
488
+ }
489
+ if (opts.fileMustExist === true) {
490
+ mustExist = true;
491
+ }
492
+ }
493
+ if (ro) {
494
+ flags = OPEN_READONLY;
495
+ }
496
+ else {
497
+ flags = OPEN_READWRITE;
498
+ if (!mustExist) {
499
+ flags = OPEN_READWRITE | OPEN_CREATE;
500
+ }
501
+ }
502
+ const h = ffiOpen(path, flags);
503
+ if (Number(h) === 0) {
504
+ throw new SqliteError("SQLITE_NOMEM", "out of memory opening " + path);
505
+ }
506
+ const rc = ffiErrcode(h);
507
+ if (rc !== 0) {
508
+ const scratch = new Scratch();
509
+ const n = ffiErrmsg(h, scratch.buf);
510
+ const msg = n >= 0 ? utf8Decode(scratch.buf, n) : "open failed";
511
+ ffiClose(h);
512
+ throw new SqliteError(sqliteCodeName(rc), msg);
513
+ }
514
+ return new Database(h, path, path === ":memory:", ro);
515
+ }
516
+ static openInMemory() {
517
+ const db = Database.open(":memory:");
518
+ db._memory = true;
519
+ return db;
520
+ }
521
+ get path() {
522
+ return this._path;
523
+ }
524
+ get memory() {
525
+ return this._memory;
526
+ }
527
+ get readonly() {
528
+ return this._readonly;
529
+ }
530
+ get inTransaction() {
531
+ if (this._closed) {
532
+ return false;
533
+ }
534
+ return ffiInTransaction(this._db) === 1;
535
+ }
536
+ _checkOpen() {
537
+ if (this._closed) {
538
+ throw new SqliteError("SQLITE_MISUSE", ERR_CLOSED);
539
+ }
540
+ }
541
+ _errmsg() {
542
+ const n = ffiErrmsg(this._db, this._scratch.buf);
543
+ if (n < 0) {
544
+ return "sqlite error";
545
+ }
546
+ if (n > this._scratch.buf.length) {
547
+ this._scratch.ensure(n);
548
+ const m = ffiErrmsg(this._db, this._scratch.buf);
549
+ if (m < 0) {
550
+ return "sqlite error";
551
+ }
552
+ return utf8Decode(this._scratch.buf, m);
553
+ }
554
+ return utf8Decode(this._scratch.buf, n);
555
+ }
556
+ _throwDb(rc, fallback) {
557
+ let msg = this._errmsg();
558
+ if (msg.length === 0) {
559
+ msg = fallback;
560
+ }
561
+ const txs = this._txs;
562
+ if (txs.length > 0) {
563
+ txs[txs.length - 1]._failed = true;
564
+ }
565
+ // Prefer the handle's EXTENDED errcode: call-site rcs are the primary
566
+ // code, so SQLITE_CONSTRAINT_UNIQUE/NOTNULL/... would be lost.
567
+ const ext = ffiErrcode(this._db);
568
+ throw new SqliteError(sqliteCodeName(ext !== 0 ? ext : rc), msg);
569
+ }
570
+ _untrack(stmt) {
571
+ const arr = this._stmts;
572
+ for (let i = 0; i < arr.length; i++) {
573
+ if (arr[i] === stmt) {
574
+ arr.splice(i, 1);
575
+ return;
576
+ }
577
+ }
578
+ }
579
+ _prepareCore(sql, schema) {
580
+ this._checkOpen();
581
+ const st = ffiPrepare(this._db, sql);
582
+ if (Number(st) === 0) {
583
+ this._throwDb(ffiErrcode(this._db), "prepare failed");
584
+ }
585
+ const stmt = new StmtCore(this, st, sql, schema);
586
+ this._stmts.push(stmt);
587
+ return stmt;
588
+ }
589
+ exec(sql) {
590
+ this._checkOpen();
591
+ const rc = ffiExec(this._db, sql);
592
+ if (rc !== 0) {
593
+ this._throwDb(rc, "exec failed");
594
+ }
595
+ }
596
+ execRaw(sql) {
597
+ this.exec(sql);
598
+ }
599
+ pragma(sql) {
600
+ const stmt = this._prepareCore("PRAGMA " + sql, null);
601
+ const rows = stmt.allRaw([]);
602
+ stmt.finalize();
603
+ const out = [];
604
+ for (let i = 0; i < rows.length; i++) {
605
+ out.push(rows[i]);
606
+ }
607
+ return { rows: out };
608
+ }
609
+ transaction() {
610
+ this._checkOpen();
611
+ if (this.inTransaction) {
612
+ const name = nextSavepoint();
613
+ const rc = ffiSavepoint(this._db, name, SP_SAVE);
614
+ if (rc !== 0) {
615
+ this._throwDb(rc, "savepoint failed");
616
+ }
617
+ const parent = this._txs.length > 0 ? this._txs[this._txs.length - 1] : null;
618
+ const tx = new Transaction(this, parent, name);
619
+ this._txs.push(tx);
620
+ return tx;
621
+ }
622
+ const rc = ffiBegin(this._db);
623
+ if (rc !== 0) {
624
+ this._throwDb(rc, "begin failed");
625
+ }
626
+ const tx = new Transaction(this, null, null);
627
+ this._txs.push(tx);
628
+ return tx;
629
+ }
630
+ prepare(sql, schema) {
631
+ return this._prepareCore(sql, schema);
632
+ }
633
+ prepareRaw(sql) {
634
+ return this._prepareCore(sql, null);
635
+ }
636
+ lastInsertRowid() {
637
+ this._checkOpen();
638
+ return Number(ffiLastInsertRowid(this._db));
639
+ }
640
+ changes() {
641
+ this._checkOpen();
642
+ return ffiChanges(this._db);
643
+ }
644
+ close() {
645
+ if (this._closed) {
646
+ return;
647
+ }
648
+ const stmts = this._stmts;
649
+ for (let i = stmts.length - 1; i >= 0; i--) {
650
+ stmts[i].finalize();
651
+ }
652
+ this._closed = true;
653
+ ffiClose(this._db);
654
+ }
655
+ [Symbol.dispose]() {
656
+ this.close();
657
+ }
658
+ }
659
+ export class Transaction {
660
+ _db;
661
+ _parent;
662
+ _savepoint;
663
+ _done;
664
+ _failed;
665
+ constructor(db, parent, savepoint) {
666
+ this._db = db;
667
+ this._parent = parent;
668
+ this._savepoint = savepoint;
669
+ this._done = false;
670
+ this._failed = false;
671
+ }
672
+ get database() {
673
+ return this._db;
674
+ }
675
+ _checkLive() {
676
+ if (this._done) {
677
+ throw new SqliteError("SQLITE_MISUSE", "transaction is already finished");
678
+ }
679
+ }
680
+ _popSelf() {
681
+ const txs = this._db._txs;
682
+ for (let i = txs.length - 1; i >= 0; i--) {
683
+ if (txs[i] === this) {
684
+ txs.splice(i, 1);
685
+ return;
686
+ }
687
+ }
688
+ }
689
+ exec(sql) {
690
+ this._checkLive();
691
+ this._db.exec(sql);
692
+ }
693
+ execRaw(sql) {
694
+ this.exec(sql);
695
+ }
696
+ prepare(sql, schema) {
697
+ this._checkLive();
698
+ return this._db._prepareCore(sql, schema);
699
+ }
700
+ prepareRaw(sql) {
701
+ this._checkLive();
702
+ return this._db._prepareCore(sql, null);
703
+ }
704
+ savepoint(name) {
705
+ this._checkLive();
706
+ const rc = ffiSavepoint(this._db._db, name, SP_SAVE);
707
+ if (rc !== 0) {
708
+ this._db._throwDb(rc, "savepoint failed");
709
+ }
710
+ const tx = new Transaction(this._db, this, name);
711
+ this._db._txs.push(tx);
712
+ return tx;
713
+ }
714
+ commit() {
715
+ this._checkLive();
716
+ if (this._savepoint !== null) {
717
+ const rc = ffiSavepoint(this._db._db, this._savepoint, SP_RELEASE);
718
+ if (rc !== 0) {
719
+ this._db._throwDb(rc, "release savepoint failed");
720
+ }
721
+ }
722
+ else {
723
+ const rc = ffiCommit(this._db._db);
724
+ if (rc !== 0) {
725
+ this._db._throwDb(rc, "commit failed");
726
+ }
727
+ }
728
+ this._done = true;
729
+ this._popSelf();
730
+ }
731
+ rollback() {
732
+ this._checkLive();
733
+ if (this._savepoint !== null) {
734
+ const rc = ffiSavepoint(this._db._db, this._savepoint, SP_ROLLBACK);
735
+ if (rc !== 0) {
736
+ this._db._throwDb(rc, "rollback to savepoint failed");
737
+ }
738
+ }
739
+ else {
740
+ const rc = ffiRollback(this._db._db);
741
+ if (rc !== 0) {
742
+ this._db._throwDb(rc, "rollback failed");
743
+ }
744
+ }
745
+ this._done = true;
746
+ this._popSelf();
747
+ }
748
+ [Symbol.dispose]() {
749
+ if (this._done) {
750
+ return;
751
+ }
752
+ // A SqliteError thrown inside the transaction marks `_failed`;
753
+ // dispose can't see user-level throws, so clean exits commit and
754
+ // error exits roll back — documented in docs/subset-quirks.md.
755
+ if (this._failed) {
756
+ this.rollback();
757
+ return;
758
+ }
759
+ this.commit();
760
+ }
761
+ }