@delali/sirannon-db 0.3.6-next.77 → 0.3.6-next.78

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 (72) hide show
  1. package/dist/backup/index.d.ts +5 -5
  2. package/dist/backup/index.mjs +5 -3
  3. package/dist/backup-scheduler/index.d.ts +2 -2
  4. package/dist/backup-scheduler/index.mjs +2 -2
  5. package/dist/chunk-22TYGH7J.mjs +55 -0
  6. package/dist/{chunk-SB3MIFLG.mjs → chunk-2ZQOZFX7.mjs} +29 -6
  7. package/dist/{chunk-UIDQGPJY.mjs → chunk-4O7WYZHN.mjs} +38 -3
  8. package/dist/{chunk-ZFFUVYYE.mjs → chunk-5DKWHT6K.mjs} +109 -1
  9. package/dist/chunk-5O4J6LQQ.mjs +35 -0
  10. package/dist/{chunk-CZL4D3BM.mjs → chunk-6CABU54O.mjs} +2 -2
  11. package/dist/{chunk-JAQ2I4OT.mjs → chunk-A5ULJVK7.mjs} +11 -11
  12. package/dist/{chunk-RLBKZZ24.mjs → chunk-AC7TKWNH.mjs} +1 -0
  13. package/dist/chunk-BKO4SCEC.mjs +99 -0
  14. package/dist/{chunk-KOTBP4AW.mjs → chunk-C7E4H6UN.mjs} +24 -4
  15. package/dist/{chunk-G4AG3ESO.mjs → chunk-FBA7P4ZA.mjs} +114 -149
  16. package/dist/{chunk-DKML5425.mjs → chunk-GBVB6WSC.mjs} +2 -2
  17. package/dist/chunk-I4JY5QPA.mjs +51 -0
  18. package/dist/{chunk-L6EX6K27.mjs → chunk-MO4APBYG.mjs} +54 -13
  19. package/dist/{chunk-RPWBYASV.mjs → chunk-MTOQ6QIS.mjs} +1 -1
  20. package/dist/chunk-MX77PGEN.mjs +60 -0
  21. package/dist/{chunk-BTUD6XQO.mjs → chunk-N3MHZV3B.mjs} +11 -29
  22. package/dist/{chunk-UHUDPQFI.mjs → chunk-OJXV4L3N.mjs} +1 -3
  23. package/dist/chunk-QF7WF7RJ.mjs +34 -0
  24. package/dist/chunk-RLBV4YTB.mjs +22 -0
  25. package/dist/{chunk-ISDT4RBD.mjs → chunk-ULUZUBON.mjs} +1 -1
  26. package/dist/{chunk-AP4367HK.mjs → chunk-VGRAAQX3.mjs} +4 -4
  27. package/dist/chunk-W74HFQFD.mjs +153 -0
  28. package/dist/chunk-Y4SP7CRF.mjs +9 -0
  29. package/dist/{chunk-JFVEGN7J.mjs → chunk-ZAUHAJ4G.mjs} +2 -1
  30. package/dist/{chunk-DDIHBUEN.mjs → chunk-ZWW53O5P.mjs} +19 -7
  31. package/dist/client/index.d.ts +8 -8
  32. package/dist/client/topology.d.ts +4 -4
  33. package/dist/{client-base-Doaqwrx3.d.ts → client-base-DJZbSY55.d.ts} +2 -2
  34. package/dist/{client-streams-MKPE4MXW.mjs → client-streams-F7IG3PTU.mjs} +13 -16
  35. package/dist/core/index.d.ts +48 -12
  36. package/dist/core/index.mjs +1812 -107
  37. package/dist/core/writer-worker.mjs +28 -4
  38. package/dist/{database-fz-8CJBP.d.ts → database-BZ96ZzAx.d.ts} +411 -184
  39. package/dist/{database-backup-BwTNiBm2.d.ts → database-backup-BhM8yJ1A.d.ts} +32 -2
  40. package/dist/driver/better-sqlite3.d.ts +11 -1
  41. package/dist/driver/better-sqlite3.mjs +212 -23
  42. package/dist/driver/bun.d.ts +1 -1
  43. package/dist/driver/bun.mjs +15 -22
  44. package/dist/driver/expo.d.ts +1 -1
  45. package/dist/driver/expo.mjs +185 -16
  46. package/dist/driver/node.d.ts +1 -1
  47. package/dist/driver/node.mjs +15 -62
  48. package/dist/driver/wa-sqlite.d.ts +1 -1
  49. package/dist/driver/wa-sqlite.mjs +14 -153
  50. package/dist/{change-tracker-Dpo99EI-.d.ts → errors-DLMAGQFb.d.ts} +200 -2
  51. package/dist/file-migrations/index.d.ts +1 -1
  52. package/dist/peer-refusal-RR37UOLV.mjs +1 -0
  53. package/dist/{protocol-DZSYSv0S.d.ts → protocol-DE10taas.d.ts} +1 -1
  54. package/dist/replication/coordinator/etcd.mjs +1 -1
  55. package/dist/replication/index.d.ts +7 -7
  56. package/dist/replication/index.mjs +66 -9
  57. package/dist/{restore-options-CcMukOLO.d.ts → restore-options-B6HV_5XT.d.ts} +6 -1
  58. package/dist/server/index.d.ts +10 -10
  59. package/dist/server/index.mjs +506 -350
  60. package/dist/{server-options-B9CsAZp5.d.ts → server-options-Bhdlvehe.d.ts} +14 -1
  61. package/dist/{server-streams-SBYCHWWM.mjs → server-streams-Q2AZAW3N.mjs} +6 -30
  62. package/dist/{sirannon-D0CjDULa.d.ts → sirannon-Ba1o2HUQ.d.ts} +5 -5
  63. package/dist/transport/grpc.d.ts +15 -4
  64. package/dist/transport/grpc.mjs +34 -37
  65. package/dist/transport/memory.d.ts +4 -3
  66. package/dist/transport/memory.mjs +8 -26
  67. package/dist/{types-DTmi7pXp.d.ts → types-BWyv_N18.d.ts} +189 -1
  68. package/dist/{types-BlZ9GG1a.d.ts → types-CDnlkVRK.d.ts} +8 -2
  69. package/package.json +17 -9
  70. package/dist/chunk-DBN57KVR.mjs +0 -171
  71. package/dist/chunk-W67A4H2E.mjs +0 -23
  72. package/dist/chunk-YW7IKQ7T.mjs +0 -62
