@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
@@ -0,0 +1,28 @@
1
+ import type { Driver } from './index.js';
2
+ import type { NodeWorkerOptions, WorkerConnection } from './node-worker.js';
3
+
4
+ export interface NodeWorkerPoolOptions {
5
+ readers?: number;
6
+ queueCapacity?: number;
7
+ graceMs?: number;
8
+ worker?: NodeWorkerOptions;
9
+ }
10
+
11
+ /** One writer and bounded read-only WAL workers. Memory uses the writer alone. */
12
+ export declare function nodeWorkerPoolDriver(options?: NodeWorkerPoolOptions): NodeWorkerPoolDriver;
13
+
14
+ export interface PoolMetrics {
15
+ readonly active: number;
16
+ readonly idle: number;
17
+ readonly queued: number;
18
+ readonly waitMs: Readonly<{ p50: number; p95: number }>;
19
+ readonly workers: readonly Readonly<{
20
+ readOnly: boolean; healthy: boolean; active: boolean; generation: number; executions: number;
21
+ }>[];
22
+ }
23
+ export interface NodeWorkerPoolConnection extends WorkerConnection {
24
+ metrics(): PoolMetrics;
25
+ }
26
+ export interface NodeWorkerPoolDriver extends Driver {
27
+ open(path?: string, options?: import('./node.js').NodeOpenOptions): Promise<NodeWorkerPoolConnection>;
28
+ }
@@ -0,0 +1,54 @@
1
+ import type { Driver } from './index.js';
2
+
3
+ export interface NodeWorkerOptions {
4
+ windowRows?: number;
5
+ windowBytes?: number;
6
+ maxPending?: number;
7
+ maxStatements?: number;
8
+ maxCursors?: number;
9
+ allMaxRows?: number;
10
+ allMaxBytes?: number;
11
+ closeTimeoutMs?: number;
12
+ startupTimeoutMs?: number;
13
+ }
14
+
15
+ /** One worker per connection; SQLite and its iterators live off the caller thread. */
16
+ export declare function nodeWorkerDriver(options?: NodeWorkerOptions): NodeWorkerDriver;
17
+
18
+ export interface WorkerMetrics {
19
+ readonly frames: number;
20
+ readonly rows: number;
21
+ readonly maxFrameRows: number;
22
+ readonly maxFrameBytes: number;
23
+ readonly maxPending: number;
24
+ readonly pending: number;
25
+ readonly generation: number;
26
+ readonly healthy: boolean;
27
+ }
28
+ export interface WorkerStatement {
29
+ run(params?: readonly unknown[]): Promise<unknown>;
30
+ get(params?: readonly unknown[]): Promise<Record<string, unknown> | undefined>;
31
+ all(params?: readonly unknown[]): Promise<Record<string, unknown>[]>;
32
+ iterate(params?: readonly unknown[]): Promise<AsyncIterableIterator<Record<string, unknown>>>;
33
+ }
34
+ /** The ordinary Connection operations; transactions pass their owning scope. */
35
+ export interface WorkerConnectionScope {
36
+ exec(sql: string): Promise<unknown>;
37
+ prepare(sql: string, metadata?: { readOnly?: boolean; ephemeral?: boolean }): WorkerStatement | Promise<WorkerStatement>;
38
+ transaction<T>(body: (scope: WorkerConnectionScope) => T | Promise<T>): Promise<T>;
39
+ }
40
+ export interface WorkerConnection extends WorkerConnectionScope {
41
+ transaction<T>(body: (scope: WorkerConnectionScope) => T | Promise<T>, signal?: AbortSignal, mode?: 'deferred' | 'immediate'): Promise<T>;
42
+ readonly synchronous: false;
43
+ readonly capabilities: Readonly<Record<string, unknown>>;
44
+ close(): Promise<void>;
45
+ }
46
+ export interface NodeWorkerConnection extends WorkerConnection {
47
+ readonly generation: number;
48
+ metrics(): WorkerMetrics;
49
+ /** Fences this handle permanently; returns a newly opened connection. */
50
+ restart(): Promise<NodeWorkerConnection>;
51
+ }
52
+ export interface NodeWorkerDriver extends Driver {
53
+ open(path?: string, options?: import('./node.js').NodeOpenOptions): Promise<NodeWorkerConnection>;
54
+ }
package/types/node.d.ts CHANGED
@@ -2,14 +2,83 @@
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(). */
11
13
  export declare function nodeDriver(): Driver;
