@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.
Files changed (83) hide show
  1. package/ARCHITECTURE.md +397 -0
  2. package/README.md +218 -0
  3. package/dist/types/algebra.d.ts +133 -0
  4. package/dist/types/app.d.ts +49 -0
  5. package/dist/types/capture.d.ts +85 -0
  6. package/dist/types/cli.d.ts +2 -0
  7. package/dist/types/dag-job.d.ts +40 -0
  8. package/dist/types/ddl.d.ts +170 -0
  9. package/dist/types/dialect.d.ts +130 -0
  10. package/dist/types/dialects/sqlite.d.ts +9 -0
  11. package/dist/types/driver.d.ts +128 -0
  12. package/dist/types/drivers/bun.d.ts +47 -0
  13. package/dist/types/drivers/node.d.ts +37 -0
  14. package/dist/types/drivers/wasm.d.ts +65 -0
  15. package/dist/types/emit-model.d.ts +44 -0
  16. package/dist/types/emit.d.ts +72 -0
  17. package/dist/types/entity.d.ts +23 -0
  18. package/dist/types/errors.d.ts +165 -0
  19. package/dist/types/graph.d.ts +28 -0
  20. package/dist/types/index.d.ts +35 -0
  21. package/dist/types/jobs.d.ts +134 -0
  22. package/dist/types/live.d.ts +62 -0
  23. package/dist/types/migrate.d.ts +163 -0
  24. package/dist/types/model.d.ts +36 -0
  25. package/dist/types/patch-sql.d.ts +37 -0
  26. package/dist/types/plan.d.ts +119 -0
  27. package/dist/types/profile.d.ts +80 -0
  28. package/dist/types/query.d.ts +100 -0
  29. package/dist/types/residual.d.ts +50 -0
  30. package/dist/types/store.d.ts +53 -0
  31. package/dist/types/tracker.d.ts +43 -0
  32. package/dist/types/typed.d.ts +15 -0
  33. package/dist/types/types.d.ts +26 -0
  34. package/dist/types/udf.d.ts +70 -0
  35. package/dist/types/window.d.ts +52 -0
  36. package/docs/JOBS-FORMAT.md +218 -0
  37. package/docs/LIVE-FORMAT.md +348 -0
  38. package/docs/MIGRATION-FORMAT.md +302 -0
  39. package/docs/MODEL-FORMAT.md +928 -0
  40. package/package.json +81 -0
  41. package/schemas/jaren-migration.draft-07.schema.json +144 -0
  42. package/schemas/jaren-migration.schema.json +144 -0
  43. package/schemas/jaren-model.draft-07.schema.json +149 -0
  44. package/schemas/jaren-model.schema.json +149 -0
  45. package/src/algebra.js +105 -0
  46. package/src/app.js +108 -0
  47. package/src/capture.js +584 -0
  48. package/src/cli.js +264 -0
  49. package/src/dag-job.js +86 -0
  50. package/src/ddl.js +588 -0
  51. package/src/dialect.js +297 -0
  52. package/src/dialects/sqlite.js +175 -0
  53. package/src/driver.js +419 -0
  54. package/src/drivers/bun.js +101 -0
  55. package/src/drivers/node.js +93 -0
  56. package/src/drivers/wasm.js +178 -0
  57. package/src/emit-model.js +208 -0
  58. package/src/emit.js +393 -0
  59. package/src/entity.js +367 -0
  60. package/src/errors.js +173 -0
  61. package/src/graph.js +101 -0
  62. package/src/index.js +64 -0
  63. package/src/jobs.js +507 -0
  64. package/src/live.js +899 -0
  65. package/src/migrate.js +1411 -0
  66. package/src/model.js +476 -0
  67. package/src/patch-sql.js +150 -0
  68. package/src/plan.js +1038 -0
  69. package/src/profile.js +131 -0
  70. package/src/query.js +1010 -0
  71. package/src/residual.js +91 -0
  72. package/src/store.js +1422 -0
  73. package/src/tracker.js +776 -0
  74. package/src/typed.js +19 -0
  75. package/src/types.js +36 -0
  76. package/src/udf.js +132 -0
  77. package/src/window.js +125 -0
  78. package/types/app.d.ts +36 -0
  79. package/types/bun.d.ts +9 -0
  80. package/types/index.d.ts +592 -0
  81. package/types/node.d.ts +15 -0
  82. package/types/typed.d.ts +108 -0
  83. 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
+ }