@githolon/dsl 0.62.0 → 0.63.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/package.json +1 -1
- package/src/fields.ts +35 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@githolon/dsl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.63.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Nomos 2 domain-authoring DSL: aggregates + directives in TS, executed and encoded to the Rust kernel's wire shapes.",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
package/src/fields.ts
CHANGED
|
@@ -397,6 +397,12 @@ function makeField<T, K extends FieldKind = FieldKind>(
|
|
|
397
397
|
extra: {
|
|
398
398
|
refWorkspace?: string;
|
|
399
399
|
refAggregateId?: string;
|
|
400
|
+
/** LAZY target resolution — `t.ref(() => Agg)` / `t.hasMany(() => Agg)`. Breaks a BIDIRECTIONAL
|
|
401
|
+
* declaration cycle (a Parent's `hasMany(Child)` ⇄ a Child's `ref(Parent)`): the thunk is stored and
|
|
402
|
+
* resolved on FIRST read of `refAggregateId` (at manifest build — after all module consts init), so
|
|
403
|
+
* neither side needs the other to already exist at its own `const`. Consumers read `refAggregateId`
|
|
404
|
+
* transparently (a memoized getter). */
|
|
405
|
+
refAggregateIdThunk?: () => string;
|
|
400
406
|
viaField?: string;
|
|
401
407
|
isOptional?: boolean;
|
|
402
408
|
fromBirth?: true;
|
|
@@ -496,6 +502,21 @@ function makeField<T, K extends FieldKind = FieldKind>(
|
|
|
496
502
|
return makeField<T, K>(kind, zod, driver, { ...extra, facet }) as Field<T, K> & { readonly facet: Fa };
|
|
497
503
|
},
|
|
498
504
|
};
|
|
505
|
+
// LAZY REF TARGET: when the target was given as a thunk (`t.ref(() => Agg)` / `t.hasMany(() => Agg)` — to
|
|
506
|
+
// break a bidirectional declaration cycle), expose `refAggregateId` as a MEMOIZED getter that resolves the
|
|
507
|
+
// thunk on first read (build time, after all module consts init). Consumers read `refAggregateId` as usual.
|
|
508
|
+
if (extra.refAggregateIdThunk !== undefined) {
|
|
509
|
+
const thunk = extra.refAggregateIdThunk;
|
|
510
|
+
let resolved: string | undefined;
|
|
511
|
+
Object.defineProperty(f, "refAggregateId", {
|
|
512
|
+
configurable: true,
|
|
513
|
+
enumerable: true,
|
|
514
|
+
get() {
|
|
515
|
+
if (resolved === undefined) resolved = thunk();
|
|
516
|
+
return resolved;
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
}
|
|
499
520
|
return f;
|
|
500
521
|
}
|
|
501
522
|
|
|
@@ -740,13 +761,16 @@ export const t = {
|
|
|
740
761
|
* (one event on the CHILD, parent untouched), and the read engine serves `parent.children` as the
|
|
741
762
|
* managed `Child by parent` index. The dev never builds a write-amplifying parent collection.
|
|
742
763
|
*/
|
|
743
|
-
hasMany(child: AggregateHandle<string, Record<string, Field>>): {
|
|
764
|
+
hasMany(child: AggregateHandle<string, Record<string, Field>> | (() => AggregateHandle<string, Record<string, Field>>)): {
|
|
744
765
|
via(backref: string): Field<string[], "hasMany">;
|
|
745
766
|
} {
|
|
767
|
+
// THUNK form (`t.hasMany(() => Child)`) — the common bidirectional case (a Parent's `hasMany(Child)` ⇄ a
|
|
768
|
+
// Child's `ref(Parent)`): pass a thunk so the Parent needn't wait for the Child's `const` to exist.
|
|
769
|
+
const isThunk = typeof child === "function";
|
|
746
770
|
return {
|
|
747
771
|
via(backref: string): Field<string[], "hasMany"> {
|
|
748
772
|
return makeField<string[], "hasMany">("hasMany", z.array(z.string()), Conflict, {
|
|
749
|
-
refAggregateId: child.id,
|
|
773
|
+
...(isThunk ? { refAggregateIdThunk: () => child().id } : { refAggregateId: child.id }),
|
|
750
774
|
viaField: backref,
|
|
751
775
|
});
|
|
752
776
|
},
|
|
@@ -768,8 +792,11 @@ export const t = {
|
|
|
768
792
|
},
|
|
769
793
|
};
|
|
770
794
|
|
|
795
|
+
type RefTarget = AggregateHandle<string, Record<string, Field>> | (() => AggregateHandle<string, Record<string, Field>>);
|
|
796
|
+
|
|
771
797
|
interface RefFn {
|
|
772
|
-
|
|
798
|
+
/** Same-workspace reference. Pass a THUNK (`t.ref(() => Agg)`) to break a bidirectional declaration cycle. */
|
|
799
|
+
(target: RefTarget): Field<string, "ref">;
|
|
773
800
|
(
|
|
774
801
|
workspaceId: string,
|
|
775
802
|
target: AggregateHandle<string, Record<string, Field>>,
|
|
@@ -777,7 +804,7 @@ interface RefFn {
|
|
|
777
804
|
}
|
|
778
805
|
|
|
779
806
|
function refImpl(
|
|
780
|
-
a: string |
|
|
807
|
+
a: string | RefTarget,
|
|
781
808
|
b?: AggregateHandle<string, Record<string, Field>>,
|
|
782
809
|
): Field<string, "ref"> {
|
|
783
810
|
if (typeof a === "string") {
|
|
@@ -787,6 +814,10 @@ function refImpl(
|
|
|
787
814
|
refAggregateId: target.id,
|
|
788
815
|
});
|
|
789
816
|
}
|
|
817
|
+
// THUNK form (`t.ref(() => Agg)`) — resolve lazily to break a bidirectional declaration cycle.
|
|
818
|
+
if (typeof a === "function") {
|
|
819
|
+
return makeField<string, "ref">("ref", z.string(), Conflict, { refAggregateIdThunk: () => a().id });
|
|
820
|
+
}
|
|
790
821
|
return makeField<string, "ref">("ref", z.string(), Conflict, { refAggregateId: a.id });
|
|
791
822
|
}
|
|
792
823
|
|