@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,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The durable job queue (JOBS-FORMAT): enqueue, the
|
|
3
|
+
* single-statement guarded claim (§3 — one statement is one
|
|
4
|
+
* transaction, so no double-claim without any distributed lock),
|
|
5
|
+
* retry with exponential backoff and jitter (§4), recovery as
|
|
6
|
+
* re-claim of expired leases (§5), polling workers with same-process
|
|
7
|
+
* wake-on-enqueue (§6), and the per-job flow checkpoint store the DAG
|
|
8
|
+
* composition binds (§7) — completion marks the job done and records
|
|
9
|
+
* the result in ONE guarded transaction.
|
|
10
|
+
*
|
|
11
|
+
* Every worker transition is guarded by `state='leased' AND
|
|
12
|
+
* lease_owner=?`: execution is at-least-once, completion is
|
|
13
|
+
* exactly-once. `now` and `random` are injectable — the runtime
|
|
14
|
+
* defaults are the clock and `Math.random`; every test injects.
|
|
15
|
+
*
|
|
16
|
+
* The worker LIFECYCLE holds two invariants that a long-running process
|
|
17
|
+
* depends on, and neither is a detail:
|
|
18
|
+
*
|
|
19
|
+
* - **No handler value can break the loop.** A handler is host code and
|
|
20
|
+
* may resolve with something JSON cannot express, or reject with a
|
|
21
|
+
* value whose own `message` throws when read. Both are normalized
|
|
22
|
+
* totally, and `runOne` is isolated inside the loop, so the worst a
|
|
23
|
+
* single job can do is fail its own attempt. A rejected claim-execute
|
|
24
|
+
* loop would stop draining the queue silently.
|
|
25
|
+
* - **Shutdown is bounded.** Handlers receive an `AbortSignal` and
|
|
26
|
+
* `stop()` takes a deadline, so a handler that never settles cannot
|
|
27
|
+
* hold `stop()` — and therefore `store.close()`, and therefore the
|
|
28
|
+
* database file — open forever.
|
|
29
|
+
*/
|
|
30
|
+
export declare const JOBS_TABLE = "_jaren_jobs";
|
|
31
|
+
export declare const JOB_CHECKPOINTS_TABLE = "_jaren_job_checkpoints";
|
|
32
|
+
/** §4 defaults, all overridable per worker. */
|
|
33
|
+
export declare const JOB_DEFAULTS: Readonly<{
|
|
34
|
+
maxAttempts: 5;
|
|
35
|
+
leaseMs: 30000;
|
|
36
|
+
pollInterval: 500;
|
|
37
|
+
backoffBase: 1000;
|
|
38
|
+
backoffCap: 60000;
|
|
39
|
+
/** How long `stop()` waits for in-flight handlers after signalling
|
|
40
|
+
* abort, before it stops waiting and reports what is still running.
|
|
41
|
+
* Bounded on purpose: an unbounded wait makes one stuck handler
|
|
42
|
+
* indistinguishable from a hung process. */
|
|
43
|
+
stopGraceMs: 5000;
|
|
44
|
+
}>;
|
|
45
|
+
/**
|
|
46
|
+
* A diagnostic string for ANY value, including ones that fight back — a
|
|
47
|
+
* getter that throws, a null-prototype object, a revoked proxy, a
|
|
48
|
+
* symbol. Total by construction: an error report is never the thing that
|
|
49
|
+
* fails.
|
|
50
|
+
* @param {any} value
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
export declare function describeValue(value: any): string;
|
|
54
|
+
/**
|
|
55
|
+
* JSON text for a job result, or `null` when the value cannot be
|
|
56
|
+
* expressed — a BigInt, a cycle, a `toJSON` that throws. The caller
|
|
57
|
+
* treats that as a failed attempt, never as a broken worker.
|
|
58
|
+
* @param {any} value
|
|
59
|
+
* @returns {{ text: string | null } | { reason: string }}
|
|
60
|
+
*/
|
|
61
|
+
export declare function serializeResult(value: any): {
|
|
62
|
+
text: string | null;
|
|
63
|
+
} | {
|
|
64
|
+
reason: string;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* The queue engine over one open connection.
|
|
68
|
+
* @param {{ connection: any, now?: () => number,
|
|
69
|
+
* random?: () => number,
|
|
70
|
+
* defaults?: Partial<typeof JOB_DEFAULTS> }} options
|
|
71
|
+
*/
|
|
72
|
+
export declare function createJobEngine(options: {
|
|
73
|
+
connection: any;
|
|
74
|
+
now?: () => number;
|
|
75
|
+
random?: () => number;
|
|
76
|
+
defaults?: Partial<typeof JOB_DEFAULTS>;
|
|
77
|
+
}): {
|
|
78
|
+
ready: any;
|
|
79
|
+
enqueue: (kind: any, payload: any, enqueueOptions: any) => any;
|
|
80
|
+
get: (id: any) => any;
|
|
81
|
+
counts: () => any;
|
|
82
|
+
claim: (claimOptions: {
|
|
83
|
+
kinds: string[];
|
|
84
|
+
owner: string;
|
|
85
|
+
leaseMs?: number;
|
|
86
|
+
}) => any;
|
|
87
|
+
complete: (id: any, owner: any, result: any) => any;
|
|
88
|
+
fail: (id: any, owner: any, error: any, workerDefaults: any) => any;
|
|
89
|
+
checkpointsFor: (job: {
|
|
90
|
+
id: string;
|
|
91
|
+
leaseOwner: string | null;
|
|
92
|
+
}) => {
|
|
93
|
+
load: (runId: any) => any;
|
|
94
|
+
save: (runId: any, nodeId: any, value: any) => any;
|
|
95
|
+
complete: (runId: any, result: any) => any;
|
|
96
|
+
};
|
|
97
|
+
createWorker: (workerOptions: {
|
|
98
|
+
handlers: Record<string, Function>;
|
|
99
|
+
concurrency?: number;
|
|
100
|
+
pollInterval?: number;
|
|
101
|
+
leaseMs?: number;
|
|
102
|
+
owner?: string;
|
|
103
|
+
backoffBase?: number;
|
|
104
|
+
backoffCap?: number;
|
|
105
|
+
}) => {
|
|
106
|
+
stats: () => {
|
|
107
|
+
claims: number;
|
|
108
|
+
completions: number;
|
|
109
|
+
failures: number;
|
|
110
|
+
polls: number;
|
|
111
|
+
wakes: number;
|
|
112
|
+
inFlight: number;
|
|
113
|
+
};
|
|
114
|
+
start(): /*elided*/ any;
|
|
115
|
+
/**
|
|
116
|
+
* Stop claiming, signal in-flight handlers to abort, and wait for
|
|
117
|
+
* the loops — but only up to `graceMs`. A handler that ignores its
|
|
118
|
+
* signal cannot hold the process open; the resolved record says so
|
|
119
|
+
* instead, and the lease expiry (§5) lets another worker re-claim.
|
|
120
|
+
* @param {{ graceMs?: number }} [stopOptions]
|
|
121
|
+
* @returns {Promise<{ drained: boolean, inFlight: number }>}
|
|
122
|
+
*/
|
|
123
|
+
stop(stopOptions?: {
|
|
124
|
+
graceMs?: number;
|
|
125
|
+
}): Promise<{
|
|
126
|
+
drained: boolean;
|
|
127
|
+
inFlight: number;
|
|
128
|
+
}>;
|
|
129
|
+
};
|
|
130
|
+
/** Stop every worker, bounded. Resolves to the per-worker outcome so
|
|
131
|
+
* `close()` can report a handler it could not wait out rather than
|
|
132
|
+
* hanging on it. */
|
|
133
|
+
stopAll: (stopOptions: any) => Promise<any[]>;
|
|
134
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Live queries (LIVE-FORMAT §§7–12): a registered query document
|
|
3
|
+
* whose result is maintained as capture records arrive, emitting
|
|
4
|
+
* RFC 6902 patches against its own `{ rows }` result document.
|
|
5
|
+
*
|
|
6
|
+
* The CLASSIFIER implements §7's normative table and nothing more —
|
|
7
|
+
* it unwraps the one-element array pack and literal `$subsequence`
|
|
8
|
+
* windows the same way the planner does, then reads the compiled
|
|
9
|
+
* plan: translated filters, order terms and aggregates are exactly
|
|
10
|
+
* the planner's, never a re-implementation. Everything outside the
|
|
11
|
+
* table re-runs on invalidation with the reason named (`live.mode`).
|
|
12
|
+
*
|
|
13
|
+
* Maintenance is synchronous inside capture delivery (§8): inserts
|
|
14
|
+
* carry their document in the patch, updates point-read the touched
|
|
15
|
+
* row, deletes are answered from maintained state. Per-row semantics
|
|
16
|
+
* reuse the ENGINE via packed one-row compilation (the residual
|
|
17
|
+
* discipline) — a live row evaluates exactly as the query would.
|
|
18
|
+
*/
|
|
19
|
+
/** The store-level live bounds and their defaults (§12: printed,
|
|
20
|
+
* never silent). */
|
|
21
|
+
export declare const LIVE_DEFAULTS: Readonly<{
|
|
22
|
+
maxQueries: 64;
|
|
23
|
+
maxMaintained: 10000;
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* Classify a collection query document against §7's table. Pure —
|
|
27
|
+
* given the document and the collection's planner shape, returns the
|
|
28
|
+
* strategy description, or a re-run description with the reason.
|
|
29
|
+
* @param {any} document
|
|
30
|
+
* @param {any} queryShape - the planner shape (collection, schema,
|
|
31
|
+
* columnByCanonical)
|
|
32
|
+
* @param {boolean} keyed - whether documents carry their key (a
|
|
33
|
+
* declared key pointer); unkeyed rows cannot be tracked by key
|
|
34
|
+
* @returns {any}
|
|
35
|
+
*/
|
|
36
|
+
export declare function classifyLiveQuery(document: any, queryShape: any, keyed: boolean): any;
|
|
37
|
+
/**
|
|
38
|
+
* Diff two row arrays into sequential add/remove/replace ops under
|
|
39
|
+
* `/rows`, relying on REFERENCE identity for unchanged rows (the §9
|
|
40
|
+
* sharing contract makes identity the equality that matters). A
|
|
41
|
+
* working copy is replayed op by op, so the emitted patch transforms
|
|
42
|
+
* the old array into the new one BY CONSTRUCTION; a remove re-filled
|
|
43
|
+
* at the same index merges into a replace.
|
|
44
|
+
* @param {any[]} oldRows
|
|
45
|
+
* @param {any[]} newRows
|
|
46
|
+
* @returns {any[]} ops
|
|
47
|
+
*/
|
|
48
|
+
export declare function diffRows(oldRows: any[], newRows: any[]): any[];
|
|
49
|
+
/**
|
|
50
|
+
* The store-level live-query registry: registration against the §12
|
|
51
|
+
* bounds, capture-record delivery in commit order, lifecycle.
|
|
52
|
+
* @param {{ maxQueries: number, maxMaintained: number }} bounds
|
|
53
|
+
*/
|
|
54
|
+
export declare function createLiveRegistry(bounds: {
|
|
55
|
+
maxQueries: number;
|
|
56
|
+
maxMaintained: number;
|
|
57
|
+
}): {
|
|
58
|
+
register: (definition: any) => any;
|
|
59
|
+
count: () => number;
|
|
60
|
+
deliver(record: any): void;
|
|
61
|
+
closeAll(): void;
|
|
62
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Document migrations (D12): two model documents diff into a
|
|
3
|
+
* migration document whose steps are rendered DDL, JSLT data
|
|
4
|
+
* transforms and query assertions; the migration replays on a shadow
|
|
5
|
+
* database first; a history table records what ran with a
|
|
6
|
+
* signature-grade checksum. This is the phase-A payoff for storing
|
|
7
|
+
* documents rather than rows: a shape change is a transformation of
|
|
8
|
+
* VALUES, not a table rebuild.
|
|
9
|
+
*
|
|
10
|
+
* Identity is a hash, not a version number: `from`/`to` are
|
|
11
|
+
* `hashContent(canonicalizeJson(model))` — the identity of a SHAPE,
|
|
12
|
+
* which nobody has to remember to bump. The checksum discipline is
|
|
13
|
+
* D12's: `canonicalizeJson` + `hashContent` (signature-grade — throws
|
|
14
|
+
* on the unserializable), never the memo-grade `contentKey`.
|
|
15
|
+
*
|
|
16
|
+
* Like the query emitter, this module is part of the emitter layer:
|
|
17
|
+
* the structural SQL it composes (the history table's statements, the
|
|
18
|
+
* batched row walk) is built from dialect primitives, and every
|
|
19
|
+
* planner-produced statement is rendered by the dialect into the
|
|
20
|
+
* migration DOCUMENT — shown before it is ever executed.
|
|
21
|
+
*/
|
|
22
|
+
/** The migration format version. */
|
|
23
|
+
export declare const MIGRATION_VERSION = "0.1";
|
|
24
|
+
/** The history table name (outside the model's identifier namespace
|
|
25
|
+
* conventions on purpose — a collection cannot collide with it). */
|
|
26
|
+
export declare const HISTORY_TABLE = "_jaren_migrations";
|
|
27
|
+
/**
|
|
28
|
+
* The signature-grade identity of a model SHAPE.
|
|
29
|
+
* @param {any} model - A jaren-model document
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
export declare function shapeHash(model: any): string;
|
|
33
|
+
/**
|
|
34
|
+
* The signature-grade checksum of a migration document.
|
|
35
|
+
* @param {any} migration
|
|
36
|
+
* @returns {string}
|
|
37
|
+
*/
|
|
38
|
+
export declare function migrationChecksum(migration: any): string;
|
|
39
|
+
/**
|
|
40
|
+
* Plan a migration between two model documents. The planner diffs the
|
|
41
|
+
* PHYSICAL plans (columns, indexes) and renders DDL through the
|
|
42
|
+
* dialect; a changed schema gets a DRAFT identity transform that
|
|
43
|
+
* refuses to run until the author fills it in — the planner cannot
|
|
44
|
+
* infer a data transform and does not pretend to. Renames are declared
|
|
45
|
+
* (`x-rename` on the target collection), never guessed.
|
|
46
|
+
* @param {any} fromModel
|
|
47
|
+
* @param {any} toModel
|
|
48
|
+
* @param {{ id?: string, dialect?: any }} [options]
|
|
49
|
+
* @returns {{ migration: any, report: {
|
|
50
|
+
* renamed: { from: string, to: string }[],
|
|
51
|
+
* added: string[], removed: string[],
|
|
52
|
+
* schemaChanged: string[], drafts: string[],
|
|
53
|
+
* destructive: boolean } }}
|
|
54
|
+
*/
|
|
55
|
+
export declare function planMigration(fromModel: any, toModel: any, options?: {
|
|
56
|
+
id?: string;
|
|
57
|
+
dialect?: any;
|
|
58
|
+
}): {
|
|
59
|
+
migration: any;
|
|
60
|
+
report: {
|
|
61
|
+
renamed: {
|
|
62
|
+
from: string;
|
|
63
|
+
to: string;
|
|
64
|
+
}[];
|
|
65
|
+
added: string[];
|
|
66
|
+
removed: string[];
|
|
67
|
+
schemaChanged: string[];
|
|
68
|
+
drafts: string[];
|
|
69
|
+
destructive: boolean;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
/** `planMigration` handles the whole model — collections AND entities
|
|
73
|
+
* — since the relational order; this name says so. */
|
|
74
|
+
export declare const planModelMigration: typeof planMigration;
|
|
75
|
+
/**
|
|
76
|
+
* Create a model's WHOLE physical shape on a connection: collections,
|
|
77
|
+
* entity tables and join tables, exactly as `openStore` would. Used
|
|
78
|
+
* by the shadow baseline, the fresh reference database that shape
|
|
79
|
+
* equality compares against, and the tests.
|
|
80
|
+
* @param {any} connection
|
|
81
|
+
* @param {any} model
|
|
82
|
+
* @returns {any} value-or-promise
|
|
83
|
+
*/
|
|
84
|
+
export declare function createModelShape(connection: any, model: any): any;
|
|
85
|
+
/**
|
|
86
|
+
* The declared schema of a database, normalized for comparison: every
|
|
87
|
+
* object carrying SQL text (tables, indexes), whitespace-collapsed,
|
|
88
|
+
* history table excluded, sorted. Shape equality after a migration —
|
|
89
|
+
* this dump versus a fresh {@link createModelShape} — is the
|
|
90
|
+
* acceptance criterion for every rebuild.
|
|
91
|
+
* @param {any} connection
|
|
92
|
+
* @returns {any} value-or-promise of `{ type, name, owner, sql }[]`
|
|
93
|
+
*/
|
|
94
|
+
export declare function schemaShapeOf(connection: any): any;
|
|
95
|
+
/**
|
|
96
|
+
* Compare a migrated database's schema against the shape a fresh
|
|
97
|
+
* `createModelShape(model)` produces, via a throwaway reference
|
|
98
|
+
* database. Returns `null` when equal, or a one-line difference.
|
|
99
|
+
* @param {any} driver
|
|
100
|
+
* @param {any} connection - the migrated database
|
|
101
|
+
* @param {any} model - the target model
|
|
102
|
+
* @param {((connection: any) => any) | undefined} registerFunctions
|
|
103
|
+
* @returns {any} value-or-promise of `string | null`
|
|
104
|
+
*/
|
|
105
|
+
export declare function compareShapeToModel(driver: any, connection: any, model: any, registerFunctions: ((connection: any) => any) | undefined): any;
|
|
106
|
+
/**
|
|
107
|
+
* Report a database's migration state without touching it: what is
|
|
108
|
+
* applied, what is pending, whether an applied migration was edited,
|
|
109
|
+
* and — once the chain is fully applied — whether the physical shape
|
|
110
|
+
* DRIFTED from the model (someone changed the database by hand, §12).
|
|
111
|
+
* @param {{ driver: any, path?: string }} target
|
|
112
|
+
* @param {any[]} migrations - the full ordered list
|
|
113
|
+
* @param {{ baseline: any, model?: any,
|
|
114
|
+
* registerFunctions?: (connection: any) => any }} options
|
|
115
|
+
* @returns {Promise<{ applied: string[], pending: string[],
|
|
116
|
+
* drift: string | null, upToDate: boolean }>}
|
|
117
|
+
*/
|
|
118
|
+
export declare function migrationStatus(target: {
|
|
119
|
+
driver: any;
|
|
120
|
+
path?: string;
|
|
121
|
+
}, migrations: any[], options: {
|
|
122
|
+
baseline: any;
|
|
123
|
+
model?: any;
|
|
124
|
+
registerFunctions?: (connection: any) => any;
|
|
125
|
+
}): Promise<{
|
|
126
|
+
applied: string[];
|
|
127
|
+
pending: string[];
|
|
128
|
+
drift: string | null;
|
|
129
|
+
upToDate: boolean;
|
|
130
|
+
}>;
|
|
131
|
+
/**
|
|
132
|
+
* Apply pending migrations to a database.
|
|
133
|
+
*
|
|
134
|
+
* The contract: `migrations` is the FULL ordered list (applied and
|
|
135
|
+
* pending — the migrations directory); `baseline` is the model the
|
|
136
|
+
* store was first created with (the chain's anchor and the shadow's
|
|
137
|
+
* starting shape); `model` is the target model the code now carries.
|
|
138
|
+
* Each pending migration runs in ONE exclusive transaction with a
|
|
139
|
+
* savepoint per step; a failing step rolls the whole migration back.
|
|
140
|
+
* The whole chain replays on a `:memory:` shadow before the real
|
|
141
|
+
* store is touched.
|
|
142
|
+
*
|
|
143
|
+
* @param {{ driver: any, path?: string, busyTimeout?: number }} target
|
|
144
|
+
* @param {any[]} migrations
|
|
145
|
+
* @param {{ baseline: any, model?: any, compileSchema?: Function,
|
|
146
|
+
* dryRun?: boolean, batchSize?: number, onProgress?: Function,
|
|
147
|
+
* shadow?: boolean, shadowPath?: string }} options
|
|
148
|
+
* @returns {Promise<any>}
|
|
149
|
+
*/
|
|
150
|
+
export declare function migrate(target: {
|
|
151
|
+
driver: any;
|
|
152
|
+
path?: string;
|
|
153
|
+
busyTimeout?: number;
|
|
154
|
+
}, migrations: any[], options: {
|
|
155
|
+
baseline: any;
|
|
156
|
+
model?: any;
|
|
157
|
+
compileSchema?: Function;
|
|
158
|
+
dryRun?: boolean;
|
|
159
|
+
batchSize?: number;
|
|
160
|
+
onProgress?: Function;
|
|
161
|
+
shadow?: boolean;
|
|
162
|
+
shadowPath?: string;
|
|
163
|
+
}): Promise<any>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The entity model walk: `x-entity` normalization with a CLOSED
|
|
3
|
+
* vocabulary, relation resolution with inverse agreement, and
|
|
4
|
+
* `explainMapping` — the derived physical shape as plain data, so the
|
|
5
|
+
* hybrid mapping rule is golden-testable rather than folklore.
|
|
6
|
+
*
|
|
7
|
+
* THE DESCENT DECISION (recorded here because TODO's D22 demands it
|
|
8
|
+
* be explicit): six copies of the `properties`/`prefixItems`/`items`/
|
|
9
|
+
* `allOf` descent spine exist in this repository, and this walk was
|
|
10
|
+
* the candidate seventh. It is NOT one. The entity walk is
|
|
11
|
+
* deliberately ONE level deep — it enumerates the TOP-LEVEL
|
|
12
|
+
* properties of an entity schema, resolves `$ref` and shallow-merges
|
|
13
|
+
* `allOf` at each property through the resolvers
|
|
14
|
+
* `@jarenjs/validate/normalize` exports for exactly this purpose, and
|
|
15
|
+
* never recurses further, because the mapping rule sends every nested
|
|
16
|
+
* shape to the JSONB document wholesale. A consumer with no recursion
|
|
17
|
+
* has no descent spine to share, so the shared-enumerator question
|
|
18
|
+
* (three different termination strategies across the six copies)
|
|
19
|
+
* stays open for the first consumer that actually recurses. No
|
|
20
|
+
* seventh copy was added.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Normalize the `entities` member of a model document.
|
|
24
|
+
* @param {any} model
|
|
25
|
+
* @returns {Map<string, any>} entity name -> normalized entity
|
|
26
|
+
*/
|
|
27
|
+
export declare function normalizeEntities(model: any): Map<string, any>;
|
|
28
|
+
/**
|
|
29
|
+
* The hybrid mapping, derived mechanically from §9.3's table and
|
|
30
|
+
* returned as DATA: per entity, the columns (name, type, source),
|
|
31
|
+
* the checks, the foreign keys, the indexes, and which properties
|
|
32
|
+
* live in the JSONB document.
|
|
33
|
+
* @param {any} model - a model document with `entities`
|
|
34
|
+
* @returns {any}
|
|
35
|
+
*/
|
|
36
|
+
export declare function explainMapping(model: any): any;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file RFC 6902 → dialect JSON-set primitives, so a one-field update
|
|
3
|
+
* does not rewrite a large document. The translation is decided
|
|
4
|
+
* AGAINST THE LIVE DOCUMENT: an RFC 6901 pointer cannot say whether
|
|
5
|
+
* `/a/0` names an array position or an object member called `"0"`, so
|
|
6
|
+
* each segment is discriminated by walking the document the patch was
|
|
7
|
+
* validated against, and the walked state is advanced op by op so a
|
|
8
|
+
* later operation sees what the earlier ones produced.
|
|
9
|
+
*
|
|
10
|
+
* Translatable in 0.1: `replace` anywhere, `add` of an object member,
|
|
11
|
+
* `add` at an array's end (`/-` or the index equal to its length), and
|
|
12
|
+
* `remove`. Everything else — `test`, `move`, `copy`, a mid-array
|
|
13
|
+
* insert (the shift has no single JSON-function spelling) — returns
|
|
14
|
+
* `null` and the store falls back to a whole-document write. The
|
|
15
|
+
* fallback is counted and exposed by the store, measured rather than
|
|
16
|
+
* assumed.
|
|
17
|
+
*/
|
|
18
|
+
export type JsonPathSegment = import('./dialect.js').JsonPathSegment;
|
|
19
|
+
/**
|
|
20
|
+
* Translate a whole patch into a dialect expression builder, or `null`
|
|
21
|
+
* when any operation needs the whole-document fallback. The caller has
|
|
22
|
+
* already applied the patch in memory (the copy-on-write engine
|
|
23
|
+
* validates the RESULT); this translation only decides how the same
|
|
24
|
+
* outcome reaches the database.
|
|
25
|
+
* @param {any[]} ops - RFC 6902 operations, already known applicable
|
|
26
|
+
* @param {any} doc - The stored document the patch applies to
|
|
27
|
+
* @param {any} dialect
|
|
28
|
+
* @returns {{ build: (docColumnSql: string,
|
|
29
|
+
* parameterIndexBase: number) => { expression: string,
|
|
30
|
+
* params: string[] } } | null}
|
|
31
|
+
*/
|
|
32
|
+
export declare function translatePatch(ops: any[], doc: any, dialect: any): {
|
|
33
|
+
build: (docColumnSql: string, parameterIndexBase: number) => {
|
|
34
|
+
expression: string;
|
|
35
|
+
params: string[];
|
|
36
|
+
};
|
|
37
|
+
} | null;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file AST → Plan. The planner walks the engine's PUBLISHED normalized
|
|
3
|
+
* AST (never the raw document), dispatches EXHAUSTIVELY on node kind —
|
|
4
|
+
* an unrecognised kind is an internal error naming the kind and the
|
|
5
|
+
* `AST_VERSION`, never a silent residual — and promotes constructs to
|
|
6
|
+
* native form strictly residual-by-default: everything starts as a
|
|
7
|
+
* residual and earns native status only where the equivalence proof
|
|
8
|
+
* exists (the truth table in ARCHITECTURE.md, pinned by the
|
|
9
|
+
* differential tests).
|
|
10
|
+
*
|
|
11
|
+
* The outcome of planning one document:
|
|
12
|
+
*
|
|
13
|
+
* { plan, mode: 'native' | 'row' | 'set', reasons, rowReturn }
|
|
14
|
+
*
|
|
15
|
+
* - `native` — everything translated; the plan alone answers.
|
|
16
|
+
* - `row` — predicates, ordering and window pushed; only the
|
|
17
|
+
* projection runs in the engine, per fetched row (streams).
|
|
18
|
+
* - `set` — the pushed conjuncts narrow candidates; the WHOLE
|
|
19
|
+
* compiled document runs over the materialized candidates.
|
|
20
|
+
*
|
|
21
|
+
* `reasons` names every construct that forced work off the database,
|
|
22
|
+
* with reason text drawn from the deliberate-residual table.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Assert a node kind is one this planner has decided. Called on every
|
|
26
|
+
* dispatch; the throw names the kind and the AST version so a language
|
|
27
|
+
* change breaks the build instead of becoming an accidental residual.
|
|
28
|
+
* Exported so the throw itself is pinned by a test.
|
|
29
|
+
* @param {any} node
|
|
30
|
+
*/
|
|
31
|
+
export declare function assertDecidedKind(node: any): void;
|
|
32
|
+
/**
|
|
33
|
+
* Plan a whole document against one collection. The store's registered
|
|
34
|
+
* operators (Ring 2) ride in `shape.operators` — the planner recognises
|
|
35
|
+
* them as vocabulary but keeps them in the residual, and names them in
|
|
36
|
+
* the reasons when it does.
|
|
37
|
+
* @param {any} document - The raw query document (kept beside the AST
|
|
38
|
+
* for residual construction — the AST has no unparser)
|
|
39
|
+
* @param {any} shape - { collection, schema, columnByCanonical, operators? }
|
|
40
|
+
* @param {{ udf?: (fragment: any) => { name: string, key: string } | null }} [options]
|
|
41
|
+
* @returns {{
|
|
42
|
+
* analysis: any,
|
|
43
|
+
* plan: import('./algebra.js').Plan | null,
|
|
44
|
+
* mode: 'native' | 'row' | 'set',
|
|
45
|
+
* reasons: { construct: string, reason: string }[],
|
|
46
|
+
* rowReturn: any,
|
|
47
|
+
* udfs: string[],
|
|
48
|
+
* }}
|
|
49
|
+
*/
|
|
50
|
+
export declare function planQuery(document: any, shape: any, options?: {
|
|
51
|
+
udf?: (fragment: any) => {
|
|
52
|
+
name: string;
|
|
53
|
+
key: string;
|
|
54
|
+
} | null;
|
|
55
|
+
}): {
|
|
56
|
+
analysis: any;
|
|
57
|
+
plan: import('./algebra.js').Plan | null;
|
|
58
|
+
mode: 'native' | 'row' | 'set';
|
|
59
|
+
reasons: {
|
|
60
|
+
construct: string;
|
|
61
|
+
reason: string;
|
|
62
|
+
}[];
|
|
63
|
+
rowReturn: any;
|
|
64
|
+
udfs: string[];
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Build the planner shape for one entity: canonical top-level paths
|
|
68
|
+
* map to REAL columns (flavor `entity-column`), epoch date columns to
|
|
69
|
+
* their derived integer twins (flavor `entity-epoch`), and everything
|
|
70
|
+
* else stays a document path over the entity's JSONB column (the
|
|
71
|
+
* phase-A guarded forms).
|
|
72
|
+
* @param {any} entity - normalized entity (model.js)
|
|
73
|
+
* @param {any} entityMapping - explainMapping(...).entities[name]
|
|
74
|
+
* @returns {any}
|
|
75
|
+
*/
|
|
76
|
+
export declare function entityShape(entity: any, entityMapping: any): any;
|
|
77
|
+
/**
|
|
78
|
+
* Resolve a singular member path on an entity binding to a flavored
|
|
79
|
+
* PlanRef.
|
|
80
|
+
* @param {any} node - a path AST node
|
|
81
|
+
* @param {number} slot
|
|
82
|
+
* @param {any} shape - from {@link entityShape}
|
|
83
|
+
* @returns {any | null}
|
|
84
|
+
*/
|
|
85
|
+
export declare function entityPathRef(node: any, slot: number, shape: any): any | null;
|
|
86
|
+
/**
|
|
87
|
+
* Plan one predicate over an entity binding: the same operator
|
|
88
|
+
* grammar as phase A, with entity-flavored refs. Reuses
|
|
89
|
+
* {@link planPredicate} for the recognition, then re-resolves refs
|
|
90
|
+
* through the flavor table.
|
|
91
|
+
* @param {any} node
|
|
92
|
+
* @param {number} slot
|
|
93
|
+
* @param {any} shape
|
|
94
|
+
* @returns {{ pred: any } | { refusal: { construct: string, reason: string } }}
|
|
95
|
+
*/
|
|
96
|
+
export declare function planEntityPredicate(node: any, slot: number, shape: any): {
|
|
97
|
+
pred: any;
|
|
98
|
+
} | {
|
|
99
|
+
refusal: {
|
|
100
|
+
construct: string;
|
|
101
|
+
reason: string;
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Plan an ENTITY query document (one planner, two document kinds). The
|
|
106
|
+
* store's registered operators (Ring 2) are recognised as vocabulary and
|
|
107
|
+
* kept in the set residual over the fetched root, named in the reasons.
|
|
108
|
+
* @param {any} document
|
|
109
|
+
* @param {Map<string, any>} entities - normalized entities
|
|
110
|
+
* @param {any} mapping - explainMapping result
|
|
111
|
+
* @param {{ functions?: any, extensions?: any } | null} [operators]
|
|
112
|
+
* @returns {any}
|
|
113
|
+
*/
|
|
114
|
+
export declare function planEntityQuery(document: any, entities: Map<string, any>, mapping: any, operators?: {
|
|
115
|
+
functions?: any;
|
|
116
|
+
extensions?: any;
|
|
117
|
+
} | null): any;
|
|
118
|
+
/** The entity names a document's root paths reference (`$.Name[*]`). */
|
|
119
|
+
export declare function collectEntityRoots(document: any, entities: any): Set<any>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The safe execution profile (D15): a query document that arrives
|
|
3
|
+
* from a tenant, a remote client or a language model can reach a
|
|
4
|
+
* database, and injection being structurally impossible (parameter
|
|
5
|
+
* binding) says nothing about resource exhaustion or cross-tenant
|
|
6
|
+
* reads. A profile composes four INDEPENDENT bounds:
|
|
7
|
+
*
|
|
8
|
+
* 1. engine limits — `{sequenceItems, resultItems, steps, depth}`
|
|
9
|
+
* wired into every residual compilation, so the JavaScript
|
|
10
|
+
* portion of a query is bounded by the engine's own enforcement;
|
|
11
|
+
* 2. the mandatory row bound — every non-aggregate fetch carries a
|
|
12
|
+
* LIMIT of `maxRows + 1`, and fetching more than `maxRows` rows is
|
|
13
|
+
* the coded `JD2007`, never a silent truncation (D14);
|
|
14
|
+
* 3. reference containment — undeclared externals, host functions,
|
|
15
|
+
* collations or collections are the compile error `JD0011`; no UDF
|
|
16
|
+
* registration happens under a profile; optionally, a plan whose
|
|
17
|
+
* database narrative shows a full-table SCAN is refused;
|
|
18
|
+
* 4. mandatory predicates — a per-collection predicate conjoined into
|
|
19
|
+
* EVERY plan at its root, after translation, so no document shape
|
|
20
|
+
* can produce a fetch without it.
|
|
21
|
+
*
|
|
22
|
+
* The non-claims are part of the contract and live in
|
|
23
|
+
* MODEL-FORMAT.md §8: no statement timeout exists on the SQLite
|
|
24
|
+
* drivers (the capability slot is empty), so a long-running native
|
|
25
|
+
* aggregate is bounded by nothing here; the row bound covers fetched
|
|
26
|
+
* rows, not database-internal work.
|
|
27
|
+
*/
|
|
28
|
+
/** The `'safe'` profile: the documented defaults. */
|
|
29
|
+
export declare const SAFE_PROFILE: Readonly<{
|
|
30
|
+
limits: Readonly<{
|
|
31
|
+
sequenceItems: 100000;
|
|
32
|
+
resultItems: 10000;
|
|
33
|
+
steps: 1000000;
|
|
34
|
+
depth: 32;
|
|
35
|
+
}>;
|
|
36
|
+
maxRows: 1000;
|
|
37
|
+
externals: readonly never[];
|
|
38
|
+
functions: readonly never[];
|
|
39
|
+
collations: readonly never[];
|
|
40
|
+
collections: null;
|
|
41
|
+
predicates: Readonly<{}>;
|
|
42
|
+
refuseFullScan: false;
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* Normalize a profile option: the string `'safe'` is the default
|
|
46
|
+
* table; an object overrides individual members over those defaults
|
|
47
|
+
* (limits merge member-wise). The result is plain JSON — cacheable by
|
|
48
|
+
* content key — and frozen.
|
|
49
|
+
* @param {any} profile - `'safe'` or a partial profile object
|
|
50
|
+
* @returns {any}
|
|
51
|
+
*/
|
|
52
|
+
export declare function normalizeProfile(profile: any): any;
|
|
53
|
+
/**
|
|
54
|
+
* Translate a profile's mandatory predicate for one collection into a
|
|
55
|
+
* plan predicate. The predicate is HOST-authored configuration, so a
|
|
56
|
+
* predicate that does not translate natively is a host programming
|
|
57
|
+
* error (TypeError), not a coded document failure — there is no
|
|
58
|
+
* residual to hide it in: the whole point is that it binds the
|
|
59
|
+
* database-side fetch.
|
|
60
|
+
* @param {any} expression - A query expression over `$it`
|
|
61
|
+
* @param {any} shape - The collection's plan shape
|
|
62
|
+
* @returns {import('./algebra.js').PlanPredicate}
|
|
63
|
+
*/
|
|
64
|
+
export declare function translateProfilePredicate(expression: any, shape: any): import('./algebra.js').PlanPredicate;
|
|
65
|
+
/**
|
|
66
|
+
* Conjoin a mandatory predicate into a plan's root filter.
|
|
67
|
+
* @param {import('./algebra.js').Plan} plan
|
|
68
|
+
* @param {import('./algebra.js').PlanPredicate | null} predicate
|
|
69
|
+
* @returns {import('./algebra.js').Plan}
|
|
70
|
+
*/
|
|
71
|
+
export declare function applyMandatoryPredicate(plan: import('./algebra.js').Plan, predicate: import('./algebra.js').PlanPredicate | null): import('./algebra.js').Plan;
|
|
72
|
+
/**
|
|
73
|
+
* Cap a plan's window at the profile's detection bound
|
|
74
|
+
* (`maxRows + 1`): a result crossing `maxRows` is detected and
|
|
75
|
+
* refused, never silently truncated. Aggregates are exempt (one row).
|
|
76
|
+
* @param {import('./algebra.js').Plan} plan
|
|
77
|
+
* @param {number} maxRows
|
|
78
|
+
* @returns {import('./algebra.js').Plan}
|
|
79
|
+
*/
|
|
80
|
+
export declare function applyRowBound(plan: import('./algebra.js').Plan, maxRows: number): import('./algebra.js').Plan;
|