@delali/sirannon-db 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +117 -969
  2. package/dist/backup-scheduler/index.d.ts +1 -1
  3. package/dist/backup-scheduler/index.mjs +2 -2
  4. package/dist/baseline-Br77Fnhb.d.ts +6 -0
  5. package/dist/{change-tracker-CbmaMO-N.d.ts → change-tracker-DKRVUC3l.d.ts} +5 -8
  6. package/dist/{chunk-DJLX6CAE.mjs → chunk-4ISB7XMA.mjs} +2 -1
  7. package/dist/chunk-5NOIGN5Y.mjs +18 -0
  8. package/dist/chunk-67M7KAH6.mjs +90 -0
  9. package/dist/{chunk-GEZUUIKV.mjs → chunk-D7LAYTKN.mjs} +5 -2
  10. package/dist/{chunk-4IGMIJQK.mjs → chunk-EBJXPQQO.mjs} +59 -20
  11. package/dist/{chunk-BNUTBHHH.mjs → chunk-FHWTZFI4.mjs} +1 -1
  12. package/dist/chunk-GVCNMPOS.mjs +130 -0
  13. package/dist/chunk-H237TXZW.mjs +1407 -0
  14. package/dist/{chunk-HHRMRFFR.mjs → chunk-H6PIVVDN.mjs} +353 -142
  15. package/dist/chunk-HR5CWTLC.mjs +661 -0
  16. package/dist/chunk-JU64Y7HM.mjs +299 -0
  17. package/dist/{chunk-CW6S3WL5.mjs → chunk-JZGINXTN.mjs} +7 -5
  18. package/dist/{chunk-CJLYFDP5.mjs → chunk-LFZ37BSX.mjs} +1 -1
  19. package/dist/chunk-LNY2VVHE.mjs +18 -0
  20. package/dist/chunk-NVQS53NT.mjs +665 -0
  21. package/dist/{chunk-H5AB6NIR.mjs → chunk-O7SLN3GI.mjs} +2 -15
  22. package/dist/chunk-OQVZBEBY.mjs +283 -0
  23. package/dist/{chunk-VLTICJOD.mjs → chunk-PIKHN33N.mjs} +1 -1
  24. package/dist/chunk-TJF5GZSV.mjs +187 -0
  25. package/dist/chunk-TUD5CJ76.mjs +126 -0
  26. package/dist/{chunk-YPYVQJ4C.mjs → chunk-UC3SCMIN.mjs} +9 -1
  27. package/dist/client/index.d.ts +216 -321
  28. package/dist/client/index.mjs +1192 -1342
  29. package/dist/client/topology.d.ts +61 -0
  30. package/dist/client/topology.mjs +682 -0
  31. package/dist/client-base-CLWmH5Ln.d.ts +234 -0
  32. package/dist/codegen/cli.d.ts +2 -0
  33. package/dist/codegen/cli.mjs +11 -0
  34. package/dist/codegen/index.d.ts +30 -0
  35. package/dist/codegen/index.mjs +5 -0
  36. package/dist/core/index.d.ts +37 -16
  37. package/dist/core/index.mjs +2322 -298
  38. package/dist/core/writer-worker.mjs +28 -6
  39. package/dist/{database-DuGp0Rtr.d.ts → database-BY0L5Q2n.d.ts} +59 -14
  40. package/dist/driver/better-sqlite3.d.ts +1 -1
  41. package/dist/driver/better-sqlite3.mjs +6 -6
  42. package/dist/driver/node.d.ts +1 -1
  43. package/dist/driver/node.mjs +6 -6
  44. package/dist/driver/wa-sqlite.d.ts +1 -136
  45. package/dist/{errors-5Nf5ZAEC.d.ts → errors-Bw5MdNCu.d.ts} +5 -1
  46. package/dist/file-migrations/index.d.ts +7 -5
  47. package/dist/file-migrations/index.mjs +42 -37
  48. package/dist/operation-registry-9DcvxcE5.d.ts +45 -0
  49. package/dist/primary-wins-DPAm2AKG.d.ts +61 -0
  50. package/dist/protocol-6KrSq2Hy.d.ts +66 -0
  51. package/dist/react/index.d.ts +20 -0
  52. package/dist/react/index.mjs +137 -0
  53. package/dist/replication/coordinator/etcd.d.ts +5 -3
  54. package/dist/replication/coordinator/etcd.mjs +554 -462
  55. package/dist/replication/index.d.ts +51 -105
  56. package/dist/replication/index.mjs +1415 -2061
  57. package/dist/server/index.d.ts +111 -87
  58. package/dist/server/index.mjs +1917 -489
  59. package/dist/server-options-1JHu8pid.d.ts +191 -0
  60. package/dist/{sirannon-4SspRvP5.d.ts → sirannon-DaQSyhbJ.d.ts} +7 -2
  61. package/dist/transport/grpc.d.ts +7 -4
  62. package/dist/transport/grpc.mjs +191 -97
  63. package/dist/transport/memory.d.ts +5 -134
  64. package/dist/transport/memory.mjs +3 -1
  65. package/dist/{types-BsjobKbl.d.ts → types-B7gmEsZW.d.ts} +8 -61
  66. package/dist/{types-BEu1I_9_.d.ts → types-BsVabqSI.d.ts} +1 -1
  67. package/dist/types-CL6piSnD.d.ts +43 -0
  68. package/dist/types-C_D8IhpO.d.ts +60 -0
  69. package/dist/{types-D_hQW1hr.d.ts → types-zhnRXrsb.d.ts} +55 -165
  70. package/package.json +44 -6
  71. package/dist/chunk-TKGHYWQ6.mjs +0 -35
  72. package/dist/types-D4p4UyDK.d.ts +0 -26
