@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
package/src/profile.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file The safe execution profile (D15): a query document that arrives
|
|
4
|
+
* from a tenant, a remote client or a language model can reach a
|
|
5
|
+
* database, and injection being structurally impossible (parameter
|
|
6
|
+
* binding) says nothing about resource exhaustion or cross-tenant
|
|
7
|
+
* reads. A profile composes four INDEPENDENT bounds:
|
|
8
|
+
*
|
|
9
|
+
* 1. engine limits — `{sequenceItems, resultItems, steps, depth}`
|
|
10
|
+
* wired into every residual compilation, so the JavaScript
|
|
11
|
+
* portion of a query is bounded by the engine's own enforcement;
|
|
12
|
+
* 2. the mandatory row bound — every non-aggregate fetch carries a
|
|
13
|
+
* LIMIT of `maxRows + 1`, and fetching more than `maxRows` rows is
|
|
14
|
+
* the coded `JD2007`, never a silent truncation (D14);
|
|
15
|
+
* 3. reference containment — undeclared externals, host functions,
|
|
16
|
+
* collations or collections are the compile error `JD0011`; no UDF
|
|
17
|
+
* registration happens under a profile; optionally, a plan whose
|
|
18
|
+
* database narrative shows a full-table SCAN is refused;
|
|
19
|
+
* 4. mandatory predicates — a per-collection predicate conjoined into
|
|
20
|
+
* EVERY plan at its root, after translation, so no document shape
|
|
21
|
+
* can produce a fetch without it.
|
|
22
|
+
*
|
|
23
|
+
* The non-claims are part of the contract and live in
|
|
24
|
+
* MODEL-FORMAT.md §8: no statement timeout exists on the SQLite
|
|
25
|
+
* drivers (the capability slot is empty), so a long-running native
|
|
26
|
+
* aggregate is bounded by nothing here; the row bound covers fetched
|
|
27
|
+
* rows, not database-internal work.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { planQuery } from './plan.js';
|
|
31
|
+
import { conjoin } from './algebra.js';
|
|
32
|
+
|
|
33
|
+
/** The `'safe'` profile: the documented defaults. */
|
|
34
|
+
export const SAFE_PROFILE = Object.freeze({
|
|
35
|
+
limits: Object.freeze({
|
|
36
|
+
sequenceItems: 100_000,
|
|
37
|
+
resultItems: 10_000,
|
|
38
|
+
steps: 1_000_000,
|
|
39
|
+
depth: 32,
|
|
40
|
+
}),
|
|
41
|
+
maxRows: 1000,
|
|
42
|
+
externals: Object.freeze([]),
|
|
43
|
+
functions: Object.freeze([]),
|
|
44
|
+
collations: Object.freeze([]),
|
|
45
|
+
collections: null,
|
|
46
|
+
predicates: Object.freeze({}),
|
|
47
|
+
refuseFullScan: false,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Normalize a profile option: the string `'safe'` is the default
|
|
52
|
+
* table; an object overrides individual members over those defaults
|
|
53
|
+
* (limits merge member-wise). The result is plain JSON — cacheable by
|
|
54
|
+
* content key — and frozen.
|
|
55
|
+
* @param {any} profile - `'safe'` or a partial profile object
|
|
56
|
+
* @returns {any}
|
|
57
|
+
*/
|
|
58
|
+
export function normalizeProfile(profile) {
|
|
59
|
+
if (profile === 'safe') return SAFE_PROFILE;
|
|
60
|
+
if (profile === null || typeof profile !== 'object' || Array.isArray(profile))
|
|
61
|
+
throw new TypeError("profile must be 'safe' or a profile object");
|
|
62
|
+
const merged = {
|
|
63
|
+
limits: Object.freeze({ ...SAFE_PROFILE.limits, ...profile.limits }),
|
|
64
|
+
maxRows: profile.maxRows ?? SAFE_PROFILE.maxRows,
|
|
65
|
+
externals: Object.freeze([...(profile.externals ?? SAFE_PROFILE.externals)]),
|
|
66
|
+
functions: Object.freeze([...(profile.functions ?? SAFE_PROFILE.functions)]),
|
|
67
|
+
collations: Object.freeze([...(profile.collations ?? SAFE_PROFILE.collations)]),
|
|
68
|
+
collections: profile.collections === undefined
|
|
69
|
+
? SAFE_PROFILE.collections
|
|
70
|
+
: profile.collections === null ? null : Object.freeze([...profile.collections]),
|
|
71
|
+
predicates: Object.freeze({ ...profile.predicates }),
|
|
72
|
+
refuseFullScan: profile.refuseFullScan === true,
|
|
73
|
+
};
|
|
74
|
+
if (typeof merged.maxRows !== 'number' || !Number.isInteger(merged.maxRows)
|
|
75
|
+
|| merged.maxRows < 1)
|
|
76
|
+
throw new TypeError('profile.maxRows must be a positive integer');
|
|
77
|
+
return Object.freeze(merged);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Translate a profile's mandatory predicate for one collection into a
|
|
82
|
+
* plan predicate. The predicate is HOST-authored configuration, so a
|
|
83
|
+
* predicate that does not translate natively is a host programming
|
|
84
|
+
* error (TypeError), not a coded document failure — there is no
|
|
85
|
+
* residual to hide it in: the whole point is that it binds the
|
|
86
|
+
* database-side fetch.
|
|
87
|
+
* @param {any} expression - A query expression over `$it`
|
|
88
|
+
* @param {any} shape - The collection's plan shape
|
|
89
|
+
* @returns {import('./algebra.js').PlanPredicate}
|
|
90
|
+
*/
|
|
91
|
+
export function translateProfilePredicate(expression, shape) {
|
|
92
|
+
const planned = planQuery(
|
|
93
|
+
{ $for: { it: '$[*]' }, $where: expression, $return: '$it' }, shape);
|
|
94
|
+
if (planned.mode !== 'native' || planned.plan.filter === null) {
|
|
95
|
+
throw new TypeError(
|
|
96
|
+
'a profile predicate must translate natively (it binds the database-side fetch); '
|
|
97
|
+
+ `this one refused: ${planned.reasons.map((r) => r.construct).join(', ')}`);
|
|
98
|
+
}
|
|
99
|
+
return planned.plan.filter;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Conjoin a mandatory predicate into a plan's root filter.
|
|
104
|
+
* @param {import('./algebra.js').Plan} plan
|
|
105
|
+
* @param {import('./algebra.js').PlanPredicate | null} predicate
|
|
106
|
+
* @returns {import('./algebra.js').Plan}
|
|
107
|
+
*/
|
|
108
|
+
export function applyMandatoryPredicate(plan, predicate) {
|
|
109
|
+
if (predicate === null) return plan;
|
|
110
|
+
return { ...plan, filter: conjoin(plan.filter, predicate) };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Cap a plan's window at the profile's detection bound
|
|
115
|
+
* (`maxRows + 1`): a result crossing `maxRows` is detected and
|
|
116
|
+
* refused, never silently truncated. Aggregates are exempt (one row).
|
|
117
|
+
* @param {import('./algebra.js').Plan} plan
|
|
118
|
+
* @param {number} maxRows
|
|
119
|
+
* @returns {import('./algebra.js').Plan}
|
|
120
|
+
*/
|
|
121
|
+
export function applyRowBound(plan, maxRows) {
|
|
122
|
+
if (plan.aggregate !== null) return plan;
|
|
123
|
+
const cap = maxRows + 1;
|
|
124
|
+
const window = plan.window === null
|
|
125
|
+
? { offset: 0, limit: cap }
|
|
126
|
+
: {
|
|
127
|
+
offset: plan.window.offset,
|
|
128
|
+
limit: plan.window.limit === null ? cap : Math.min(plan.window.limit, cap),
|
|
129
|
+
};
|
|
130
|
+
return { ...plan, window };
|
|
131
|
+
}
|