@delali/sirannon-db 0.1.6 → 0.1.7

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.
Files changed (54) hide show
  1. package/README.md +134 -19
  2. package/dist/backup-scheduler/index.d.ts +18 -3
  3. package/dist/backup-scheduler/index.mjs +2 -2
  4. package/dist/{change-tracker-CFTQ9TSn.d.ts → change-tracker-CbmaMO-N.d.ts} +12 -2
  5. package/dist/chunk-4IGMIJQK.mjs +318 -0
  6. package/dist/chunk-BNUTBHHH.mjs +22 -0
  7. package/dist/chunk-CJLYFDP5.mjs +26 -0
  8. package/dist/chunk-CW6S3WL5.mjs +222 -0
  9. package/dist/chunk-DJLX6CAE.mjs +20 -0
  10. package/dist/chunk-DVWQD3GF.mjs +49 -0
  11. package/dist/chunk-GEZUUIKV.mjs +268 -0
  12. package/dist/{chunk-UVMVN3OT.mjs → chunk-H5AB6NIR.mjs} +1 -1
  13. package/dist/{chunk-UTO3ZAFS.mjs → chunk-HHRMRFFR.mjs} +148 -28
  14. package/dist/chunk-TKGHYWQ6.mjs +35 -0
  15. package/dist/chunk-VLTICJOD.mjs +470 -0
  16. package/dist/{chunk-O7BHI3CF.mjs → chunk-YPYVQJ4C.mjs} +15 -1
  17. package/dist/client/index.d.ts +107 -15
  18. package/dist/client/index.mjs +348 -37
  19. package/dist/core/index.d.ts +70 -13
  20. package/dist/core/index.mjs +635 -192
  21. package/dist/core/writer-worker.d.ts +2 -0
  22. package/dist/core/writer-worker.mjs +107 -0
  23. package/dist/{database-BVY1GqE7.d.ts → database-DuGp0Rtr.d.ts} +51 -19
  24. package/dist/driver/better-sqlite3.d.ts +1 -1
  25. package/dist/driver/better-sqlite3.mjs +44 -6
  26. package/dist/driver/bun.mjs +35 -6
  27. package/dist/driver/expo.mjs +2 -2
  28. package/dist/driver/node.d.ts +1 -1
  29. package/dist/driver/node.mjs +48 -5
  30. package/dist/driver/wa-sqlite.d.ts +109 -0
  31. package/dist/driver/wa-sqlite.mjs +30 -2
  32. package/dist/{errors-C00ed08Q.d.ts → errors-5Nf5ZAEC.d.ts} +19 -1
  33. package/dist/file-migrations/index.d.ts +2 -3
  34. package/dist/file-migrations/index.mjs +1 -1
  35. package/dist/replication/coordinator/etcd.mjs +2 -2
  36. package/dist/replication/index.d.ts +7 -8
  37. package/dist/replication/index.mjs +79 -78
  38. package/dist/server/index.d.ts +111 -28
  39. package/dist/server/index.mjs +1016 -384
  40. package/dist/{sirannon-Cd-lK6T0.d.ts → sirannon-4SspRvP5.d.ts} +3 -3
  41. package/dist/transport/grpc.d.ts +3 -4
  42. package/dist/transport/grpc.mjs +2 -2
  43. package/dist/{types-Lc7ywFx7.d.ts → types-BsjobKbl.d.ts} +2 -2
  44. package/dist/{types-BeozgNPr.d.ts → types-D4p4UyDK.d.ts} +1 -1
  45. package/dist/types-D_hQW1hr.d.ts +494 -0
  46. package/package.json +3 -23
  47. package/dist/chunk-3MCMONVP.mjs +0 -115
  48. package/dist/chunk-74UN4DIE.mjs +0 -14
  49. package/dist/chunk-FB2U2Q3Y.mjs +0 -21
  50. package/dist/chunk-GS7T5YMI.mjs +0 -51
  51. package/dist/chunk-PXKAKK2V.mjs +0 -124
  52. package/dist/index-CLdNrcPz.d.ts +0 -16
  53. package/dist/types-BFSsG77t.d.ts +0 -29
  54. package/dist/types-D-74JiXb.d.ts +0 -265
@@ -9,13 +9,27 @@ var RemoteSubscriptionBuilderImpl = class {
9
9
  this.conditions = { ...this.conditions, ...conditions };
10
10
  return this;
11
11
  }
12
- subscribe(callback) {
12
+ subscribe(callback, options) {
13
13
  const filter = Object.keys(this.conditions).length > 0 ? this.conditions : void 0;
14
- return this.transport.subscribe(this.table, filter, callback);
14
+ return this.transport.subscribe(this.table, filter, callback, options);
15
+ }
16
+ };
17
+
18
+ // src/client/types.ts
19
+ var RemoteError = class extends Error {
20
+ code;
21
+ constructor(code, message) {
22
+ super(message);
23
+ this.name = "RemoteError";
24
+ this.code = code;
15
25
  }
16
26
  };
