@nanobpm/urban 0.81.0 → 0.82.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/dist/runtime/core/modules/workers.js +25 -1
- package/dist/runtime/core/read-model.d.ts +268 -5
- package/dist/runtime/core/read-model.js +1033 -35
- package/dist/runtime/index.d.ts +2 -2
- package/dist/runtime/index.js +6 -1
- package/package.json +1 -1
- package/src/runtime/core/modules/workers.ts +24 -1
- package/src/runtime/core/read-model.test.ts +27 -2
- package/src/runtime/core/read-model.ts +1313 -41
- package/src/runtime/core/rollup.test.ts +818 -0
- package/src/runtime/index.ts +43 -0
package/dist/runtime/index.d.ts
CHANGED
|
@@ -40,8 +40,8 @@ export { applyAmbientLineage, buildLineageTree, deriveLineage, LINEAGE_KEY, LINE
|
|
|
40
40
|
export type { ApplyLineageOptions, LineageAttachment, LineageEdge, LineageEdgeType, LineageEnvelope, LineageNode, LineageTree, } from "./core/lineage.ts";
|
|
41
41
|
export { LINEAGE_ATTACHMENTS_TABLE, LINEAGE_EDGES_TABLE, LINEAGE_SCHEMA_SQL, LineageStore, systemClock as lineageSystemClock, } from "./core/modules/lineage-store.ts";
|
|
42
42
|
export type { Clock as LineageClock, LineageJobLike, LineageStoreOptions, } from "./core/modules/lineage-store.ts";
|
|
43
|
-
export { and, assertReadModelParity, assertSqlIdentifier, caseWhen, col, compileToFn, compileToSqlSelect, defineReadModel, deriveReadModelViewDdl, eq, exists, gt, gte, lit, lt, lte, neq, not, or, pcol, ProjectionRegistry, projectionRegistry, ReadModelRegistry, readModelRegistry, when, } from "./core/read-model.ts";
|
|
44
|
-
export type { AndExpr, BaseRow, CaseExpr, ColExpr, CompareExpr, CompareOp, DerivationFn, ExistsExpr, Expr, Literal, LitExpr, NotExpr, OrExpr, ParityDb, ParityOptions, ParitySample, ProjColExpr, ProjectionRows, ProjectionSource, ReadModel, ReadModelDecl, SqlCompileOptions, WhenClause, } from "./core/read-model.ts";
|
|
43
|
+
export { add, and, assertReadModelParity, assertRollupParity, assertSqlIdentifier, caseWhen, coalesce, col, compileToFn, compileToSqlSelect, count, countWhere, defineReadModel, defineRollup, deriveReadModelViewDdl, eq, exists, fromProjection, fromRollup, fromTable, gt, gte, isNotNull, isNull, joinSource, lit, lt, lte, max, minWhere, neq, not, or, pcol, ProjectionRegistry, projectionRegistry, rcol, ReadModelRegistry, readModelRegistry, RollupRegistry, rollupRegistry, when, } from "./core/read-model.ts";
|
|
44
|
+
export type { AddAgg, AggExpr, AndExpr, BaseRow, CaseExpr, ColExpr, CoalesceAgg, CompareExpr, CompareOp, CountAgg, CountWhereAgg, DerivationFn, ExistsExpr, Expr, IsNullExpr, Literal, LitExpr, LookupRows, MaxAgg, MinWhereAgg, NotExpr, OrExpr, ParityDb, ParityOptions, ParitySample, ProjColExpr, ProjectionRows, ProjectionSource, ReadModel, ReadModelDecl, RollupColExpr, Rollup, RollupDecl, RollupInputs, RollupJoinSource, RollupLookupDecl, RollupParityOptions, RollupProjectionSource, RollupRelation, RollupRollupSource, RollupSource, RollupTableSource, SqlCompileOptions, WhenClause, } from "./core/read-model.ts";
|
|
45
45
|
export { CANONICAL_PROJECTIONS, registerCanonicalProjections, } from "./core/modules/canonical-projections.ts";
|
|
46
46
|
export { OPEN_USER_TASKS_PROJECTION, OPEN_USER_TASKS_SCHEMA_SQL, OPEN_USER_TASKS_TABLE, OpenUserTasksStore, } from "./core/modules/open-user-tasks-store.ts";
|
|
47
47
|
export type { OpenUserTaskRow, OpenUserTasksStoreOptions, } from "./core/modules/open-user-tasks-store.ts";
|
package/dist/runtime/index.js
CHANGED
|
@@ -52,7 +52,12 @@ export { LINEAGE_ATTACHMENTS_TABLE, LINEAGE_EDGES_TABLE, LINEAGE_SCHEMA_SQL, Lin
|
|
|
52
52
|
// workers.ts) applies its managed VIEW. The `writer-source-inversion` task registers here.
|
|
53
53
|
// • `projectionRegistry` — register a canonical engine-truth projection NAME usable in `exists(...)`.
|
|
54
54
|
// The `canonical-projections` task registers `urban_open_user_tasks` / `urban_instance_state`.
|
|
55
|
-
|
|
55
|
+
// The GROUP-BY sibling `defineRollup` (ADR 0065, #468): a declare-once aggregate rollup compiled to
|
|
56
|
+
// BOTH a managed `*_counts` VIEW and a TS group-reduce (closed aggregate op set: `count`/`countWhere`/
|
|
57
|
+
// `max`/`minWhere`/`coalesce`/`add`), registered as a canonical projection via `rollupRegistry` and
|
|
58
|
+
// parity-guarded by `assertRollupParity`. `defineReadModel` gains a key-correlated rollup lookup
|
|
59
|
+
// (`ReadModelDecl.lookups` + `rcol`) so per-row derivations read a rollup's columns for the base row.
|
|
60
|
+
export { add, and, assertReadModelParity, assertRollupParity, assertSqlIdentifier, caseWhen, coalesce, col, compileToFn, compileToSqlSelect, count, countWhere, defineReadModel, defineRollup, deriveReadModelViewDdl, eq, exists, fromProjection, fromRollup, fromTable, gt, gte, isNotNull, isNull, joinSource, lit, lt, lte, max, minWhere, neq, not, or, pcol, ProjectionRegistry, projectionRegistry, rcol, ReadModelRegistry, readModelRegistry, RollupRegistry, rollupRegistry, when, } from "./core/read-model.js";
|
|
56
61
|
// Canonical engine-truth projection sidecars (ADR 0065, proposal point #1): two framework-owned,
|
|
57
62
|
// per-source projections the DSL `exists(...)` derives over — `urban_open_user_tasks` (which instances
|
|
58
63
|
// are parked on a human) and `urban_instance_state` (per-instance engine lifecycle state). Provisioned
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nanobpm/urban",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.82.0",
|
|
4
4
|
"description": "Urban: build and run code-first apps on Nano — runtime, derivation toolkit, and CLI in one. Author durable processes with @nanobpm/workflow (defineFlow), a typed datasource, forms, triggers and surfaces from a nano.app.json manifest, on Node or Deno.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -11,7 +11,7 @@ import { runInJobContext } from "../execContext.ts";
|
|
|
11
11
|
import type { JobExecContext } from "../execContext.ts";
|
|
12
12
|
import { readLineage } from "../lineage.ts";
|
|
13
13
|
import { LineageStore } from "./lineage-store.ts";
|
|
14
|
-
import { readModelRegistry } from "../read-model.ts";
|
|
14
|
+
import { readModelRegistry, rollupRegistry } from "../read-model.ts";
|
|
15
15
|
import { registerCanonicalProjections } from "./canonical-projections.ts";
|
|
16
16
|
import { InstanceStateStore } from "./instance-state-store.ts";
|
|
17
17
|
import { OpenUserTasksStore } from "./open-user-tasks-store.ts";
|
|
@@ -139,6 +139,25 @@ function tryLineageStore(app: AppApi, log: RuntimeContext["host"]["log"]): Linea
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Apply every managed rollup VIEW registered in the process-wide `rollupRegistry` (ADR 0065, #468) to
|
|
144
|
+
* the app's DEFAULT data source, in dependency order (a composed rollup after the rollup it reads).
|
|
145
|
+
* Mirrors {@link tryProvisionReadModelViews}: empty until a sibling task registers a rollup, and
|
|
146
|
+
* absent-/error-safe so a missing datasource or a VIEW-provisioning failure never breaks a worker mount.
|
|
147
|
+
*/
|
|
148
|
+
function tryProvisionRollupViews(app: AppApi, log: RuntimeContext["host"]["log"]): void {
|
|
149
|
+
if (rollupRegistry.all().length === 0) return;
|
|
150
|
+
if (!app.data.hasDefaultSource()) {
|
|
151
|
+
log("debug", "rollups: no default data source; managed VIEW provisioning skipped");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
rollupRegistry.ensureViews(app.data.source().db);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
log("warn", "rollups: failed to provision managed VIEWs", { error: String(err) });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
142
161
|
/**
|
|
143
162
|
* Apply every managed read-model VIEW registered in the process-wide `readModelRegistry` to the app's
|
|
144
163
|
* DEFAULT data source (ADR 0065, surface #3). A `defineReadModel(...)` declaration compiles to a
|
|
@@ -251,6 +270,10 @@ export async function mountWorkers(
|
|
|
251
270
|
// provision the projection tables per source (absent-safe), and finally the read-model VIEWs.
|
|
252
271
|
registerCanonicalProjections();
|
|
253
272
|
tryProvisionCanonicalProjections(app, ctx.host.log);
|
|
273
|
+
// Rollup VIEWs (ADR 0065, #468) BEFORE the read-model VIEWs: a read model's key-correlated rollup
|
|
274
|
+
// lookup `LEFT JOIN`s a rollup's `*_counts` VIEW, and a composed rollup reads another rollup's VIEW,
|
|
275
|
+
// so every rollup VIEW (in dependency order) must exist before any dependent VIEW compiles.
|
|
276
|
+
tryProvisionRollupViews(app, ctx.host.log);
|
|
254
277
|
tryProvisionReadModelViews(app, ctx.host.log);
|
|
255
278
|
|
|
256
279
|
// Cache module loads so a multi-type handler module is imported once.
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
pcol,
|
|
25
25
|
ProjectionRegistry,
|
|
26
26
|
projectionRegistry,
|
|
27
|
+
rcol,
|
|
27
28
|
ReadModelRegistry,
|
|
28
29
|
isReservedObjectName,
|
|
29
30
|
RESERVED_OBJECT_PREFIXES,
|
|
@@ -247,8 +248,32 @@ test("compileToFn resolves col/pcol identifiers case-insensitively (SQLite ident
|
|
|
247
248
|
assert.equal(corr({ key: "pi-1" }, { p: [{ process_instance_key: "pi-2" }] }), false);
|
|
248
249
|
});
|
|
249
250
|
|
|
250
|
-
test("
|
|
251
|
-
//
|
|
251
|
+
test("compileToFn / rcol reads the resolved lookup row by OWN property (a `__proto__` alias can't resolve Object.prototype)", () => {
|
|
252
|
+
// Lookup aliases are only identifier-validated, so `__proto__` is a legal alias. A bare `fnFor` caller
|
|
253
|
+
// supplies a plain `{}` for `lookups`; a raw `lookups["__proto__"]` resolves the inherited
|
|
254
|
+
// `Object.prototype` (truthy), bypassing the "missing lookup row → throw" guard and reading NULLs off the
|
|
255
|
+
// prototype chain. Reading by OWN property keeps the guard fail-loud on the bare-caller path.
|
|
256
|
+
const fn = compileToFn(rcol("__proto__", "amount"));
|
|
257
|
+
assert.throws(() => fn({}), /without a resolved lookup row/);
|
|
258
|
+
// A genuinely-supplied own `__proto__` row still resolves (null-prototype dict, an own property).
|
|
259
|
+
const lookups: Record<string, Record<string, unknown>> = Object.create(null);
|
|
260
|
+
lookups.__proto__ = { amount: 7 };
|
|
261
|
+
assert.equal(fn({}, {}, lookups), 7);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("compileToFn / exists reads projections by OWN property (a `__proto__` projection can't resolve Object.prototype)", () => {
|
|
265
|
+
// Projection names are user-controlled, so `__proto__` is legal. A bare `fnFor` caller passes a plain
|
|
266
|
+
// `{}` for projections; a raw `projections["__proto__"]` resolves the inherited `Object.prototype`
|
|
267
|
+
// (truthy, non-array), so `.some(...)` throws a TypeError. Reading by OWN property yields an empty EXISTS.
|
|
268
|
+
const fn = compileToFn(exists("__proto__", eq(col("k"), lit(1))));
|
|
269
|
+
assert.equal(fn({ k: 1 }), false); // no such projection → empty EXISTS, not a prototype read
|
|
270
|
+
// A genuinely-supplied own `__proto__` projection still matches.
|
|
271
|
+
const projections: Record<string, ReadonlyArray<Record<string, unknown>>> = Object.create(null);
|
|
272
|
+
projections.__proto__ = [{ k: 1 }];
|
|
273
|
+
assert.equal(fn({ k: 1 }, projections), true);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("mixed-case col/pcol identifiers: the VIEW and TS backends agree (VIEW/TS parity on the no-edge fallback)", async () => { // End-to-end guard for the case-fold fix: author a model with mixed-case identifiers over lowercase
|
|
252
277
|
// columns and confirm the SQLite VIEW and the TS backend derive the SAME value — including the
|
|
253
278
|
// parity-critical no-edge fallback that passes the stored status through.
|
|
254
279
|
const mixed: ReadModel = defineReadModel({
|