@jarenjs/db 0.49.2 → 0.66.1

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 (110) hide show
  1. package/ARCHITECTURE.md +420 -71
  2. package/README.md +711 -79
  3. package/docs/HOSTS.md +269 -0
  4. package/docs/JOBS-FORMAT.md +309 -45
  5. package/docs/LIVE-FORMAT.md +156 -19
  6. package/docs/MIGRATION-FORMAT.md +247 -40
  7. package/docs/MODEL-FORMAT.md +968 -86
  8. package/package.json +21 -8
  9. package/schemas/jaren-migration.draft-07.schema.json +73 -0
  10. package/schemas/jaren-migration.schema.json +73 -0
  11. package/schemas/jaren-model.draft-07.schema.json +224 -162
  12. package/schemas/jaren-model.schema.json +224 -162
  13. package/src/algebra.js +227 -9
  14. package/src/backup.js +161 -0
  15. package/src/cancellation.js +48 -0
  16. package/src/capture.js +255 -44
  17. package/src/cli.js +337 -50
  18. package/src/cursor.js +411 -0
  19. package/src/dag-job.js +154 -21
  20. package/src/ddl.js +125 -11
  21. package/src/dialect.js +267 -112
  22. package/src/dialects/expression-read.js +158 -0
  23. package/src/dialects/postgres.js +618 -0
  24. package/src/dialects/rtree-ddl.js +129 -0
  25. package/src/dialects/sqlite.js +245 -12
  26. package/src/document-files.js +311 -0
  27. package/src/document-steps.js +422 -0
  28. package/src/documents.js +335 -0
  29. package/src/driver.js +503 -69
  30. package/src/drivers/bun.js +37 -1
  31. package/src/drivers/indexeddb-snapshot.js +149 -0
  32. package/src/drivers/node-pool.js +11 -0
  33. package/src/drivers/node-worker-endpoint.js +105 -0
  34. package/src/drivers/node-worker.js +204 -0
  35. package/src/drivers/node.js +41 -7
  36. package/src/drivers/postgres.js +331 -0
  37. package/src/drivers/wasm-oo1.js +97 -0
  38. package/src/drivers/wasm-session.js +67 -0
  39. package/src/drivers/wasm.js +18 -83
  40. package/src/drivers/worker-pool.js +183 -0
  41. package/src/drivers/worker-protocol.js +79 -0
  42. package/src/drivers/worker-queue.js +60 -0
  43. package/src/emit-model.js +14 -0
  44. package/src/emit.js +349 -51
  45. package/src/entity.js +102 -59
  46. package/src/errors.js +430 -2
  47. package/src/expression.js +284 -0
  48. package/src/graph.js +64 -8
  49. package/src/index.js +48 -19
  50. package/src/introspect.js +583 -0
  51. package/src/jobs.js +870 -99
  52. package/src/json-bytes.js +58 -0
  53. package/src/live-time.js +12 -3
  54. package/src/live.js +11 -1
  55. package/src/maintenance.js +175 -0
  56. package/src/migrate.js +606 -333
  57. package/src/model.js +241 -8
  58. package/src/plan.js +1238 -160
  59. package/src/pragmas.js +314 -0
  60. package/src/profile.js +151 -3
  61. package/src/query.js +1748 -312
  62. package/src/residual.js +17 -0
  63. package/src/series.js +12 -4
  64. package/src/store.js +1672 -276
  65. package/src/tracker.js +367 -68
  66. package/src/udf.js +88 -7
  67. package/types/index.d.ts +1246 -32
  68. package/types/node-pool.d.ts +28 -0
  69. package/types/node-worker.d.ts +54 -0
  70. package/types/node.d.ts +72 -3
  71. package/types/postgres.d.ts +46 -0
  72. package/types/typed.d.ts +81 -3
  73. package/types/wasm.d.ts +21 -0
  74. package/dist/types/algebra.d.ts +0 -230
  75. package/dist/types/app.d.ts +0 -49
  76. package/dist/types/capture.d.ts +0 -85
  77. package/dist/types/cli.d.ts +0 -2
  78. package/dist/types/dag-job.d.ts +0 -40
  79. package/dist/types/ddl.d.ts +0 -229
  80. package/dist/types/derive.d.ts +0 -250
  81. package/dist/types/dialect.d.ts +0 -154
  82. package/dist/types/dialects/sqlite.d.ts +0 -9
  83. package/dist/types/driver.d.ts +0 -110
  84. package/dist/types/drivers/bun.d.ts +0 -47
  85. package/dist/types/drivers/node.d.ts +0 -37
  86. package/dist/types/drivers/wasm.d.ts +0 -65
  87. package/dist/types/emit-model.d.ts +0 -44
  88. package/dist/types/emit.d.ts +0 -75
  89. package/dist/types/entity.d.ts +0 -23
  90. package/dist/types/errors.d.ts +0 -170
  91. package/dist/types/graph.d.ts +0 -28
  92. package/dist/types/index.d.ts +0 -37
  93. package/dist/types/jobs.d.ts +0 -140
  94. package/dist/types/knn.d.ts +0 -69
  95. package/dist/types/live-time.d.ts +0 -141
  96. package/dist/types/live.d.ts +0 -64
  97. package/dist/types/migrate.d.ts +0 -170
  98. package/dist/types/model.d.ts +0 -36
  99. package/dist/types/patch-sql.d.ts +0 -37
  100. package/dist/types/plan.d.ts +0 -142
  101. package/dist/types/profile.d.ts +0 -80
  102. package/dist/types/query.d.ts +0 -112
  103. package/dist/types/residual.d.ts +0 -64
  104. package/dist/types/series.d.ts +0 -227
  105. package/dist/types/store.d.ts +0 -60
  106. package/dist/types/tracker.d.ts +0 -43
  107. package/dist/types/typed.d.ts +0 -15
  108. package/dist/types/types.d.ts +0 -26
  109. package/dist/types/udf.d.ts +0 -75
  110. package/dist/types/window.d.ts +0 -52
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,11 +176,55 @@ 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;
85
199
  }
86
200
 
87
- export interface LoadInclude extends LoadSpec {
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;
213
+ }
214
+
215
+ /** An include's clauses: the root's without `after` — a keyset cursor
216
+ * paginates the root alone; an include windows with `skip`/`take`. */
217
+ export interface LoadInclude extends Omit<LoadSpec, 'after'> {
88
218
  /** Project the related-row COUNT instead of the rows. */
89
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;
90
228
  }
91
229
 
92
230
  export interface LoadSpec {
@@ -95,16 +233,103 @@ export interface LoadSpec {
95
233
  orderBy?: unknown;
96
234
  take?: number;
97
235
  skip?: number;
98
- /** The keyset cursor: needs a single unique-column ordering. */
99
- 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;
100
241
  maxDepth?: number;
101
242
  include?: Readonly<Record<string, boolean | LoadInclude>>;
102
243
  }
103
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
+
104
312
  export interface LoadExplanation {
105
313
  sql: string;
106
314
  pagination: 'keyset' | 'offset' | 'none';
107
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;
108
333
  }
109
334
 
110
335
  /** What `saveChanges()` returns: data, not a boolean (§11.6). */
@@ -128,17 +353,165 @@ export interface StoreStats {
128
353
  tracked: number;
129
354
  pendingInserts: number;
130
355
  pendingDeletes: number;
356
+ /** Pending `link`/`unlink` records: one per entity, own key and member (§11.7). */
357
+ pendingMemberships: number;
131
358
  } | null;
132
359
  liveQueries: number;
133
360
  }
