@jarenjs/db 0.49.2 → 0.66.1

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 (110) hide show
  1. package/ARCHITECTURE.md +420 -71
  2. package/README.md +711 -79
  3. package/docs/HOSTS.md +269 -0
  4. package/docs/JOBS-FORMAT.md +309 -45
  5. package/docs/LIVE-FORMAT.md +156 -19
  6. package/docs/MIGRATION-FORMAT.md +247 -40
  7. package/docs/MODEL-FORMAT.md +968 -86
  8. package/package.json +21 -8
  9. package/schemas/jaren-migration.draft-07.schema.json +73 -0
  10. package/schemas/jaren-migration.schema.json +73 -0
  11. package/schemas/jaren-model.draft-07.schema.json +224 -162
  12. package/schemas/jaren-model.schema.json +224 -162
  13. package/src/algebra.js +227 -9
  14. package/src/backup.js +161 -0
  15. package/src/cancellation.js +48 -0
  16. package/src/capture.js +255 -44
  17. package/src/cli.js +337 -50
  18. package/src/cursor.js +411 -0
  19. package/src/dag-job.js +154 -21
  20. package/src/ddl.js +125 -11
  21. package/src/dialect.js +267 -112
  22. package/src/dialects/expression-read.js +158 -0
  23. package/src/dialects/postgres.js +618 -0
  24. package/src/dialects/rtree-ddl.js +129 -0
  25. package/src/dialects/sqlite.js +245 -12
  26. package/src/document-files.js +311 -0
  27. package/src/document-steps.js +422 -0
  28. package/src/documents.js +335 -0
  29. package/src/driver.js +503 -69
  30. package/src/drivers/bun.js +37 -1
  31. package/src/drivers/indexeddb-snapshot.js +149 -0
  32. package/src/drivers/node-pool.js +11 -0
  33. package/src/drivers/node-worker-endpoint.js +105 -0
  34. package/src/drivers/node-worker.js +204 -0
  35. package/src/drivers/node.js +41 -7
  36. package/src/drivers/postgres.js +331 -0
  37. package/src/drivers/wasm-oo1.js +97 -0
  38. package/src/drivers/wasm-session.js +67 -0
  39. package/src/drivers/wasm.js +18 -83
  40. package/src/drivers/worker-pool.js +183 -0
  41. package/src/drivers/worker-protocol.js +79 -0
  42. package/src/drivers/worker-queue.js +60 -0
  43. package/src/emit-model.js +14 -0
  44. package/src/emit.js +349 -51
  45. package/src/entity.js +102 -59
  46. package/src/errors.js +430 -2
  47. package/src/expression.js +284 -0
  48. package/src/graph.js +64 -8
  49. package/src/index.js +48 -19
  50. package/src/introspect.js +583 -0
  51. package/src/jobs.js +870 -99
  52. package/src/json-bytes.js +58 -0
  53. package/src/live-time.js +12 -3
  54. package/src/live.js +11 -1
  55. package/src/maintenance.js +175 -0
  56. package/src/migrate.js +606 -333
  57. package/src/model.js +241 -8
  58. package/src/plan.js +1238 -160
  59. package/src/pragmas.js +314 -0
  60. package/src/profile.js +151 -3
  61. package/src/query.js +1748 -312
  62. package/src/residual.js +17 -0
  63. package/src/series.js +12 -4
  64. package/src/store.js +1672 -276
  65. package/src/tracker.js +367 -68
  66. package/src/udf.js +88 -7
  67. package/types/index.d.ts +1246 -32
  68. package/types/node-pool.d.ts +28 -0
  69. package/types/node-worker.d.ts +54 -0
  70. package/types/node.d.ts +72 -3
  71. package/types/postgres.d.ts +46 -0
  72. package/types/typed.d.ts +81 -3
  73. package/types/wasm.d.ts +21 -0
  74. package/dist/types/algebra.d.ts +0 -230
  75. package/dist/types/app.d.ts +0 -49
  76. package/dist/types/capture.d.ts +0 -85
  77. package/dist/types/cli.d.ts +0 -2
  78. package/dist/types/dag-job.d.ts +0 -40
  79. package/dist/types/ddl.d.ts +0 -229
  80. package/dist/types/derive.d.ts +0 -250
  81. package/dist/types/dialect.d.ts +0 -154
  82. package/dist/types/dialects/sqlite.d.ts +0 -9
  83. package/dist/types/driver.d.ts +0 -110
  84. package/dist/types/drivers/bun.d.ts +0 -47
  85. package/dist/types/drivers/node.d.ts +0 -37
  86. package/dist/types/drivers/wasm.d.ts +0 -65
  87. package/dist/types/emit-model.d.ts +0 -44
  88. package/dist/types/emit.d.ts +0 -75
  89. package/dist/types/entity.d.ts +0 -23
  90. package/dist/types/errors.d.ts +0 -170
  91. package/dist/types/graph.d.ts +0 -28
  92. package/dist/types/index.d.ts +0 -37
  93. package/dist/types/jobs.d.ts +0 -140
  94. package/dist/types/knn.d.ts +0 -69
  95. package/dist/types/live-time.d.ts +0 -141
  96. package/dist/types/live.d.ts +0 -64
  97. package/dist/types/migrate.d.ts +0 -170
  98. package/dist/types/model.d.ts +0 -36
  99. package/dist/types/patch-sql.d.ts +0 -37
  100. package/dist/types/plan.d.ts +0 -142
  101. package/dist/types/profile.d.ts +0 -80
  102. package/dist/types/query.d.ts +0 -112
  103. package/dist/types/residual.d.ts +0 -64
  104. package/dist/types/series.d.ts +0 -227
  105. package/dist/types/store.d.ts +0 -60
  106. package/dist/types/tracker.d.ts +0 -43
  107. package/dist/types/typed.d.ts +0 -15
  108. package/dist/types/types.d.ts +0 -26
  109. package/dist/types/udf.d.ts +0 -75
  110. package/dist/types/window.d.ts +0 -52