@@ -1,8 +1,8 @@
1
- import { B as BackupDestination, g as BackupRunReport } from '../types-DTmi7pXp.js';
2
- export { h as BackupChain, i as BackupChainBase, j as BackupChainChange, k as BackupChainPosition, l as BackupChainRecord, m as BackupCycle, n as BackupCycleError, o as BackupCycleOptions, p as BackupCycleStatus, q as BackupFileReport, r as BackupGroupMembership, s as BackupGroupSource, t as BackupLogPosition, u as BackupNodePreference, v as BackupPiece, w as BackupProgress, x as BackupSkip, y as BackupSkipReason, z as BackupToDestinationOptions, C as BackupVerifyResult, E as DEFAULT_CHAIN_NAME, F as chainLogName, G as readBackupChains, H as verifyBackupRecord } from '../types-DTmi7pXp.js';
3
- export { B as BackupCapabilities, a as BackupChainLocation, b as BackupRestorePlan, c as BackupSafeToDeleteOptions, d as backupPiecesSafeToDelete, p as planBackupRestore } from '../database-backup-BwTNiBm2.js';
4
- import { B as BackupRestoreOptions, a as BackupRestoreReport } from '../restore-options-CcMukOLO.js';
5
- export { b as BackupRestoreProgress } from '../restore-options-CcMukOLO.js';
1
+ import { B as BackupDestination, g as BackupRunReport } from '../types-BWyv_N18.js';
2
+ export { h as BackupChain, i as BackupChainBase, j as BackupChainChange, k as BackupChainPosition, l as BackupChainRecord, m as BackupCycle, n as BackupCycleError, o as BackupCycleOptions, p as BackupCycleStatus, q as BackupFileReport, r as BackupGroupMembership, s as BackupGroupSource, t as BackupLogPosition, u as BackupNodePreference, v as BackupPiece, w as BackupProgress, x as BackupSkip, y as BackupSkipReason, z as BackupToDestinationOptions, C as BackupVerifyResult, F as DEFAULT_CHAIN_NAME, G as chainLogName, H as readBackupChains, I as verifyBackupRecord } from '../types-BWyv_N18.js';
3
+ export { B as BackupCapabilities, a as BackupChainLocation, b as BackupRestorePlan, c as BackupSafeToDeleteOptions, d as backupPiecesSafeToDelete, p as planBackupRestore } from '../database-backup-BhM8yJ1A.js';
4
+ import { B as BackupRestoreOptions, a as BackupRestoreReport } from '../restore-options-B6HV_5XT.js';
5
+ export { b as BackupRestoreProgress } from '../restore-options-B6HV_5XT.js';
6
6
  import '../query-types-5B02ixH0.js';
7
7
 