12
- /** Adapt an already-constructed DatabaseSync-shaped database. */
13
- export declare function adaptNodeDatabase(db: unknown): unknown;
14
+ /** Adapt an already-constructed DatabaseSync-shaped database. `backup`
15
+ * is the online-backup primitive triple (`copy`, `rename`, `remove`);
16
+ * the connection declares the capability exactly when it is given. */
17
+ export declare function adaptNodeDatabase(db: unknown, options?: {
18
+ queueTimeout?: number;
19
+ backup?: { copy: Function; rename: Function; remove: Function };
20
+ }): unknown;
14
21
  /** Construct and adapt from a loaded `node:sqlite`-shaped module. */
15
22
  export declare function fromNodeModule(mod: unknown, path: string, options?: NodeOpenOptions): unknown;
23
+
24
+ // ————— documents on a filesystem —————
25
+
26
+ /** A byte source: a path, or anything that yields chunks — a
27
+ * `node:fs` read stream and `process.stdin` both are. Spelled
28
+ * structurally so these declarations need no ambient Node types. */
29
+ export type DocumentByteSource = string | AsyncIterable<Uint8Array>;
30
+
31
+ /** A byte sink: anything with `node:stream`'s callback `write`. */
32
+ export interface DocumentByteSink {
33
+ write(chunk: string, callback: (error?: Error | null) => void): unknown;
34
+ }
35
+
36
+ /** The document encodings a file may carry. */
37
+ export declare const DOCUMENT_FORMATS: readonly ['json', 'jsonl'];
38
+
39
+ /** The encoding a path declares by its extension: `.jsonl`/`.ndjson`
40
+ * are line-delimited, everything else is one JSON array. */
41
+ export declare function formatOf(file: string): 'json' | 'jsonl';
42
+
43
+ /** The documents of a top-level JSON array, scanned structurally so the
44
+ * array is never held whole. Refuses a root that is not an array
45
+ * (`JD0024`). */
46
+ export declare function readJsonDocuments(
47
+ source: DocumentByteSource,
48
+ ): AsyncGenerator<unknown>;
49
+
50
+ /** The documents of a JSONL source, one line at a time. */
51
+ export declare function readJsonlDocuments(
52
+ source: DocumentByteSource,
53
+ ): AsyncGenerator<unknown>;
54
+
55
+ /** The documents of a file or stream in the named encoding. */
56
+ export declare function readDocuments(
57
+ source: DocumentByteSource, format: 'json' | 'jsonl',
58
+ ): AsyncGenerator<unknown>;
59
+
60
+ /** A sink that publishes whole or not at all. */
61
+ export interface DocumentTarget {
62
+ /** The sibling file being filled, or null for a sink with no file. */
63
+ readonly temporary: string | null;
64
+ write(document: unknown): Promise<void>;
65
+ /** Flush, rename over the target, and answer what was written. */
66
+ commit(): Promise<{ bytes: number; documents: number }>;
67
+ /** Remove the temporary; the target keeps the bytes it had. */
68
+ abort(): Promise<void>;
69
+ }
70
+
71
+ /** Replace a file whole: a sibling temporary is renamed over the target
72
+ * on `commit`, and removed on `abort`, so a failed run leaves the
73
+ * original byte for byte. */
74
+ export declare function openAtomicTarget(
75
+ target: string, format: 'json' | 'jsonl',
76
+ ): Promise<DocumentTarget>;
77
+
78
+ /** Write to an open stream; `abort` cannot take back what has left. */
79
+ export declare function openStreamTarget(
80
+ stream: DocumentByteSink, format: 'json' | 'jsonl',
81
+ ): DocumentTarget;
82
+
83
+ /** Validate everything and write nothing. */
84
+ export declare function openNullTarget(): DocumentTarget;
@@ -0,0 +1,46 @@
1
+ /** Hand-authored declarations for @jarenjs/db/postgres (strategy 1). */
2
+ import type { Dialect, Driver } from '@jarenjs/db';
3
+
4
+ /** The byte length PostgreSQL truncates an identifier at; the dialect
5
+ * refuses a longer one rather than letting two names silently become
6
+ * one. */
7
+ export declare const IDENTIFIER_BYTES: number;
8
+ /** The minimum server this store accepts, as `server_version_num`
9
+ * spells it. */
10
+ export declare const POSTGRES_FLOOR: number;
11
+
12
+ /**
13
+ * The PostgreSQL 16+ dialect. Pure text: it imports no client, so this
14
+ * subpath resolves and type-checks with nothing installed.
15
+ */
16
+ export declare function postgresDialect(options?: {
17
+ /** The one schema every catalog statement looks in. Unset, the
18
+ * connection's own search path -- what a disposable per-run schema
19
+ * needs. */
20
+ searchPath?: string;
21
+ }): Dialect;
22
+
23
+ /** One acquired client, adapted to the raw binding contract. */
24
+ export declare function adaptPostgresClient(client: {
25
+ query: Function;
26
+ release?: Function;
27
+ }, options?: { onClose?: () => unknown }): unknown;
28
+
29
+ /** The probe: the server's version floor, and every capability this
30
+ * engine does and does not have. */
31
+ export declare function postgresProbe(raw: unknown): unknown;
32
+
33
+ /**
34
+ * The driver over an injected connection source. A `pg.Pool` satisfies
35
+ * `{ connect() }` as it stands; a single client becomes one with
36
+ * `{ connect: () => client }`. One client is acquired at open, held for
37
+ * the store's life, and released exactly once at close.
38
+ */
39
+ export declare function postgresDriver(source: {
40
+ connect: Function;
41
+ }, options?: {
42
+ /** The schema the store lives in: set on the connection AND given to
43
+ * the dialect, so the DDL and the catalog reads agree. */
44
+ schema?: string;
45
+ queueTimeout?: number;
46
+ }): Driver;
package/types/typed.d.ts CHANGED
@@ -15,6 +15,9 @@
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, EntityCursorOptions, QueryCursor,
20
+ LoadContinuation, PageOptions, Page, ChangesReader,
18
21
  } from '@jarenjs/db';
