@jarenjs/db 0.46.5 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/ARCHITECTURE.md +133 -17
  2. package/README.md +270 -36
  3. package/docs/JOBS-FORMAT.md +24 -8
  4. package/docs/LIVE-FORMAT.md +139 -7
  5. package/docs/MIGRATION-FORMAT.md +118 -36
  6. package/docs/MODEL-FORMAT.md +251 -30
  7. package/package.json +4 -5
  8. package/schemas/jaren-migration.draft-07.schema.json +73 -0
  9. package/schemas/jaren-migration.schema.json +73 -0
  10. package/src/algebra.js +22 -3
  11. package/src/capture.js +66 -28
  12. package/src/cli.js +225 -44
  13. package/src/ddl.js +23 -3
  14. package/src/dialect.js +13 -0
  15. package/src/dialects/sqlite.js +21 -1
  16. package/src/driver.js +63 -16
  17. package/src/drivers/wasm.js +1 -0
  18. package/src/emit-model.js +14 -0
  19. package/src/emit.js +42 -9
  20. package/src/entity.js +92 -47
  21. package/src/errors.js +28 -0
  22. package/src/index.js +2 -2
  23. package/src/jobs.js +40 -5
  24. package/src/live-time.js +605 -0
  25. package/src/live.js +52 -9
  26. package/src/migrate.js +397 -191
  27. package/src/model.js +173 -8
  28. package/src/plan.js +834 -47
  29. package/src/query.js +296 -22
  30. package/src/residual.js +15 -6
  31. package/src/series.js +349 -0
  32. package/src/store.js +243 -69
  33. package/src/tracker.js +173 -48
  34. package/types/index.d.ts +206 -12
  35. package/types/node.d.ts +3 -1
  36. package/types/typed.d.ts +58 -2
  37. package/types/wasm.d.ts +7 -0
  38. package/dist/types/algebra.d.ts +0 -199
  39. package/dist/types/app.d.ts +0 -49
  40. package/dist/types/capture.d.ts +0 -85
  41. package/dist/types/cli.d.ts +0 -2
  42. package/dist/types/dag-job.d.ts +0 -40
  43. package/dist/types/ddl.d.ts +0 -229
  44. package/dist/types/derive.d.ts +0 -250
  45. package/dist/types/dialect.d.ts +0 -149
  46. package/dist/types/dialects/sqlite.d.ts +0 -9
  47. package/dist/types/driver.d.ts +0 -110
  48. package/dist/types/drivers/bun.d.ts +0 -47
  49. package/dist/types/drivers/node.d.ts +0 -37
  50. package/dist/types/drivers/wasm.d.ts +0 -65
  51. package/dist/types/emit-model.d.ts +0 -44
  52. package/dist/types/emit.d.ts +0 -75
  53. package/dist/types/entity.d.ts +0 -23
  54. package/dist/types/errors.d.ts +0 -167
  55. package/dist/types/graph.d.ts +0 -28
  56. package/dist/types/index.d.ts +0 -37
  57. package/dist/types/jobs.d.ts +0 -140
  58. package/dist/types/knn.d.ts +0 -69
  59. package/dist/types/live.d.ts +0 -62
  60. package/dist/types/migrate.d.ts +0 -170
  61. package/dist/types/model.d.ts +0 -36
  62. package/dist/types/patch-sql.d.ts +0 -37
  63. package/dist/types/plan.d.ts +0 -140
  64. package/dist/types/profile.d.ts +0 -80
  65. package/dist/types/query.d.ts +0 -111
  66. package/dist/types/residual.d.ts +0 -61
  67. package/dist/types/store.d.ts +0 -53
  68. package/dist/types/tracker.d.ts +0 -43
  69. package/dist/types/typed.d.ts +0 -15
  70. package/dist/types/types.d.ts +0 -26
  71. package/dist/types/udf.d.ts +0 -75
  72. package/dist/types/window.d.ts +0 -52
package/types/index.d.ts CHANGED
@@ -84,7 +84,9 @@ export interface ExecuteOptions {
84
84
  pushdown?: boolean;
85
85
  }
86
86
 
