@jarenjs/db 0.56.0 → 0.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/ARCHITECTURE.md +412 -56
  2. package/README.md +600 -57
  3. package/docs/HOSTS.md +269 -0
  4. package/docs/JOBS-FORMAT.md +293 -45
  5. package/docs/LIVE-FORMAT.md +169 -20
  6. package/docs/MIGRATION-FORMAT.md +142 -17
  7. package/docs/MODEL-FORMAT.md +752 -64
  8. package/docs/REPLICATION-FORMAT.md +208 -0
  9. package/package.json +21 -7
  10. package/schemas/jaren-model.draft-07.schema.json +224 -162
  11. package/schemas/jaren-model.schema.json +224 -162
  12. package/schemas/jaren-replication-snapshot.draft-07.schema.json +83 -0
  13. package/schemas/jaren-replication-snapshot.schema.json +83 -0
  14. package/schemas/jaren-replication.draft-07.schema.json +82 -0
  15. package/schemas/jaren-replication.schema.json +82 -0
  16. package/src/algebra.js +227 -9
  17. package/src/backup.js +161 -0
  18. package/src/cancellation.js +48 -0
  19. package/src/capture.js +230 -47
  20. package/src/cli.js +165 -59
  21. package/src/cursor.js +417 -0
  22. package/src/dag-job.js +154 -21
  23. package/src/ddl.js +102 -8
  24. package/src/dialect.js +268 -113
  25. package/src/dialects/expression-read.js +158 -0
  26. package/src/dialects/postgres.js +618 -0
  27. package/src/dialects/rtree-ddl.js +129 -0
  28. package/src/dialects/sqlite.js +244 -11
  29. package/src/document-files.js +311 -0
  30. package/src/document-steps.js +422 -0
  31. package/src/documents.js +335 -0
  32. package/src/driver.js +448 -61
  33. package/src/drivers/bun.js +37 -1
  34. package/src/drivers/indexeddb-snapshot.js +149 -0
  35. package/src/drivers/node-pool.js +11 -0
  36. package/src/drivers/node-worker-endpoint.js +105 -0
  37. package/src/drivers/node-worker.js +204 -0
  38. package/src/drivers/node.js +41 -7
  39. package/src/drivers/postgres.js +331 -0
  40. package/src/drivers/wasm-oo1.js +97 -0
  41. package/src/drivers/wasm-session.js +67 -0
  42. package/src/drivers/wasm.js +17 -83
  43. package/src/drivers/worker-pool.js +183 -0
  44. package/src/drivers/worker-protocol.js +79 -0
  45. package/src/drivers/worker-queue.js +60 -0
  46. package/src/emit.js +339 -48
  47. package/src/entity.js +20 -22
  48. package/src/errors.js +430 -19
  49. package/src/expression.js +284 -0
  50. package/src/graph.js +64 -8
  51. package/src/index.js +48 -17
  52. package/src/introspect.js +583 -0
  53. package/src/jobs.js +843 -107
  54. package/src/json-bytes.js +58 -0
  55. package/src/live-join.js +250 -0
  56. package/src/live-nested.js +120 -0
  57. package/src/live.js +18 -4
  58. package/src/logical-rows.js +90 -0
  59. package/src/maintenance.js +175 -0
  60. package/src/migrate.js +248 -181
  61. package/src/model.js +68 -0
  62. package/src/plan.js +1119 -138
  63. package/src/pragmas.js +314 -0
  64. package/src/profile.js +151 -3
  65. package/src/query.js +1634 -323
  66. package/src/replication-format.js +115 -0
  67. package/src/replication.js +332 -0
  68. package/src/residual.js +17 -0
  69. package/src/series.js +12 -4
  70. package/src/store.js +1567 -273
  71. package/src/tracker.js +203 -29
  72. package/src/udf.js +88 -7
  73. package/types/index.d.ts +1158 -27
  74. package/types/node-pool.d.ts +28 -0
  75. package/types/node-worker.d.ts +54 -0
  76. package/types/node.d.ts +69 -2
  77. package/types/postgres.d.ts +46 -0
  78. package/types/typed.d.ts +27 -4
  79. package/types/wasm.d.ts +14 -0