19
22
 
20
23
  /** The self-referential constraint an interface can satisfy: generated
@@ -36,9 +39,20 @@ export type TypedInclude<E extends MetaMap<E>, M extends EntityMeta> = {
36
39
  readonly [K in keyof M['relations']]?:
37
40
  true
38
41
  | { count: true }
39
- | TypedLoadSpec<E, E[M['relations'][K]['entity'] & keyof E]>;
42
+ | TypedIncludeSpec<E, E[M['relations'][K]['entity'] & keyof E]>;
40
43
  };
41
44
 
45
+ /** An include's clauses: the root's without `after` (a keyset cursor
46
+ * paginates the root alone; an include windows with `skip`/`take`). */
47
+ export type TypedIncludeSpec<E extends MetaMap<E>, M extends EntityMeta> =
48
+ TypedLoadSpecBase & {
49
+ /** The per-root bounds (MODEL-FORMAT §10.4); `Infinity` spells the
50
+ * unbounded case. Crossing one is `JD2073`. */
51
+ maxRows?: number;
52
+ maxBytes?: number;
53
+ include?: TypedInclude<E, M>;
54
+ };
55
+
42
56
  export interface TypedLoadSpecBase {
43
57
  /** A query expression over `$it` — its format is the runtime's. */
44
58
  where?: unknown;
@@ -50,7 +64,9 @@ export interface TypedLoadSpecBase {
50
64
 
51
65
  export type TypedLoadSpec<E extends MetaMap<E>, M extends EntityMeta> =
52
66
  TypedLoadSpecBase & {
53
- after?: M['key'] extends string | number ? M['key'] : never;
67
+ /** The keyset cursor: a page's structural continuation, or the
68
+ * single-column form over the key. */
69
+ after?: (M['key'] extends string | number ? M['key'] : never) | LoadContinuation;
54
70
  include?: TypedInclude<E, M>;
55
71
  };
56
72
 
@@ -68,35 +84,97 @@ export type Loaded<
68
84
  : Loaded<E, E[M['relations'][K]['entity'] & keyof E], I[K]> | null;
69
85
  } : NonNullable<unknown>);