87
- export interface LoadInclude extends LoadSpec {
87
+ /** An include's clauses: the root's without `after` — a keyset cursor
88
+ * paginates the root alone; an include windows with `skip`/`take`. */
89
+ export interface LoadInclude extends Omit<LoadSpec, 'after'> {
88
90
  /** Project the related-row COUNT instead of the rows. */
89
91
  count?: boolean;
90
92
  }
@@ -128,6 +130,8 @@ export interface StoreStats {
128
130
  tracked: number;
129
131
  pendingInserts: number;
130
132
  pendingDeletes: number;
133
+ /** Pending `link`/`unlink` records: one per entity, own key and member (§11.7). */
134
+ pendingMemberships: number;
131
135
  } | null;
132
136
  liveQueries: number;
133
137
  }
@@ -143,6 +147,10 @@ export interface StoreCapabilities {
143
147
  readonly captureLog: boolean;
144
148
  readonly live: boolean;
145
149
  readonly jobs: boolean;
150
+ /** Whether a temporal spec naming a ZONE compiles here (D7's
151
+ * injected clock was supplied at open). Without it such a document
152
+ * is refused rather than answered in UTC. */
153
+ readonly zoneProvider: boolean;
146
154
  readonly [capability: string]: unknown;
147
155
  }
148
156
 