package/src/udf.js CHANGED
@@ -16,10 +16,16 @@
16
16
  * fingerprint clash between DIFFERENT identities is disambiguated with
17
17
  * a suffix rather than collapsed.
18
18
  *
19
- * WHERE-clause use only: an INDEX over a registered function would
20
- * make the database unwritable from any connection that has not
21
- * registered the identical function that schema-dependency hazard is
22
- * why the model format declares no UDF-expression indexes.
19
+ * WHERE-clause use only, and that is the difference between this hatch
20
+ * and a DECLARED index expression (MODEL-FORMAT §7A). An index over a
21
+ * registered function makes the database unwritable from a connection
22
+ * that has not registered the identical function; a fragment registered
23
+ * here is a QUERY's, discovered from the caller's document at run time,
24
+ * and indexing one would make a passing query a permanent schema
25
+ * dependency nobody declared. A model's `indexes[].expression` carries
26
+ * exactly that dependency in the model, where every store that opens it
27
+ * is handed the same declaration and one that cannot honour it refuses
28
+ * at open.
23
29
  *
24
30
  * Ring 3 extends the hatch to registry `pushable:'scalar'`
25
31
  * operators: a predicate fragment that uses a registered scalar operator
@@ -34,7 +40,7 @@
34
40
 
35
41
  import { semanticKey } from '@jarenjs/core/object';
36
42
  import { hashContent } from '@jarenjs/core/string';
37
- import { compileJsonQuery, analyzeQuery } from '@jarenjs/json/query';
43
+ import { compileJsonQuery, analyzeQuery, JsonQueryRuntimeError } from '@jarenjs/json/query';
38
44
 
39
45
  /**
40
46
  * The SQL identifier for one fragment identity: a short fingerprint of
@@ -61,7 +67,14 @@ const functionNameFor = (identity) => `jaren_p_${hashContent(identity)}`;
61
67
  * external, the determinism check below rejects the fragment, and the
62
68
  * hatch silently never engages.
63
69
  * @returns {{ key: string, name: string,
64
- * compile: () => (docText: string) => number } | null}
70
+ * compile: () => (docText: string, mount?: string) => number } | null}
71
+ * - the compiled function takes the row's document text and the
72
+ * conjunct's JSON Pointer in the CALLER's document (`/$where`, or
73
+ * `/$where/$and/<i>`), which the emitter passes as a literal: an
74
+ * engine error raised inside names the wrapper's path (`/$return/…`)
75
+ * and is rebased onto that mount, so the native mode and the residual
76
+ * report the same location while one registration still serves every
77
+ * document that carries the fragment
65
78
  */