@@ -1,7 +1,7 @@
1
- import { serializeError } from '../chunk-DJLX6CAE.mjs';
2
- import { executeGroup } from '../chunk-CW6S3WL5.mjs';
3
- import '../chunk-GEZUUIKV.mjs';
4
- import { SirannonError } from '../chunk-YPYVQJ4C.mjs';
1
+ import { executeGroup } from '../chunk-JZGINXTN.mjs';
2
+ import '../chunk-D7LAYTKN.mjs';
3
+ import { WORKER_CANCELLED_CODE, serializeError } from '../chunk-4ISB7XMA.mjs';
4
+ import { SirannonError } from '../chunk-UC3SCMIN.mjs';
5
5
  import { parentPort } from 'worker_threads';
6
6
 
7
7
  var STATEMENT_CACHE_CAPACITY = 128;
@@ -93,7 +93,22 @@ async function dispatch(req) {
93
93
  return void 0;
94
94
  }
95
95
  }
96
+ var cancelledIds = /* @__PURE__ */ new Set();
97
+ var latestDispatchedId = 0;
96
98
  async function handle(req) {
99
+ latestDispatchedId = req.id;
100
+ if (cancelledIds.delete(req.id)) {
101
+ port.postMessage({
102
+ id: req.id,
103
+ ok: false,
104
+ error: {
105
+ message: "The caller abandoned this operation before the worker reached it, so it was not run",
106
+ name: "SirannonError",
107
+ code: WORKER_CANCELLED_CODE
108
+ }
109
+ });
110
+ return;
111
+ }
97
112
  try {
98
113
  const value = await dispatch(req);
99
114
  port.postMessage({ id: req.id, ok: true, value });
@@ -101,7 +116,14 @@ async function handle(req) {
101
116
  port.postMessage({ id: req.id, ok: false, error: serializeError(err) });
102
117
  }
103
118
  }
119
+ function settleDeliveredMessages() {
120
+ return new Promise((resolve) => setImmediate(resolve));
121
+ }
104
122
  var tail = Promise.resolve();
105
- port.on("message", (req) => {
106
- tail = tail.then(() => handle(req));
123
+ port.on("message", (msg) => {
124
+ if (msg.kind === "cancel") {
125
+ if (msg.id > latestDispatchedId) cancelledIds.add(msg.id);
126
+ return;
127
+ }
128
+ tail = tail.then(settleDeliveredMessages).then(() => handle(msg));
107
129
  });
@@ -1,5 +1,7 @@
1
- import { Q as QueryHookContext, k as ConnectionHookContext, l as BeforeSubscribeHook, H as HookConfig, M as MetricsConfig, m as QueryMetrics, n as ConnectionMetrics, o as CDCMetrics, a as SQLiteDriver, D as DatabaseOptions, P as Params, p as QueryOptions, E as ExecuteResult, q as BulkLoadOptions, h as BulkLoadResult, T as Transaction, e as SQLiteConnection, r as SubscriptionBuilder, f as BackupScheduleOptions, B as BeforeQueryHook, A as AfterQueryHook } from './types-D_hQW1hr.js';
2
- import { M as Migration, a as MigrationResult, R as RollbackResult } from './types-D4p4UyDK.js';
1
+ import { i as QueryHookContext, j as ConnectionHookContext, k as BeforeSubscribeHook, H as HookConfig, l as MetricsConfig, m as QueryMetrics, n as ConnectionMetrics, o as CDCMetrics, h as ChangeEvent, a as SQLiteDriver, D as DatabaseOptions, P as Params, Q as QueryOptions, E as ExecuteResult, T as Transaction, e as SQLiteConnection, p as SubscriptionBuilder, M as Migration, q as MigrationResult, r as RollbackResult, f as BackupScheduleOptions, B as BeforeQueryHook, A as AfterQueryHook } from './types-zhnRXrsb.js';
2
+ import { c as ReplicationChange, C as ConflictResolver, A as ApplyResult, R as ReplicationBatch } from './types-C_D8IhpO.js';
3
+ import { A as AppliedMigrationRow, b as BulkLoadOptions, a as BulkLoadResult } from './server-options-1JHu8pid.js';
4
+ import { L as LiveQueryOptions, a as LiveQuery } from './types-CL6piSnD.js';
3
5
 
4
6
  type HookEvent = 'beforeQuery' | 'afterQuery' | 'beforeConnect' | 'databaseOpen' | 'databaseClose' | 'beforeSubscribe';
5
7
  type SubscribeHookContext = Parameters<BeforeSubscribeHook>[0];
@@ -38,10 +40,56 @@ declare class MetricsCollector {
38
40
  get active(): boolean;
39
41
  }
40
42
 
43
+ interface StagedRecovery {
44
+ resumeSeq: bigint | null;
45
+ appliedSeq: bigint | null;
46
+ /**
47
+ * The failure that stopped the recovery apply, or null. The staged rows
48
+ * are untouched by the failure and the resume watermark still covers
49
+ * them, so the caller opens the subscription anyway: a schema-gate refusal
50
+ * there is what tells a device it must migrate before this apply can
51
+ * succeed. The caller must then retry the recovery, because the resume
52
+ * watermark is past the transaction this apply left unapplied, so the
53
+ * server sends nothing that would prompt another attempt.
54
+ */
55
+ applyError: unknown | null;
56
+ }
57
+
58
+ interface DeviceSyncPullState {
59
+ seq: bigint;
60
+ epoch: string | undefined;
61
+ }
62
+ interface DeviceSyncPort {
63
+ identity(): Promise<{
64
+ nodeId: string;
65
+ }>;
66
+ applyPulledTransaction(changes: readonly ReplicationChange[], pullSeq: bigint, resolver?: ConflictResolver | ((table: string) => ConflictResolver)): Promise<ApplyResult>;
67
+ stagePulledChanges(events: readonly ChangeEvent[]): Promise<bigint | null>;
68
+ applyStagedPull(resolver?: ConflictResolver | ((table: string) => ConflictResolver), onChange?: (event: ChangeEvent) => void): Promise<bigint | null>;
69
+ recoverStagedPull(resolver?: ConflictResolver | ((table: string) => ConflictResolver), onChange?: (event: ChangeEvent) => void): Promise<StagedRecovery>;
70
+ readOutboxBatch(afterSeq: bigint, limit: number): Promise<ReplicationBatch | null>;
71
+ countOutboxPending(afterSeq: bigint): Promise<number>;
72
+ getPushCursor(): Promise<bigint>;
73
+ setPushCursor(seq: bigint): Promise<void>;
74
+ getPullState(): Promise<DeviceSyncPullState | null>;
75
+ getResyncRequired(): Promise<boolean>;
76
+ setResyncRequired(required: boolean): Promise<void>;
77
+ setPullState(seq: bigint, epoch?: string): Promise<void>;
78
+ protectUnpushedChanges(pushedSeq: bigint): void;
79
+ snapshotLoadPending(): Promise<boolean>;
80
+ beginSnapshotLoad(tables: readonly string[]): Promise<void>;
81
+ applySnapshotSchema(schema: readonly string[]): Promise<void>;
82
+ loadSnapshotPage(table: string, rows: readonly Record<string, unknown>[]): Promise<void>;
83
+ replaceMigrationHistory(rows: readonly AppliedMigrationRow[]): Promise<void>;
84
+ endSnapshotLoad(tables: readonly string[]): Promise<void>;
85
+ abortSnapshotLoad(): Promise<void>;
86
+ }
87
+
41
88
  interface DatabaseInternals {
42
89
  parentHooks?: HookRegistry;
43
90
  metrics?: MetricsCollector;
44
91
  }
92
+
45
93
  declare class Database {
46
94
  readonly id: string;
47
95
  readonly path: string;
@@ -56,25 +104,18 @@ declare class Database {
56
104
  private readonly closeListeners;
57
105
  private _closed;
58
106
  private readonly cdc;
107
+ private readonly sync;
59
108
  private readonly hookRegistry;
60
109
  private readonly observer;
61
110
  private readonly backups;
111
+ private readonly reads;
62
112
  private constructor();
63
113
  static create(id: string, path: string, driver: SQLiteDriver, options?: DatabaseOptions, internals?: DatabaseInternals): Promise<Database>;
114
+ applyChanges(batch: ReplicationBatch, resolver?: ConflictResolver | ((table: string) => ConflictResolver)): Promise<ApplyResult>;
115
+ deviceSync(): DeviceSyncPort;
64
116
  query<T = Record<string, unknown>>(sql: string, params?: Params, options?: QueryOptions): Promise<T[]>;
65
- /**
66
- * Returns query rows already encoded for the wire (safe-range integers as
67
- * plain numbers, larger integers and BLOBs as tagged envelopes) in a single
68
- * pass. The server response path uses this so a read walks its values once
69
- * rather than narrowing on the driver and re-scanning to tag.
70
- */
71
117
  queryForWire(sql: string, params?: Params, options?: QueryOptions): Promise<unknown[]>;
72
118
  queryOne<T = Record<string, unknown>>(sql: string, params?: Params, options?: QueryOptions): Promise<T | undefined>;
73
- /**
74
- * On a single-connection driver the reader is the writer, so serialise the
75
- * read through the writer lock to avoid a bulk load's uncommitted rows.
76
- */
77
- private runRead;
78
119
  execute(sql: string, params?: Params, options?: QueryOptions): Promise<ExecuteResult>;
79
120
  executeBatch(sql: string, paramsBatch: Params[], options?: QueryOptions): Promise<ExecuteResult[]>;
80
121
  private runInTransaction;
@@ -108,9 +149,12 @@ declare class Database {
108
149
  * lock and stalls the event loop on `busy_timeout`.
109
150
  */
110
151
  runCdcMaintenance(op: (writer: SQLiteConnection) => Promise<unknown>): Promise<void>;
152
+ ensureChangeStamping(): Promise<void>;
111
153
  unwatch(table: string): Promise<void>;
112
154
  on(table: string): SubscriptionBuilder;
155
+ live<T = Record<string, unknown>>(sql: string, params?: Params, options?: LiveQueryOptions): Promise<LiveQuery<T>>;
113
156
  migrate(migrations: Migration[]): Promise<MigrationResult>;
157
+ appliedMigrations(): Promise<AppliedMigrationRow[]>;
114
158
  rollback(migrations: Migration[], version?: number): Promise<RollbackResult>;
115
159
  backup(destPath: string): Promise<void>;
116
160
  scheduleBackup(options: BackupScheduleOptions): void;
@@ -122,6 +166,7 @@ declare class Database {
122
166
  get closed(): boolean;
123
167
  get readerCount(): number;
124
168
  private ensureOpen;
169
+ private ensureNotClosed;
125
170
  }
126
171
 
127
- export { Database as D, type HookDispose as H, MetricsCollector as M, type SubscribeHookContext as S, type HookEvent as a, type HookEventContextMap as b, type HookHandler as c, HookRegistry as d };
172
+ export { Database as D, type HookDispose as H, MetricsCollector as M, type SubscribeHookContext as S, type HookEvent as a, type HookEventContextMap as b, type HookHandler as c, HookRegistry as d, type DeviceSyncPort as e };
@@ -1,4 +1,4 @@
1
- import { a as SQLiteDriver } from '../types-D_hQW1hr.js';
1
+ import { a as SQLiteDriver } from '../types-zhnRXrsb.js';
2
2
 
3
3
  interface BetterSqlite3Options {
4
4
  busyTimeout?: number;
@@ -1,9 +1,9 @@
1
- import { nodeResolveExtensionPath, nodeBackupEngine, nodeWriterContext, WriterWorker, createStatementCache, narrowSafeBigInt, narrowRowIntegers, narrowRowsIntegers } from '../chunk-4IGMIJQK.mjs';
2
- import { defineDriver } from '../chunk-BNUTBHHH.mjs';
3
- import '../chunk-DJLX6CAE.mjs';
4
- import { synchronousPragmaValue } from '../chunk-CJLYFDP5.mjs';
5
- import '../chunk-VLTICJOD.mjs';
6
- import '../chunk-YPYVQJ4C.mjs';
1
+ import { nodeResolveExtensionPath, nodeBackupEngine, nodeWriterContext, WriterWorker, createStatementCache, narrowSafeBigInt, narrowRowIntegers, narrowRowsIntegers } from '../chunk-EBJXPQQO.mjs';
2
+ import '../chunk-PIKHN33N.mjs';
3
+ import { defineDriver } from '../chunk-FHWTZFI4.mjs';
4
+ import '../chunk-4ISB7XMA.mjs';
5
+ import { synchronousPragmaValue } from '../chunk-LFZ37BSX.mjs';
6
+ import '../chunk-UC3SCMIN.mjs';
7
7
 
8
8
  // src/drivers/better-sqlite3/index.ts
9
9
  function createConnection(db) {
@@ -1,4 +1,4 @@
1
- import { a as SQLiteDriver } from '../types-D_hQW1hr.js';
1
+ import { a as SQLiteDriver } from '../types-zhnRXrsb.js';
2
2
 
3
3
  interface NodeSqliteOptions {
4
4
  busyTimeout?: number;
@@ -1,9 +1,9 @@
1
- import { nodeResolveExtensionPath, nodeBackupEngine, nodeWriterContext, createStatementCache, WriterWorker, narrowSafeBigInt, narrowRowIntegers, narrowRowsIntegers } from '../chunk-4IGMIJQK.mjs';
2
- import { defineDriver } from '../chunk-BNUTBHHH.mjs';
3
- import '../chunk-DJLX6CAE.mjs';
4
- import { synchronousPragmaValue } from '../chunk-CJLYFDP5.mjs';
5
- import '../chunk-VLTICJOD.mjs';
6
- import '../chunk-YPYVQJ4C.mjs';
1
+ import { nodeResolveExtensionPath, nodeBackupEngine, nodeWriterContext, createStatementCache, WriterWorker, narrowSafeBigInt, narrowRowIntegers, narrowRowsIntegers } from '../chunk-EBJXPQQO.mjs';
2
+ import '../chunk-PIKHN33N.mjs';
3
+ import { defineDriver } from '../chunk-FHWTZFI4.mjs';
4
+ import '../chunk-4ISB7XMA.mjs';
5
+ import { synchronousPragmaValue } from '../chunk-LFZ37BSX.mjs';
6
+ import '../chunk-UC3SCMIN.mjs';
7
7
 
8
8
  // src/drivers/node/index.ts
9
9
  function nodeSqlite(driverOptions) {
@@ -1,139 +1,4 @@
1
- /** Options for scheduled backups. */
2
- interface BackupScheduleOptions {
3
- /** Cron expression (e.g., '0 * * * *' for hourly). */
4
- cron: string;
5
- /** Directory to store backup files. */
6
- destDir: string;
7
- /** Maximum number of backup files to keep. Default: 5. */
8
- maxFiles?: number;
9
- /**
10
- * Sirannon evaluates the cron expression in this IANA time zone (e.g. 'America/New_York').
11
- * When omitted, it uses the host's local time zone, which also sets the daylight saving rules that apply.
12
- */
13
- timezone?: string;
14
- /** Called when a scheduled backup fails. Without this, errors are silently discarded. */
15
- onError?: (error: Error) => void;
16
- }
17
-
18
- interface WorkerHostOptions {
19
- writeTimeoutMs?: number;
20
- maxRestarts?: number;
21
- }
22
-
23
- interface RunResult {
24
- changes: number;
25
- lastInsertRowId: number | bigint;
26
- }
27
- /**
28
- * Tells a caller running inside the operation that holds the writer from one
29
- * merely waiting on it. A runtime without async context tracking cannot answer
30
- * this, and answering it wrongly runs one caller's writes inside another
31
- * caller's transaction.
32
- */
33
- interface WriterContext {
34
- run<T>(operation: () => T): T;
35
- isActive(): boolean;
36
- exit<T>(operation: () => T): T;
37
- }
38
- interface BackupEngine {
39
- backup(conn: SQLiteConnection, destPath: string): Promise<void>;
40
- schedule(conn: SQLiteConnection, options: BackupScheduleOptions, runExclusive: (op: () => Promise<void>) => Promise<void>): () => void;
41
- }
42
- interface BatchSummary {
43
- rowsLoaded: number;
44
- changes: number;
45
- }
46
- interface SQLiteStatement {
47
- all<T = unknown>(...params: unknown[]): Promise<T[]>;
48
- get<T = unknown>(...params: unknown[]): Promise<T | undefined>;
49
- run(...params: unknown[]): Promise<RunResult>;
50
- /**
51
- * Like {@link all} but skips the safe-range BigInt narrowing, leaving every
52
- * integer as a BigInt. The server wire path narrows and tags in one pass, so
53
- * feeding it raw rows avoids a second walk. Optional: a driver that omits it
54
- * falls back to {@link all}, still correct but with the extra narrowing walk.
55
- */
56
- allRaw?<T = unknown>(...params: unknown[]): Promise<T[]>;
57
- }
58
- interface GroupRunError {
59
- message: string;
60
- name?: string;
61
- code?: string;
62
- }
63
- type GroupRunOutcome = {
64
- ok: true;
65
- results: RunResult[];
66
- } | {
67
- ok: false;
68
- error: GroupRunError;
69
- };
70
- interface SQLiteConnection {
71
- exec(sql: string): Promise<void>;
72
- prepare(sql: string): Promise<SQLiteStatement>;
73
- transaction<T>(fn: (conn: SQLiteConnection) => Promise<T>): Promise<T>;
74
- close(): Promise<void>;
75
- runBatch?(sql: string, paramsBatch: readonly unknown[][]): Promise<RunResult[]>;
76
- runBatchSummary?(sql: string, paramsBatch: readonly unknown[][]): Promise<BatchSummary>;
77
- /**
78
- * Runs several independent units in one transaction, one outcome per unit in
79
- * order. A unit is one write or one whole transaction, and a unit that fails
80
- * must not disturb the others.
81
- */
82
- runGroup?(units: readonly {
83
- statements: readonly {
84
- sql: string;
85
- params?: readonly unknown[];
86
- }[];
87
- }[]): Promise<GroupRunOutcome[]>;
88
- }
89
- /**
90
- * SQLite `PRAGMA synchronous` level applied to a connection. `normal` is safe
91
- * from corruption in WAL mode but can lose the most recent commits on power
92
- * loss; `full` fsyncs every commit; `extra` adds a directory sync after the
93
- * rollback journal is unlinked in DELETE journal mode and equals `full` in
94
- * WAL mode; `off` hands writes to the OS without syncing and is sanctioned
95
- * only for re-runnable bulk loads.
96
- */
97
- type SynchronousLevel = 'off' | 'normal' | 'full' | 'extra';
98
- interface OpenOptions {
99
- readonly?: boolean;
100
- walMode?: boolean;
101
- synchronous?: SynchronousLevel;
102
- }
103
- interface DriverCapabilities {
104
- multipleConnections: boolean;
105
- extensions: boolean;
106
- }
107
- /**
108
- * Lets a worker thread rebuild the driver, since the driver's `open` function
109
- * cannot cross the thread boundary. `specifier` must be importable from the
110
- * worker and `config` must survive a structured clone; the worker imports the
111
- * module and calls its `exportName` factory (default export otherwise) with it.
112
- */
113
- interface DriverWorkerEntry {
114
- specifier: string;
115
- exportName?: string;
116
- config?: unknown;
117
- }
118
- interface SQLiteDriver {
119
- readonly capabilities: DriverCapabilities;
120
- open(path: string, options?: OpenOptions): Promise<SQLiteConnection>;
121
- readonly worker?: DriverWorkerEntry;
122
- /**
123
- * Offloads writes to a worker thread. Only a driver whose runtime has
124
- * threads implements this, which is what keeps the thread machinery out of
125
- * bundles built for runtimes that do not.
126
- */
127
- startWriterHost?(path: string, options: OpenOptions, hostOptions?: WorkerHostOptions): Promise<SQLiteConnection>;
128
- createWriterContext?(): WriterContext;
129
- createBackupEngine?(): BackupEngine;
130
- /**
131
- * Makes an extension path absolute. Passing a bare name to `load_extension`
132
- * would let the dynamic linker search its own paths and open a different
133
- * library than the operator named.
134
- */
135
- resolveExtensionPath?(extensionPath: string): string;
136
- }
1
+ import { a as SQLiteDriver } from '../types-zhnRXrsb.js';
137
2
 
138
3
  interface WaSqliteOptions {
139
4
  vfs?: 'IDBBatchAtomicVFS' | 'AccessHandlePoolVFS';
@@ -60,6 +60,10 @@ declare class MigrationError extends SirannonError {
60
60
  declare class HookDeniedError extends SirannonError {
61
61
  constructor(hookName: string, reason?: string);
62
62
  }
63
+ declare class RequestDeniedError extends SirannonError {
64
+ readonly status: number;
65
+ constructor(status: number, code: string, message: string);
66
+ }
63
67
  /**
64
68
  * Thrown when the change-data-capture pipeline encounters an unrecoverable
65
69
  * error, such as a failed event dispatch or a corrupt change record.
@@ -116,4 +120,4 @@ declare class ExtensionError extends SirannonError {
116
120
  constructor(path: string, cause?: string);
117
121
  }
118
122
 
119
- export { BackupError as B, CDCError as C, DatabaseAlreadyExistsError as D, ExtensionError as E, ForbiddenSqlError as F, HookDeniedError as H, MaxDatabasesError as M, QueryError as Q, ReadOnlyError as R, SirannonError as S, TransactionError as T, WriteOverloadError as W, ConnectionPoolError as a, DatabaseNotFoundError as b, MigrationError as c };
123
+ export { BackupError as B, CDCError as C, DatabaseAlreadyExistsError as D, ExtensionError as E, ForbiddenSqlError as F, HookDeniedError as H, MaxDatabasesError as M, QueryError as Q, ReadOnlyError as R, SirannonError as S, TransactionError as T, WriteOverloadError as W, ConnectionPoolError as a, DatabaseNotFoundError as b, MigrationError as c, RequestDeniedError as d };
@@ -1,6 +1,9 @@
1
- import { M as Migration } from '../types-D4p4UyDK.js';
2
- import '../types-D_hQW1hr.js';
1
+ import { B as BaselineFileOption } from '../baseline-Br77Fnhb.js';
2
+ import { M as Migration } from '../types-zhnRXrsb.js';
3
3
 
4
+ interface LoadMigrationsOptions {
5
+ baseline?: BaselineFileOption;
6
+ }
4
7
  interface ScannedMigration {
5
8
  version: number;
6
9
  name: string;
@@ -9,7 +12,6 @@ interface ScannedMigration {
9
12
  }
10
13
  declare function scanDirectory(dirPath: string): ScannedMigration[];
11
14
  declare function readUpMigrations(scanned: ScannedMigration[]): Migration[];
12
- declare function readDownMigrations(scanned: ScannedMigration[], versions: number[]): Migration[];
13
- declare function loadMigrations(dirPath: string): Migration[];
15
+ declare function loadMigrations(dirPath: string, options?: LoadMigrationsOptions): Migration[];
14
16
 
15
- export { type ScannedMigration, loadMigrations, readDownMigrations, readUpMigrations, scanDirectory };
17
+ export { type LoadMigrationsOptions, type ScannedMigration, loadMigrations, readUpMigrations, scanDirectory };
@@ -1,8 +1,9 @@
1
- import { MigrationError } from '../chunk-YPYVQJ4C.mjs';
1
+ import { parseMigrationFilename, LAZY_DOWN_SQL } from '../chunk-LNY2VVHE.mjs';
2
+ import { applyBaselineOption } from '../chunk-67M7KAH6.mjs';
3
+ import { MigrationError } from '../chunk-UC3SCMIN.mjs';
2
4
  import { statSync, readdirSync, readFileSync } from 'fs';
3
5
  import { resolve, join } from 'path';
4
6
 
5
- var MIGRATION_FILENAME_PATTERN = /^(\d+)_(\w+)\.(up|down)\.sql$/;
6
7
  function hasControlCharacters(s) {
7
8
  for (let i = 0; i < s.length; i++) {
8
9
  if (s.charCodeAt(i) <= 31) return true;
@@ -35,14 +36,9 @@ function scanDirectory(dirPath) {
35
36
  const grouped = /* @__PURE__ */ new Map();
36
37
  for (const entry of entries) {
37
38
  if (!entry.isFile()) continue;
38
- const match = MIGRATION_FILENAME_PATTERN.exec(entry.name);
39
- if (!match) continue;
40
- const version = parseInt(match[1], 10);
41
- const name = match[2];
42
- const direction = match[3];
43
- if (!Number.isFinite(version) || version <= 0 || !Number.isSafeInteger(version)) {
44
- throw new MigrationError(`Invalid migration version: ${match[1]}`, version, "MIGRATION_VALIDATION_ERROR");
45
- }
39
+ const parsed = parseMigrationFilename(entry.name);
40
+ if (!parsed) continue;
41
+ const { version, name, direction } = parsed;
46
42
  const existing = grouped.get(version);
47
43
  if (existing && existing.name !== name) {
48
44
  throw new MigrationError(
@@ -94,35 +90,44 @@ function readUpMigrations(scanned) {
94
90
  };
95
91
  });
96
92
  }
97
- function readDownMigrations(scanned, versions) {
98
- const versionSet = new Set(versions);
99
- const filtered = scanned.filter((s) => versionSet.has(s.version));
100
- return filtered.map((entry) => {
101
- if (!entry.downPath) {
102
- throw new MigrationError(
103
- `Migration version ${entry.version} (${entry.name}) has no .down.sql file`,
104
- entry.version,
105
- "MIGRATION_NO_DOWN"
106
- );
107
- }
108
- const downSql = readFileSync(entry.downPath, "utf-8").trim();
109
- if (downSql.length === 0) {
110
- throw new MigrationError(
111
- `Down migration file is empty: ${entry.downPath}`,
112
- entry.version,
113
- "MIGRATION_VALIDATION_ERROR"
114
- );
93
+ function readDownSql(downPath, version) {
94
+ let contents;
95
+ try {
96
+ contents = readFileSync(downPath, "utf-8");
97
+ } catch (err) {
98
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
99
+ throw new MigrationError(`Down migration file no longer exists: ${downPath}`, version, "MIGRATION_NO_DOWN");
115
100
  }
116
- return {
117
- version: entry.version,
118
- name: entry.name,
119
- up: "",
120
- down: downSql
121
- };
101
+ throw new MigrationError(
102
+ `Failed to read down migration file ${downPath}: ${err instanceof Error ? err.message : String(err)}`,
103
+ version,
104
+ "MIGRATION_ROLLBACK_ERROR"
105
+ );
106
+ }
107
+ const downSql = contents.trim();
108
+ if (downSql.length === 0) {
109
+ throw new MigrationError(`Down migration file is empty: ${downPath}`, version, "MIGRATION_VALIDATION_ERROR");
110
+ }
111
+ return downSql;
112
+ }
113
+ function attachLazyDown(migration, downPath) {
114
+ Object.defineProperty(migration, LAZY_DOWN_SQL, {
115
+ value: () => readDownSql(downPath, migration.version),
116
+ enumerable: false,
117
+ configurable: false,
118
+ writable: false
122
119
  });
123
120
  }
124
- function loadMigrations(dirPath) {
125
- return readUpMigrations(scanDirectory(dirPath));
121
+ function loadMigrations(dirPath, options) {
122
+ const scanned = scanDirectory(dirPath);
123
+ const migrations = readUpMigrations(scanned);
124
+ for (let i = 0; i < migrations.length; i++) {
125
+ const downPath = scanned[i].downPath;
126
+ if (downPath !== null) {
127
+ attachLazyDown(migrations[i], downPath);
128
+ }
129
+ }
130
+ return applyBaselineOption(migrations, options?.baseline);
126
131
  }
127
132
 
128
- export { loadMigrations, readDownMigrations, readUpMigrations, scanDirectory };
133
+ export { loadMigrations, readUpMigrations, scanDirectory };
@@ -0,0 +1,45 @@
1
+ import { P as Params } from './types-zhnRXrsb.js';
2
+
3
+ interface OperationStatement {
4
+ sql: string;
5
+ params?: Params;
6
+ }
7
+ type OperationArguments = Record<string, unknown>;
8
+ interface ReadOperation<Identity = unknown> {
9
+ args?: readonly string[];
10
+ fromIdentity?: Readonly<Record<string, keyof Identity & string>>;
11
+ /**
12
+ * The columns every row of this read carries. Code generation emits a typed
13
+ * row from it, and reads the columns from the statement only when the
14
+ * operation takes no arguments, because arguments choose the statement.
15
+ */
16
+ columns?: readonly string[];
17
+ statement(args: OperationArguments): OperationStatement;
18
+ }
19
+ interface WriteOperation<Identity = unknown> {
20
+ args?: readonly string[];
21
+ fromIdentity?: Readonly<Record<string, keyof Identity & string>>;
22
+ statements(args: OperationArguments): OperationStatement | readonly OperationStatement[];
23
+ }
24
+ interface DatabaseOperations<Identity = unknown> {
25
+ reads?: Readonly<Record<string, ReadOperation<Identity>>>;
26
+ writes?: Readonly<Record<string, WriteOperation<Identity>>>;
27
+ }
28
+ type OperationRegistry<Identity = unknown> = Readonly<Record<string, DatabaseOperations<Identity>>>;
29
+ /**
30
+ * A named operation a remote caller invokes, carrying the argument and row
31
+ * types of the registered operation. Only `name` exists at runtime; `types`
32
+ * is never assigned and is present so both type parameters are inferable at
33
+ * the call site. Code generation emits one reference per registered operation.
34
+ */
35
+ interface OperationRef<Args = OperationArguments, Row = Record<string, unknown>> {
36
+ readonly name: string;
37
+ readonly types?: {
38
+ args: Args;
39
+ row: Row;
40
+ };
41
+ }
42
+ declare function operationRef<Args = OperationArguments, Row = Record<string, unknown>>(name: string): OperationRef<Args, Row>;
43
+ declare function operationName(operation: string | OperationRef<never, never>): string;
44
+
45
+ export { type DatabaseOperations as D, type OperationRef as O, type ReadOperation as R, type WriteOperation as W, type OperationRegistry as a, type OperationArguments as b, type OperationStatement as c, operationRef as d, operationName as o };
@@ -0,0 +1,61 @@
1
+ import { C as ConflictResolver, a as ConflictContext, b as ConflictResolution } from './types-C_D8IhpO.js';
2
+
3
+ type ColumnVersionGetter = (table: string, rowId: string) => Promise<Map<string, {
4
+ hlc: string;
5
+ nodeId: string;
6
+ }>>;
7
+ /**
8
+ * Per-column conflict resolver that merges non-overlapping field changes.
9
+ *
10
+ * Given a conflict between a local and remote write on the same row, this
11
+ * resolver computes the set of columns each side changed relative to the
12
+ * common ancestor (oldData). If the changed column sets don't overlap, both
13
+ * sides' changes are merged into a single row, avoiding unnecessary data
14
+ * loss. When columns do overlap, per-column HLC versions (retrieved via the
15
+ * injected `getColumnVersions` callback) determine which side's value wins
16
+ * for each contested column. If no column version metadata exists, the
17
+ * resolver falls back to whole-row LWW.
18
+ */
19
+ declare class FieldMergeResolver implements ConflictResolver {
20
+ private readonly getColumnVersions;
21
+ private readonly lww;
22
+ constructor(getColumnVersions: ColumnVersionGetter);
23
+ resolve(ctx: ConflictContext): Promise<ConflictResolution>;
24
+ }
25
+
26
+ /**
27
+ * Last-Writer-Wins conflict resolver.
28
+ *
29
+ * A remote delete is accepted whatever the timestamps say, so a delete wins over
30
+ * a concurrent update and a deleted row is never resurrected.
31
+ *
32
+ * Otherwise compares the remote HLC against the local HLC. The higher timestamp wins.
33
+ * When both timestamps are equal (concurrent writes within the same
34
+ * millisecond and logical tick), the tie is broken deterministically by
35
+ * comparing node IDs lexicographically, so that every node reaches the same
36
+ * resolution without coordination. This is the default resolver and the
37
+ * fallback used by PrimaryWinsResolver and FieldMergeResolver when they
38
+ * cannot make a more specific decision.
39
+ */
40
+ declare class LWWResolver implements ConflictResolver {
41
+ resolve(ctx: ConflictContext): ConflictResolution;
42
+ }
43
+
44
+ /**
45
+ * Conflict resolver that unconditionally favors the designated primary node.
46
+ *
47
+ * If the remote change originated from the primary, it is accepted. If the
48
+ * local change originated from the primary, the remote is rejected. When
49
+ * neither side is the primary (e.g., two replicas syncing through a relay),
50
+ * the decision falls back to LWW ordering. This resolver is designed for
51
+ * primary-replica topologies where the primary is the authoritative source of
52
+ * truth and replica-side writes should never override it.
53
+ */
54
+ declare class PrimaryWinsResolver implements ConflictResolver {
55
+ private readonly primaryNodeId;
56
+ private readonly lww;
57
+ constructor(primaryNodeId: string);
58
+ resolve(ctx: ConflictContext): ConflictResolution;
59
+ }
60
+
61
+ export { FieldMergeResolver as F, LWWResolver as L, PrimaryWinsResolver as P };