70
86
 
87
+ /** The relation members `link`/`unlink` take: the many-to-many ones —
88
+ * exactly the relation members the generated INPUT type also carries,
89
+ * since a membership array is writable where a projection is not. */
90
+ export type MembershipMember<M extends EntityMeta> =
91
+ keyof M['relations'] & keyof M['input'] & string;
92
+
93
+ /** What a membership names on the target side: the target's key, or a
94
+ * document carrying it. */
95
+ export type MembershipTarget<E extends MetaMap<E>, M extends EntityMeta, K extends keyof M['relations']> =
96
+ E[M['relations'][K]['entity'] & keyof E]['key'] | M['relations'][K]['doc'];
97
+
71
98
  export interface TypedUntrackedReads<E extends MetaMap<E>, M extends EntityMeta> {
72
99
  get(key: EntityKeyArg): Promise<M['doc'] | undefined>;
73
100
  load<const S extends TypedLoadSpec<E, M>>(spec?: S): Promise<Array<Loaded<E, M, S>>>;
74
101
  }
75
102
 
76
103
  export interface TypedEntitySet<E extends MetaMap<E>, M extends EntityMeta> {
104
+ /** The provider phantom: `from(typed.entity('User'))` infers `User`
105
+ * without a cast. */
106
+ readonly __item?: M['doc'];
77
107
  create(doc: M['input']): Promise<Readonly<M['doc']>>;
78
108
  get(key: EntityKeyArg): Promise<Readonly<M['doc']> | undefined>;
79
- update(key: EntityKeyArg, changes: Partial<M['doc']>): Promise<Readonly<M['doc']>>;
109
+ /** A relation member is a projection, never stored state: `update()`
110
+ * refuses it (`JD2003`), and the type does not offer it. */
111
+ update(key: EntityKeyArg, changes: Partial<Omit<M['doc'], keyof M['relations']>>): Promise<Readonly<M['doc']>>;
80
112
  delete(key: EntityKeyArg): Promise<boolean>;
81
113
  load<const S extends TypedLoadSpec<E, M>>(spec?: S):
82
114
  Promise<Array<Readonly<Loaded<E, M, S>>>>;
115
+ /** The graph cursor: one root graph per pull, typed by the includes;
116
+ * untracked unless `tracking: true`. */
117
+ loadCursor<const S extends TypedLoadSpec<E, M>>(spec?: S, options?: EntityCursorOptions):
118
+ QueryCursor<Readonly<Loaded<E, M, S>>>;
119
+ /** One bounded page over the composite keyset, typed by the includes. */
120
+ page<const S extends TypedLoadSpec<E, M>>(spec?: S, options?: PageOptions):
121
+ Promise<Page<Readonly<Loaded<E, M, S>>>>;
83
122
  explainLoad(spec?: TypedLoadSpec<E, M>): LoadExplanation;
84
123
  add(doc: M['input']): Readonly<M['doc']>;
85
124
  put(next: M['doc']): Readonly<M['doc']>;
86
125
  remove(key: EntityKeyArg | M['doc']): void;
87
126
  discard(key: EntityKeyArg | M['doc']): void;
127
+ /** Attach / detach one many-to-many membership through the unit of
128
+ * work (MODEL-FORMAT §11.7): `member` is one of the relation members
129
+ * `create`/`add` also take as an array — never a projection — and
130
+ * `target` the target's key or a document carrying it. */
131
+ link<K extends MembershipMember<M>>(own: M['key'] | M['doc'], member: K, target: MembershipTarget<E, M, K>): void;
132
+ unlink<K extends MembershipMember<M>>(own: M['key'] | M['doc'], member: K, target: MembershipTarget<E, M, K>): void;
88
133
  asNoTracking(): TypedUntrackedReads<E, M>;
134
+ /** The provider contract over this entity's root (MODEL-FORMAT §10.1);
135
+ * the answer is the engine's result shape, value-or-promise (D2). */
136
+ execute<R = unknown>(document: unknown, options?: ExecuteOptions): ValueOrPromise<SequenceResult<R>>;
137
+ /** The same document as an item cursor: one row per pull, the
138
+ * statement released on `return()`; untracked unless `tracking: true`. */
139
+ cursor<R = M['doc']>(document: unknown, options?: EntityCursorOptions): QueryCursor<R>;
140
+ explain(document: unknown, options?: ExecuteOptions): Promise<unknown>;
141
+ /** The root expression this set's rows are bound through (`$.<Name>[*]`). */
142
+ readonly root: string;
143
+ /** The identity every entity set of one store shares; it carries every
144
+ * root's relation table. */
145
+ readonly scope: EntityScope;
146
+ /** This entity's relation table (MODEL-FORMAT §10.1): exactly the
147
+ * generated metadata's relation members, as the plain rows a query
148
+ * producer lowers a hop from. */
149
+ readonly relations: Readonly<Record<keyof M['relations'] & string, RelationEntry>>;
89
150
  }