66
79
  export function deterministicFragment(fragment, operators = null, binding = 'it') {
67
80
  const analyzeOpts = operators === null
@@ -103,6 +116,7 @@ export function deterministicFragment(fragment, operators = null, binding = 'it'
103
116
  // correct, so it does not qualify for the hatch
104
117
  return null;
105
118
  }
119
+ const WRAPPER = '/$return';
106
120
  return {
107
121
  key,
108
122
  name: functionNameFor(key),
@@ -111,7 +125,25 @@ export function deterministicFragment(fragment, operators = null, binding = 'it'
111
125
  // different name than the analysis would judge one document and
112
126
  // run another
113
127
  const compiled = compileJsonQuery(wrap(fragment), analyzeOpts);
114
- return (docText) => (compiled.ebv(JSON.parse(docText)) ? 1 : 0);
128
+ // two declared parameters on purpose: node:sqlite registers the
129
+ // function with the arity `fn.length` reports, and the emitter
130
+ // always passes the mount beside the document
131
+ return (docText, mount) => {
132
+ try {
133
+ return compiled.ebv(JSON.parse(docText)) ? 1 : 0;
134
+ }
135
+ catch (error) {
136
+ // the engine's own refusal, relocated from the wrapper onto
137
+ // the caller's document; anything else propagates as it is
138
+ if (error instanceof JsonQueryRuntimeError && typeof error.docPath === 'string'
139
+ && error.docPath.startsWith(WRAPPER)) {
140
+ throw new JsonQueryRuntimeError(error.code, error.reason,
141
+ (typeof mount === 'string' ? mount : '/$where') + error.docPath.slice(WRAPPER.length),
142
+ Object.hasOwn(error, 'cause') ? { cause: error.cause } : undefined);
143
+ }
144
+ throw error;
145
+ }
146
+ };
115
147
  },
116
148
  };
117
149
  }
@@ -139,3 +171,52 @@ export function registerFragment(connection, registered, fragment) {
139
171
  registered.set(fragment.key, name);
140
172
  return name;
141
173
  }
174
+
175
+ /**
176
+ * The SQL identifier for one registered aggregate. Unlike a predicate
177
+ * fragment, the identity IS the operator name — one registry, one
178
+ * function per name — so the fingerprint has nothing to disambiguate.
179
+ * @param {string} name
180
+ * @returns {string}
181
+ */
182
+ const aggregateNameFor = (name) => `jaren_a_${hashContent(name)}`;
183
+
184
+ /**
185
+ * Register a pushable aggregate once per store and answer the SQL name
186
+ * to call. The SQL fold accumulates the column's values and hands them
187
+ * to the SAME pure function the residual would call, so the two sides
188
+ * differ in who drives the loop and in nothing else.
189
+ *
190
+ * A `NULL` column value is SKIPPED, because the engine's sequence has no
191
+ * item where the member is absent — which is why only a path the schema
192
+ * types as a number that cannot hold `null` reaches here: a stored
193
+ * `null` and an absent member are one value in SQL, and dropping a
194
+ * present `null` would answer where the engine does not.
195
+ *
196
+ * `undefined` — what these summaries answer for an input they cannot
197
+ * summarise — becomes SQL `NULL`, which the aggregate decoder reads back
198
+ * as the empty answer, exactly as the engine's empty sequence does.
199
+ * @param {any} connection
200
+ * @param {Map<string, string>} registered - operator name → SQL name
201
+ * @param {string} name - the registry operator name (`$mean`)
202
+ * @param {{ fn: Function }} spec
203
+ * @returns {string} the SQL function name to call
204
+ */
205
+ export function registerAggregateOperator(connection, registered, name, spec) {
206
+ const owned = registered.get(name);
207
+ if (owned !== undefined) return owned;
208
+ const sqlName = aggregateNameFor(name);
209
+ connection.registerAggregate(sqlName, {
210
+ start: () => [],
211
+ step: (values, value) => {
212
+ if (value !== null && value !== undefined) values.push(value);
213
+ return values;
214
+ },
215
+ result: (values) => {
216
+ const out = spec.fn(values);
217
+ return out === undefined || out === null ? null : out;
218
+ },
219
+ });
220
+ registered.set(name, sqlName);
221
+ return sqlName;
222
+ }