134
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
+
135
484
  export interface StoreCapabilities {
136
485
  readonly version: string;
137
486
  readonly readOnly: boolean;
138
487
  readonly validated: boolean;
139
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. */
140
492
  readonly busyTimeoutMs: number | null;
493
+ /** `pragmas.journalMode` under its long-published name. */
141
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;
142
515
  readonly capture: 'session' | 'journal' | 'none';
143
516
  readonly captureLog: boolean;
144
517
  readonly live: boolean;
@@ -167,7 +540,7 @@ export interface Collection<T = unknown> {
167
540
  * a synchronous driver stays synchronous. */
168
541
  execute<R = unknown>(document: unknown, options?: ExecuteOptions): ValueOrPromise<SequenceResult<R>>;
169
542
  /** The same document as an item cursor — one item per pull. */
170
- query<R = unknown>(document: unknown, options?: ExecuteOptions): QueryCursor<R>;
543
+ query<R = unknown>(document: unknown, options?: CursorOptions): QueryCursor<R>;
171
544
  explain(document: unknown, options?: ExecuteOptions): Promise<unknown>;
172
545
  /** Register a live query (LIVE-FORMAT §7); requires capture. */
173
546
  live(document: unknown, options?: LiveOptions): Promise<LiveQuery>;
@@ -186,17 +559,56 @@ export interface SyncCollection<T = unknown> {
186
559
 
187
560
  // ————— entities (phase B) —————
188
561
 
562
+ /** One row of an entity's relation table (MODEL-FORMAT §10.1): the
563
+ * declared relation as plain data a query producer can lower a hop
564
+ * from — never a document dialect. For a foreign-key relation `via`
565
+ * names the key property, `fkEntity` the entity holding it, `fkTargets`
566
+ * the entity it references and `targetKey` the key property it
567
+ * references there (the column a hop's equality compares `via` with);
568
+ * `kind` says which side holds the key (`oneToOne`: the declaring
569
+ * entity; `oneToMany`: the target). A many-to-many carries its
570
+ * `joinTable` and the target's `targetKey`. */
571
+ export interface RelationEntry {
572
+ readonly to: string;
573
+ readonly kind: 'oneToOne' | 'oneToMany' | 'manyToMany';
574
+ readonly via?: string;
575
+ readonly fkEntity?: string;
576
+ readonly fkTargets?: string;
577
+ readonly joinTable?: string;
578
+ readonly targetKey: string;
579
+ }
580
+
581
+ /** An entity's relation table: one entry per declared relation member. */
582
+ export type RelationTable = Readonly<Record<string, RelationEntry>>;
583
+
584
+ /** The identity every entity set of one store shares — two sets with one
585
+ * `scope` may be joined in one document — carrying the relation tables
586
+ * of every root, keyed by entity name, so a hop can chain into another
587
+ * root of the same scope. */
588
+ export interface EntityScope {
589
+ readonly relations: Readonly<Record<string, RelationTable>>;
590
+ }
591
+
189
592
  export interface UntrackedReads<T = unknown> {
190
593
  get(key: EntityKeyArg): Promise<T | undefined>;
191
594
  load(spec?: LoadSpec): Promise<T[]>;
192
595
  }
193
596
 
194
597
  export interface EntitySet<T = unknown, I = unknown> {
598
+ /** The provider phantom: a chain over this set infers its item type. */
599
+ readonly __item?: T;
195
600
  create(doc: I): Promise<Readonly<T>>;
196
601
  get(key: EntityKeyArg): Promise<Readonly<T> | undefined>;
197
602
  update(key: EntityKeyArg, changes: Partial<T>): Promise<Readonly<T>>;
198
603
  delete(key: EntityKeyArg): Promise<boolean>;
199
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>>>;
200
612
  explainLoad(spec?: LoadSpec): LoadExplanation;
201
613
  /** Track a pending insert (local, synchronous — no round trip). */
202
614
  add(doc: I): Readonly<T>;
@@ -206,7 +618,33 @@ export interface EntitySet<T = unknown, I = unknown> {
206
618
  remove(key: EntityKeyArg | T): void;
207
619
  /** Drop tracking without scheduling anything — conflict recovery. */
208
620
  discard(key: EntityKeyArg | T): void;
621
+ /** Attach / detach one many-to-many membership through the unit of
622
+ * work (§11.7): local bookkeeping, written by `saveChanges()` as join
623
+ * rows against the join table as it stands then — idempotent. `own`
624
+ * and `target` are each a key or a document carrying the key. */
625
+ link(own: EntityKeyArg | T, member: string, target: EntityKeyArg | object): void;
626
+ unlink(own: EntityKeyArg | T, member: string, target: EntityKeyArg | object): void;
209
627
  asNoTracking(): UntrackedReads<T>;
628
+ /** The provider contract over this entity's root (MODEL-FORMAT §10.1):
629
+ * the document is over the multi-entity root and arrives whole; the
630
+ * answer is the engine's result shape, value-or-promise (D2). */
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>;
638
+ explain(document: unknown, options?: ExecuteOptions): Promise<unknown>;
639
+ /** The root expression this set's rows are bound through (`$.<Name>[*]`). */
640
+ readonly root: string;
641
+ /** The identity every entity set of one store shares: two sets with one
642
+ * `scope` may be joined in one document; it carries every root's
643
+ * relation table. */
644
+ readonly scope: EntityScope;
645
+ /** This entity's relation table (MODEL-FORMAT §10.1) — what a query
646
+ * producer lowers a relation hop from. */
647
+ readonly relations: RelationTable;
210
648
  }
211
649
 
212
650
  export interface SyncUntrackedReads<T = unknown> {
@@ -215,17 +653,39 @@ export interface SyncUntrackedReads<T = unknown> {
215
653
  }
216
654
 
217
655
  export interface SyncEntitySet<T = unknown, I = unknown> {
656
+ /** The provider phantom: a chain over this set infers its item type. */
657
+ readonly __item?: T;
218
658
  create(doc: I): Readonly<T>;
219
659
  get(key: EntityKeyArg): Readonly<T> | undefined;
220
660
  update(key: EntityKeyArg, changes: Partial<T>): Readonly<T>;
221
661
  delete(key: EntityKeyArg): boolean;
222
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>;
223
666
  explainLoad(spec?: LoadSpec): LoadExplanation;
224
667
  add(doc: I): Readonly<T>;
225
668
  put(next: T): Readonly<T>;
226
669
  remove(key: EntityKeyArg | T): void;
227
670
  discard(key: EntityKeyArg | T): void;
671
+ link(own: EntityKeyArg | T, member: string, target: EntityKeyArg | object): void;
672
+ unlink(own: EntityKeyArg | T, member: string, target: EntityKeyArg | object): void;
228
673
  asNoTracking(): SyncUntrackedReads<T>;
674
+ /** The provider contract over this entity's root, answering values. */
675
+ execute<R = unknown>(document: unknown, options?: ExecuteOptions): SequenceResult<R>;
676
+ explain(document: unknown, options?: ExecuteOptions): unknown;
677
+ readonly root: string;
678
+ readonly scope: EntityScope;
679
+ readonly relations: RelationTable;
680
+ }
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;
229
689
  }
230
690
 
231
691
  // ————— the store —————
@@ -235,8 +695,16 @@ export interface SyncStore {
235
695
  * the consumer's words; the handle's writes take it and reads answer it. */
236
696
  collection<T = unknown>(name: string): SyncCollection<T>;
237
697
  entity(name: string): SyncEntitySet;
238
- transaction<R>(fn: (store: Store) => R): R;
698
+ transaction<R>(fn: (store: TransactionStore) => R): R;
239
699
  execute?<R = unknown>(document: unknown, options?: ExecuteOptions): SequenceResult<R>;
700
+ explain?(document: unknown, options?: ExecuteOptions): unknown;
701
+ /** The entity roots this store-level provider serves (present with
702
+ * entities): it has no single root of its own, so a chain over it is
703
+ * refused by name — chain over `entity(name)` instead. */
704
+ readonly roots?: readonly string[];
705
+ /** The relation tables of every entity, keyed by entity name (present
706
+ * with entities; MODEL-FORMAT §10.1). */
707
+ readonly relations?: Readonly<Record<string, RelationTable>>;
240
708
  saveChanges?(): SaveReport;
241
709
  }
242
710
 
@@ -253,15 +721,65 @@ export interface Store {
253
721
  * in the engine's result shape. */
254
722
  execute?<R = unknown>(document: unknown, options?: ExecuteOptions): ValueOrPromise<SequenceResult<R>>;
255
723
  explain?(document: unknown, options?: ExecuteOptions): Promise<unknown>;
724
+ /** The entity roots this store-level provider serves (present with
725
+ * entities): it has no single root of its own, so a chain over it is
726
+ * refused by name — chain over `entity(name)` instead. */
727
+ readonly roots?: readonly string[];
728
+ /** The relation tables of every entity, keyed by entity name (present
729
+ * with entities; MODEL-FORMAT §10.1). */
730
+ readonly relations?: Readonly<Record<string, RelationTable>>;
256
731
  /** The unit of work (§11); present only with entities. */
257
732
  saveChanges?(): Promise<SaveReport>;
258
- transaction<R>(fn: (store: Store) => R | Promise<R>): Promise<Awaited<R>>;
733
+ /**
734
+ * A top-level transaction. The callback receives a
735
+ * {@link TransactionStore} whose handles are INSIDE it; this store's
736
+ * own handles are an unrelated caller and wait for the commit.
737
+ *
738
+ * `signal` abandons the call while it is still QUEUED — the callback
739
+ * then never runs and no statement is issued (`JD2064`). A transaction
740
+ * that has already taken the connection runs to its own end.
741
+ *
742
+ * `unitOfWork: 'own'` gives the callback a tracker of its own, so two
743
+ * concurrent handlers hold two records for one entity key and neither
744
+ * sees the other's pending state; `'shared'` (the default) writes
745
+ * through the store's, which is what lets a caller `add()` a document
746
+ * outside the transaction and save it inside. Both behave identically
747
+ * with and without capture.
748
+ */
749
+ transaction<R>(fn: (store: TransactionStore) => R | Promise<R>,
750
+ options?: TransactionScopeOptions): Promise<Awaited<R>>;
259
751
  /** Register a change observer; requires capture. Returns unsubscribe. */
260
752
  observe(fn: (record: ChangeRecord) => void): () => void;
261
- /** Read the persisted log forward (JD2051 without capture.log). */
753
+ /** Read the persisted log forward from `after` (`JD2051` without
754
+ * `capture.log`) — EVERY surviving record in one array, UNBOUNDED, with
755
+ * no watermark: a reconnecting consumer whose cursor fell below the
756
+ * retention floor receives the surviving suffix and cannot tell it
757
+ * from the whole. Unsafe for a reconnecting consumer; `changes.page()`
758
+ * is the supported path (LIVE-FORMAT §5). */
262
759
  changesSince?(after: number): Promise<ChangeRecord[]>;
760
+ /** The bounded change reader: the log's watermarks and pages that
761
+ * never exceed their bounds and report a retention gap explicitly;
762
+ * present exactly when the log is enabled. */
763
+ readonly changes?: ChangesReader;
263
764
  /** PRAGMA data_version — the coarse cross-connection signal. */
264
765
  dataVersion(): Promise<number>;
766
+ /** `PRAGMA wal_checkpoint(<mode>)` under the store gate (MODEL-FORMAT
767
+ * §4). Refused `JD2077` on a read-only store or a binding that does
768
+ * not declare it; a driver failure is `JD2078`. */
769
+ checkpoint(options?: CheckpointOptions): Promise<CheckpointResult>;
770
+ /** `PRAGMA integrity_check` under the store gate; corruption is the
771
+ * RESULT (`ok: false`), never a throw. */
772
+ integrityCheck(options?: IntegrityCheckOptions): Promise<IntegrityCheckResult>;
773
+ /** `PRAGMA foreign_key_check` under the store gate. */
774
+ foreignKeyCheck(options?: MaintenanceCallOptions): Promise<ForeignKeyCheckResult>;
775
+ /** `PRAGMA optimize` under the store gate; refused `JD2077` on a
776
+ * read-only store. */
777
+ optimize(options?: MaintenanceCallOptions): Promise<OptimizeResult>;
778
+ /** An online backup published whole or not at all (MODEL-FORMAT §4):
779
+ * copied to a temporary sibling, renamed onto `targetPath` only at
780
+ * verified completion; a cancelled (`JD2079`) or failed (`JD2078`)
781
+ * copy leaves neither file. Writers proceed during the copy. */
782
+ backupTo(targetPath: string, options?: BackupOptions): Promise<BackupResult>;
265
783
  /** Register a live query over an entity-root document (re-run
266
784
  * strategy in this version); present only with entities. */
267
785
  live?(document: unknown, options?: LiveOptions): Promise<LiveQuery>;
@@ -269,12 +787,148 @@ export interface Store {
269
787
  * `graceMs` to wind up; the connection closes whether or not they
270
788
  * did, and a handler still in flight is reported as JD2062. */
271
789
  close(options?: { graceMs?: number }): Promise<void>;
272
- /** The queue surface; present when opened with `jobs` (JOBS-FORMAT). */
273
- readonly jobs?: JobsApi;
790
+ /** The queue surface; present when opened with `jobs` (JOBS-FORMAT).
791
+ * Root calls here take the store gate — an unrelated enqueue, claim,
792
+ * checkpoint or settlement never joins an open application
793
+ * transaction's fate. The transactional-outbox spelling is the
794
+ * `tx.jobs` a transaction callback receives, which runs as the exact
795
+ * scope and co-commits with the domain transaction. */
796
+ readonly jobs?: JobsApi & JobsAdminApi;
274
797
  /** Present exactly when the driver is synchronous — never stubs. */
275
798
  readonly sync?: SyncStore;
276
799
  }
277
800
 
801
+ /** The options a top-level transaction takes. An unknown `unitOfWork`
802
+ * value is a compile error here and runtime API misuse there. */
803
+ export interface TransactionScopeOptions {
804
+ /** Abandons the call while it is still QUEUED (`JD2064`); a
805
+ * transaction that has taken the connection runs to its own end. */
806
+ signal?: AbortSignal;
807
+ /** `'own'` gives the callback an independent tracker; `'shared'`
808
+ * (the default) writes through the store's. */
809
+ unitOfWork?: 'own' | 'shared';
810
+ /** `'immediate'` takes the write lock up front (`BEGIN IMMEDIATE`), so
811
+ * a body that reads before it writes never meets the read→write
812
+ * upgrade `SQLITE_BUSY` the busy handler cannot retry — what a claim
813
+ * needs under concurrent writers; `'deferred'` (the default) is the
814
+ * savepoint as always. A nested `tx.transaction()` is a savepoint
815
+ * whichever mode the root chose; the synchronous twin has no mode. */
816
+ mode?: 'deferred' | 'immediate';
817
+ }
818
+
819
+ /**
820
+ * The named-savepoint group a live transaction view carries
821
+ * (MODEL-FORMAT §5.2): checkpoint-and-continue without a sentinel
822
+ * exception. The label is a map key and diagnostic for that exact
823
+ * transaction — never SQL; the driver generates the identifier. A
824
+ * blank, duplicate or unknown label is `JD2071`; a stale or
825
+ * cross-scope view is `JD2070` first. `rollbackTo` keeps the target
826
+ * active (repeated rollback is defined) and invalidates every later
827
+ * checkpoint; `release` removes the target and every later checkpoint,
828
+ * keeping their rows — the engine's own semantics, exactly.
829
+ */
830
+ export interface SavepointController {
831
+ create(label: string): Promise<void>;
832
+ rollbackTo(label: string): Promise<void>;
833
+ release(label: string): Promise<void>;
834
+ }
835
+
836
+ /** The synchronous twin of {@link SavepointController}, answering
837
+ * values (present under `tx.sync` on a synchronous driver). */
838
+ export interface SyncSavepointController {
839
+ create(label: string): void;
840
+ rollbackTo(label: string): void;
841
+ release(label: string): void;
842
+ }
843
+
844
+ /** The synchronous surface a transaction view carries: the store's,
845
+ * plus the transaction-only savepoint group. */
846
+ export interface TransactionSyncStore extends SyncStore {
847
+ readonly savepoints: SyncSavepointController;
848
+ }
849
+
850
+ /**
851
+ * The store a transaction callback receives: the same surface, with
852
+ * every handle bound to THIS transaction's exact scope.
853
+ *
854
+ * `tx.collection(...)`, `tx.entity(...)`, `tx.sync`, `tx.jobs` and
855
+ * `tx.saveChanges()` run as the transaction's owner, and
856
+ * `tx.transaction(...)` nests through its savepoint. The outer store's
857
+ * handles are, by construction, an unrelated caller: they wait for the
858
+ * commit, and one awaited from inside the callback is a self-wait that
859
+ * `JD0012` names rather than a hang.
860
+ *
861
+ * The view lives exactly as long as its own scope: any stateful member
862
+ * used after the transaction settled, or while an async inner savepoint
863
+ * is current, refuses `JD2070` before touching tracker state or the
864
+ * database. There is deliberately no `close` — a transaction view does
865
+ * not own the store lifetime; the root store remains the only owner of
866
+ * the connection.
867
+ */
868
+ export interface TransactionStore extends Omit<Store,
869
+ 'close' | 'transaction' | 'sync' | 'checkpoint' | 'integrityCheck' | 'foreignKeyCheck' | 'optimize' | 'backupTo'
870
+ | 'jobs'> {
871
+ /** The transactional outbox (JOBS-FORMAT §3): no administration here —
872
+ * an admin operation is a root call. */
873
+ readonly jobs?: JobsApi;
874
+ transaction<R>(fn: (store: TransactionStore) => R | Promise<R>): Promise<Awaited<R>>;
875
+ /** Named partial rollback over the transaction's one savepoint stack
876
+ * (MODEL-FORMAT §5.2). Root stores, clients, workers and checkpoint
877
+ * stores expose none of it. */
878
+ readonly savepoints: SavepointController;
879
+ /** Present exactly when the driver is synchronous, as on the store. */
880
+ readonly sync?: TransactionSyncStore;
881
+ }
882
+
883
+ /** The log's two watermarks (LIVE-FORMAT §5): the earliest surviving
884
+ * sequence (`null` when nothing survives) and the highest sequence the
885
+ * FILE ever allocated — durable across an emptied log, a reopen and a
886
+ * second store over the same file, never a process counter. */
887
+ export interface ChangeBounds {
888
+ readonly earliestAvailable: number | null;
889
+ readonly highWatermark: number;
890
+ }
891
+
892
+ /** A change page's options: `after` is the last sequence seen and is
893
+ * required — there is no legitimate "give me everything" for a change
894
+ * log; `limit` records at most (default `PAGE_LIMIT_DEFAULT`),
895
+ * `maxBytes` serialised patch bytes at most (none unless given),
896
+ * `signal` honoured at a record boundary (`JD2072`). */
897
+ export interface ChangePageOptions {
898
+ after: number;
899
+ limit?: number;
900
+ maxBytes?: number | null;
901
+ signal?: AbortSignal;
902
+ /** An epoch-millisecond deadline, read against the store's clock at every record boundary (`JD2075`). */
903
+ deadline?: number;
904
+ }
905
+
906
+ /**
907
+ * One page of the log. `resetRequired: true` means the record after
908
+ * `after` no longer survives: `items` is EMPTY and `next` absent — a
909
+ * total refusal, never a partial suffix — and the consumer re-seeds
910
+ * from a snapshot and resumes at `highWatermark`. Otherwise `next` is
911
+ * the sequence to continue from (`after` itself when nothing was
912
+ * delivered), `hasMore` says whether records remain above it, and the
913
+ * watermarks are the log's as read after the page.
914
+ */
915
+ export interface ChangePage {
916
+ readonly items: ChangeRecord[];
917
+ readonly next?: number;
918
+ readonly earliestAvailable: number | null;
919
+ readonly highWatermark: number;
920
+ readonly hasMore: boolean;
921
+ readonly resetRequired: boolean;
922
+ }
923
+
924
+ /** The bounded change reader (LIVE-FORMAT §5). A record larger than
925
+ * `maxBytes` is `JD2074` without advancing `next` — the same rule, the
926
+ * same implementation, as an entity page. */
927
+ export interface ChangesReader {
928
+ bounds(): Promise<ChangeBounds>;
929
+ page(options: ChangePageOptions): Promise<ChangePage>;
930
+ }
931
+
278
932
  /** One committed transaction's change record (LIVE-FORMAT §§1–5). */
279
933
  export interface ChangeRecord {
280
934
  /** Monotonic; continues across reopens when the log is enabled. */
@@ -393,6 +1047,14 @@ export interface LiveBounds {
393
1047
  export interface OpenStoreOptions {
394
1048
  driver: Driver;
395
1049
  path?: string;
1050
+ /**
1051
+ * What a store-level call does while another caller's transaction owns
1052
+ * the connection. `'wait'` (the default) queues behind it under
1053
+ * `queueTimeout` and then refuses `JD0012`; `'strict'` refuses at once,
1054
+ * for a host that would rather see the contention than pay for it.
1055
+ * Either way the call never joins the transaction.
1056
+ */
1057
+ transactions?: 'wait' | 'strict';
396
1058
  /** Change capture (LIVE-FORMAT): off unless requested. */
397
1059
  capture?: boolean | CaptureOptions;
398
1060
  /** Live-query bounds (LIVE-FORMAT §12). */
@@ -408,11 +1070,50 @@ export interface OpenStoreOptions {
408
1070
  * where one was injected; without it the document is refused rather
409
1071
  * than answered in UTC. No time-zone database is bundled. */
410
1072
  zoneProvider?: unknown;
1073
+ /** The host's runtime record (`@jarenjs/core/runtime`): the clock the
1074
+ * capture log and the job queue stamp, the identifier a `uuid`
1075
+ * identity and a `default: 'uuid'` allocate, the job queue's backoff
1076
+ * jitter and the zone provider — each read only where the explicit
1077
+ * option (`zoneProvider`, `jobs.now`, `jobs.random`) is absent, and
1078
+ * handed on to the job engine. */
1079
+ runtime?: Partial<Runtime>;
1080
+ /** How long work waits for an open transaction to settle before
1081
+ * `JD0012` (MODEL-FORMAT §5.1); reaches every driver. */
1082
+ queueTimeout?: number;
1083
+ /**
1084
+ * The connection pragmas — a closed, validated set (MODEL-FORMAT §4).
1085
+ * An option naming any other pragma is `JD0006`; a pragma the driver
1086
+ * or the store kind cannot apply is `JD0007`; every value is read back
1087
+ * after the open sequence and reported on `capabilities.pragmas`, and
1088
+ * one the engine did not take is `JD0008`.
1089
+ */
1090
+ /** `PRAGMA busy_timeout`, in milliseconds (default 5000). */
411
1091
  busyTimeout?: number;
412
- journalMode?: string;
1092
+ /** `PRAGMA journal_mode` (default `'wal'` on a writable file; a
1093
+ * read-only store keeps the file's mode and refuses an explicit one). */
1094
+ journalMode?: 'delete' | 'truncate' | 'persist' | 'memory' | 'wal' | 'off';
1095
+ /** `PRAGMA synchronous`. */
1096
+ synchronous?: 'off' | 'normal' | 'full' | 'extra';
1097
+ /** `PRAGMA wal_autocheckpoint`, in pages; `0` disables. */
1098
+ walAutocheckpoint?: number;
1099
+ /** `PRAGMA journal_size_limit`, in bytes; `-1` for none. */
1100
+ journalSizeLimit?: number;
1101
+ /** `PRAGMA cache_size`: pages, or negative KiB. */
1102
+ cacheSize?: number;
1103
+ /** `PRAGMA mmap_size`, in bytes. */
1104
+ mmapSize?: number;
1105
+ /** `PRAGMA temp_store`. */
1106
+ tempStore?: 'default' | 'file' | 'memory';
413
1107
  statementCacheBound?: number;
414
- profile?: unknown;
1108
+ /** The store-level safety profile (MODEL-FORMAT §8). */
1109
+ profile?: 'safe' | ProfileSpec;
415
1110
  readOnly?: boolean;
1111
+ /** A `createJsltRegistry()` registry (Ring 2/3): the operators a
1112
+ * query may use, and the pushable subset. */
1113
+ operators?: unknown;
1114
+ /** Raw registry-free operators; never pushed. */
1115
+ functions?: Record<string, unknown>;
1116
+ extensions?: Record<string, unknown>;
416
1117
  }
417
1118
 
418
1119
  export declare function openStore(model: unknown, options: OpenStoreOptions): Promise<Store>;
@@ -433,17 +1134,55 @@ export interface Dialect {
433
1134
  export interface Driver {
434
1135
  readonly name: string;
435
1136
  readonly dialect: Dialect;
436
- open(options?: unknown): unknown;
1137
+ /** Open a connection (value-or-promise) at `path` (`':memory:'` for
1138
+ * none) with the driver's own options. */
1139
+ open(path: string, options?: unknown): unknown;
437
1140
  }
438
1141
 
439
1142
  export declare const sqliteDialect: Dialect;
440
1143
  export declare function createDialect(spec: unknown): Dialect;
1144
+ /** The closed set of dialect capabilities, each with the answer a
1145
+ * spelling spec that says nothing gets. `createDialect` refuses a name
1146
+ * outside it, so a misspelling cannot read as a quiet `false`. */
1147
+ export declare const DIALECT_CAPABILITIES: Readonly<Record<string, boolean>>;
1148
+ /** The R*Tree mapping's DDL group for one spelling spec — the virtual
1149
+ * table and the three row triggers that keep it in sync. Composed into
1150
+ * a dialect whose `capabilities.virtualTables` is true. */
1151
+ export declare function rtreeDdl(spec: unknown): Record<string, Function>;
441
1152
  export declare const SQLITE_FLOOR: string;
1153
+ /** Assemble the connection contract around a raw binding whose
1154
+ * capabilities a probe has already answered. */
1155
+ export declare function finishConnection(
1156
+ raw: unknown, dialect: Dialect, synchronous: boolean,
1157
+ capabilities: Readonly<Record<string, unknown>>, queueTimeout: number,
1158
+ ): unknown;
1159
+ /** The default probe: SQLite's version report, its compile options and
1160
+ * what the binding declares. */
1161
+ export declare function sqliteProbe(
1162
+ raw: unknown, dialect: Dialect, declared: unknown,
1163
+ ): unknown;
1164
+ /** The capability answers every connection carries, each defaulted to
1165
+ * the conservative one — what a probe for another engine fills in. */
1166
+ export declare function baseCapabilities(): Record<string, unknown>;
1167
+ /** The option names of the closed configurable-pragma set, in the
1168
+ * order the open sequence applies them (MODEL-FORMAT §4). */
1169
+ export declare const PRAGMA_NAMES: readonly string[];
1170
+ /** The `PRAGMA wal_checkpoint` modes, closed. */
1171
+ export declare const CHECKPOINT_MODES: readonly string[];
1172
+ /** The maintenance operations, in the order `capabilities.maintenance`
1173
+ * lists them. */
1174
+ export declare const MAINTENANCE_OPERATIONS: readonly string[];
442
1175
 
443
1176
  // ————— entities: models, mapping, generated types —————
444
1177
 
445
1178
  export declare function normalizeEntities(model: unknown): Map<string, unknown>;
446
1179
  export declare function explainMapping(model: unknown): unknown;
1180
+ /** The relation tables of normalized entities, keyed by entity name
1181
+ * then by relation member (MODEL-FORMAT §10.1) — what every entity set
1182
+ * exposes as `relations` and every scope carries for all its roots. */
1183
+ export declare function relationTables(
1184
+ entities: Map<string, unknown>,
1185
+ ): Readonly<Record<string, RelationTable>>;
447
1186
 
448
1187
  /**
449
1188
  * Build the EMIT-FORMAT model document for a model's entities.
@@ -466,6 +1205,8 @@ export declare function translateOperations(
466
1205
  export declare function keyToken(parts: readonly unknown[]): string;
467
1206
  export declare function createCaptureEngine(options: unknown): unknown;
468
1207
  export declare const CHANGES_TABLE: string;
1208
+ /** The change log's durable state table: the highest sequence the file ever allocated (LIVE-FORMAT §5). */
1209
+ export declare const CHANGES_STATE_TABLE: string;
469
1210
  export declare const DEFAULT_RETENTION: number;
470
1211
  /** Deep-freeze a JSON value in place and return it (idempotent). */
471
1212
  export declare function deepFreeze<T>(value: T): T;
@@ -474,6 +1215,58 @@ export declare const BATCH_ROW_BOUND: number;
474
1215
 
475
1216
  // ————— migrations —————
476
1217
 
1218
+ /** Where a migration runs: the driver, the file (`':memory:'` when
1219
+ * absent) and the busy timeout the run opens with. */
1220
+ export interface MigrationTarget {
1221
+ driver: Driver;
1222
+ path?: string;
1223
+ /** `PRAGMA busy_timeout` for the run's connection, ms (default 5000). */
1224
+ busyTimeout?: number;
1225
+ }
1226
+
1227
+ /** One progress event: the migration and collection a data step is
1228
+ * walking, and the running count of the rows it has transformed,
1229
+ * derived or asserted so far (one of the three counters per event). */
1230
+ export interface MigrationProgress {
1231
+ readonly migration: string;
1232
+ readonly collection: string;
1233
+ readonly transformed?: number;
1234
+ readonly derived?: number;
1235
+ readonly asserted?: number;
1236
+ }
1237
+
1238
+ /** One row of an introspection's loss report: what the physical shape
1239
+ * does not carry, by a stable code, once, sorted. */
1240
+ export interface IntrospectReportRow {
1241
+ readonly code: string;
1242
+ /** The physical object it is about (`users`, `users.gx_age`). */
1243
+ readonly object: string;
1244
+ readonly detail: string;
1245
+ }
1246
+
1247
+ /** Every code the loss report uses, with what it means. */
1248
+ export declare const INTROSPECT_CODES: Readonly<Record<string, string>>;
1249
+
1250
+ /**
1251
+ * Database → model, read-only. Answers the `jaren-model` document this
1252
+ * database's shape says it is, beside a report of everything the shape
1253
+ * cannot carry. `strict` refuses instead of returning a partial model.
1254
+ */
1255
+ export declare function introspectModel(connection: unknown, options?: {
1256
+ strict?: boolean;
1257
+ /** Narrow the read to a named set of tables. */
1258
+ tables?: readonly string[];
1259
+ /** The document pointer a text key column cannot record, per table. */
1260
+ keys?: Record<string, string>;
1261
+ }): unknown;
1262
+
1263
+ /** The neutral IR one read produces: tables with their columns,
1264
+ * generated expressions, indexes and foreign keys, plus the views a
1265
+ * model cannot declare. */
1266
+ export declare function readSchema(connection: unknown, options?: {
1267
+ tables?: readonly string[];
1268
+ }): unknown;
1269
+
477
1270
  export interface MigrateOptions {
478
1271
  baseline: unknown;
479
1272
  model?: unknown;
@@ -481,14 +1274,64 @@ export interface MigrateOptions {
481
1274
  dryRun?: boolean;
482
1275
  batchSize?: number;
483
1276
  shadow?: boolean;
1277
+ /** Where the shadow replay runs (default `':memory:'`). */
1278
+ shadowPath?: string;
1279
+ /** The host's declared index-expression functions, by name — the same
1280
+ * declarations `openStore` is given, resolved into the planned DDL. */
1281
+ expressions?: Record<string, ExpressionFunction>;
1282
+ /** The driver the shadow replay opens through (default the target's).
1283
+ * A file engine's shadow is another file; a SERVER engine's is another
1284
+ * schema, and only the host can name one — the baseline shape the
1285
+ * replay creates would otherwise collide with the real store's. */
1286
+ shadowDriver?: unknown;
1287
+ /** Called once per batch a data step walks (transform, derive, or a
1288
+ * per-document assertion). */
1289
+ onProgress?: (progress: MigrationProgress) => void;
484
1290
  /** Re-register declared deterministic functions on every connection
485
1291
  * the migration opens (real, shadow, reference) — §10. */
486
1292
  registerFunctions?: (connection: unknown) => unknown;
1293
+ /** The host's runtime record: the clock every applied migration is
1294
+ * stamped with and the deadline is read against; the platform's own
1295
+ * when absent. */
1296
+ runtime?: Partial<Runtime>;
1297
+ /** Cancels between migrations, steps and batches (`JD2080`); the
1298
+ * migration in flight rolls back whole. */
1299
+ signal?: AbortSignal;
1300
+ /** An epoch-millisecond deadline on `runtime`'s clock (`JD2075`). */
1301
+ deadline?: number;
1302
+ /** What a MATERIALIZING assertion may hold. A per-document predicate
1303
+ * and a single associative aggregate over the root are answered in
1304
+ * batches and are never bounded by this; anything else must hold the
1305
+ * collection at once and crosses these bounds before the excess is
1306
+ * held (`JD2007` rows, `JD2076` bytes). Defaults to
1307
+ * {@link ASSERTION_BOUNDS_DEFAULT}; `null` on either member removes
1308
+ * that bound, deliberately. */
1309
+ assertionBounds?: { maxRows?: number | null; maxBytes?: number | null };
487
1310
  }
488
1311
 
1312
+ /** The finite defaults a materializing assertion runs under when the
1313
+ * caller declares none: 100,000 rows and 64 MiB. */
1314
+ export declare const ASSERTION_BOUNDS_DEFAULT: {
1315
+ readonly maxRows: number;
1316
+ readonly maxBytes: number;
1317
+ };
1318
+
1319
+ /** How a host must run an assertion, and why. `perDocument` walks in
1320
+ * batches; `fold` is one associative aggregate whose batch answers
1321
+ * combine; `materialize` needs every document at once and is bounded. */
1322
+ export declare function classifyAssertion(query: unknown): {
1323
+ strategy: 'perDocument' | 'fold' | 'materialize';
1324
+ shape: string | null;
1325
+ reason: string;
1326
+ };
1327
+
489
1328
  export declare function migrate(
490
- target: unknown, migrations: readonly unknown[], options: MigrateOptions,
1329
+ target: MigrationTarget, migrations: readonly unknown[], options: MigrateOptions,
491
1330
  ): Promise<unknown>;
1331
+ /** Whether an assertion step is a per-document predicate (a FLWOR over
1332
+ * `$[*]` whose body reads only its binding), which the runner evaluates
1333
+ * per batch; anything else reads the collection whole. */
1334
+ export declare function isPerDocumentAssertion(query: unknown): boolean;
492
1335
  export declare function planMigration(from: unknown, to: unknown, options?: unknown): unknown;
493
1336
  /** The whole-model diff — collections AND entities (MIGRATION-FORMAT §9). */
494
1337
  export declare function planModelMigration(from: unknown, to: unknown, options?: unknown): unknown;
@@ -499,11 +1342,15 @@ export interface MigrationStatusReport {
499
1342
  drift: string | null;
500
1343
  upToDate: boolean;
501
1344
  }
1345
+ /** Report a database's migration state without touching it: the
1346
+ * history table is probed, never created. `model` enables the drift
1347
+ * comparison once the chain is fully applied. */
502
1348
  export declare function migrationStatus(
503
- target: { driver: Driver; path?: string },
1349
+ target: MigrationTarget,
504
1350
  migrations: readonly unknown[],
505
- options: { baseline: unknown; model?: unknown;
506
- registerFunctions?: (connection: unknown) => unknown },
1351
+ options?: { model?: unknown;
1352
+ registerFunctions?: (connection: unknown) => unknown;
1353
+ signal?: AbortSignal; deadline?: number; runtime?: Partial<Runtime> },
507
1354
  ): Promise<MigrationStatusReport>;
508
1355
  /** Create a model's whole physical shape on a connection. */
509
1356
  export declare function createModelShape(connection: unknown, model: unknown): unknown;
@@ -521,18 +1368,125 @@ export declare function migrationChecksum(migration: unknown): string;
521
1368
  export declare const MIGRATION_VERSION: string;
522
1369
  export declare const HISTORY_TABLE: string;
523
1370
 
1371
+ /** The counters a storeless run reports for one collection. */
1372
+ export interface DocumentMigrationCounts {
1373
+ readonly read: number;
1374
+ readonly transformed: number;
1375
+ readonly asserted: number;
1376
+ }
1377
+
1378
+ /** What a storeless run did, per collection: `materialized` held the
1379
+ * documents and ran the steps as a Store does; `streamed` carried each
1380
+ * batch through every step in one pass. */
1381
+ export type DocumentMigrationStrategy = 'materialized' | 'streamed';
1382
+
1383
+ export interface DocumentMigrationReport {
1384
+ /** The migration ids this run applied, in order. */
1385
+ readonly applied: string[];
1386
+ /** Always empty: a storeless run has no history to skip against. */
1387
+ readonly skipped: string[];
1388
+ /** The last migration's target shape hash, or null for no migrations. */
1389
+ readonly shape: string | null;
1390
+ readonly counts: Record<string, DocumentMigrationCounts>;
1391
+ readonly strategy: Record<string, DocumentMigrationStrategy>;
1392
+ }
1393
+
1394
+ export interface DocumentMigrationOptions {
1395
+ /** What a MATERIALIZING assertion may hold; the same bounds, and the
1396
+ * same refusals, a Store applies. Defaults to
1397
+ * {@link ASSERTION_BOUNDS_DEFAULT}. */
1398
+ assertionBounds?: { maxRows?: number | null; maxBytes?: number | null };
1399
+ /** Documents per assertion batch and per progress event (default 500). */
1400
+ batchSize?: number;
1401
+ onProgress?: (progress: MigrationProgress) => void;
1402
+ /** The key members of a collection's documents, which a transform may
1403
+ * keep but never move; a Store reads these from its model. */
1404
+ keys?: Record<string, readonly string[]>;
1405
+ /** The JSLT compiler a `jslt` step is compiled with (the suite's own
1406
+ * when absent). */
1407
+ compileJslt?: (stylesheet: unknown) => (document: unknown) => unknown;
1408
+ /** The query compiler a `query` step is compiled with. */
1409
+ compileQuery?: (query: unknown) => unknown;
1410
+ runtime?: Partial<Runtime>;
1411
+ /** Cancels between steps and batches (`JD2080`). */
1412
+ signal?: AbortSignal;
1413
+ /** An epoch-millisecond deadline on `runtime`'s clock (`JD2075`). */
1414
+ deadline?: number;
1415
+ }
1416
+
1417
+ /** Apply a migration's document steps to documents held in memory. The
1418
+ * source is rewindable, so the steps run exactly as a Store runs them —
1419
+ * every step over the whole collection, in step order — which is what
1420
+ * makes the answer, and the refusal, identical to the Store's. A step
1421
+ * that needs tables (`ddl`, `sql`, `rebuild`, `derive`) is refused
1422
+ * (`JD0023`) before the first document is read. */
1423
+ export declare function migrateDocuments(
1424
+ collections: Record<string, readonly unknown[]>,
1425
+ migrations: readonly unknown[],
1426
+ options?: DocumentMigrationOptions,
1427
+ ): Promise<{ documents: Record<string, unknown[]>; report: DocumentMigrationReport }>;
1428
+
1429
+ /** Apply a migration's document steps to a source that can be walked
1430
+ * only once, writing each document out as it finishes: the input is
1431
+ * consumed exactly once and nothing beyond one batch is held. A
1432
+ * cross-document assertion, which needs every document at once, is
1433
+ * refused by name rather than silently buffering the collection. */
1434
+ export declare function streamDocuments(
1435
+ sources: Record<string, Iterable<unknown> | AsyncIterable<unknown>>,
1436
+ migrations: readonly unknown[],
1437
+ options: DocumentMigrationOptions & {
1438
+ write: (collection: string, document: unknown) => unknown;
1439
+ },
1440
+ ): Promise<DocumentMigrationReport>;
1441
+
1442
+ /** The step kinds that act on documents, and so run on any host. */
1443
+ export declare const DOCUMENT_STEP_KINDS: ReadonlySet<string>;
1444
+ /** The step kinds that need a physical database and are refused without one. */
1445
+ export declare const PHYSICAL_STEP_KINDS: ReadonlySet<string>;
1446
+ /** Compile one document step into the operation every host runs. */
1447
+ export declare function compileDocumentStep(
1448
+ step: unknown, index: number, context: {
1449
+ migrationId: string;
1450
+ compileJslt: (stylesheet: unknown) => (document: unknown) => unknown;
1451
+ compileQuery: (query: unknown) => unknown;
1452
+ keys?: readonly string[];
1453
+ },
1454
+ ): unknown;
1455
+ /** Structural validation of one migration document (`JD0023`/`JD0021`). */
1456
+ export declare function checkMigrationDocument(migration: unknown): void;
1457
+ /** The refusal a failing step raises, spelled the one way every host spells it. */
1458
+ export declare function stepFailure(
1459
+ migrationId: string, index: number, kind: string, reason: string, cause?: Error,
1460
+ ): DbCompileError;
1461
+
524
1462
  // ————— the machinery exports —————
525
1463
  // The planner/emitter/residual/profile internals are public for tools
526
1464
  // and tests; their documents have their own formats, so their types
527
1465
  // are deliberately WIDE (unknown), never wrong.
528
1466
 
529
1467
  export declare function planCollection(name: string, collection: unknown, dialect: Dialect): unknown;
530
- export declare function compileIndexPath(path: string, schema: unknown): unknown;
1468
+ export declare function compileIndexPath(expression: string, docPath: string): unknown;
1469
+ export declare function normalizeDeclaredSql(sql: string): string;
1470
+ export declare function comparableDeclaredSql(sql: string): string;
1471
+ /** The comparison kind a declared schema type implies — what a column
1472
+ * over that member holds, and how its expression must read it. */
1473
+ export declare function columnKindFor(
1474
+ schemaType: string | undefined,
1475
+ ): 'text' | 'number' | 'boolean' | undefined;
531
1476
  export declare function schemaTypeAt(schema: unknown, segments: unknown): unknown;
532
1477
  export declare const KEY_COLUMN: string;
533
1478
  export declare const DOC_COLUMN: string;
534
1479
  export declare function planQuery(document: unknown, shape: unknown, options?: unknown): unknown;
535
1480
  export declare function assertDecidedKind(node: unknown): void;
1481
+ /** The planner's reason vocabulary: every cause a plan can name for work
1482
+ * it left in the engine, under a stable identifier. Most entries are the
1483
+ * whole sentence; the four that quote the caller's own values carry the
1484
+ * stable opening they begin with. */
1485
+ export declare const PLANNER_REASONS: Readonly<Record<string,
1486
+ { readonly text: string } | { readonly prefix: string }>>;
1487
+ /** The vocabulary identifier of one reason sentence, or `null` when no
1488
+ * entry claims it. */
1489
+ export declare function reasonId(reason: string): string | null;
536
1490
  export declare function entityShape(entity: unknown, entityMapping: unknown): unknown;
537
1491
  export declare function entityPathRef(node: unknown, slot: number, shape: unknown): unknown;
538
1492
  export declare function planEntityPredicate(node: unknown, slot: number, shape: unknown): unknown;
@@ -545,12 +1499,80 @@ export declare function parseGraphRow(node: unknown, row: unknown, docField?: st
545
1499
  export declare function selectPlan(collection: string): unknown;
546
1500
  export declare function conjoin(plan: unknown, predicate: unknown): unknown;
547
1501
  export declare function assertNoSqlText(plan: unknown): void;
1502
+ /** Whether an order term reads its value from a mapped column rather
1503
+ * than the document. */
1504
+ export declare function ordersByColumn(ref: unknown): boolean;
1505
+ /** The effective order a set of declared terms executes under: the terms,
1506
+ * then the tie-breaker the emitter appends — the primary key when
1507
+ * `keyColumns` is given (keyset mode), one row identity per binding
1508
+ * otherwise. */
1509
+ export declare function effectiveOrder(
1510
+ terms: unknown, options?: { bindings?: (string | null)[]; keyColumns?: readonly string[] },
1511
+ ): EffectiveOrderTerm[];
1512
+ /** The effective order of one plan, in the same normalized form its
1513
+ * emitter renders; `null` for a statement that orders nothing. */
1514
+ export declare function planOrder(plan: unknown): EffectiveOrderTerm[] | null;
548
1515
  export declare const PLAN_VERSION: number;
549
1516
  export declare function typeOfPath(shape: unknown, segments: unknown): unknown;
550
1517
  export declare function isNumericType(type: unknown): boolean;
551
1518
  export declare function compileSetResidual(document: unknown, limits?: unknown): unknown;
552
1519
  export declare function compileRowResidual(rowReturn: unknown, limits?: unknown): unknown;
1520
+ export declare function compilePackedResidual(document: unknown, limits?: unknown): unknown;
1521
+ /** The one cursor mechanism every engine builds on: a row source pulled
1522
+ * one row per `next()` and released exactly once, or a materialised
1523
+ * source that says so. */
1524
+ export declare function createCursor<T = unknown>(spec: {
1525
+ streaming: 'row' | 'buffered';
1526
+ barrier?: CursorBarrier | null;
1527
+ signal?: AbortSignal;
1528
+ materialize?: () => unknown;
1529
+ open?: () => unknown;
1530
+ items?: (row: unknown) => T[];
1531
+ }): QueryCursor<T>;
553
1532
  export declare function sequenceResult(items: unknown[]): unknown;
1533
+ // ————— model-declared index expressions —————
1534
+
1535
+ /** The three node kinds, closed: `member`, `value`, `call`. */
1536
+ export declare const EXPRESSION_KINDS: readonly string[];
1537
+ /** How deep a declared expression may nest. */
1538
+ export declare const EXPRESSION_DEPTH: number;
1539
+ /** One host declaration for a function a model's index expression may
1540
+ * name. An engine that registers functions needs `apply`; one that
1541
+ * cannot needs the `sql` name of an IMMUTABLE function it already has. */
1542
+ export interface ExpressionFunction {
1543
+ arity: number;
1544
+ /** Required, and never inferred: an index over a function that may
1545
+ * answer differently for one row is an index that lies. */
1546
+ deterministic: true;
1547
+ apply?: (...args: any[]) => unknown;
1548
+ sql?: string;
1549
+ }
1550
+ /** Resolve one expression against the host's declarations, refusing an
1551
+ * unknown, wrong-arity or non-deterministic function with `JD0004`. */
1552
+ export declare function normalizeExpression(
1553
+ node: unknown, docPath: string, declarations: Record<string, ExpressionFunction>,
1554
+ depth?: number,
1555
+ ): unknown;
1556
+ /** The identity two declarations of one expression share. */
1557
+ export declare function canonicalExpression(node: unknown): string;
1558
+ /** Every member path an expression reads, in order. */
1559
+ export declare function expressionMembers(node: unknown, out?: string[]): string[];
1560
+ /** Every function an expression calls, sorted. */
1561
+ export declare function expressionFunctions(node: unknown, out?: Set<string>): string[];
1562
+ /** The SQL an expression compiles to on one dialect. */
1563
+ export declare function expressionSql(
1564
+ node: unknown, dialect: Dialect, context: unknown,
1565
+ ): string;
1566
+ /** A short, stable column stem for one expression. */
1567
+ export declare function expressionStem(node: unknown): string;
1568
+ /** The SQL name a declared function is registered under. */
1569
+ export declare function registeredName(name: string): string;
1570
+ /** Register every function a set of expressions calls, on one connection. */
1571
+ export declare function registerExpressionFunctions(
1572
+ connection: unknown, names: readonly string[],
1573
+ declarations: Record<string, ExpressionFunction>,
1574
+ ): unknown;
1575
+
554
1576
  export declare function deterministicFragment(fragment: unknown): unknown;
555
1577
  export declare function registerFragment(connection: unknown, registered: Set<string>, fragment: unknown): void;
556
1578
  export declare function createQueryEngine(context: unknown): unknown;
@@ -558,6 +1580,9 @@ export declare function createQueryState(bound?: number): unknown;
558
1580
  export declare function createEntityQueryEngine(context: unknown): unknown;
559
1581
  export declare function createLoadEngine(context: unknown, entityName: string): unknown;
560
1582
  export declare const INCLUDE_DEPTH_DEFAULT: number;
1583
+ export declare const INCLUDE_ROWS_DEFAULT: number;
1584
+ export declare const INCLUDE_BYTES_DEFAULT: number;
1585
+ export declare const PAGE_LIMIT_DEFAULT: number;
561
1586
  export declare function normalizeProfile(profile: unknown): unknown;
562
1587
  export declare const SAFE_PROFILE: unknown;
563
1588
  export declare function translateProfilePredicate(predicate: unknown, shape: unknown): unknown;
@@ -575,7 +1600,7 @@ export declare function openConnection(raw: unknown, options: unknown): unknown;
575
1600
  export declare function wrapStatement(statement: unknown): unknown;
576
1601
  export declare function lazyOpen(spec: unknown, reason: string, use: unknown, args?: unknown): unknown;
577
1602
  export declare function classifyLiveQuery(
578
- document: unknown, queryShape: unknown, keyed: boolean): unknown;
1603
+ document: unknown, queryShape: unknown, keyed: boolean, eventTime?: unknown): unknown;
579
1604
  export declare function createLiveRegistry(
580
1605
  bounds: { maxQueries: number; maxMaintained: number }): unknown;
581
1606
  export declare function diffRows(oldRows: readonly unknown[], newRows: readonly unknown[]):
@@ -586,52 +1611,172 @@ export declare function createSortedWindow(
586
1611
  export declare function compareCodepoint(a: string, b: string): number;
587
1612
  export declare function collectEntityRoots(
588
1613
  document: unknown, entities: ReadonlyMap<string, unknown>): Set<string>;
1614
+ /** The root expression an entity's rows are bound through (`$.<Name>[*]`) —
1615
+ * what an entity set exposes as `root` and what `collectEntityRoots` reads. */
1616
+ export declare function entityRoot(name: string): string;
1617
+
1618
+ // ————— the derived-index and k-nearest machinery —————
1619
+ // Constants carry their real shapes; the functions take and answer the
1620
+ // planner's own records, which have no published type — WIDE, never
1621
+ // wrong (the line at the top of this file).
1622
+
1623
+ export declare const DERIVE_KINDS: ReadonlySet<string>;
1624
+ export declare const DERIVE_MAPPING: Readonly<Record<string, string | null>>;
1625
+ export declare const PHYSICAL_KINDS: ReadonlySet<string>;
1626
+ export declare const BBOX_COMPONENTS: readonly ['w', 's', 'e', 'n'];
1627
+ export declare const BBOX_INDEX_ORDER: readonly ['w', 'e', 's', 'n'];
1628
+ export declare const PRECISION_MIN: number;
1629
+ export declare const PRECISION_MAX: number;
1630
+ export declare const DIMS_MIN: number;
1631
+ export declare const DIMS_MAX: number;
1632
+ export declare function derivedMappingFor(kind: string, driverMapping: unknown): unknown;
1633
+ export declare function deriveGeohash(value: unknown, precision: number): unknown;
1634
+ export declare function deriveBboxEdge(value: unknown, component: 'w' | 's' | 'e' | 'n'): unknown;
1635
+ export declare function deriveVector(member: unknown, dims: number): unknown;
1636
+ export declare function storedMemberForm(member: unknown): unknown;
1637
+ export declare function derivedValue(column: unknown, member: unknown): unknown;
1638
+ export declare function memberAt(doc: unknown, segments: unknown): unknown;
1639
+ export declare function registerDeriveFunctions(connection: unknown): unknown;
1640
+ export declare function probeVector(value: unknown, dims: number): unknown;
1641
+ export declare function columnScore(bytes: unknown, dims: number, probe: unknown): unknown;
1642
+ export declare const KNN_MARGIN: number;
1643
+ export declare const IDENTITY_CHUNK: number;
1644
+ export declare function cutCandidates(rows: unknown[], m: number, margin: number): unknown;
1645
+ export declare function identityBatches(identities: unknown[]): unknown;
589
1646
 
590
1647
  // ————— the job queue (JOBS-FORMAT) —————
591
1648
 
1649
+ /** A job's state: `cancelled` is terminal like `done` and `dead` and
1650
+ * is set only by `cancel()` (JOBS-FORMAT §10). */
1651
+ export type JobState = 'pending' | 'leased' | 'done' | 'failed' | 'dead' | 'cancelled';
1652
+
592
1653
  export interface JobRecord {
593
1654
  readonly id: string;
594
1655
  readonly kind: string;
595
1656
  readonly payload: unknown;
596
- readonly state: 'pending' | 'leased' | 'done' | 'failed' | 'dead';
1657
+ readonly state: JobState;
597
1658
  readonly runAt: number;
598
1659
  readonly attempts: number;
599
1660
  readonly maxAttempts: number;
600
1661
  readonly leaseUntil: number | null;
601
1662
  readonly leaseOwner: string | null;
1663
+ /** How many times this job has been claimed. It identifies the
1664
+ * ATTEMPT, which the owner cannot: one worker reuses one owner. */
1665
+ readonly leaseGeneration: number;
602
1666
  readonly lastError: string | null;
603
1667
  readonly result: unknown;
604
1668
  readonly createdAt: number;
605
1669
  readonly updatedAt: number;
606
1670
  }
607
1671
 
1672
+ /**
1673
+ * The capability one claim mints: the right to settle THIS attempt of
1674
+ * this job, for as long as the lease is valid.
1675
+ *
1676
+ * It is a token rather than an owner, because an owner is reused by
1677
+ * every attempt one worker makes and so cannot say which attempt is
1678
+ * speaking. It is immutable: `renew` answers a NEW lease and retires
1679
+ * this one, so a reference kept across a renewal can never quietly
1680
+ * become valid again.
1681
+ *
1682
+ * Only `claim` and `renew` hand one out. `get` does not — a record
1683
+ * anyone can read must not carry the capability to settle it.
1684
+ */
1685
+ export interface JobLease {
1686
+ readonly jobId: string;
1687
+ readonly token: string;
1688
+ readonly generation: number;
1689
+ readonly attempt: number;
1690
+ /** Diagnostics only: never a guard. */
1691
+ readonly owner: string | null;
1692
+ readonly expiresAt: number;
1693
+ }
1694
+
1695
+ /** What `claim` answers: the record, and the lease to settle it with. */
1696
+ export interface ClaimedJob extends JobRecord {
1697
+ readonly lease: JobLease;
1698
+ }
1699
+
608
1700
  export interface JobCounts {
609
1701
  pending: number;
610
1702
  leased: number;
611
1703
  done: number;
612
1704
  failed: number;
613
1705
  dead: number;
1706
+ cancelled: number;
614
1707
  /** Pending/failed totals per kind — how a handler-less kind REPORTS. */
615
1708
  pendingKinds: Record<string, number>;
616
1709
  }
617
1710
 
1711
+ /** What became of one attempt. A LOST settlement is its own outcome:
1712
+ * counting it as a completion is what let a corpse report success over
1713
+ * work another attempt was still doing, and counting it as a failure
1714
+ * would burn a retry the job never spent. */
1715
+ export interface JobOutcome {
1716
+ /** `cancelled`: the attempt was taken from this worker by `cancel()`
1717
+ * with its lease — neither a completion, a failure nor a loss. */
1718
+ readonly outcome: 'completed' | 'failed' | 'lost' | 'cancelled';
1719
+ /** Where the loss was noticed: settling the result, or settling the
1720
+ * failure that came before it. Absent on the other two outcomes. */
1721
+ readonly phase?: 'completion' | 'failure';
1722
+ readonly jobId: string;
1723
+ readonly kind: string;
1724
+ readonly attempt: number;
1725
+ readonly generation: number;
1726
+ /** The refusal code a lost settlement carries (`JD2065`/`JD2066`/
1727
+ * `JD2067`); `null` when the lease was lost some other way. */
1728
+ readonly code?: string | null;
1729
+ readonly reason?: string;
1730
+ }
1731
+
618
1732
  export interface JobWorker {
619
1733
  start(): JobWorker;
620
- /** Resolves after in-flight handlers settle. */
621
- stop(): Promise<void>;
1734
+ /** Stop claiming, signal in-flight handlers, and wait up to `graceMs`
1735
+ * (JOBS-FORMAT §6): the record says whether every loop drained. */
1736
+ stop(options?: { graceMs?: number }): Promise<{ drained: boolean; inFlight: number }>;
622
1737
  stats(): { claims: number; completions: number; failures: number;
623
- polls: number; wakes: number };
1738
+ polls: number; wakes: number; claimErrors: number; inFlight: number;
1739
+ /** Leases replaced while a handler was still running. */
1740
+ renewals: number;
1741
+ /** Attempts whose lease was lost mid-flight — never a completion,
1742
+ * never a failure. */
1743
+ lostSettlements: number;
1744
+ /** Attempts cancelled through `cancel()` while their handler ran. */
1745
+ cancellations: number };
1746
+ /** The leases this worker holds right now: one per in-flight attempt,
1747
+ * each the newest that attempt has been given. */
1748
+ leases(): readonly JobLease[];
624
1749
  }
625
1750
 
626
1751
  export interface JobWorkerOptions {
627
- handlers: Record<string,
628
- (payload: unknown, context: { job: JobRecord, checkpointsFor: Function }) => unknown>;
1752
+ /**
1753
+ * `checkpoints` is bound to THIS attempt and follows its current
1754
+ * lease, so a renewal does not strand it. `signal` aborts for either
1755
+ * reason a handler must wind up for: the worker is stopping, or the
1756
+ * job is no longer this attempt's to finish — in which case the
1757
+ * signal's `reason` is the coded refusal that says which.
1758
+ */
1759
+ handlers: Record<string, (payload: unknown, context: {
1760
+ job: ClaimedJob;
1761
+ checkpoints: { load(runId: string): unknown;
1762
+ save(runId: string, nodeId: string, value: unknown): unknown;
1763
+ complete(runId: string, result: unknown): unknown };
1764
+ signal: AbortSignal }) => unknown>;
1765
+ /** A positive integer; the loops claiming concurrently. */
629
1766
  concurrency?: number;
630
1767
  pollInterval?: number;
631
1768
  leaseMs?: number;
632
1769
  owner?: string;
1770
+ /** Renew each attempt's lease while its handler runs (the default).
1771
+ * `false` for a handler that must not outlive its lease. */
1772
+ renew?: boolean;
1773
+ /** Called once per settled attempt, including the lost ones. An
1774
+ * observer that throws never affects the loop. */
1775
+ onOutcome?: (event: JobOutcome) => void;
633
1776
  backoffBase?: number;
634
1777
  backoffCap?: number;
1778
+ /** How long `stop()` waits for in-flight handlers by default. */
1779
+ stopGraceMs?: number;
635
1780
  }
636
1781
 
637
1782
  export interface JobsApi {
@@ -639,13 +1784,30 @@ export interface JobsApi {
639
1784
  options?: { id?: string; runAt?: number; maxAttempts?: number }): Promise<string>;
640
1785
  get(id: string): Promise<JobRecord | undefined>;
641
1786
  counts(): Promise<JobCounts>;
642
- /** The low-level guarded claim the worker itself uses (§3). */
1787
+ /** The low-level guarded claim the worker itself uses (§3). It mints
1788
+ * the fence: a fresh token and the next generation. */
643
1789
  claim(options: { kinds: string[]; owner: string; leaseMs?: number }):
644
- Promise<JobRecord | undefined>;
645
- complete(id: string, owner: string, result?: unknown): Promise<boolean>;
646
- fail(id: string, owner: string, error: unknown): Promise<boolean>;
647
- /** The per-job flow checkpoint store binding (§7). */
648
- checkpointsFor(job: JobRecord): {
1790
+ Promise<ClaimedJob | undefined>;
1791
+ /**
1792
+ * Replace a lease with a later one (§3). A handler that runs longer
1793
+ * than its lease renews rather than hoping; the lease it is given
1794
+ * back supersedes the one it passed in, which then settles nothing.
1795
+ *
1796
+ * Refuses `JD2065` (the job is not leased — unknown, or already
1797
+ * settled), `JD2066` (the lease was superseded) or `JD2067` (it
1798
+ * expired), never a silent `false`.
1799
+ */
1800
+ renew(lease: JobLease, options?: { leaseMs?: number }): Promise<JobLease>;
1801
+ /** Settle the attempt this lease holds. `true`, or one of the three
1802
+ * coded refusals above — a caller that cannot tell "already done"
1803
+ * from "you are stale" guesses, and guesses wrong. */
1804
+ complete(lease: JobLease, result?: unknown): Promise<boolean>;
1805
+ fail(lease: JobLease, error: unknown): Promise<boolean>;
1806
+ /** The per-attempt flow checkpoint store binding (§7). Rows are
1807
+ * stamped with the attempt's generation: `load` reads what was
1808
+ * written up to it, and a settlement prunes no further, so a stale
1809
+ * attempt cannot erase a live one's work. */
1810
+ checkpointsFor(job: ClaimedJob): {
649
1811
  load(runId: string): unknown;
650
1812
  save(runId: string, nodeId: string, value: unknown): unknown;
651
1813
  complete(runId: string, result: unknown): unknown;
@@ -653,12 +1815,52 @@ export interface JobsApi {
653
1815
  createWorker(options: JobWorkerOptions): JobWorker;
654
1816
  }
655
1817
 
1818
+ export interface JobPageOptions {
1819
+ /** A closed state; every job when absent. */
1820
+ state?: JobState;
1821
+ kind?: string;
1822
+ /** The id to continue past (keyset by id). */
1823
+ after?: string;
1824
+ /** At most this many items (default 100). */
1825
+ limit?: number;
1826
+ /** Row-boundary cancellation, as every cursor (`JD2072` / `JD2075`). */
1827
+ signal?: AbortSignal;
1828
+ deadline?: number;
1829
+ }
1830
+
1831
+ /**
1832
+ * Job administration (JOBS-FORMAT §10) — root-only mechanism, never a
1833
+ * schedule: WHEN to sweep or cancel is the host's call.
1834
+ */
1835
+ export interface JobsAdminApi {
1836
+ /** A keyset cursor over the queue by id, admitted per pull under the
1837
+ * store gate; each item is the record `get` answers. */
1838
+ page(options?: JobPageOptions): QueryCursor<JobRecord>;
1839
+ /** Cancel a queued job by its identity, or a claimed one through its
1840
+ * CURRENT lease (the fence's rule): the row settles as `cancelled`,
1841
+ * a local attempt's handler signal aborts, and the call resolves once
1842
+ * that attempt wound up. `true` when this call cancelled it, `false`
1843
+ * when it already was; `JD2065`/`JD2066`/`JD2067`/`JD2068` as a
1844
+ * settling call refuses. */
1845
+ cancel(id: string, options?: { lease?: JobLease; signal?: AbortSignal; deadline?: number }): Promise<boolean>;
1846
+ /** Return a failed, dead, cancelled or lease-expired job to the queue
1847
+ * with its attempt history intact; `false` when it already was
1848
+ * pending; a live lease refuses `JD2068`, unknown or done `JD2065`. */
1849
+ requeue(id: string, options?: { signal?: AbortSignal; deadline?: number }): Promise<boolean>;
1850
+ /** Delete settled jobs (done, dead, cancelled) last changed before
1851
+ * `settledBefore`, oldest first, at most `limit`, with their
1852
+ * checkpoints; a second identical sweep answers `{ removed: 0 }`. */
1853
+ sweep(options: { settledBefore: number; limit?: number; signal?: AbortSignal; deadline?: number }):
1854
+ Promise<{ removed: number }>;
1855
+ }
1856
+
656
1857
  export interface JobsOptions {
657
1858
  maxAttempts?: number;
658
1859
  leaseMs?: number;
659
1860
  pollInterval?: number;
660
1861
  backoffBase?: number;
661
1862
  backoffCap?: number;
1863
+ stopGraceMs?: number;
662
1864
  /** Injectable clock and randomness — every test injects both. */
663
1865
  now?: () => number;
664
1866
  random?: () => number;
@@ -672,15 +1874,27 @@ export declare function createDagJobRunner(store: Store, options: {
672
1874
  pollInterval?: number;
673
1875
  leaseMs?: number;
674
1876
  owner?: string;
1877
+ renew?: boolean;
1878
+ onOutcome?: (event: JobOutcome) => void;
675
1879
  backoffBase?: number;
676
1880
  backoffCap?: number;
1881
+ stopGraceMs?: number;
677
1882
  }): JobWorker;
678
1883
 
1884
+ /** The checkpoint row a run's identity lives in — the workflow revision
1885
+ * and a hash of the input a resume must agree with (`JD2069` when it
1886
+ * does not). Pruned with the run it belongs to. */
1887
+ export declare const RUN_IDENTITY_NODE: string;
1888
+
679
1889
  export declare function createJobEngine(options: {
680
1890
  connection: unknown; now?: () => number; random?: () => number;
681
- defaults?: JobsOptions }): unknown;
1891
+ defaults?: JobsOptions; runtime?: Partial<Runtime> }): unknown;
682
1892
  export declare const JOBS_TABLE: string;
683
1893
  export declare const JOB_CHECKPOINTS_TABLE: string;
684
1894
  export declare const JOB_DEFAULTS: Readonly<{
685
1895
  maxAttempts: number; leaseMs: number; pollInterval: number;
686
- backoffBase: number; backoffCap: number }>;
1896
+ backoffBase: number; backoffCap: number; stopGraceMs: number }>;
1897
+ /** A total diagnostic string for any value, including ones that fight back. */
1898
+ export declare function describeValue(value: unknown): string;
1899
+ /** A job result as the queue stores it: JSON text, or the reason it could not be. */
1900
+ export declare function serializeResult(value: unknown): unknown;