90
151
 
152
+ /** The typed store: every member of `Store` (a typed store is the same
153
+ * object, identity at runtime), with the entity sets typed. */
91
154
  export interface TypedStore<E extends MetaMap<E>> {
92
155
  readonly capabilities: StoreCapabilities;
156
+ readonly dialect: Dialect;
93
157
  stats(): StoreStats;
94
158
  collection<T = unknown>(name: string): Collection<T>;
95
159
  entity<K extends keyof E & string>(name: K): TypedEntitySet<E, E[K]>;
96
160
  execute?<R = unknown>(document: unknown, options?: ExecuteOptions): ValueOrPromise<SequenceResult<R>>;
161
+ explain?(document: unknown, options?: ExecuteOptions): Promise<unknown>;
162
+ /** The entity roots this store-level provider serves (present with entities). */
163
+ readonly roots?: readonly (keyof E & string)[];
164
+ /** The relation tables of every entity, keyed by entity name. */
165
+ readonly relations?: Readonly<Record<keyof E & string, RelationTable>>;
97
166
  saveChanges?(): Promise<SaveReport>;
98
167
  transaction<R>(fn: (store: Store) => R | Promise<R>): Promise<Awaited<R>>;
168
+ observe(fn: (record: ChangeRecord) => void): () => void;
169
+ /** Unbounded, and unsafe for a reconnecting consumer: `changes.page()`
170
+ * is the supported path (LIVE-FORMAT §5). */
171
+ changesSince?(after: number): Promise<ChangeRecord[]>;
172
+ readonly changes?: ChangesReader;
173
+ dataVersion(): Promise<number>;
174
+ live?(document: unknown, options?: LiveOptions): Promise<LiveQuery>;
99
175
  close(options?: { graceMs?: number }): Promise<void>;
176
+ readonly jobs?: JobsApi;
177
+ readonly sync?: SyncStore;
100
178
  }
101
179
 