8
8
  /** The byte and piece counts for one file that Sirannon assembles from a destination.
@@ -1,9 +1,11 @@
1
- export { assembleFromDestination, restoreBackup } from '../chunk-L6EX6K27.mjs';
2
- export { backupPiecesSafeToDelete, planBackupRestore } from '../chunk-YW7IKQ7T.mjs';
1
+ export { assembleFromDestination, restoreBackup } from '../chunk-MO4APBYG.mjs';
2
+ export { backupPiecesSafeToDelete, planBackupRestore } from '../chunk-W74HFQFD.mjs';
3
+ import '../chunk-5DKWHT6K.mjs';
3
4
  export { verifyBackupRecord } from '../chunk-DX4WGJL7.mjs';
4
5
  import '../chunk-QGVRRKB2.mjs';
5
6
  import '../chunk-BUWJH3D3.mjs';
6
- export { DEFAULT_CHAIN_NAME, chainLogName, readBackupChains } from '../chunk-CZL4D3BM.mjs';
7
+ export { DEFAULT_CHAIN_NAME, chainLogName, readBackupChains } from '../chunk-6CABU54O.mjs';
7
8
  import '../chunk-BGJ5JVYL.mjs';
8
9
  import '../chunk-HO2XTXVX.mjs';
10
+ import '../chunk-5O4J6LQQ.mjs';
9
11
  import '../chunk-KINDFBQP.mjs';
@@ -1,5 +1,5 @@
1
- import { J as SQLiteConnection, K as BackupFileCopy, L as BackupRunRequest, g as BackupRunReport, O as BackupScheduleRequest } from '../types-DTmi7pXp.js';
2
- export { q as BackupFileReport, P as BackupScheduleOptions, R as RunExclusive } from '../types-DTmi7pXp.js';
1
+ import { L as SQLiteConnection, O as BackupFileCopy, P as BackupRunRequest, g as BackupRunReport, Q as BackupScheduleRequest } from '../types-BWyv_N18.js';
2
+ export { q as BackupFileReport, R as BackupScheduleOptions, U as RunExclusive } from '../types-BWyv_N18.js';
3
3
  import '../query-types-5B02ixH0.js';
4
4
 
5
5
  /** What a runtime supplies so that Sirannon can send a copy to a destination without writing a local file.
@@ -1,5 +1,5 @@
1
- export { BackupScheduler } from '../chunk-BTUD6XQO.mjs';
2
- import '../chunk-W67A4H2E.mjs';
1
+ export { BackupScheduler } from '../chunk-N3MHZV3B.mjs';
2
+ import '../chunk-I4JY5QPA.mjs';
3
3
  import '../chunk-AHCMO4YL.mjs';
4
4
  import '../chunk-BUWJH3D3.mjs';
5
5
  import '../chunk-BGJ5JVYL.mjs';
@@ -0,0 +1,55 @@
1
+ // src/core/driver/statement-text.ts
2
+ var SPACE = 32;
3
+ var TAB = 9;
4
+ var LINE_FEED = 10;
5
+ var VERTICAL_TAB = 11;
6
+ var FORM_FEED = 12;
7
+ var CARRIAGE_RETURN = 13;
8
+ var SEMICOLON = 59;
9
+ var HYPHEN = 45;
10
+ var SLASH = 47;
11
+ var ASTERISK = 42;
12
+ var NO_STATEMENT_MESSAGE = "The supplied SQL string contains no statements";
13
+ function startsSqliteSpace(code) {
14
+ return code === SPACE || code === TAB || code === LINE_FEED || code === FORM_FEED || code === CARRIAGE_RETURN;
15
+ }
16
+ function continuesSqliteSpace(code) {
17
+ return startsSqliteSpace(code) || code === VERTICAL_TAB;
18
+ }
19
+ function holdsNoStatement(sql) {
20
+ const firstNul = sql.indexOf("\0");
21
+ const text = firstNul === -1 ? sql : sql.slice(0, firstNul);
22
+ let index = 0;
23
+ while (index < text.length) {
24
+ const code = text.charCodeAt(index);
25
+ if (startsSqliteSpace(code)) {
26
+ index++;
27
+ while (index < text.length && continuesSqliteSpace(text.charCodeAt(index))) index++;
28
+ continue;
29
+ }
30
+ if (code === SEMICOLON) {
31
+ index++;
32
+ continue;
33
+ }
34
+ const next = text.charCodeAt(index + 1);
35
+ if (code === HYPHEN && next === HYPHEN) {
36
+ const lineEnd = text.indexOf("\n", index + 2);
37
+ if (lineEnd === -1) return true;
38
+ index = lineEnd;
39
+ continue;
40
+ }
41
+ if (code === SLASH && next === ASTERISK && index + 2 < text.length) {
42
+ const commentEnd = text.indexOf("*/", index + 2);
43
+ if (commentEnd === -1) return true;
44
+ index = commentEnd + 2;
45
+ continue;
46
+ }
47
+ return false;
48
+ }
49
+ return true;
50
+ }
51
+ function assertHoldsStatement(sql) {
52
+ if (holdsNoStatement(sql)) throw new RangeError(NO_STATEMENT_MESSAGE);
53
+ }
54
+
55
+ export { assertHoldsStatement };
@@ -1,9 +1,9 @@
1
- import { selectOrdinaryTableName, deleteReplacedRowsOfTable, selectMinChangeSeqSql, selectMaxChangeSeq, deleteChangesBeforeUpToSeqSql, deleteAllReplacedRows, selectTableExists, ensureChangesTable, ensureReplacedRowsTable, ensureMetaTable, insertMetaValueIfAbsent, selectMetaValue, selectTriggerSql, selectChangesAfterSeqSql, selectTablesChangesInRangeSql, tableInfoRows, primaryKeyColumns, tableColumnsWithGenerated, tableIndexList, indexColumns, selectTableSql, TABLE_XINFO_HIDDEN, deleteExpiredDeviceCursors, selectMinDeviceCursorBoundary, SEQ_STRING_RE, selectIndexSql } from './chunk-RPWBYASV.mjs';
2
- import { CDC_TRIGGER_PREFIX, CHANGES_TABLE, isReservedIdentifier, META_TABLE, encodeTaggedValues, REPLACED_ROWS_TABLE, changedAtToEventTimestamp, decodeTaggedValues, DEVICE_CURSORS_TABLE, SAFE_INT_BOUND_TEXT } from './chunk-JFVEGN7J.mjs';
1
+ import { selectOrdinaryTableName, deleteReplacedRowsOfTable, selectMinChangeSeqSql, selectMaxChangeSeq, deleteChangesBeforeUpToSeqSql, deleteAllReplacedRows, selectTableExists, ensureChangesTable, ensureReplacedRowsTable, ensureMetaTable, insertMetaValueIfAbsent, selectMetaValue, selectTriggerSql, selectChangesAfterSeqSql, selectTablesChangesInRangeSql, tableInfoRows, primaryKeyColumns, tableColumnsWithGenerated, tableIndexList, indexColumns, selectTableSql, TABLE_XINFO_HIDDEN, deleteExpiredDeviceCursors, selectMinDeviceCursorBoundary, SEQ_STRING_RE, selectIndexSql } from './chunk-MTOQ6QIS.mjs';
2
+ import { CDC_TRIGGER_PREFIX, CHANGES_TABLE, isReservedIdentifier, META_TABLE, encodeTaggedValues, REPLACED_ROWS_TABLE, changedAtToEventTimestamp, decodeTaggedValues, DEVICE_CURSORS_TABLE, SAFE_INT_BOUND_TEXT } from './chunk-ZAUHAJ4G.mjs';
3
3
  import { tokenizeSql, isKeyword } from './chunk-SEL4A4WT.mjs';
4
- import { synchronousPragmaValue } from './chunk-YCMB6GR4.mjs';
5
4
  import { randomHex } from './chunk-BGJ5JVYL.mjs';
6
5
  import { invokeCallerCallback, reportCallerFailure, toError } from './chunk-HO2XTXVX.mjs';
6
+ import { synchronousPragmaValue } from './chunk-YCMB6GR4.mjs';
7
7
  import { SirannonError, ForbiddenSqlError, CDCError, MigrationError } from './chunk-KINDFBQP.mjs';
8
8
 
9
9
  // src/core/bulk-load.ts
