@jarenjs/db 0.56.0 → 0.67.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 (79) hide show
  1. package/ARCHITECTURE.md +412 -56
  2. package/README.md +600 -57
  3. package/docs/HOSTS.md +269 -0
  4. package/docs/JOBS-FORMAT.md +293 -45
  5. package/docs/LIVE-FORMAT.md +169 -20
  6. package/docs/MIGRATION-FORMAT.md +142 -17
  7. package/docs/MODEL-FORMAT.md +752 -64
  8. package/docs/REPLICATION-FORMAT.md +208 -0
  9. package/package.json +21 -7
  10. package/schemas/jaren-model.draft-07.schema.json +224 -162
  11. package/schemas/jaren-model.schema.json +224 -162
  12. package/schemas/jaren-replication-snapshot.draft-07.schema.json +83 -0
  13. package/schemas/jaren-replication-snapshot.schema.json +83 -0
  14. package/schemas/jaren-replication.draft-07.schema.json +82 -0
  15. package/schemas/jaren-replication.schema.json +82 -0
  16. package/src/algebra.js +227 -9
  17. package/src/backup.js +161 -0
  18. package/src/cancellation.js +48 -0
  19. package/src/capture.js +230 -47
  20. package/src/cli.js +165 -59
  21. package/src/cursor.js +417 -0
  22. package/src/dag-job.js +154 -21
  23. package/src/ddl.js +102 -8
  24. package/src/dialect.js +268 -113
  25. package/src/dialects/expression-read.js +158 -0
  26. package/src/dialects/postgres.js +618 -0
  27. package/src/dialects/rtree-ddl.js +129 -0
  28. package/src/dialects/sqlite.js +244 -11
  29. package/src/document-files.js +311 -0
  30. package/src/document-steps.js +422 -0
  31. package/src/documents.js +335 -0
  32. package/src/driver.js +448 -61
  33. package/src/drivers/bun.js +37 -1
  34. package/src/drivers/indexeddb-snapshot.js +149 -0
  35. package/src/drivers/node-pool.js +11 -0
  36. package/src/drivers/node-worker-endpoint.js +105 -0
  37. package/src/drivers/node-worker.js +204 -0
  38. package/src/drivers/node.js +41 -7
  39. package/src/drivers/postgres.js +331 -0
  40. package/src/drivers/wasm-oo1.js +97 -0
  41. package/src/drivers/wasm-session.js +67 -0
  42. package/src/drivers/wasm.js +17 -83
  43. package/src/drivers/worker-pool.js +183 -0
  44. package/src/drivers/worker-protocol.js +79 -0
  45. package/src/drivers/worker-queue.js +60 -0
  46. package/src/emit.js +339 -48
  47. package/src/entity.js +20 -22
  48. package/src/errors.js +430 -19
  49. package/src/expression.js +284 -0
  50. package/src/graph.js +64 -8
  51. package/src/index.js +48 -17
  52. package/src/introspect.js +583 -0
  53. package/src/jobs.js +843 -107
  54. package/src/json-bytes.js +58 -0
  55. package/src/live-join.js +250 -0
  56. package/src/live-nested.js +120 -0
  57. package/src/live.js +18 -4
  58. package/src/logical-rows.js +90 -0
  59. package/src/maintenance.js +175 -0
  60. package/src/migrate.js +248 -181
  61. package/src/model.js +68 -0
  62. package/src/plan.js +1119 -138
  63. package/src/pragmas.js +314 -0
  64. package/src/profile.js +151 -3
  65. package/src/query.js +1634 -323
  66. package/src/replication-format.js +115 -0
  67. package/src/replication.js +332 -0
  68. package/src/residual.js +17 -0
  69. package/src/series.js +12 -4
  70. package/src/store.js +1567 -273
  71. package/src/tracker.js +203 -29
  72. package/src/udf.js +88 -7
  73. package/types/index.d.ts +1158 -27
  74. package/types/node-pool.d.ts +28 -0
  75. package/types/node-worker.d.ts +54 -0
  76. package/types/node.d.ts +69 -2
  77. package/types/postgres.d.ts +46 -0
  78. package/types/typed.d.ts +27 -4
  79. package/types/wasm.d.ts +14 -0
package/types/index.d.ts CHANGED
@@ -16,6 +16,8 @@
16
16
  * `@jarenjs/db/typed`.
17
17
  */
18
18
 
19
+ import type { Runtime } from '@jarenjs/core/runtime';
20
+
19
21
  // ————— errors —————
20
22
 
21
23
  export declare const DB_CODES: Readonly<Record<string, string>>;
@@ -25,8 +27,37 @@ export declare class DbCompileError extends Error {
25
27
  readonly code: string;
26
28
  readonly reason: string;
27
29
  readonly docPath?: string;
30
+ /** Present on a `JD0002` raised by a driver failure at open: the
31
+ * classifier's class and verdict (MODEL-FORMAT §7). */
32
+ readonly class?: DriverErrorClass;
33
+ readonly retryable?: boolean;
28
34
  }
29
35
 