102
180
  /**
package/types/wasm.d.ts CHANGED
@@ -3,3 +3,24 @@ 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;
13
+
14
+ export interface SnapshotRecord { readonly revision: number; readonly bytes: Uint8Array }
15
+ export interface SnapshotStorage {
16
+ read(key: string): Promise<SnapshotRecord | null>;
17
+ write(key: string, bytes: Uint8Array, revision: number): Promise<number>;
18
+ remove(key: string): Promise<number>;
19
+ close(): void;
20
+ }
21
+ /** Atomic version swaps; resolves writes only on IndexedDB transaction completion. */
22
+ export declare function openSnapshotStorage(factory: unknown, name: string): Promise<SnapshotStorage>;
23
+ /** Async SQLite binding with bounded, versioned IndexedDB snapshot durability. */
24
+ export declare function indexedDbSnapshotHandle(sqlite3: unknown, options?: {
25
+ name?: string; indexedDB?: unknown; maxBytes?: number;
26
+ }): unknown;
@@ -1,230 +0,0 @@
1
- /**
2
- * @file The Plan algebra: the dialect-neutral middle stage between the
3
- * engine's AST and a dialect's SQL. A plan is a plain JSON value —
4
- * inspectable, golden-testable without a database — and it carries NO
5
- * SQL text: every string in a plan is a member name, a type tag, an
6
- * external name or a reason sentence, never a fragment of any query
7
- * language. `assertNoSqlText` is the tripwire the tests run over every
8
- * golden.
9
- *
10
- * One plan shape covers this version: a guarded selection over ONE
11
- * collection with optional ordering, window, aggregate and a
12
- * whole-document projection — or, instead of an ordering and a window,
13
- * a k-nearest RANK the engine finishes over the rows the plan fetches,
14
- * or, instead of a projection, a fixed-width temporal BUCKET the plan
15
- * groups and aggregates itself. Constructs beyond it are residuals by
16
- * design (see ARCHITECTURE.md's deliberate-residual table).
17
- */
18
- /** The plan format version, carried on every plan. */
19
- export declare const PLAN_VERSION = 2;
20
- export type PlanRef = {
21
- segments: ({
22
- name: string;
23
- } | {
24
- index: number;
25
- })[];
26
- type: string;
27
- column: string | null;
28
- };
29
- export type PlanOperand = {
30
- lit: unknown;
31
- } | {
32
- ext: string;
33
- };
34
- export type PlanPredicate = ({
35
- p: 'and' | 'or';
36
- items: PlanPredicate[];
37
- } | {
38
- p: 'not';
39
- item: PlanPredicate;
40
- } | {
41
- p: 'cmp';
42
- op: 'eq' | 'ne' | 'lt' | 'le' | 'gt' | 'ge';
43
- ref: PlanRef;
44
- operand: PlanOperand;
45
- } | {
46
- p: 'typeIs';
47
- ref: PlanRef;
48
- types: string[];
49
- positive: boolean;
50
- } | {
51
- p: 'strop';
52
- kind: 'starts' | 'ends' | 'contains';
53
- ref: PlanRef;
54
- operand: PlanOperand;
55
- } | {
56
- p: 'const';
57
- value: boolean;
58
- } | {
59
- p: 'udf';
60
- name: string;
61
- key: string;
62
- } | {
63
- p: 'bboxOverlap';
64
- columns: {
65
- w: string;
66
- s: string;
67
- e: string;
68
- n: string;
69
- };
70
- probe: {
71
- box: number[];
72
- } | {
73
- ext: string;
74
- };
75
- } | {
76
- p: 'cellIn';
77
- column: string;
78
- cells: string[];
79
- } | {
80
- p: 'cellPrefix';
81
- column: string;
82
- prefix: string;
83
- });
84
- export type PlanOrderTerm = {
85
- ref: PlanRef;
86
- desc: boolean;
87
- emptyGreatest: boolean;
88
- };
89
- export type PlanBucket = {
90
- ref: PlanRef;
91
- every: number;
92
- origin: number;
93
- as: string;
94
- order: 'asc' | 'desc' | 'first-seen';
95
- aggregates: {
96
- fn: 'rows' | 'sum' | 'avg' | 'min' | 'max';
97
- ref: PlanRef | null;
98
- as: string;
99
- empty: 'null' | 'zero' | 'omit';
100
- }[];
101
- };
102
- export type PlanRank = {
103
- column: string;
104
- dims: number;
105
- probe: {
106
- lit: number[];
107
- } | {
108
- ext: string;
109
- };
110
- offset: number;
111
- limit: number;
112
- margin: number;
113
- };
114
- export type Plan = {
115
- planVersion: number;
116
- alg: 'select';
117
- collection: string;
118
- filter: PlanPredicate | null;
119
- order: PlanOrderTerm[] | null;
120
- window: {
121
- offset: number;
122
- limit: number | null;
123
- } | null;
124
- rank: PlanRank | null;
125
- bucket: PlanBucket | null;
126
- aggregate: {
127
- fn: 'count' | 'sum' | 'avg' | 'min' | 'max';
128
- ref: PlanRef | null;
129
- } | null;
130
- project: 'document';
131
- };
132
- /**
133
- * @typedef {{ segments: ({ name: string } | { index: number })[],
134
- * type: string, column: string | null }} PlanRef
135
- * A typed reference into the stored document: `type` is the
136
- * schema-declared type or `'unknown'`; `column` is the generated
137
- * column name when the collection indexes this path.
138
- *
139
- * @typedef {{ lit: unknown } | { ext: string }} PlanOperand
140
- *
141
- * @typedef {(
142
- * { p: 'and' | 'or', items: PlanPredicate[] } |
143
- * { p: 'not', item: PlanPredicate } |
144
- * { p: 'cmp', op: 'eq' | 'ne' | 'lt' | 'le' | 'gt' | 'ge',
145
- * ref: PlanRef, operand: PlanOperand } |
146
- * { p: 'typeIs', ref: PlanRef, types: string[], positive: boolean } |
147
- * { p: 'strop', kind: 'starts' | 'ends' | 'contains',
148
- * ref: PlanRef, operand: PlanOperand } |
149
- * { p: 'const', value: boolean } |
150
- * { p: 'udf', name: string, key: string } |
151
- * { p: 'bboxOverlap', columns: { w: string, s: string, e: string,
152
- * n: string }, probe: { box: number[] } | { ext: string } } |
153
- * { p: 'cellIn', column: string, cells: string[] } |
154
- * { p: 'cellPrefix', column: string, prefix: string }
155
- * )} PlanPredicate
156
- * The last three are the SPATIAL forms: predicates over the derived
157
- * index columns a model declares, which a spatial conjunct either
158
- * translates to exactly or is proven to IMPLY. `bboxOverlap` is true
159
- * when the row's stored box meets the probe's (touching edges count,
160
- * as the kernel's `bboxIntersects` does); `cellIn` when the row's
161
- * cell is one of the listed ones (the nine-cell neighbourhood, or a
162
- * single whole cell); `cellPrefix` when it begins with a shorter one.
163
- * None carries a `json_type` guard — the derived column IS the value
164
- * — but each is TOTAL through its own `IS NOT NULL`, so a row with no
165
- * box or no cell answers FALSE rather than SQL's NULL and negation
166
- * still composes classically.
167
- *
168
- * @typedef {{ ref: PlanRef, desc: boolean, emptyGreatest: boolean }} PlanOrderTerm
169
- *
170
- * @typedef {{ ref: PlanRef, every: number, origin: number, as: string,
171
- * order: 'asc' | 'desc' | 'first-seen',
172
- * aggregates: { fn: 'rows' | 'sum' | 'avg' | 'min' | 'max',
173
- * ref: PlanRef | null, as: string,
174
- * empty: 'null' | 'zero' | 'omit' }[] }} PlanBucket
175
- * The fixed-width temporal GROUP BY: the instant column, the ladder's
176
- * width and anchor in epoch milliseconds, the name the bucket's start
177
- * is answered under, how the groups are ordered, and one aggregate
178
- * per answered member. `rows` is `COUNT(*)` — the D5 count of SOURCE
179
- * rows, duplicates and measured gaps included — and the four value
180
- * aggregates skip a `NULL` reading exactly as the kernel skips a
181
- * `null` one. `first-seen` order is the group's earliest row identity,
182
- * which is the engine's own "order of first appearance" (§6.5).
183
- * A plan carrying a bucket carries no `aggregate` and no `rank`.
184
- *
185
- * @typedef {{ column: string, dims: number,
186
- * probe: { lit: number[] } | { ext: string },
187
- * offset: number, limit: number, margin: number }} PlanRank
188
- * The k-nearest stage: the packed vector column the ranking reads,
189
- * its declared width, the probe (a plan-time literal vector, or the
190
- * external that carries one at call time), the window the ENGINE
191
- * will apply, and the inclusive score margin of the candidate cut.
192
- * The column cuts — every row whose column score is within `margin`
193
- * of the `offset + limit`-th best is a candidate — and the engine
194
- * decides: the original document, its whole ordering and window
195
- * included, runs over the candidates' documents. A plan carrying a
196
- * rank carries no order and no window of its own: nothing in SQL
197
- * orders or limits the fetch.
198
- *
199
- * @typedef {{
200
- * planVersion: number,
201
- * alg: 'select',
202
- * collection: string,
203
- * filter: PlanPredicate | null,
204
- * order: PlanOrderTerm[] | null,
205
- * window: { offset: number, limit: number | null } | null,
206
- * rank: PlanRank | null,
207
- * bucket: PlanBucket | null,
208
- * aggregate: { fn: 'count' | 'sum' | 'avg' | 'min' | 'max',
209
- * ref: PlanRef | null } | null,
210
- * project: 'document',
211
- * }} Plan
212
- */
213
- /**
214
- * A fresh select plan over one collection.
215
- * @param {string} collection
216
- * @returns {Plan}
217
- */
218
- export declare function selectPlan(collection: string): Plan;
219
- /**
220
- * Conjoin a predicate onto a plan's filter.
221
- * @param {PlanPredicate | null} filter
222
- * @param {PlanPredicate} predicate
223
- * @returns {PlanPredicate}
224
- */
225
- export declare function conjoin(filter: PlanPredicate | null, predicate: PlanPredicate): PlanPredicate;
226
- /**
227
- * Throw when a plan value carries anything that smells like SQL.
228
- * @param {unknown} plan
229
- */
230
- export declare function assertNoSqlText(plan: unknown): void;
@@ -1,49 +0,0 @@
1
- /**
2
- * @file The app binding for live queries (LIVE-FORMAT §10): GENERATED
3
- * documents plus a handler factory — the `fsmToApp` precedent. The db
4
- * package never imports `@jarenjs/app`; the app document declares a
5
- * subscription (`APP-FORMAT §5.3`) whose registered handler is
6
- * `createLiveSubscription(store)`, and a two-line action whose whole
7
- * body is `{ patch: '$payload' }` — the handler prefixes every op
8
- * with the declared state path, so the app loop applies live patches
9
- * with the machinery it already has.
10
- */
11
- /**
12
- * Prefix every op path in a live patch with the state slot.
13
- * @param {any[]} patch
14
- * @param {string} statePath - JSON Pointer to the slot holding the
15
- * live result document
16
- */
17
- export declare function prefixLivePatch(patch: any[], statePath: string): any[];
18
- /**
19
- * The generated documents (§10): a subscription entry and the
20
- * patch-forwarding action, both plain data for the app document.
21
- * @param {{ run?: string, action?: string, statePath: string,
22
- * collection?: string, query: any, externals?: any, mode?: string,
23
- * when?: any }} options
24
- * @returns {{ subscription: any, actions: any }}
25
- */
26
- export declare function liveAppBinding(options: {
27
- run?: string;
28
- action?: string;
29
- statePath: string;
30
- collection?: string;
31
- query: any;
32
- externals?: any;
33
- mode?: string;
34
- when?: any;
35
- }): {
36
- subscription: any;
37
- actions: any;
38
- };
39
- /**
40
- * The subscription handler factory: registers the live query when the
41
- * subscription starts, dispatches ONE initializing patch (a `replace`
42
- * of the whole slot), forwards each emission prefixed, and closes on
43
- * cleanup. An emission error surfaces as a dispatch of
44
- * `<action>/error` so the app can render it — silence is not an
45
- * option the format allows.
46
- * @param {any} store - an open store with capture
47
- * @returns {(props: any, dispatch: Function) => Function}
48
- */
49
- export declare function createLiveSubscription(store: any): (props: any, dispatch: Function) => Function;