@@ -182,12 +190,44 @@ export interface SyncCollection<T = unknown> {
182
190
 
183
191
  // ————— entities (phase B) —————
184
192
 
193
+ /** One row of an entity's relation table (MODEL-FORMAT §10.1): the
194
+ * declared relation as plain data a query producer can lower a hop
195
+ * from — never a document dialect. For a foreign-key relation `via`
196
+ * names the key property, `fkEntity` the entity holding it, `fkTargets`
197
+ * the entity it references and `targetKey` the key property it
198
+ * references there (the column a hop's equality compares `via` with);
199
+ * `kind` says which side holds the key (`oneToOne`: the declaring
200
+ * entity; `oneToMany`: the target). A many-to-many carries its
201
+ * `joinTable` and the target's `targetKey`. */
202
+ export interface RelationEntry {
203
+ readonly to: string;
204
+ readonly kind: 'oneToOne' | 'oneToMany' | 'manyToMany';
205
+ readonly via?: string;
206
+ readonly fkEntity?: string;
207
+ readonly fkTargets?: string;
208
+ readonly joinTable?: string;
209
+ readonly targetKey: string;
210
+ }
211
+
212
+ /** An entity's relation table: one entry per declared relation member. */
213
+ export type RelationTable = Readonly<Record<string, RelationEntry>>;
214
+
215
+ /** The identity every entity set of one store shares — two sets with one
216
+ * `scope` may be joined in one document — carrying the relation tables
217
+ * of every root, keyed by entity name, so a hop can chain into another
218
+ * root of the same scope. */
219
+ export interface EntityScope {
220
+ readonly relations: Readonly<Record<string, RelationTable>>;
221
+ }
222
+
185
223
  export interface UntrackedReads<T = unknown> {
186
224
  get(key: EntityKeyArg): Promise<T | undefined>;
187
225
  load(spec?: LoadSpec): Promise<T[]>;
188
226
  }
189
227
 
190
228
  export interface EntitySet<T = unknown, I = unknown> {
229
+ /** The provider phantom: a chain over this set infers its item type. */
230
+ readonly __item?: T;
191
231
  create(doc: I): Promise<Readonly<T>>;
192
232
  get(key: EntityKeyArg): Promise<Readonly<T> | undefined>;
193
233
  update(key: EntityKeyArg, changes: Partial<T>): Promise<Readonly<T>>;
@@ -202,7 +242,27 @@ export interface EntitySet<T = unknown, I = unknown> {
202
242
  remove(key: EntityKeyArg | T): void;
203
243
  /** Drop tracking without scheduling anything — conflict recovery. */
204
244
  discard(key: EntityKeyArg | T): void;
245
+ /** Attach / detach one many-to-many membership through the unit of
246
+ * work (§11.7): local bookkeeping, written by `saveChanges()` as join
247
+ * rows against the join table as it stands then — idempotent. `own`
248
+ * and `target` are each a key or a document carrying the key. */
249
+ link(own: EntityKeyArg | T, member: string, target: EntityKeyArg | object): void;
250
+ unlink(own: EntityKeyArg | T, member: string, target: EntityKeyArg | object): void;
205
251
  asNoTracking(): UntrackedReads<T>;
252
+ /** The provider contract over this entity's root (MODEL-FORMAT §10.1):
253
+ * the document is over the multi-entity root and arrives whole; the
254
+ * answer is the engine's result shape, value-or-promise (D2). */
255
+ execute<R = unknown>(document: unknown, options?: ExecuteOptions): ValueOrPromise<SequenceResult<R>>;
256
+ explain(document: unknown, options?: ExecuteOptions): Promise<unknown>;
257
+ /** The root expression this set's rows are bound through (`$.<Name>[*]`). */
258
+ readonly root: string;
259
+ /** The identity every entity set of one store shares: two sets with one
260
+ * `scope` may be joined in one document; it carries every root's
261
+ * relation table. */
262
+ readonly scope: EntityScope;
263
+ /** This entity's relation table (MODEL-FORMAT §10.1) — what a query
264
+ * producer lowers a relation hop from. */
265
+ readonly relations: RelationTable;
206
266
  }
207
267
 
208
268
  export interface SyncUntrackedReads<T = unknown> {
@@ -211,6 +271,8 @@ export interface SyncUntrackedReads<T = unknown> {
211
271
  }
212
272
 
213
273
  export interface SyncEntitySet<T = unknown, I = unknown> {
274
+ /** The provider phantom: a chain over this set infers its item type. */
275
+ readonly __item?: T;
214
276
  create(doc: I): Readonly<T>;
215
277
  get(key: EntityKeyArg): Readonly<T> | undefined;
216
278
  update(key: EntityKeyArg, changes: Partial<T>): Readonly<T>;
@@ -221,7 +283,15 @@ export interface SyncEntitySet<T = unknown, I = unknown> {
221
283
  put(next: T): Readonly<T>;
222
284
  remove(key: EntityKeyArg | T): void;
223
285
  discard(key: EntityKeyArg | T): void;
286
+ link(own: EntityKeyArg | T, member: string, target: EntityKeyArg | object): void;
287
+ unlink(own: EntityKeyArg | T, member: string, target: EntityKeyArg | object): void;
224
288
  asNoTracking(): SyncUntrackedReads<T>;
289
+ /** The provider contract over this entity's root, answering values. */
290
+ execute<R = unknown>(document: unknown, options?: ExecuteOptions): SequenceResult<R>;
291
+ explain(document: unknown, options?: ExecuteOptions): unknown;
292
+ readonly root: string;
293
+ readonly scope: EntityScope;
294
+ readonly relations: RelationTable;
225
295
  }
226
296
 
227
297
  // ————— the store —————
@@ -233,6 +303,14 @@ export interface SyncStore {
233
303
  entity(name: string): SyncEntitySet;
234
304
  transaction<R>(fn: (store: Store) => R): R;
235
305
  execute?<R = unknown>(document: unknown, options?: ExecuteOptions): SequenceResult<R>;
306
+ explain?(document: unknown, options?: ExecuteOptions): unknown;
307
+ /** The entity roots this store-level provider serves (present with
308
+ * entities): it has no single root of its own, so a chain over it is
309
+ * refused by name — chain over `entity(name)` instead. */
310
+ readonly roots?: readonly string[];
311
+ /** The relation tables of every entity, keyed by entity name (present
312
+ * with entities; MODEL-FORMAT §10.1). */
313
+ readonly relations?: Readonly<Record<string, RelationTable>>;
236
314
  saveChanges?(): SaveReport;
237
315
  }
238
316
 
@@ -249,6 +327,13 @@ export interface Store {
249
327
  * in the engine's result shape. */
250
328
  execute?<R = unknown>(document: unknown, options?: ExecuteOptions): ValueOrPromise<SequenceResult<R>>;
251
329
  explain?(document: unknown, options?: ExecuteOptions): Promise<unknown>;
330
+ /** The entity roots this store-level provider serves (present with
331
+ * entities): it has no single root of its own, so a chain over it is
332
+ * refused by name — chain over `entity(name)` instead. */
333
+ readonly roots?: readonly string[];
334
+ /** The relation tables of every entity, keyed by entity name (present
335
+ * with entities; MODEL-FORMAT §10.1). */
336
+ readonly relations?: Readonly<Record<string, RelationTable>>;
252
337
  /** The unit of work (§11); present only with entities. */
253
338
  saveChanges?(): Promise<SaveReport>;
254
339
  transaction<R>(fn: (store: Store) => R | Promise<R>): Promise<Awaited<R>>;
@@ -297,10 +382,30 @@ export interface LiveOptions {
297
382
  /** 'incremental' DEMANDS incrementality (JD0051 when the shape
298
383
  * re-runs); 'rerun' forces the re-run strategy. */
299
384
  mode?: 'auto' | 'incremental' | 'rerun';
385
+ /** Event time for a `$resample` / `$rolling` view (LIVE-FORMAT §13).
386
+ * Its members are closed: anything else is JD0053. */
387
+ eventTime?: LiveEventTime;
388
+ }
389
+
390
+ export interface LiveEventTime {
391
+ /** A singular row selector naming the instant member, e.g. '$.at'.
392
+ * It must be the member the spec aggregates by. */
393
+ path: string;
394
+ /** A finite epoch in milliseconds. Never a clock reading — the host
395
+ * supplies it, and `advance()` is the only way it moves. */
396
+ watermark: number;
397
+ /** How far behind the watermark a reading may still be applied
398
+ * (default 0). Older readings emit `lateData` and re-run. */
399
+ allowedLateness?: number;
400
+ /** The horizon this view claims, in milliseconds. It must cover the
401
+ * window (or bucket) width plus `allowedLateness`, or the view is
402
+ * classified as a re-run. */
403
+ retention: number;
300
404
  }
301
405
 
302
406
  export interface LiveMode {
303
- readonly strategy: 'rows' | 'window' | 'accumulator' | 'group' | 'rerun';
407
+ readonly strategy: 'rows' | 'window' | 'accumulator' | 'group'
408
+ | 'bucket' | 'rolling' | 'rerun';
304
409
  readonly mode: 'incremental' | 'rerun';
305
410
  /** Present exactly when the strategy is 'rerun': the named reason. */
306
411
  readonly reason?: string;
@@ -312,6 +417,17 @@ export interface LiveEvent {
312
417
  seq?: number;
313
418
  /** A maintenance failure (JD2060 …): the query closed after this. */
314
419
  error?: unknown;
420
+ /** Present when a reading behind the lateness boundary forced this
421
+ * emission: the view re-read, and the row was never folded in as
422
+ * though it had arrived on time (LIVE-FORMAT §13). */
423
+ lateData?: {
424
+ reason: 'late-data';
425
+ at: number;
426
+ key: string;
427
+ watermark: number;
428
+ allowedLateness: number;
429
+ boundary: number;
430
+ };
315
431
  }
316
432
 
317
433
  export interface LiveStats {
@@ -320,8 +436,15 @@ export interface LiveStats {
320
436
  emissions: number;
321
437
  /** min/max extremum-removal recomputes (accumulator strategy). */
322
438
  fallbacks?: number;
323
- /** whole-query re-executions (re-run strategy). */
439
+ /** whole-query re-executions (re-run strategy, and the re-read a
440
+ * late reading forces). */
324
441
  reruns?: number;
442
+ /** readings that arrived behind the lateness boundary (event time). */
443
+ lateData?: number;
444
+ /** buckets or window stretches folded again (event time). */
445
+ recomputes?: number;
446
+ /** the current watermark (event time). */
447
+ watermark?: number;
325
448
  }
326
449
 
327
450
  export interface LiveQuery {
@@ -333,6 +456,10 @@ export interface LiveQuery {
333
456
  readonly mode: LiveMode;
334
457
  stats(): LiveStats;
335
458
  subscribe(observer: (event: LiveEvent) => void): () => void;
459
+ /** Move the event-time watermark forward. Present only on a view
460
+ * registered with `eventTime`; a non-finite or backward value is a
461
+ * TypeError. */
462
+ advance?(watermark: number): void;
336
463
  close(): void;
337
464
  }
338
465
 
@@ -356,11 +483,26 @@ export interface OpenStoreOptions {
356
483
  /** The injected validation hook (D10); absent means unvalidated,
357
484
  * declared through `capabilities.validated`. */
358
485
  compileSchema?: (schema: unknown) => (doc: unknown) => unknown;
486
+ /** D7's injected clock — `{ toParts(epoch, zone), toEpoch(parts, zone,
487
+ * disambiguation) }`. A temporal spec naming a zone
488
+ * (`{ "every": "P1M", "zone": "Europe/Amsterdam" }`) compiles only
489
+ * where one was injected; without it the document is refused rather
490
+ * than answered in UTC. No time-zone database is bundled. */
491
+ zoneProvider?: unknown;
359
492
  busyTimeout?: number;
493
+ /** How long work waits for an open transaction to settle before
494
+ * `JD0012` (MODEL-FORMAT §5.1); reaches every driver. */
495
+ queueTimeout?: number;
360
496
  journalMode?: string;
361
497
  statementCacheBound?: number;
362
498
  profile?: unknown;
363
499
  readOnly?: boolean;
500
+ /** A `createJsltRegistry()` registry (Ring 2/3): the operators a
501
+ * query may use, and the pushable subset. */
502
+ operators?: unknown;
503
+ /** Raw registry-free operators; never pushed. */
504
+ functions?: Record<string, unknown>;
505
+ extensions?: Record<string, unknown>;
364
506
  }
365
507
 
366
508
  export declare function openStore(model: unknown, options: OpenStoreOptions): Promise<Store>;
@@ -381,7 +523,9 @@ export interface Dialect {
381
523
  export interface Driver {
382
524
  readonly name: string;
383
525
  readonly dialect: Dialect;
384
- open(options?: unknown): unknown;
526
+ /** Open a connection (value-or-promise) at `path` (`':memory:'` for
527
+ * none) with the driver's own options. */
528
+ open(path: string, options?: unknown): unknown;
385
529
  }
386
530
 
387
531
  export declare const sqliteDialect: Dialect;
@@ -392,6 +536,12 @@ export declare const SQLITE_FLOOR: string;
392
536
 
393
537
  export declare function normalizeEntities(model: unknown): Map<string, unknown>;
394
538
  export declare function explainMapping(model: unknown): unknown;
539
+ /** The relation tables of normalized entities, keyed by entity name
540
+ * then by relation member (MODEL-FORMAT §10.1) — what every entity set
541
+ * exposes as `relations` and every scope carries for all its roots. */
542
+ export declare function relationTables(
543
+ entities: Map<string, unknown>,
544
+ ): Readonly<Record<string, RelationTable>>;
395
545
 
396
546
  /**
397
547
  * Build the EMIT-FORMAT model document for a model's entities.
@@ -475,7 +625,9 @@ export declare const HISTORY_TABLE: string;
475
625
  // are deliberately WIDE (unknown), never wrong.
476
626
 
477
627
  export declare function planCollection(name: string, collection: unknown, dialect: Dialect): unknown;
478
- export declare function compileIndexPath(path: string, schema: unknown): unknown;
628
+ export declare function compileIndexPath(expression: string, docPath: string): unknown;
629
+ export declare function normalizeDeclaredSql(sql: string): string;
630
+ export declare function comparableDeclaredSql(sql: string): string;
479
631
  export declare function schemaTypeAt(schema: unknown, segments: unknown): unknown;
480
632
  export declare const KEY_COLUMN: string;
481
633
  export declare const DOC_COLUMN: string;
@@ -523,7 +675,7 @@ export declare function openConnection(raw: unknown, options: unknown): unknown;
523
675
  export declare function wrapStatement(statement: unknown): unknown;
524
676
  export declare function lazyOpen(spec: unknown, reason: string, use: unknown, args?: unknown): unknown;
525
677
  export declare function classifyLiveQuery(
526
- document: unknown, queryShape: unknown, keyed: boolean): unknown;
678
+ document: unknown, queryShape: unknown, keyed: boolean, eventTime?: unknown): unknown;
527
679
  export declare function createLiveRegistry(
528
680
  bounds: { maxQueries: number; maxMaintained: number }): unknown;
529
681
  export declare function diffRows(oldRows: readonly unknown[], newRows: readonly unknown[]):
@@ -534,6 +686,38 @@ export declare function createSortedWindow(
534
686
  export declare function compareCodepoint(a: string, b: string): number;
535
687
  export declare function collectEntityRoots(
536
688
  document: unknown, entities: ReadonlyMap<string, unknown>): Set<string>;
689
+ /** The root expression an entity's rows are bound through (`$.<Name>[*]`) —
690
+ * what an entity set exposes as `root` and what `collectEntityRoots` reads. */
691
+ export declare function entityRoot(name: string): string;
692
+
693
+ // ————— the derived-index and k-nearest machinery —————
694
+ // Constants carry their real shapes; the functions take and answer the
695
+ // planner's own records, which have no published type — WIDE, never
696
+ // wrong (the line at the top of this file).
697
+
698
+ export declare const DERIVE_KINDS: ReadonlySet<string>;
699
+ export declare const DERIVE_MAPPING: Readonly<Record<string, string | null>>;
700
+ export declare const PHYSICAL_KINDS: ReadonlySet<string>;
701
+ export declare const BBOX_COMPONENTS: readonly ['w', 's', 'e', 'n'];
702
+ export declare const BBOX_INDEX_ORDER: readonly ['w', 'e', 's', 'n'];
703
+ export declare const PRECISION_MIN: number;
704
+ export declare const PRECISION_MAX: number;
705
+ export declare const DIMS_MIN: number;
706
+ export declare const DIMS_MAX: number;
707
+ export declare function derivedMappingFor(kind: string, driverMapping: unknown): unknown;
708
+ export declare function deriveGeohash(value: unknown, precision: number): unknown;
709
+ export declare function deriveBboxEdge(value: unknown, component: 'w' | 's' | 'e' | 'n'): unknown;
710
+ export declare function deriveVector(member: unknown, dims: number): unknown;
711
+ export declare function storedMemberForm(member: unknown): unknown;
712
+ export declare function derivedValue(column: unknown, member: unknown): unknown;
713
+ export declare function memberAt(doc: unknown, segments: unknown): unknown;
714
+ export declare function registerDeriveFunctions(connection: unknown): unknown;
715
+ export declare function probeVector(value: unknown, dims: number): unknown;
716
+ export declare function columnScore(bytes: unknown, dims: number, probe: unknown): unknown;
717
+ export declare const KNN_MARGIN: number;
718
+ export declare const IDENTITY_CHUNK: number;
719
+ export declare function cutCandidates(rows: unknown[], m: number, margin: number): unknown;
720
+ export declare function identityBatches(identities: unknown[]): unknown;
537
721
 
538
722
  // ————— the job queue (JOBS-FORMAT) —————
539
723
 
@@ -565,21 +749,25 @@ export interface JobCounts {
565
749
 
566
750
  export interface JobWorker {
567
751
  start(): JobWorker;
568
- /** Resolves after in-flight handlers settle. */
569
- stop(): Promise<void>;
752
+ /** Stop claiming, signal in-flight handlers, and wait up to `graceMs`
753
+ * (JOBS-FORMAT §6): the record says whether every loop drained. */
754
+ stop(options?: { graceMs?: number }): Promise<{ drained: boolean; inFlight: number }>;
570
755
  stats(): { claims: number; completions: number; failures: number;
571
- polls: number; wakes: number };
756
+ polls: number; wakes: number; claimErrors: number; inFlight: number };
572
757
  }
573
758
 
574
759
  export interface JobWorkerOptions {
575
- handlers: Record<string,
576
- (payload: unknown, context: { job: JobRecord, checkpointsFor: Function }) => unknown>;
760
+ handlers: Record<string, (payload: unknown, context: {
761
+ job: JobRecord; checkpointsFor: Function; signal: AbortSignal }) => unknown>;
762
+ /** A positive integer; the loops claiming concurrently. */
577
763
  concurrency?: number;
578
764
  pollInterval?: number;
579
765
  leaseMs?: number;
580
766
  owner?: string;
581
767
  backoffBase?: number;
582
768
  backoffCap?: number;
769
+ /** How long `stop()` waits for in-flight handlers by default. */
770
+ stopGraceMs?: number;
583
771
  }
584
772
 
585
773
  export interface JobsApi {
@@ -607,6 +795,7 @@ export interface JobsOptions {
607
795
  pollInterval?: number;
608
796
  backoffBase?: number;
609
797
  backoffCap?: number;
798
+ stopGraceMs?: number;
610
799
  /** Injectable clock and randomness — every test injects both. */
611
800
  now?: () => number;
612
801
  random?: () => number;
@@ -622,6 +811,7 @@ export declare function createDagJobRunner(store: Store, options: {
622
811
  owner?: string;
623
812
  backoffBase?: number;
624
813
  backoffCap?: number;
814
+ stopGraceMs?: number;
625
815
  }): JobWorker;
626
816
 
627
817
  export declare function createJobEngine(options: {
@@ -631,4 +821,8 @@ export declare const JOBS_TABLE: string;
631
821
  export declare const JOB_CHECKPOINTS_TABLE: string;
632
822
  export declare const JOB_DEFAULTS: Readonly<{
633
823
  maxAttempts: number; leaseMs: number; pollInterval: number;
634
- backoffBase: number; backoffCap: number }>;
824
+ backoffBase: number; backoffCap: number; stopGraceMs: number }>;
825
+ /** A total diagnostic string for any value, including ones that fight back. */
826
+ export declare function describeValue(value: unknown): string;
827
+ /** A job result as the queue stores it: JSON text, or the reason it could not be. */
828
+ export declare function serializeResult(value: unknown): unknown;
package/types/node.d.ts CHANGED
@@ -2,9 +2,11 @@
2
2
  import type { Driver } from '@jarenjs/db';
3
3
 
4
4
  export interface NodeOpenOptions {
5
- path?: string;
5
+ /** The busy timeout in milliseconds. */
6
6
  timeout?: number;
7
7
  readOnly?: boolean;
8
+ /** How long work waits for an open transaction (`JD0012` after). */
9
+ queueTimeout?: number;
8
10
  }
9
11
 
10
12
  /** The `node:sqlite` binding; the builtin loads lazily inside open(). */
package/types/typed.d.ts CHANGED
@@ -15,6 +15,8 @@
15
15
  import type {
16
16
  EntityKeyArg, LoadExplanation, SaveReport, Store, StoreCapabilities,
17
17
  StoreStats, Collection, ExecuteOptions, SequenceResult, ValueOrPromise,
18
+ Dialect, ChangeRecord, LiveOptions, LiveQuery, JobsApi, SyncStore,
19
+ EntityScope, RelationEntry, RelationTable,
18
20
  } from '@jarenjs/db';
19
21
 
20
22
  /** The self-referential constraint an interface can satisfy: generated
@@ -36,9 +38,14 @@ export type TypedInclude<E extends MetaMap<E>, M extends EntityMeta> = {
36
38
  readonly [K in keyof M['relations']]?:
37
39
  true
38
40
  | { count: true }
39
- | TypedLoadSpec<E, E[M['relations'][K]['entity'] & keyof E]>;
41
+ | TypedIncludeSpec<E, E[M['relations'][K]['entity'] & keyof E]>;
40
42
  };
41
43
 
44
+ /** An include's clauses: the root's without `after` (a keyset cursor
45
+ * paginates the root alone; an include windows with `skip`/`take`). */
46
+ export type TypedIncludeSpec<E extends MetaMap<E>, M extends EntityMeta> =
47
+ TypedLoadSpecBase & { include?: TypedInclude<E, M> };
48
+
42
49
  export interface TypedLoadSpecBase {
43
50
  /** A query expression over `$it` — its format is the runtime's. */
44
51
  where?: unknown;
@@ -68,15 +75,31 @@ export type Loaded<
68
75
  : Loaded<E, E[M['relations'][K]['entity'] & keyof E], I[K]> | null;
69
76
  } : NonNullable<unknown>);
70
77
 
78
+ /** The relation members `link`/`unlink` take: the many-to-many ones —
79
+ * exactly the relation members the generated INPUT type also carries,
80
+ * since a membership array is writable where a projection is not. */
81
+ export type MembershipMember<M extends EntityMeta> =
82
+ keyof M['relations'] & keyof M['input'] & string;
83
+
84
+ /** What a membership names on the target side: the target's key, or a
85
+ * document carrying it. */
86
+ export type MembershipTarget<E extends MetaMap<E>, M extends EntityMeta, K extends keyof M['relations']> =
87
+ E[M['relations'][K]['entity'] & keyof E]['key'] | M['relations'][K]['doc'];
88
+
71
89
  export interface TypedUntrackedReads<E extends MetaMap<E>, M extends EntityMeta> {
72
90
  get(key: EntityKeyArg): Promise<M['doc'] | undefined>;
73
91
  load<const S extends TypedLoadSpec<E, M>>(spec?: S): Promise<Array<Loaded<E, M, S>>>;
74
92
  }
75
93
 
76
94
  export interface TypedEntitySet<E extends MetaMap<E>, M extends EntityMeta> {
95
+ /** The provider phantom: `from(typed.entity('User'))` infers `User`
96
+ * without a cast. */
97
+ readonly __item?: M['doc'];
77
98
  create(doc: M['input']): Promise<Readonly<M['doc']>>;
78
99
  get(key: EntityKeyArg): Promise<Readonly<M['doc']> | undefined>;
79
- update(key: EntityKeyArg, changes: Partial<M['doc']>): Promise<Readonly<M['doc']>>;
100
+ /** A relation member is a projection, never stored state: `update()`
101
+ * refuses it (`JD2003`), and the type does not offer it. */
102
+ update(key: EntityKeyArg, changes: Partial<Omit<M['doc'], keyof M['relations']>>): Promise<Readonly<M['doc']>>;
80
103
  delete(key: EntityKeyArg): Promise<boolean>;
81
104
  load<const S extends TypedLoadSpec<E, M>>(spec?: S):
82
105
  Promise<Array<Readonly<Loaded<E, M, S>>>>;
@@ -85,18 +108,51 @@ export interface TypedEntitySet<E extends MetaMap<E>, M extends EntityMeta> {
85
108
  put(next: M['doc']): Readonly<M['doc']>;
86
109
  remove(key: EntityKeyArg | M['doc']): void;
87
110
  discard(key: EntityKeyArg | M['doc']): void;
111
+ /** Attach / detach one many-to-many membership through the unit of
112
+ * work (MODEL-FORMAT §11.7): `member` is one of the relation members
113
+ * `create`/`add` also take as an array — never a projection — and
114
+ * `target` the target's key or a document carrying it. */
115
+ link<K extends MembershipMember<M>>(own: M['key'] | M['doc'], member: K, target: MembershipTarget<E, M, K>): void;
116
+ unlink<K extends MembershipMember<M>>(own: M['key'] | M['doc'], member: K, target: MembershipTarget<E, M, K>): void;
88
117
  asNoTracking(): TypedUntrackedReads<E, M>;
118
+ /** The provider contract over this entity's root (MODEL-FORMAT §10.1);
119
+ * the answer is the engine's result shape, value-or-promise (D2). */
120
+ execute<R = unknown>(document: unknown, options?: ExecuteOptions): ValueOrPromise<SequenceResult<R>>;
121
+ explain(document: unknown, options?: ExecuteOptions): Promise<unknown>;
122
+ /** The root expression this set's rows are bound through (`$.<Name>[*]`). */
123
+ readonly root: string;
124
+ /** The identity every entity set of one store shares; it carries every
125
+ * root's relation table. */
126
+ readonly scope: EntityScope;
127
+ /** This entity's relation table (MODEL-FORMAT §10.1): exactly the
128
+ * generated metadata's relation members, as the plain rows a query
129
+ * producer lowers a hop from. */
130
+ readonly relations: Readonly<Record<keyof M['relations'] & string, RelationEntry>>;
89
131
  }
90
132
 
133
+ /** The typed store: every member of `Store` (a typed store is the same
134
+ * object, identity at runtime), with the entity sets typed. */
91
135
  export interface TypedStore<E extends MetaMap<E>> {
92
136
  readonly capabilities: StoreCapabilities;
137
+ readonly dialect: Dialect;
93
138
  stats(): StoreStats;
94
139
  collection<T = unknown>(name: string): Collection<T>;
95
140
  entity<K extends keyof E & string>(name: K): TypedEntitySet<E, E[K]>;
96
141
  execute?<R = unknown>(document: unknown, options?: ExecuteOptions): ValueOrPromise<SequenceResult<R>>;
142
+ explain?(document: unknown, options?: ExecuteOptions): Promise<unknown>;
143
+ /** The entity roots this store-level provider serves (present with entities). */
144
+ readonly roots?: readonly (keyof E & string)[];
145
+ /** The relation tables of every entity, keyed by entity name. */
146
+ readonly relations?: Readonly<Record<keyof E & string, RelationTable>>;
97
147
  saveChanges?(): Promise<SaveReport>;
98
148
  transaction<R>(fn: (store: Store) => R | Promise<R>): Promise<Awaited<R>>;
149
+ observe(fn: (record: ChangeRecord) => void): () => void;
150
+ changesSince?(after: number): Promise<ChangeRecord[]>;
151
+ dataVersion(): Promise<number>;
152
+ live?(document: unknown, options?: LiveOptions): Promise<LiveQuery>;
99
153
  close(options?: { graceMs?: number }): Promise<void>;
154
+ readonly jobs?: JobsApi;
155
+ readonly sync?: SyncStore;
100
156
  }
101
157
 
102
158
  /**
package/types/wasm.d.ts CHANGED
@@ -3,3 +3,10 @@ import type { Driver } from '@jarenjs/db';
3
3
 
4
4
  /** A driver over an injected wasm SQLite handle (possibly async). */
5
5
  export declare function wasmDriver(handle: unknown): Driver;
6
+ /** Adapt an `sqlite3.oo1.DB`-shaped database (a loaded sqlite3 module
7
+ * and one of its database objects) to the raw connection contract. */
8
+ export declare function adaptOo1Database(sqlite3: unknown, db: unknown): unknown;
9
+ /** Build the injected HANDLE for `wasmDriver` from a loaded sqlite3
10
+ * module: `DbClass` picks the database class (default `sqlite3.oo1.DB`;
11
+ * the SAH-pool util's `OpfsSAHPoolDb` for OPFS persistence). */
12
+ export declare function sqlite3Handle(sqlite3: unknown, options?: { DbClass?: unknown }): unknown;