@jarenjs/db 0.34.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.
- package/ARCHITECTURE.md +397 -0
- package/README.md +218 -0
- package/dist/types/algebra.d.ts +133 -0
- package/dist/types/app.d.ts +49 -0
- package/dist/types/capture.d.ts +85 -0
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/dag-job.d.ts +40 -0
- package/dist/types/ddl.d.ts +170 -0
- package/dist/types/dialect.d.ts +130 -0
- package/dist/types/dialects/sqlite.d.ts +9 -0
- package/dist/types/driver.d.ts +128 -0
- package/dist/types/drivers/bun.d.ts +47 -0
- package/dist/types/drivers/node.d.ts +37 -0
- package/dist/types/drivers/wasm.d.ts +65 -0
- package/dist/types/emit-model.d.ts +44 -0
- package/dist/types/emit.d.ts +72 -0
- package/dist/types/entity.d.ts +23 -0
- package/dist/types/errors.d.ts +165 -0
- package/dist/types/graph.d.ts +28 -0
- package/dist/types/index.d.ts +35 -0
- package/dist/types/jobs.d.ts +134 -0
- package/dist/types/live.d.ts +62 -0
- package/dist/types/migrate.d.ts +163 -0
- package/dist/types/model.d.ts +36 -0
- package/dist/types/patch-sql.d.ts +37 -0
- package/dist/types/plan.d.ts +119 -0
- package/dist/types/profile.d.ts +80 -0
- package/dist/types/query.d.ts +100 -0
- package/dist/types/residual.d.ts +50 -0
- package/dist/types/store.d.ts +53 -0
- package/dist/types/tracker.d.ts +43 -0
- package/dist/types/typed.d.ts +15 -0
- package/dist/types/types.d.ts +26 -0
- package/dist/types/udf.d.ts +70 -0
- package/dist/types/window.d.ts +52 -0
- package/docs/JOBS-FORMAT.md +218 -0
- package/docs/LIVE-FORMAT.md +348 -0
- package/docs/MIGRATION-FORMAT.md +302 -0
- package/docs/MODEL-FORMAT.md +928 -0
- package/package.json +81 -0
- package/schemas/jaren-migration.draft-07.schema.json +144 -0
- package/schemas/jaren-migration.schema.json +144 -0
- package/schemas/jaren-model.draft-07.schema.json +149 -0
- package/schemas/jaren-model.schema.json +149 -0
- package/src/algebra.js +105 -0
- package/src/app.js +108 -0
- package/src/capture.js +584 -0
- package/src/cli.js +264 -0
- package/src/dag-job.js +86 -0
- package/src/ddl.js +588 -0
- package/src/dialect.js +297 -0
- package/src/dialects/sqlite.js +175 -0
- package/src/driver.js +419 -0
- package/src/drivers/bun.js +101 -0
- package/src/drivers/node.js +93 -0
- package/src/drivers/wasm.js +178 -0
- package/src/emit-model.js +208 -0
- package/src/emit.js +393 -0
- package/src/entity.js +367 -0
- package/src/errors.js +173 -0
- package/src/graph.js +101 -0
- package/src/index.js +64 -0
- package/src/jobs.js +507 -0
- package/src/live.js +899 -0
- package/src/migrate.js +1411 -0
- package/src/model.js +476 -0
- package/src/patch-sql.js +150 -0
- package/src/plan.js +1038 -0
- package/src/profile.js +131 -0
- package/src/query.js +1010 -0
- package/src/residual.js +91 -0
- package/src/store.js +1422 -0
- package/src/tracker.js +776 -0
- package/src/typed.js +19 -0
- package/src/types.js +36 -0
- package/src/udf.js +132 -0
- package/src/window.js +125 -0
- package/types/app.d.ts +36 -0
- package/types/bun.d.ts +9 -0
- package/types/index.d.ts +592 -0
- package/types/node.d.ts +15 -0
- package/types/typed.d.ts +108 -0
- package/types/wasm.d.ts +5 -0
|
@@ -0,0 +1,133 @@
|
|
|
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. Constructs beyond it are residuals by
|
|
13
|
+
* design (see ARCHITECTURE.md's deliberate-residual table).
|
|
14
|
+
*/
|
|
15
|
+
/** The plan format version, carried on every plan. */
|
|
16
|
+
export declare const PLAN_VERSION = 1;
|
|
17
|
+
export type PlanRef = {
|
|
18
|
+
segments: ({
|
|
19
|
+
name: string;
|
|
20
|
+
} | {
|
|
21
|
+
index: number;
|
|
22
|
+
})[];
|
|
23
|
+
type: string;
|
|
24
|
+
column: string | null;
|
|
25
|
+
};
|
|
26
|
+
export type PlanOperand = {
|
|
27
|
+
lit: unknown;
|
|
28
|
+
} | {
|
|
29
|
+
ext: string;
|
|
30
|
+
};
|
|
31
|
+
export type PlanPredicate = ({
|
|
32
|
+
p: 'and' | 'or';
|
|
33
|
+
items: PlanPredicate[];
|
|
34
|
+
} | {
|
|
35
|
+
p: 'not';
|
|
36
|
+
item: PlanPredicate;
|
|
37
|
+
} | {
|
|
38
|
+
p: 'cmp';
|
|
39
|
+
op: 'eq' | 'ne' | 'lt' | 'le' | 'gt' | 'ge';
|
|
40
|
+
ref: PlanRef;
|
|
41
|
+
operand: PlanOperand;
|
|
42
|
+
} | {
|
|
43
|
+
p: 'typeIs';
|
|
44
|
+
ref: PlanRef;
|
|
45
|
+
types: string[];
|
|
46
|
+
positive: boolean;
|
|
47
|
+
} | {
|
|
48
|
+
p: 'strop';
|
|
49
|
+
kind: 'starts' | 'ends' | 'contains';
|
|
50
|
+
ref: PlanRef;
|
|
51
|
+
operand: PlanOperand;
|
|
52
|
+
} | {
|
|
53
|
+
p: 'const';
|
|
54
|
+
value: boolean;
|
|
55
|
+
} | {
|
|
56
|
+
p: 'udf';
|
|
57
|
+
name: string;
|
|
58
|
+
key: string;
|
|
59
|
+
});
|
|
60
|
+
export type PlanOrderTerm = {
|
|
61
|
+
ref: PlanRef;
|
|
62
|
+
desc: boolean;
|
|
63
|
+
emptyGreatest: boolean;
|
|
64
|
+
};
|
|
65
|
+
export type Plan = {
|
|
66
|
+
planVersion: number;
|
|
67
|
+
alg: 'select';
|
|
68
|
+
collection: string;
|
|
69
|
+
filter: PlanPredicate | null;
|
|
70
|
+
order: PlanOrderTerm[] | null;
|
|
71
|
+
window: {
|
|
72
|
+
offset: number;
|
|
73
|
+
limit: number | null;
|
|
74
|
+
} | null;
|
|
75
|
+
aggregate: {
|
|
76
|
+
fn: 'count' | 'sum' | 'avg' | 'min' | 'max';
|
|
77
|
+
ref: PlanRef | null;
|
|
78
|
+
} | null;
|
|
79
|
+
project: 'document';
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* @typedef {{ segments: ({ name: string } | { index: number })[],
|
|
83
|
+
* type: string, column: string | null }} PlanRef
|
|
84
|
+
* A typed reference into the stored document: `type` is the
|
|
85
|
+
* schema-declared type or `'unknown'`; `column` is the generated
|
|
86
|
+
* column name when the collection indexes this path.
|
|
87
|
+
*
|
|
88
|
+
* @typedef {{ lit: unknown } | { ext: string }} PlanOperand
|
|
89
|
+
*
|
|
90
|
+
* @typedef {(
|
|
91
|
+
* { p: 'and' | 'or', items: PlanPredicate[] } |
|
|
92
|
+
* { p: 'not', item: PlanPredicate } |
|
|
93
|
+
* { p: 'cmp', op: 'eq' | 'ne' | 'lt' | 'le' | 'gt' | 'ge',
|
|
94
|
+
* ref: PlanRef, operand: PlanOperand } |
|
|
95
|
+
* { p: 'typeIs', ref: PlanRef, types: string[], positive: boolean } |
|
|
96
|
+
* { p: 'strop', kind: 'starts' | 'ends' | 'contains',
|
|
97
|
+
* ref: PlanRef, operand: PlanOperand } |
|
|
98
|
+
* { p: 'const', value: boolean } |
|
|
99
|
+
* { p: 'udf', name: string, key: string }
|
|
100
|
+
* )} PlanPredicate
|
|
101
|
+
*
|
|
102
|
+
* @typedef {{ ref: PlanRef, desc: boolean, emptyGreatest: boolean }} PlanOrderTerm
|
|
103
|
+
*
|
|
104
|
+
* @typedef {{
|
|
105
|
+
* planVersion: number,
|
|
106
|
+
* alg: 'select',
|
|
107
|
+
* collection: string,
|
|
108
|
+
* filter: PlanPredicate | null,
|
|
109
|
+
* order: PlanOrderTerm[] | null,
|
|
110
|
+
* window: { offset: number, limit: number | null } | null,
|
|
111
|
+
* aggregate: { fn: 'count' | 'sum' | 'avg' | 'min' | 'max',
|
|
112
|
+
* ref: PlanRef | null } | null,
|
|
113
|
+
* project: 'document',
|
|
114
|
+
* }} Plan
|
|
115
|
+
*/
|
|
116
|
+
/**
|
|
117
|
+
* A fresh select plan over one collection.
|
|
118
|
+
* @param {string} collection
|
|
119
|
+
* @returns {Plan}
|
|
120
|
+
*/
|
|
121
|
+
export declare function selectPlan(collection: string): Plan;
|
|
122
|
+
/**
|
|
123
|
+
* Conjoin a predicate onto a plan's filter.
|
|
124
|
+
* @param {PlanPredicate | null} filter
|
|
125
|
+
* @param {PlanPredicate} predicate
|
|
126
|
+
* @returns {PlanPredicate}
|
|
127
|
+
*/
|
|
128
|
+
export declare function conjoin(filter: PlanPredicate | null, predicate: PlanPredicate): PlanPredicate;
|
|
129
|
+
/**
|
|
130
|
+
* Throw when a plan value carries anything that smells like SQL.
|
|
131
|
+
* @param {unknown} plan
|
|
132
|
+
*/
|
|
133
|
+
export declare function assertNoSqlText(plan: unknown): void;
|
|
@@ -0,0 +1,49 @@
|
|
|
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;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Change capture (D13): committed writes become an observable,
|
|
3
|
+
* ordered stream of RFC 6902 patches — derived from SQLite's own
|
|
4
|
+
* session changesets where the binding has them, from a write-path
|
|
5
|
+
* journal where it does not (`bun:sqlite` has no `createSession`), or
|
|
6
|
+
* off entirely. One diff format then runs end to end: store → patch →
|
|
7
|
+
* live query → patch → O(k) render.
|
|
8
|
+
*
|
|
9
|
+
* The pointer contract (LIVE-FORMAT §2): `/<table>/<key>/<path…>`,
|
|
10
|
+
* every token escaped per RFC 6901. A single key renders as its
|
|
11
|
+
* scalar text (integers in decimal); a composite key renders as the
|
|
12
|
+
* JSON text of its parts array. Join-table rows are tiny documents
|
|
13
|
+
* under the join table's name — membership changes are part of the
|
|
14
|
+
* stream, not a blind spot.
|
|
15
|
+
*
|
|
16
|
+
* Session facts this file is built on (probed, 3.51.2):
|
|
17
|
+
* - a changeset carries ONE NET OP PER ROW (insert+update coalesce;
|
|
18
|
+
* insert+delete vanish; a no-op update is absent), and within-table
|
|
19
|
+
* order is NOT statement order — every row op targets a distinct
|
|
20
|
+
* pointer, so application order across rows cannot matter;
|
|
21
|
+
* - `ROLLBACK TO` a savepoint removes the undone rows from the
|
|
22
|
+
* session (pinned by test — the classic caveat does NOT hold here);
|
|
23
|
+
* - a rolled-back transaction yields an empty changeset;
|
|
24
|
+
* - virtual generated columns are invisible;
|
|
25
|
+
* - an UPDATE's old record carries the primary key and the CHANGED
|
|
26
|
+
* columns only — which is exactly enough for property-level ops,
|
|
27
|
+
* and why the doc column's old/new blobs make a minimal nested
|
|
28
|
+
* diff possible (`SELECT json(?)` turns JSONB back into text).
|
|
29
|
+
*/
|
|
30
|
+
/** The persisted change log (LIVE-FORMAT §5). */
|
|
31
|
+
export declare const CHANGES_TABLE = "_jaren_changes";
|
|
32
|
+
export declare const DEFAULT_RETENTION = 1000;
|
|
33
|
+
/**
|
|
34
|
+
* Decode a binary changeset into row operations.
|
|
35
|
+
* @param {Uint8Array} bytes
|
|
36
|
+
* @returns {{ table: string, pk: boolean[], op: 'insert'|'update'|'delete',
|
|
37
|
+
* indirect: boolean, oldValues: any[] | null, newValues: any[] | null }[]}
|
|
38
|
+
*/
|
|
39
|
+
export declare function parseChangeset(bytes: Uint8Array): {
|
|
40
|
+
table: string;
|
|
41
|
+
pk: boolean[];
|
|
42
|
+
op: 'insert' | 'update' | 'delete';
|
|
43
|
+
indirect: boolean;
|
|
44
|
+
oldValues: any[] | null;
|
|
45
|
+
newValues: any[] | null;
|
|
46
|
+
}[];
|
|
47
|
+
/**
|
|
48
|
+
* The key token (LIVE-FORMAT §2): a single key is its scalar text;
|
|
49
|
+
* a composite key is the JSON text of its parts array.
|
|
50
|
+
* @param {any[]} parts
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
export declare function keyToken(parts: any[]): string;
|
|
54
|
+
export type TableShape = {
|
|
55
|
+
kind: 'collection' | 'entity' | 'join';
|
|
56
|
+
columns: {
|
|
57
|
+
name: string;
|
|
58
|
+
role: 'key' | 'doc' | 'scalar' | 'fk' | 'epoch';
|
|
59
|
+
storage?: string;
|
|
60
|
+
}[];
|
|
61
|
+
keyIndexes: number[];
|
|
62
|
+
docIndex: number;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Translate parsed row operations into RFC 6902 ops, resolving JSONB
|
|
66
|
+
* blobs through the connection (`SELECT json(?)`).
|
|
67
|
+
* @param {any} connection
|
|
68
|
+
* @param {Map<string, TableShape>} shapes
|
|
69
|
+
* @param {any[]} operations
|
|
70
|
+
* @returns {any} value-or-promise of RFC 6902 ops
|
|
71
|
+
*/
|
|
72
|
+
export declare function translateOperations(connection: any, shapes: Map<string, TableShape>, operations: any[]): any;
|
|
73
|
+
/**
|
|
74
|
+
* @param {{ connection: any, shapes: Map<string, TableShape>,
|
|
75
|
+
* mode: 'session' | 'journal',
|
|
76
|
+
* log: boolean, retention: number }} options
|
|
77
|
+
* @returns {any}
|
|
78
|
+
*/
|
|
79
|
+
export declare function createCaptureEngine(options: {
|
|
80
|
+
connection: any;
|
|
81
|
+
shapes: Map<string, TableShape>;
|
|
82
|
+
mode: 'session' | 'journal';
|
|
83
|
+
log: boolean;
|
|
84
|
+
retention: number;
|
|
85
|
+
}): any;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The composition (JOBS-FORMAT §7): a persisted `@jarenjs/flow`
|
|
3
|
+
* DAG run wired to a queue job. THE FLOW ENGINE IS INJECTED, NEVER
|
|
4
|
+
* IMPORTED — the shared invariant forbids `@jarenjs/db` importing
|
|
5
|
+
* `@jarenjs/flow`, so `compileDag` arrives as a capability (the D10
|
|
6
|
+
* shape applied to flow) and a test asserts the manifest and import
|
|
7
|
+
* graph name flow nowhere.
|
|
8
|
+
*
|
|
9
|
+
* Each kind's document compiles ONCE against a delegating checkpoint
|
|
10
|
+
* store; per claimed job, the delegate binds the engine's guarded
|
|
11
|
+
* per-job store (`checkpointsFor`) — `save` refuses once the lease is
|
|
12
|
+
* lost and `complete` records the DAG result, marks the job done and
|
|
13
|
+
* prunes the checkpoint rows in ONE transaction, so a failure leaves
|
|
14
|
+
* neither and a crash resumes instead of restarting.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Build a worker whose handlers run checkpointed DAG documents.
|
|
18
|
+
* @param {any} store - an open store with `{ jobs: true }`
|
|
19
|
+
* @param {{ compileDag: Function,
|
|
20
|
+
* documents: Record<string, any>,
|
|
21
|
+
* tasks?: Record<string, Function>,
|
|
22
|
+
* concurrency?: number, pollInterval?: number, leaseMs?: number,
|
|
23
|
+
* owner?: string, backoffBase?: number, backoffCap?: number }} options
|
|
24
|
+
* @returns {{ start: () => any, stop: () => Promise<void>, stats: () => any }}
|
|
25
|
+
*/
|
|
26
|
+
export declare function createDagJobRunner(store: any, options: {
|
|
27
|
+
compileDag: Function;
|
|
28
|
+
documents: Record<string, any>;
|
|
29
|
+
tasks?: Record<string, Function>;
|
|
30
|
+
concurrency?: number;
|
|
31
|
+
pollInterval?: number;
|
|
32
|
+
leaseMs?: number;
|
|
33
|
+
owner?: string;
|
|
34
|
+
backoffBase?: number;
|
|
35
|
+
backoffCap?: number;
|
|
36
|
+
}): {
|
|
37
|
+
start: () => any;
|
|
38
|
+
stop: () => Promise<void>;
|
|
39
|
+
stats: () => any;
|
|
40
|
+
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file DDL planning: a normalized collection becomes one physical
|
|
3
|
+
* table — a key column, a JSON document column, a virtual generated
|
|
4
|
+
* column per indexed path, and the declared indexes — with every byte
|
|
5
|
+
* of SQL rendered by the dialect.
|
|
6
|
+
*
|
|
7
|
+
* Index paths are JSONPath expressions analyzed through the engine's
|
|
8
|
+
* PUBLISHED AST (`analyzeQuery`): a path is indexable exactly when the
|
|
9
|
+
* analysis says it is singular and every segment is a plain member or
|
|
10
|
+
* index selection. That reuses the one grammar authority instead of
|
|
11
|
+
* re-parsing, and it fails loudly (`JD0004`) on everything else —
|
|
12
|
+
* wildcards, slices, filters, descendants, functions — rather than
|
|
13
|
+
* silently indexing the wrong thing.
|
|
14
|
+
*/
|
|
15
|
+
/** The fixed physical column names of the 0.1 mapping. */
|
|
16
|
+
export declare const KEY_COLUMN = "key";
|
|
17
|
+
export declare const DOC_COLUMN = "doc";
|
|
18
|
+
/**
|
|
19
|
+
* Analyze one index path expression down to typed segments.
|
|
20
|
+
* @param {string} expression - A JSONPath expression (`$.email`)
|
|
21
|
+
* @param {string} docPath - Model-document pointer for diagnostics
|
|
22
|
+
* @returns {{ segments: import('./dialect.js').JsonPathSegment[],
|
|
23
|
+
* canonical: string }}
|
|
24
|
+
*/
|
|
25
|
+
export declare function compileIndexPath(expression: string, docPath: string): {
|
|
26
|
+
segments: import('./dialect.js').JsonPathSegment[];
|
|
27
|
+
canonical: string;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* The declared schema type at a segment path, walked structurally
|
|
31
|
+
* through `properties` / `items` / `prefixItems`. The collection's
|
|
32
|
+
* schema is the type source — that is why the physical mapping needs
|
|
33
|
+
* no engine-side inference.
|
|
34
|
+
* @param {any} schema
|
|
35
|
+
* @param {import('./dialect.js').JsonPathSegment[]} segments
|
|
36
|
+
* @returns {string | undefined}
|
|
37
|
+
*/
|
|
38
|
+
export declare function schemaTypeAt(schema: any, segments: import('./dialect.js').JsonPathSegment[]): string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Plan one collection's physical shape: the DDL statements to create
|
|
41
|
+
* it and the structural facts an existing table must match (the
|
|
42
|
+
* `JD0002` comparison set).
|
|
43
|
+
* @param {string} name - The collection name (also the table name)
|
|
44
|
+
* @param {{ schema: any, keySegments: { name: string }[] | null,
|
|
45
|
+
* identity: string, indexes: { name: string, paths: string[],
|
|
46
|
+
* unique: boolean, docPath: string }[] }} collection - normalized
|
|
47
|
+
* @param {any} dialect
|
|
48
|
+
* @returns {{
|
|
49
|
+
* table: string, keyColumn: string, docColumn: string,
|
|
50
|
+
* keyType: string,
|
|
51
|
+
* generated: { name: string, type: string, pathText: string,
|
|
52
|
+
* canonical: string }[],
|
|
53
|
+
* columnByCanonical: Map<string, string>,
|
|
54
|
+
* createSql: string[],
|
|
55
|
+
* expected: { columns: { name: string, type: string,
|
|
56
|
+
* generated: boolean }[], indexes: { name: string, unique: boolean,
|
|
57
|
+
* columns: string[] }[] },
|
|
58
|
+
* }}
|
|
59
|
+
*/
|
|
60
|
+
export declare function planCollection(name: string, collection: {
|
|
61
|
+
schema: any;
|
|
62
|
+
keySegments: {
|
|
63
|
+
name: string;
|
|
64
|
+
}[] | null;
|
|
65
|
+
identity: string;
|
|
66
|
+
indexes: {
|
|
67
|
+
name: string;
|
|
68
|
+
paths: string[];
|
|
69
|
+
unique: boolean;
|
|
70
|
+
docPath: string;
|
|
71
|
+
}[];
|
|
72
|
+
}, dialect: any): {
|
|
73
|
+
table: string;
|
|
74
|
+
keyColumn: string;
|
|
75
|
+
docColumn: string;
|
|
76
|
+
keyType: string;
|
|
77
|
+
generated: {
|
|
78
|
+
name: string;
|
|
79
|
+
type: string;
|
|
80
|
+
pathText: string;
|
|
81
|
+
canonical: string;
|
|
82
|
+
}[];
|
|
83
|
+
columnByCanonical: Map<string, string>;
|
|
84
|
+
createSql: string[];
|
|
85
|
+
expected: {
|
|
86
|
+
columns: {
|
|
87
|
+
name: string;
|
|
88
|
+
type: string;
|
|
89
|
+
generated: boolean;
|
|
90
|
+
}[];
|
|
91
|
+
indexes: {
|
|
92
|
+
name: string;
|
|
93
|
+
unique: boolean;
|
|
94
|
+
columns: string[];
|
|
95
|
+
}[];
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Normalize a stored `CREATE` statement for comparison: collapse runs of
|
|
100
|
+
* whitespace, drop whitespace around punctuation, and strip the
|
|
101
|
+
* `IF NOT EXISTS` SQLite does not keep. What survives is every token that
|
|
102
|
+
* carries meaning, so two statements compare equal exactly when they
|
|
103
|
+
* declare the same physical object.
|
|
104
|
+
* @param {string} sql
|
|
105
|
+
* @returns {string}
|
|
106
|
+
*/
|
|
107
|
+
export declare function normalizeDeclaredSql(sql: string): string;
|
|
108
|
+
/**
|
|
109
|
+
* A comparable form of one `CREATE` statement.
|
|
110
|
+
*
|
|
111
|
+
* For a TABLE the column definitions compare as a SET, because
|
|
112
|
+
* `ALTER TABLE … ADD COLUMN` can only append — so a migrated table and a
|
|
113
|
+
* freshly built one legitimately differ in column order, and this store
|
|
114
|
+
* never reads a column positionally. Everything else is exact: each
|
|
115
|
+
* column's full definition (type, `PRIMARY KEY`, `NOT NULL`, `DEFAULT`,
|
|
116
|
+
* `CHECK`, `GENERATED … AS`, `REFERENCES … ON DELETE …`), the table
|
|
117
|
+
* constraints, and the trailing table options (`STRICT`,
|
|
118
|
+
* `WITHOUT ROWID`).
|
|
119
|
+
*
|
|
120
|
+
* For an INDEX the text compares whole, because an index IS its order —
|
|
121
|
+
* `(a,b)` and `(b,a)` serve different lookups — as are its partial
|
|
122
|
+
* predicate and each term's collation and direction.
|
|
123
|
+
* @param {string} sql
|
|
124
|
+
* @returns {string}
|
|
125
|
+
*/
|
|
126
|
+
export declare function comparableDeclaredSql(sql: string): string;
|
|
127
|
+
/**
|
|
128
|
+
* Verify an existing table against the planned shape; any difference
|
|
129
|
+
* is `JD0002` and nothing is altered. Shared by the store's open path
|
|
130
|
+
* and the migration engine's shadow validation.
|
|
131
|
+
* @param {any} connection
|
|
132
|
+
* @param {any} plan
|
|
133
|
+
* @param {string} collection
|
|
134
|
+
* @param {string} docPath
|
|
135
|
+
* @returns {any} value-or-promise
|
|
136
|
+
*/
|
|
137
|
+
export declare function verifyShape(connection: any, plan: any, collection: string, docPath: string): any;
|
|
138
|
+
/**
|
|
139
|
+
* Plan one ENTITY's physical shape from the mapping data
|
|
140
|
+
* `explainMapping` derived: the relational table (typed columns,
|
|
141
|
+
* checks, foreign keys, the JSONB document column), its indexes, and
|
|
142
|
+
* the structural facts an existing table must match. One verify path
|
|
143
|
+
* serves both document kinds.
|
|
144
|
+
* @param {string} name
|
|
145
|
+
* @param {any} entityMapping - `explainMapping(model).entities[name]`
|
|
146
|
+
* @param {any} entities - the full `explainMapping` result (key types
|
|
147
|
+
* come from the referenced entity's columns)
|
|
148
|
+
* @param {any} dialect
|
|
149
|
+
* @returns {{ table: string, createSql: string[], expected: any,
|
|
150
|
+
* columnNames: Set<string> }}
|
|
151
|
+
*/
|
|
152
|
+
export declare function planEntity(name: string, entityMapping: any, entities: any, dialect: any): {
|
|
153
|
+
table: string;
|
|
154
|
+
createSql: string[];
|
|
155
|
+
expected: any;
|
|
156
|
+
columnNames: Set<string>;
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Plan a many-to-many join table.
|
|
160
|
+
* @param {string} tableName
|
|
161
|
+
* @param {any} join - `explainMapping(model).joinTables[tableName]`
|
|
162
|
+
* @param {any} entities - the full mapping
|
|
163
|
+
* @param {any} dialect
|
|
164
|
+
* @returns {{ table: string, createSql: string[], expected: any }}
|
|
165
|
+
*/
|
|
166
|
+
export declare function planJoinTable(tableName: string, join: any, entities: any, dialect: any): {
|
|
167
|
+
table: string;
|
|
168
|
+
createSql: string[];
|
|
169
|
+
expected: any;
|
|
170
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The dialect seam: the ONLY place SQL text is produced. A
|
|
3
|
+
* dialect is data plus a small emitter — a spelling spec (how to quote
|
|
4
|
+
* an identifier, reference a parameter, extract a JSON member, open a
|
|
5
|
+
* savepoint) composed by {@link createDialect} into the DDL and DML
|
|
6
|
+
* statement builders the store consumes. Nothing outside a dialect
|
|
7
|
+
* concatenates SQL; that costs one indirection now, and without it a
|
|
8
|
+
* second backend is a rewrite.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately NOT in the dialect, because they are behavioural rather
|
|
11
|
+
* than syntactic: whether functions can be registered per connection,
|
|
12
|
+
* whether change capture exists and in what form, and whether tables
|
|
13
|
+
* can be restructured in place. Those are capabilities on the
|
|
14
|
+
* connection.
|
|
15
|
+
*/
|
|
16
|
+
export type JsonPathSegment = {
|
|
17
|
+
name: string;
|
|
18
|
+
} | {
|
|
19
|
+
index: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* A typed member path into the JSON document column: name segments for
|
|
23
|
+
* object members, index segments for array positions. Produced by the
|
|
24
|
+
* DDL planner (from analyzed index paths) and the patch translator
|
|
25
|
+
* (from pointers discriminated against the live document).
|
|
26
|
+
* @typedef {{ name: string } | { index: number }} JsonPathSegment
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Compose a dialect from its spelling spec. Every statement the store
|
|
30
|
+
* ever runs is built here from the spec's primitives, so a spec with
|
|
31
|
+
* different quoting or parameter style produces correspondingly
|
|
32
|
+
* different SQL from the same model — the property the test-double
|
|
33
|
+
* dialect pins.
|
|
34
|
+
* @param {{
|
|
35
|
+
* name: string,
|
|
36
|
+
* capabilities: Record<string, any>,
|
|
37
|
+
* tableSuffix: string,
|
|
38
|
+
* docColumnType: string,
|
|
39
|
+
* quoteIdentifier: (s: string) => string,
|
|
40
|
+
* parameterRef: (i: number, name: string) => string,
|
|
41
|
+
* stringLiteral: (s: string) => string,
|
|
42
|
+
* booleanLiteral: (b: boolean) => string,
|
|
43
|
+
* typeFor: (schemaType: string | undefined, hint: string) => string,
|
|
44
|
+
* limitClause: (limit: number, offset?: number) => string,
|
|
45
|
+
* jsonPathText: (segments: JsonPathSegment[]) => string | null,
|
|
46
|
+
* jsonExtract: (columnSql: string, pathText: string) => string,
|
|
47
|
+
* jsonSet: (exprSql: string, pathText: string, valueSql: string) => string,
|
|
48
|
+
* jsonRemove: (exprSql: string, pathText: string) => string,
|
|
49
|
+
* jsonAppend: (exprSql: string, arrayPathText: string, valueSql: string) => string,
|
|
50
|
+
* jsonEncode: (paramSql: string) => string,
|
|
51
|
+
* jsonText: (columnSql: string) => string,
|
|
52
|
+
* jsonAgg: (exprSql: string) => string,
|
|
53
|
+
* jsonTypeOf: (columnSql: string, pathText: string) => string,
|
|
54
|
+
* valueTypeOf: (paramSql: string) => string,
|
|
55
|
+
* strStartsWith: (valueSql: string, patternA: string, patternB: string) => string,
|
|
56
|
+
* strEndsWith: (valueSql: string, patternA: string, patternB: string, patternC: string) => string,
|
|
57
|
+
* strContains: (valueSql: string, patternSql: string) => string,
|
|
58
|
+
* orderNulls: (nullsFirst: boolean) => string,
|
|
59
|
+
* rowIdentity: () => string,
|
|
60
|
+
* explainQuery: (sql: string) => string,
|
|
61
|
+
* excludedRef: (columnSql: string) => string,
|
|
62
|
+
* tx: { begin: string, beginImmediate: string, commit: string,
|
|
63
|
+
* rollback: string,
|
|
64
|
+
* savepoint: (n: string) => string, release: (n: string) => string,
|
|
65
|
+
* rollbackTo: (n: string) => string },
|
|
66
|
+
* pragma: { busyTimeout: (ms: number) => string,
|
|
67
|
+
* journalMode: (mode: string) => string,
|
|
68
|
+
* foreignKeys: (on: boolean) => string },
|
|
69
|
+
* introspect: { version: () => string, compileOptions: () => string,
|
|
70
|
+
* tableExists: () => string, columns: (table: string) => string,
|
|
71
|
+
* indexes: (table: string) => string,
|
|
72
|
+
* indexColumns: (index: string) => string,
|
|
73
|
+
* foreignKeysOn: () => string,
|
|
74
|
+
* foreignKeyList: (table: string) => string },
|
|
75
|
+
* }} spec
|
|
76
|
+
* @returns {any} the frozen dialect
|
|
77
|
+
*/
|
|
78
|
+
export declare function createDialect(spec: {
|
|
79
|
+
name: string;
|
|
80
|
+
capabilities: Record<string, any>;
|
|
81
|
+
tableSuffix: string;
|
|
82
|
+
docColumnType: string;
|
|
83
|
+
quoteIdentifier: (s: string) => string;
|
|
84
|
+
parameterRef: (i: number, name: string) => string;
|
|
85
|
+
stringLiteral: (s: string) => string;
|
|
86
|
+
booleanLiteral: (b: boolean) => string;
|
|
87
|
+
typeFor: (schemaType: string | undefined, hint: string) => string;
|
|
88
|
+
limitClause: (limit: number, offset?: number) => string;
|
|
89
|
+
jsonPathText: (segments: JsonPathSegment[]) => string | null;
|
|
90
|
+
jsonExtract: (columnSql: string, pathText: string) => string;
|
|
91
|
+
jsonSet: (exprSql: string, pathText: string, valueSql: string) => string;
|
|
92
|
+
jsonRemove: (exprSql: string, pathText: string) => string;
|
|
93
|
+
jsonAppend: (exprSql: string, arrayPathText: string, valueSql: string) => string;
|
|
94
|
+
jsonEncode: (paramSql: string) => string;
|
|
95
|
+
jsonText: (columnSql: string) => string;
|
|
96
|
+
jsonAgg: (exprSql: string) => string;
|
|
97
|
+
jsonTypeOf: (columnSql: string, pathText: string) => string;
|
|
98
|
+
valueTypeOf: (paramSql: string) => string;
|
|
99
|
+
strStartsWith: (valueSql: string, patternA: string, patternB: string) => string;
|
|
100
|
+
strEndsWith: (valueSql: string, patternA: string, patternB: string, patternC: string) => string;
|
|
101
|
+
strContains: (valueSql: string, patternSql: string) => string;
|
|
102
|
+
orderNulls: (nullsFirst: boolean) => string;
|
|
103
|
+
rowIdentity: () => string;
|
|
104
|
+
explainQuery: (sql: string) => string;
|
|
105
|
+
excludedRef: (columnSql: string) => string;
|
|
106
|
+
tx: {
|
|
107
|
+
begin: string;
|
|
108
|
+
beginImmediate: string;
|
|
109
|
+
commit: string;
|
|
110
|
+
rollback: string;
|
|
111
|
+
savepoint: (n: string) => string;
|
|
112
|
+
release: (n: string) => string;
|
|
113
|
+
rollbackTo: (n: string) => string;
|
|
114
|
+
};
|
|
115
|
+
pragma: {
|
|
116
|
+
busyTimeout: (ms: number) => string;
|
|
117
|
+
journalMode: (mode: string) => string;
|
|
118
|
+
foreignKeys: (on: boolean) => string;
|
|
119
|
+
};
|
|
120
|
+
introspect: {
|
|
121
|
+
version: () => string;
|
|
122
|
+
compileOptions: () => string;
|
|
123
|
+
tableExists: () => string;
|
|
124
|
+
columns: (table: string) => string;
|
|
125
|
+
indexes: (table: string) => string;
|
|
126
|
+
indexColumns: (index: string) => string;
|
|
127
|
+
foreignKeysOn: () => string;
|
|
128
|
+
foreignKeyList: (table: string) => string;
|
|
129
|
+
};
|
|
130
|
+
}): any;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The SQLite dialect — the first spelling of the dialect
|
|
3
|
+
* contract, not the only conceivable one. Documents are stored JSONB
|
|
4
|
+
* in a BLOB column of a STRICT table; indexed paths become virtual
|
|
5
|
+
* generated columns over `jsonb_extract`; reads render back to text
|
|
6
|
+
* through `json()`. Parameters are positional (`?`) because every
|
|
7
|
+
* binding this package ships binds arrays.
|
|
8
|
+
*/
|
|
9
|
+
export declare const sqliteDialect: any;
|