36
+ /** The stable classes `classifyDriverError` assigns (MODEL-FORMAT §7). */
37
+ export type DriverErrorClass = 'busy' | 'full' | 'readonly' | 'io' | 'corrupt' | 'cantopen'
38
+ | 'constraint' | 'duplicate' | 'overflow' | 'error';
39
+
40
+ /** Classify a SQLite driver failure: the class, the runtime code it is
41
+ * raised under (`null` for `overflow`, which the query path answers by
42
+ * re-running in the engine), whether a retry can succeed, and the
43
+ * sentence the wrapped error leads with. */
44
+ export declare function classifyDriverError(error: unknown,
45
+ unique?: { table: string; column: string }):
46
+ { class: DriverErrorClass; code: string | null; retryable: boolean; reason: string };
47
+ /** Wrap a driver failure as the coded runtime error its class calls
48
+ * for, `class`/`retryable`/`cause` attached; anything that is not a
49
+ * driver's own error is returned as it is. */
50
+ export declare function wrapDriverError(error: unknown, details?: {
51
+ docPath?: string; collection?: string; key?: string | number;
52
+ unique?: { table: string; column: string }; duplicateReason?: string;
53
+ code?: string; reason?: string;
54
+ /** Wrap even a failure that is not a driver's under `code`, with
55
+ * `class: 'error'` — for a lifecycle that promises a coded failure. */
56
+ always?: boolean }): Error;
57
+ /** Whether an error is a SQLite driver's own (a numeric result code, or
58
+ * node:sqlite's error shape). */
59
+ export declare function isDriverError(error: unknown): boolean;
60
+
30
61
  export declare class DbRuntimeError extends Error {
31
62
  constructor(code: string, reason: string, options?: {
32
63
  docPath?: string;
@@ -41,6 +72,10 @@ export declare class DbRuntimeError extends Error {
41
72
  readonly collection?: string;
42
73
  readonly key?: unknown;
43
74
  readonly errors?: unknown[];
75
+ /** Present on an error the driver-failure classifier wrapped
76
+ * (MODEL-FORMAT §7): the stable class and whether a retry can succeed. */
77
+ readonly class?: DriverErrorClass;
78
+ readonly retryable?: boolean;
44
79
  }
45
80
 
46
81
  // ————— shared shapes —————
@@ -73,8 +108,67 @@ export type ValueOrPromise<T> = T | Promise<T>;
73
108
  */
74
109
  export interface QueryCursor<T = unknown> {
75
110
  next(): Promise<IteratorResult<T, undefined>>;
111
+ /** Release the statement, exactly once, at the row boundary the
112
+ * cursor is on; idempotent, and what `for await`'s break, throw and
113
+ * exhaustion all reach. */
76
114
  return(): Promise<IteratorResult<T, undefined>>;
77
115
  [Symbol.asyncIterator](): QueryCursor<T>;
116
+ /** What this cursor will do for the externals it was given: pull one
117
+ * database row per `next()`, or fill a buffer on the first pull. */
118
+ readonly streaming: 'row' | 'buffered';
119
+ /** What forces the buffer, `null` when the cursor streams. */
120
+ readonly barrier: CursorBarrier | null;
121
+ }
122
+
123
+ /** Why a cursor buffers: `construct` is the stable identifier (a
124
+ * planner construct such as `$orderby` or `$let`, or `external`,
125
+ * `window`, `pushdown`), `reason` the sentence for a person. */
126
+ export interface CursorBarrier {
127
+ readonly construct: string;
128
+ readonly reason: string;
129
+ }
130
+
131
+ /**
132
+ * The safe execution profile (MODEL-FORMAT §8): five independent bounds
133
+ * — engine limits, a row bound, reference containment, mandatory
134
+ * predicates, a member allow-list — plus the graph bounds. Every member
135
+ * is optional over the `'safe'` defaults. A budget the engine can count is ENFORCED; one it
136
+ * cannot count on SQLite (visited rows, elapsed statement time) is
137
+ * refused at preflight on plan shape (`refuseFullScan`) or reported as
138
+ * unavailable (`explain().budget`), never approximated.
139
+ */
140
+ export interface ProfileSpec {
141
+ limits?: { sequenceItems?: number; resultItems?: number; steps?: number; depth?: number };
142
+ /** Rows a fetch may return, materialise or feed a residual, per call
143
+ * (`JD2007` when crossed). */
144
+ maxRows?: number;
145
+ externals?: readonly string[];
146
+ functions?: readonly string[];
147
+ collations?: readonly string[];
148
+ /** The names a document may read — collections AND entity roots;
149
+ * `null` allows all of the store's. */
150
+ collections?: readonly string[] | null;
151
+ /** A predicate conjoined into every plan over the named collection or
152
+ * entity, at its root, after translation. */
153
+ predicates?: Readonly<Record<string, unknown>>;
154
+ /** Refuse a plan whose shape is a full-table scan — including the
155
+ * whole-root fetch an entity residual needs (`JD0011`). */
156
+ refuseFullScan?: boolean;
157
+ /** A cap on any include's per-root rows, on the include depth, and on
158
+ * one item's serialised bytes (`JD2076`); `null` for none. */
159
+ maxIncludedRows?: number | null;
160
+ maxDepth?: number | null;
161
+ maxBytes?: number | null;
162
+ /** Per ROOT (a collection or an entity), the members a document may
163
+ * READ, as the model's own singular index-path spelling (`'$.name'`,
164
+ * `'$.address.city'`). Allowing a member allows everything under it
165
+ * and none of its siblings. A root the list does not name is
166
+ * unrestricted; a root the MODEL does not declare is `JD0011` before
167
+ * any statement. Reading a root item whole — the bare binding, an
168
+ * alias of it, a wildcard with no singular prefix — is refused rather
169
+ * than narrowed, and so is a graph `load()` of a policed entity, which
170
+ * answers whole documents by definition. */
171
+ members?: Readonly<Record<string, readonly string[]>>;
78
172
  }
79
173
 
80
174
  export interface ExecuteOptions {
@@ -82,6 +176,40 @@ export interface ExecuteOptions {
82
176
  strict?: boolean;
83
177
  /** `false` forces the set residual — the oracle's harness switch. */
84
178
  pushdown?: boolean;
179
+ /** The safety profile for THIS call, replacing the store's (normalized
180
+ * over the `'safe'` defaults, MODEL-FORMAT §8); applies to collection,
181
+ * entity, graph, include and store-root execution alike. */
182
+ profile?: 'safe' | ProfileSpec;
183
+ /** Cancellation: a call already aborted runs no statement (`JD2072`);
184
+ * a cursor or page is released at its next row boundary. */
185
+ signal?: AbortSignal;
186
+ /** An epoch-millisecond deadline, checked before a statement runs and
187
+ * at every row boundary of a cursor or page (`JD2075`). NOT a
188
+ * statement timeout: the shipped SQLite drivers expose no interrupt
189
+ * (`capabilities.statementTimeout` is `false`), so a single statement
190
+ * runs to its end — `explain().budget.time` says so. */
191
+ deadline?: number;
192
+ /** On a cursor: a plan that would buffer — a set residual, a
193
+ * k-nearest cut, a native group, a chain's window, an external the
194
+ * database cannot bind — is the refusal `JD0037` naming the barrier,
195
+ * raised before any statement runs; the plan is declined, never run
196
+ * with its memory behaviour quietly changed. On `execute()` it is a
197
+ * `TypeError`: a whole answer has no stream to hold to. */
198
+ strictStreaming?: boolean;
199
+ }
200
+
201
+ /** What a cursor takes: `execute`'s options, `signal` honoured at every
202
+ * row boundary — an aborted cursor releases its statement and every
203
+ * later pull is `JD2072`. */
204
+ export interface CursorOptions extends ExecuteOptions {}
205
+
206
+ /** An entity cursor's options. `tracking: true` registers every yielded
207
+ * entity document with the unit of work — a snapshot per row, so the
208
+ * tracker grows with the result and is bounded by nothing but it; off
209
+ * by default for exactly that reason. The document must then return a
210
+ * bare entity binding (`JD0034` for a projection, a count or a window). */
211
+ export interface EntityCursorOptions extends CursorOptions {
212
+ tracking?: boolean;
85
213
  }
86
214
 
87
215
  /** An include's clauses: the root's without `after` — a keyset cursor
@@ -89,6 +217,14 @@ export interface ExecuteOptions {
89
217
  export interface LoadInclude extends Omit<LoadSpec, 'after'> {
90
218
  /** Project the related-row COUNT instead of the rows. */
91
219
  count?: boolean;
220
+ /** The per-root bounds (MODEL-FORMAT §10.4): rows of this relation
221
+ * per parent (default `INCLUDE_ROWS_DEFAULT`, or the include's own
222
+ * `take`), and serialised bytes per parent (default
223
+ * `INCLUDE_BYTES_DEFAULT`). Crossing one is the refusal `JD2073`,
224
+ * never a truncated graph. `Infinity` (`null` in JSON) is the
225
+ * unbounded case, spelled. */
226
+ maxRows?: number | null;
227
+ maxBytes?: number | null;
92
228
  }
93
229
 
94
230
  export interface LoadSpec {
@@ -97,16 +233,103 @@ export interface LoadSpec {
97
233
  orderBy?: unknown;
98
234
  take?: number;
99
235
  skip?: number;
100
- /** The keyset cursor: needs a single unique-column ordering. */
101
- after?: string | number;
236
+ /** The keyset cursor (MODEL-FORMAT §10.5): the structural continuation
237
+ * a page emitted, over the declared ordering with the primary key
238
+ * appended; or, the single-column form, one value of a unique
239
+ * ordering column. */
240
+ after?: string | number | LoadContinuation;
102
241
  maxDepth?: number;
103
242
  include?: Readonly<Record<string, boolean | LoadInclude>>;
104
243
  }
105
244
 
245
+ /** One term of a keyset ordering's identity: the mapped column, its
246
+ * direction, and where its nulls sort. */
247
+ export interface OrderIdentity {
248
+ readonly column: string;
249
+ readonly desc: boolean;
250
+ readonly nullsFirst: boolean;
251
+ }
252
+
253
+ /** One term of the order a statement actually executes under. `source`
254
+ * is closed: a mapped `column`, a `document` path the dialect extracts,
255
+ * the `group` key of a bucketed plan, or the row `identity` an emitter
256
+ * appends so a sequence answers in insertion order. Only a `column`
257
+ * term carries a column name, only a `document` term a path; the
258
+ * identity term carries neither, and its `nullsFirst` is `null` because
259
+ * a row identity is never absent. `tieBreaker` marks a term the plan
260
+ * appended rather than one the caller declared. */
261
+ export interface EffectiveOrderTerm {
262
+ readonly source: 'column' | 'document' | 'group' | 'identity';
263
+ readonly binding: string | null;
264
+ readonly column: string | null;
265
+ readonly path: readonly (string | number)[] | null;
266
+ readonly desc: boolean;
267
+ readonly nullsFirst: boolean | null;
268
+ readonly tieBreaker: boolean;
269
+ }
270
+
271
+ /**
272
+ * The continuation a page emits (MODEL-FORMAT §10.5): unsigned,
273
+ * structural, opaque — the ordering's identity, so it cannot be
274
+ * replayed against another ordering (`JD0035`); the last row's declared
275
+ * order-key values as the document carries them; and the row's primary
276
+ * key, the tie-breaker the plan appends. Signing, tenant scoping, expiry
277
+ * and wire encoding are the HOST's: the store has no principal and no
278
+ * key, and a continuation handed to an untrusted client unsigned is the
279
+ * host's mistake, not a store guarantee.
280
+ */
281
+ export interface LoadContinuation {
282
+ readonly order: readonly OrderIdentity[];
283
+ readonly keys: readonly unknown[];
284
+ readonly key: EntityKeyArg;
285
+ }
286
+
287
+ /** A page's options: `limit` roots at most (default `PAGE_LIMIT_DEFAULT`),
288
+ * `maxBytes` serialised bytes at most (`Infinity`/absent for no byte
289
+ * bound), the continuation to resume from, and `consistency` —
290
+ * `'snapshot'` is refused (`JD0036`) over an ordering whose keys a
291
+ * write may change; `'live'` (the default) reports the truth in
292
+ * `snapshot`. */
293
+ export interface PageOptions<C = LoadContinuation> extends EntityCursorOptions {
294
+ limit?: number;
295
+ after?: C;
296
+ maxBytes?: number | null;
297
+ consistency?: 'live' | 'snapshot';
298
+ }
299
+
300
+ /** One page: never more than `limit` items or `maxBytes` bytes; the
301
+ * continuation of the last delivered item (or the one resumed from, when
302
+ * nothing fit); `hasMore` by one peek past the page; `snapshot` true
303
+ * only over an immutable ordering — otherwise LIVE pagination, where a
304
+ * row whose order key changes can move across the cursor. */
305
+ export interface Page<T, C = LoadContinuation> {
306
+ readonly items: T[];
307
+ readonly continuation: C | null;
308
+ readonly hasMore: boolean;
309
+ readonly snapshot: boolean;
310
+ }
311
+
106
312
  export interface LoadExplanation {
107
313
  sql: string;
108
314
  pagination: 'keyset' | 'offset' | 'none';
109
315
  includes: ReadonlyArray<{ path: string; kind: string; count: boolean }>;
316
+ /** The per-root bounds every row-projecting include runs under;
317
+ * `null` is the unbounded case a caller spelled. */
318
+ bounds: ReadonlyArray<{ path: string; maxRows: number | null; maxBytes: number | null }>;
319
+ /** The effective deterministic order the statement executes under, in
320
+ * every load mode: the declared terms, then the tie-breaker the clause
321
+ * appends — the primary key in keyset mode, the row identity
322
+ * otherwise. */
323
+ order: readonly EffectiveOrderTerm[];
324
+ /** The keyset ordering's IDENTITY: the value a continuation carries and
325
+ * is checked against (`JD0035`); `null` for a load outside keyset
326
+ * mode, which has no continuation to emit. Whether a page over it is a
327
+ * snapshot is `snapshot`. */
328
+ identity: readonly OrderIdentity[] | null;
329
+ snapshot: boolean | null;
330
+ /** A graph load pulls one root row per statement row, always. */
331
+ streaming: 'row';
332
+ barrier: null;
110
333
  }
111
334
 
112
335
  /** What `saveChanges()` returns: data, not a boolean (§11.6). */
@@ -136,13 +359,159 @@ export interface StoreStats {
136
359
  liveQueries: number;
137
360
  }
138
361
 
362
+ /**
363
+ * The connection pragmas in effect, read back from the connection after
364
+ * the open sequence applied them — never the requested values. `null`
365
+ * where the driver's binding declares the pragma absent or the engine
366
+ * answers nothing (a `:memory:` database's `mmapSize`).
367
+ */
368
+ export interface StorePragmas {
369
+ readonly busyTimeout: number | null;
370
+ readonly journalMode: 'delete' | 'truncate' | 'persist' | 'memory' | 'wal' | 'off' | null;
371
+ readonly synchronous: 'off' | 'normal' | 'full' | 'extra' | null;
372
+ readonly walAutocheckpoint: number | null;
373
+ readonly journalSizeLimit: number | null;
374
+ readonly cacheSize: number | null;
375
+ readonly mmapSize: number | null;
376
+ readonly tempStore: 'default' | 'file' | 'memory' | null;
377
+ }
378
+
379
+ export interface MaintenanceCapabilities {
380
+ readonly checkpoint: boolean;
381
+ readonly integrityCheck: boolean;
382
+ readonly foreignKeyCheck: boolean;
383
+ readonly optimize: boolean;
384
+ /** The online backup (`backupTo`): the Node binding's; `false`
385
+ * elsewhere, where the call is refused `JD2077`. */
386
+ readonly backup: boolean;
387
+ }
388
+
389
+ /** The platform's progress report, verbatim: the last event may still
390
+ * carry a remainder — completion is the resolved call. */
391
+ export interface BackupProgress {
392
+ readonly totalPages: number;
393
+ readonly remainingPages: number;
394
+ }
395
+
396
+ export interface BackupOptions {
397
+ /** Pages copied per step (the platform's `rate`); progress and the
398
+ * cancellation check happen between steps. */
399
+ rate?: number;
400
+ onProgress?: (progress: BackupProgress) => void;
401
+ /** Cancels between pages: `JD2079`, the temporary file removed, the
402
+ * target untouched. */
403
+ signal?: AbortSignal;
404
+ /** An epoch-millisecond deadline on the store's clock, honoured before
405
+ * the copy and between its pages (`JD2075`, same cleanup). */
406
+ deadline?: number;
407
+ /** The checkpoint that fixes the snapshot boundary (default
408
+ * `'passive'`; `false` skips it; a read-only store skips it by
409
+ * default and refuses an explicit one `JD2077`). */
410
+ checkpoint?: 'passive' | 'full' | 'restart' | 'truncate' | false;
411
+ }
412
+
413
+ export interface BackupResult {
414
+ readonly path: string;
415
+ /** The page total the platform answered. */
416
+ readonly pages: number;
417
+ readonly checkpoint: CheckpointResult | null;
418
+ }
419
+
420
+ /** Cancellation of a maintenance operation: checked once, before the
421
+ * one statement it issues (`JD2081` / `JD2075`, on the store's clock). */
422
+ export interface MaintenanceCallOptions {
423
+ signal?: AbortSignal;
424
+ /** An epoch-millisecond deadline on the runtime record's clock. */
425
+ deadline?: number;
426
+ }
427
+
428
+ export interface CheckpointOptions extends MaintenanceCallOptions {
429
+ /** `PRAGMA wal_checkpoint` mode (default `'passive'`). */
430
+ mode?: 'passive' | 'full' | 'restart' | 'truncate';
431
+ }
432
+
433
+ /** The engine's own checkpoint row: `-1` frames on a database that is
434
+ * not in WAL mode. A second passive checkpoint reports the same counts
435
+ * as the first; a second `truncate` reports zeros. */
436
+ export interface CheckpointResult {
437
+ readonly busy: boolean;
438
+ readonly logFrames: number;
439
+ readonly checkpointedFrames: number;
440
+ }
441
+
442
+ export interface IntegrityCheckOptions extends MaintenanceCallOptions {
443
+ /** At most this many problem rows (`PRAGMA integrity_check(N)`). */
444
+ limit?: number;
445
+ }
446
+
447
+ /** `ok` for the engine's single `ok` row; otherwise its problem rows
448
+ * verbatim. Corruption is the result, never a throw. */
449
+ export interface IntegrityCheckResult {
450
+ readonly ok: boolean;
451
+ readonly problems: readonly string[];
452
+ }
453
+
454
+ export interface ForeignKeyViolation {
455
+ readonly table: string;
456
+ readonly rowId: number | null;
457
+ readonly parent: string;
458
+ readonly fkid: number;
459
+ }
460
+
461
+ export interface ForeignKeyCheckResult {
462
+ readonly ok: boolean;
463
+ readonly violations: readonly ForeignKeyViolation[];
464
+ }
465
+
466
+ /** `PRAGMA optimize` reports nothing; the honest result is that it ran. */
467
+ export interface OptimizeResult {
468
+ readonly ran: true;
469
+ }
470
+
471
+ /** The granularity at which each lifecycle honours `signal`/`deadline`:
472
+ * the boundaries the driver actually has. `midStatement` is a filled
473
+ * slot — `false` on every shipped SQLite binding, which exposes no
474
+ * interrupt. */
475
+ export interface CancellationCapabilities {
476
+ readonly query: 'row';
477
+ readonly queue: true;
478
+ readonly migration: 'step';
479
+ readonly maintenance: 'statement';
480
+ readonly backup: 'page';
481
+ readonly midStatement: boolean;
482
+ }
483
+
139
484
  export interface StoreCapabilities {
140
485
  readonly version: string;
141
486
  readonly readOnly: boolean;
142
487
  readonly validated: boolean;
143
488
  readonly profiled: boolean;
489
+ /** The read-back connection configuration (MODEL-FORMAT §4). */
490
+ readonly pragmas: StorePragmas;
491
+ /** `pragmas.busyTimeout` under its long-published name. */
144
492
  readonly busyTimeoutMs: number | null;
493
+ /** `pragmas.journalMode` under its long-published name. */
145
494
  readonly journalMode: string | null;
495
+ /** The configuration pragmas the driver's binding declares it can
496
+ * apply, by option name; a request outside them is `JD0007`. */
497
+ readonly configurablePragmas: readonly string[];
498
+ /** Per-operation availability of the maintenance surface: `false`
499
+ * where the driver's binding does not declare an operation and, for
500
+ * the two that write, on a read-only store; a call is refused
501
+ * `JD2077` exactly where this says `false`. */
502
+ readonly maintenance: MaintenanceCapabilities;
503
+ /** Where a cancellation takes effect, per lifecycle (MODEL-FORMAT §4). */
504
+ readonly cancellation: CancellationCapabilities;
505
+ /** Whether the binding's statements carry a lazy row iterator; when
506
+ * `false` every cursor reports `streaming: 'buffered'` with a
507
+ * `{ construct: 'driver' }` barrier. Probed at open. */
508
+ readonly lazyIteration: boolean;
509
+ readonly sessions: boolean;
510
+ readonly sessionReason: string | null;
511
+ readonly worker: boolean;
512
+ readonly pooling: boolean;
513
+ readonly poolReaders: number;
514
+ readonly poolWriters: number;
146
515
  readonly capture: 'session' | 'journal' | 'none';
147
516
  readonly captureLog: boolean;
148
517
  readonly live: boolean;
@@ -171,7 +540,7 @@ export interface Collection<T = unknown> {
171
540
  * a synchronous driver stays synchronous. */
172
541
  execute<R = unknown>(document: unknown, options?: ExecuteOptions): ValueOrPromise<SequenceResult<R>>;
173
542
  /** The same document as an item cursor — one item per pull. */
174
- query<R = unknown>(document: unknown, options?: ExecuteOptions): QueryCursor<R>;
543
+ query<R = unknown>(document: unknown, options?: CursorOptions): QueryCursor<R>;
175
544
  explain(document: unknown, options?: ExecuteOptions): Promise<unknown>;
176
545
  /** Register a live query (LIVE-FORMAT §7); requires capture. */
177
546
  live(document: unknown, options?: LiveOptions): Promise<LiveQuery>;
@@ -233,6 +602,13 @@ export interface EntitySet<T = unknown, I = unknown> {
233
602
  update(key: EntityKeyArg, changes: Partial<T>): Promise<Readonly<T>>;
234
603
  delete(key: EntityKeyArg): Promise<boolean>;
235
604
  load(spec?: LoadSpec): Promise<ReadonlyArray<Readonly<T>>>;
605
+ /** The graph cursor: one root graph per pull, its includes attached
606
+ * and bounded (§10.4), from the same one statement `load` runs;
607
+ * `return()` releases it. Untracked unless `tracking: true`. */
608
+ loadCursor(spec?: LoadSpec, options?: EntityCursorOptions): QueryCursor<Readonly<T>>;
609
+ /** One bounded page over the composite keyset (§10.5). A `take` or
610
+ * `skip` in the spec is refused: the page windows by its limit. */
611
+ page(spec?: LoadSpec, options?: PageOptions): Promise<Page<Readonly<T>>>;
236
612
  explainLoad(spec?: LoadSpec): LoadExplanation;
237
613
  /** Track a pending insert (local, synchronous — no round trip). */
238
614
  add(doc: I): Readonly<T>;
@@ -253,6 +629,12 @@ export interface EntitySet<T = unknown, I = unknown> {
253
629
  * the document is over the multi-entity root and arrives whole; the
254
630
  * answer is the engine's result shape, value-or-promise (D2). */
255
631
  execute<R = unknown>(document: unknown, options?: ExecuteOptions): ValueOrPromise<SequenceResult<R>>;
632
+ /** The same document as an item cursor: one row per pull from an open
633
+ * statement, released on `return()`; a set residual materialises the
634
+ * fetched root first and says so (`streaming: 'buffered'`). A chain's
635
+ * `for await` over this set is this cursor. Untracked unless
636
+ * `tracking: true`. */
637
+ cursor<R = T>(document: unknown, options?: EntityCursorOptions): QueryCursor<R>;
256
638
  explain(document: unknown, options?: ExecuteOptions): Promise<unknown>;
257
639
  /** The root expression this set's rows are bound through (`$.<Name>[*]`). */
258
640
  readonly root: string;
@@ -278,6 +660,9 @@ export interface SyncEntitySet<T = unknown, I = unknown> {
278
660
  update(key: EntityKeyArg, changes: Partial<T>): Readonly<T>;
279
661
  delete(key: EntityKeyArg): boolean;
280
662
  load(spec?: LoadSpec): ReadonlyArray<Readonly<T>>;
663
+ loadCursor(spec?: LoadSpec, options?: EntityCursorOptions): SyncQueryCursor<Readonly<T>>;
664
+ page(spec?: LoadSpec, options?: PageOptions): Page<Readonly<T>>;
665
+ cursor<R = T>(document: unknown, options?: EntityCursorOptions): SyncQueryCursor<R>;
281
666
  explainLoad(spec?: LoadSpec): LoadExplanation;
282
667
  add(doc: I): Readonly<T>;
283
668
  put(next: T): Readonly<T>;
@@ -294,6 +679,15 @@ export interface SyncEntitySet<T = unknown, I = unknown> {
294
679
  readonly relations: RelationTable;
295
680
  }
296
681
 
682
+ /** A lazy synchronous cursor; return, disposal, errors and close release its source. */
683
+ export interface SyncQueryCursor<T = unknown> extends IterableIterator<T> {
684
+ readonly streaming: 'row' | 'buffered';
685
+ readonly barrier: CursorBarrier | null;
686
+ return(): IteratorResult<T, undefined>;
687
+ [Symbol.iterator](): SyncQueryCursor<T>;
688
+ [Symbol.dispose](): void;
689
+ }
690
+
297
691
  // ————— the store —————
298
692
 
299
693
  export interface SyncStore {
@@ -301,7 +695,7 @@ export interface SyncStore {
301
695
  * the consumer's words; the handle's writes take it and reads answer it. */
302
696
  collection<T = unknown>(name: string): SyncCollection<T>;
303
697
  entity(name: string): SyncEntitySet;
304
- transaction<R>(fn: (store: Store) => R): R;
698
+ transaction<R>(fn: (store: TransactionStore) => R): R;
305
699
  execute?<R = unknown>(document: unknown, options?: ExecuteOptions): SequenceResult<R>;
306
700
  explain?(document: unknown, options?: ExecuteOptions): unknown;
307
701
  /** The entity roots this store-level provider serves (present with
@@ -315,6 +709,7 @@ export interface SyncStore {
315
709
  }
316
710
 
317
711
  export interface Store {
712
+ readonly replication?: Replication;
318
713
  readonly capabilities: StoreCapabilities;
319
714
  readonly dialect: Dialect;
320
715
  stats(): StoreStats;
@@ -336,13 +731,56 @@ export interface Store {
336
731
  readonly relations?: Readonly<Record<string, RelationTable>>;
337
732
  /** The unit of work (§11); present only with entities. */
338
733
  saveChanges?(): Promise<SaveReport>;
339
- transaction<R>(fn: (store: Store) => R | Promise<R>): Promise<Awaited<R>>;
734
+ /**
735
+ * A top-level transaction. The callback receives a
736
+ * {@link TransactionStore} whose handles are INSIDE it; this store's
737
+ * own handles are an unrelated caller and wait for the commit.
738
+ *
739
+ * `signal` abandons the call while it is still QUEUED — the callback
740
+ * then never runs and no statement is issued (`JD2064`). A transaction
741
+ * that has already taken the connection runs to its own end.
742
+ *
743
+ * `unitOfWork: 'own'` gives the callback a tracker of its own, so two
744
+ * concurrent handlers hold two records for one entity key and neither
745
+ * sees the other's pending state; `'shared'` (the default) writes
746
+ * through the store's, which is what lets a caller `add()` a document
747
+ * outside the transaction and save it inside. Both behave identically
748
+ * with and without capture.
749
+ */
750
+ transaction<R>(fn: (store: TransactionStore) => R | Promise<R>,
751
+ options?: TransactionScopeOptions): Promise<Awaited<R>>;
340
752
  /** Register a change observer; requires capture. Returns unsubscribe. */
341
753
  observe(fn: (record: ChangeRecord) => void): () => void;
342
- /** Read the persisted log forward (JD2051 without capture.log). */
754
+ /** Read the persisted log forward from `after` (`JD2051` without
755
+ * `capture.log`) — EVERY surviving record in one array, UNBOUNDED, with
756
+ * no watermark: a reconnecting consumer whose cursor fell below the
757
+ * retention floor receives the surviving suffix and cannot tell it
758
+ * from the whole. Unsafe for a reconnecting consumer; `changes.page()`
759
+ * is the supported path (LIVE-FORMAT §5). */
343
760
  changesSince?(after: number): Promise<ChangeRecord[]>;
761
+ /** The bounded change reader: the log's watermarks and pages that
762
+ * never exceed their bounds and report a retention gap explicitly;
763
+ * present exactly when the log is enabled. */
764
+ readonly changes?: ChangesReader;
344
765
  /** PRAGMA data_version — the coarse cross-connection signal. */
345
766
  dataVersion(): Promise<number>;
767
+ /** `PRAGMA wal_checkpoint(<mode>)` under the store gate (MODEL-FORMAT
768
+ * §4). Refused `JD2077` on a read-only store or a binding that does
769
+ * not declare it; a driver failure is `JD2078`. */
770
+ checkpoint(options?: CheckpointOptions): Promise<CheckpointResult>;
771
+ /** `PRAGMA integrity_check` under the store gate; corruption is the
772
+ * RESULT (`ok: false`), never a throw. */
773
+ integrityCheck(options?: IntegrityCheckOptions): Promise<IntegrityCheckResult>;
774
+ /** `PRAGMA foreign_key_check` under the store gate. */
775
+ foreignKeyCheck(options?: MaintenanceCallOptions): Promise<ForeignKeyCheckResult>;
776
+ /** `PRAGMA optimize` under the store gate; refused `JD2077` on a
777
+ * read-only store. */
778
+ optimize(options?: MaintenanceCallOptions): Promise<OptimizeResult>;
779
+ /** An online backup published whole or not at all (MODEL-FORMAT §4):
780
+ * copied to a temporary sibling, renamed onto `targetPath` only at
781
+ * verified completion; a cancelled (`JD2079`) or failed (`JD2078`)
782
+ * copy leaves neither file. Writers proceed during the copy. */
783
+ backupTo(targetPath: string, options?: BackupOptions): Promise<BackupResult>;
346
784
  /** Register a live query over an entity-root document (re-run
347
785
  * strategy in this version); present only with entities. */
348
786
  live?(document: unknown, options?: LiveOptions): Promise<LiveQuery>;
@@ -350,12 +788,148 @@ export interface Store {
350
788
  * `graceMs` to wind up; the connection closes whether or not they
351
789
  * did, and a handler still in flight is reported as JD2062. */
352
790
  close(options?: { graceMs?: number }): Promise<void>;
353
- /** The queue surface; present when opened with `jobs` (JOBS-FORMAT). */
354
- readonly jobs?: JobsApi;
791
+ /** The queue surface; present when opened with `jobs` (JOBS-FORMAT).
792
+ * Root calls here take the store gate — an unrelated enqueue, claim,
793
+ * checkpoint or settlement never joins an open application
794
+ * transaction's fate. The transactional-outbox spelling is the
795
+ * `tx.jobs` a transaction callback receives, which runs as the exact
796
+ * scope and co-commits with the domain transaction. */
797
+ readonly jobs?: JobsApi & JobsAdminApi;
355
798
  /** Present exactly when the driver is synchronous — never stubs. */
356
799
  readonly sync?: SyncStore;
357
800
  }
358
801
 
802
+ /** The options a top-level transaction takes. An unknown `unitOfWork`
803
+ * value is a compile error here and runtime API misuse there. */
804
+ export interface TransactionScopeOptions {
805
+ /** Abandons the call while it is still QUEUED (`JD2064`); a
806
+ * transaction that has taken the connection runs to its own end. */
807
+ signal?: AbortSignal;
808
+ /** `'own'` gives the callback an independent tracker; `'shared'`
809
+ * (the default) writes through the store's. */
810
+ unitOfWork?: 'own' | 'shared';
811
+ /** `'immediate'` takes the write lock up front (`BEGIN IMMEDIATE`), so
812
+ * a body that reads before it writes never meets the read→write
813
+ * upgrade `SQLITE_BUSY` the busy handler cannot retry — what a claim
814
+ * needs under concurrent writers; `'deferred'` (the default) is the
815
+ * savepoint as always. A nested `tx.transaction()` is a savepoint
816
+ * whichever mode the root chose; the synchronous twin has no mode. */
817
+ mode?: 'deferred' | 'immediate';
818
+ }
819
+
820
+ /**
821
+ * The named-savepoint group a live transaction view carries
822
+ * (MODEL-FORMAT §5.2): checkpoint-and-continue without a sentinel
823
+ * exception. The label is a map key and diagnostic for that exact
824
+ * transaction — never SQL; the driver generates the identifier. A
825
+ * blank, duplicate or unknown label is `JD2071`; a stale or
826
+ * cross-scope view is `JD2070` first. `rollbackTo` keeps the target
827
+ * active (repeated rollback is defined) and invalidates every later
828
+ * checkpoint; `release` removes the target and every later checkpoint,
829
+ * keeping their rows — the engine's own semantics, exactly.
830
+ */
831
+ export interface SavepointController {
832
+ create(label: string): Promise<void>;
833
+ rollbackTo(label: string): Promise<void>;
834
+ release(label: string): Promise<void>;
835
+ }
836
+
837
+ /** The synchronous twin of {@link SavepointController}, answering
838
+ * values (present under `tx.sync` on a synchronous driver). */
839
+ export interface SyncSavepointController {
840
+ create(label: string): void;
841
+ rollbackTo(label: string): void;
842
+ release(label: string): void;
843
+ }
844
+
845
+ /** The synchronous surface a transaction view carries: the store's,
846
+ * plus the transaction-only savepoint group. */
847
+ export interface TransactionSyncStore extends SyncStore {
848
+ readonly savepoints: SyncSavepointController;
849
+ }
850
+
851
+ /**
852
+ * The store a transaction callback receives: the same surface, with
853
+ * every handle bound to THIS transaction's exact scope.
854
+ *
855
+ * `tx.collection(...)`, `tx.entity(...)`, `tx.sync`, `tx.jobs` and
856
+ * `tx.saveChanges()` run as the transaction's owner, and
857
+ * `tx.transaction(...)` nests through its savepoint. The outer store's
858
+ * handles are, by construction, an unrelated caller: they wait for the
859
+ * commit, and one awaited from inside the callback is a self-wait that
860
+ * `JD0012` names rather than a hang.
861
+ *
862
+ * The view lives exactly as long as its own scope: any stateful member
863
+ * used after the transaction settled, or while an async inner savepoint
864
+ * is current, refuses `JD2070` before touching tracker state or the
865
+ * database. There is deliberately no `close` — a transaction view does
866
+ * not own the store lifetime; the root store remains the only owner of
867
+ * the connection.
868
+ */
869
+ export interface TransactionStore extends Omit<Store,
870
+ 'close' | 'transaction' | 'sync' | 'checkpoint' | 'integrityCheck' | 'foreignKeyCheck' | 'optimize' | 'backupTo'
871
+ | 'jobs' | 'replication'> {
872
+ /** The transactional outbox (JOBS-FORMAT §3): no administration here —
873
+ * an admin operation is a root call. */
874
+ readonly jobs?: JobsApi;
875
+ transaction<R>(fn: (store: TransactionStore) => R | Promise<R>): Promise<Awaited<R>>;
876
+ /** Named partial rollback over the transaction's one savepoint stack
877
+ * (MODEL-FORMAT §5.2). Root stores, clients, workers and checkpoint
878
+ * stores expose none of it. */
879
+ readonly savepoints: SavepointController;
880
+ /** Present exactly when the driver is synchronous, as on the store. */
881
+ readonly sync?: TransactionSyncStore;
882
+ }
883
+
884
+ /** The log's two watermarks (LIVE-FORMAT §5): the earliest surviving
885
+ * sequence (`null` when nothing survives) and the highest sequence the
886
+ * FILE ever allocated — durable across an emptied log, a reopen and a
887
+ * second store over the same file, never a process counter. */
888
+ export interface ChangeBounds {
889
+ readonly earliestAvailable: number | null;
890
+ readonly highWatermark: number;
891
+ }
892
+
893
+ /** A change page's options: `after` is the last sequence seen and is
894
+ * required — there is no legitimate "give me everything" for a change
895
+ * log; `limit` records at most (default `PAGE_LIMIT_DEFAULT`),
896
+ * `maxBytes` serialised patch bytes at most (none unless given),
897
+ * `signal` honoured at a record boundary (`JD2072`). */
898
+ export interface ChangePageOptions {
899
+ after: number;
900
+ limit?: number;
901
+ maxBytes?: number | null;
902
+ signal?: AbortSignal;
903
+ /** An epoch-millisecond deadline, read against the store's clock at every record boundary (`JD2075`). */
904
+ deadline?: number;
905
+ }
906
+
907
+ /**
908
+ * One page of the log. `resetRequired: true` means the record after
909
+ * `after` no longer survives: `items` is EMPTY and `next` absent — a
910
+ * total refusal, never a partial suffix — and the consumer re-seeds
911
+ * from a snapshot and resumes at `highWatermark`. Otherwise `next` is
912
+ * the sequence to continue from (`after` itself when nothing was
913
+ * delivered), `hasMore` says whether records remain above it, and the
914
+ * watermarks are the log's as read after the page.
915
+ */
916
+ export interface ChangePage {
917
+ readonly items: ChangeRecord[];
918
+ readonly next?: number;
919
+ readonly earliestAvailable: number | null;
920
+ readonly highWatermark: number;
921
+ readonly hasMore: boolean;
922
+ readonly resetRequired: boolean;
923
+ }
924
+
925
+ /** The bounded change reader (LIVE-FORMAT §5). A record larger than
926
+ * `maxBytes` is `JD2074` without advancing `next` — the same rule, the
927
+ * same implementation, as an entity page. */
928
+ export interface ChangesReader {
929
+ bounds(): Promise<ChangeBounds>;
930
+ page(options: ChangePageOptions): Promise<ChangePage>;
931
+ }
932
+
359
933
  /** One committed transaction's change record (LIVE-FORMAT §§1–5). */
360
934
  export interface ChangeRecord {
361
935
  /** Monotonic; continues across reopens when the log is enabled. */
@@ -405,7 +979,7 @@ export interface LiveEventTime {
405
979
 
406
980
  export interface LiveMode {
407
981
  readonly strategy: 'rows' | 'window' | 'accumulator' | 'group'
408
- | 'bucket' | 'rolling' | 'rerun';
982
+ | 'bucket' | 'rolling' | 'join' | 'graph' | 'nested-group' | 'rerun';
409
983
  readonly mode: 'incremental' | 'rerun';
410
984
  /** Present exactly when the strategy is 'rerun': the named reason. */
411
985
  readonly reason?: string;
@@ -431,6 +1005,9 @@ export interface LiveEvent {
431
1005
  }
432
1006
 
433
1007
  export interface LiveStats {
1008
+ dependencyReads?: number;
1009
+ refreshedRoots?: number;
1010
+ refreshedGroups?: number;
434
1011
  records: number;
435
1012
  matched: number;
436
1013
  emissions: number;
@@ -464,6 +1041,8 @@ export interface LiveQuery {
464
1041
  }
465
1042
 
466
1043
  export interface LiveBounds {
1044
+ /** Serialized input/output cache credit for join, graph and nested-group strategies. */
1045
+ maxBytes?: number;
467
1046
  /** Registrations beyond it are JD0052 (default 64). */
468
1047
  maxQueries?: number;
469
1048
  /** Per-query ceiling on maintained entries — rows, window entries
@@ -472,8 +1051,17 @@ export interface LiveBounds {
472
1051
  }
473
1052
 
474
1053
  export interface OpenStoreOptions {
1054
+ replication?: ReplicationOptions;
475
1055
  driver: Driver;
476
1056
  path?: string;
1057
+ /**
1058
+ * What a store-level call does while another caller's transaction owns
1059
+ * the connection. `'wait'` (the default) queues behind it under
1060
+ * `queueTimeout` and then refuses `JD0012`; `'strict'` refuses at once,
1061
+ * for a host that would rather see the contention than pay for it.
1062
+ * Either way the call never joins the transaction.
1063
+ */
1064
+ transactions?: 'wait' | 'strict';
477
1065
  /** Change capture (LIVE-FORMAT): off unless requested. */
478
1066
  capture?: boolean | CaptureOptions;
479
1067
  /** Live-query bounds (LIVE-FORMAT §12). */
@@ -489,13 +1077,43 @@ export interface OpenStoreOptions {
489
1077
  * where one was injected; without it the document is refused rather
490
1078
  * than answered in UTC. No time-zone database is bundled. */
491
1079
  zoneProvider?: unknown;
492
- busyTimeout?: number;
1080
+ /** The host's runtime record (`@jarenjs/core/runtime`): the clock the
1081
+ * capture log and the job queue stamp, the identifier a `uuid`
1082
+ * identity and a `default: 'uuid'` allocate, the job queue's backoff
1083
+ * jitter and the zone provider — each read only where the explicit
1084
+ * option (`zoneProvider`, `jobs.now`, `jobs.random`) is absent, and
1085
+ * handed on to the job engine. */
1086
+ runtime?: Partial<Runtime>;
493
1087
  /** How long work waits for an open transaction to settle before
494
1088
  * `JD0012` (MODEL-FORMAT §5.1); reaches every driver. */
495
1089
  queueTimeout?: number;
496
- journalMode?: string;
1090
+ /**
1091
+ * The connection pragmas — a closed, validated set (MODEL-FORMAT §4).
1092
+ * An option naming any other pragma is `JD0006`; a pragma the driver
1093
+ * or the store kind cannot apply is `JD0007`; every value is read back
1094
+ * after the open sequence and reported on `capabilities.pragmas`, and
1095
+ * one the engine did not take is `JD0008`.
1096
+ */
1097
+ /** `PRAGMA busy_timeout`, in milliseconds (default 5000). */
1098
+ busyTimeout?: number;
1099
+ /** `PRAGMA journal_mode` (default `'wal'` on a writable file; a
1100
+ * read-only store keeps the file's mode and refuses an explicit one). */
1101
+ journalMode?: 'delete' | 'truncate' | 'persist' | 'memory' | 'wal' | 'off';
1102
+ /** `PRAGMA synchronous`. */
1103
+ synchronous?: 'off' | 'normal' | 'full' | 'extra';
1104
+ /** `PRAGMA wal_autocheckpoint`, in pages; `0` disables. */
1105
+ walAutocheckpoint?: number;
1106
+ /** `PRAGMA journal_size_limit`, in bytes; `-1` for none. */
1107
+ journalSizeLimit?: number;
1108
+ /** `PRAGMA cache_size`: pages, or negative KiB. */
1109
+ cacheSize?: number;
1110
+ /** `PRAGMA mmap_size`, in bytes. */
1111
+ mmapSize?: number;
1112
+ /** `PRAGMA temp_store`. */
1113
+ tempStore?: 'default' | 'file' | 'memory';
497
1114
  statementCacheBound?: number;
498
- profile?: unknown;
1115
+ /** The store-level safety profile (MODEL-FORMAT §8). */
1116
+ profile?: 'safe' | ProfileSpec;
499
1117
  readOnly?: boolean;
500
1118
  /** A `createJsltRegistry()` registry (Ring 2/3): the operators a
501
1119
  * query may use, and the pushable subset. */
@@ -530,7 +1148,37 @@ export interface Driver {
530
1148
 
531
1149
  export declare const sqliteDialect: Dialect;
532
1150
  export declare function createDialect(spec: unknown): Dialect;
1151
+ /** The closed set of dialect capabilities, each with the answer a
1152
+ * spelling spec that says nothing gets. `createDialect` refuses a name
1153
+ * outside it, so a misspelling cannot read as a quiet `false`. */
1154
+ export declare const DIALECT_CAPABILITIES: Readonly<Record<string, boolean>>;
1155
+ /** The R*Tree mapping's DDL group for one spelling spec — the virtual
1156
+ * table and the three row triggers that keep it in sync. Composed into
1157
+ * a dialect whose `capabilities.virtualTables` is true. */
1158
+ export declare function rtreeDdl(spec: unknown): Record<string, Function>;
533
1159
  export declare const SQLITE_FLOOR: string;
1160
+ /** Assemble the connection contract around a raw binding whose
1161
+ * capabilities a probe has already answered. */
1162
+ export declare function finishConnection(
1163
+ raw: unknown, dialect: Dialect, synchronous: boolean,
1164
+ capabilities: Readonly<Record<string, unknown>>, queueTimeout: number,
1165
+ ): unknown;
1166
+ /** The default probe: SQLite's version report, its compile options and
1167
+ * what the binding declares. */
1168
+ export declare function sqliteProbe(
1169
+ raw: unknown, dialect: Dialect, declared: unknown,
1170
+ ): unknown;
1171
+ /** The capability answers every connection carries, each defaulted to
1172
+ * the conservative one — what a probe for another engine fills in. */
1173
+ export declare function baseCapabilities(): Record<string, unknown>;
1174
+ /** The option names of the closed configurable-pragma set, in the
1175
+ * order the open sequence applies them (MODEL-FORMAT §4). */
1176
+ export declare const PRAGMA_NAMES: readonly string[];
1177
+ /** The `PRAGMA wal_checkpoint` modes, closed. */
1178
+ export declare const CHECKPOINT_MODES: readonly string[];
1179
+ /** The maintenance operations, in the order `capabilities.maintenance`
1180
+ * lists them. */
1181
+ export declare const MAINTENANCE_OPERATIONS: readonly string[];
534
1182
 
535
1183
  // ————— entities: models, mapping, generated types —————
536
1184
 
@@ -564,6 +1212,8 @@ export declare function translateOperations(
564
1212
  export declare function keyToken(parts: readonly unknown[]): string;
565
1213
  export declare function createCaptureEngine(options: unknown): unknown;
566
1214
  export declare const CHANGES_TABLE: string;
1215
+ /** The change log's durable state table: the highest sequence the file ever allocated (LIVE-FORMAT §5). */
1216
+ export declare const CHANGES_STATE_TABLE: string;
567
1217
  export declare const DEFAULT_RETENTION: number;
568
1218
  /** Deep-freeze a JSON value in place and return it (idempotent). */
569
1219
  export declare function deepFreeze<T>(value: T): T;
@@ -572,6 +1222,58 @@ export declare const BATCH_ROW_BOUND: number;
572
1222
 
573
1223
  // ————— migrations —————
574
1224
 
1225
+ /** Where a migration runs: the driver, the file (`':memory:'` when
1226
+ * absent) and the busy timeout the run opens with. */
1227
+ export interface MigrationTarget {
1228
+ driver: Driver;
1229
+ path?: string;
1230
+ /** `PRAGMA busy_timeout` for the run's connection, ms (default 5000). */
1231
+ busyTimeout?: number;
1232
+ }
1233
+
1234
+ /** One progress event: the migration and collection a data step is
1235
+ * walking, and the running count of the rows it has transformed,
1236
+ * derived or asserted so far (one of the three counters per event). */
1237
+ export interface MigrationProgress {
1238
+ readonly migration: string;
1239
+ readonly collection: string;
1240
+ readonly transformed?: number;
1241
+ readonly derived?: number;
1242
+ readonly asserted?: number;
1243
+ }
1244
+
1245
+ /** One row of an introspection's loss report: what the physical shape
1246
+ * does not carry, by a stable code, once, sorted. */
1247
+ export interface IntrospectReportRow {
1248
+ readonly code: string;
1249
+ /** The physical object it is about (`users`, `users.gx_age`). */
1250
+ readonly object: string;
1251
+ readonly detail: string;
1252
+ }
1253
+
1254
+ /** Every code the loss report uses, with what it means. */
1255
+ export declare const INTROSPECT_CODES: Readonly<Record<string, string>>;
1256
+
1257
+ /**
1258
+ * Database → model, read-only. Answers the `jaren-model` document this
1259
+ * database's shape says it is, beside a report of everything the shape
1260
+ * cannot carry. `strict` refuses instead of returning a partial model.
1261
+ */
1262
+ export declare function introspectModel(connection: unknown, options?: {
1263
+ strict?: boolean;
1264
+ /** Narrow the read to a named set of tables. */
1265
+ tables?: readonly string[];
1266
+ /** The document pointer a text key column cannot record, per table. */
1267
+ keys?: Record<string, string>;
1268
+ }): unknown;
1269
+
1270
+ /** The neutral IR one read produces: tables with their columns,
1271
+ * generated expressions, indexes and foreign keys, plus the views a
1272
+ * model cannot declare. */
1273
+ export declare function readSchema(connection: unknown, options?: {
1274
+ tables?: readonly string[];
1275
+ }): unknown;
1276
+
575
1277
  export interface MigrateOptions {
576
1278
  baseline: unknown;
577
1279
  model?: unknown;
@@ -579,14 +1281,64 @@ export interface MigrateOptions {
579
1281
  dryRun?: boolean;
580
1282
  batchSize?: number;
581
1283
  shadow?: boolean;
1284
+ /** Where the shadow replay runs (default `':memory:'`). */
1285
+ shadowPath?: string;
1286
+ /** The host's declared index-expression functions, by name — the same
1287
+ * declarations `openStore` is given, resolved into the planned DDL. */
1288
+ expressions?: Record<string, ExpressionFunction>;
1289
+ /** The driver the shadow replay opens through (default the target's).
1290
+ * A file engine's shadow is another file; a SERVER engine's is another
1291
+ * schema, and only the host can name one — the baseline shape the
1292
+ * replay creates would otherwise collide with the real store's. */
1293
+ shadowDriver?: unknown;
1294
+ /** Called once per batch a data step walks (transform, derive, or a
1295
+ * per-document assertion). */
1296
+ onProgress?: (progress: MigrationProgress) => void;
582
1297
  /** Re-register declared deterministic functions on every connection
583
1298
  * the migration opens (real, shadow, reference) — §10. */
584
1299
  registerFunctions?: (connection: unknown) => unknown;
1300
+ /** The host's runtime record: the clock every applied migration is
1301
+ * stamped with and the deadline is read against; the platform's own
1302
+ * when absent. */
1303
+ runtime?: Partial<Runtime>;
1304
+ /** Cancels between migrations, steps and batches (`JD2080`); the
1305
+ * migration in flight rolls back whole. */
1306
+ signal?: AbortSignal;
1307
+ /** An epoch-millisecond deadline on `runtime`'s clock (`JD2075`). */
1308
+ deadline?: number;
1309
+ /** What a MATERIALIZING assertion may hold. A per-document predicate
1310
+ * and a single associative aggregate over the root are answered in
1311
+ * batches and are never bounded by this; anything else must hold the
1312
+ * collection at once and crosses these bounds before the excess is
1313
+ * held (`JD2007` rows, `JD2076` bytes). Defaults to
1314
+ * {@link ASSERTION_BOUNDS_DEFAULT}; `null` on either member removes
1315
+ * that bound, deliberately. */
1316
+ assertionBounds?: { maxRows?: number | null; maxBytes?: number | null };
585
1317
  }
586
1318
 
1319
+ /** The finite defaults a materializing assertion runs under when the
1320
+ * caller declares none: 100,000 rows and 64 MiB. */
1321
+ export declare const ASSERTION_BOUNDS_DEFAULT: {
1322
+ readonly maxRows: number;
1323
+ readonly maxBytes: number;
1324
+ };
1325
+
1326
+ /** How a host must run an assertion, and why. `perDocument` walks in
1327
+ * batches; `fold` is one associative aggregate whose batch answers
1328
+ * combine; `materialize` needs every document at once and is bounded. */
1329
+ export declare function classifyAssertion(query: unknown): {
1330
+ strategy: 'perDocument' | 'fold' | 'materialize';
1331
+ shape: string | null;
1332
+ reason: string;
1333
+ };
1334
+
587
1335
  export declare function migrate(
588
- target: unknown, migrations: readonly unknown[], options: MigrateOptions,
1336
+ target: MigrationTarget, migrations: readonly unknown[], options: MigrateOptions,
589
1337
  ): Promise<unknown>;
1338
+ /** Whether an assertion step is a per-document predicate (a FLWOR over
1339
+ * `$[*]` whose body reads only its binding), which the runner evaluates
1340
+ * per batch; anything else reads the collection whole. */
1341
+ export declare function isPerDocumentAssertion(query: unknown): boolean;
590
1342
  export declare function planMigration(from: unknown, to: unknown, options?: unknown): unknown;
591
1343
  /** The whole-model diff — collections AND entities (MIGRATION-FORMAT §9). */
592
1344
  export declare function planModelMigration(from: unknown, to: unknown, options?: unknown): unknown;
@@ -597,11 +1349,15 @@ export interface MigrationStatusReport {
597
1349
  drift: string | null;
598
1350
  upToDate: boolean;
599
1351
  }
1352
+ /** Report a database's migration state without touching it: the
1353
+ * history table is probed, never created. `model` enables the drift
1354
+ * comparison once the chain is fully applied. */
600
1355
  export declare function migrationStatus(
601
- target: { driver: Driver; path?: string },
1356
+ target: MigrationTarget,
602
1357
  migrations: readonly unknown[],
603
- options: { baseline: unknown; model?: unknown;
604
- registerFunctions?: (connection: unknown) => unknown },
1358
+ options?: { model?: unknown;
1359
+ registerFunctions?: (connection: unknown) => unknown;
1360
+ signal?: AbortSignal; deadline?: number; runtime?: Partial<Runtime> },
605
1361
  ): Promise<MigrationStatusReport>;
606
1362
  /** Create a model's whole physical shape on a connection. */
607
1363
  export declare function createModelShape(connection: unknown, model: unknown): unknown;
@@ -619,6 +1375,97 @@ export declare function migrationChecksum(migration: unknown): string;
619
1375
  export declare const MIGRATION_VERSION: string;
620
1376
  export declare const HISTORY_TABLE: string;
621
1377
 
1378
+ /** The counters a storeless run reports for one collection. */
1379
+ export interface DocumentMigrationCounts {
1380
+ readonly read: number;
1381
+ readonly transformed: number;
1382
+ readonly asserted: number;
1383
+ }
1384
+
1385
+ /** What a storeless run did, per collection: `materialized` held the
1386
+ * documents and ran the steps as a Store does; `streamed` carried each
1387
+ * batch through every step in one pass. */
1388
+ export type DocumentMigrationStrategy = 'materialized' | 'streamed';
1389
+
1390
+ export interface DocumentMigrationReport {
1391
+ /** The migration ids this run applied, in order. */
1392
+ readonly applied: string[];
1393
+ /** Always empty: a storeless run has no history to skip against. */
1394
+ readonly skipped: string[];
1395
+ /** The last migration's target shape hash, or null for no migrations. */
1396
+ readonly shape: string | null;
1397
+ readonly counts: Record<string, DocumentMigrationCounts>;
1398
+ readonly strategy: Record<string, DocumentMigrationStrategy>;
1399
+ }
1400
+
1401
+ export interface DocumentMigrationOptions {
1402
+ /** What a MATERIALIZING assertion may hold; the same bounds, and the
1403
+ * same refusals, a Store applies. Defaults to
1404
+ * {@link ASSERTION_BOUNDS_DEFAULT}. */
1405
+ assertionBounds?: { maxRows?: number | null; maxBytes?: number | null };
1406
+ /** Documents per assertion batch and per progress event (default 500). */
1407
+ batchSize?: number;
1408
+ onProgress?: (progress: MigrationProgress) => void;
1409
+ /** The key members of a collection's documents, which a transform may
1410
+ * keep but never move; a Store reads these from its model. */
1411
+ keys?: Record<string, readonly string[]>;
1412
+ /** The JSLT compiler a `jslt` step is compiled with (the suite's own
1413
+ * when absent). */
1414
+ compileJslt?: (stylesheet: unknown) => (document: unknown) => unknown;
1415
+ /** The query compiler a `query` step is compiled with. */
1416
+ compileQuery?: (query: unknown) => unknown;
1417
+ runtime?: Partial<Runtime>;
1418
+ /** Cancels between steps and batches (`JD2080`). */
1419
+ signal?: AbortSignal;
1420
+ /** An epoch-millisecond deadline on `runtime`'s clock (`JD2075`). */
1421
+ deadline?: number;
1422
+ }
1423
+
1424
+ /** Apply a migration's document steps to documents held in memory. The
1425
+ * source is rewindable, so the steps run exactly as a Store runs them —
1426
+ * every step over the whole collection, in step order — which is what
1427
+ * makes the answer, and the refusal, identical to the Store's. A step
1428
+ * that needs tables (`ddl`, `sql`, `rebuild`, `derive`) is refused
1429
+ * (`JD0023`) before the first document is read. */
1430
+ export declare function migrateDocuments(
1431
+ collections: Record<string, readonly unknown[]>,
1432
+ migrations: readonly unknown[],
1433
+ options?: DocumentMigrationOptions,
1434
+ ): Promise<{ documents: Record<string, unknown[]>; report: DocumentMigrationReport }>;
1435
+
1436
+ /** Apply a migration's document steps to a source that can be walked
1437
+ * only once, writing each document out as it finishes: the input is
1438
+ * consumed exactly once and nothing beyond one batch is held. A
1439
+ * cross-document assertion, which needs every document at once, is
1440
+ * refused by name rather than silently buffering the collection. */
1441
+ export declare function streamDocuments(
1442
+ sources: Record<string, Iterable<unknown> | AsyncIterable<unknown>>,
1443
+ migrations: readonly unknown[],
1444
+ options: DocumentMigrationOptions & {
1445
+ write: (collection: string, document: unknown) => unknown;
1446
+ },
1447
+ ): Promise<DocumentMigrationReport>;
1448
+
1449
+ /** The step kinds that act on documents, and so run on any host. */
1450
+ export declare const DOCUMENT_STEP_KINDS: ReadonlySet<string>;
1451
+ /** The step kinds that need a physical database and are refused without one. */
1452
+ export declare const PHYSICAL_STEP_KINDS: ReadonlySet<string>;
1453
+ /** Compile one document step into the operation every host runs. */
1454
+ export declare function compileDocumentStep(
1455
+ step: unknown, index: number, context: {
1456
+ migrationId: string;
1457
+ compileJslt: (stylesheet: unknown) => (document: unknown) => unknown;
1458
+ compileQuery: (query: unknown) => unknown;
1459
+ keys?: readonly string[];
1460
+ },
1461
+ ): unknown;
1462
+ /** Structural validation of one migration document (`JD0023`/`JD0021`). */
1463
+ export declare function checkMigrationDocument(migration: unknown): void;
1464
+ /** The refusal a failing step raises, spelled the one way every host spells it. */
1465
+ export declare function stepFailure(
1466
+ migrationId: string, index: number, kind: string, reason: string, cause?: Error,
1467
+ ): DbCompileError;
1468
+
622
1469
  // ————— the machinery exports —————
623
1470
  // The planner/emitter/residual/profile internals are public for tools
624
1471
  // and tests; their documents have their own formats, so their types
@@ -628,11 +1475,25 @@ export declare function planCollection(name: string, collection: unknown, dialec
628
1475
  export declare function compileIndexPath(expression: string, docPath: string): unknown;
629
1476
  export declare function normalizeDeclaredSql(sql: string): string;
630
1477
  export declare function comparableDeclaredSql(sql: string): string;
1478
+ /** The comparison kind a declared schema type implies — what a column
1479
+ * over that member holds, and how its expression must read it. */
1480
+ export declare function columnKindFor(
1481
+ schemaType: string | undefined,
1482
+ ): 'text' | 'number' | 'boolean' | undefined;
631
1483
  export declare function schemaTypeAt(schema: unknown, segments: unknown): unknown;
632
1484
  export declare const KEY_COLUMN: string;
633
1485
  export declare const DOC_COLUMN: string;
634
1486
  export declare function planQuery(document: unknown, shape: unknown, options?: unknown): unknown;
635
1487
  export declare function assertDecidedKind(node: unknown): void;
1488
+ /** The planner's reason vocabulary: every cause a plan can name for work
1489
+ * it left in the engine, under a stable identifier. Most entries are the
1490
+ * whole sentence; the four that quote the caller's own values carry the
1491
+ * stable opening they begin with. */
1492
+ export declare const PLANNER_REASONS: Readonly<Record<string,
1493
+ { readonly text: string } | { readonly prefix: string }>>;
1494
+ /** The vocabulary identifier of one reason sentence, or `null` when no
1495
+ * entry claims it. */
1496
+ export declare function reasonId(reason: string): string | null;
636
1497
  export declare function entityShape(entity: unknown, entityMapping: unknown): unknown;
637
1498
  export declare function entityPathRef(node: unknown, slot: number, shape: unknown): unknown;
638
1499
  export declare function planEntityPredicate(node: unknown, slot: number, shape: unknown): unknown;
@@ -645,12 +1506,80 @@ export declare function parseGraphRow(node: unknown, row: unknown, docField?: st
645
1506
  export declare function selectPlan(collection: string): unknown;
646
1507
  export declare function conjoin(plan: unknown, predicate: unknown): unknown;
647
1508
  export declare function assertNoSqlText(plan: unknown): void;
1509
+ /** Whether an order term reads its value from a mapped column rather
1510
+ * than the document. */
1511
+ export declare function ordersByColumn(ref: unknown): boolean;
1512
+ /** The effective order a set of declared terms executes under: the terms,
1513
+ * then the tie-breaker the emitter appends — the primary key when
1514
+ * `keyColumns` is given (keyset mode), one row identity per binding
1515
+ * otherwise. */
1516
+ export declare function effectiveOrder(
1517
+ terms: unknown, options?: { bindings?: (string | null)[]; keyColumns?: readonly string[] },
1518
+ ): EffectiveOrderTerm[];
1519
+ /** The effective order of one plan, in the same normalized form its
1520
+ * emitter renders; `null` for a statement that orders nothing. */
1521
+ export declare function planOrder(plan: unknown): EffectiveOrderTerm[] | null;
648
1522
  export declare const PLAN_VERSION: number;
649
1523
  export declare function typeOfPath(shape: unknown, segments: unknown): unknown;
650
1524
  export declare function isNumericType(type: unknown): boolean;
651
1525
  export declare function compileSetResidual(document: unknown, limits?: unknown): unknown;
652
1526
  export declare function compileRowResidual(rowReturn: unknown, limits?: unknown): unknown;
1527
+ export declare function compilePackedResidual(document: unknown, limits?: unknown): unknown;
1528
+ /** The one cursor mechanism every engine builds on: a row source pulled
1529
+ * one row per `next()` and released exactly once, or a materialised
1530
+ * source that says so. */
1531
+ export declare function createCursor<T = unknown>(spec: {
1532
+ streaming: 'row' | 'buffered';
1533
+ barrier?: CursorBarrier | null;
1534
+ signal?: AbortSignal;
1535
+ materialize?: () => unknown;
1536
+ open?: () => unknown;
1537
+ items?: (row: unknown) => T[];
1538
+ }): QueryCursor<T>;
653
1539
  export declare function sequenceResult(items: unknown[]): unknown;
1540
+ // ————— model-declared index expressions —————
1541
+
1542
+ /** The three node kinds, closed: `member`, `value`, `call`. */
1543
+ export declare const EXPRESSION_KINDS: readonly string[];
1544
+ /** How deep a declared expression may nest. */
1545
+ export declare const EXPRESSION_DEPTH: number;
1546
+ /** One host declaration for a function a model's index expression may
1547
+ * name. An engine that registers functions needs `apply`; one that
1548
+ * cannot needs the `sql` name of an IMMUTABLE function it already has. */
1549
+ export interface ExpressionFunction {
1550
+ arity: number;
1551
+ /** Required, and never inferred: an index over a function that may
1552
+ * answer differently for one row is an index that lies. */
1553
+ deterministic: true;
1554
+ apply?: (...args: any[]) => unknown;
1555
+ sql?: string;
1556
+ }
1557
+ /** Resolve one expression against the host's declarations, refusing an
1558
+ * unknown, wrong-arity or non-deterministic function with `JD0004`. */
1559
+ export declare function normalizeExpression(
1560
+ node: unknown, docPath: string, declarations: Record<string, ExpressionFunction>,
1561
+ depth?: number,
1562
+ ): unknown;
1563
+ /** The identity two declarations of one expression share. */
1564
+ export declare function canonicalExpression(node: unknown): string;
1565
+ /** Every member path an expression reads, in order. */
1566
+ export declare function expressionMembers(node: unknown, out?: string[]): string[];
1567
+ /** Every function an expression calls, sorted. */
1568
+ export declare function expressionFunctions(node: unknown, out?: Set<string>): string[];
1569
+ /** The SQL an expression compiles to on one dialect. */
1570
+ export declare function expressionSql(
1571
+ node: unknown, dialect: Dialect, context: unknown,
1572
+ ): string;
1573
+ /** A short, stable column stem for one expression. */
1574
+ export declare function expressionStem(node: unknown): string;
1575
+ /** The SQL name a declared function is registered under. */
1576
+ export declare function registeredName(name: string): string;
1577
+ /** Register every function a set of expressions calls, on one connection. */
1578
+ export declare function registerExpressionFunctions(
1579
+ connection: unknown, names: readonly string[],
1580
+ declarations: Record<string, ExpressionFunction>,
1581
+ ): unknown;
1582
+
654
1583
  export declare function deterministicFragment(fragment: unknown): unknown;
655
1584
  export declare function registerFragment(connection: unknown, registered: Set<string>, fragment: unknown): void;
656
1585
  export declare function createQueryEngine(context: unknown): unknown;
@@ -658,6 +1587,9 @@ export declare function createQueryState(bound?: number): unknown;
658
1587
  export declare function createEntityQueryEngine(context: unknown): unknown;
659
1588
  export declare function createLoadEngine(context: unknown, entityName: string): unknown;
660
1589
  export declare const INCLUDE_DEPTH_DEFAULT: number;
1590
+ export declare const INCLUDE_ROWS_DEFAULT: number;
1591
+ export declare const INCLUDE_BYTES_DEFAULT: number;
1592
+ export declare const PAGE_LIMIT_DEFAULT: number;
661
1593
  export declare function normalizeProfile(profile: unknown): unknown;
662
1594
  export declare const SAFE_PROFILE: unknown;
663
1595
  export declare function translateProfilePredicate(predicate: unknown, shape: unknown): unknown;
@@ -680,7 +1612,7 @@ export declare function createLiveRegistry(
680
1612
  bounds: { maxQueries: number; maxMaintained: number }): unknown;
681
1613
  export declare function diffRows(oldRows: readonly unknown[], newRows: readonly unknown[]):
682
1614
  Array<{ op: string; path: string; value?: unknown }>;
683
- export declare const LIVE_DEFAULTS: { maxQueries: number; maxMaintained: number };
1615
+ export declare const LIVE_DEFAULTS: { maxQueries: number; maxMaintained: number; maxBytes: number };
684
1616
  export declare function createSortedWindow(
685
1617
  terms: unknown[], limit: number | null): unknown;
686
1618
  export declare function compareCodepoint(a: string, b: string): number;
@@ -721,49 +1653,133 @@ export declare function identityBatches(identities: unknown[]): unknown;
721
1653
 
722
1654
  // ————— the job queue (JOBS-FORMAT) —————
723
1655
 
1656
+ /** A job's state: `cancelled` is terminal like `done` and `dead` and
1657
+ * is set only by `cancel()` (JOBS-FORMAT §10). */
1658
+ export type JobState = 'pending' | 'leased' | 'done' | 'failed' | 'dead' | 'cancelled';
1659
+
724
1660
  export interface JobRecord {
725
1661
  readonly id: string;
726
1662
  readonly kind: string;
727
1663
  readonly payload: unknown;
728
- readonly state: 'pending' | 'leased' | 'done' | 'failed' | 'dead';
1664
+ readonly state: JobState;
729
1665
  readonly runAt: number;
730
1666
  readonly attempts: number;
731
1667
  readonly maxAttempts: number;
732
1668
  readonly leaseUntil: number | null;
733
1669
  readonly leaseOwner: string | null;
1670
+ /** How many times this job has been claimed. It identifies the
1671
+ * ATTEMPT, which the owner cannot: one worker reuses one owner. */
1672
+ readonly leaseGeneration: number;
734
1673
  readonly lastError: string | null;
735
1674
  readonly result: unknown;
736
1675
  readonly createdAt: number;
737
1676
  readonly updatedAt: number;
738
1677
  }
739
1678
 
1679
+ /**
1680
+ * The capability one claim mints: the right to settle THIS attempt of
1681
+ * this job, for as long as the lease is valid.
1682
+ *
1683
+ * It is a token rather than an owner, because an owner is reused by
1684
+ * every attempt one worker makes and so cannot say which attempt is
1685
+ * speaking. It is immutable: `renew` answers a NEW lease and retires
1686
+ * this one, so a reference kept across a renewal can never quietly
1687
+ * become valid again.
1688
+ *
1689
+ * Only `claim` and `renew` hand one out. `get` does not — a record
1690
+ * anyone can read must not carry the capability to settle it.
1691
+ */
1692
+ export interface JobLease {
1693
+ readonly jobId: string;
1694
+ readonly token: string;
1695
+ readonly generation: number;
1696
+ readonly attempt: number;
1697
+ /** Diagnostics only: never a guard. */
1698
+ readonly owner: string | null;
1699
+ readonly expiresAt: number;
1700
+ }
1701
+
1702
+ /** What `claim` answers: the record, and the lease to settle it with. */
1703
+ export interface ClaimedJob extends JobRecord {
1704
+ readonly lease: JobLease;
1705
+ }
1706
+
740
1707
  export interface JobCounts {
741
1708
  pending: number;
742
1709
  leased: number;
743
1710
  done: number;
744
1711
  failed: number;
745
1712
  dead: number;
1713
+ cancelled: number;
746
1714
  /** Pending/failed totals per kind — how a handler-less kind REPORTS. */
747
1715
  pendingKinds: Record<string, number>;
748
1716
  }
749
1717
 
1718
+ /** What became of one attempt. A LOST settlement is its own outcome:
1719
+ * counting it as a completion is what let a corpse report success over
1720
+ * work another attempt was still doing, and counting it as a failure
1721
+ * would burn a retry the job never spent. */
1722
+ export interface JobOutcome {
1723
+ /** `cancelled`: the attempt was taken from this worker by `cancel()`
1724
+ * with its lease — neither a completion, a failure nor a loss. */
1725
+ readonly outcome: 'completed' | 'failed' | 'lost' | 'cancelled';
1726
+ /** Where the loss was noticed: settling the result, or settling the
1727
+ * failure that came before it. Absent on the other two outcomes. */
1728
+ readonly phase?: 'completion' | 'failure';
1729
+ readonly jobId: string;
1730
+ readonly kind: string;
1731
+ readonly attempt: number;
1732
+ readonly generation: number;
1733
+ /** The refusal code a lost settlement carries (`JD2065`/`JD2066`/
1734
+ * `JD2067`); `null` when the lease was lost some other way. */
1735
+ readonly code?: string | null;
1736
+ readonly reason?: string;
1737
+ }
1738
+
750
1739
  export interface JobWorker {
751
1740
  start(): JobWorker;
752
1741
  /** Stop claiming, signal in-flight handlers, and wait up to `graceMs`
753
1742
  * (JOBS-FORMAT §6): the record says whether every loop drained. */
754
1743
  stop(options?: { graceMs?: number }): Promise<{ drained: boolean; inFlight: number }>;
755
1744
  stats(): { claims: number; completions: number; failures: number;
756
- polls: number; wakes: number; claimErrors: number; inFlight: number };
1745
+ polls: number; wakes: number; claimErrors: number; inFlight: number;
1746
+ /** Leases replaced while a handler was still running. */
1747
+ renewals: number;
1748
+ /** Attempts whose lease was lost mid-flight — never a completion,
1749
+ * never a failure. */
1750
+ lostSettlements: number;
1751
+ /** Attempts cancelled through `cancel()` while their handler ran. */
1752
+ cancellations: number };
1753
+ /** The leases this worker holds right now: one per in-flight attempt,
1754
+ * each the newest that attempt has been given. */
1755
+ leases(): readonly JobLease[];
757
1756
  }
758
1757
 
759
1758
  export interface JobWorkerOptions {
1759
+ /**
1760
+ * `checkpoints` is bound to THIS attempt and follows its current
1761
+ * lease, so a renewal does not strand it. `signal` aborts for either
1762
+ * reason a handler must wind up for: the worker is stopping, or the
1763
+ * job is no longer this attempt's to finish — in which case the
1764
+ * signal's `reason` is the coded refusal that says which.
1765
+ */
760
1766
  handlers: Record<string, (payload: unknown, context: {
761
- job: JobRecord; checkpointsFor: Function; signal: AbortSignal }) => unknown>;
1767
+ job: ClaimedJob;
1768
+ checkpoints: { load(runId: string): unknown;
1769
+ save(runId: string, nodeId: string, value: unknown): unknown;
1770
+ complete(runId: string, result: unknown): unknown };
1771
+ signal: AbortSignal }) => unknown>;
762
1772
  /** A positive integer; the loops claiming concurrently. */
763
1773
  concurrency?: number;
764
1774
  pollInterval?: number;
765
1775
  leaseMs?: number;
766
1776
  owner?: string;
1777
+ /** Renew each attempt's lease while its handler runs (the default).
1778
+ * `false` for a handler that must not outlive its lease. */
1779
+ renew?: boolean;
1780
+ /** Called once per settled attempt, including the lost ones. An
1781
+ * observer that throws never affects the loop. */
1782
+ onOutcome?: (event: JobOutcome) => void;
767
1783
  backoffBase?: number;
768
1784
  backoffCap?: number;
769
1785
  /** How long `stop()` waits for in-flight handlers by default. */
@@ -775,13 +1791,30 @@ export interface JobsApi {
775
1791
  options?: { id?: string; runAt?: number; maxAttempts?: number }): Promise<string>;
776
1792
  get(id: string): Promise<JobRecord | undefined>;
777
1793
  counts(): Promise<JobCounts>;
778
- /** The low-level guarded claim the worker itself uses (§3). */
1794
+ /** The low-level guarded claim the worker itself uses (§3). It mints
1795
+ * the fence: a fresh token and the next generation. */
779
1796
  claim(options: { kinds: string[]; owner: string; leaseMs?: number }):
780
- Promise<JobRecord | undefined>;
781
- complete(id: string, owner: string, result?: unknown): Promise<boolean>;
782
- fail(id: string, owner: string, error: unknown): Promise<boolean>;
783
- /** The per-job flow checkpoint store binding (§7). */
784
- checkpointsFor(job: JobRecord): {
1797
+ Promise<ClaimedJob | undefined>;
1798
+ /**
1799
+ * Replace a lease with a later one (§3). A handler that runs longer
1800
+ * than its lease renews rather than hoping; the lease it is given
1801
+ * back supersedes the one it passed in, which then settles nothing.
1802
+ *
1803
+ * Refuses `JD2065` (the job is not leased — unknown, or already
1804
+ * settled), `JD2066` (the lease was superseded) or `JD2067` (it
1805
+ * expired), never a silent `false`.
1806
+ */
1807
+ renew(lease: JobLease, options?: { leaseMs?: number }): Promise<JobLease>;
1808
+ /** Settle the attempt this lease holds. `true`, or one of the three
1809
+ * coded refusals above — a caller that cannot tell "already done"
1810
+ * from "you are stale" guesses, and guesses wrong. */
1811
+ complete(lease: JobLease, result?: unknown): Promise<boolean>;
1812
+ fail(lease: JobLease, error: unknown): Promise<boolean>;
1813
+ /** The per-attempt flow checkpoint store binding (§7). Rows are
1814
+ * stamped with the attempt's generation: `load` reads what was
1815
+ * written up to it, and a settlement prunes no further, so a stale
1816
+ * attempt cannot erase a live one's work. */
1817
+ checkpointsFor(job: ClaimedJob): {
785
1818
  load(runId: string): unknown;
786
1819
  save(runId: string, nodeId: string, value: unknown): unknown;
787
1820
  complete(runId: string, result: unknown): unknown;
@@ -789,6 +1822,45 @@ export interface JobsApi {
789
1822
  createWorker(options: JobWorkerOptions): JobWorker;
790
1823
  }
791
1824
 
1825
+ export interface JobPageOptions {
1826
+ /** A closed state; every job when absent. */
1827
+ state?: JobState;
1828
+ kind?: string;
1829
+ /** The id to continue past (keyset by id). */
1830
+ after?: string;
1831
+ /** At most this many items (default 100). */
1832
+ limit?: number;
1833
+ /** Row-boundary cancellation, as every cursor (`JD2072` / `JD2075`). */
1834
+ signal?: AbortSignal;
1835
+ deadline?: number;
1836
+ }
1837
+
1838
+ /**
1839
+ * Job administration (JOBS-FORMAT §10) — root-only mechanism, never a
1840
+ * schedule: WHEN to sweep or cancel is the host's call.
1841
+ */
1842
+ export interface JobsAdminApi {
1843
+ /** A keyset cursor over the queue by id, admitted per pull under the
1844
+ * store gate; each item is the record `get` answers. */
1845
+ page(options?: JobPageOptions): QueryCursor<JobRecord>;
1846
+ /** Cancel a queued job by its identity, or a claimed one through its
1847
+ * CURRENT lease (the fence's rule): the row settles as `cancelled`,
1848
+ * a local attempt's handler signal aborts, and the call resolves once
1849
+ * that attempt wound up. `true` when this call cancelled it, `false`
1850
+ * when it already was; `JD2065`/`JD2066`/`JD2067`/`JD2068` as a
1851
+ * settling call refuses. */
1852
+ cancel(id: string, options?: { lease?: JobLease; signal?: AbortSignal; deadline?: number }): Promise<boolean>;
1853
+ /** Return a failed, dead, cancelled or lease-expired job to the queue
1854
+ * with its attempt history intact; `false` when it already was
1855
+ * pending; a live lease refuses `JD2068`, unknown or done `JD2065`. */
1856
+ requeue(id: string, options?: { signal?: AbortSignal; deadline?: number }): Promise<boolean>;
1857
+ /** Delete settled jobs (done, dead, cancelled) last changed before
1858
+ * `settledBefore`, oldest first, at most `limit`, with their
1859
+ * checkpoints; a second identical sweep answers `{ removed: 0 }`. */
1860
+ sweep(options: { settledBefore: number; limit?: number; signal?: AbortSignal; deadline?: number }):
1861
+ Promise<{ removed: number }>;
1862
+ }
1863
+
792
1864
  export interface JobsOptions {
793
1865
  maxAttempts?: number;
794
1866
  leaseMs?: number;
@@ -809,14 +1881,21 @@ export declare function createDagJobRunner(store: Store, options: {
809
1881
  pollInterval?: number;
810
1882
  leaseMs?: number;
811
1883
  owner?: string;
1884
+ renew?: boolean;
1885
+ onOutcome?: (event: JobOutcome) => void;
812
1886
  backoffBase?: number;
813
1887
  backoffCap?: number;
814
1888
  stopGraceMs?: number;
815
1889
  }): JobWorker;
816
1890
 
1891
+ /** The checkpoint row a run's identity lives in — the workflow revision
1892
+ * and a hash of the input a resume must agree with (`JD2069` when it
1893
+ * does not). Pruned with the run it belongs to. */
1894
+ export declare const RUN_IDENTITY_NODE: string;
1895
+
817
1896
  export declare function createJobEngine(options: {
818
1897
  connection: unknown; now?: () => number; random?: () => number;
819
- defaults?: JobsOptions }): unknown;
1898
+ defaults?: JobsOptions; runtime?: Partial<Runtime> }): unknown;
820
1899
  export declare const JOBS_TABLE: string;
821
1900
  export declare const JOB_CHECKPOINTS_TABLE: string;
822
1901
  export declare const JOB_DEFAULTS: Readonly<{
@@ -826,3 +1905,55 @@ export declare const JOB_DEFAULTS: Readonly<{
826
1905
  export declare function describeValue(value: unknown): string;
827
1906
  /** A job result as the queue stores it: JSON text, or the reason it could not be. */
828
1907
  export declare function serializeResult(value: unknown): unknown;
1908
+
1909
+ /** Transport-neutral, net logical operations in a single transaction. */
1910
+ export interface ReplicationOperation {
1911
+ table: string;
1912
+ key: string;
1913
+ before: Record<string, unknown> | null;
1914
+ after: Record<string, unknown> | null;
1915
+ }
1916
+ export type ReplicationFrontier = Record<string, number>;
1917
+ export interface ReplicationEnvelope {
1918
+ $replication: '0.1'; replica: string; seq: number; model: string;
1919
+ frontier: ReplicationFrontier; operations: ReplicationOperation[];
1920
+ }
1921
+ export interface ReplicationConflict {
1922
+ envelope: string; table: string; key: string;
1923
+ base: Record<string, unknown> | null;
1924
+ local: { value: Record<string, unknown> | null; frontier: ReplicationFrontier };
1925
+ remote: { value: Record<string, unknown> | null; replica: string; seq: number; frontier: ReplicationFrontier };
1926
+ resolver: string | null;
1927
+ resolution: { action: 'local' | 'remote' | 'merged'; value: Record<string, unknown> | null } | null;
1928
+ }
1929
+ export interface ReplicationOptions {
1930
+ replica: string; retention?: number; maxOperations?: number; maxBytes?: number;
1931
+ resolver?: { id: string; resolve(conflict: Readonly<ReplicationConflict>):
1932
+ { action: 'local' | 'remote' } | { action: 'merged'; value: Record<string, unknown> | null } };
1933
+ }
1934
+ export interface ReplicationRequest { signal?: AbortSignal; deadline?: number }
1935
+ export interface Replication {
1936
+ snapshot(request?: ReplicationRequest): Promise<ReplicationSnapshot>;
1937
+ reset(snapshot: ReplicationSnapshot, request?: ReplicationRequest): Promise<{ status: 'reset'; frontier: ReplicationFrontier }>;
1938
+ frontier(): Promise<ReplicationFrontier>;
1939
+ apply(envelope: ReplicationEnvelope, request?: ReplicationRequest): Promise<{
1940
+ status: 'applied' | 'duplicate' | 'conflict'; frontier: ReplicationFrontier; conflicts: ReplicationConflict[];
1941
+ }>;
1942
+ page(request?: ReplicationRequest & { after?: number; limit?: number; maxBytes?: number }): Promise<{
1943
+ items: ReplicationEnvelope[]; earliestAvailable: number | null; highWatermark: number;
1944
+ next?: number; bytes?: number; hasMore: boolean; resetRequired: boolean;
1945
+ }>;
1946
+ conflicts(request?: ReplicationRequest & { limit?: number; maxBytes?: number }): Promise<ReplicationConflict[]>;
1947
+ }
1948
+ export declare const REPLICATION_VERSION: '0.1';
1949
+ export declare const REPLICATION_DEFAULTS: Readonly<{ retention: number; maxOperations: number; maxBytes: number }>;
1950
+ export declare function normalizeFrontier(value: unknown): ReplicationFrontier;
1951
+ export declare function replicationIdentity(replica: string, seq: number): string;
1952
+ export declare function normalizeReplication(document: unknown): ReplicationEnvelope;
1953
+ export declare function encodeReplication(document: unknown): string;
1954
+ export interface ReplicationSnapshot {
1955
+ $replicationSnapshot: '0.1'; model: string; frontier: ReplicationFrontier;
1956
+ rows: { table: string; key: string; value: Record<string, unknown> | null; frontier: ReplicationFrontier }[];
1957
+ receipts: ReplicationEnvelope[];
1958
+ }
1959
+ export declare function normalizeReplicationSnapshot(document: unknown): ReplicationSnapshot;