@nextlyhq/adapter-sqlite 0.0.2-alpha.67 → 0.0.2-alpha.69
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 +5 -5
- package/dist/index.cjs +871 -840
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +277 -313
- package/dist/index.d.ts +277 -313
- package/dist/index.mjs +869 -837
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -16
package/dist/index.cjs
CHANGED
|
@@ -1,865 +1,896 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
var sqliteCore = require('drizzle-orm/sqlite-core');
|
|
9
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let node_async_hooks = require("node:async_hooks");
|
|
3
|
+
let _nextlyhq_adapter_drizzle = require("@nextlyhq/adapter-drizzle");
|
|
4
|
+
let _nextlyhq_adapter_drizzle_types = require("@nextlyhq/adapter-drizzle/types");
|
|
5
|
+
let _nextlyhq_adapter_drizzle_version_check = require("@nextlyhq/adapter-drizzle/version-check");
|
|
6
|
+
let drizzle_orm_better_sqlite3 = require("drizzle-orm/better-sqlite3");
|
|
7
|
+
let drizzle_orm_sqlite_core = require("drizzle-orm/sqlite-core");
|
|
10
8
|
// src/index.ts
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
9
|
+
/**
|
|
10
|
+
* @nextlyhq/adapter-sqlite
|
|
11
|
+
*
|
|
12
|
+
* SQLite database adapter for Nextly.
|
|
13
|
+
* Extends DrizzleAdapter from @nextlyhq/adapter-drizzle to provide SQLite-specific functionality.
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* This adapter uses the better-sqlite3 package for database connectivity and provides:
|
|
17
|
+
* - Synchronous API wrapped for async interface compatibility
|
|
18
|
+
* - Transactions the adapter runs itself (BEGIN IMMEDIATE / COMMIT / ROLLBACK), with nested calls run as savepoints
|
|
19
|
+
* - CRUD operations with RETURNING clause support (SQLite 3.35+)
|
|
20
|
+
* - WAL mode for better concurrent read performance
|
|
21
|
+
* - In-memory and file-based database support
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import { createSqliteAdapter } from '@nextlyhq/adapter-sqlite';
|
|
26
|
+
*
|
|
27
|
+
* const adapter = createSqliteAdapter({
|
|
28
|
+
* url: 'file:./data/app.db',
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* await adapter.connect();
|
|
32
|
+
*
|
|
33
|
+
* // Query data
|
|
34
|
+
* const users = await adapter.select('users', {
|
|
35
|
+
* where: { and: [{ column: 'status', op: '=', value: 'active' }] },
|
|
36
|
+
* limit: 10,
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* await adapter.disconnect();
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @packageDocumentation
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* Package version
|
|
46
|
+
*/
|
|
47
|
+
const VERSION = "0.1.0";
|
|
48
|
+
/**
|
|
49
|
+
* SQLite error codes mapping to DatabaseErrorKind.
|
|
50
|
+
*
|
|
51
|
+
* @see https://sqlite.org/rescode.html
|
|
52
|
+
*/
|
|
53
|
+
const SQLITE_ERROR_CODES = {
|
|
54
|
+
SQLITE_CONSTRAINT: "constraint",
|
|
55
|
+
SQLITE_CONSTRAINT_UNIQUE: "unique_violation",
|
|
56
|
+
SQLITE_CONSTRAINT_PRIMARYKEY: "unique_violation",
|
|
57
|
+
SQLITE_CONSTRAINT_FOREIGNKEY: "foreign_key_violation",
|
|
58
|
+
SQLITE_CONSTRAINT_NOTNULL: "not_null_violation",
|
|
59
|
+
SQLITE_CONSTRAINT_CHECK: "check_violation",
|
|
60
|
+
SQLITE_BUSY: "timeout",
|
|
61
|
+
SQLITE_LOCKED: "timeout",
|
|
62
|
+
SQLITE_CANTOPEN: "connection",
|
|
63
|
+
SQLITE_NOTADB: "connection",
|
|
64
|
+
SQLITE_CORRUPT: "connection",
|
|
65
|
+
SQLITE_ERROR: "query",
|
|
66
|
+
SQLITE_MISUSE: "query",
|
|
67
|
+
SQLITE_RANGE: "query"
|
|
31
68
|
};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Default configuration values.
|
|
71
|
+
*/
|
|
72
|
+
const DEFAULT_CONFIG = {
|
|
73
|
+
busyTimeout: 5e3,
|
|
74
|
+
wal: true,
|
|
75
|
+
foreignKeys: true,
|
|
76
|
+
connectionTimeoutMs: 15e3
|
|
36
77
|
};
|
|
78
|
+
/**
|
|
79
|
+
* Converts a JavaScript value to a type that better-sqlite3 can bind.
|
|
80
|
+
* better-sqlite3 only accepts: number, string, bigint, Buffer, null.
|
|
81
|
+
*
|
|
82
|
+
* These conversions have to match what the schema declares, because the rows
|
|
83
|
+
* they write are read back through it. Every column here that takes a `Date`
|
|
84
|
+
* is `integer({ mode: "timestamp" })` — the system columns and user date
|
|
85
|
+
* fields in `runtime-schema-generator.ts`, and the core tables, whose date
|
|
86
|
+
* columns are all INTEGER. So a date is bound as unix seconds, which is what
|
|
87
|
+
* that mode reads.
|
|
88
|
+
*/
|
|
37
89
|
function sanitizeSqliteValue(v) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
90
|
+
if (v === void 0) return null;
|
|
91
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
92
|
+
if (Buffer.isBuffer(v) || ArrayBuffer.isView(v)) return v;
|
|
93
|
+
if (v instanceof Date) return Math.floor(v.getTime() / 1e3);
|
|
94
|
+
if (v !== null && typeof v === "object") return JSON.stringify(v);
|
|
95
|
+
return v;
|
|
44
96
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
const mappedRecords = data.map(
|
|
716
|
-
(r) => this.mapKeysToSqlColumns(tableObj, r)
|
|
717
|
-
);
|
|
718
|
-
const columns = Object.keys(mappedRecords[0]);
|
|
719
|
-
const allValues = [];
|
|
720
|
-
const valuesClauses = [];
|
|
721
|
-
for (const record of mappedRecords) {
|
|
722
|
-
const placeholders = [];
|
|
723
|
-
for (const col of columns) {
|
|
724
|
-
allValues.push(sanitizeSqliteValue(record[col]));
|
|
725
|
-
placeholders.push("?");
|
|
726
|
-
}
|
|
727
|
-
valuesClauses.push(`(${placeholders.join(", ")})`);
|
|
728
|
-
}
|
|
729
|
-
let sql = `INSERT INTO ${this.escapeIdentifier(table)} (${columns.map((c) => this.escapeIdentifier(c)).join(", ")}) VALUES ${valuesClauses.join(", ")}`;
|
|
730
|
-
const ret = options?.returning;
|
|
731
|
-
const returningEmpty = Array.isArray(ret) && ret.length === 0;
|
|
732
|
-
if (!returningEmpty) {
|
|
733
|
-
const returning = !ret || ret === "*" ? "*" : this.mapColumnNamesToSql(this.getTableObject(table), ret).map((col) => this.escapeIdentifier(col)).join(", ");
|
|
734
|
-
sql += ` RETURNING ${returning}`;
|
|
735
|
-
}
|
|
736
|
-
const stmt = db.prepare(sql);
|
|
737
|
-
if (returningEmpty) {
|
|
738
|
-
stmt.run(...allValues);
|
|
739
|
-
return [];
|
|
740
|
-
}
|
|
741
|
-
return stmt.all(...allValues).map(
|
|
742
|
-
(r) => this.mapRowFromRawSql(tableObj, r)
|
|
743
|
-
);
|
|
744
|
-
},
|
|
745
|
-
// Adapter-built, as `insert` above is; `transactionUpdate` says why.
|
|
746
|
-
// A column the model does not declare binds as every other value on
|
|
747
|
-
// this path does, through `sanitizeSqliteValue`. `all` only when the
|
|
748
|
-
// statement carries RETURNING: better-sqlite3 throws on `all` for a
|
|
749
|
-
// statement that returns no rows, and on `run` for one that does.
|
|
750
|
-
update: this.transactionUpdate(
|
|
751
|
-
txDb,
|
|
752
|
-
(statement, returnsRows) => returnsRows ? txDb().all(statement) : (txDb().run(statement), void 0),
|
|
753
|
-
sanitizeSqliteValue
|
|
754
|
-
),
|
|
755
|
-
...this.createTransactionForwarders(txDb),
|
|
756
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
757
|
-
savepoint: async (name) => {
|
|
758
|
-
db.exec(`SAVEPOINT ${this.escapeIdentifier(name)}`);
|
|
759
|
-
},
|
|
760
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
761
|
-
rollbackToSavepoint: async (name) => {
|
|
762
|
-
db.exec(`ROLLBACK TO SAVEPOINT ${this.escapeIdentifier(name)}`);
|
|
763
|
-
},
|
|
764
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
765
|
-
releaseSavepoint: async (name) => {
|
|
766
|
-
db.exec(`RELEASE SAVEPOINT ${this.escapeIdentifier(name)}`);
|
|
767
|
-
}
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
/**
|
|
771
|
-
* Classifies a SQLite error into a DatabaseError.
|
|
772
|
-
*
|
|
773
|
-
* @param error - Original error from better-sqlite3
|
|
774
|
-
* @param sql - SQL statement that caused the error (optional)
|
|
775
|
-
* @returns DatabaseError with proper classification
|
|
776
|
-
*/
|
|
777
|
-
// fallow-ignore-next-line complexity
|
|
778
|
-
classifyError(error, sql) {
|
|
779
|
-
if (types.isDatabaseError(error)) return error;
|
|
780
|
-
const sqliteError = this.driverErrorIn(
|
|
781
|
-
error,
|
|
782
|
-
(candidate) => typeof candidate.code === "string" && candidate.code !== ""
|
|
783
|
-
);
|
|
784
|
-
let kind = "unknown";
|
|
785
|
-
if (sqliteError.code) {
|
|
786
|
-
kind = SQLITE_ERROR_CODES[sqliteError.code] || "unknown";
|
|
787
|
-
} else if (sqliteError.message) {
|
|
788
|
-
const msg = sqliteError.message.toUpperCase();
|
|
789
|
-
if (msg.includes("UNIQUE CONSTRAINT")) {
|
|
790
|
-
kind = "unique_violation";
|
|
791
|
-
} else if (msg.includes("FOREIGN KEY CONSTRAINT")) {
|
|
792
|
-
kind = "foreign_key_violation";
|
|
793
|
-
} else if (msg.includes("NOT NULL CONSTRAINT")) {
|
|
794
|
-
kind = "not_null_violation";
|
|
795
|
-
} else if (msg.includes("CHECK CONSTRAINT")) {
|
|
796
|
-
kind = "check_violation";
|
|
797
|
-
} else if (msg.includes("BUSY") || msg.includes("LOCKED")) {
|
|
798
|
-
kind = "timeout";
|
|
799
|
-
} else if (msg.includes("SQLITE_CANTOPEN") || msg.includes("UNABLE TO OPEN")) {
|
|
800
|
-
kind = "connection";
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
let message = sqliteError.message ?? String(error);
|
|
804
|
-
if (sql && kind === "query") {
|
|
805
|
-
message = `Query failed: ${message}`;
|
|
806
|
-
}
|
|
807
|
-
return types.createDatabaseError({
|
|
808
|
-
kind,
|
|
809
|
-
message,
|
|
810
|
-
code: sqliteError.code,
|
|
811
|
-
cause: error instanceof Error ? error : void 0
|
|
812
|
-
});
|
|
813
|
-
}
|
|
97
|
+
/**
|
|
98
|
+
* SQLite database adapter for Nextly.
|
|
99
|
+
*
|
|
100
|
+
* Extends the base DrizzleAdapter to provide SQLite-specific functionality
|
|
101
|
+
* using the better-sqlite3 package.
|
|
102
|
+
*
|
|
103
|
+
* @remarks
|
|
104
|
+
* SQLite has some differences from PostgreSQL:
|
|
105
|
+
* - Synchronous API (wrapped for async compatibility)
|
|
106
|
+
* - No connection pooling (single file/connection)
|
|
107
|
+
* - RETURNING clause supported (SQLite 3.35+)
|
|
108
|
+
* - Savepoints supported via nested transactions
|
|
109
|
+
* - No native ILIKE (uses LOWER() LIKE workaround)
|
|
110
|
+
* - JSON support (not JSONB)
|
|
111
|
+
* - No array types
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```typescript
|
|
115
|
+
* const adapter = new SqliteAdapter({
|
|
116
|
+
* url: 'file:./data.db',
|
|
117
|
+
* });
|
|
118
|
+
*
|
|
119
|
+
* await adapter.connect();
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
var SqliteAdapter = class extends _nextlyhq_adapter_drizzle.DrizzleAdapter {
|
|
123
|
+
drizzleByRelations = /* @__PURE__ */ new WeakMap();
|
|
124
|
+
drizzleBare;
|
|
125
|
+
/**
|
|
126
|
+
* The database dialect - always 'sqlite' for this adapter.
|
|
127
|
+
*/
|
|
128
|
+
dialect = "sqlite";
|
|
129
|
+
/**
|
|
130
|
+
* Adapter configuration.
|
|
131
|
+
*/
|
|
132
|
+
config;
|
|
133
|
+
/**
|
|
134
|
+
* better-sqlite3 Database instance.
|
|
135
|
+
*/
|
|
136
|
+
db = null;
|
|
137
|
+
/**
|
|
138
|
+
* Connection state flag.
|
|
139
|
+
*/
|
|
140
|
+
connected = false;
|
|
141
|
+
/**
|
|
142
|
+
* Serialization queue for concurrent `transaction()` calls.
|
|
143
|
+
*
|
|
144
|
+
* better-sqlite3 is synchronous, single-connection, and rejects nested
|
|
145
|
+
* `BEGIN` with "cannot start a transaction within a transaction".
|
|
146
|
+
* When two `await adapter.transaction(...)` calls overlap (e.g. a bulk
|
|
147
|
+
* mutation calling `Promise.allSettled` with N per-row updates), the
|
|
148
|
+
* second one tries to BEGIN while the first is still open and the
|
|
149
|
+
* driver throws.
|
|
150
|
+
*
|
|
151
|
+
* Postgres/MySQL avoid this by allocating a fresh client/connection
|
|
152
|
+
* per transaction from a pool — there is no equivalent for
|
|
153
|
+
* better-sqlite3, so we serialize at the JS layer instead. Every
|
|
154
|
+
* `transaction()` invocation chains onto this promise; only one
|
|
155
|
+
* BEGIN→COMMIT/ROLLBACK section runs at any moment, preserving
|
|
156
|
+
* write-isolation semantics that callers expect from a transactional
|
|
157
|
+
* adapter.
|
|
158
|
+
*
|
|
159
|
+
* Performance impact is the natural floor: SQLite already serializes
|
|
160
|
+
* writers at the file-lock level, so the queue removes failed-call
|
|
161
|
+
* surface area without changing the achievable concurrency.
|
|
162
|
+
*/
|
|
163
|
+
transactionQueue = Promise.resolve();
|
|
164
|
+
/**
|
|
165
|
+
* The transaction the current async chain is running inside, if any.
|
|
166
|
+
*
|
|
167
|
+
* A `transaction()` call made from INSIDE a transaction's work — a store
|
|
168
|
+
* writing on behalf of the caller, a plugin's write inside its own
|
|
169
|
+
* `ctx.db.transaction` — used to queue behind the very transaction that
|
|
170
|
+
* was waiting for it, and neither ever settled: that hung every later
|
|
171
|
+
* transaction on the instance too. Such a call now runs as a SAVEPOINT of
|
|
172
|
+
* the active transaction instead, so it commits or rolls back with it and
|
|
173
|
+
* its own failure undoes only its own part.
|
|
174
|
+
*/
|
|
175
|
+
transactionScope = new node_async_hooks.AsyncLocalStorage();
|
|
176
|
+
/**
|
|
177
|
+
* Creates a new SQLite adapter instance.
|
|
178
|
+
*
|
|
179
|
+
* @param config - Adapter configuration
|
|
180
|
+
*/
|
|
181
|
+
constructor(config) {
|
|
182
|
+
super();
|
|
183
|
+
this.config = config;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Connect to the SQLite database.
|
|
187
|
+
*
|
|
188
|
+
* @remarks
|
|
189
|
+
* This method initializes the database connection. For SQLite, this
|
|
190
|
+
* opens the database file or creates an in-memory database.
|
|
191
|
+
* Also configures WAL mode and foreign keys based on config.
|
|
192
|
+
*
|
|
193
|
+
* @throws {DatabaseError} If connection fails
|
|
194
|
+
*/
|
|
195
|
+
async connect() {
|
|
196
|
+
if (this.connected && this.db) return;
|
|
197
|
+
try {
|
|
198
|
+
const Database = (await import("better-sqlite3")).default;
|
|
199
|
+
let dbPath;
|
|
200
|
+
if (this.config.memory) dbPath = ":memory:";
|
|
201
|
+
else if (this.config.url) dbPath = this.config.url.replace(/^file:/, "");
|
|
202
|
+
else dbPath = ":memory:";
|
|
203
|
+
if (dbPath !== ":memory:" && !this.config.readonly) {
|
|
204
|
+
const fs = await import("node:fs");
|
|
205
|
+
const path = await import("node:path");
|
|
206
|
+
const dir = path.dirname(path.resolve(dbPath));
|
|
207
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
208
|
+
}
|
|
209
|
+
this.db = new Database(dbPath, {
|
|
210
|
+
readonly: this.config.readonly ?? false,
|
|
211
|
+
timeout: this.config.busyTimeout ?? DEFAULT_CONFIG.busyTimeout
|
|
212
|
+
});
|
|
213
|
+
if ((this.config.wal ?? DEFAULT_CONFIG.wal) && dbPath !== ":memory:" && !this.config.readonly) this.db.pragma("journal_mode = WAL");
|
|
214
|
+
if (this.config.foreignKeys ?? DEFAULT_CONFIG.foreignKeys) this.db.pragma("foreign_keys = ON");
|
|
215
|
+
await (0, _nextlyhq_adapter_drizzle_version_check.checkDialectVersion)(this.db, "sqlite");
|
|
216
|
+
this.connected = true;
|
|
217
|
+
if (this.config.logger?.info) this.config.logger.info("SQLite connection established", {
|
|
218
|
+
url: dbPath === ":memory:" ? "in-memory" : dbPath,
|
|
219
|
+
wal: this.config.wal ?? DEFAULT_CONFIG.wal,
|
|
220
|
+
foreignKeys: this.config.foreignKeys ?? DEFAULT_CONFIG.foreignKeys
|
|
221
|
+
});
|
|
222
|
+
} catch (error) {
|
|
223
|
+
if (this.db) {
|
|
224
|
+
try {
|
|
225
|
+
this.db.close();
|
|
226
|
+
} catch {}
|
|
227
|
+
this.db = null;
|
|
228
|
+
}
|
|
229
|
+
throw this.classifyError(error);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Disconnect from the SQLite database.
|
|
234
|
+
*
|
|
235
|
+
* @remarks
|
|
236
|
+
* This method closes the database connection and releases resources.
|
|
237
|
+
*/
|
|
238
|
+
async disconnect() {
|
|
239
|
+
this.drizzleBare = void 0;
|
|
240
|
+
this.drizzleByRelations = /* @__PURE__ */ new WeakMap();
|
|
241
|
+
if (!this.db) return;
|
|
242
|
+
try {
|
|
243
|
+
this.db.close();
|
|
244
|
+
if (this.config.logger?.info) this.config.logger.info("SQLite connection closed");
|
|
245
|
+
} finally {
|
|
246
|
+
this.db = null;
|
|
247
|
+
this.connected = false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Check if connected to the database.
|
|
252
|
+
*/
|
|
253
|
+
isConnected() {
|
|
254
|
+
return this.connected && this.db !== null;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Get connection pool statistics.
|
|
258
|
+
*
|
|
259
|
+
* @remarks
|
|
260
|
+
* SQLite doesn't use connection pooling, so this returns null.
|
|
261
|
+
* The database is single-file with a single connection.
|
|
262
|
+
*/
|
|
263
|
+
getPoolStats() {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Execute a raw SQL query.
|
|
268
|
+
*
|
|
269
|
+
* @param sql - SQL query string with $1, $2 placeholders (converted to ? for SQLite)
|
|
270
|
+
* @param params - Query parameters
|
|
271
|
+
* @returns Query results
|
|
272
|
+
*
|
|
273
|
+
* @throws {DatabaseError} If query execution fails
|
|
274
|
+
*/
|
|
275
|
+
async executeQuery(sql, params = []) {
|
|
276
|
+
const db = this.ensureDb();
|
|
277
|
+
const startTime = Date.now();
|
|
278
|
+
try {
|
|
279
|
+
const convertedSql = this.convertPlaceholders(sql);
|
|
280
|
+
const trimmedSql = convertedSql.trim().toUpperCase();
|
|
281
|
+
const isPragmaQuery = trimmedSql.startsWith("PRAGMA") && !trimmedSql.includes("=");
|
|
282
|
+
const isSelect = trimmedSql.startsWith("SELECT") || isPragmaQuery || trimmedSql.startsWith("WITH");
|
|
283
|
+
const hasReturning = trimmedSql.includes("RETURNING");
|
|
284
|
+
let result;
|
|
285
|
+
const sanitizedParams = params.map(sanitizeSqliteValue);
|
|
286
|
+
if (isSelect || hasReturning) result = db.prepare(convertedSql).all(...sanitizedParams);
|
|
287
|
+
else {
|
|
288
|
+
const runResult = db.prepare(convertedSql).run(...sanitizedParams);
|
|
289
|
+
result = [{
|
|
290
|
+
changes: runResult.changes,
|
|
291
|
+
lastInsertRowid: runResult.lastInsertRowid
|
|
292
|
+
}];
|
|
293
|
+
}
|
|
294
|
+
if (this.config.logger?.query) {
|
|
295
|
+
const durationMs = Date.now() - startTime;
|
|
296
|
+
this.config.logger.query(convertedSql, params, durationMs);
|
|
297
|
+
}
|
|
298
|
+
return result;
|
|
299
|
+
} catch (error) {
|
|
300
|
+
throw this.classifyError(error, sql);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Execute work within a transaction.
|
|
305
|
+
*
|
|
306
|
+
* @param work - Function containing transactional operations
|
|
307
|
+
* @param options - Transaction options (isolation level not fully supported in SQLite)
|
|
308
|
+
* @returns Result of the work function
|
|
309
|
+
*
|
|
310
|
+
* @remarks
|
|
311
|
+
* The adapter manages the transaction itself, because better-sqlite3's
|
|
312
|
+
* `db.transaction()` does not take async work: it runs `BEGIN IMMEDIATE`,
|
|
313
|
+
* then `COMMIT` when `work` resolves or `ROLLBACK` when it throws.
|
|
314
|
+
* - Top-level calls run one at a time.
|
|
315
|
+
* - A call made inside an open transaction's work joins it as a savepoint
|
|
316
|
+
* (`SAVEPOINT` / `RELEASE` / `ROLLBACK TO`).
|
|
317
|
+
* - Nested calls of one scope run one at a time.
|
|
318
|
+
* - A call made later from a savepoint that has already released joins the
|
|
319
|
+
* innermost scope still open.
|
|
320
|
+
* - Nested work started and not awaited finishes before the enclosing scope
|
|
321
|
+
* commits or rolls back.
|
|
322
|
+
*
|
|
323
|
+
* Note: SQLite uses DEFERRED, IMMEDIATE, or EXCLUSIVE transaction modes
|
|
324
|
+
* rather than isolation levels. This adapter uses IMMEDIATE.
|
|
325
|
+
*/
|
|
326
|
+
/** How long a transaction waits for the connection: `pool.connectionTimeoutMs`. */
|
|
327
|
+
transactionWaitMs() {
|
|
328
|
+
return this.config.pool?.connectionTimeoutMs ?? DEFAULT_CONFIG.connectionTimeoutMs;
|
|
329
|
+
}
|
|
330
|
+
async transaction(work, _options) {
|
|
331
|
+
const own = this.transactionScope.getStore();
|
|
332
|
+
const parent = this.nestingScope();
|
|
333
|
+
if (own && parent !== own) own.writesAfterClosing += 1;
|
|
334
|
+
if (parent) return enqueueIn(parent, () => this.runSavepoint(work, parent));
|
|
335
|
+
const run = async () => this.runTransaction(work);
|
|
336
|
+
const previous = this.transactionQueue;
|
|
337
|
+
const next = waitForTurn(previous, this.transactionWaitMs()).then(run);
|
|
338
|
+
this.transactionQueue = previous.then(() => next).catch(() => void 0);
|
|
339
|
+
const { result, effects } = await next;
|
|
340
|
+
await this.runHeldEffects(effects);
|
|
341
|
+
return result;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Hold `effect` until the transaction enclosing this async context commits,
|
|
345
|
+
* or run it now when none does.
|
|
346
|
+
*
|
|
347
|
+
* The effect belongs to the scope this async context runs in, even one
|
|
348
|
+
* that has already released or rolled back: a service whose own
|
|
349
|
+
* transaction nested as a savepoint registers from its caller's scope once
|
|
350
|
+
* that savepoint has released, and the change is undoable for as long as
|
|
351
|
+
* any scope around it is. The outermost transaction runs the effect once
|
|
352
|
+
* its COMMIT succeeds, unless a scope between the effect's scope and the
|
|
353
|
+
* outermost transaction rolled back, and drops every effect on ROLLBACK or
|
|
354
|
+
* a failed COMMIT. A context whose outermost transaction has already
|
|
355
|
+
* finished is outside any transaction, so its effect runs now, unless what
|
|
356
|
+
* it announces was undone and it has run no transaction since.
|
|
357
|
+
*/
|
|
358
|
+
async afterCommit(effect, onDeferredFailure) {
|
|
359
|
+
const scope = this.transactionScope.getStore();
|
|
360
|
+
if (!scope) {
|
|
361
|
+
await effect();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
const root = outermost(scope);
|
|
365
|
+
if (!root.finished) {
|
|
366
|
+
root.held.push({
|
|
367
|
+
effect: this.inRegisteringContext(effect),
|
|
368
|
+
onDeferredFailure,
|
|
369
|
+
scope,
|
|
370
|
+
writesBefore: scope.writesAfterClosing
|
|
371
|
+
});
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (!undone(scope, scope.writesAfterClosing)) await effect();
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* `effect`, bound to the async context it is registered from.
|
|
378
|
+
*
|
|
379
|
+
* A held effect runs after the outermost COMMIT, from the code that ran the
|
|
380
|
+
* outermost transaction, so without this it sees that code's context rather
|
|
381
|
+
* than its own: whatever its caller pinned around the write is gone. That
|
|
382
|
+
* includes who is calling. A scope pinned for a scoped API key lost there
|
|
383
|
+
* leaves a hook's nested call judged by the key's owner instead of the key,
|
|
384
|
+
* and a person's frame lost there leaves the hook told of nobody.
|
|
385
|
+
*
|
|
386
|
+
* The adapter's own transaction scope is the one store it leaves: the
|
|
387
|
+
* effect runs once that transaction has finished, outside it, as an effect
|
|
388
|
+
* held to the outermost commit always has.
|
|
389
|
+
*/
|
|
390
|
+
inRegisteringContext(effect) {
|
|
391
|
+
return node_async_hooks.AsyncResource.bind(() => this.transactionScope.exit(effect));
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Run the effects a committed transaction held, in order. A failure is
|
|
395
|
+
* reported, never thrown: the change is durable, and reporting the
|
|
396
|
+
* transaction as failed would invite a retry of something already done.
|
|
397
|
+
*/
|
|
398
|
+
async runHeldEffects(effects) {
|
|
399
|
+
for (const { effect, onDeferredFailure } of effects) try {
|
|
400
|
+
await effect();
|
|
401
|
+
} catch (error) {
|
|
402
|
+
this.reportHeldEffectFailure(error, onDeferredFailure);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/** Hand a held effect's failure to its reporter, or to the adapter's log. */
|
|
406
|
+
reportHeldEffectFailure(error, onDeferredFailure) {
|
|
407
|
+
try {
|
|
408
|
+
if (onDeferredFailure) {
|
|
409
|
+
onDeferredFailure(error);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
} catch {}
|
|
413
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
414
|
+
if (this.config.logger?.error) this.config.logger.error(failure, { phase: "after-commit" });
|
|
415
|
+
else console.error("An effect held until commit failed:", failure);
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Whether a `transaction()` call made here would join an open transaction
|
|
419
|
+
* as a savepoint. On SQLite it would: the connection is the only one, so a
|
|
420
|
+
* transaction open on it encloses whatever the async chain that opened it
|
|
421
|
+
* starts.
|
|
422
|
+
*/
|
|
423
|
+
inTransaction() {
|
|
424
|
+
return this.nestingScope() !== void 0;
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Whether the calling async flow is inside a transaction that has not
|
|
428
|
+
* finished, judged from the transaction this flow was started inside,
|
|
429
|
+
* never from the connection: another request's open transaction answers
|
|
430
|
+
* false here, where its own `transaction()` queues behind that one.
|
|
431
|
+
*
|
|
432
|
+
* A flow started inside a transaction, even inside a savepoint that has
|
|
433
|
+
* since released, stays inside it until its outermost transaction has
|
|
434
|
+
* committed or rolled back: until then whatever it writes on the one
|
|
435
|
+
* connection lands in that transaction.
|
|
436
|
+
*/
|
|
437
|
+
transactionOpenInFlow() {
|
|
438
|
+
const own = this.transactionScope.getStore();
|
|
439
|
+
return own !== void 0 && !outermost(own).finished;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* The scope a `transaction()` call made here nests in, or none when it
|
|
443
|
+
* opens a transaction of its own.
|
|
444
|
+
*
|
|
445
|
+
* A call made later from inside a savepoint that has since released runs
|
|
446
|
+
* inside the innermost scope open on the connection. Queued on an enclosing
|
|
447
|
+
* scope, or on the instance, it would wait behind the scope running there,
|
|
448
|
+
* which may be the one awaiting it. Savepoints on one connection form a
|
|
449
|
+
* stack, so whatever opens now is inside that innermost scope anyway; when
|
|
450
|
+
* none is open the call queues on the instance.
|
|
451
|
+
*
|
|
452
|
+
* The innermost open scope need not be related to the call. When it is a
|
|
453
|
+
* sibling savepoint that later rolls back, the call's write rolls back with
|
|
454
|
+
* it, although the call itself already resolved. Waiting for an unrelated
|
|
455
|
+
* scope instead could wait on the very scope that awaits the call.
|
|
456
|
+
*/
|
|
457
|
+
nestingScope() {
|
|
458
|
+
const own = this.transactionScope.getStore();
|
|
459
|
+
return own?.active ? own : own?.open.at(-1);
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Run `work` as a savepoint of the active transaction: released when it
|
|
463
|
+
* resolves, rolled back to when it throws.
|
|
464
|
+
*/
|
|
465
|
+
async runSavepoint(work, parent) {
|
|
466
|
+
const db = this.ensureDb();
|
|
467
|
+
const name = `nextly_sp_${parent.depth + 1}`;
|
|
468
|
+
try {
|
|
469
|
+
const ctx = this.createTransactionContext(db);
|
|
470
|
+
db.exec(`SAVEPOINT ${name}`);
|
|
471
|
+
const scope = openScope(parent);
|
|
472
|
+
try {
|
|
473
|
+
const result = await this.transactionScope.run(scope, () => work(ctx));
|
|
474
|
+
await settleNested(scope);
|
|
475
|
+
db.exec(`RELEASE ${name}`);
|
|
476
|
+
return result;
|
|
477
|
+
} catch (error) {
|
|
478
|
+
await settleNested(scope);
|
|
479
|
+
scope.rolledBack = true;
|
|
480
|
+
try {
|
|
481
|
+
db.exec(`ROLLBACK TO ${name}`);
|
|
482
|
+
db.exec(`RELEASE ${name}`);
|
|
483
|
+
} catch {}
|
|
484
|
+
throw error;
|
|
485
|
+
}
|
|
486
|
+
} catch (error) {
|
|
487
|
+
if ((0, _nextlyhq_adapter_drizzle_types.isApplicationError)(error)) throw error;
|
|
488
|
+
throw this.classifyError(error);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Inner transaction body. Kept private so callers always go through
|
|
493
|
+
* the serialized `transaction()` queue above and the BEGIN/COMMIT
|
|
494
|
+
* pair is never invoked outside of it.
|
|
495
|
+
*/
|
|
496
|
+
async runTransaction(work) {
|
|
497
|
+
const db = this.ensureDb();
|
|
498
|
+
const startTime = Date.now();
|
|
499
|
+
try {
|
|
500
|
+
const ctx = this.createTransactionContext(db);
|
|
501
|
+
db.exec("BEGIN IMMEDIATE");
|
|
502
|
+
const scope = openScope();
|
|
503
|
+
try {
|
|
504
|
+
const result = await this.transactionScope.run(scope, () => work(ctx));
|
|
505
|
+
await settleNested(scope);
|
|
506
|
+
db.exec("COMMIT");
|
|
507
|
+
scope.finished = true;
|
|
508
|
+
if (this.config.logger?.debug) {
|
|
509
|
+
const durationMs = Date.now() - startTime;
|
|
510
|
+
this.config.logger.debug("Transaction committed", { durationMs });
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
result,
|
|
514
|
+
effects: scope.held.filter((held) => !undone(held.scope, held.writesBefore))
|
|
515
|
+
};
|
|
516
|
+
} catch (error) {
|
|
517
|
+
await settleNested(scope);
|
|
518
|
+
scope.rolledBack = true;
|
|
519
|
+
scope.finished = true;
|
|
520
|
+
scope.held.length = 0;
|
|
521
|
+
try {
|
|
522
|
+
db.exec("ROLLBACK");
|
|
523
|
+
} catch {}
|
|
524
|
+
throw error;
|
|
525
|
+
}
|
|
526
|
+
} catch (error) {
|
|
527
|
+
if ((0, _nextlyhq_adapter_drizzle_types.isApplicationError)(error)) throw error;
|
|
528
|
+
throw this.classifyError(error);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Get SQLite database capabilities.
|
|
533
|
+
*
|
|
534
|
+
* @remarks
|
|
535
|
+
* SQLite capabilities:
|
|
536
|
+
* - JSON support (not JSONB)
|
|
537
|
+
* - No arrays
|
|
538
|
+
* - No native ILIKE
|
|
539
|
+
* - RETURNING clause (3.35+)
|
|
540
|
+
* - Savepoints supported
|
|
541
|
+
* - ON CONFLICT supported
|
|
542
|
+
*/
|
|
543
|
+
getCapabilities() {
|
|
544
|
+
return {
|
|
545
|
+
dialect: "sqlite",
|
|
546
|
+
supportsJsonb: false,
|
|
547
|
+
supportsJson: true,
|
|
548
|
+
supportsArrays: false,
|
|
549
|
+
supportsGeneratedColumns: true,
|
|
550
|
+
supportsFts: true,
|
|
551
|
+
supportsIlike: false,
|
|
552
|
+
supportsReturning: true,
|
|
553
|
+
supportsSavepoints: true,
|
|
554
|
+
supportsOnConflict: true,
|
|
555
|
+
maxParamsPerQuery: 999,
|
|
556
|
+
maxIdentifierLength: 128
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Override insertMany for bulk insert optimization.
|
|
561
|
+
*
|
|
562
|
+
* @remarks
|
|
563
|
+
* Uses a single multi-row INSERT statement for better performance.
|
|
564
|
+
*/
|
|
565
|
+
async insertMany(table, data, options) {
|
|
566
|
+
if (data.length === 0) return [];
|
|
567
|
+
const db = this.ensureDb();
|
|
568
|
+
if (data.length === 1) return [await this.insert(table, data[0], options)];
|
|
569
|
+
const columns = Object.keys(data[0]);
|
|
570
|
+
const params = [];
|
|
571
|
+
const valuesClauses = [];
|
|
572
|
+
for (const record of data) {
|
|
573
|
+
const placeholders = [];
|
|
574
|
+
for (const col of columns) {
|
|
575
|
+
params.push(sanitizeSqliteValue(record[col]));
|
|
576
|
+
placeholders.push("?");
|
|
577
|
+
}
|
|
578
|
+
valuesClauses.push(`(${placeholders.join(", ")})`);
|
|
579
|
+
}
|
|
580
|
+
const columnList = columns.map((col) => this.escapeIdentifier(col)).join(", ");
|
|
581
|
+
let sql = `INSERT INTO ${this.escapeIdentifier(table)} (${columnList}) VALUES ${valuesClauses.join(", ")}`;
|
|
582
|
+
if (options?.returning) {
|
|
583
|
+
const returning = options.returning === "*" ? "*" : options.returning.map((col) => this.escapeIdentifier(col)).join(", ");
|
|
584
|
+
sql += ` RETURNING ${returning}`;
|
|
585
|
+
} else sql += " RETURNING *";
|
|
586
|
+
try {
|
|
587
|
+
const rows = db.prepare(sql).all(...params);
|
|
588
|
+
const tableObj = this.getTableObject(table);
|
|
589
|
+
return rows.map((r) => this.mapRowFromRawSql(tableObj, r));
|
|
590
|
+
} catch (error) {
|
|
591
|
+
throw this.handleQueryError(error, "insertMany", table);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Ensures database is connected and returns it.
|
|
596
|
+
*
|
|
597
|
+
* @throws {DatabaseError} If not connected
|
|
598
|
+
*/
|
|
599
|
+
ensureDb() {
|
|
600
|
+
if (!this.db) throw (0, _nextlyhq_adapter_drizzle_types.createDatabaseError)({
|
|
601
|
+
kind: "connection",
|
|
602
|
+
message: "SqliteAdapter is not connected. Call connect() first."
|
|
603
|
+
});
|
|
604
|
+
return this.db;
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Return the typed Drizzle instance for SQLite.
|
|
608
|
+
* Guarded for server-only usage and requires an active connection.
|
|
609
|
+
*
|
|
610
|
+
* @param schema - Optional schema for relational queries (db.query.*)
|
|
611
|
+
* @returns Drizzle ORM instance wrapping the better-sqlite3 connection
|
|
612
|
+
* @throws {Error} If called in browser or not connected
|
|
613
|
+
*/
|
|
614
|
+
getDrizzle(relations) {
|
|
615
|
+
if (typeof window !== "undefined") throw new Error("getDrizzle() is server-only");
|
|
616
|
+
const db = this.ensureDb();
|
|
617
|
+
if (!relations) {
|
|
618
|
+
this.drizzleBare ??= (0, drizzle_orm_better_sqlite3.drizzle)({ client: db });
|
|
619
|
+
return this.drizzleBare;
|
|
620
|
+
}
|
|
621
|
+
let cached = this.drizzleByRelations.get(relations);
|
|
622
|
+
if (!cached) {
|
|
623
|
+
cached = (0, drizzle_orm_better_sqlite3.drizzle)({
|
|
624
|
+
client: db,
|
|
625
|
+
relations
|
|
626
|
+
});
|
|
627
|
+
this.drizzleByRelations.set(relations, cached);
|
|
628
|
+
}
|
|
629
|
+
return cached;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Convert $1, $2 placeholders to ? for better-sqlite3.
|
|
633
|
+
*
|
|
634
|
+
* @param sql - SQL with PostgreSQL-style placeholders
|
|
635
|
+
* @returns SQL with ? placeholders
|
|
636
|
+
*/
|
|
637
|
+
convertPlaceholders(sql) {
|
|
638
|
+
return sql.replace(/\$\d+/g, "?");
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Creates a TransactionContext for the given database connection.
|
|
642
|
+
*/
|
|
643
|
+
/** A table-level `primaryKey({ columns })`, read through the SQLite table config. */
|
|
644
|
+
compositePrimaryKey(tableObj) {
|
|
645
|
+
return (0, drizzle_orm_sqlite_core.getTableConfig)(tableObj).primaryKeys.flatMap((key) => key.columns);
|
|
646
|
+
}
|
|
647
|
+
createTransactionContext(db) {
|
|
648
|
+
const txHandles = this.transactionDrizzleHandles((relations) => relations ? (0, drizzle_orm_better_sqlite3.drizzle)({
|
|
649
|
+
client: db,
|
|
650
|
+
relations
|
|
651
|
+
}) : (0, drizzle_orm_better_sqlite3.drizzle)({ client: db }));
|
|
652
|
+
const txDb = txHandles.bare;
|
|
653
|
+
return {
|
|
654
|
+
drizzleWithRelations: txHandles.withRelations,
|
|
655
|
+
lockRow: () => Promise.resolve(),
|
|
656
|
+
runStatement: (statement) => {
|
|
657
|
+
txDb().run(statement);
|
|
658
|
+
return Promise.resolve();
|
|
659
|
+
},
|
|
660
|
+
queryStatement: (statement) => Promise.resolve(txDb().all(statement)),
|
|
661
|
+
execute: async (sql, params = []) => {
|
|
662
|
+
const convertedSql = this.convertPlaceholders(sql);
|
|
663
|
+
const trimmedSql = convertedSql.trim().toUpperCase();
|
|
664
|
+
const isSelect = trimmedSql.startsWith("SELECT") || trimmedSql.includes("RETURNING");
|
|
665
|
+
const sanitizedParams = params.map(sanitizeSqliteValue);
|
|
666
|
+
if (isSelect) return db.prepare(convertedSql).all(...sanitizedParams);
|
|
667
|
+
else {
|
|
668
|
+
const result = db.prepare(convertedSql).run(...sanitizedParams);
|
|
669
|
+
return [{
|
|
670
|
+
changes: result.changes,
|
|
671
|
+
lastInsertRowid: result.lastInsertRowid
|
|
672
|
+
}];
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
insert: async (table, data, options) => {
|
|
676
|
+
const mapped = this.mapKeysToSqlColumns(this.getTableObject(table), data);
|
|
677
|
+
const columns = Object.keys(mapped);
|
|
678
|
+
const values = Object.values(mapped).map(sanitizeSqliteValue);
|
|
679
|
+
const placeholders = values.map(() => "?").join(", ");
|
|
680
|
+
let sql = `INSERT INTO ${this.escapeIdentifier(table)} (${columns.map((c) => this.escapeIdentifier(c)).join(", ")}) VALUES (${placeholders})`;
|
|
681
|
+
const ret = options?.returning;
|
|
682
|
+
const returningEmpty = Array.isArray(ret) && ret.length === 0;
|
|
683
|
+
if (!returningEmpty) {
|
|
684
|
+
const returning = !ret || ret === "*" ? "*" : this.mapColumnNamesToSql(this.getTableObject(table), ret).map((col) => this.escapeIdentifier(col)).join(", ");
|
|
685
|
+
sql += ` RETURNING ${returning}`;
|
|
686
|
+
}
|
|
687
|
+
const stmt = db.prepare(sql);
|
|
688
|
+
if (returningEmpty) {
|
|
689
|
+
stmt.run(...values);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
const rows = stmt.all(...values);
|
|
693
|
+
return this.mapRowFromRawSql(this.getTableObject(table), rows[0]);
|
|
694
|
+
},
|
|
695
|
+
insertMany: async (table, data, options) => {
|
|
696
|
+
if (data.length === 0) return [];
|
|
697
|
+
const tableObj = this.getTableObject(table);
|
|
698
|
+
const mappedRecords = data.map((r) => this.mapKeysToSqlColumns(tableObj, r));
|
|
699
|
+
const columns = Object.keys(mappedRecords[0]);
|
|
700
|
+
const allValues = [];
|
|
701
|
+
const valuesClauses = [];
|
|
702
|
+
for (const record of mappedRecords) {
|
|
703
|
+
const placeholders = [];
|
|
704
|
+
for (const col of columns) {
|
|
705
|
+
allValues.push(sanitizeSqliteValue(record[col]));
|
|
706
|
+
placeholders.push("?");
|
|
707
|
+
}
|
|
708
|
+
valuesClauses.push(`(${placeholders.join(", ")})`);
|
|
709
|
+
}
|
|
710
|
+
let sql = `INSERT INTO ${this.escapeIdentifier(table)} (${columns.map((c) => this.escapeIdentifier(c)).join(", ")}) VALUES ${valuesClauses.join(", ")}`;
|
|
711
|
+
const ret = options?.returning;
|
|
712
|
+
const returningEmpty = Array.isArray(ret) && ret.length === 0;
|
|
713
|
+
if (!returningEmpty) {
|
|
714
|
+
const returning = !ret || ret === "*" ? "*" : this.mapColumnNamesToSql(this.getTableObject(table), ret).map((col) => this.escapeIdentifier(col)).join(", ");
|
|
715
|
+
sql += ` RETURNING ${returning}`;
|
|
716
|
+
}
|
|
717
|
+
const stmt = db.prepare(sql);
|
|
718
|
+
if (returningEmpty) {
|
|
719
|
+
stmt.run(...allValues);
|
|
720
|
+
return [];
|
|
721
|
+
}
|
|
722
|
+
return stmt.all(...allValues).map((r) => this.mapRowFromRawSql(tableObj, r));
|
|
723
|
+
},
|
|
724
|
+
update: this.transactionUpdate(txDb, (statement, returnsRows) => returnsRows ? txDb().all(statement) : (txDb().run(statement), void 0), sanitizeSqliteValue),
|
|
725
|
+
...this.createTransactionForwarders(txDb),
|
|
726
|
+
savepoint: async (name) => {
|
|
727
|
+
db.exec(`SAVEPOINT ${this.escapeIdentifier(name)}`);
|
|
728
|
+
},
|
|
729
|
+
rollbackToSavepoint: async (name) => {
|
|
730
|
+
db.exec(`ROLLBACK TO SAVEPOINT ${this.escapeIdentifier(name)}`);
|
|
731
|
+
},
|
|
732
|
+
releaseSavepoint: async (name) => {
|
|
733
|
+
db.exec(`RELEASE SAVEPOINT ${this.escapeIdentifier(name)}`);
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Classifies a SQLite error into a DatabaseError.
|
|
739
|
+
*
|
|
740
|
+
* @param error - Original error from better-sqlite3
|
|
741
|
+
* @param sql - SQL statement that caused the error (optional)
|
|
742
|
+
* @returns DatabaseError with proper classification
|
|
743
|
+
*/
|
|
744
|
+
classifyError(error, sql) {
|
|
745
|
+
if ((0, _nextlyhq_adapter_drizzle_types.isDatabaseError)(error)) return error;
|
|
746
|
+
const sqliteError = this.driverErrorIn(error, (candidate) => typeof candidate.code === "string" && candidate.code !== "");
|
|
747
|
+
let kind = "unknown";
|
|
748
|
+
if (sqliteError.code) kind = SQLITE_ERROR_CODES[sqliteError.code] || "unknown";
|
|
749
|
+
else if (sqliteError.message) {
|
|
750
|
+
const msg = sqliteError.message.toUpperCase();
|
|
751
|
+
if (msg.includes("UNIQUE CONSTRAINT")) kind = "unique_violation";
|
|
752
|
+
else if (msg.includes("FOREIGN KEY CONSTRAINT")) kind = "foreign_key_violation";
|
|
753
|
+
else if (msg.includes("NOT NULL CONSTRAINT")) kind = "not_null_violation";
|
|
754
|
+
else if (msg.includes("CHECK CONSTRAINT")) kind = "check_violation";
|
|
755
|
+
else if (msg.includes("BUSY") || msg.includes("LOCKED")) kind = "timeout";
|
|
756
|
+
else if (msg.includes("SQLITE_CANTOPEN") || msg.includes("UNABLE TO OPEN")) kind = "connection";
|
|
757
|
+
}
|
|
758
|
+
let message = sqliteError.message ?? String(error);
|
|
759
|
+
if (sql && kind === "query") message = `Query failed: ${message}`;
|
|
760
|
+
return (0, _nextlyhq_adapter_drizzle_types.createDatabaseError)({
|
|
761
|
+
kind,
|
|
762
|
+
message,
|
|
763
|
+
code: sqliteError.code,
|
|
764
|
+
cause: error instanceof Error ? error : void 0
|
|
765
|
+
});
|
|
766
|
+
}
|
|
814
767
|
};
|
|
768
|
+
/**
|
|
769
|
+
* Create a SQLite database adapter.
|
|
770
|
+
*
|
|
771
|
+
* @param config - SQLite adapter configuration
|
|
772
|
+
* @returns A new SqliteAdapter instance
|
|
773
|
+
*
|
|
774
|
+
* @example
|
|
775
|
+
* ```typescript
|
|
776
|
+
* // Simple usage with file path
|
|
777
|
+
* const adapter = createSqliteAdapter({
|
|
778
|
+
* url: 'file:./data.db',
|
|
779
|
+
* });
|
|
780
|
+
*
|
|
781
|
+
* // In-memory database
|
|
782
|
+
* const memAdapter = createSqliteAdapter({
|
|
783
|
+
* memory: true,
|
|
784
|
+
* });
|
|
785
|
+
*
|
|
786
|
+
* // Full configuration
|
|
787
|
+
* const adapter = createSqliteAdapter({
|
|
788
|
+
* url: 'file:./data.db',
|
|
789
|
+
* wal: true,
|
|
790
|
+
* foreignKeys: true,
|
|
791
|
+
* busyTimeout: 5000,
|
|
792
|
+
* logger: {
|
|
793
|
+
* query: (sql, params, duration) => console.log(`Query: ${sql}`),
|
|
794
|
+
* },
|
|
795
|
+
* });
|
|
796
|
+
*
|
|
797
|
+
* await adapter.connect();
|
|
798
|
+
* ```
|
|
799
|
+
*/
|
|
815
800
|
function createSqliteAdapter(config) {
|
|
816
|
-
|
|
801
|
+
return new SqliteAdapter(config);
|
|
817
802
|
}
|
|
803
|
+
/**
|
|
804
|
+
* Type guard to check if a value is a SqliteAdapter.
|
|
805
|
+
*
|
|
806
|
+
* @param value - Value to check
|
|
807
|
+
* @returns True if value is a SqliteAdapter instance
|
|
808
|
+
*
|
|
809
|
+
* @example
|
|
810
|
+
* ```typescript
|
|
811
|
+
* if (isSqliteAdapter(adapter)) {
|
|
812
|
+
* // TypeScript knows adapter is SqliteAdapter
|
|
813
|
+
* console.log('Using SQLite');
|
|
814
|
+
* }
|
|
815
|
+
* ```
|
|
816
|
+
*/
|
|
818
817
|
function isSqliteAdapter(value) {
|
|
819
|
-
|
|
818
|
+
return value instanceof SqliteAdapter;
|
|
820
819
|
}
|
|
820
|
+
/**
|
|
821
|
+
* Open the scope of a savepoint inside `parent`, or of an outermost
|
|
822
|
+
* transaction when there is none, and record it among the connection's open
|
|
823
|
+
* scopes.
|
|
824
|
+
*/
|
|
821
825
|
function openScope(parent) {
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
826
|
+
const scope = {
|
|
827
|
+
active: true,
|
|
828
|
+
depth: parent ? parent.depth + 1 : 0,
|
|
829
|
+
parent,
|
|
830
|
+
rolledBack: false,
|
|
831
|
+
writesAfterClosing: 0,
|
|
832
|
+
finished: false,
|
|
833
|
+
held: [],
|
|
834
|
+
queue: Promise.resolve(),
|
|
835
|
+
open: parent ? parent.open : []
|
|
836
|
+
};
|
|
837
|
+
scope.open.push(scope);
|
|
838
|
+
return scope;
|
|
835
839
|
}
|
|
840
|
+
/** The outermost transaction `scope` runs inside; itself for that one. */
|
|
836
841
|
function outermost(scope) {
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
842
|
+
let at = scope;
|
|
843
|
+
while (at.parent) at = at.parent;
|
|
844
|
+
return at;
|
|
840
845
|
}
|
|
846
|
+
/**
|
|
847
|
+
* Whether an effect registered from `scope` describes a write that was
|
|
848
|
+
* undone: `scope`, or a scope it runs inside, rolled back, and no
|
|
849
|
+
* transaction had run from its context since it closed when the effect was
|
|
850
|
+
* registered (`writesBefore`). An effect registered before such a write
|
|
851
|
+
* still describes the undone one.
|
|
852
|
+
*/
|
|
841
853
|
function undone(scope, writesBefore) {
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
854
|
+
if (writesBefore > 0) return false;
|
|
855
|
+
for (let at = scope; at; at = at.parent) if (at.rolledBack) return true;
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Settles once `turn` does, or rejects with the pool-wait timeout error once
|
|
860
|
+
* `boundMs` passes first. A bound of 0 waits without limit, as pg-pool reads a
|
|
861
|
+
* `connectionTimeoutMillis` of 0.
|
|
862
|
+
*/
|
|
863
|
+
function waitForTurn(turn, boundMs) {
|
|
864
|
+
if (boundMs <= 0) return turn.then(() => void 0);
|
|
865
|
+
return new Promise((resolve, reject) => {
|
|
866
|
+
const timer = setTimeout(() => reject((0, _nextlyhq_adapter_drizzle_types.poolWaitTimeoutError)(boundMs)), boundMs);
|
|
867
|
+
timer.unref();
|
|
868
|
+
turn.then(() => {
|
|
869
|
+
clearTimeout(timer);
|
|
870
|
+
resolve();
|
|
871
|
+
});
|
|
872
|
+
});
|
|
847
873
|
}
|
|
874
|
+
/** Chain `run` onto a scope's queue, keeping the queue itself unrejected. */
|
|
848
875
|
function enqueueIn(scope, run) {
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
876
|
+
const next = scope.queue.then(run, run);
|
|
877
|
+
scope.queue = next.catch(() => void 0);
|
|
878
|
+
return next;
|
|
852
879
|
}
|
|
880
|
+
/**
|
|
881
|
+
* Close a scope to new nested calls and wait for those already queued. A call
|
|
882
|
+
* arriving after this joins the innermost scope still open on the connection,
|
|
883
|
+
* or, when none is, goes to the instance queue behind the transaction.
|
|
884
|
+
*/
|
|
853
885
|
async function settleNested(scope) {
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
886
|
+
scope.active = false;
|
|
887
|
+
const index = scope.open.indexOf(scope);
|
|
888
|
+
if (index !== -1) scope.open.splice(index, 1);
|
|
889
|
+
await scope.queue;
|
|
858
890
|
}
|
|
859
|
-
|
|
860
891
|
exports.SqliteAdapter = SqliteAdapter;
|
|
861
892
|
exports.VERSION = VERSION;
|
|
862
893
|
exports.createSqliteAdapter = createSqliteAdapter;
|
|
863
894
|
exports.isSqliteAdapter = isSqliteAdapter;
|
|
864
|
-
|
|
895
|
+
|
|
865
896
|
//# sourceMappingURL=index.cjs.map
|