@@ -666,6 +666,28 @@ function buildJsonObject(columns, ref) {
666
666
  function escStr(name) {
667
667
  return name.replace(/'/g, "''");
668
668
  }
669
+ async function installKeyChangeCaptureTriggers(conn, triggers, table, log, keys) {
670
+ const logOf = (row) => `INSERT INTO "${escId(log)}" VALUES (${keys.map((key) => `${row}.${key}`).join(", ")});`;
671
+ const on = `main."${escId(table)}"`;
672
+ await conn.exec(`CREATE TEMP TRIGGER "${escId(triggers.insert)}" AFTER INSERT ON ${on} BEGIN ${logOf("NEW")} END`);
673
+ await conn.exec(
674
+ `CREATE TEMP TRIGGER "${escId(triggers.update)}" AFTER UPDATE ON ${on} BEGIN ${logOf("OLD")} ${logOf("NEW")} END`
675
+ );
676
+ await conn.exec(`CREATE TEMP TRIGGER "${escId(triggers.delete)}" AFTER DELETE ON ${on} BEGIN ${logOf("OLD")} END`);
677
+ }
678
+ async function dropKeyChangeCaptureTriggers(conn, triggers) {
679
+ for (const name of [triggers.insert, triggers.update, triggers.delete]) {
680
+ await conn.exec(`DROP TRIGGER IF EXISTS temp."${escId(name)}"`);
681
+ }
682
+ }
683
+ async function dropMainSchemaTriggers(conn) {
684
+ const triggers = await (await conn.prepare("SELECT name, sql FROM main.sqlite_schema WHERE type = 'trigger' AND sql IS NOT NULL")).all();
685
+ for (const trigger of triggers) await conn.exec(`DROP TRIGGER main."${escId(trigger.name)}"`);
686
+ return triggers.map((trigger) => trigger.sql);
687
+ }
688
+ async function createMainSchemaTriggers(conn, statements) {
689
+ for (const statement of statements) await conn.exec(statement);
690
+ }
669
691
 
670
692
  // src/core/cdc/change-tracker.ts
671
693
  var DEFAULT_RETENTION_MS = 36e5;
@@ -1155,7 +1177,7 @@ var SubscriptionBuilderImpl = class {
1155
1177
  return this.manager.subscribe(this.table, this.conditions, callback, options);
1156
1178
  }
1157
1179
  };
1158
- function startPolling(conn, tracker, manager, intervalMs, onError, runExclusive, deviceRetention = {
1180
+ function startPolling(connection, tracker, manager, intervalMs, onError, runExclusive, deviceRetention = {
1159
1181
  cursorRetentionMs: DEFAULT_DEVICE_CURSOR_RETENTION_MS,
1160
1182
  maxChangesHeldForDevice: 0
1161
1183
  }) {
@@ -1170,7 +1192,7 @@ function startPolling(conn, tracker, manager, intervalMs, onError, runExclusive,
1170
1192
  if (polling) return;
1171
1193
  polling = true;
1172
1194
  try {
1173
- const events = await exclusive(() => tracker.poll(conn));
1195
+ const events = await exclusive(() => tracker.poll(connection()));
1174
1196
  if (events.length > 0) {
1175
1197
  manager.dispatch(events);
1176
1198
  }
@@ -1180,6 +1202,7 @@ function startPolling(conn, tracker, manager, intervalMs, onError, runExclusive,
1180
1202
  if (tickCount >= CLEANUP_INTERVAL_TICKS) {
1181
1203
  tickCount = 0;
1182
1204
  await exclusive(async () => {
1205
+ const conn = connection();
1183
1206
  await applyDeviceCursorBoundary(conn, tracker, deviceRetention);
1184
1207
  await tracker.cleanup(conn);
1185
1208
  });
@@ -1371,4 +1394,4 @@ var TransactionGrouper = class {
1371
1394
  }
1372
1395
  };
1373
1396
 
1374
- export { ChangeTracker, DEVICE_SYNC_PUSHED_SEQ_META_KEY, PrimedSubscription, ROWID_ALIASES, SubscriptionBuilderImpl, SubscriptionManager, TransactionGrouper, applyDeviceCursorBoundary, dropCdcTriggers, ensureCdcEpoch, filteredChange, isBulkLoadDurability, migrationChecksum, migrationContentChecksum, needsResync, parseColumnDefinitions, reconcileMigrationChecksums, resolveDeviceRetentionPolicy, runBulkLoad, startPolling };
1397
+ export { ChangeTracker, DEVICE_SYNC_PUSHED_SEQ_META_KEY, PrimedSubscription, ROWID_ALIASES, SubscriptionBuilderImpl, SubscriptionManager, TransactionGrouper, applyDeviceCursorBoundary, createMainSchemaTriggers, dropCdcTriggers, dropKeyChangeCaptureTriggers, dropMainSchemaTriggers, ensureCdcEpoch, escId, filteredChange, installKeyChangeCaptureTriggers, isBulkLoadDurability, migrationChecksum, migrationContentChecksum, needsResync, parseColumnDefinitions, reconcileMigrationChecksums, resolveDeviceRetentionPolicy, runBulkLoad, startPolling };
@@ -883,7 +883,7 @@ var AckPayload = {
883
883
  }
884
884
  };
885
885
  function createBaseHello() {
886
- return { nodeId: "", role: "", groupId: "", primaryTerm: 0n, protocolVersion: "" };
886
+ return { nodeId: "", role: "", groupId: "", primaryTerm: 0n, protocolVersion: "", encrypted: false };
887
887
  }
888
888
  var Hello = {
889
889
  encode(message, writer = new BinaryWriter()) {
@@ -905,6 +905,9 @@ var Hello = {
905
905
  if (message.protocolVersion !== "") {
906
906
  writer.uint32(42).string(message.protocolVersion);
907
907
  }
908
+ if (message.encrypted !== false) {
909
+ writer.uint32(48).bool(message.encrypted);
910
+ }
908
911
  return writer;
909
912
  },
910
913
  decode(input, length) {
@@ -949,6 +952,13 @@ var Hello = {
949
952
  message.protocolVersion = reader.string();
950
953
  continue;
951
954
  }
955
+ case 6: {
956
+ if (tag !== 48) {
957
+ break;
958
+ }
959
+ message.encrypted = reader.bool();
960
+ continue;
961
+ }
952
962
  }
953
963
  if ((tag & 7) === 4 || tag === 0) {
954
964
  break;
@@ -963,7 +973,8 @@ var Hello = {
963
973
  role: isSet(object.role) ? globalThis.String(object.role) : "",
964
974
  groupId: isSet(object.groupId) ? globalThis.String(object.groupId) : isSet(object.group_id) ? globalThis.String(object.group_id) : "",
965
975
  primaryTerm: isSet(object.primaryTerm) ? BigInt(object.primaryTerm) : isSet(object.primary_term) ? BigInt(object.primary_term) : 0n,
966
- protocolVersion: isSet(object.protocolVersion) ? globalThis.String(object.protocolVersion) : isSet(object.protocol_version) ? globalThis.String(object.protocol_version) : ""
976
+ protocolVersion: isSet(object.protocolVersion) ? globalThis.String(object.protocolVersion) : isSet(object.protocol_version) ? globalThis.String(object.protocol_version) : "",
977
+ encrypted: isSet(object.encrypted) ? globalThis.Boolean(object.encrypted) : false
967
978
  };
968
979
  },
969
980
  toJSON(message) {
@@ -983,6 +994,9 @@ var Hello = {
983
994
  if (message.protocolVersion !== "") {
984
995
  obj.protocolVersion = message.protocolVersion;
985
996
  }
997
+ if (message.encrypted !== false) {
998
+ obj.encrypted = message.encrypted;
999
+ }
986
1000
  return obj;
987
1001
  },
988
1002
  create(base) {
@@ -995,6 +1009,7 @@ var Hello = {
995
1009
  message.groupId = object.groupId ?? "";
996
1010
  message.primaryTerm = object.primaryTerm ?? 0n;
997
1011
  message.protocolVersion = object.protocolVersion ?? "";
1012
+ message.encrypted = object.encrypted ?? false;
998
1013
  return message;
999
1014
  }
1000
1015
  };
@@ -2639,4 +2654,24 @@ function buildChannelCreds(options) {
2639
2654
  return credentials.createSsl(ca, key, cert);
2640
2655
  }
2641
2656
 
2642
- export { ReplicationClient, ReplicationService, buildChannelCreds, buildServerCreds };
2657
+ // src/transport/grpc/hello.ts
2658
+ function localHello(t) {
2659
+ return {
2660
+ nodeId: t.localNodeId,
2661
+ role: t.localRole,
2662
+ groupId: t.localGroupId ?? "",
2663
+ primaryTerm: t.localPrimaryTerm ?? 0n,
2664
+ protocolVersion: t.localProtocolVersion ?? "",
2665
+ encrypted: t.localEncrypted
2666
+ };
2667
+ }
2668
+ function peerHelloFields(hello) {
2669
+ return {
2670
+ groupId: hello.groupId || void 0,
2671
+ primaryTerm: hello.primaryTerm === 0n ? void 0 : hello.primaryTerm,
2672
+ protocolVersion: hello.protocolVersion || void 0,
2673
+ encrypted: hello.encrypted === true
2674
+ };
2675
+ }
2676
+
2677
+ export { ReplicationClient, ReplicationService, buildChannelCreds, buildServerCreds, localHello, peerHelloFields };
@@ -94,6 +94,54 @@ function getDotPath(issue) {
94
94
  return null;
95
95
  }
96
96
  // @__NO_SIDE_EFFECTS__
97
+ function check(requirement, message$1) {
98
+ return {
99
+ kind: "validation",
100
+ type: "check",
101
+ reference: check,
102
+ async: false,
103
+ expects: null,
104
+ requirement,
105
+ message: message$1,
106
+ "~run"(dataset, config$1) {
107
+ if (dataset.typed && !this.requirement(dataset.value)) _addIssue(this, "input", dataset, config$1);
108
+ return dataset;
109
+ }
110
+ };
111
+ }
112
+ // @__NO_SIDE_EFFECTS__
113
+ function maxLength(requirement, message$1) {
114
+ return {
115
+ kind: "validation",
116
+ type: "max_length",
117
+ reference: maxLength,
118
+ async: false,
119
+ expects: `<=${requirement}`,
120
+ requirement,
121
+ message: message$1,
122
+ "~run"(dataset, config$1) {
123
+ if (dataset.typed && dataset.value.length > this.requirement) _addIssue(this, "length", dataset, config$1, { received: `${dataset.value.length}` });
124
+ return dataset;
125
+ }
126
+ };
127
+ }
128
+ // @__NO_SIDE_EFFECTS__
129
+ function minLength(requirement, message$1) {
130
+ return {
131
+ kind: "validation",
132
+ type: "min_length",
133
+ reference: minLength,
134
+ async: false,
135
+ expects: `>=${requirement}`,
136
+ requirement,
137
+ message: message$1,
138
+ "~run"(dataset, config$1) {
139
+ if (dataset.typed && dataset.value.length < this.requirement) _addIssue(this, "length", dataset, config$1, { received: `${dataset.value.length}` });
140
+ return dataset;
141
+ }
142
+ };
143
+ }
144
+ // @__NO_SIDE_EFFECTS__
97
145
  function regex(requirement, message$1) {
98
146
  return {
99
147
  kind: "validation",
@@ -163,6 +211,23 @@ function array(item, message$1) {
163
211
  });
164
212
  }
165
213
  // @__NO_SIDE_EFFECTS__
214
+ function instance(class_, message$1) {
215
+ return _standardSchema({
216
+ kind: "schema",
217
+ type: "instance",
218
+ reference: instance,
219
+ expects: class_.name,
220
+ async: false,
221
+ class: class_,
222
+ message: message$1,
223
+ "~run"(dataset, config$1) {
224
+ if (dataset.value instanceof this.class) dataset.typed = true;
225
+ else _addIssue(this, "type", dataset, config$1);
226
+ return dataset;
227
+ }
228
+ });
229
+ }
230
+ // @__NO_SIDE_EFFECTS__
166
231
  function nullable(wrapped, default_) {
167
232
  return _standardSchema({
168
233
  kind: "schema",
@@ -383,6 +448,49 @@ function string(message$1) {
383
448
  });
384
449
  }
385
450
  // @__NO_SIDE_EFFECTS__
451
+ function _subIssues(datasets) {
452
+ let issues;
453
+ if (datasets) for (const dataset of datasets) if (issues) for (const issue of dataset.issues) issues.push(issue);
454
+ else issues = dataset.issues;
455
+ return issues;
456
+ }
457
+ // @__NO_SIDE_EFFECTS__
458
+ function union(options, message$1) {
459
+ return _standardSchema({
460
+ kind: "schema",
461
+ type: "union",
462
+ reference: union,
463
+ expects: /* @__PURE__ */ _joinExpects(options.map((option) => option.expects), "|"),
464
+ async: false,
465
+ options,
466
+ message: message$1,
467
+ "~run"(dataset, config$1) {
468
+ let validDataset;
469
+ let typedDatasets;
470
+ let untypedDatasets;
471
+ for (const schema of this.options) {
472
+ const optionDataset = schema["~run"]({ value: dataset.value }, config$1);
473
+ if (optionDataset.typed) if (optionDataset.issues) if (typedDatasets) typedDatasets.push(optionDataset);
474
+ else typedDatasets = [optionDataset];
475
+ else {
476
+ validDataset = optionDataset;
477
+ break;
478
+ }
479
+ else if (untypedDatasets) untypedDatasets.push(optionDataset);
480
+ else untypedDatasets = [optionDataset];
481
+ }
482
+ if (validDataset) return validDataset;
483
+ if (typedDatasets) {
484
+ if (typedDatasets.length === 1) return typedDatasets[0];
485
+ _addIssue(this, "type", dataset, config$1, { issues: /* @__PURE__ */ _subIssues(typedDatasets) });
486
+ dataset.typed = true;
487
+ } else if (untypedDatasets?.length === 1) return untypedDatasets[0];
488
+ else _addIssue(this, "type", dataset, config$1, { issues: /* @__PURE__ */ _subIssues(untypedDatasets) });
489
+ return dataset;
490
+ }
491
+ });
492
+ }
493
+ // @__NO_SIDE_EFFECTS__
386
494
  function unknown() {
387
495
  return _standardSchema({
388
496
  kind: "schema",
@@ -436,4 +544,4 @@ function summarize(issues) {
436
544
  return summary;
437
545
  }
438
546
 
439
- export { array, getDotPath, nullable, number, object, optional, picklist, pipe, record, regex, safeParse, string, summarize, unknown };
547
+ export { array, check, getDotPath, instance, maxLength, minLength, nullable, number, object, optional, picklist, pipe, record, regex, safeParse, string, summarize, union, unknown };
@@ -0,0 +1,35 @@
1
+ import { SirannonError } from './chunk-KINDFBQP.mjs';
2
+
3
+ // src/core/encryption/errors.ts
4
+ var EncryptionError = class extends SirannonError {
5
+ /**
6
+ * Returns an error with an encryption code, and the failure that caused it when there is one.
7
+ *
8
+ * @param message - What failed, written without the key.
9
+ * @param code - The machine-readable code.
10
+ * @param cause - The failure that caused this error, such as the error that a key function threw.
11
+ */
12
+ constructor(message, code, cause) {
13
+ super(message, code);
14
+ this.name = "EncryptionError";
15
+ if (cause !== void 0) {
16
+ Object.defineProperty(this, "cause", { value: cause, enumerable: false, writable: true, configurable: true });
17
+ }
18
+ }
19
+ };
20
+ function keyRejected(path, cause) {
21
+ return new EncryptionError(
22
+ `The key failed the authentication check of the first page of the database ${path}. Either the key is wrong, or the file comes from another encryption scheme: the better-sqlite3 driver encrypts with AEGIS-256 and the Expo driver encrypts with SQLCipher 4, and neither opens the other's files`,
23
+ "ENCRYPTION_KEY_REJECTED",
24
+ cause
25
+ );
26
+ }
27
+ function refuseEncryptedOpen(driverName, options) {
28
+ if (options?.encryption === void 0) return;
29
+ throw new EncryptionError(
30
+ `The ${driverName} driver opens SQLite through a library that Sirannon cannot replace with an encrypting build, so it cannot open an encrypted database`,
31
+ "ENCRYPTION_UNSUPPORTED"
32
+ );
33
+ }
34
+
35
+ export { EncryptionError, keyRejected, refuseEncryptedOpen };
@@ -20,11 +20,11 @@ function isBackupChainHead(value) {
20
20
  }
21
21
  function isBackupChainBase(value) {
22
22
  const base = value;
23
- return base?.kind === "full" && typeof base.chainId === "string" && typeof base.name === "string" && typeof base.runId === "string" && isWholeNumber(base.finishedAt) && isWholeNumber(base.pieceCount) && isAtLeastOne(base.pieceBytes) && isWholeNumber(base.bytesWritten) && isTextOrAbsent(base.fingerprint);
23
+ return base?.kind === "full" && typeof base.chainId === "string" && typeof base.name === "string" && typeof base.runId === "string" && isWholeNumber(base.finishedAt) && isWholeNumber(base.pieceCount) && isAtLeastOne(base.pieceBytes) && isWholeNumber(base.bytesWritten) && isTextOrAbsent(base.fingerprint) && isTextOrAbsent(base.keyId);
24
24
  }
25
25
  function isBackupChainChange(value) {
26
26
  const change = value;
27
- return change?.kind === "change" && typeof change.chainId === "string" && typeof change.name === "string" && typeof change.runId === "string" && isAtLeastOne(change.sequence) && isBackupChainPosition(change.position) && isWholeNumber(change.capturedAt) && isAtLeastOne(change.frameCount) && isWholeNumber(change.pieceCount) && isAtLeastOne(change.pieceBytes) && isWholeNumber(change.bytesWritten) && typeof change.checkpointed === "boolean" && isTextOrAbsent(change.fingerprint);
27
+ return change?.kind === "change" && typeof change.chainId === "string" && typeof change.name === "string" && typeof change.runId === "string" && isAtLeastOne(change.sequence) && isBackupChainPosition(change.position) && isWholeNumber(change.capturedAt) && isAtLeastOne(change.frameCount) && isWholeNumber(change.pieceCount) && isAtLeastOne(change.pieceBytes) && isWholeNumber(change.bytesWritten) && typeof change.checkpointed === "boolean" && isTextOrAbsent(change.fingerprint) && isTextOrAbsent(change.keyId);
28
28
  }
29
29
 
30
30
  // src/core/backup/chain.ts
@@ -1,6 +1,13 @@
1
1
  import { SirannonError } from './chunk-KINDFBQP.mjs';
2
2
 
3
3
  // src/core/driver/define.ts
4
+ function boundFileSystem(files) {
5
+ return Object.freeze({
6
+ readStart: files.readStart.bind(files),
7
+ remove: files.remove.bind(files),
8
+ rename: files.rename.bind(files)
9
+ });
10
+ }
4
11
  function defineDriver(config) {
5
12
  if (!config.capabilities || typeof config.open !== "function") {
6
13
  throw new SirannonError("Driver must define capabilities and open()", "INVALID_DRIVER");
@@ -9,22 +16,15 @@ function defineDriver(config) {
9
16
  throw new SirannonError("Driver worker entry must define a string specifier", "INVALID_DRIVER");
10
17
  }
11
18
  return Object.freeze({
12
- capabilities: Object.freeze({ ...config.capabilities }),
19
+ capabilities: Object.freeze({ ...config.capabilities, encryption: config.capabilities.encryption === true }),
13
20
  open: config.open.bind(config),
14
21
  ...config.worker ? { worker: Object.freeze({ ...config.worker }) } : {},
15
22
  ...config.startWriterHost ? { startWriterHost: config.startWriterHost.bind(config) } : {},
16
23
  ...config.createWriterContext ? { createWriterContext: config.createWriterContext.bind(config) } : {},
17
24
  ...config.createBackupEngine ? { createBackupEngine: config.createBackupEngine.bind(config) } : {},
18
- ...config.resolveExtensionPath ? { resolveExtensionPath: config.resolveExtensionPath.bind(config) } : {}
25
+ ...config.resolveExtensionPath ? { resolveExtensionPath: config.resolveExtensionPath.bind(config) } : {},
26
+ ...config.files ? { files: boundFileSystem(config.files) } : {}
19
27
  });
20
28
  }
21
29
 
22
- // src/core/backup/cycle-options.ts
23
- var DEFAULT_CAPTURE_INTERVAL_MS = 6e4;
24
- var DEFAULT_FULL_COPY_INTERVAL_MS = 24 * 60 * 60 * 1e3;
25
- var DEFAULT_BACKUP_NAME_PREFIX = "sirannon-backup";
26
- function defaultStagingDir(sourcePath) {
27
- return `${sourcePath}-backup`;
28
- }
29
-
30
- export { DEFAULT_BACKUP_NAME_PREFIX, DEFAULT_CAPTURE_INTERVAL_MS, DEFAULT_FULL_COPY_INTERVAL_MS, defaultStagingDir, defineDriver };
30
+ export { defineDriver };
@@ -19,6 +19,7 @@ function registerPeer(connectedPeers, peerConnectedHandler, nodeId, role, metada
19
19
  role,
20
20
  primaryTerm: metadata.primaryTerm,
21
21
  protocolVersion: metadata.protocolVersion,
22
+ encrypted: metadata.encrypted === true,
22
23
  joinedAt: Date.now(),
23
24
  lastSeenAt: Date.now(),
24
25
  lastAckedSeq: 0n
@@ -0,0 +1,99 @@
1
+ import { withRowCounts, rowCountsOf } from './chunk-E2GRX4ZD.mjs';
2
+ import { EncryptionError } from './chunk-5O4J6LQQ.mjs';
3
+ import { SirannonError } from './chunk-KINDFBQP.mjs';
4
+
5
+ // src/core/encryption/page-errors.ts
6
+ var PAGE_AUTHENTICATION_FAILURE_CODE = /^SQLITE_(CORRUPT|NOTADB)/;
7
+ var PAGE_AUTHENTICATION_FAILURE_MESSAGE = /database disk image is malformed|file is not a database/i;
8
+ var PAGE_CORRUPT_MESSAGE = "A page of the encrypted database failed its authentication check, so Sirannon returned no rows from it. The file was changed or damaged outside SQLite; restore it from a backup";
9
+ function failsPageAuthentication(err) {
10
+ if (err instanceof SirannonError || typeof err !== "object" || err === null) return false;
11
+ const { code, message } = err;
12
+ if (typeof code === "string" && PAGE_AUTHENTICATION_FAILURE_CODE.test(code)) return true;
13
+ return typeof message === "string" && PAGE_AUTHENTICATION_FAILURE_MESSAGE.test(message);
14
+ }
15
+ function asPageError(err) {
16
+ if (!failsPageAuthentication(err)) return err;
17
+ return withRowCounts(new EncryptionError(PAGE_CORRUPT_MESSAGE, "ENCRYPTED_PAGE_CORRUPT", err), rowCountsOf(err));
18
+ }
19
+ async function guarded(call) {
20
+ try {
21
+ return await call();
22
+ } catch (err) {
23
+ throw asPageError(err);
24
+ }
25
+ }
26
+ function guardedStatement(statement) {
27
+ const { allRaw, allCounted, getCounted, allRawCounted } = statement;
28
+ const result = {
29
+ all: (...params) => guarded(() => statement.all(...params)),
30
+ get: (...params) => guarded(() => statement.get(...params)),
31
+ run: (...params) => guarded(() => statement.run(...params))
32
+ };
33
+ if (allRaw)
34
+ result.allRaw = (...params) => guarded(() => allRaw.call(statement, ...params));
35
+ if (allCounted) {
36
+ result.allCounted = (...params) => guarded(() => allCounted.call(statement, ...params));
37
+ }
38
+ if (getCounted) {
39
+ result.getCounted = (...params) => guarded(() => getCounted.call(statement, ...params));
40
+ }
41
+ if (allRawCounted) {
42
+ result.allRawCounted = (...params) => guarded(() => allRawCounted.call(statement, ...params));
43
+ }
44
+ return result;
45
+ }
46
+ function guardedOutcomes(outcomes) {
47
+ return outcomes.map((outcome) => {
48
+ if (outcome.ok || !failsPageAuthentication(outcome.error)) return outcome;
49
+ return {
50
+ ...outcome,
51
+ error: {
52
+ ...outcome.error,
53
+ message: PAGE_CORRUPT_MESSAGE,
54
+ name: "EncryptionError",
55
+ code: "ENCRYPTED_PAGE_CORRUPT"
56
+ }
57
+ };
58
+ });
59
+ }
60
+ function reportEncryptedPageFailures(connection) {
61
+ const guardedConnection = {
62
+ exec: (sql) => guarded(() => connection.exec(sql)),
63
+ prepare: async (sql) => guardedStatement(await guarded(() => connection.prepare(sql))),
64
+ transaction: (fn) => guarded(() => connection.transaction(() => fn(guardedConnection))),
65
+ close: () => connection.close(),
66
+ ...connection.copyRunsOffCallerThread === void 0 ? {} : { copyRunsOffCallerThread: connection.copyRunsOffCallerThread }
67
+ };
68
+ const {
69
+ loadExtension,
70
+ copyDatabase,
71
+ runBatch,
72
+ runBatchSummary,
73
+ runGroup,
74
+ startChangeCapture,
75
+ takeCapturedChanges,
76
+ stopChangeCapture,
77
+ applyCapturedChanges
78
+ } = connection;
79
+ if (loadExtension) guardedConnection.loadExtension = (path) => loadExtension.call(connection, path);
80
+ if (copyDatabase) guardedConnection.copyDatabase = (request) => guarded(() => copyDatabase.call(connection, request));
81
+ if (runBatch) guardedConnection.runBatch = (sql, batch) => guarded(() => runBatch.call(connection, sql, batch));
82
+ if (runBatchSummary) {
83
+ guardedConnection.runBatchSummary = (sql, batch) => guarded(() => runBatchSummary.call(connection, sql, batch));
84
+ }
85
+ if (runGroup) {
86
+ guardedConnection.runGroup = async (units, opening) => guardedOutcomes(await guarded(() => runGroup.call(connection, units, opening)));
87
+ }
88
+ if (startChangeCapture) guardedConnection.startChangeCapture = () => startChangeCapture.call(connection);
89
+ if (takeCapturedChanges) {
90
+ guardedConnection.takeCapturedChanges = () => guarded(() => takeCapturedChanges.call(connection));
91
+ }
92
+ if (stopChangeCapture) guardedConnection.stopChangeCapture = () => stopChangeCapture.call(connection);
93
+ if (applyCapturedChanges) {
94
+ guardedConnection.applyCapturedChanges = (changes) => guarded(() => applyCapturedChanges.call(connection, changes));
95
+ }
96
+ return guardedConnection;
97
+ }
98
+
99
+ export { reportEncryptedPageFailures };
@@ -5,7 +5,13 @@ var stateByTransport = /* @__PURE__ */ new WeakMap();
5
5
  function stateFor(transport) {
6
6
  const existing = stateByTransport.get(transport);
7
7
  if (existing) return existing;
8
- const created = { timers: /* @__PURE__ */ new Map(), delays: /* @__PURE__ */ new Map(), dialled: /* @__PURE__ */ new Map(), wanted: null };
8
+ const created = {
9
+ timers: /* @__PURE__ */ new Map(),
10
+ delays: /* @__PURE__ */ new Map(),
11
+ refusalDelays: /* @__PURE__ */ new Map(),
12
+ dialled: /* @__PURE__ */ new Map(),
13
+ wanted: null
14
+ };
9
15
  stateByTransport.set(transport, created);
10
16
  return created;
11
17
  }
@@ -21,6 +27,7 @@ function replaceWantedEndpoints(transport, endpoints) {
21
27
  clearTimeout(timer);
22
28
  state.timers.delete(endpoint);
23
29
  state.delays.delete(endpoint);
30
+ state.refusalDelays.delete(endpoint);
24
31
  }
25
32
  const aborts = [];
26
33
  for (const [endpoint, abort] of state.dialled) {
@@ -48,12 +55,24 @@ function releaseEndpoint(transport, endpoint) {
48
55
  function noteEndpointReachable(transport, endpoint) {
49
56
  stateFor(transport).delays.delete(endpoint);
50
57
  }
58
+ function noteEndpointRefused(transport, endpoint) {
59
+ const state = stateFor(transport);
60
+ const previous = state.refusalDelays.get(endpoint);
61
+ state.refusalDelays.set(
62
+ endpoint,
63
+ previous === void 0 ? RECONNECT_MIN_DELAY_MS * 2 : Math.min(previous * 2, RECONNECT_MAX_DELAY_MS)
64
+ );
65
+ }
66
+ function noteEndpointServing(transport, endpoint) {
67
+ stateFor(transport).refusalDelays.delete(endpoint);
68
+ }
51
69
  function scheduleEndpointRedial(transport, endpoint, redial) {
52
70
  if (!transport.connected) return;
53
71
  const state = stateFor(transport);
54
72
  if (state.timers.has(endpoint) || !isWanted(state, endpoint)) return;
55
- const delayMs = state.delays.get(endpoint) ?? RECONNECT_MIN_DELAY_MS;
56
- state.delays.set(endpoint, Math.min(delayMs * 2, RECONNECT_MAX_DELAY_MS));
73
+ const backoffMs = state.delays.get(endpoint) ?? RECONNECT_MIN_DELAY_MS;
74
+ state.delays.set(endpoint, Math.min(backoffMs * 2, RECONNECT_MAX_DELAY_MS));
75
+ const delayMs = Math.max(backoffMs, state.refusalDelays.get(endpoint) ?? 0);
57
76
  const timer = setTimeout(() => {
58
77
  state.timers.delete(endpoint);
59
78
  if (!transport.connected || !isWanted(state, endpoint)) return;
@@ -69,6 +88,7 @@ function stopEndpointDialling(transport) {
69
88
  }
70
89
  state.timers.clear();
71
90
  state.delays.clear();
91
+ state.refusalDelays.clear();
72
92
  state.wanted = null;
73
93
  const aborts = [...state.dialled.values()];
74
94
  state.dialled.clear();
@@ -77,4 +97,4 @@ function stopEndpointDialling(transport) {
77
97
  }
78
98
  }
79
99
 
80
- export { claimEndpoint, noteEndpointReachable, registerEndpointAbort, releaseEndpoint, replaceWantedEndpoints, scheduleEndpointRedial, stopEndpointDialling };
100
+ export { claimEndpoint, noteEndpointReachable, noteEndpointRefused, noteEndpointServing, registerEndpointAbort, releaseEndpoint, replaceWantedEndpoints, scheduleEndpointRedial, stopEndpointDialling };