@@ -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
@@ -11,7 +11,74 @@ export interface NodeOpenOptions {
11
11
 
12
12
  /** The `node:sqlite` binding; the builtin loads lazily inside open(). */
13
13
  export declare function nodeDriver(): Driver;
14
- /** Adapt an already-constructed DatabaseSync-shaped database. */
15
- 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;
16
21
  /** Construct and adapt from a loaded `node:sqlite`-shaped module. */
17
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
@@ -16,7 +16,8 @@ import type {
16
16
  EntityKeyArg, LoadExplanation, SaveReport, Store, StoreCapabilities,
17
17
  StoreStats, Collection, ExecuteOptions, SequenceResult, ValueOrPromise,
18
18
  Dialect, ChangeRecord, LiveOptions, LiveQuery, JobsApi, SyncStore,
19
- EntityScope, RelationEntry, RelationTable,
19
+ EntityScope, RelationEntry, RelationTable, EntityCursorOptions, QueryCursor,
20
+ LoadContinuation, PageOptions, Page, ChangesReader, Replication, TransactionStore,
20
21
  } from '@jarenjs/db';
21
22
 
22
23
  /** The self-referential constraint an interface can satisfy: generated
@@ -44,7 +45,13 @@ export type TypedInclude<E extends MetaMap<E>, M extends EntityMeta> = {
44
45
  /** An include's clauses: the root's without `after` (a keyset cursor
45
46
  * paginates the root alone; an include windows with `skip`/`take`). */
46
47
  export type TypedIncludeSpec<E extends MetaMap<E>, M extends EntityMeta> =
47
- TypedLoadSpecBase & { include?: TypedInclude<E, M> };
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
+ };
48
55
 
49
56
  export interface TypedLoadSpecBase {
50
57
  /** A query expression over `$it` — its format is the runtime's. */
@@ -57,7 +64,9 @@ export interface TypedLoadSpecBase {
57
64
 
58
65
  export type TypedLoadSpec<E extends MetaMap<E>, M extends EntityMeta> =
59
66
  TypedLoadSpecBase & {
60
- 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;
61
70
  include?: TypedInclude<E, M>;
62
71
  };
63
72
 
@@ -103,6 +112,13 @@ export interface TypedEntitySet<E extends MetaMap<E>, M extends EntityMeta> {
103
112
  delete(key: EntityKeyArg): Promise<boolean>;
104
113
  load<const S extends TypedLoadSpec<E, M>>(spec?: S):
105
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>>>>;
106
122
  explainLoad(spec?: TypedLoadSpec<E, M>): LoadExplanation;
107
123
  add(doc: M['input']): Readonly<M['doc']>;
108
124
  put(next: M['doc']): Readonly<M['doc']>;
@@ -118,6 +134,9 @@ export interface TypedEntitySet<E extends MetaMap<E>, M extends EntityMeta> {
118
134
  /** The provider contract over this entity's root (MODEL-FORMAT §10.1);
119
135
  * the answer is the engine's result shape, value-or-promise (D2). */
120
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>;
121
140
  explain(document: unknown, options?: ExecuteOptions): Promise<unknown>;
122
141
  /** The root expression this set's rows are bound through (`$.<Name>[*]`). */
123
142
  readonly root: string;
@@ -145,13 +164,17 @@ export interface TypedStore<E extends MetaMap<E>> {
145
164
  /** The relation tables of every entity, keyed by entity name. */
146
165
  readonly relations?: Readonly<Record<keyof E & string, RelationTable>>;
147
166
  saveChanges?(): Promise<SaveReport>;
148
- transaction<R>(fn: (store: Store) => R | Promise<R>): Promise<Awaited<R>>;
167
+ transaction<R>(fn: (store: TransactionStore) => R | Promise<R>): Promise<Awaited<R>>;
149
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). */
150
171
  changesSince?(after: number): Promise<ChangeRecord[]>;
172
+ readonly changes?: ChangesReader;
151
173
  dataVersion(): Promise<number>;
152
174
  live?(document: unknown, options?: LiveOptions): Promise<LiveQuery>;
153
175
  close(options?: { graceMs?: number }): Promise<void>;
154
176
  readonly jobs?: JobsApi;
177
+ readonly replication?: Replication;
155
178
  readonly sync?: SyncStore;
156
179
  }
157
180
 
package/types/wasm.d.ts CHANGED
@@ -10,3 +10,17 @@ export declare function adaptOo1Database(sqlite3: unknown, db: unknown): unknown
10
10
  * module: `DbClass` picks the database class (default `sqlite3.oo1.DB`;
11
11
  * the SAH-pool util's `OpfsSAHPoolDb` for OPFS persistence). */
12
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;