graphddb 0.3.1 → 0.4.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.
- package/README.md +1 -1
- package/dist/{chunk-PNIZS37E.js → chunk-BROCT574.js} +8 -118
- package/dist/{chunk-YIXXTGZ6.js → chunk-CPTV3H2U.js} +220 -250
- package/dist/{chunk-VST3WOK3.js → chunk-G5RWWBAL.js} +43 -23
- package/dist/cli.js +51 -14
- package/dist/index.d.ts +307 -106
- package/dist/index.js +305 -46
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +2 -2
- package/dist/{types-DuJ08bgc.d.ts → types-BWOrWcbd.d.ts} +309 -425
- package/package.json +1 -1
|
@@ -167,6 +167,7 @@ var requireLimitRule = {
|
|
|
167
167
|
const results = [];
|
|
168
168
|
for (const relation of metadata.relations) {
|
|
169
169
|
if (relation.type !== "hasMany") continue;
|
|
170
|
+
if (relation.options?.versioned) continue;
|
|
170
171
|
const limit = relation.options?.limit;
|
|
171
172
|
if (!limit || limit.default === void 0 || limit.max === void 0) {
|
|
172
173
|
const kb = Object.keys(relation.keyBinding).join(", ");
|
|
@@ -983,30 +984,46 @@ var TableMapping = class {
|
|
|
983
984
|
};
|
|
984
985
|
|
|
985
986
|
// src/define/param.ts
|
|
986
|
-
function
|
|
987
|
-
return
|
|
987
|
+
function isParamOptions(value) {
|
|
988
|
+
return typeof value === "object" && value !== null;
|
|
989
|
+
}
|
|
990
|
+
function makeParam(kind, literals, description) {
|
|
991
|
+
const param2 = { kind };
|
|
992
|
+
if (literals !== void 0) param2.literals = literals;
|
|
993
|
+
if (description !== void 0) param2.description = description;
|
|
994
|
+
return param2;
|
|
988
995
|
}
|
|
989
996
|
var param = {
|
|
990
997
|
/** A placeholder for a `string` value. */
|
|
991
|
-
string() {
|
|
992
|
-
return makeParam("string");
|
|
998
|
+
string(options) {
|
|
999
|
+
return makeParam("string", void 0, options?.description);
|
|
993
1000
|
},
|
|
994
1001
|
/** A placeholder for a `number` value. */
|
|
995
|
-
number() {
|
|
996
|
-
return makeParam("number");
|
|
1002
|
+
number(options) {
|
|
1003
|
+
return makeParam("number", void 0, options?.description);
|
|
997
1004
|
},
|
|
998
1005
|
/**
|
|
999
1006
|
* A placeholder constrained to one of the given literal values. The value
|
|
1000
1007
|
* type of the resulting {@link Param} is the **union of the literals**, so it
|
|
1001
|
-
* is preserved precisely in the IR and the inferred definition types.
|
|
1008
|
+
* is preserved precisely in the IR and the inferred definition types. A final
|
|
1009
|
+
* plain-object argument is read as {@link ParamOptions} (a `description`),
|
|
1010
|
+
* distinguished from the `string` / `number` literal values.
|
|
1002
1011
|
*
|
|
1003
1012
|
* @example
|
|
1004
1013
|
* ```ts
|
|
1005
1014
|
* param.literal('active', 'disabled'); // Param<'active' | 'disabled'>
|
|
1015
|
+
* param.literal('active', 'disabled', { description: 'Account state.' });
|
|
1006
1016
|
* ```
|
|
1007
1017
|
*/
|
|
1008
|
-
literal(...
|
|
1009
|
-
|
|
1018
|
+
literal(...valuesAndOptions) {
|
|
1019
|
+
const args = [...valuesAndOptions];
|
|
1020
|
+
const last = args[args.length - 1];
|
|
1021
|
+
const options = isParamOptions(last) ? args.pop() : void 0;
|
|
1022
|
+
return makeParam(
|
|
1023
|
+
"literal",
|
|
1024
|
+
args,
|
|
1025
|
+
options?.description
|
|
1026
|
+
);
|
|
1010
1027
|
},
|
|
1011
1028
|
/**
|
|
1012
1029
|
* A placeholder for an **array** parameter whose elements have the given field
|
|
@@ -1019,8 +1036,10 @@ var param = {
|
|
|
1019
1036
|
* // Param<{ userId: string; role: string }[]>
|
|
1020
1037
|
* ```
|
|
1021
1038
|
*/
|
|
1022
|
-
array(element) {
|
|
1023
|
-
|
|
1039
|
+
array(element, options) {
|
|
1040
|
+
const param2 = { kind: "array", element };
|
|
1041
|
+
if (options?.description !== void 0) param2.description = options.description;
|
|
1042
|
+
return param2;
|
|
1024
1043
|
}
|
|
1025
1044
|
};
|
|
1026
1045
|
function isParam(value) {
|
|
@@ -3453,11 +3472,14 @@ function makeProjectionTransform(path, op, args) {
|
|
|
3453
3472
|
`transform: unknown projection op ${JSON.stringify(op)} (expected \`identity\` or \`preview\`).`
|
|
3454
3473
|
);
|
|
3455
3474
|
}
|
|
3475
|
+
function pathOf(value) {
|
|
3476
|
+
return typeof value === "string" ? value : value.path;
|
|
3477
|
+
}
|
|
3456
3478
|
function preview(path, n) {
|
|
3457
|
-
return makeProjectionTransform(path, "preview", [n]);
|
|
3479
|
+
return makeProjectionTransform(pathOf(path), "preview", [n]);
|
|
3458
3480
|
}
|
|
3459
3481
|
function identity(path) {
|
|
3460
|
-
return makeProjectionTransform(path, "identity", []);
|
|
3482
|
+
return makeProjectionTransform(pathOf(path), "identity", []);
|
|
3461
3483
|
}
|
|
3462
3484
|
var LIFECYCLE_CONTRACT_MARKER = /* @__PURE__ */ Symbol(
|
|
3463
3485
|
"graphddb:lifecycleContract"
|
|
@@ -3549,251 +3571,208 @@ function lifecyclePhaseForIntent(intent) {
|
|
|
3549
3571
|
return intent;
|
|
3550
3572
|
}
|
|
3551
3573
|
|
|
3552
|
-
// src/
|
|
3553
|
-
function
|
|
3554
|
-
|
|
3555
|
-
const needsValue = op === "eq" || op === "ne";
|
|
3556
|
-
if (needsValue && value === void 0) {
|
|
3557
|
-
throw new Error(
|
|
3558
|
-
`whenMember('${field}', '${op}', \u2026): the '${op}' op requires a comparand value (e.g. \`whenMember('${field}', '${op}', 'active')\`).`
|
|
3559
|
-
);
|
|
3560
|
-
}
|
|
3561
|
-
return needsValue ? { path, op, value } : { path, op };
|
|
3574
|
+
// src/relation/maintenance-view-adapter.ts
|
|
3575
|
+
function logicalNameOf(meta) {
|
|
3576
|
+
return meta.prefix.endsWith("#") ? meta.prefix.slice(0, -1) : meta.prefix;
|
|
3562
3577
|
}
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
static views = /* @__PURE__ */ new Map();
|
|
3566
|
-
static gen = 0;
|
|
3567
|
-
/** Register (or replace, by view class) a declared view. Bumps the generation. */
|
|
3568
|
-
static register(def) {
|
|
3569
|
-
this.views.set(def.viewClass, def);
|
|
3570
|
-
this.gen += 1;
|
|
3571
|
-
}
|
|
3572
|
-
/** Every registered view, in declaration order. */
|
|
3573
|
-
static getAll() {
|
|
3574
|
-
return [...this.views.values()];
|
|
3575
|
-
}
|
|
3576
|
-
/** The monotonic generation counter (changes whenever the set of views changes). */
|
|
3577
|
-
static get generation() {
|
|
3578
|
-
return this.gen;
|
|
3579
|
-
}
|
|
3580
|
-
/** Forget every registered view (test reset). Bumps the generation. */
|
|
3581
|
-
static clear() {
|
|
3582
|
-
this.views.clear();
|
|
3583
|
-
this.gen += 1;
|
|
3584
|
-
}
|
|
3585
|
-
};
|
|
3586
|
-
function isViewDefinition(value) {
|
|
3587
|
-
return typeof value === "object" && value !== null && value[VIEW_DEFINITION_MARKER] === true;
|
|
3578
|
+
function resolveClass(factory) {
|
|
3579
|
+
return factory();
|
|
3588
3580
|
}
|
|
3589
|
-
function
|
|
3590
|
-
const
|
|
3591
|
-
|
|
3592
|
-
return resolveModelClass(resolved);
|
|
3593
|
-
} catch {
|
|
3594
|
-
return resolved;
|
|
3595
|
-
}
|
|
3581
|
+
function sourceLogicalName(sourceClass) {
|
|
3582
|
+
const meta = MetadataRegistry.get(sourceClass);
|
|
3583
|
+
return logicalNameOf(meta);
|
|
3596
3584
|
}
|
|
3597
|
-
function
|
|
3598
|
-
return
|
|
3585
|
+
function triggersFor(sourceLogical, on) {
|
|
3586
|
+
return on.map((ev) => `${sourceLogical}.${ev}`);
|
|
3599
3587
|
}
|
|
3600
|
-
function
|
|
3601
|
-
const
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
)
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
out[attr] = value;
|
|
3588
|
+
function assertNoCrossSliceCollision(viewName, decls) {
|
|
3589
|
+
const projectionOwner = /* @__PURE__ */ new Map();
|
|
3590
|
+
const collectionOwner = /* @__PURE__ */ new Map();
|
|
3591
|
+
let keyFieldShape;
|
|
3592
|
+
decls.forEach((d, idx) => {
|
|
3593
|
+
for (const attr of Object.keys(d.project)) {
|
|
3594
|
+
const prev = projectionOwner.get(attr);
|
|
3595
|
+
if (prev !== void 0 && prev !== idx) {
|
|
3596
|
+
throw new Error(
|
|
3597
|
+
`View '${viewName}': projection target '${attr}' is maintained by more than one \`@maintainedFrom\` declaration. A view field must be filled by exactly one source \u2014 the declaration order carries no meaning, so this is ambiguous. Remove '${attr}' from one of the declarations (or split it onto a distinct view field).`
|
|
3598
|
+
);
|
|
3599
|
+
}
|
|
3600
|
+
projectionOwner.set(attr, idx);
|
|
3614
3601
|
}
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
}
|
|
3624
|
-
return raw;
|
|
3625
|
-
}
|
|
3626
|
-
function defineView(view, input) {
|
|
3627
|
-
const viewClass = resolveSourceClass((() => view));
|
|
3628
|
-
const name = viewClass.name;
|
|
3629
|
-
if (typeof name !== "string" || name.length === 0) {
|
|
3630
|
-
throw new Error("defineView(view, input): the view model must be a named class.");
|
|
3631
|
-
}
|
|
3632
|
-
if (input.pattern !== void 0 && input.pattern !== "materializedView" && input.pattern !== "sparseView") {
|
|
3633
|
-
throw new Error(
|
|
3634
|
-
`defineView('${name}'): \`pattern\` must be 'materializedView' or 'sparseView' (got ${JSON.stringify(input.pattern)}).`
|
|
3635
|
-
);
|
|
3636
|
-
}
|
|
3637
|
-
const pattern = input.pattern ?? "materializedView";
|
|
3638
|
-
const sources = Array.isArray(input.source) ? input.source : [input.source];
|
|
3639
|
-
if (sources.length === 0) {
|
|
3640
|
-
throw new Error(
|
|
3641
|
-
`defineView('${name}'): at least one \`source\` is required (a view with no source is maintained by nothing).`
|
|
3642
|
-
);
|
|
3643
|
-
}
|
|
3644
|
-
const slices = [];
|
|
3645
|
-
for (const src of sources) {
|
|
3646
|
-
const keys = src.keys ?? input.keys;
|
|
3647
|
-
if (keys === void 0 || Object.keys(keys).length === 0) {
|
|
3648
|
-
throw new Error(
|
|
3649
|
-
`defineView('${name}'): a source declares no \`keys\` and the view has no default \`keys\`. The view-row key binding (view-row key field \u2192 \`$.entity.<sourceField>\`) is required to resolve which view row a source event maintains.`
|
|
3650
|
-
);
|
|
3602
|
+
if (d.collectionField !== void 0) {
|
|
3603
|
+
const prev = collectionOwner.get(d.collectionField);
|
|
3604
|
+
if (prev !== void 0 && prev !== idx) {
|
|
3605
|
+
throw new Error(
|
|
3606
|
+
`View '${viewName}': collection field '${d.collectionField}' is maintained by more than one \`@maintainedFrom\` declaration. A maintained collection has exactly one source \u2014 split the second source onto a distinct collection field.`
|
|
3607
|
+
);
|
|
3608
|
+
}
|
|
3609
|
+
collectionOwner.set(d.collectionField, idx);
|
|
3651
3610
|
}
|
|
3652
|
-
|
|
3611
|
+
const shape = Object.keys(d.keyBind).slice().sort().join(",");
|
|
3612
|
+
if (keyFieldShape === void 0) {
|
|
3613
|
+
keyFieldShape = shape;
|
|
3614
|
+
} else if (keyFieldShape !== shape) {
|
|
3653
3615
|
throw new Error(
|
|
3654
|
-
`
|
|
3616
|
+
`View '${viewName}': \`@maintainedFrom\` declarations bind different view-key field SETS ([${keyFieldShape}] vs [${shape}]). All sources must resolve the SAME view row identity \u2014 they must bind the same set of view-key fields (the source field a given key reads from may differ per source, but the key SHAPE may not). A divergent key shape would split the row.`
|
|
3655
3617
|
);
|
|
3656
3618
|
}
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
} : {}
|
|
3666
|
-
} : void 0;
|
|
3667
|
-
const predicate = src.when;
|
|
3668
|
-
if (predicate && collection) {
|
|
3619
|
+
});
|
|
3620
|
+
}
|
|
3621
|
+
function viewFromMaintainedFrom(viewClass, meta, kind) {
|
|
3622
|
+
const viewName = viewClass.name ?? logicalNameOf(meta);
|
|
3623
|
+
const decls = meta.maintainedFrom ?? [];
|
|
3624
|
+
const pattern = kind === "sparseView" ? "sparseView" : "materializedView";
|
|
3625
|
+
for (const d of decls) {
|
|
3626
|
+
if (kind === "sparseView" && !d.when) {
|
|
3669
3627
|
throw new Error(
|
|
3670
|
-
`
|
|
3628
|
+
`View '${viewName}': \`kind: 'sparseView'\` requires every \`@maintainedFrom\` to declare a \`when\` membership predicate (e.g. \`whenMember(source.status, 'eq', 'active')\`) \u2014 the predicate decides PUT (holds) vs DELETE (flips false).`
|
|
3671
3629
|
);
|
|
3672
3630
|
}
|
|
3673
|
-
if (
|
|
3631
|
+
if (kind !== "sparseView" && d.when) {
|
|
3674
3632
|
throw new Error(
|
|
3675
|
-
`
|
|
3633
|
+
`View '${viewName}': a \`@maintainedFrom\` declares a \`when\` membership predicate but the view \`kind\` is '${kind}'. Declare \`kind: 'sparseView'\` to maintain a predicate-gated put/delete row.`
|
|
3676
3634
|
);
|
|
3677
3635
|
}
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3636
|
+
}
|
|
3637
|
+
assertNoCrossSliceCollision(
|
|
3638
|
+
viewName,
|
|
3639
|
+
decls.map((d) => ({
|
|
3640
|
+
keyBind: d.keyBind,
|
|
3641
|
+
project: d.project,
|
|
3642
|
+
collectionField: d.collection?.field
|
|
3643
|
+
}))
|
|
3644
|
+
);
|
|
3645
|
+
let updateMode;
|
|
3646
|
+
let consistency;
|
|
3647
|
+
for (const d of decls) {
|
|
3648
|
+
if (d.updateMode !== void 0) {
|
|
3649
|
+
if (updateMode !== void 0 && updateMode !== d.updateMode) {
|
|
3650
|
+
throw new Error(
|
|
3651
|
+
`View '${viewName}': conflicting \`updateMode\` across \`@maintainedFrom\` declarations ('${updateMode}' vs '${d.updateMode}'). A view row has a single write path \u2014 declare the same \`updateMode\` on every source.`
|
|
3652
|
+
);
|
|
3653
|
+
}
|
|
3654
|
+
updateMode = d.updateMode;
|
|
3655
|
+
}
|
|
3656
|
+
if (d.consistency !== void 0) {
|
|
3657
|
+
if (consistency !== void 0 && consistency !== d.consistency) {
|
|
3658
|
+
throw new Error(
|
|
3659
|
+
`View '${viewName}': conflicting \`consistency\` across \`@maintainedFrom\` declarations ('${consistency}' vs '${d.consistency}').`
|
|
3660
|
+
);
|
|
3661
|
+
}
|
|
3662
|
+
consistency = d.consistency;
|
|
3682
3663
|
}
|
|
3683
|
-
slices.push({
|
|
3684
|
-
sourceClass: resolveSourceClass(src.entity),
|
|
3685
|
-
maintainedOn,
|
|
3686
|
-
keys,
|
|
3687
|
-
project,
|
|
3688
|
-
...collection ? { collection } : {},
|
|
3689
|
-
...predicate ? { predicate } : {}
|
|
3690
|
-
});
|
|
3691
3664
|
}
|
|
3692
|
-
const
|
|
3693
|
-
|
|
3694
|
-
name,
|
|
3665
|
+
const slices = decls.map((d) => sliceFromDeclaration(d));
|
|
3666
|
+
return {
|
|
3667
|
+
name: viewName,
|
|
3695
3668
|
viewClass,
|
|
3696
3669
|
pattern,
|
|
3697
3670
|
slices,
|
|
3698
|
-
...
|
|
3699
|
-
...
|
|
3671
|
+
...updateMode !== void 0 ? { updateMode } : {},
|
|
3672
|
+
...consistency !== void 0 ? { consistency } : {}
|
|
3700
3673
|
};
|
|
3701
|
-
ViewRegistry.register(def);
|
|
3702
|
-
return def;
|
|
3703
3674
|
}
|
|
3704
|
-
function
|
|
3705
|
-
|
|
3706
|
-
|
|
3675
|
+
function sliceFromDeclaration(d) {
|
|
3676
|
+
const sourceClass = resolveClass(d.sourceFactory);
|
|
3677
|
+
const sourceLogical = sourceLogicalName(sourceClass);
|
|
3678
|
+
const collection = d.collection ? {
|
|
3679
|
+
field: d.collection.field,
|
|
3680
|
+
...d.collection.maxItems !== void 0 ? { maxItems: d.collection.maxItems } : {},
|
|
3681
|
+
...d.collection.orderBy !== void 0 ? { orderBy: d.collection.orderBy, orderDir: d.collection.order ?? "DESC" } : {}
|
|
3682
|
+
} : void 0;
|
|
3683
|
+
return {
|
|
3684
|
+
sourceClass,
|
|
3685
|
+
maintainedOn: triggersFor(sourceLogical, d.on),
|
|
3686
|
+
keys: d.keyBind,
|
|
3687
|
+
project: d.project,
|
|
3688
|
+
...collection ? { collection } : {},
|
|
3689
|
+
...d.when ? { predicate: d.when } : {}
|
|
3690
|
+
};
|
|
3691
|
+
}
|
|
3692
|
+
function versionedViews(ownerClass, ownerMeta) {
|
|
3693
|
+
const ownerLogical = logicalNameOf(ownerMeta);
|
|
3694
|
+
const rels = [];
|
|
3695
|
+
for (const rel of ownerMeta.relations) {
|
|
3696
|
+
const v = rel.options?.versioned;
|
|
3697
|
+
if (!v) continue;
|
|
3698
|
+
const targetClass = resolveClass(rel.targetFactory);
|
|
3699
|
+
const targetMeta = MetadataRegistry.get(targetClass);
|
|
3700
|
+
if (!targetMeta.primaryKey) {
|
|
3707
3701
|
throw new Error(
|
|
3708
|
-
`
|
|
3702
|
+
`Versioned relation '${rel.propertyName}' on '${ownerLogical}': the target model '${targetClass.name}' has no \`key\` \u2014 a versioned latest/history row needs a key to resolve which row to overwrite / append.`
|
|
3709
3703
|
);
|
|
3710
3704
|
}
|
|
3705
|
+
const keyFields = [
|
|
3706
|
+
...segmentFieldNames(targetMeta.primaryKey.segmented.pkSegments),
|
|
3707
|
+
...segmentFieldNames(targetMeta.primaryKey.segmented.skSegments)
|
|
3708
|
+
];
|
|
3709
|
+
rels.push({
|
|
3710
|
+
propertyName: rel.propertyName,
|
|
3711
|
+
mode: v.mode,
|
|
3712
|
+
on: v.on,
|
|
3713
|
+
project: v.project,
|
|
3714
|
+
...v.updateMode !== void 0 ? { updateMode: v.updateMode } : {},
|
|
3715
|
+
...v.consistency !== void 0 ? { consistency: v.consistency } : {},
|
|
3716
|
+
targetClass,
|
|
3717
|
+
targetMeta,
|
|
3718
|
+
keyFields
|
|
3719
|
+
});
|
|
3711
3720
|
}
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
);
|
|
3721
|
+
assertVersionedDiscriminator(ownerLogical, rels);
|
|
3722
|
+
const out = [];
|
|
3723
|
+
for (const rel of rels) {
|
|
3724
|
+
const keys = {};
|
|
3725
|
+
for (const f of rel.keyFields) keys[f] = `$.entity.${f}`;
|
|
3726
|
+
const targetName = rel.targetClass.name ?? logicalNameOf(rel.targetMeta);
|
|
3727
|
+
out.push({
|
|
3728
|
+
name: targetName,
|
|
3729
|
+
viewClass: rel.targetClass,
|
|
3730
|
+
pattern: "materializedView",
|
|
3731
|
+
slices: [
|
|
3732
|
+
{
|
|
3733
|
+
sourceClass: ownerClass,
|
|
3734
|
+
maintainedOn: triggersFor(ownerLogical, rel.on),
|
|
3735
|
+
keys,
|
|
3736
|
+
project: rel.project
|
|
3737
|
+
}
|
|
3738
|
+
],
|
|
3739
|
+
...rel.updateMode !== void 0 ? { updateMode: rel.updateMode } : {},
|
|
3740
|
+
...rel.consistency !== void 0 ? { consistency: rel.consistency } : {}
|
|
3741
|
+
});
|
|
3734
3742
|
}
|
|
3735
|
-
|
|
3736
|
-
pattern: "materializedView",
|
|
3737
|
-
source: { entity: input.source, maintainedOn: input.maintainedOn, keys: input.latestKeys },
|
|
3738
|
-
fields: input.fields,
|
|
3739
|
-
...input.write ? { write: input.write } : {}
|
|
3740
|
-
});
|
|
3741
|
-
const historyView = defineView(history, {
|
|
3742
|
-
pattern: "materializedView",
|
|
3743
|
-
source: { entity: input.source, maintainedOn: input.maintainedOn, keys: input.historyKeys },
|
|
3744
|
-
fields: input.fields,
|
|
3745
|
-
...input.write ? { write: input.write } : {}
|
|
3746
|
-
});
|
|
3747
|
-
return { latest: latestView, history: historyView };
|
|
3748
|
-
}
|
|
3749
|
-
var PROJECTION_DEFINITION_MARKER = /* @__PURE__ */ Symbol(
|
|
3750
|
-
"graphddb:projectionDefinition"
|
|
3751
|
-
);
|
|
3752
|
-
function isProjectionDefinition(value) {
|
|
3753
|
-
return typeof value === "object" && value !== null && value[PROJECTION_DEFINITION_MARKER] === true;
|
|
3743
|
+
return out;
|
|
3754
3744
|
}
|
|
3755
|
-
function
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
throw new Error(
|
|
3772
|
-
`defineProjection('${name}'): \`idempotencyKey\` must be a non-empty source attribute name (e.g. 'resultId'). It folds at-least-once delivery to one sink upsert per genuine source event.`
|
|
3773
|
-
);
|
|
3745
|
+
function assertVersionedDiscriminator(ownerLogical, rels) {
|
|
3746
|
+
const latests = rels.filter((r) => r.mode === "latest");
|
|
3747
|
+
const histories = rels.filter((r) => r.mode === "history");
|
|
3748
|
+
if (latests.length === 0 || histories.length === 0) return;
|
|
3749
|
+
for (const latest of latests) {
|
|
3750
|
+
const latestKeyFields = new Set(latest.keyFields);
|
|
3751
|
+
for (const history of histories) {
|
|
3752
|
+
const discriminators = history.keyFields.filter((f) => !latestKeyFields.has(f));
|
|
3753
|
+
if (discriminators.length === 0) {
|
|
3754
|
+
const latestName = latest.targetClass.name ?? "<latest>";
|
|
3755
|
+
const historyName = history.targetClass.name ?? "<history>";
|
|
3756
|
+
throw new Error(
|
|
3757
|
+
`Versioned relation pair on '${ownerLogical}': the \`versionedHistory\` target '${historyName}' (\`${history.propertyName}\`) is keyed by [${history.keyFields.map((f) => `'${f}'`).join(", ")}], which carries NO version discriminator beyond the \`versionedLatest\` target '${latestName}' (\`${latest.propertyName}\`) keyed by [${[...latestKeyFields].map((f) => `'${f}'`).join(", ")}]. The history row must be keyed by a per-version field (e.g. add a \`version\` segment to '${historyName}'s \`@key\`) so each revision is a NEW row; otherwise every revision overwrites one row (a latest pointer, not an append-only history).`
|
|
3758
|
+
);
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3774
3761
|
}
|
|
3775
|
-
const on = input.on ?? ["created", "updated", "removed"];
|
|
3776
|
-
const project = input.fields ? buildProjection(name, input.fields) : {};
|
|
3777
|
-
return {
|
|
3778
|
-
[PROJECTION_DEFINITION_MARKER]: true,
|
|
3779
|
-
name,
|
|
3780
|
-
pattern: "externalProjection",
|
|
3781
|
-
sourceClass: resolveSourceClass(input.source),
|
|
3782
|
-
via,
|
|
3783
|
-
idempotencyKey: input.idempotencyKey,
|
|
3784
|
-
on: [...on],
|
|
3785
|
-
project
|
|
3786
|
-
};
|
|
3787
3762
|
}
|
|
3788
|
-
function
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3763
|
+
function collectViewDefinitions(registry) {
|
|
3764
|
+
const resolved = registry ?? MetadataRegistry.getAll();
|
|
3765
|
+
const out = [];
|
|
3766
|
+
for (const [klass, meta] of resolved) {
|
|
3767
|
+
const kind = meta.kind ?? "entity";
|
|
3768
|
+
if (kind === "materializedView" || kind === "sparseView") {
|
|
3769
|
+
out.push(viewFromMaintainedFrom(klass, meta, kind));
|
|
3770
|
+
}
|
|
3771
|
+
if (meta.relations?.some((r) => r.options?.versioned)) {
|
|
3772
|
+
out.push(...versionedViews(klass, meta));
|
|
3794
3773
|
}
|
|
3795
3774
|
}
|
|
3796
|
-
return
|
|
3775
|
+
return out;
|
|
3797
3776
|
}
|
|
3798
3777
|
|
|
3799
3778
|
// src/relation/maintenance-graph.ts
|
|
@@ -3803,7 +3782,7 @@ var COUNT_DELTA_FOR_EVENT = {
|
|
|
3803
3782
|
updated: 0
|
|
3804
3783
|
};
|
|
3805
3784
|
var MAINTAIN_EVENTS = ["created", "updated", "removed"];
|
|
3806
|
-
function
|
|
3785
|
+
function isPathRooted(value) {
|
|
3807
3786
|
return value.startsWith("$.input.") || value.startsWith("$.entity.");
|
|
3808
3787
|
}
|
|
3809
3788
|
function sourceAttributeOf(path) {
|
|
@@ -3812,7 +3791,7 @@ function sourceAttributeOf(path) {
|
|
|
3812
3791
|
}
|
|
3813
3792
|
function normalizeProjectionEntry(value) {
|
|
3814
3793
|
if (typeof value === "string") {
|
|
3815
|
-
const path =
|
|
3794
|
+
const path = isPathRooted(value) ? value : `$.entity.${value}`;
|
|
3816
3795
|
return identity(path);
|
|
3817
3796
|
}
|
|
3818
3797
|
return value;
|
|
@@ -3879,7 +3858,7 @@ function assertMaintenanceRoundTrips(args) {
|
|
|
3879
3858
|
}
|
|
3880
3859
|
}
|
|
3881
3860
|
for (const [attr, transform] of Object.entries(projection)) {
|
|
3882
|
-
if (!
|
|
3861
|
+
if (!isPathRooted(transform.path)) {
|
|
3883
3862
|
throw new Error(
|
|
3884
3863
|
`Maintenance projection for relation '${relationProperty}' on '${ownerEntity}' captures attribute '${attr}' from '${transform.path}', which is not a payload-rooted source path (\`$.input.*\` / \`$.entity.*\`). The maintenance graph projects only from the source payload (payload \u540C\u68B1) \u2014 there is no fetch / re-projection.`
|
|
3885
3864
|
);
|
|
@@ -3926,12 +3905,12 @@ function assertCounterRoundTrips(args) {
|
|
|
3926
3905
|
}
|
|
3927
3906
|
}
|
|
3928
3907
|
}
|
|
3929
|
-
function
|
|
3908
|
+
function logicalNameOf2(meta) {
|
|
3930
3909
|
return meta.prefix.endsWith("#") ? meta.prefix.slice(0, -1) : meta.prefix;
|
|
3931
3910
|
}
|
|
3932
3911
|
function resolveEntityByName(name, registry) {
|
|
3933
3912
|
for (const [klass, meta] of registry) {
|
|
3934
|
-
if (
|
|
3913
|
+
if (logicalNameOf2(meta) === name) return klass;
|
|
3935
3914
|
}
|
|
3936
3915
|
return void 0;
|
|
3937
3916
|
}
|
|
@@ -3999,12 +3978,12 @@ function resolveCollectionOrder(relation, sourceMeta, ownerEntity, project) {
|
|
|
3999
3978
|
const orderField = skFields[skFields.length - 1];
|
|
4000
3979
|
if (orderField === void 0) {
|
|
4001
3980
|
throw new Error(
|
|
4002
|
-
`Relation '${relation.propertyName}' on '${ownerEntity}' declares \`read.order: '${order}'\` for its maintained collection, but its source entity '${
|
|
3981
|
+
`Relation '${relation.propertyName}' on '${ownerEntity}' declares \`read.order: '${order}'\` for its maintained collection, but its source entity '${logicalNameOf2(sourceMeta)}' has no sort key to order by. A maintained collection orders by the source's sort-key field (the field the live read sorts on); declare a sort key on '${logicalNameOf2(sourceMeta)}' or drop \`read.order\`.`
|
|
4003
3982
|
);
|
|
4004
3983
|
}
|
|
4005
3984
|
if (!Object.prototype.hasOwnProperty.call(project, orderField)) {
|
|
4006
3985
|
throw new Error(
|
|
4007
|
-
`Relation '${relation.propertyName}' on '${ownerEntity}' declares \`read.order: '${order}'\` for its maintained collection, which orders by the source entity '${
|
|
3986
|
+
`Relation '${relation.propertyName}' on '${ownerEntity}' declares \`read.order: '${order}'\` for its maintained collection, which orders by the source entity '${logicalNameOf2(sourceMeta)}'s sort-key field '${orderField}', but that field is not in the relation's \`projection\` (projected: ${Object.keys(project).map((a) => `'${a}'`).join(", ")}). The maintained collection sorts its projected items, so the order field must be projected \u2014 add '${orderField}' to the projection (e.g. \`{ ${orderField}: '${orderField}', \u2026 }\`) or drop \`read.order\`.`
|
|
4008
3987
|
);
|
|
4009
3988
|
}
|
|
4010
3989
|
return { orderBy: `$.entity.${orderField}`, orderDir: order };
|
|
@@ -4075,16 +4054,15 @@ function assertNoCycle(items) {
|
|
|
4075
4054
|
}
|
|
4076
4055
|
}
|
|
4077
4056
|
function buildMaintenanceGraph(registry, views) {
|
|
4078
|
-
const useGlobal = registry === void 0;
|
|
4079
4057
|
const resolvedRegistry = registry ?? MetadataRegistry.getAll();
|
|
4080
|
-
const resolvedViews = views ?? (
|
|
4058
|
+
const resolvedViews = views ?? collectViewDefinitions(resolvedRegistry);
|
|
4081
4059
|
return buildMaintenanceGraphImpl(resolvedRegistry, resolvedViews);
|
|
4082
4060
|
}
|
|
4083
4061
|
function buildMaintenanceGraphImpl(registry, views) {
|
|
4084
4062
|
const items = [];
|
|
4085
4063
|
const owners = [...registry.entries()].map(([klass, meta]) => ({ klass, meta })).sort((a, b) => a.klass.name < b.klass.name ? -1 : a.klass.name > b.klass.name ? 1 : 0);
|
|
4086
4064
|
for (const { klass: ownerClass, meta: ownerMeta } of owners) {
|
|
4087
|
-
const ownerEntity =
|
|
4065
|
+
const ownerEntity = logicalNameOf2(ownerMeta);
|
|
4088
4066
|
const relations = [...ownerMeta.relations].sort(
|
|
4089
4067
|
(a, b) => a.propertyName < b.propertyName ? -1 : a.propertyName > b.propertyName ? 1 : 0
|
|
4090
4068
|
);
|
|
@@ -4093,7 +4071,7 @@ function buildMaintenanceGraphImpl(registry, views) {
|
|
|
4093
4071
|
if (!maintainedOn || maintainedOn.length === 0) continue;
|
|
4094
4072
|
const sourceClass = relation.targetFactory();
|
|
4095
4073
|
const sourceMeta = MetadataRegistry.get(sourceClass);
|
|
4096
|
-
const relationTargetEntity =
|
|
4074
|
+
const relationTargetEntity = logicalNameOf2(sourceMeta);
|
|
4097
4075
|
const project = buildProjectionMap(
|
|
4098
4076
|
relation.options?.projection,
|
|
4099
4077
|
ownerEntity,
|
|
@@ -4159,7 +4137,7 @@ function buildMaintenanceGraphImpl(registry, views) {
|
|
|
4159
4137
|
if (!maintainedOn || maintainedOn.length === 0) continue;
|
|
4160
4138
|
const sourceClass = agg.targetFactory();
|
|
4161
4139
|
const sourceMeta = MetadataRegistry.get(sourceClass);
|
|
4162
|
-
const aggSourceEntity =
|
|
4140
|
+
const aggSourceEntity = logicalNameOf2(sourceMeta);
|
|
4163
4141
|
const { keys, sourceFields } = buildDestinationKeys(agg.keyBinding);
|
|
4164
4142
|
const destinationKeyFields = Object.keys(keys);
|
|
4165
4143
|
const valueSourceFields = agg.options.value.op === "max" ? [agg.options.value.field] : [];
|
|
@@ -4216,11 +4194,11 @@ function buildMaintenanceGraphImpl(registry, views) {
|
|
|
4216
4194
|
for (const view of [...views].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
|
|
4217
4195
|
const ownerClass = view.viewClass;
|
|
4218
4196
|
const ownerMeta = MetadataRegistry.get(ownerClass);
|
|
4219
|
-
const ownerEntity =
|
|
4197
|
+
const ownerEntity = logicalNameOf2(ownerMeta);
|
|
4220
4198
|
view.slices.forEach((slice, sliceIndex) => {
|
|
4221
4199
|
const sourceClass = slice.sourceClass;
|
|
4222
4200
|
const sourceMeta = MetadataRegistry.get(sourceClass);
|
|
4223
|
-
const sourceEntity =
|
|
4201
|
+
const sourceEntity = logicalNameOf2(sourceMeta);
|
|
4224
4202
|
const keys = slice.keys;
|
|
4225
4203
|
const destinationKeyFields = Object.keys(keys);
|
|
4226
4204
|
const keyBindingSourceFields = Object.values(keys).map((p) => sourceAttributeOf(p));
|
|
@@ -4242,7 +4220,7 @@ function buildMaintenanceGraphImpl(registry, views) {
|
|
|
4242
4220
|
projection: slice.project
|
|
4243
4221
|
});
|
|
4244
4222
|
if (slice.predicate) {
|
|
4245
|
-
if (!
|
|
4223
|
+
if (!isPathRooted(slice.predicate.path)) {
|
|
4246
4224
|
throw new Error(
|
|
4247
4225
|
`defineView '${view.name}': sparse-view predicate path ${JSON.stringify(slice.predicate.path)} is not a payload-rooted source path (\`$.input.*\` / \`$.entity.*\`). The drain evaluates the predicate against the source image \u2014 use a source attribute path.`
|
|
4248
4226
|
);
|
|
@@ -4368,14 +4346,6 @@ export {
|
|
|
4368
4346
|
entityWrites,
|
|
4369
4347
|
getEntityWrites,
|
|
4370
4348
|
lifecyclePhaseForIntent,
|
|
4371
|
-
|
|
4372
|
-
ViewRegistry,
|
|
4373
|
-
isViewDefinition,
|
|
4374
|
-
defineView,
|
|
4375
|
-
defineViews,
|
|
4376
|
-
defineVersioned,
|
|
4377
|
-
isProjectionDefinition,
|
|
4378
|
-
defineProjection,
|
|
4379
|
-
defineProjections,
|
|
4349
|
+
collectViewDefinitions,
|
|
4380
4350
|
buildMaintenanceGraph
|
|
4381
4351
|
};
|