17
27
 
18
28
  // src/client/database-proxy.ts
29
+ var DEFAULT_LOAD_BATCH_SIZE = 1e3;
30
+ function isAsyncIterable(value) {
31
+ return typeof value[Symbol.asyncIterator] === "function";
32
+ }
19
33
  var RemoteDatabase = class {
20
34
  constructor(id, transport, onDispose) {
21
35
  this.id = id;
@@ -47,13 +61,96 @@ var RemoteDatabase = class {
47
61
  * Execute multiple statements as a single atomic transaction.
48
62
  * Returns an array of results, one per statement.
49
63
  *
50
- * Requires HTTP transport. WebSocket transport does not
51
- * support server-side transactions.
64
+ * The whole list travels in one request and commits or rolls back as a
65
+ * unit, so the client is never in the loop between statements.
52
66
  */
53
67
  async transaction(statements) {
54
68
  const response = await this.transport.transaction(statements);
55
69
  return response.results;
56
70
  }
71
+ /**
72
+ * Run the same statement once per parameter set as a single atomic
73
+ * transaction that commits with one fsync. Returns one result per
74
+ * parameter set, in order. Use this for a burst of same-shape writes
75
+ * (an import, a bulk insert) that must all commit or all roll back.
76
+ */
77
+ async batch(sql, paramsBatch, writeConcern) {
78
+ const response = await this.transport.batch(sql, paramsBatch, writeConcern);
79
+ return response.results;
80
+ }
81
+ /**
82
+ * Load a whole dataset through the same statement, batching it into requests
83
+ * for you and paying the one fsyncing WAL checkpoint once, after the final
84
+ * batch. The configured durability is restored after every batch, so an
85
+ * import that stops partway never leaves the writer at the relaxed level.
86
+ * Prefer this over {@link load} for anything larger than a single request:
87
+ * it runs the finalize itself, so there is no checkpoint flag to forget.
88
+ *
89
+ * Accepts a synchronous or asynchronous iterable of parameter sets, so rows
90
+ * can stream from a file or the network without being held in memory at
91
+ * once. Returns the total rows loaded and changes applied.
92
+ *
93
+ * ```ts
94
+ * const summary = await db.loadAll(
95
+ * 'INSERT INTO events (id, payload) VALUES (?, ?)',
96
+ * rowStream,
97
+ * { batchSize: 5000, durability: 'off' },
98
+ * )
99
+ * ```
100
+ */
101
+ async loadAll(sql, rows, options) {
102
+ const batchSize = options?.batchSize ?? DEFAULT_LOAD_BATCH_SIZE;
103
+ if (!Number.isInteger(batchSize) || batchSize <= 0) {
104
+ throw new RemoteError("INVALID_ARGUMENT", "loadAll batchSize must be a positive integer");
105
+ }
106
+ const durability = options?.durability;
107
+ const total = { rowsLoaded: 0, changes: 0 };
108
+ const send = async (paramsBatch, checkpoint) => {
109
+ const summary = await this.transport.load(sql, paramsBatch, durability, checkpoint);
110
+ total.rowsLoaded += summary.rowsLoaded;
111
+ total.changes += summary.changes;
112
+ };
113
+ let batch = [];
114
+ let previous = null;
115
+ const rotate = async () => {
116
+ if (previous !== null) await send(previous, false);
117
+ previous = batch;
118
+ batch = [];
119
+ };
120
+ if (isAsyncIterable(rows)) {
121
+ for await (const row of rows) {
122
+ batch.push(row);
123
+ if (batch.length >= batchSize) await rotate();
124
+ }
125
+ } else {
126
+ for (const row of rows) {
127
+ batch.push(row);
128
+ if (batch.length >= batchSize) await rotate();
129
+ }
130
+ }
131
+ if (batch.length > 0) {
132
+ if (previous !== null) await send(previous, false);
133
+ await send(batch, true);
134
+ } else if (previous !== null) {
135
+ await send(previous, true);
136
+ }
137
+ return total;
138
+ }
139
+ /**
140
+ * Load one batch of rows through the same statement with writer durability
141
+ * relaxed for the duration, then restored before this resolves. This is the
142
+ * low-level primitive; prefer {@link loadAll} for a dataset that spans more
143
+ * than one request, since it runs the finalize itself rather than relying on
144
+ * a `checkpoint` flag.
145
+ *
146
+ * Returns the total rows loaded and changes applied. When splitting a dataset
147
+ * across many `load` calls by hand, pass `checkpoint: false` on every call
148
+ * but the last so the one fsyncing WAL checkpoint runs once at the end; the
149
+ * configured durability is restored after each call regardless.
150
+ */
151
+ async load(sql, paramsBatch, durability, checkpoint) {
152
+ return this.transport.load(sql, paramsBatch, durability, checkpoint);
153
+ }
57
154
  /**
58
155
  * Start building a CDC subscription for the given table.
59
156
  * Chain `.filter()` to narrow the events, then call `.subscribe()`
@@ -82,15 +179,99 @@ var RemoteDatabase = class {
82
179
  }
83
180
  };
84
181
 
85
- // src/client/types.ts
86
- var RemoteError = class extends Error {
87
- code;
88
- constructor(code, message) {
89
- super(message);
90
- this.name = "RemoteError";
91
- this.code = code;
182
+ // src/core/cdc/encoding.ts
183
+ var BLOB_TAG = "__sirannon_blob";
184
+ var INT_TAG = "__sirannon_int";
185
+ var SAFE_INT_BOUND = 9007199254740991n;
186
+ var HEX_BYTES_RE = /^[0-9a-fA-F]*$/;
187
+ var INT_PAYLOAD_RE = /^-?\d{1,19}$/;
188
+ function encodeTaggedValues(value) {
189
+ if (typeof value === "bigint" || isBinaryValue(value)) {
190
+ return encodeTaggedLeaf(value);
191
+ }
192
+ if (value === null || typeof value !== "object") return value;
193
+ if (Array.isArray(value)) {
194
+ return value.map(encodeTaggedValues);
195
+ }
196
+ if (typeof value.toJSON === "function") {
197
+ return value;
198
+ }
199
+ const obj = value;
200
+ const out = {};
201
+ for (const k of Object.keys(obj)) {
202
+ out[k] = encodeTaggedValues(obj[k]);
203
+ }
204
+ return out;
205
+ }
206
+ function encodeTaggedLeaf(value) {
207
+ if (typeof value === "bigint") {
208
+ if (value >= -SAFE_INT_BOUND && value <= SAFE_INT_BOUND) {
209
+ return Number(value);
210
+ }
211
+ return { [INT_TAG]: value.toString() };
92
212
  }
93
- };
213
+ if (isBinaryValue(value)) {
214
+ return { [BLOB_TAG]: encodeHexBytes(value) };
215
+ }
216
+ return value;
217
+ }
218
+ function decodeTaggedValues(value) {
219
+ if (value === null || typeof value !== "object") return value;
220
+ if (isBinaryValue(value)) return value;
221
+ if (Array.isArray(value)) {
222
+ return value.map(decodeTaggedValues);
223
+ }
224
+ const obj = value;
225
+ const keys = Object.keys(obj);
226
+ if (keys.length === 1) {
227
+ const tag = keys[0];
228
+ const tagValue = obj[tag];
229
+ if (tag === BLOB_TAG && typeof tagValue === "string") {
230
+ return decodeHexBytes(tagValue);
231
+ }
232
+ if (tag === INT_TAG && typeof tagValue === "string") {
233
+ if (!INT_PAYLOAD_RE.test(tagValue)) {
234
+ throw new Error("Invalid tagged integer payload");
235
+ }
236
+ return BigInt(tagValue);
237
+ }
238
+ }
239
+ const out = {};
240
+ for (const k of keys) {
241
+ out[k] = decodeTaggedValues(obj[k]);
242
+ }
243
+ return out;
244
+ }
245
+ function isBinaryValue(value) {
246
+ return value instanceof Uint8Array || typeof Buffer !== "undefined" && Buffer.isBuffer(value);
247
+ }
248
+ function encodeHexBytes(bytes) {
249
+ if (typeof Buffer !== "undefined") {
250
+ return Buffer.from(bytes).toString("hex").toUpperCase();
251
+ }
252
+ let hex = "";
253
+ for (const byte of bytes) {
254
+ hex += byte.toString(16).padStart(2, "0");
255
+ }
256
+ return hex.toUpperCase();
257
+ }
258
+ function decodeHexBytes(hex) {
259
+ if (hex.length % 2 !== 0 || !HEX_BYTES_RE.test(hex)) {
260
+ throw new Error("Invalid CDC BLOB hex payload");
261
+ }
262
+ if (typeof Buffer !== "undefined") {
263
+ return Buffer.from(hex, "hex");
264
+ }
265
+ const bytes = new Uint8Array(hex.length / 2);
266
+ for (let i = 0; i < hex.length; i += 2) {
267
+ const byte = Number.parseInt(hex.slice(i, i + 2), 16);
268
+ if (Number.isNaN(byte)) {
269
+ throw new Error("Invalid CDC BLOB hex payload");
270
+ }
271
+ bytes[i / 2] = byte;
272
+ }
273
+ return bytes;
274
+ }
94
275
 
95
276
  // src/client/transport/http.ts
96
277
  var HttpTransport = class {
@@ -105,13 +286,35 @@ var HttpTransport = class {
105
286
  };
106
287
  }
107
288
  async query(sql, params, readConcern) {
108
- return this.post("/query", { sql, params, readConcern });
289
+ const response = await this.post("/query", {
290
+ sql,
291
+ params: encodeTaggedValues(params),
292
+ readConcern
293
+ });
294
+ return { rows: decodeTaggedValues(response.rows ?? []) };
109
295
  }
110
296
  async execute(sql, params) {
111
- return this.post("/execute", { sql, params });
297
+ return this.post("/execute", { sql, params: encodeTaggedValues(params) });
112
298
  }
113
299
  async transaction(statements) {
114
- return this.post("/transaction", { statements });
300
+ return this.post("/transaction", {
301
+ statements: statements.map((stmt) => ({ sql: stmt.sql, params: encodeTaggedValues(stmt.params) }))
302
+ });
303
+ }
304
+ async batch(sql, paramsBatch, writeConcern) {
305
+ return this.post("/batch", {
306
+ sql,
307
+ paramsBatch: paramsBatch.map((entry) => encodeTaggedValues(entry)),
308
+ writeConcern
309
+ });
310
+ }
311
+ async load(sql, paramsBatch, durability, checkpoint) {
312
+ return this.post("/load", {
313
+ sql,
314
+ paramsBatch: paramsBatch.map((entry) => encodeTaggedValues(entry)),
315
+ durability,
316
+ checkpoint
317
+ });
115
318
  }
116
319
  async subscribe(_table, _filter, _callback) {
117
320
  throw new RemoteError(
@@ -182,29 +385,76 @@ var WebSocketTransport = class {
182
385
  async query(sql, params) {
183
386
  await this.ensureConnected();
184
387
  const id = this.nextId();
185
- return this.request({ type: "query", id, sql, params });
388
+ const response = await this.request({
389
+ type: "query",
390
+ id,
391
+ sql,
392
+ params: encodeTaggedValues(params)
393
+ });
394
+ return { rows: decodeTaggedValues(response.rows ?? []) };
186
395
  }
187
396
  async execute(sql, params) {
188
397
  await this.ensureConnected();
189
398
  const id = this.nextId();
190
- return this.request({ type: "execute", id, sql, params });
399
+ return this.request({
400
+ type: "execute",
401
+ id,
402
+ sql,
403
+ params: encodeTaggedValues(params)
404
+ });
191
405
  }
192
- async transaction(_statements) {
193
- throw new RemoteError(
194
- "TRANSPORT_ERROR",
195
- "Transactions are not supported over WebSocket. Use HTTP transport for batch transactions."
196
- );
406
+ async transaction(statements) {
407
+ await this.ensureConnected();
408
+ const id = this.nextId();
409
+ return this.request({
410
+ type: "transaction",
411
+ id,
412
+ statements: statements.map((stmt) => ({
413
+ sql: stmt.sql,
414
+ params: encodeTaggedValues(stmt.params)
415
+ }))
416
+ });
417
+ }
418
+ async batch(sql, paramsBatch, writeConcern) {
419
+ await this.ensureConnected();
420
+ const id = this.nextId();
421
+ return this.request({
422
+ type: "batch",
423
+ id,
424
+ sql,
425
+ paramsBatch: paramsBatch.map((entry) => encodeTaggedValues(entry)),
426
+ ...writeConcern ? { writeConcern } : {}
427
+ });
197
428
  }
198
- async subscribe(table, filter, callback) {
429
+ async load(sql, paramsBatch, durability, checkpoint) {
199
430
  await this.ensureConnected();
200
431
  const id = this.nextId();
201
- this.activeSubscriptions.set(id, { table, filter, callback });
432
+ return this.request({
433
+ type: "load",
434
+ id,
435
+ sql,
436
+ paramsBatch: paramsBatch.map((entry) => encodeTaggedValues(entry)),
437
+ ...durability ? { durability } : {},
438
+ ...checkpoint !== void 0 ? { checkpoint } : {}
439
+ });
440
+ }
441
+ async subscribe(table, filter, callback, options) {
442
+ await this.ensureConnected();
443
+ const id = this.nextId();
444
+ this.activeSubscriptions.set(id, {
445
+ table,
446
+ filter,
447
+ callback,
448
+ onReset: options?.onReset,
449
+ lastSeq: void 0,
450
+ epoch: void 0
451
+ });
202
452
  try {
203
453
  const msg = {
204
454
  type: "subscribe",
205
455
  id,
206
456
  table,
207
- ...filter ? { filter } : {}
457
+ ...filter ? { filter: encodeTaggedValues(filter) } : {}
208
458
  };
209
459
  await this.request(msg);
210
460
  } catch (err) {
@@ -303,6 +553,29 @@ var WebSocketTransport = class {
303
553
  break;
304
554
  }
305
555
  case "subscribed": {
556
+ const sub = this.activeSubscriptions.get(msg.id);
557
+ if (sub) {
558
+ if (msg.epoch !== void 0) {
559
+ sub.epoch = msg.epoch;
560
+ }
561
+ let baseline;
562
+ if (msg.seq !== void 0) {
563
+ try {
564
+ baseline = BigInt(msg.seq);
565
+ } catch {
566
+ baseline = void 0;
567
+ }
568
+ }
569
+ if (msg.resync) {
570
+ sub.lastSeq = baseline;
571
+ try {
572
+ sub.onReset?.();
573
+ } catch {
574
+ }
575
+ } else if (sub.lastSeq === void 0 && baseline !== void 0) {
576
+ sub.lastSeq = baseline;
577
+ }
578
+ }
306
579
  const pending = this.pendingRequests.get(msg.id);
307
580
  if (pending) {
308
581
  clearTimeout(pending.timer);
@@ -327,11 +600,14 @@ var WebSocketTransport = class {
327
600
  const event = {
328
601
  type: msg.event.type,
329
602
  table: msg.event.table,
330
- row: msg.event.row,
331
- oldRow: msg.event.oldRow,
603
+ row: decodeTaggedValues(msg.event.row),
604
+ oldRow: msg.event.oldRow === void 0 ? void 0 : decodeTaggedValues(msg.event.oldRow),
332
605
  seq: BigInt(msg.event.seq),
333
606
  timestamp: msg.event.timestamp
334
607
  };
608
+ if (sub.lastSeq === void 0 || event.seq > sub.lastSeq) {
609
+ sub.lastSeq = event.seq;
610
+ }
335
611
  sub.callback(event);
336
612
  } catch {
337
613
  }
@@ -372,7 +648,9 @@ var WebSocketTransport = class {
372
648
  type: "subscribe",
373
649
  id,
374
650
  table: sub.table,
375
- ...sub.filter ? { filter: sub.filter } : {}
651
+ ...sub.filter ? { filter: encodeTaggedValues(sub.filter) } : {},
652
+ ...sub.lastSeq !== void 0 ? { sinceSeq: sub.lastSeq.toString() } : {},
653
+ ...sub.epoch !== void 0 ? { epoch: sub.epoch } : {}
376
654
  };
377
655
  await this.request(msg);
378
656
  } catch {
@@ -387,10 +665,10 @@ var WebSocketTransport = class {
387
665
  reject(new RemoteError("CONNECTION_ERROR", "WebSocket is not connected"));
388
666
  return;
389
667
  }
390
- const timer = setTimeout(() => {
668
+ const timer = this.requestTimeout > 0 ? setTimeout(() => {
391
669
  this.pendingRequests.delete(id);
392
670
  reject(new RemoteError("TIMEOUT", `Request timed out after ${this.requestTimeout}ms`));
393
- }, this.requestTimeout);
671
+ }, this.requestTimeout) : void 0;
394
672
  this.pendingRequests.set(id, {
395
673
  resolve,
396
674
  reject,
@@ -551,6 +829,7 @@ var SirannonClient = class {
551
829
  webSocketProtocols;
552
830
  autoReconnect;
553
831
  reconnectInterval;
832
+ requestTimeout;
554
833
  databases = /* @__PURE__ */ new Map();
555
834
  closed = false;
556
835
  topologyEnabled;
@@ -584,6 +863,7 @@ var SirannonClient = class {
584
863
  this.webSocketProtocols = topoOpts.webSocketProtocols;
585
864
  this.autoReconnect = topoOpts.autoReconnect ?? true;
586
865
  this.reconnectInterval = topoOpts.reconnectInterval ?? 1e3;
866
+ this.requestTimeout = topoOpts.requestTimeout;
587
867
  } else {
588
868
  this.topologyEnabled = false;
589
869
  this.primaryUrl = void 0;
@@ -599,6 +879,7 @@ var SirannonClient = class {
599
879
  this.webSocketProtocols = options?.webSocketProtocols;
600
880
  this.autoReconnect = options?.autoReconnect ?? true;
601
881
  this.reconnectInterval = options?.reconnectInterval ?? 1e3;
882
+ this.requestTimeout = options?.requestTimeout;
602
883
  }
603
884
  }
604
885
  database(id) {
@@ -640,7 +921,8 @@ var SirannonClient = class {
640
921
  return new WebSocketTransport(`${wsBaseUrl}/db/${encodedId}`, {
641
922
  autoReconnect: this.autoReconnect,
642
923
  reconnectInterval: this.reconnectInterval,
643
- protocols: this.webSocketProtocols
924
+ protocols: this.webSocketProtocols,
925
+ requestTimeout: this.requestTimeout
644
926
  });
645
927
  }
646
928
  _createTransportForEndpoint(url, databaseId) {
@@ -915,9 +1197,35 @@ var TopologyAwareTransport = class {
915
1197
  throw err;
916
1198
  }
917
1199
  }
918
- async subscribe(table, filter, callback) {
1200
+ async batch(sql, paramsBatch, writeConcern) {
1201
+ const transport = await this.getWriteTransport();
1202
+ try {
1203
+ return await transport.batch(sql, paramsBatch, writeConcern);
1204
+ } catch (err) {
1205
+ if (this.client._usesCoordinatorDiscovery() && shouldRefreshRouting(err)) {
1206
+ await this.client._refreshClusterRouting(this.databaseId);
1207
+ this.writeTransport = null;
1208
+ this.currentWriteUrl = "";
1209
+ }
1210
+ throw err;
1211
+ }
1212
+ }
1213
+ async load(sql, paramsBatch, durability, checkpoint) {
1214
+ const transport = await this.getWriteTransport();
1215
+ try {
1216
+ return await transport.load(sql, paramsBatch, durability, checkpoint);
1217
+ } catch (err) {
1218
+ if (this.client._usesCoordinatorDiscovery() && shouldRefreshRouting(err)) {
1219
+ await this.client._refreshClusterRouting(this.databaseId);
1220
+ this.writeTransport = null;
1221
+ this.currentWriteUrl = "";
1222
+ }
1223
+ throw err;
1224
+ }
1225
+ }
1226
+ async subscribe(table, filter, callback, options) {
919
1227
  try {
920
- return await this.subscribeOnCurrentEndpoint(table, filter, callback);
1228
+ return await this.subscribeOnCurrentEndpoint(table, filter, callback, options);
921
1229
  } catch (err) {
922
1230
  if (this.client._usesCoordinatorDiscovery() && shouldRefreshRouting(err)) {
923
1231
  const hadActiveSubscriptions = this.activeSubscriptions.size > 0;
@@ -928,7 +1236,7 @@ var TopologyAwareTransport = class {
928
1236
  if (!hadActiveSubscriptions) {
929
1237
  this.closeSubscriptionTransport();
930
1238
  }
931
- return this.subscribeOnCurrentEndpoint(table, filter, callback);
1239
+ return this.subscribeOnCurrentEndpoint(table, filter, callback, options);
932
1240
  }
933
1241
  throw err;
934
1242
  }
@@ -963,15 +1271,16 @@ var TopologyAwareTransport = class {
963
1271
  this.currentSubscriptionUrl = "";
964
1272
  this.client._unregisterTopologyTransport(this.databaseId, this);
965
1273
  }
966
- async subscribeOnCurrentEndpoint(table, filter, callback) {
1274
+ async subscribeOnCurrentEndpoint(table, filter, callback, options) {
967
1275
  return this.withSubscriptionOperation(async () => {
968
1276
  const transport = await this.getSubscriptionTransport(this.client._getReadConcern());
969
- const remote = await transport.subscribe(table, filter, callback);
1277
+ const remote = await transport.subscribe(table, filter, callback, options);
970
1278
  const subscription = {
971
1279
  id: ++this.nextSubscriptionId,
972
1280
  table,
973
1281
  filter,
974
1282
  callback,
1283
+ onReset: options?.onReset,
975
1284
  remote,
976
1285
  active: true
977
1286
  };
@@ -1006,7 +1315,9 @@ var TopologyAwareTransport = class {
1006
1315
  try {
1007
1316
  for (const subscription of subscriptions) {
1008
1317
  if (!subscription.active) continue;
1009
- const remote = await nextTransport.subscribe(subscription.table, subscription.filter, subscription.callback);
1318
+ const remote = await nextTransport.subscribe(subscription.table, subscription.filter, subscription.callback, {
1319
+ onReset: subscription.onReset
1320
+ });
1010
1321
  if (!subscription.active) {
1011
1322
  remote.unsubscribe();
1012
1323
  continue;
@@ -1,15 +1,61 @@
1
- export { B as BackupManager, a as BackupScheduler } from '../index-CLdNrcPz.js';
2
- export { C as ChangeTracker, a as ChangeTrackerOptions } from '../change-tracker-CFTQ9TSn.js';
3
- import { S as SQLiteDriver, a as SQLiteConnection } from '../types-BFSsG77t.js';
4
- export { D as DriverCapabilities, O as OpenOptions, R as RunResult, b as SQLiteStatement } from '../types-BFSsG77t.js';
5
- import { D as Database } from '../database-BVY1GqE7.js';
6
- export { H as HookDispose, a as HookEvent, b as HookEventContextMap, c as HookHandler, d as HookRegistry, M as MetricsCollector, S as SubscribeHookContext } from '../database-BVY1GqE7.js';
7
- export { B as BackupError, C as CDCError, a as ConnectionPoolError, D as DatabaseAlreadyExistsError, b as DatabaseNotFoundError, E as ExtensionError, H as HookDeniedError, M as MaxDatabasesError, c as MigrationError, Q as QueryError, R as ReadOnlyError, S as SirannonError, T as TransactionError } from '../errors-C00ed08Q.js';
8
- import { D as DatabaseOptions, L as LifecycleConfig, P as Params, E as ExecuteResult } from '../types-D-74JiXb.js';
9
- export { A as AfterQueryHook, m as BackupScheduleOptions, a as BeforeConnectHook, B as BeforeQueryHook, g as BeforeSubscribeHook, j as CDCMetrics, C as ChangeEvent, n as ChangeOperation, o as ClientOptions, p as ClusterReadEndpointInfo, q as ClusterStatusInfo, f as ConnectionHookContext, i as ConnectionMetrics, r as CorsOptions, c as DatabaseCloseHook, b as DatabaseOpenHook, H as HookConfig, M as MetricsConfig, O as OnRequestHook, Q as QueryHookContext, h as QueryMetrics, k as QueryOptions, R as ReadConcern, s as ReadConcernLevel, t as ReplicationStatusInfo, u as RequestContext, v as RequestDenial, w as ServerExecutionTarget, x as ServerExecutionTargetResolver, d as ServerOptions, S as SirannonOptions, y as Subscription, l as SubscriptionBuilder, T as Transaction, e as WSHandlerOptions, W as WriteConcern, z as WriteConcernLevel } from '../types-D-74JiXb.js';
10
- import { M as Migration, a as MigrationResult, R as RollbackResult } from '../types-BeozgNPr.js';
11
- export { A as AppliedMigration, b as AppliedMigrationEntry } from '../types-BeozgNPr.js';
12
- export { S as Sirannon } from '../sirannon-Cd-lK6T0.js';
1
+ import { g as BulkLoadDurability, e as SQLiteConnection, s as SynchronousLevel, h as BulkLoadResult, a as SQLiteDriver, t as WorkerHostOptions, D as DatabaseOptions, L as LifecycleConfig, P as Params, E as ExecuteResult } from '../types-D_hQW1hr.js';
2
+ export { A as AfterQueryHook, f as BackupScheduleOptions, u as BatchSummary, b as BeforeConnectHook, B as BeforeQueryHook, l as BeforeSubscribeHook, q as BulkLoadOptions, o as CDCMetrics, C as ChangeEvent, v as ChangeOperation, w as ClientOptions, x as ClusterReadEndpointInfo, y as ClusterStatusInfo, k as ConnectionHookContext, n as ConnectionMetrics, z as CorsOptions, d as DatabaseCloseHook, c as DatabaseOpenHook, F as DriverCapabilities, G as DriverWorkerEntry, H as HookConfig, M as MetricsConfig, O as OnRequestHook, I as OpenOptions, Q as QueryHookContext, m as QueryMetrics, p as QueryOptions, R as ReadConcern, J as ReadConcernLevel, K as ReplicationStatusInfo, N as RequestContext, U as RequestDenial, V as RunResult, X as SQLiteStatement, Y as ServerExecutionTarget, Z as ServerExecutionTargetResolver, i as ServerOptions, S as SirannonOptions, _ as Subscription, r as SubscriptionBuilder, T as Transaction, j as WSHandlerOptions, W as WriteConcern, $ as WriteConcernLevel, a0 as WriterWorkerOptions } from '../types-D_hQW1hr.js';
3
+ export { C as ChangeTracker, a as ChangeTrackerOptions } from '../change-tracker-CbmaMO-N.js';
4
+ import { D as Database } from '../database-DuGp0Rtr.js';
5
+ export { H as HookDispose, a as HookEvent, b as HookEventContextMap, c as HookHandler, d as HookRegistry, M as MetricsCollector, S as SubscribeHookContext } from '../database-DuGp0Rtr.js';
6
+ export { B as BackupError, C as CDCError, a as ConnectionPoolError, D as DatabaseAlreadyExistsError, b as DatabaseNotFoundError, E as ExtensionError, F as ForbiddenSqlError, H as HookDeniedError, M as MaxDatabasesError, c as MigrationError, Q as QueryError, R as ReadOnlyError, S as SirannonError, T as TransactionError, W as WriteOverloadError } from '../errors-5Nf5ZAEC.js';
7
+ import { M as Migration, a as MigrationResult, R as RollbackResult } from '../types-D4p4UyDK.js';
8
+ export { A as AppliedMigration, b as AppliedMigrationEntry } from '../types-D4p4UyDK.js';
9
+ export { S as Sirannon } from '../sirannon-4SspRvP5.js';
10
+
11
+ declare function isBulkLoadDurability(value: unknown): value is BulkLoadDurability;
12
+ interface BulkLoadRun {
13
+ writer: SQLiteConnection;
14
+ configuredSynchronous: SynchronousLevel;
15
+ walMode: boolean;
16
+ durability: BulkLoadDurability | undefined;
17
+ checkpoint?: boolean;
18
+ loadRows: () => Promise<BulkLoadResult>;
19
+ }
20
+ /**
21
+ * Run a bulk load with relaxed writer durability, then restore the
22
+ * operator-configured level. The caller must hold the database's writer lock
23
+ * for the whole call, so no other write commits under the relaxed level.
24
+ *
25
+ * Interruption safety, by failure mode:
26
+ * - A load statement fails: the surrounding transaction rolls back, the
27
+ * configured level is restored, and the database is exactly as it was before
28
+ * the load; the operator recovers by re-running the load.
29
+ * - The process crashes mid-load: `PRAGMA synchronous` is connection state
30
+ * that SQLite never stores in the database file, so the next open re-applies
31
+ * the configured level, and the uncommitted transaction rolls back from the
32
+ * WAL on open.
33
+ * - Power loss or an OS crash during a load at 'off': SQLite documents that
34
+ * the file may be corrupted, which is why 'off' fits only a load the
35
+ * operator can re-run from scratch; 'normal' keeps WAL corruption safety.
36
+ *
37
+ * The restore runs on both the success and the failure path. A restore pragma
38
+ * only fails when the writer connection itself is gone, so on a committed load
39
+ * that failure is reported as 'DURABILITY_RESTORE_FAILED', which tells the
40
+ * operator the load committed and must not be re-run; on a failed load the
41
+ * original load error stays dominant.
42
+ *
43
+ * On success, and only when the load changed rows, the WAL is checkpointed
44
+ * after the restore so the loaded pages are written into the main database
45
+ * file and fsync'd at the restored synchronous level. A reader holding the WAL
46
+ * can defer that transfer: the checkpoint retries a few times and then leaves
47
+ * the remaining pages for a later checkpoint rather than failing a load that
48
+ * has already committed, so those pages stay in the WAL until then. The
49
+ * checkpoint runs synchronously in the engine and blocks the event loop for
50
+ * the flush, which grows with the size of the load.
51
+ *
52
+ * A multi-batch import passes `checkpoint: false` on every load but the last so
53
+ * the one fsyncing checkpoint is paid once at the end instead of once per
54
+ * batch; each intermediate load still restores the configured level, and
55
+ * SQLite's automatic checkpoint keeps the WAL bounded during the import at the
56
+ * relaxed level with no fsync of its own.
57
+ */
58
+ declare function runBulkLoad(run: BulkLoadRun): Promise<BulkLoadResult>;
13
59
 
14
60
  interface ConnectionPoolOptions {
15
61
  driver: SQLiteDriver;
@@ -17,6 +63,9 @@ interface ConnectionPoolOptions {
17
63
  readOnly?: boolean;
18
64
  readPoolSize?: number;
19
65
  walMode?: boolean;
66
+ synchronous?: SynchronousLevel;
67
+ useWriterWorker?: boolean;
68
+ workerHostOptions?: WorkerHostOptions;
20
69
  }
21
70
  declare class ConnectionPool {
22
71
  private readonly writer;
@@ -34,6 +83,14 @@ declare class ConnectionPool {
34
83
 
35
84
  declare function defineDriver(config: SQLiteDriver): SQLiteDriver;
36
85
 
86
+ declare const DEFAULT_SYNCHRONOUS: SynchronousLevel;
87
+ /**
88
+ * Map a synchronous level to its PRAGMA argument through an allowlist, so a
89
+ * value smuggled past the type system can never reach SQL interpolation.
90
+ */
91
+ declare function synchronousPragmaValue(level: SynchronousLevel | undefined): string;
92
+ declare function isSynchronousLevel(value: unknown): value is SynchronousLevel;
93
+
37
94
  interface LifecycleCallbacks {
38
95
  open: (id: string, path: string, options?: DatabaseOptions) => Promise<Database>;
39
96
  close: (id: string) => Promise<void>;
@@ -83,4 +140,4 @@ declare function queryOne<T = Record<string, unknown>>(conn: SQLiteConnection, s
83
140
  declare function execute(conn: SQLiteConnection, sql: string, params?: Params): Promise<ExecuteResult>;
84
141
  declare function executeBatch(conn: SQLiteConnection, sql: string, paramsBatch: Params[]): Promise<ExecuteResult[]>;
85
142
 
86
- export { ConnectionPool, type ConnectionPoolOptions, Database, DatabaseOptions, ExecuteResult, type LifecycleCallbacks, LifecycleConfig, LifecycleManager, Migration, MigrationResult, MigrationRunner, Params, RollbackResult, SQLiteConnection, SQLiteDriver, type TenantResolverOptions, createTenantResolver, defineDriver, execute, executeBatch, query, queryOne, sanitizeTenantId, tenantPath };
143
+ export { BulkLoadDurability, BulkLoadResult, ConnectionPool, type ConnectionPoolOptions, DEFAULT_SYNCHRONOUS, Database, DatabaseOptions, ExecuteResult, type LifecycleCallbacks, LifecycleConfig, LifecycleManager, Migration, MigrationResult, MigrationRunner, Params, RollbackResult, SQLiteConnection, SQLiteDriver, SynchronousLevel, type TenantResolverOptions, createTenantResolver, defineDriver, execute, executeBatch, isBulkLoadDurability, isSynchronousLevel, query, queryOne, runBulkLoad, sanitizeTenantId, synchronousPragmaValue, tenantPath };