@githolon/dsl 0.101.5 → 0.102.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/dart/CHANGELOG.md +13 -0
- package/dart/lib/nomos_client.dart +3 -52
- package/dart/lib/src/dev_cloud.dart +5 -0
- package/dart/lib/src/home.dart +10 -15
- package/dart/lib/src/nms_conformance_runtime.dart +12 -0
- package/dart/lib/src/nms_generated_runtime.dart +29 -0
- package/dart/lib/src/nms_test_environment.dart +8 -0
- package/dart/lib/src/node_runner_transport.dart +56 -18
- package/dart/lib/src/provider.dart +5 -1
- package/dart/lib/src/record_origin.dart +55 -0
- package/dart/lib/src/runner/node-entry.mjs +9105 -0
- package/dart/lib/src/runner/offline-install.mjs +4729 -0
- package/dart/lib/src/session_bound.dart +23 -10
- package/dart/lib/src/source_trust.dart +51 -0
- package/dart/lib/src/test_harness.dart +490 -58
- package/dart/lib/src/webview_bridge.dart +167 -58
- package/dart/lib/src/workspace_name.dart +7 -1
- package/dart/pubspec.lock +3 -3
- package/dart/pubspec.yaml +4 -2
- package/dart/test/acquire_session_credentials_test.dart +34 -17
- package/dart/test/acquire_session_test.dart +109 -24
- package/dart/test/adv_realm_interference_test.dart +227 -99
- package/dart/test/bundle_version_pairing_test.dart +1 -1
- package/dart/test/cloud_bound_topology_test.dart +1 -1
- package/dart/test/cluster_attested_test.dart +52 -35
- package/dart/test/cluster_births_test.dart +5 -2
- package/dart/test/cluster_device_keyed_acceptance_test.dart +5 -2
- package/dart/test/cluster_local_autosync_test.dart +5 -2
- package/dart/test/cluster_scale_test.dart +5 -2
- package/dart/test/compat_test.dart +1 -1
- package/dart/test/dlq_session_surface_test.dart +2 -1
- package/dart/test/errors_test.dart +1 -1
- package/dart/test/first_app_gauntlet_test.dart +78 -51
- package/dart/test/four_layer_nested_topology_test.dart +1 -1
- package/dart/test/harness_phase_timeout_test.dart +1 -1
- package/dart/test/home_parity_test.dart +9 -5
- package/dart/test/local_sync_inert_test.dart +22 -14
- package/dart/test/node_runner_transport_test.dart +9 -5
- package/dart/test/null_slot_test.dart +1 -1
- package/dart/test/parent_signer_recovery_test.dart +5 -2
- package/dart/test/schema_validation_test.dart +1 -1
- package/dart/test/sealing_client_test.dart +2 -1
- package/dart/test/session_bound_test.dart +92 -119
- package/dart/test/stale_bundle_test.dart +1 -1
- package/dart/test/test_harness_test.dart +77 -22
- package/dart/test/vo_read_typing_test.dart +32 -14
- package/dart/test/watch_after_offer_stall_test.dart +2 -1
- package/dart/test/webview_bridge_test.dart +17 -4
- package/dart/test/wire_test.dart +1 -1
- package/dart_types/CHANGELOG.md +8 -0
- package/dart_types/lib/nomos_types.dart +7 -6
- package/dart_types/lib/src/activity.dart +23 -13
- package/dart_types/lib/src/foreign_ref.dart +61 -112
- package/dart_types/lib/src/invitation.dart +96 -0
- package/dart_types/lib/src/nomos_ref.dart +97 -59
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/surfaced_test.dart +125 -19
- package/package.json +7 -3
- package/runtime/generated-application-host.js +8851 -0
- package/src/activity.ts +2 -2
- package/src/aggregate.ts +24 -1
- package/src/application_composition.ts +282 -0
- package/src/application_topology.ts +151 -0
- package/src/attested_core.ts +2 -2
- package/src/authoring_context.ts +9 -0
- package/src/birth_meter_ensure.ts +1 -1
- package/src/births.ts +53 -5
- package/src/build_package.ts +79 -71
- package/src/business_birth.ts +93 -10
- package/src/clear_directive.ts +3 -1
- package/src/codegen_dart.ts +369 -420
- package/src/codegen_dart_application.ts +1508 -0
- package/src/codegen_dart_client.ts +156 -95
- package/src/codegen_dart_scenario.ts +299 -0
- package/src/codegen_openapi.ts +8 -3
- package/src/codegen_proof.ts +1113 -1137
- package/src/codegen_provider_dart.ts +12 -1
- package/src/codegen_ts.ts +1176 -496
- package/src/compat_package_main.ts +22 -10
- package/src/compile_package_main.ts +1536 -337
- package/src/contract.ts +10 -4
- package/src/dart_barrel_guard.ts +5 -1
- package/src/describe_model.ts +13 -5
- package/src/directive.ts +244 -5
- package/src/engine_entry.ts +56 -13
- package/src/engine_javascript.ts +31 -0
- package/src/evolve_preview.ts +3 -12
- package/src/framework/application_home.ts +24 -10
- package/src/framework/capability.ts +15 -1
- package/src/framework/domain_lifecycle.ts +8 -0
- package/src/framework/framework_aggregates.ts +2 -2
- package/src/framework/genesis.ts +11 -1
- package/src/framework/home.ts +178 -75
- package/src/framework/impure_capability.ts +24 -7
- package/src/framework/relations.ts +13 -5
- package/src/framework/workspaces.ts +13 -12
- package/src/generated_client_names.ts +42 -3
- package/src/idof_lint.ts +65 -13
- package/src/index.ts +46 -12
- package/src/intent.ts +587 -0
- package/src/law_check.ts +64 -16
- package/src/precondition.ts +16 -4
- package/src/read_projection_namespace.ts +2 -7
- package/src/recipient.ts +33 -0
- package/src/relation_schema.ts +93 -0
- package/src/secondary_ensure.ts +11 -2
- package/src/shape_compat.ts +11 -1
- package/src/usd.ts +30 -8
- package/src/usd_layers.ts +447 -246
- package/src/usd_state.ts +1 -1
- package/src/value_object.ts +49 -13
- package/src/wire_encode.ts +124 -26
- package/src/workspace_sharing.ts +269 -0
- package/src/zod_introspection.ts +84 -0
- package/dart/example/nomos_secure_key_store.dart +0 -82
- package/dart/example/nomos_webview_transport.dart +0 -63
- package/dart/lib/nomos_headless.dart +0 -50
- package/dart/lib/testing.dart +0 -14
- package/dart/test/co2_product_gauntlet_test.dart +0 -244
- package/src/framework/identity.ts +0 -537
package/src/codegen_dart.ts
CHANGED
|
@@ -19,17 +19,17 @@
|
|
|
19
19
|
* - a Dart enum per `t.enum([...])` field;
|
|
20
20
|
* - a Dart intent class per domain action — a pure typed DTO. Each class exposes its
|
|
21
21
|
* `domain` / `intentType` (so the call site never hand-types a string) and a
|
|
22
|
-
* `
|
|
22
|
+
* internal `nmsInternalToPayloadJson()` seam that serialises only its typed fields to data. It builds no
|
|
23
23
|
* `Event` and no `Hlc`: the GitHolon executes the installed domain plan.
|
|
24
24
|
* - a single generic effectful write verb [`offer`] that ships {domain, intentType,
|
|
25
|
-
*
|
|
25
|
+
* nmsInternalToPayloadJson()} to the GitHolon `author(domain, intentType, payloadJson, actor)`.
|
|
26
26
|
*
|
|
27
27
|
* It does NOT hand-write the fixed wire types (`Value`/`Op`/`Event`/...): those
|
|
28
28
|
* live hand-written in `dart_types/lib/src/wire.dart`. Only the per-domain shapes are
|
|
29
29
|
* generated, so they stay reproducible from the domain definitions.
|
|
30
30
|
*/
|
|
31
31
|
import { z } from "zod";
|
|
32
|
-
import { voMetaOf, collectPayloadValueObjectDescriptors, idOfMetaOf, brandedIdMetaOf, NOMOS_UNIT_BRAND } from "./value_object.js";
|
|
32
|
+
import { voMetaOf, collectPayloadValueObjectDescriptors, idOfMetaOf, idOfTargetKindOf, brandedIdMetaOf, NOMOS_UNIT_BRAND } from "./value_object.js";
|
|
33
33
|
import { recursiveTypeBaseName, zodUnwrapLazy } from "./zod_lazy.js";
|
|
34
34
|
import type { AggregateHandle } from "./aggregate.js";
|
|
35
35
|
import type { WorkspaceInvariantDecl } from "./framework/workspace_invariant.js";
|
|
@@ -53,12 +53,25 @@ import type { WorkspaceTypeDecl } from "./workspace_type.js";
|
|
|
53
53
|
import { defaultActivityDescriptor } from "./activity.js";
|
|
54
54
|
import { activityKey } from "./activity.js";
|
|
55
55
|
import { BUSINESS_CORRECTION_DIRECTIVE } from "./framework/business_corrections.js";
|
|
56
|
+
import { isBusinessIntent } from "./intent.js";
|
|
57
|
+
import { contractTypeName } from "./contract.js";
|
|
58
|
+
import { isNamespacedAggregate, qualifyReadProjectionType } from "./read_projection_namespace.js";
|
|
59
|
+
import { recipientMetaOf } from "./recipient.js";
|
|
56
60
|
|
|
57
61
|
/** A directive of any payload type. `Directive<P>` is invariant in `P`, so the
|
|
58
62
|
* generator (which only introspects shapes) takes the `any`-payload form. */
|
|
59
63
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
64
|
type AnyDirective = Directive<any>;
|
|
61
65
|
|
|
66
|
+
/** Public business actions accept input facts. Private directive DTOs retain their wire-oriented name. */
|
|
67
|
+
export function dartActionInputClassName(directive: AnyDirective): string {
|
|
68
|
+
const businessBirth = (directive as { businessBirth?: { aggregateId?: string } }).businessBirth;
|
|
69
|
+
if (businessBirth?.aggregateId !== undefined && !isBusinessIntent(directive)) {
|
|
70
|
+
return `Create${concreteClassName(businessBirth.aggregateId)}Input`;
|
|
71
|
+
}
|
|
72
|
+
return `${cap(directive.id)}${isBusinessIntent(directive) ? "Input" : "Payload"}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
62
75
|
// ---- Zod payload introspection --------------------------------------------
|
|
63
76
|
|
|
64
77
|
/** How a typed payload field serialises into the payload-as-DATA JSON the engine reads. */
|
|
@@ -69,18 +82,22 @@ export type ToJsonWrap =
|
|
|
69
82
|
| "json" // nested object — emit the field's `Map<String, Object?>` directly.
|
|
70
83
|
| "vo" // a declared value object — emit `.toJson()` (the generated VO class).
|
|
71
84
|
| "voList" // a list of value objects — emit `.map((e) => e.toJson()).toList()`.
|
|
72
|
-
| "wrapped"
|
|
85
|
+
| "wrapped" // a standalone branded-id or unit extension type — emit `.value`.
|
|
86
|
+
| "ref" // an aggregate-specific NomosRef<T> — emit its `.id` wire scalar.
|
|
87
|
+
| "recipient"; // a framework-created Recipient<User> — emit its private delivery address.
|
|
73
88
|
|
|
74
89
|
export interface PayloadFieldSpec {
|
|
75
90
|
name: string;
|
|
76
91
|
dartType: string;
|
|
92
|
+
/** Aggregate wire type when this field is a NomosRef<T>. */
|
|
93
|
+
referenceType?: string;
|
|
77
94
|
/** for enums: the Dart enum type name + raw values. */
|
|
78
95
|
enumValues?: string[];
|
|
79
|
-
/** how this field serialises into `
|
|
96
|
+
/** how this field serialises into `nmsInternalToPayloadJson()`. */
|
|
80
97
|
toJson: ToJsonWrap;
|
|
81
98
|
/**
|
|
82
99
|
* Whether the payload field is OPTIONAL (Zod `.optional()`/`.default()`). Optional
|
|
83
|
-
* fields are nullable in Dart and OMITTED from `
|
|
100
|
+
* fields are nullable in Dart and OMITTED from `nmsInternalToPayloadJson()` when null — so the
|
|
84
101
|
* engine's `.plan()` sees exactly the present-field set its TS Zod schema would (it
|
|
85
102
|
* only emits ops for present fields). Required fields are always emitted.
|
|
86
103
|
*/
|
|
@@ -89,11 +106,67 @@ export interface PayloadFieldSpec {
|
|
|
89
106
|
* NULL SEMANTICS (null_semantics.md): the payload field ADMITS a domain `null` (its zod is
|
|
90
107
|
* `.nullable()`, a `t.nullable(...)`/`t.clearable(...)` sink). It generates as the sealed
|
|
91
108
|
* tri-state [NomosSlot] so the call site says OMITTED / EXPLICIT-NULL / PRESENT explicitly —
|
|
92
|
-
* `
|
|
109
|
+
* `nmsInternalToPayloadJson` then omits, writes a real `null`, or writes the value accordingly.
|
|
93
110
|
* `false`/absent for a plain field (byte-identical to a pre-feature payload). */
|
|
94
111
|
nullAdmits?: boolean;
|
|
95
112
|
}
|
|
96
113
|
|
|
114
|
+
export interface DartBusinessRecordInput {
|
|
115
|
+
readonly idField: string;
|
|
116
|
+
readonly workspaceField: string;
|
|
117
|
+
readonly argument: string;
|
|
118
|
+
readonly dartType: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Resolve `.fromRecord(field)` against the directive's target aggregate. The
|
|
123
|
+
* aggregate owns the foreign-reference pairing, so authors name only the
|
|
124
|
+
* business id field and cannot accidentally pair it with the wrong workspace.
|
|
125
|
+
*/
|
|
126
|
+
export function dartBusinessRecordInputs(
|
|
127
|
+
directive: AnyDirective,
|
|
128
|
+
aggregates: readonly AggregateHandle[],
|
|
129
|
+
): DartBusinessRecordInput[] {
|
|
130
|
+
const declared = directive.businessRecordInputs ?? [];
|
|
131
|
+
if (declared.length === 0) return [];
|
|
132
|
+
const aggregate = aggregates.find((candidate) => candidate.id === directive.aggregateId);
|
|
133
|
+
if (aggregate === undefined) {
|
|
134
|
+
throw new Error(`${directive.id}.fromRecord(): target aggregate '${directive.aggregateId}' is not generated`);
|
|
135
|
+
}
|
|
136
|
+
const payloadNames = new Set(introspectPayload(directive.payloadSchema as z.ZodTypeAny, new Map()).map((spec) => spec.name));
|
|
137
|
+
const seenArguments = new Set<string>();
|
|
138
|
+
return declared.map((input) => {
|
|
139
|
+
const field = aggregate.fields[input.field] as Field | undefined;
|
|
140
|
+
if (field?.kind !== "ref" || field.foreignRef === undefined) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`${directive.id}.fromRecord('${input.field}'): '${directive.aggregateId}.${input.field}' must be a foreignRef field`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
const workspaceField = field.foreignRef.workspaceField;
|
|
146
|
+
if (!payloadNames.has(input.field) || !payloadNames.has(workspaceField)) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`${directive.id}.fromRecord('${input.field}'): payload must carry both '${input.field}' and its declared locator '${workspaceField}'`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
const fallback = input.field.endsWith("Id") ? input.field.slice(0, -2) : `${input.field}Record`;
|
|
152
|
+
const argument = input.as ?? fallback;
|
|
153
|
+
if (!/^[a-z][A-Za-z0-9_]*$/.test(argument)) {
|
|
154
|
+
throw new Error(`${directive.id}.fromRecord('${input.field}'): '${argument}' is not a lowerCamelCase Dart argument`);
|
|
155
|
+
}
|
|
156
|
+
if (seenArguments.has(argument) || (payloadNames.has(argument) && argument !== input.field && argument !== workspaceField)) {
|
|
157
|
+
throw new Error(`${directive.id}.fromRecord('${input.field}'): business argument '${argument}' collides with another payload field`);
|
|
158
|
+
}
|
|
159
|
+
seenArguments.add(argument);
|
|
160
|
+
const target = field.foreignRef.targetAggregateId;
|
|
161
|
+
return {
|
|
162
|
+
idField: input.field,
|
|
163
|
+
workspaceField,
|
|
164
|
+
argument,
|
|
165
|
+
dartType: field.foreignRef.targetIsContract === true ? contractTypeName(target) : concreteClassName(target),
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
97
170
|
type ZodInternalDef = {
|
|
98
171
|
type?: string | z.ZodTypeAny;
|
|
99
172
|
innerType?: z.ZodTypeAny;
|
|
@@ -269,11 +342,11 @@ export function introspectPayload(
|
|
|
269
342
|
|
|
270
343
|
/**
|
|
271
344
|
* The aggregate ids a directive set references via `idOf(Agg)` in its TOP-LEVEL payload fields
|
|
272
|
-
* (dart_codegen_parity.md P2). A cross-domain `idOf` types the field as
|
|
273
|
-
*
|
|
345
|
+
* (dart_codegen_parity.md P2). A cross-domain `idOf` types the field as `NomosRef<SiblingAggregate>`;
|
|
346
|
+
* the aggregate type lives in the owning domain's generated file, so the referencing
|
|
274
347
|
* domain's file must import it (flat: a relative `<domain>.dart`; layered: the sibling TYPES file).
|
|
275
348
|
* Returns EVERY idOf target (same- and cross-domain); the caller filters to the cross-domain ones it
|
|
276
|
-
* must import (a same-domain target
|
|
349
|
+
* must import (a same-domain target aggregate is declared locally). Nullable/optional-wrapped
|
|
277
350
|
* idOf fields are unwrapped by `idOfMetaOf`.
|
|
278
351
|
*/
|
|
279
352
|
export function collectPayloadIdOfTargets(directives: readonly { payloadSchema?: z.ZodTypeAny }[]): Set<string> {
|
|
@@ -305,6 +378,9 @@ function introspectField(
|
|
|
305
378
|
enumDartTypes: Map<string, string>,
|
|
306
379
|
lazyNames?: Map<z.ZodTypeAny, string>,
|
|
307
380
|
): Omit<PayloadFieldSpec, "optional"> {
|
|
381
|
+
if (recipientMetaOf(zt) !== undefined) {
|
|
382
|
+
return { name, dartType: "Recipient<User>", toJson: "recipient" };
|
|
383
|
+
}
|
|
308
384
|
// RECURSIVE `z.lazy` (a self-referencing tree/JSON payload): if the compiler synthesized a
|
|
309
385
|
// typed recursive class for this knot (`lazyNames`), the field IS that class (toJson via its
|
|
310
386
|
// `.toJson()`). Without a synthesized class (no map, or a degraded knot) it resolves one level
|
|
@@ -334,11 +410,20 @@ function introspectField(
|
|
|
334
410
|
const tn = zodTypeName(zt);
|
|
335
411
|
switch (tn) {
|
|
336
412
|
case "ZodString": {
|
|
337
|
-
// idOf(Agg)
|
|
338
|
-
//
|
|
413
|
+
// idOf(Agg): wire-identical to z.string(), but the public Dart value is the ONE
|
|
414
|
+
// workspace-qualified aggregate reference. NomosRef<Site> and NomosRef<Asset> are
|
|
415
|
+
// different compile-time types; serialization keeps the kernel's string id wire.
|
|
339
416
|
const idAgg = idOfMetaOf(zt);
|
|
340
417
|
if (idAgg !== undefined) {
|
|
341
|
-
|
|
418
|
+
const targetClass = idOfTargetKindOf(zt) === "contract"
|
|
419
|
+
? contractTypeName(idAgg)
|
|
420
|
+
: concreteClassName(idAgg);
|
|
421
|
+
return {
|
|
422
|
+
name,
|
|
423
|
+
dartType: `NomosRef<${targetClass}>`,
|
|
424
|
+
referenceType: idAgg,
|
|
425
|
+
toJson: "ref",
|
|
426
|
+
};
|
|
342
427
|
}
|
|
343
428
|
// brandedId("SiteId"): a standalone nominal string → the generated extension type.
|
|
344
429
|
const brand = brandedIdMetaOf(zt);
|
|
@@ -447,24 +532,30 @@ interface AggFieldSpec {
|
|
|
447
532
|
kind: MaterializedFieldKind;
|
|
448
533
|
dartType: string;
|
|
449
534
|
enumValues?: string[];
|
|
450
|
-
/** For an own-id string field represented as
|
|
535
|
+
/** For an own-id string field represented as `NomosRef<TAggregate>`. */
|
|
451
536
|
valueClass?: string;
|
|
537
|
+
/** Stable wire type for an own-id field's canonical reference. */
|
|
538
|
+
valueAggregateType?: string;
|
|
452
539
|
/**
|
|
453
|
-
* For a `ref`-kind field: the
|
|
454
|
-
* `Building.siteId` ref → `
|
|
540
|
+
* For a `ref`-kind field: the generated class of the target aggregate (e.g. a
|
|
541
|
+
* `Building.siteId` ref → `NomosRef<Site>`). The field's Dart type is this reference (NOT a
|
|
455
542
|
* bare `String`), so a cross-aggregate ref carries its target's TYPE — passing a
|
|
456
|
-
* `
|
|
543
|
+
* `NomosRef<Room>` where a `NomosRef<Site>` is expected is a compile error. `undefined` for a
|
|
457
544
|
* non-ref field (or a ref whose target type is unknown — falls back to `String`).
|
|
458
545
|
*/
|
|
459
546
|
refIdClass?: string;
|
|
547
|
+
/** The target aggregate's stable wire type, carried in the runtime reference triple. */
|
|
548
|
+
refAggregateType?: string;
|
|
460
549
|
/**
|
|
461
550
|
* For a `t.foreignRef` field (a TYPED CROSS-WORKSPACE LOCATOR, not an atomic FK): the target
|
|
462
551
|
* aggregate's Dart class (e.g. `CatalogueListing`). The field's Dart type is `ForeignRef<Target>`
|
|
463
|
-
* (from the shared `nomos_types` runtime),
|
|
552
|
+
* (from the shared `nomos_types` runtime), rather than a same-workspace `NomosRef` — so the app can
|
|
464
553
|
* `resolve`/`watch`/`attest` the foreign target on demand. `undefined` for a plain same-workspace
|
|
465
|
-
* `ref` field (which stays
|
|
554
|
+
* `ref` field (which stays `NomosRef<Target>` via {@link refIdClass}).
|
|
466
555
|
*/
|
|
467
556
|
foreignRefTarget?: string;
|
|
557
|
+
/** The target aggregate's domain-qualified kernel type carried by the locator. */
|
|
558
|
+
foreignRefAggregateType?: string;
|
|
468
559
|
/**
|
|
469
560
|
* For a `t.foreignRef` field: the wire name of the SIBLING field on THIS aggregate holding the foreign
|
|
470
561
|
* WORKSPACE id (the locator's `workspace`, dynamic per row). `undefined` for a non-foreignRef field.
|
|
@@ -601,11 +692,7 @@ function dartTypeNameFromField(raw: string): string {
|
|
|
601
692
|
}
|
|
602
693
|
|
|
603
694
|
function aggregateIdDartType(aggregateType: string): string {
|
|
604
|
-
return
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
function aggregateIdFromRowExpr(aggregateType: string, rowIdExpr: string): string {
|
|
608
|
-
return `${aggregateIdDartType(aggregateType)}.fromRow(${rowIdExpr})`;
|
|
695
|
+
return `NomosRef<${concreteClassName(aggregateType)}>`;
|
|
609
696
|
}
|
|
610
697
|
|
|
611
698
|
function aggregateFieldSpecForKey(
|
|
@@ -613,7 +700,7 @@ function aggregateFieldSpecForKey(
|
|
|
613
700
|
aggregateType: string,
|
|
614
701
|
keyField: string,
|
|
615
702
|
enumDartTypes: Map<string, string>,
|
|
616
|
-
refIdClassForAggregate: (aggregateType: string) => string =
|
|
703
|
+
refIdClassForAggregate: (aggregateType: string) => string = concreteClassName,
|
|
617
704
|
ownIdFieldsByAggregate: ReadonlyMap<string, ReadonlySet<string>> = new Map(),
|
|
618
705
|
): AggFieldSpec | undefined {
|
|
619
706
|
const agg = aggregatesById.get(aggregateType);
|
|
@@ -621,9 +708,16 @@ function aggregateFieldSpecForKey(
|
|
|
621
708
|
if (field === undefined || field.kind === "hasMany") return undefined;
|
|
622
709
|
const valueClass =
|
|
623
710
|
field.kind === "string" && (ownIdFieldsByAggregate.get(aggregateType)?.has(keyField) ?? false)
|
|
624
|
-
?
|
|
711
|
+
? concreteClassName(aggregateType)
|
|
625
712
|
: undefined;
|
|
626
|
-
return aggFieldSpec(
|
|
713
|
+
return aggFieldSpec(
|
|
714
|
+
keyField,
|
|
715
|
+
field,
|
|
716
|
+
enumDartTypes,
|
|
717
|
+
refIdClassForAggregate,
|
|
718
|
+
valueClass,
|
|
719
|
+
valueClass === undefined ? undefined : aggregateType,
|
|
720
|
+
);
|
|
627
721
|
}
|
|
628
722
|
|
|
629
723
|
function readKeyParamSpec(
|
|
@@ -632,10 +726,10 @@ function readKeyParamSpec(
|
|
|
632
726
|
): { dartType: string; valueExpr: string } {
|
|
633
727
|
if (fieldSpec === undefined) return { dartType: "String", valueExpr: paramName };
|
|
634
728
|
if (fieldSpec.valueClass !== undefined) {
|
|
635
|
-
return { dartType: fieldSpec.valueClass
|
|
729
|
+
return { dartType: `NomosRef<${fieldSpec.valueClass}>`, valueExpr: `NmsInternalNomosRefs.id(${paramName})` };
|
|
636
730
|
}
|
|
637
731
|
if (fieldSpec.kind === "ref" && fieldSpec.refIdClass !== undefined) {
|
|
638
|
-
return { dartType: fieldSpec.refIdClass
|
|
732
|
+
return { dartType: `NomosRef<${fieldSpec.refIdClass}>`, valueExpr: `NmsInternalNomosRefs.id(${paramName})` };
|
|
639
733
|
}
|
|
640
734
|
if (fieldSpec.kind === "enum") {
|
|
641
735
|
return { dartType: fieldSpec.dartType, valueExpr: `${paramName}.wire` };
|
|
@@ -658,7 +752,7 @@ function refIdClassResolver(mod: DomainModule): (aggregateType: string) => strin
|
|
|
658
752
|
}
|
|
659
753
|
return (aggregateType: string): string => {
|
|
660
754
|
const imp = imports.get(aggregateType);
|
|
661
|
-
const cls =
|
|
755
|
+
const cls = concreteClassName(aggregateType);
|
|
662
756
|
return imp === undefined ? cls : `${imp.prefix}.${cls}`;
|
|
663
757
|
};
|
|
664
758
|
}
|
|
@@ -736,8 +830,10 @@ function aggFieldSpec(
|
|
|
736
830
|
wireName: string,
|
|
737
831
|
field: Field,
|
|
738
832
|
enumDartTypes: Map<string, string>,
|
|
739
|
-
refIdClassForAggregate: (aggregateType: string) => string =
|
|
833
|
+
refIdClassForAggregate: (aggregateType: string) => string = concreteClassName,
|
|
740
834
|
valueClass?: string,
|
|
835
|
+
valueAggregateType?: string,
|
|
836
|
+
aggregateWireTypeForAggregate: (aggregateType: string) => string = (aggregateType) => aggregateType,
|
|
741
837
|
): AggFieldSpec {
|
|
742
838
|
const name = dartMemberName(wireName);
|
|
743
839
|
// Collections are ALWAYS empty-defaultable; scalars are optional only when the DSL
|
|
@@ -749,9 +845,11 @@ function aggFieldSpec(
|
|
|
749
845
|
name,
|
|
750
846
|
wireName,
|
|
751
847
|
kind: field.kind,
|
|
752
|
-
dartType: valueClass
|
|
848
|
+
dartType: valueClass === undefined ? "String" : `NomosRef<${valueClass}>`,
|
|
753
849
|
optional,
|
|
754
|
-
...(valueClass !== undefined
|
|
850
|
+
...(valueClass !== undefined && valueAggregateType !== undefined
|
|
851
|
+
? { valueClass, valueAggregateType }
|
|
852
|
+
: {}),
|
|
755
853
|
};
|
|
756
854
|
case "json":
|
|
757
855
|
// A NAMED VALUE OBJECT (`ValueObject.field()`) types as its generated Dart class
|
|
@@ -786,7 +884,7 @@ function aggFieldSpec(
|
|
|
786
884
|
// A contract target names the shared DTO class `<ContractId>Contract` (emitted into the shared
|
|
787
885
|
// `contracts.dart`); the aggregate class is `<ContractId>` and must not collide, hence the suffix.
|
|
788
886
|
const target = field.foreignRef.targetIsContract === true
|
|
789
|
-
?
|
|
887
|
+
? contractTypeName(field.foreignRef.targetAggregateId)
|
|
790
888
|
: concreteClassName(field.foreignRef.targetAggregateId);
|
|
791
889
|
return {
|
|
792
890
|
name,
|
|
@@ -795,12 +893,14 @@ function aggFieldSpec(
|
|
|
795
893
|
dartType: `ForeignRef<${target}>`,
|
|
796
894
|
optional,
|
|
797
895
|
foreignRefTarget: target,
|
|
896
|
+
foreignRefAggregateType: aggregateWireTypeForAggregate(
|
|
897
|
+
field.foreignRef.targetAggregateId,
|
|
898
|
+
),
|
|
798
899
|
foreignRefWorkspaceField: field.foreignRef.workspaceField,
|
|
799
900
|
};
|
|
800
901
|
}
|
|
801
|
-
// A plain same-workspace `ref` field carries ANOTHER aggregate's
|
|
802
|
-
//
|
|
803
|
-
// ref is a `SiteId`, never a bare String. The target wire id is recorded on
|
|
902
|
+
// A plain same-workspace `ref` field carries ANOTHER aggregate's identity — type it as
|
|
903
|
+
// `NomosRef<Target>` (cross-aggregate ref typing), never a bare String. The target wire id is recorded on
|
|
804
904
|
// the field (`refAggregateId`); fall back to `String` only if it is absent.
|
|
805
905
|
const targetType = field.refAggregateId;
|
|
806
906
|
const refIdClass =
|
|
@@ -811,9 +911,14 @@ function aggFieldSpec(
|
|
|
811
911
|
name,
|
|
812
912
|
wireName,
|
|
813
913
|
kind: field.kind,
|
|
814
|
-
dartType: refIdClass
|
|
914
|
+
dartType: refIdClass === undefined ? "String" : `NomosRef<${refIdClass}>`,
|
|
815
915
|
optional,
|
|
816
|
-
...(refIdClass !== undefined
|
|
916
|
+
...(refIdClass !== undefined && targetType !== undefined
|
|
917
|
+
? {
|
|
918
|
+
refIdClass,
|
|
919
|
+
refAggregateType: aggregateWireTypeForAggregate(targetType),
|
|
920
|
+
}
|
|
921
|
+
: {}),
|
|
817
922
|
};
|
|
818
923
|
}
|
|
819
924
|
case "int":
|
|
@@ -824,7 +929,16 @@ function aggFieldSpec(
|
|
|
824
929
|
case "counter":
|
|
825
930
|
return { name, wireName, kind: "int", dartType: "int", optional };
|
|
826
931
|
case "position":
|
|
827
|
-
return {
|
|
932
|
+
return {
|
|
933
|
+
name,
|
|
934
|
+
wireName,
|
|
935
|
+
kind: "string",
|
|
936
|
+
dartType: valueClass === undefined ? "String" : `NomosRef<${valueClass}>`,
|
|
937
|
+
optional,
|
|
938
|
+
...(valueClass !== undefined && valueAggregateType !== undefined
|
|
939
|
+
? { valueClass, valueAggregateType }
|
|
940
|
+
: {}),
|
|
941
|
+
};
|
|
828
942
|
case "bool":
|
|
829
943
|
return { name, wireName, kind: field.kind, dartType: "bool", optional };
|
|
830
944
|
case "enum": {
|
|
@@ -872,10 +986,11 @@ function aggFieldSpec(
|
|
|
872
986
|
function aggregateFieldSpecs(
|
|
873
987
|
agg: AggregateHandle,
|
|
874
988
|
enumDartTypes: Map<string, string>,
|
|
875
|
-
refIdClassForAggregate: (aggregateType: string) => string =
|
|
989
|
+
refIdClassForAggregate: (aggregateType: string) => string = concreteClassName,
|
|
876
990
|
ownIdFields: ReadonlySet<string> = new Set(),
|
|
991
|
+
aggregateWireTypeForAggregate: (aggregateType: string) => string = (aggregateType) => aggregateType,
|
|
877
992
|
): AggFieldSpec[] {
|
|
878
|
-
const used = new Set<string>(["
|
|
993
|
+
const used = new Set<string>(["ref"]);
|
|
879
994
|
const internal = new Set(agg.internalFields ?? []);
|
|
880
995
|
// `agg.fields` is STORED fields only — virtual `t.hasMany` inverses are in `agg.hasMany`
|
|
881
996
|
// and never reach Dart materialization (they are served by the inverse read index).
|
|
@@ -884,7 +999,7 @@ function aggregateFieldSpecs(
|
|
|
884
999
|
.map(([name, f]) => {
|
|
885
1000
|
const valueClass =
|
|
886
1001
|
f.kind === "string" && ownIdFields.has(name)
|
|
887
|
-
?
|
|
1002
|
+
? concreteClassName(agg.id)
|
|
888
1003
|
: undefined;
|
|
889
1004
|
const base = aggFieldSpec(
|
|
890
1005
|
name,
|
|
@@ -892,6 +1007,10 @@ function aggregateFieldSpecs(
|
|
|
892
1007
|
enumDartTypes,
|
|
893
1008
|
refIdClassForAggregate,
|
|
894
1009
|
valueClass,
|
|
1010
|
+
valueClass === undefined
|
|
1011
|
+
? undefined
|
|
1012
|
+
: aggregateWireTypeForAggregate(agg.id),
|
|
1013
|
+
aggregateWireTypeForAggregate,
|
|
895
1014
|
);
|
|
896
1015
|
// PARTIAL-FOLD DOCTRINE (every read-model field is nullable). The projection
|
|
897
1016
|
// serves PARTIAL folds: a freshly-created or not-yet-fully-folded aggregate may
|
|
@@ -923,6 +1042,12 @@ function aggregateFieldSpecs(
|
|
|
923
1042
|
? { ...surfacedSpec, nullAdmits: true, ...(nmeta.nullReason !== undefined ? { nullReason: nmeta.nullReason } : {}) }
|
|
924
1043
|
: surfacedSpec;
|
|
925
1044
|
if (used.has(spec.name)) {
|
|
1045
|
+
if (spec.name === "ref") {
|
|
1046
|
+
throw new Error(
|
|
1047
|
+
`aggregate '${agg.id}' field '${spec.wireName}' collides with Nomos's canonical record reference 'ref' — ` +
|
|
1048
|
+
`rename the business field; NomosRef<${concreteClassName(agg.id)}> is the one generated identity for this aggregate`,
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
926
1051
|
throw new Error(
|
|
927
1052
|
`aggregate '${agg.id}' field '${spec.wireName}' maps to duplicate Dart field '${spec.name}'`,
|
|
928
1053
|
);
|
|
@@ -938,21 +1063,9 @@ export function cap(s: string): string {
|
|
|
938
1063
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
939
1064
|
}
|
|
940
1065
|
|
|
941
|
-
// ---- typed-id naming (the type-safety keystone, generalized) ----------------
|
|
942
|
-
//
|
|
943
|
-
// A DISTINCT typed id class is emitted PER AGGREGATE so passing one aggregate's
|
|
944
|
-
// id where another's is expected is a COMPILE ERROR. The class NAME must be a pure
|
|
945
|
-
// function of the aggregate's wire TYPE (e.g. `SiteRootAggregate`) so three sites
|
|
946
|
-
// agree on it everywhere it is referenced — the typed-id class declaration, every
|
|
947
|
-
// `createX` factory's return type, AND every cross-aggregate REF field that targets
|
|
948
|
-
// that type. Convention: strip a trailing `RootAggregate`/`Aggregate` suffix and
|
|
949
|
-
// append `Id` — `SiteRootAggregate → SiteId`, `ThingAggregate → ThingId`,
|
|
950
|
-
// `TrackableThing → TrackableThingId`, `BuildingAggregate → BuildingId`. The
|
|
951
|
-
// `RootAggregate` strip BEFORE `Aggregate` keeps the proven `SiteId` byte-identical.
|
|
952
|
-
|
|
953
1066
|
// ---- Dart product-name registry (`.dartName`, architecture/dart_naming.md) ----
|
|
954
1067
|
// A wire id → declared Dart base name map. When an aggregate declared `.dartName("Site")`,
|
|
955
|
-
// EVERY Dart naming derivation (concrete class, `<stem>ReadModel`,
|
|
1068
|
+
// EVERY Dart naming derivation (concrete class, `<stem>ReadModel`, `NomosRef<Concrete>`,
|
|
956
1069
|
// `read/watch<Name>ById`, `ForeignRef<Target>`) consults this instead of stemming the wire id.
|
|
957
1070
|
// Populated per-domain by `registerDartNames` at each Dart-emit entry point. NON-law, Dart-only
|
|
958
1071
|
// (TS codegen never reads it); empty ⇒ every derivation is byte-identical to before this existed.
|
|
@@ -1002,24 +1115,6 @@ function dartNameStem(aggregateType: string): string {
|
|
|
1002
1115
|
return cap(stem === "" ? aggregateType : stem);
|
|
1003
1116
|
}
|
|
1004
1117
|
|
|
1005
|
-
/** The typed-id class name for an aggregate WIRE TYPE (e.g. `SiteRootAggregate` →
|
|
1006
|
-
* `SiteId`). Pure + deterministic: the SAME wire type ALWAYS yields the SAME name,
|
|
1007
|
-
* so the id-class decl, the `createX` factory return, and every typed ref field that
|
|
1008
|
-
* points at this type all agree. Honours `.dartName` (`Site` → `SiteId`). */
|
|
1009
|
-
export function typedIdClassForAggregateType(aggregateType: string): string {
|
|
1010
|
-
const declared = dartNameByWireId.get(aggregateType);
|
|
1011
|
-
if (declared !== undefined) return `${declared}Id`;
|
|
1012
|
-
let stem = aggregateType;
|
|
1013
|
-
if (stem.endsWith("RootAggregate")) {
|
|
1014
|
-
stem = stem.slice(0, -"RootAggregate".length);
|
|
1015
|
-
} else if (stem.endsWith("Aggregate")) {
|
|
1016
|
-
stem = stem.slice(0, -"Aggregate".length);
|
|
1017
|
-
}
|
|
1018
|
-
// Empty stem (a type literally named `Aggregate`) is impossible for registered
|
|
1019
|
-
// aggregates, but guard so the name is always a legal identifier.
|
|
1020
|
-
return `${cap(stem === "" ? aggregateType : stem)}Id`;
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
1118
|
/** Collect all enum value-sets across aggregates + directives, name each ONCE. */
|
|
1024
1119
|
function collectEnums(
|
|
1025
1120
|
aggregates: AggregateHandle[],
|
|
@@ -1271,16 +1366,17 @@ function emitPermissionVocabulary(vocab: PermissionVocabulary | undefined): stri
|
|
|
1271
1366
|
function emitAggregate(
|
|
1272
1367
|
agg: AggregateHandle,
|
|
1273
1368
|
enumDartTypes: Map<string, string>,
|
|
1274
|
-
refIdClassForAggregate: (aggregateType: string) => string =
|
|
1369
|
+
refIdClassForAggregate: (aggregateType: string) => string = concreteClassName,
|
|
1275
1370
|
ownIdFields: ReadonlySet<string> = new Set(),
|
|
1371
|
+
aggregateWireTypeForAggregate: (aggregateType: string) => string = (aggregateType) => aggregateType,
|
|
1276
1372
|
): string {
|
|
1277
1373
|
const className = concreteClassName(agg.id); // wire id (already PascalCase) unless `.dartName` overrides.
|
|
1278
|
-
const idType = aggregateIdDartType(agg.id);
|
|
1279
1374
|
const specs = aggregateFieldSpecs(
|
|
1280
1375
|
agg,
|
|
1281
1376
|
enumDartTypes,
|
|
1282
1377
|
refIdClassForAggregate,
|
|
1283
1378
|
ownIdFields,
|
|
1379
|
+
aggregateWireTypeForAggregate,
|
|
1284
1380
|
);
|
|
1285
1381
|
// ATTRIBUTED READS (field_provenance.md): the `.attributed()` field names — drives the
|
|
1286
1382
|
// `$provenance` map + per-field `<field>Provenance` getters + `lastAuthors`. Empty for
|
|
@@ -1300,14 +1396,9 @@ function emitAggregate(
|
|
|
1300
1396
|
// nullable (`String?`/`int?`/`Enum?`); collections (`set`/`map`) keep their non-null
|
|
1301
1397
|
// container type but default to EMPTY when absent.
|
|
1302
1398
|
const fieldDecls = [
|
|
1303
|
-
` /// The
|
|
1304
|
-
` ///
|
|
1305
|
-
`
|
|
1306
|
-
` final ${idType} aggregateId;`,
|
|
1307
|
-
` /// THE ONE REFERENCE (architecture/nomos_ref.md): this instance's canonical, workspace-qualified`,
|
|
1308
|
-
` /// [NomosRef] (\`nomos://<workspace>/<type>/<id>\`) — composed at decode from the session workspace +`,
|
|
1309
|
-
` /// the row's type/id. Workspace is '' when decoded off the bare bridge (the client accessors fill it).`,
|
|
1310
|
-
` final NomosRef ref;`,
|
|
1399
|
+
` /// The canonical workspace-qualified identity of this ${className}. Pass this exact value`,
|
|
1400
|
+
` /// to generated actions, find and watch methods; never copy its id into application state.`,
|
|
1401
|
+
` final NomosRef<${className}> ref;`,
|
|
1311
1402
|
...(attributedFields.length > 0
|
|
1312
1403
|
? [
|
|
1313
1404
|
` /// ATTRIBUTED READS (field_provenance.md): per-field author provenance for the`,
|
|
@@ -1335,8 +1426,7 @@ function emitAggregate(
|
|
|
1335
1426
|
}),
|
|
1336
1427
|
].join("\n");
|
|
1337
1428
|
const ctorParams = [
|
|
1338
|
-
` this.
|
|
1339
|
-
` this.ref = const NomosRef(workspace: '', type: '${agg.id}', id: ''),`,
|
|
1429
|
+
` required this.ref,`,
|
|
1340
1430
|
...(attributedFields.length > 0 ? [` this.\$provenance = const {},`] : []),
|
|
1341
1431
|
...specs.map(
|
|
1342
1432
|
// A required scalar stays `required`; an optional scalar OR a collection is not
|
|
@@ -1358,8 +1448,7 @@ function emitAggregate(
|
|
|
1358
1448
|
// flat-projection reader from `subscriptions.dart`. The kernel aggregate [id] is
|
|
1359
1449
|
// threaded from the row id (GAP 2) — defaulting to '' for a bare-map call.
|
|
1360
1450
|
const parseLines = [
|
|
1361
|
-
`
|
|
1362
|
-
` ref: NomosRef(workspace: workspace, type: type.isEmpty ? '${agg.id}' : type, id: id),`,
|
|
1451
|
+
` ref: NmsInternalNomosRefs.create<${className}>(workspace: _location, type: _kind.isEmpty ? '${aggregateWireTypeForAggregate(agg.id)}' : _kind, id: id),`,
|
|
1363
1452
|
...(attributedFields.length > 0
|
|
1364
1453
|
? [` \$provenance: FieldProvenance.mapFromJson(data['__prov']),`]
|
|
1365
1454
|
: []),
|
|
@@ -1376,7 +1465,7 @@ function emitAggregate(
|
|
|
1376
1465
|
// distinct aggregates). An optional scalar compares with `==` (null-safe); a
|
|
1377
1466
|
// collection compares set/map-wise (always non-null after the empty default).
|
|
1378
1467
|
const eqLines = [
|
|
1379
|
-
`other.
|
|
1468
|
+
`other.ref == ref`,
|
|
1380
1469
|
...specs.map((s) =>
|
|
1381
1470
|
s.kind === "set"
|
|
1382
1471
|
? `other.${s.name}.length == ${s.name}.length && other.${s.name}.containsAll(${s.name})`
|
|
@@ -1387,7 +1476,7 @@ function emitAggregate(
|
|
|
1387
1476
|
].join(" &&\n ");
|
|
1388
1477
|
|
|
1389
1478
|
const hashLines = [
|
|
1390
|
-
`
|
|
1479
|
+
` ref,`,
|
|
1391
1480
|
...specs.map((s) =>
|
|
1392
1481
|
s.kind === "set" || s.kind === "map"
|
|
1393
1482
|
? ` Object.hashAll(${s.name}${s.kind === "map" ? ".entries.map((e) => Object.hash(e.key, e.value))" : ""}),`
|
|
@@ -1401,7 +1490,7 @@ function emitAggregate(
|
|
|
1401
1490
|
`/// Deserialized via [${className}.fromJson] from the FLAT, already-decoded`,
|
|
1402
1491
|
`/// projection \`data\` map (the \`{type,id,data}\` row \`query()\`/\`query_by_id()\``,
|
|
1403
1492
|
`/// emits) — e.g. \`{"name":"HQ","createdAt":1000}\`. The kernel aggregate`,
|
|
1404
|
-
`/// [
|
|
1493
|
+
`/// [ref] is composed from the row's workspace, type and id. Read it reactively through the`,
|
|
1405
1494
|
`/// generated \`watch…\` accessors below. The projection serves PARTIAL folds, so`,
|
|
1406
1495
|
`/// EVERY scalar field is nullable — \`null\` when a freshly-folded aggregate has not`,
|
|
1407
1496
|
`/// yet folded it (never a strict cast that throws); collections default to EMPTY.`,
|
|
@@ -1417,10 +1506,7 @@ function emitAggregate(
|
|
|
1417
1506
|
` /// decoded to bare JSON), carrying the kernel aggregate [id] from the row \`id\``,
|
|
1418
1507
|
` /// (defaults to '' for a bare-map call). PARTIAL folds: every scalar decodes`,
|
|
1419
1508
|
` /// \`null\` when absent (never throws); absent collections decode empty.`,
|
|
1420
|
-
`
|
|
1421
|
-
` /// [workspace] + [type] compose the canonical [ref] (both optional — the client accessors thread the`,
|
|
1422
|
-
` /// session workspace + the row type; a bare decode leaves workspace '' and falls back to the type '${agg.id}').`,
|
|
1423
|
-
` factory ${className}.fromJson(Map<String, dynamic> data, [String id = '', String workspace = '', String type = '']) {`,
|
|
1509
|
+
` factory ${className}.fromJson(Map<String, dynamic> data, [String id = '', String _location = '', String _kind = '']) {`,
|
|
1424
1510
|
` return ${className}(`,
|
|
1425
1511
|
parseLines,
|
|
1426
1512
|
` );`,
|
|
@@ -1620,7 +1706,9 @@ function unionArmClassName(unionName: string, armKey: string): string {
|
|
|
1620
1706
|
function dartTypeOfVoField(f: ValueObjectFieldDescriptor, voName: string, fieldName: string): string {
|
|
1621
1707
|
switch (f.type) {
|
|
1622
1708
|
case "string":
|
|
1623
|
-
return f.
|
|
1709
|
+
return f.idOf !== undefined
|
|
1710
|
+
? `NomosRef<${concreteClassName(f.idOf)}>`
|
|
1711
|
+
: f.brand ?? "String"; // a brandedId(...) → the generated extension-type wrapper
|
|
1624
1712
|
case "number":
|
|
1625
1713
|
// a UNIT-tagged number is the generated zero-cost extension type; `.int()` is Dart int.
|
|
1626
1714
|
if (f.unit !== undefined) return f.unit;
|
|
@@ -1653,6 +1741,10 @@ function voFromExpr(
|
|
|
1653
1741
|
): string {
|
|
1654
1742
|
switch (f.type) {
|
|
1655
1743
|
case "string":
|
|
1744
|
+
if (f.idOf !== undefined) {
|
|
1745
|
+
const target = concreteClassName(f.idOf);
|
|
1746
|
+
return `NmsInternalNomosRefs.create<${target}>(workspace: '', type: '${f.idOf}', id: ${expr} as String)`;
|
|
1747
|
+
}
|
|
1656
1748
|
return f.brand !== undefined ? `${f.brand}(${expr} as String)` : `${expr} as String`;
|
|
1657
1749
|
case "enum":
|
|
1658
1750
|
// TOLERANT typed decode — an unknown wire value becomes the sentinel case.
|
|
@@ -1684,6 +1776,7 @@ function voToJsonExpr(f: ValueObjectFieldDescriptor, expr: string, depth = 0): s
|
|
|
1684
1776
|
case "number":
|
|
1685
1777
|
return f.unit !== undefined ? `${expr}.value` : expr;
|
|
1686
1778
|
case "string":
|
|
1779
|
+
if (f.idOf !== undefined) return `NmsInternalNomosRefs.id(${expr})`;
|
|
1687
1780
|
return f.brand !== undefined ? `${expr}.value` : expr; // a brandedId extension type → its wire string
|
|
1688
1781
|
case "boolean":
|
|
1689
1782
|
case "literal":
|
|
@@ -2113,7 +2206,7 @@ function contractFromExpr(field: Field, wire: string): string {
|
|
|
2113
2206
|
* references only nomos_types primitives (+ any VO class, which is shared). Emitted into `contracts.dart`.
|
|
2114
2207
|
*/
|
|
2115
2208
|
function emitDartContractClass(c: import("./contract.js").ContractHandle): string {
|
|
2116
|
-
const cls =
|
|
2209
|
+
const cls = contractTypeName(c.id);
|
|
2117
2210
|
const fields = Object.entries(c.fields).map(([wireName, f]) => ({
|
|
2118
2211
|
wireName,
|
|
2119
2212
|
member: dartMemberName(wireName),
|
|
@@ -2154,37 +2247,19 @@ export function generateDartContracts(
|
|
|
2154
2247
|
* `ReferenceCode.field()`) render inside the DTOs, and their classes are HOISTED there (layer-zero),
|
|
2155
2248
|
* so contracts.dart must import it (co2 #117b). */
|
|
2156
2249
|
readonly frameworkImportUri?: string;
|
|
2157
|
-
/** Local aggregates' typed-id class names — a contract named after a local aggregate must NOT
|
|
2158
|
-
* re-declare its `<X>Id` (the aggregate's own class, with `.fromRow`, already exists). */
|
|
2159
|
-
readonly aggregateIdClassNames?: ReadonlySet<string>;
|
|
2160
2250
|
} = {},
|
|
2161
2251
|
): string {
|
|
2162
2252
|
const sorted = [...contracts].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
2163
|
-
// CONTRACT TYPED IDS (co2 #117a): `idOf(SomeContract)` in a payload types the field `<ContractId>Id` —
|
|
2164
|
-
// exactly like an aggregate's typed id — but a contract has no aggregate emitter to declare the class.
|
|
2165
|
-
// Emit each contract's id as a ZERO-COST String extension type here (the shared layer every domain
|
|
2166
|
-
// imports): a const ctor from the wire string + `.value` — the exact surface the generated payloads use.
|
|
2167
|
-
const idTypes = sorted
|
|
2168
|
-
.map((c) => typedIdClassForAggregateType(c.id))
|
|
2169
|
-
.filter((cls) => !(opts.aggregateIdClassNames?.has(cls) ?? false))
|
|
2170
|
-
.map((cls) => [
|
|
2171
|
-
`/// TYPED CONTRACT ID \`${cls}\` — a ZERO-COST nominal String (Dart extension type): the id of the`,
|
|
2172
|
-
`/// foreign aggregate behind the contract, as \`idOf(<Contract>)\` payload fields carry it. Construct`,
|
|
2173
|
-
`/// it from a \`ForeignRef.id\` / a queried row id: \`${cls}(ref.id)\`; \`.value\` is the wire string.`,
|
|
2174
|
-
`extension type const ${cls}(String value) {}`,
|
|
2175
|
-
].join("\n"));
|
|
2176
2253
|
return [
|
|
2177
2254
|
`// GENERATED by nomos-compile — the package's shared domain CONTRACTS (public DTOs). DO NOT EDIT.`,
|
|
2178
2255
|
`// Every domain depends on this shared layer; no domain imports another's concrete types (the acyclic graph).`,
|
|
2179
2256
|
`//`,
|
|
2180
|
-
`// ignore_for_file: unused_import`,
|
|
2257
|
+
`// ignore_for_file: unused_import, invalid_use_of_internal_member`,
|
|
2181
2258
|
`library;`,
|
|
2182
2259
|
``,
|
|
2183
|
-
`import 'package:nomos_client/
|
|
2260
|
+
`import 'package:nomos_client/src/nms_generated_runtime.dart';`,
|
|
2184
2261
|
...(opts.frameworkImportUri !== undefined ? [`import '${opts.frameworkImportUri}';`] : []),
|
|
2185
2262
|
``,
|
|
2186
|
-
...idTypes,
|
|
2187
|
-
``,
|
|
2188
2263
|
...sorted.map(emitDartContractClass),
|
|
2189
2264
|
``,
|
|
2190
2265
|
].join("\n");
|
|
@@ -2213,7 +2288,18 @@ export function collectActivityDescriptors(modules: readonly DomainModule[]): Pa
|
|
|
2213
2288
|
const out: PackageActivityEntry[] = [];
|
|
2214
2289
|
for (const mod of modules) {
|
|
2215
2290
|
const domain = mod.domain ?? mod.name;
|
|
2216
|
-
|
|
2291
|
+
const privateAggregateIds = new Set(
|
|
2292
|
+
mod.aggregates
|
|
2293
|
+
.filter((aggregate) => aggregate.framework === true || (aggregate as { businessBirthInternal?: unknown }).businessBirthInternal === true)
|
|
2294
|
+
.map((aggregate) => aggregate.id),
|
|
2295
|
+
);
|
|
2296
|
+
const storyDirectives = mod.applicationIntents === undefined
|
|
2297
|
+
? mod.directives
|
|
2298
|
+
: [
|
|
2299
|
+
...mod.applicationIntents,
|
|
2300
|
+
...mod.directives.filter((d) => d.id === BUSINESS_CORRECTION_DIRECTIVE),
|
|
2301
|
+
];
|
|
2302
|
+
for (const d of storyDirectives as AnyDirective[]) {
|
|
2217
2303
|
if ((d as { businessBirthInternal?: unknown }).businessBirthInternal === true) {
|
|
2218
2304
|
if (d.id === BUSINESS_CORRECTION_DIRECTIVE) {
|
|
2219
2305
|
const kind = `${domain}.correction`;
|
|
@@ -2244,7 +2330,7 @@ export function collectActivityDescriptors(modules: readonly DomainModule[]): Pa
|
|
|
2244
2330
|
const outputs = [
|
|
2245
2331
|
{ marker: d.marker, aggregateId: d.aggregateId },
|
|
2246
2332
|
...(d.secondaryOutputs ?? []),
|
|
2247
|
-
];
|
|
2333
|
+
].filter((output) => !privateAggregateIds.has(output.aggregateId));
|
|
2248
2334
|
const seen = new Set<string>();
|
|
2249
2335
|
const refs: Array<{ name: string; type: string; from: string }> = [];
|
|
2250
2336
|
for (const output of outputs) {
|
|
@@ -2260,12 +2346,14 @@ export function collectActivityDescriptors(modules: readonly DomainModule[]): Pa
|
|
|
2260
2346
|
out.push({
|
|
2261
2347
|
domain,
|
|
2262
2348
|
directiveId: d.id,
|
|
2263
|
-
desc: desc
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2349
|
+
desc: desc === undefined
|
|
2350
|
+
? defaultActivityDescriptor({
|
|
2351
|
+
domain,
|
|
2352
|
+
directiveId: d.id,
|
|
2353
|
+
...(d.description === undefined ? {} : { description: d.description }),
|
|
2354
|
+
refs,
|
|
2355
|
+
})
|
|
2356
|
+
: { ...desc, refs: desc.refs.filter((ref) => !privateAggregateIds.has(ref.type)) },
|
|
2269
2357
|
declared: desc !== undefined,
|
|
2270
2358
|
});
|
|
2271
2359
|
}
|
|
@@ -2308,7 +2396,7 @@ function emitDartActivityArm(e: PackageActivityEntry): string[] {
|
|
|
2308
2396
|
` return ActivityEvent(`,
|
|
2309
2397
|
` eventId: ${e.correction === true ? "_activityOpaqueCorrectionId((rawRow['intentId'] ?? '') as String)" : "(rawRow['intentId'] ?? '') as String"},`,
|
|
2310
2398
|
` kind: ${dartLit(desc.kind)},`,
|
|
2311
|
-
`
|
|
2399
|
+
` performedBy: actorSubject,`,
|
|
2312
2400
|
` occurredAt: (rawRow['occurredAt'] ?? '') as String,`,
|
|
2313
2401
|
` title: renderActivityTemplate(${dartLit(desc.title)}, vars),`,
|
|
2314
2402
|
` message: renderActivityTemplate(${dartLit(desc.message)}, vars),`,
|
|
@@ -2349,10 +2437,10 @@ export function generateDartActivityRegistry(entries: readonly PackageActivityEn
|
|
|
2349
2437
|
`//`,
|
|
2350
2438
|
`// Some baseline-only registries need neither payload interpolation nor one of the aggregate lists.`,
|
|
2351
2439
|
`// Keep generated packages analyzer-clean without branching the shared renderer shape per directive mix.`,
|
|
2352
|
-
`// ignore_for_file: unused_import, unused_element, unused_local_variable`,
|
|
2440
|
+
`// ignore_for_file: unused_import, unused_element, unused_local_variable, invalid_use_of_internal_member`,
|
|
2353
2441
|
`library;`,
|
|
2354
2442
|
``,
|
|
2355
|
-
`import 'package:nomos_client/
|
|
2443
|
+
`import 'package:nomos_client/src/nms_generated_runtime.dart';`,
|
|
2356
2444
|
``,
|
|
2357
2445
|
`/// One business-readable difference found while proving a candidate model against history.`,
|
|
2358
2446
|
`enum NomosModelDifferenceKind { rules, outcome, evidence }`,
|
|
@@ -2456,7 +2544,7 @@ export function generateDartActivityRegistry(entries: readonly PackageActivityEn
|
|
|
2456
2544
|
` final String label;`,
|
|
2457
2545
|
` final String summary;`,
|
|
2458
2546
|
` final Map<String, dynamic>? details;`,
|
|
2459
|
-
` final List<NomosRef
|
|
2547
|
+
` final List<NomosRef<Object?>> refs;`,
|
|
2460
2548
|
` const NomosDescribeItem({required this.kind, required this.id, required this.label, required this.summary, this.details, this.refs = const []});`,
|
|
2461
2549
|
`}`,
|
|
2462
2550
|
``,
|
|
@@ -2665,7 +2753,7 @@ function foreignRefExpr(s: AggFieldSpec, aggregate: string): string {
|
|
|
2665
2753
|
// workspace resolves to not-found, never throws).
|
|
2666
2754
|
const wsExpr = `(readStrOrNull(data, '${wsField}') ?? '')`;
|
|
2667
2755
|
const build = (idVar: string): string =>
|
|
2668
|
-
`
|
|
2756
|
+
`NmsInternalForeignRefs.create<${target}>(id: ${idVar}, workspace: ${wsExpr}, type: '${s.foreignRefAggregateType ?? target}', fromJson: ${target}.fromJson)`;
|
|
2669
2757
|
return s.optional
|
|
2670
2758
|
? `() { final v = ${idReader}; return v == null ? null : ${build("v")}; }()`
|
|
2671
2759
|
: build(idReader);
|
|
@@ -2680,7 +2768,7 @@ function parseField(s: AggFieldSpec, aggregate: string): string {
|
|
|
2680
2768
|
switch (s.kind) {
|
|
2681
2769
|
case "ref": {
|
|
2682
2770
|
// A `t.foreignRef` field decodes to the shared `ForeignRef<Target>` locator (raw id + sibling
|
|
2683
|
-
// workspace field + the target `fromJson`), not a
|
|
2771
|
+
// workspace field + the target `fromJson`), not a same-workspace NomosRef.
|
|
2684
2772
|
if (s.foreignRefTarget !== undefined) return foreignRefExpr(s, aggregate);
|
|
2685
2773
|
// A ref reads its id STRING, then wraps it via `.fromRow` (NOT the public
|
|
2686
2774
|
// `.fromMinted`, which asserts the type tag). A ref may
|
|
@@ -2692,8 +2780,8 @@ function parseField(s: AggFieldSpec, aggregate: string): string {
|
|
|
2692
2780
|
: `readStr(data, '${aggregate}', '${s.wireName}')`;
|
|
2693
2781
|
if (s.refIdClass === undefined) return reader;
|
|
2694
2782
|
return opt
|
|
2695
|
-
? `() { final v = ${reader}; return v == null ? null :
|
|
2696
|
-
:
|
|
2783
|
+
? `() { final v = ${reader}; return v == null ? null : NmsInternalNomosRefs.create<${s.refIdClass}>(workspace: _location, type: '${s.refAggregateType}', id: v); }()`
|
|
2784
|
+
: `NmsInternalNomosRefs.create<${s.refIdClass}>(workspace: _location, type: '${s.refAggregateType}', id: ${reader})`;
|
|
2697
2785
|
}
|
|
2698
2786
|
// `position` normalizes to `string`, `counter` to `int` in `aggFieldSpec` — unreachable here.
|
|
2699
2787
|
case "position":
|
|
@@ -2703,8 +2791,8 @@ function parseField(s: AggFieldSpec, aggregate: string): string {
|
|
|
2703
2791
|
? `readStrOrNull(data, '${s.wireName}')`
|
|
2704
2792
|
: `readStr(data, '${aggregate}', '${s.wireName}')`;
|
|
2705
2793
|
return opt
|
|
2706
|
-
? `() { final v = ${reader}; return v == null ? null :
|
|
2707
|
-
:
|
|
2794
|
+
? `() { final v = ${reader}; return v == null ? null : NmsInternalNomosRefs.create<${s.valueClass}>(workspace: _location, type: '${s.valueAggregateType}', id: v); }()`
|
|
2795
|
+
: `NmsInternalNomosRefs.create<${s.valueClass}>(workspace: _location, type: '${s.valueAggregateType}', id: ${reader})`;
|
|
2708
2796
|
}
|
|
2709
2797
|
return opt
|
|
2710
2798
|
? `readStrOrNull(data, '${s.wireName}')`
|
|
@@ -2772,15 +2860,15 @@ function slotDecodeCallback(s: AggFieldSpec, mode: "aggregate" | "readModel"): s
|
|
|
2772
2860
|
// read through the normal field path). A plain ref keeps its typed-id decode.
|
|
2773
2861
|
if (s.foreignRefTarget !== undefined) {
|
|
2774
2862
|
const t = s.foreignRefTarget;
|
|
2775
|
-
return `(v) =>
|
|
2863
|
+
return `(v) => NmsInternalForeignRefs.create<${t}>(id: v as String, workspace: '', type: '${t}', fromJson: ${t}.fromJson)`;
|
|
2776
2864
|
}
|
|
2777
2865
|
return s.refIdClass !== undefined
|
|
2778
|
-
? `(v) =>
|
|
2866
|
+
? `(v) => NmsInternalNomosRefs.create<${s.refIdClass}>(workspace: _location, type: '${s.refAggregateType}', id: v as String)`
|
|
2779
2867
|
: `(v) => v as String`;
|
|
2780
2868
|
case "position":
|
|
2781
2869
|
case "string":
|
|
2782
2870
|
return s.valueClass !== undefined
|
|
2783
|
-
? `(v) =>
|
|
2871
|
+
? `(v) => NmsInternalNomosRefs.create<${s.valueClass}>(workspace: _location, type: '${s.valueAggregateType}', id: v as String)`
|
|
2784
2872
|
: `(v) => v as String`;
|
|
2785
2873
|
case "json":
|
|
2786
2874
|
if (s.voClass !== undefined) {
|
|
@@ -2832,9 +2920,9 @@ function readModelDartType(s: AggFieldSpec): string {
|
|
|
2832
2920
|
switch (s.kind) {
|
|
2833
2921
|
case "ref":
|
|
2834
2922
|
// A `t.foreignRef` field surfaces as the typed cross-workspace `ForeignRef<Target>` locator; a plain
|
|
2835
|
-
// same-workspace ref stays
|
|
2923
|
+
// same-workspace ref stays `NomosRef<Target>` (or a bare String when the target is unknown).
|
|
2836
2924
|
if (s.foreignRefTarget !== undefined) return `ForeignRef<${s.foreignRefTarget}>`;
|
|
2837
|
-
return s.refIdClass
|
|
2925
|
+
return s.refIdClass === undefined ? "String" : `NomosRef<${s.refIdClass}>`;
|
|
2838
2926
|
case "json":
|
|
2839
2927
|
// A `t.jsonObject()` leaf (`jsonShape:"object"`, e.g. `location`) is the DECODED
|
|
2840
2928
|
// structured JSON OBJECT (`Map<String, Object?>`, strict). A plain `t.json()` leaf
|
|
@@ -2848,7 +2936,7 @@ function readModelDartType(s: AggFieldSpec): string {
|
|
|
2848
2936
|
// so these labels are unreachable at runtime — present for FieldKind exhaustiveness.
|
|
2849
2937
|
case "position":
|
|
2850
2938
|
case "string":
|
|
2851
|
-
return s.valueClass
|
|
2939
|
+
return s.valueClass === undefined ? "String" : `NomosRef<${s.valueClass}>`;
|
|
2852
2940
|
case "counter":
|
|
2853
2941
|
case "int":
|
|
2854
2942
|
return "int";
|
|
@@ -2897,15 +2985,15 @@ function parseReadModelField(s: AggFieldSpec, aggregate: string): string {
|
|
|
2897
2985
|
switch (s.kind) {
|
|
2898
2986
|
case "ref": {
|
|
2899
2987
|
// A `t.foreignRef` field decodes to the shared `ForeignRef<Target>` locator (same as the aggregate
|
|
2900
|
-
// class); a plain same-workspace ref stays
|
|
2988
|
+
// class); a plain same-workspace ref stays `NomosRef<Target>`.
|
|
2901
2989
|
if (s.foreignRefTarget !== undefined) return foreignRefExpr(s, aggregate);
|
|
2902
2990
|
const reader = opt
|
|
2903
2991
|
? `readStrOrNull(data, '${s.wireName}')`
|
|
2904
2992
|
: `readStr(data, '${aggregate}', '${s.wireName}')`;
|
|
2905
2993
|
if (s.refIdClass === undefined) return reader;
|
|
2906
2994
|
return opt
|
|
2907
|
-
? `() { final v = ${reader}; return v == null ? null :
|
|
2908
|
-
:
|
|
2995
|
+
? `() { final v = ${reader}; return v == null ? null : NmsInternalNomosRefs.create<${s.refIdClass}>(workspace: _location, type: '${s.refAggregateType}', id: v); }()`
|
|
2996
|
+
: `NmsInternalNomosRefs.create<${s.refIdClass}>(workspace: _location, type: '${s.refAggregateType}', id: ${reader})`;
|
|
2909
2997
|
}
|
|
2910
2998
|
case "position":
|
|
2911
2999
|
case "string":
|
|
@@ -2914,8 +3002,8 @@ function parseReadModelField(s: AggFieldSpec, aggregate: string): string {
|
|
|
2914
3002
|
? `readStrOrNull(data, '${s.wireName}')`
|
|
2915
3003
|
: `readStr(data, '${aggregate}', '${s.wireName}')`;
|
|
2916
3004
|
return opt
|
|
2917
|
-
? `() { final v = ${reader}; return v == null ? null :
|
|
2918
|
-
:
|
|
3005
|
+
? `() { final v = ${reader}; return v == null ? null : NmsInternalNomosRefs.create<${s.valueClass}>(workspace: _location, type: '${s.valueAggregateType}', id: v); }()`
|
|
3006
|
+
: `NmsInternalNomosRefs.create<${s.valueClass}>(workspace: _location, type: '${s.valueAggregateType}', id: ${reader})`;
|
|
2919
3007
|
}
|
|
2920
3008
|
return opt
|
|
2921
3009
|
? `readStrOrNull(data, '${s.wireName}')`
|
|
@@ -2970,8 +3058,8 @@ function parseReadModelField(s: AggFieldSpec, aggregate: string): string {
|
|
|
2970
3058
|
}
|
|
2971
3059
|
|
|
2972
3060
|
/** Emit the generated typed read-model class for one aggregate. The class
|
|
2973
|
-
* (`SiteReadModel`, …) carries the
|
|
2974
|
-
* `{type,id,data}` row
|
|
3061
|
+
* (`SiteReadModel`, …) carries the canonical aggregate-typed [ref] (composed from the
|
|
3062
|
+
* `{type,id,data}` row location) plus a DSL-named typed field per DSL
|
|
2975
3063
|
* field declaration, and a [fromProjected] factory that reads the read engine's
|
|
2976
3064
|
* already-decoded projected `data` shape. */
|
|
2977
3065
|
function emitReadModel(
|
|
@@ -2979,17 +3067,18 @@ function emitReadModel(
|
|
|
2979
3067
|
enumDartTypes: Map<string, string>,
|
|
2980
3068
|
deriveds: DerivedDecl[] = [],
|
|
2981
3069
|
combineds: CombinedDecl[] = [],
|
|
2982
|
-
refIdClassForAggregate: (aggregateType: string) => string =
|
|
3070
|
+
refIdClassForAggregate: (aggregateType: string) => string = concreteClassName,
|
|
2983
3071
|
ownIdFields: ReadonlySet<string> = new Set(),
|
|
3072
|
+
aggregateWireTypeForAggregate: (aggregateType: string) => string = (aggregateType) => aggregateType,
|
|
2984
3073
|
): string {
|
|
2985
3074
|
const className = readModelClassName(agg.id);
|
|
2986
|
-
const idType = aggregateIdDartType(agg.id);
|
|
2987
3075
|
const wireType = agg.id;
|
|
2988
3076
|
const specs = aggregateFieldSpecs(
|
|
2989
3077
|
agg,
|
|
2990
3078
|
enumDartTypes,
|
|
2991
3079
|
refIdClassForAggregate,
|
|
2992
3080
|
ownIdFields,
|
|
3081
|
+
aggregateWireTypeForAggregate,
|
|
2993
3082
|
);
|
|
2994
3083
|
|
|
2995
3084
|
// DERIVED/COMBINED read fields are engine-projected JSON leaves. Their value schema is
|
|
@@ -3002,13 +3091,9 @@ function emitReadModel(
|
|
|
3002
3091
|
// optional json-object leaf) is nullable; a PERMISSIVE json leaf is already the
|
|
3003
3092
|
// nullable `Object?` (no extra `?`); a collection keeps its non-null container.
|
|
3004
3093
|
const fieldDecls = [
|
|
3005
|
-
` /// The
|
|
3006
|
-
` ///
|
|
3007
|
-
` final
|
|
3008
|
-
` /// THE ONE REFERENCE (architecture/nomos_ref.md): this row's canonical, workspace-qualified [NomosRef]`,
|
|
3009
|
-
` /// (\`nomos://<workspace>/<type>/<id>\`) — composed at decode from the session workspace + the row's`,
|
|
3010
|
-
` /// type/id. The generated client accessors thread the session workspace; a bare decode leaves it ''.`,
|
|
3011
|
-
` final NomosRef ref;`,
|
|
3094
|
+
` /// The canonical workspace-qualified identity of this ${concreteClassName(agg.id)}.`,
|
|
3095
|
+
` /// Pass this exact value to generated actions, find and watch methods.`,
|
|
3096
|
+
` final NomosRef<${concreteClassName(agg.id)}> ref;`,
|
|
3012
3097
|
...specs.flatMap((s) => {
|
|
3013
3098
|
const doc = s.nullReason !== undefined ? [` /// null semantic: ${s.nullReason}`] : [];
|
|
3014
3099
|
// SURFACED MERGE CONFLICTS: pending observations surface typed; resolve = ordinary next write.
|
|
@@ -3032,8 +3117,7 @@ function emitReadModel(
|
|
|
3032
3117
|
),
|
|
3033
3118
|
].join("\n");
|
|
3034
3119
|
const ctorParams = [
|
|
3035
|
-
` this.
|
|
3036
|
-
` this.ref = const NomosRef(workspace: '', type: '${agg.id}', id: ''),`,
|
|
3120
|
+
` required this.ref,`,
|
|
3037
3121
|
...specs.map((s) =>
|
|
3038
3122
|
// A required field is `required`; an optional scalar, a collection, OR a
|
|
3039
3123
|
// permissive json leaf (already nullable `Object?`) takes its default/null.
|
|
@@ -3074,8 +3158,7 @@ function emitReadModel(
|
|
|
3074
3158
|
];
|
|
3075
3159
|
|
|
3076
3160
|
const parseLines = [
|
|
3077
|
-
`
|
|
3078
|
-
` ref: NomosRef(workspace: workspace, type: type.isEmpty ? '${agg.id}' : type, id: id),`,
|
|
3161
|
+
` ref: NmsInternalNomosRefs.create<${concreteClassName(agg.id)}>(workspace: _location, type: _kind.isEmpty ? '${aggregateWireTypeForAggregate(agg.id)}' : _kind, id: id),`,
|
|
3079
3162
|
...specs.map((s) =>
|
|
3080
3163
|
s.surfaced === true
|
|
3081
3164
|
? ` ${s.name}: SurfacedValue.decode<${readModelDartType(s)}>(data, '${s.wireName}', ${slotDecodeCallback(s, "readModel")}),`
|
|
@@ -3111,10 +3194,7 @@ function emitReadModel(
|
|
|
3111
3194
|
` /// already decoded to bare/structured JSON), carrying the kernel aggregate [id] from`,
|
|
3112
3195
|
` /// the row \`id\` (defaults to '' for a bare-map call). PARTIAL folds: every scalar`,
|
|
3113
3196
|
` /// decodes \`null\` when absent (never throws); absent collections decode empty.`,
|
|
3114
|
-
`
|
|
3115
|
-
` /// [workspace] + [type] compose the canonical [ref] (both optional — the client accessors thread the`,
|
|
3116
|
-
` /// session workspace + the row type; a bare decode leaves workspace '' and falls back to the type '${agg.id}').`,
|
|
3117
|
-
` factory ${className}.fromProjected(Map<String, dynamic> data, [String id = '', String workspace = '', String type = '']) {`,
|
|
3197
|
+
` factory ${className}.fromProjected(Map<String, dynamic> data, [String id = '', String _location = '', String _kind = '']) {`,
|
|
3118
3198
|
` return ${className}(`,
|
|
3119
3199
|
parseLines,
|
|
3120
3200
|
` );`,
|
|
@@ -3124,39 +3204,63 @@ function emitReadModel(
|
|
|
3124
3204
|
].join("\n");
|
|
3125
3205
|
}
|
|
3126
3206
|
|
|
3127
|
-
// ----
|
|
3207
|
+
// ---- typed action input class ----------------------------------------------
|
|
3128
3208
|
|
|
3129
3209
|
function emitDirective(
|
|
3130
3210
|
d: AnyDirective,
|
|
3131
3211
|
domainName: string,
|
|
3132
3212
|
enumDartTypes: Map<string, string>,
|
|
3133
3213
|
lazyNames?: Map<z.ZodTypeAny, string>,
|
|
3214
|
+
aggregates: readonly AggregateHandle[] = [],
|
|
3134
3215
|
): string {
|
|
3135
|
-
const className =
|
|
3216
|
+
const className = dartActionInputClassName(d);
|
|
3136
3217
|
const specs = introspectPayload(d.payloadSchema as z.ZodTypeAny, enumDartTypes, lazyNames);
|
|
3218
|
+
const recordInputs = dartBusinessRecordInputs(d, aggregates);
|
|
3219
|
+
const principalField = d.businessPrincipalField;
|
|
3220
|
+
const derivedFields = new Set([
|
|
3221
|
+
...recordInputs.flatMap((input) => [input.idField, input.workspaceField]),
|
|
3222
|
+
...(principalField === undefined ? [] : [principalField]),
|
|
3223
|
+
]);
|
|
3224
|
+
const publicSpecs = specs.filter((spec) => !derivedFields.has(spec.name));
|
|
3225
|
+
const internalPayloadArguments = [
|
|
3226
|
+
...(principalField === undefined ? [] : ["required String nmsInternalPrincipal"]),
|
|
3227
|
+
...(recordInputs.length === 0
|
|
3228
|
+
? []
|
|
3229
|
+
: [
|
|
3230
|
+
"required String Function(Object record) nmsInternalLocationOf",
|
|
3231
|
+
"required String Function(Object record) nmsInternalIdOf",
|
|
3232
|
+
]),
|
|
3233
|
+
];
|
|
3234
|
+
const internalPayloadSignature = internalPayloadArguments.length === 0
|
|
3235
|
+
? ""
|
|
3236
|
+
: `{${internalPayloadArguments.join(", ")}}`;
|
|
3137
3237
|
|
|
3138
3238
|
// Field decls: optional payload fields are nullable Dart fields (omitted from the
|
|
3139
3239
|
// payload JSON when null); required fields are non-null + `required` in the ctor.
|
|
3140
|
-
const fieldDecls =
|
|
3240
|
+
const fieldDecls = [
|
|
3241
|
+
...publicSpecs
|
|
3141
3242
|
.map((s) =>
|
|
3142
3243
|
s.nullAdmits === true
|
|
3143
3244
|
? ` final NomosSlot<${stripNullable(s.dartType)}> ${s.name};`
|
|
3144
3245
|
: ` final ${s.dartType}${s.optional ? "?" : ""} ${s.name};`,
|
|
3145
|
-
)
|
|
3146
|
-
.
|
|
3147
|
-
|
|
3246
|
+
),
|
|
3247
|
+
...recordInputs.map((input) => ` final ${input.dartType} ${input.argument};`),
|
|
3248
|
+
].join("\n");
|
|
3249
|
+
const ctorParams = [
|
|
3250
|
+
...publicSpecs
|
|
3148
3251
|
.map((s) =>
|
|
3149
3252
|
// A null-admitting slot defaults to Omitted (the call site chooses Present/ExplicitNull);
|
|
3150
3253
|
// an optional field defaults to null; a required field is `required`.
|
|
3151
3254
|
s.nullAdmits === true
|
|
3152
3255
|
? ` this.${s.name} = const NomosSlot.omitted(),`
|
|
3153
3256
|
: ` ${s.optional ? "" : "required "}this.${s.name},`,
|
|
3154
|
-
)
|
|
3155
|
-
.
|
|
3257
|
+
),
|
|
3258
|
+
...recordInputs.map((input) => ` required this.${input.argument},`),
|
|
3259
|
+
].join("\n");
|
|
3156
3260
|
// Zero-field payload (e.g. a `.births()` sanctioning directive with an empty
|
|
3157
3261
|
// object schema): `const X({});` / a dangling blank field block is INVALID Dart —
|
|
3158
3262
|
// emit a plain `const X();` and an empty map literal instead.
|
|
3159
|
-
const hasFields =
|
|
3263
|
+
const hasFields = publicSpecs.length > 0 || recordInputs.length > 0;
|
|
3160
3264
|
const ctorLines = hasFields
|
|
3161
3265
|
? [` const ${className}({`, ctorParams, ` });`]
|
|
3162
3266
|
: [` const ${className}();`];
|
|
@@ -3166,17 +3270,17 @@ function emitDirective(
|
|
|
3166
3270
|
// fields are written only when present (mirrors the engine's Zod: ops are emitted
|
|
3167
3271
|
// only for present fields).
|
|
3168
3272
|
const requiredEntries = specs
|
|
3169
|
-
.filter((s) => !s.optional && s.nullAdmits !== true)
|
|
3273
|
+
.filter((s) => !derivedFields.has(s.name) && !s.optional && s.nullAdmits !== true)
|
|
3170
3274
|
.map((s) => ` '${s.name}': ${toJsonExpr(s, s.name)},`);
|
|
3171
3275
|
const optionalLines = specs
|
|
3172
|
-
.filter((s) => s.optional && s.nullAdmits !== true)
|
|
3276
|
+
.filter((s) => !derivedFields.has(s.name) && s.optional && s.nullAdmits !== true)
|
|
3173
3277
|
.map(
|
|
3174
3278
|
(s) =>
|
|
3175
3279
|
` if (${s.name} != null) '${s.name}': ${toJsonExpr(s, `${s.name}!`)},`,
|
|
3176
3280
|
);
|
|
3177
3281
|
// NULL SEMANTICS: a null-admitting field writes through its slot — Omitted writes nothing,
|
|
3178
3282
|
// ExplicitNull writes a real `null`, Present writes the encoded value (null_semantics.md).
|
|
3179
|
-
const slotSpecs = specs.filter((s) => s.nullAdmits === true);
|
|
3283
|
+
const slotSpecs = specs.filter((s) => !derivedFields.has(s.name) && s.nullAdmits === true);
|
|
3180
3284
|
const slotWriteLines = slotSpecs.map(
|
|
3181
3285
|
(s) => ` ${s.name}.writeTo(data, '${s.name}', (v) => ${toJsonExpr(s, "v")});`,
|
|
3182
3286
|
);
|
|
@@ -3186,51 +3290,43 @@ function emitDirective(
|
|
|
3186
3290
|
|
|
3187
3291
|
return [
|
|
3188
3292
|
`/// ${docFirst}`,
|
|
3293
|
+
`/// Business facts accepted by this generated application action.`,
|
|
3189
3294
|
`///`,
|
|
3190
|
-
`///
|
|
3191
|
-
`///
|
|
3192
|
-
|
|
3193
|
-
`/// \`Event\` and no \`Hlc\`. Pass it to [offer]; the GitHolon executes`,
|
|
3194
|
-
`/// the installed domain plan.`,
|
|
3195
|
-
`class ${className} implements NomosIntent {`,
|
|
3196
|
-
` /// The policy domain this intent belongs to (the kernel \`author\` \`domain\`).`,
|
|
3197
|
-
` static const String domainName = '${domainName}';`,
|
|
3198
|
-
` /// The public intent type within [domainName] — never hand-typed at the call site.`,
|
|
3199
|
-
` static const String intentTypeName = '${d.id}';`,
|
|
3200
|
-
` /// The aggregate this intent targets (provenance only; the engine binds the instance).`,
|
|
3201
|
-
` static const String aggregateId = '${d.aggregateId}';`,
|
|
3202
|
-
` /// The referential marker (creates/mutates/ensures/archives).`,
|
|
3203
|
-
` static const String marker = '${d.marker}';`,
|
|
3295
|
+
`/// Pass this value straight to the generated domain application. It is a command,`,
|
|
3296
|
+
`/// not application state; do not retain it in a repository or submit it through runtime APIs.`,
|
|
3297
|
+
`class ${className} {`,
|
|
3204
3298
|
``,
|
|
3205
3299
|
...(hasFields ? [fieldDecls, ``] : []),
|
|
3206
3300
|
` /// ${docFirst}`,
|
|
3207
3301
|
...ctorLines,
|
|
3208
3302
|
``,
|
|
3209
|
-
`
|
|
3210
|
-
`
|
|
3211
|
-
``,
|
|
3212
|
-
` @override`,
|
|
3213
|
-
` String get intentType => intentTypeName;`,
|
|
3214
|
-
``,
|
|
3215
|
-
` /// Serialise this payload to the intent DATA JSON (the shape the`,
|
|
3216
|
-
` /// domain Zod schema validates inside the engine). Optional fields are`,
|
|
3217
|
-
` /// omitted when null; a null-admitting field writes through its tri-state slot.`,
|
|
3218
|
-
` @override`,
|
|
3303
|
+
` /// Nomos implementation seam used only by the generated application package.`,
|
|
3304
|
+
` /// Tenant code should pass this object to the generated domain application instead.`,
|
|
3219
3305
|
...(hasSlots
|
|
3220
3306
|
? [
|
|
3221
|
-
` Map<String, Object?>
|
|
3307
|
+
` Map<String, Object?> nmsInternalToPayloadJson(${internalPayloadSignature}) {`,
|
|
3222
3308
|
` final data = <String, Object?>{`,
|
|
3223
3309
|
...requiredEntries,
|
|
3224
3310
|
...optionalLines,
|
|
3311
|
+
...(principalField === undefined ? [] : [` '${principalField}': nmsInternalPrincipal,`]),
|
|
3312
|
+
...recordInputs.flatMap((input) => [
|
|
3313
|
+
` '${input.workspaceField}': nmsInternalLocationOf(${input.argument}),`,
|
|
3314
|
+
` '${input.idField}': nmsInternalIdOf(${input.argument}),`,
|
|
3315
|
+
]),
|
|
3225
3316
|
` };`,
|
|
3226
3317
|
...slotWriteLines,
|
|
3227
3318
|
` return data;`,
|
|
3228
3319
|
` }`,
|
|
3229
3320
|
]
|
|
3230
3321
|
: [
|
|
3231
|
-
` Map<String, Object?>
|
|
3322
|
+
` Map<String, Object?> nmsInternalToPayloadJson(${internalPayloadSignature}) => <String, Object?>{`,
|
|
3232
3323
|
...requiredEntries,
|
|
3233
3324
|
...optionalLines,
|
|
3325
|
+
...(principalField === undefined ? [] : [` '${principalField}': nmsInternalPrincipal,`]),
|
|
3326
|
+
...recordInputs.flatMap((input) => [
|
|
3327
|
+
` '${input.workspaceField}': nmsInternalLocationOf(${input.argument}),`,
|
|
3328
|
+
` '${input.idField}': nmsInternalIdOf(${input.argument}),`,
|
|
3329
|
+
]),
|
|
3234
3330
|
` };`,
|
|
3235
3331
|
]),
|
|
3236
3332
|
`}`,
|
|
@@ -3252,6 +3348,10 @@ export function toJsonExpr(spec: PayloadFieldSpec, accessor: string): string {
|
|
|
3252
3348
|
return `${accessor}.map((e) => e.toJson()).toList()`;
|
|
3253
3349
|
case "wrapped":
|
|
3254
3350
|
return `${accessor}.value`;
|
|
3351
|
+
case "ref":
|
|
3352
|
+
return `NmsInternalNomosRefs.id(${accessor})`;
|
|
3353
|
+
case "recipient":
|
|
3354
|
+
return `NmsInternalRecipients.address(${accessor})`;
|
|
3255
3355
|
}
|
|
3256
3356
|
}
|
|
3257
3357
|
|
|
@@ -3316,7 +3416,7 @@ export function synthesizeRecursivePayloadTypes(
|
|
|
3316
3416
|
const b = brandedIdMetaOf(zt);
|
|
3317
3417
|
if (b !== undefined) return { type: "string", brand: b };
|
|
3318
3418
|
const idAgg = idOfMetaOf(zt);
|
|
3319
|
-
if (idAgg !== undefined) return { type: "string",
|
|
3419
|
+
if (idAgg !== undefined) return { type: "string", idOf: idAgg };
|
|
3320
3420
|
return { type: "string" };
|
|
3321
3421
|
}
|
|
3322
3422
|
case "ZodNumber": {
|
|
@@ -3450,7 +3550,7 @@ function emitQueryReads(
|
|
|
3450
3550
|
q: QueryDecl,
|
|
3451
3551
|
aggregatesById: Map<string, AggregateHandle>,
|
|
3452
3552
|
enumDartTypes: Map<string, string>,
|
|
3453
|
-
refIdClassForAggregate: (aggregateType: string) => string =
|
|
3553
|
+
refIdClassForAggregate: (aggregateType: string) => string = concreteClassName,
|
|
3454
3554
|
ownIdFieldsByAggregate: ReadonlyMap<string, ReadonlySet<string>> = new Map(),
|
|
3455
3555
|
): string {
|
|
3456
3556
|
const agg = q.returns; // the aggregate TYPE id (== the wire id, used for field/index lookups).
|
|
@@ -3507,12 +3607,12 @@ function emitAggregateByIdReads(agg: AggregateHandle): string {
|
|
|
3507
3607
|
`/// (\`null\` until/unless it has been folded). Emits the current value, then`,
|
|
3508
3608
|
`/// re-emits on each projection tick. Primary-key lookup (#140) — O(1).`,
|
|
3509
3609
|
`Stream<${cls}?> watch${cls}ById(NomosReads reads, ${idType} id) =>`,
|
|
3510
|
-
` reads.watchById(id
|
|
3610
|
+
` reads.watchById(NmsInternalNomosRefs.id(id), ${cls}.fromJson);`,
|
|
3511
3611
|
``,
|
|
3512
3612
|
`/// One-shot typed by-id read: the CURRENT \`${cls}\` with kernel aggregate id`,
|
|
3513
3613
|
`/// [id], or \`null\` when no such aggregate has been folded. O(1).`,
|
|
3514
3614
|
`${cls}? read${cls}ById(NomosReads reads, ${idType} id) =>`,
|
|
3515
|
-
` reads.readById(id
|
|
3615
|
+
` reads.readById(NmsInternalNomosRefs.id(id), ${cls}.fromJson);`,
|
|
3516
3616
|
].join("\n");
|
|
3517
3617
|
}
|
|
3518
3618
|
|
|
@@ -3541,7 +3641,7 @@ function emitCountReads(
|
|
|
3541
3641
|
c: ReturnType<typeof finishCount>,
|
|
3542
3642
|
aggregatesById: Map<string, AggregateHandle>,
|
|
3543
3643
|
enumDartTypes: Map<string, string>,
|
|
3544
|
-
refIdClassForAggregate: (aggregateType: string) => string =
|
|
3644
|
+
refIdClassForAggregate: (aggregateType: string) => string = concreteClassName,
|
|
3545
3645
|
ownIdFieldsByAggregate: ReadonlyMap<string, ReadonlySet<string>> = new Map(),
|
|
3546
3646
|
): string {
|
|
3547
3647
|
const method = readMethodName(c.id);
|
|
@@ -3593,7 +3693,7 @@ function emitSumReads(
|
|
|
3593
3693
|
s: ReturnType<typeof finishSum>,
|
|
3594
3694
|
aggregatesById: Map<string, AggregateHandle>,
|
|
3595
3695
|
enumDartTypes: Map<string, string>,
|
|
3596
|
-
refIdClassForAggregate: (aggregateType: string) => string =
|
|
3696
|
+
refIdClassForAggregate: (aggregateType: string) => string = concreteClassName,
|
|
3597
3697
|
ownIdFieldsByAggregate: ReadonlyMap<string, ReadonlySet<string>> = new Map(),
|
|
3598
3698
|
): string {
|
|
3599
3699
|
const method = readMethodName(s.id);
|
|
@@ -3647,7 +3747,7 @@ function emitMinReads(
|
|
|
3647
3747
|
e: ReturnType<typeof finishExtremum>,
|
|
3648
3748
|
aggregatesById: Map<string, AggregateHandle>,
|
|
3649
3749
|
enumDartTypes: Map<string, string>,
|
|
3650
|
-
refIdClassForAggregate: (aggregateType: string) => string =
|
|
3750
|
+
refIdClassForAggregate: (aggregateType: string) => string = concreteClassName,
|
|
3651
3751
|
ownIdFieldsByAggregate: ReadonlyMap<string, ReadonlySet<string>> = new Map(),
|
|
3652
3752
|
): string {
|
|
3653
3753
|
const method = readMethodName(e.id);
|
|
@@ -3700,7 +3800,7 @@ function emitMaxReads(
|
|
|
3700
3800
|
e: ReturnType<typeof finishExtremum>,
|
|
3701
3801
|
aggregatesById: Map<string, AggregateHandle>,
|
|
3702
3802
|
enumDartTypes: Map<string, string>,
|
|
3703
|
-
refIdClassForAggregate: (aggregateType: string) => string =
|
|
3803
|
+
refIdClassForAggregate: (aggregateType: string) => string = concreteClassName,
|
|
3704
3804
|
ownIdFieldsByAggregate: ReadonlyMap<string, ReadonlySet<string>> = new Map(),
|
|
3705
3805
|
): string {
|
|
3706
3806
|
const method = readMethodName(e.id);
|
|
@@ -3755,7 +3855,7 @@ function emitExistsReads(
|
|
|
3755
3855
|
e: ReturnType<typeof finishExists>,
|
|
3756
3856
|
aggregatesById: Map<string, AggregateHandle>,
|
|
3757
3857
|
enumDartTypes: Map<string, string>,
|
|
3758
|
-
refIdClassForAggregate: (aggregateType: string) => string =
|
|
3858
|
+
refIdClassForAggregate: (aggregateType: string) => string = concreteClassName,
|
|
3759
3859
|
ownIdFieldsByAggregate: ReadonlyMap<string, ReadonlySet<string>> = new Map(),
|
|
3760
3860
|
): string {
|
|
3761
3861
|
const method = readMethodName(e.id);
|
|
@@ -3837,23 +3937,10 @@ function emitOrderedReads(d: OrderedReadDecl): string {
|
|
|
3837
3937
|
].join("\n");
|
|
3838
3938
|
}
|
|
3839
3939
|
|
|
3840
|
-
// ----
|
|
3841
|
-
//
|
|
3842
|
-
//
|
|
3843
|
-
//
|
|
3844
|
-
// such a directive we emit, ALONGSIDE the plain payload DTO:
|
|
3845
|
-
// * a typed id value class (e.g. `SiteId`) wrapping the kernel-minted string, so a
|
|
3846
|
-
// raw `String` is never accepted where a minted id is required, and
|
|
3847
|
-
// * a top-level `create…({Minter mint, …})` factory that calls the KERNEL mint (no
|
|
3848
|
-
// raw id param, NO seed) to RESERVE the typed id and builds the payload from it — so
|
|
3849
|
-
// the only way to construct a minted create at the call site is THROUGH the kernel
|
|
3850
|
-
// mint, and the dev never sees a uuid/seed/nonce.
|
|
3851
|
-
//
|
|
3852
|
-
// THE MODEL: the KERNEL mints `<Type>_<uuidv7>` from the HOST-INJECTED rng; the id is
|
|
3853
|
-
// CAPTURED in the create event and READ on replay (never re-minted). The UUID body is
|
|
3854
|
-
// opaque uniqueness only — NOT time/order; intent HLCs carry ordering. There is NO
|
|
3855
|
-
// `mintSeed` payload field any more — the id itself is the proof. The factory passes
|
|
3856
|
-
// NOTHING to the mint but the aggregate type.
|
|
3940
|
+
// ---- legacy payload-bound own-id discovery ---------------------------------
|
|
3941
|
+
// Some old laws copied a payload string into their created aggregate's own id field. We still trace
|
|
3942
|
+
// that shape so decoded legacy fields become `NomosRef<TAggregate>`. No FooId class or frontend mint
|
|
3943
|
+
// factory is emitted; current creates are law-minted and return their canonical reference.
|
|
3857
3944
|
|
|
3858
3945
|
/**
|
|
3859
3946
|
* The target-id field of a minted create: the payload field bound to the aggregate the
|
|
@@ -3868,11 +3955,11 @@ function emitOrderedReads(d: OrderedReadDecl): string {
|
|
|
3868
3955
|
* Mechanism: probe each top-level STRING field with a unique sentinel, run `.plan()`, find
|
|
3869
3956
|
* the `Create` event whose `__type` == the directive's `.creates` aggregate, and map its
|
|
3870
3957
|
* `__id` sentinel back to the payload field. Returns `undefined` when the created aggregate's
|
|
3871
|
-
* id is not a verbatim top-level payload string
|
|
3958
|
+
* id is not a verbatim top-level payload string.
|
|
3872
3959
|
*
|
|
3873
3960
|
* `agg` is the `.creates` target handle (`directive.aggregateId`), required by `executeDirectiveToIntent`.
|
|
3874
|
-
* If the plan cannot be traced,
|
|
3875
|
-
*
|
|
3961
|
+
* If the plan cannot be traced, do not classify an own-id field. Guessing by field name would make
|
|
3962
|
+
* the decoded Dart surface lie about aggregate identity.
|
|
3876
3963
|
*/
|
|
3877
3964
|
function mintedIdField(
|
|
3878
3965
|
d: AnyDirective,
|
|
@@ -3987,161 +4074,6 @@ function sampleFromZod(zt: z.ZodTypeAny): unknown {
|
|
|
3987
4074
|
}
|
|
3988
4075
|
}
|
|
3989
4076
|
|
|
3990
|
-
/** The Dart typed-id class name for a minted create's id field. Unified on the
|
|
3991
|
-
* aggregate WIRE TYPE (so `createSite`'s `siteId` → `SiteId`, the same name a ref
|
|
3992
|
-
* targeting `SiteRootAggregate` uses), NOT on the payload field name.
|
|
3993
|
-
* The minting factory uses `FooId._(mintedId)` (the truly private ctor, same file). */
|
|
3994
|
-
function typedIdClassName(aggregateType: string): string {
|
|
3995
|
-
return typedIdClassForAggregateType(aggregateType);
|
|
3996
|
-
}
|
|
3997
|
-
|
|
3998
|
-
/** Emit a typed id value class for an aggregate TYPE. Type-safe: a raw `String` — or
|
|
3999
|
-
* another aggregate's typed id — is a COMPILE ERROR where a [${cls}] is required (the
|
|
4000
|
-
* value classes are distinct nominal types). Public constructors:
|
|
4001
|
-
* * [${cls}] / [${cls}.fromString] — non-strict wrappers for app route params,
|
|
4002
|
-
* existing refs, and test fixtures.
|
|
4003
|
-
* * [${cls}.fromMinted] — STRICT: asserts the kernel type tag matches.
|
|
4004
|
-
* One row-id constructor:
|
|
4005
|
-
* * [${cls}.fromRow] — wraps a kernel-stored row/ref id when deserializing a
|
|
4006
|
-
* projection row. This intentionally does NOT enforce the minted type tag because
|
|
4007
|
-
* reads may include not-yet-minted ids; write admission remains the mint gate.
|
|
4008
|
-
*/
|
|
4009
|
-
function emitTypedIdClass(aggregateType: string): string {
|
|
4010
|
-
const cls = typedIdClassName(aggregateType);
|
|
4011
|
-
return [
|
|
4012
|
-
`/// Typed id for \`${aggregateType}\`. A DISTINCT nominal type so a raw String — or`,
|
|
4013
|
-
`/// ANOTHER aggregate's typed id — is a COMPILE ERROR where a [${cls}] is required.`,
|
|
4014
|
-
`/// [fromMinted] is the strict constructor for ids reserved by the kernel mint.`,
|
|
4015
|
-
`/// [${cls}] / [fromString] / [fromRow] are non-strict wrappers for route params,`,
|
|
4016
|
-
`/// existing refs, and generated projection decoding.`,
|
|
4017
|
-
`class ${cls} {`,
|
|
4018
|
-
` /// The kernel-minted id string (\`${aggregateType}_<uuidv7>\`).`,
|
|
4019
|
-
` /// The UUID body is opaque uniqueness only — NOT time/order; intent HLCs carry ordering.`,
|
|
4020
|
-
` final String value;`,
|
|
4021
|
-
` const ${cls}(this.value);`,
|
|
4022
|
-
` const ${cls}._(this.value);`,
|
|
4023
|
-
` factory ${cls}.fromString(String value) => ${cls}.fromRow(value);`,
|
|
4024
|
-
` /// Wrap a kernel-stored row/ref id while decoding a projection row. This is`,
|
|
4025
|
-
` /// intentionally non-strict: old ledgers and cross-domain refs may not carry the`,
|
|
4026
|
-
` /// minted type tag, so admission/minting is enforced on writes, not reads.`,
|
|
4027
|
-
` const ${cls}.fromRow(this.value);`,
|
|
4028
|
-
` /// Wrap an ALREADY-kernel-minted id string (e.g. one read back from a query).`,
|
|
4029
|
-
` /// STRICT: asserts the type tag matches \`${aggregateType}\` — a wrong-typed id throws.`,
|
|
4030
|
-
` /// A minted id is \`<TypeTag>_<uuidv7>\`, so the tag is the prefix before the FIRST \`_\`.`,
|
|
4031
|
-
` /// Do not read time or ordering from the UUID body; it is an opaque uniqueness token.`,
|
|
4032
|
-
` factory ${cls}.fromMinted(String minted) {`,
|
|
4033
|
-
` final i = minted.indexOf('_');`,
|
|
4034
|
-
` final tag = i < 0 ? minted : minted.substring(0, i);`,
|
|
4035
|
-
` if (tag != '${aggregateType}') {`,
|
|
4036
|
-
` throw ArgumentError('id "$minted" is not a ${aggregateType} id (tag "$tag")');`,
|
|
4037
|
-
` }`,
|
|
4038
|
-
` return ${cls}._(minted);`,
|
|
4039
|
-
` }`,
|
|
4040
|
-
` @override`,
|
|
4041
|
-
` bool operator ==(Object other) => other is ${cls} && other.value == value;`,
|
|
4042
|
-
` @override`,
|
|
4043
|
-
` int get hashCode => value.hashCode;`,
|
|
4044
|
-
` @override`,
|
|
4045
|
-
` String toString() => '${cls}($value)';`,
|
|
4046
|
-
`}`,
|
|
4047
|
-
].join("\n");
|
|
4048
|
-
}
|
|
4049
|
-
|
|
4050
|
-
/** Emit the top-level minted `create…(...)` factory function: it calls the KERNEL mint
|
|
4051
|
-
* (NO raw id param, NO seed) to RESERVE a typed id and builds the payload from it,
|
|
4052
|
-
* returning `(payload, typedId)`. This is the ONLY way to construct a minted create at the
|
|
4053
|
-
* call site — the dev supplies NOTHING about the id. The factory itself contains NO
|
|
4054
|
-
* id/uuid/seed construction — only the runtime kernel-mint call via [mint]. */
|
|
4055
|
-
function emitMintedFactory(
|
|
4056
|
-
d: AnyDirective,
|
|
4057
|
-
_domainName: string,
|
|
4058
|
-
idField: PayloadFieldSpec,
|
|
4059
|
-
specs: PayloadFieldSpec[],
|
|
4060
|
-
aggregateType: string,
|
|
4061
|
-
): string {
|
|
4062
|
-
const className = `${cap(d.id)}Payload`;
|
|
4063
|
-
const idCls = typedIdClassName(aggregateType);
|
|
4064
|
-
const fnName = d.id; // e.g. `createSite` — the typed minting factory.
|
|
4065
|
-
// The caller supplies every field EXCEPT the minted id (the kernel mint produces it).
|
|
4066
|
-
// Optional fields become optional named params. There is NO seed/author param: the
|
|
4067
|
-
// kernel mints the UUIDv7 from the host-injected rng — the dev touches none of it.
|
|
4068
|
-
const callerFields = specs.filter((s) => s.name !== idField.name);
|
|
4069
|
-
const ctorParams = callerFields
|
|
4070
|
-
.map((s) => ` ${s.optional ? "" : "required "}${s.dartType}${s.optional ? "?" : ""} ${s.name},`)
|
|
4071
|
-
.join("\n");
|
|
4072
|
-
const forwarded = callerFields.map((s) => ` ${s.name}: ${s.name},`).join("\n");
|
|
4073
|
-
return [
|
|
4074
|
-
`/// KERNEL-MINTED \`${d.id}\` factory: calls [mint] (the kernel id-mint) to RESERVE a`,
|
|
4075
|
-
`/// fresh typed [${idCls}] — NO raw id param, NO seed — and ships the minted id in the`,
|
|
4076
|
-
`/// payload, so the kernel GATE verifies its shape + type tag on author. The kernel`,
|
|
4077
|
-
`/// mints the \`${aggregateType}_<uuidv7>\` from the HOST-INJECTED rng (native OsRng / web`,
|
|
4078
|
-
`/// crypto). The UUID body is opaque uniqueness only — NOT time/order; intent HLCs carry`,
|
|
4079
|
-
`/// ordering. This factory builds NO id/uuid/seed itself — only the runtime mint call.`,
|
|
4080
|
-
`/// Returns the [${className}] AND the minted [${idCls}] (so the caller can reference the`,
|
|
4081
|
-
`/// new aggregate). This is the type-safe create path: the frontend cannot supply a raw id.`,
|
|
4082
|
-
`(${className}, ${idCls}) ${fnName}({`,
|
|
4083
|
-
` required Minter mint,`,
|
|
4084
|
-
ctorParams,
|
|
4085
|
-
`}) {`,
|
|
4086
|
-
` final mintedId = mint(aggregateType: '${aggregateType}');`,
|
|
4087
|
-
` final payload = ${className}(`,
|
|
4088
|
-
` ${idField.name}: mintedId,`,
|
|
4089
|
-
forwarded,
|
|
4090
|
-
` );`,
|
|
4091
|
-
` return (payload, ${idCls}._(mintedId));`,
|
|
4092
|
-
`}`,
|
|
4093
|
-
].join("\n");
|
|
4094
|
-
}
|
|
4095
|
-
|
|
4096
|
-
/** Emit the typed-id keystone for a domain (GENERALIZED across ALL aggregates):
|
|
4097
|
-
* 1. a DISTINCT typed id value class PER AGGREGATE (so cross-aggregate type
|
|
4098
|
-
* confusion is a compile error + ref fields have a target type to name); and
|
|
4099
|
-
* 2. a kernel-minting `create…` factory for every `.creates` directive whose real
|
|
4100
|
-
* plan trace proves a payload field is the created aggregate id.
|
|
4101
|
-
* Every aggregate gets a typed id even if it has no minting factory (a singleton /
|
|
4102
|
-
* kernel-minted create), because OTHER aggregates' ref fields may target it.
|
|
4103
|
-
* Returns `""` when the domain has no aggregates (nothing to emit). */
|
|
4104
|
-
function emitMintedCreates(
|
|
4105
|
-
aggregates: AggregateHandle[],
|
|
4106
|
-
directives: AnyDirective[],
|
|
4107
|
-
domainName: string,
|
|
4108
|
-
enumDartTypes: Map<string, string>,
|
|
4109
|
-
/** Aggregate ids whose typed-id CLASS is emitted in the shared `nomos_framework.dart` (imported
|
|
4110
|
-
* here), so it must NOT be re-declared. Minting FACTORIES still emit and reference the imported
|
|
4111
|
-
* class. Empty = emit every id class locally (the single-domain / shared-file path). */
|
|
4112
|
-
skipIdClassFor: ReadonlySet<string> = new Set(),
|
|
4113
|
-
): string {
|
|
4114
|
-
const blocks: string[] = [];
|
|
4115
|
-
|
|
4116
|
-
// (1) A typed id class per aggregate. Dedup by the typed-id class NAME: two wire
|
|
4117
|
-
// types never collide on a name (the name is a pure function of the type), and a
|
|
4118
|
-
// domain that re-exports a framework aggregate must not emit its id class twice.
|
|
4119
|
-
// Framework-shared aggregates are emitted ONCE in the package's `nomos_framework.dart`,
|
|
4120
|
-
// so their id class is SKIPPED here (referenced via import).
|
|
4121
|
-
const emittedIdClasses = new Set<string>();
|
|
4122
|
-
for (const agg of aggregates) {
|
|
4123
|
-
if (skipIdClassFor.has(agg.id)) continue;
|
|
4124
|
-
const cls = typedIdClassForAggregateType(agg.id);
|
|
4125
|
-
if (emittedIdClasses.has(cls)) continue;
|
|
4126
|
-
emittedIdClasses.add(cls);
|
|
4127
|
-
blocks.push(emitTypedIdClass(agg.id));
|
|
4128
|
-
}
|
|
4129
|
-
|
|
4130
|
-
// (2) A minting factory per qualifying create — the kernel mints the `.creates` target's
|
|
4131
|
-
// id, identified MARKER-DRIVEN by running the real `.plan()` (see `mintedIdField`).
|
|
4132
|
-
const aggById = new Map<string, AggregateHandle>();
|
|
4133
|
-
for (const a of aggregates) aggById.set(a.id, a);
|
|
4134
|
-
for (const d of directives) {
|
|
4135
|
-
if (d.marker !== "creates") continue;
|
|
4136
|
-
const specs = introspectPayload(d.payloadSchema as z.ZodTypeAny, enumDartTypes);
|
|
4137
|
-
const idField = mintedIdField(d, aggById.get(d.aggregateId), specs);
|
|
4138
|
-
if (idField === undefined) continue;
|
|
4139
|
-
const aggregateType = d.aggregateId;
|
|
4140
|
-
blocks.push(emitMintedFactory(d, domainName, idField, specs, aggregateType));
|
|
4141
|
-
}
|
|
4142
|
-
return blocks.join("\n\n");
|
|
4143
|
-
}
|
|
4144
|
-
|
|
4145
4077
|
function collectOwnIdFieldsByAggregate(
|
|
4146
4078
|
aggregates: AggregateHandle[],
|
|
4147
4079
|
directives: AnyDirective[],
|
|
@@ -4180,6 +4112,18 @@ export interface DomainModule {
|
|
|
4180
4112
|
domain?: string;
|
|
4181
4113
|
aggregates: AggregateHandle[];
|
|
4182
4114
|
directives: AnyDirective[];
|
|
4115
|
+
/**
|
|
4116
|
+
* The domain's complete public business language. When present, generated
|
|
4117
|
+
* application clients publish only these intent methods; their private
|
|
4118
|
+
* constituent directives have already been inlined into the executable plans.
|
|
4119
|
+
* Application metadata only: executable law remains in {@link directives}.
|
|
4120
|
+
*/
|
|
4121
|
+
applicationIntents?: AnyDirective[];
|
|
4122
|
+
/**
|
|
4123
|
+
* Durable cross-workspace business sharing policies. Code-generation metadata
|
|
4124
|
+
* only: the order and receipt directives remain ordinary certified law.
|
|
4125
|
+
*/
|
|
4126
|
+
applicationWorkspaceSharing?: import("./workspace_sharing.js").WorkspaceSharingPolicy[];
|
|
4183
4127
|
/**
|
|
4184
4128
|
* SECONDARY-CREATE AUTO-DECLARE (secondary_ensure.ts) — the aggregate TYPES whose deterministic
|
|
4185
4129
|
* secondary `ensure(...)` `composeDomainModule` plan-probed and SANCTIONED by injecting a no-op
|
|
@@ -4346,7 +4290,7 @@ export interface DomainModule {
|
|
|
4346
4290
|
valueObjects?: readonly ValueObjectDescriptor[];
|
|
4347
4291
|
/**
|
|
4348
4292
|
* DOMAIN CONTRACTS (`architecture/domain_contracts.md`) — the public DTO shapes this domain declares
|
|
4349
|
-
* (`
|
|
4293
|
+
* (`recordContract(id, fields)`), discovered by shape and composed across the domain's modules. ADDITIVE +
|
|
4350
4294
|
* OPTIONAL: a contract-free domain omits this entirely and is byte-identical to before contracts existed.
|
|
4351
4295
|
* Contracts are PURE typing/codegen metadata (NOT hashed law); they drive the additive `nomosContracts`
|
|
4352
4296
|
* IR key + the shared `contracts.dart` DTO classes + the TS `<Id>Contract` interfaces. `implementedBy`
|
|
@@ -4425,12 +4369,12 @@ export interface DomainModule {
|
|
|
4425
4369
|
permissionVocabulary?: PermissionVocabulary;
|
|
4426
4370
|
/**
|
|
4427
4371
|
* Extra Dart imports needed by this generated file. Used when a domain has typed
|
|
4428
|
-
* refs to aggregate
|
|
4372
|
+
* refs to aggregate classes emitted by another generated domain file.
|
|
4429
4373
|
*/
|
|
4430
4374
|
dartImports?: DartImport[];
|
|
4431
4375
|
/**
|
|
4432
4376
|
* Mapping from referenced aggregate wire type to the Dart import prefix that exposes
|
|
4433
|
-
* that aggregate's
|
|
4377
|
+
* that aggregate's generated class. This preserves cross-domain ref typing without
|
|
4434
4378
|
* forcing every domain into one giant generated library.
|
|
4435
4379
|
*/
|
|
4436
4380
|
dartRefImports?: DartRefImport[];
|
|
@@ -4438,15 +4382,19 @@ export interface DomainModule {
|
|
|
4438
4382
|
|
|
4439
4383
|
/** Options controlling FRAMEWORK-SHARED aggregate deduplication (Dart-codegen hygiene). */
|
|
4440
4384
|
export interface GenerateDartDomainOptions {
|
|
4385
|
+
/** Package-wide aggregate ownership used to emit canonical domain-qualified reference types. */
|
|
4386
|
+
readonly packageAggregateDomains?: Readonly<
|
|
4387
|
+
Record<string, { domain: string; name: string; namespaced?: boolean }>
|
|
4388
|
+
>;
|
|
4441
4389
|
/**
|
|
4442
|
-
* Aggregate ids whose Dart surface (projection class, read model,
|
|
4390
|
+
* Aggregate ids whose Dart surface (projection class, read model, by-id accessors)
|
|
4443
4391
|
* is emitted ONCE per package in the shared `nomos_framework.dart` and MUST NOT be re-emitted here —
|
|
4444
4392
|
* this domain REFERENCES them via {@link frameworkImportUri}. Empty/absent ⇒ emit everything locally
|
|
4445
4393
|
* (the legacy single-file behaviour; byte-identical to before this option existed).
|
|
4446
4394
|
*/
|
|
4447
4395
|
readonly skipAggregateIds?: ReadonlySet<string>;
|
|
4448
4396
|
/**
|
|
4449
|
-
* Framework-shared DIRECTIVE ids whose generated payload class
|
|
4397
|
+
* Framework-shared DIRECTIVE ids whose generated payload class is emitted
|
|
4450
4398
|
* ONCE per package in the shared file and MUST NOT be re-emitted here. The domain still OFFERS them
|
|
4451
4399
|
* (the client offer methods stay per-domain); only the payload TYPE moves to the shared file.
|
|
4452
4400
|
*/
|
|
@@ -4480,7 +4428,7 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
4480
4428
|
mod = {
|
|
4481
4429
|
...mod,
|
|
4482
4430
|
aggregates: publicAggregates,
|
|
4483
|
-
directives: mod.directives.filter(
|
|
4431
|
+
directives: (mod.applicationIntents ?? mod.directives).filter(
|
|
4484
4432
|
(d) => (d as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
|
|
4485
4433
|
),
|
|
4486
4434
|
...(mod.queries === undefined
|
|
@@ -4498,10 +4446,16 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
4498
4446
|
const codegenDirectives = mod.directives
|
|
4499
4447
|
.filter((d) => (d as { businessBirthInternal?: unknown }).businessBirthInternal !== true)
|
|
4500
4448
|
.map((d) => {
|
|
4501
|
-
const businessBirth = (d as { businessBirth?: { publicPayloadSchema?: z.ZodTypeAny } }).businessBirth;
|
|
4449
|
+
const businessBirth = (d as { businessBirth?: { aggregateId?: string; publicPayloadSchema?: z.ZodTypeAny } }).businessBirth;
|
|
4502
4450
|
return businessBirth?.publicPayloadSchema === undefined
|
|
4503
4451
|
? d
|
|
4504
|
-
: {
|
|
4452
|
+
: {
|
|
4453
|
+
...d,
|
|
4454
|
+
// Frontend-only name: the lowered framework directive remains `nomosBirth<Aggregate>` in law,
|
|
4455
|
+
// while tenant code receives the ordinary business input `Create<Aggregate>Input`.
|
|
4456
|
+
id: isBusinessIntent(d) ? d.id : `create${businessBirth.aggregateId ?? cap(d.id)}`,
|
|
4457
|
+
payloadSchema: businessBirth.publicPayloadSchema,
|
|
4458
|
+
} as AnyDirective;
|
|
4505
4459
|
});
|
|
4506
4460
|
const skipAggregateIds = opts.skipAggregateIds ?? new Set<string>();
|
|
4507
4461
|
const skipDirectiveIds = opts.skipDirectiveIds ?? new Set<string>();
|
|
@@ -4530,13 +4484,12 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
4530
4484
|
// in local VO descriptors OR directive payloads gets ONE zero-cost wrapper. Opaque by design —
|
|
4531
4485
|
// no generated arithmetic/conversions (a physics library is not a type system).
|
|
4532
4486
|
const unitNames = new Set<string>();
|
|
4533
|
-
// brandedId(...) names → their own zero-cost String extension type
|
|
4534
|
-
//
|
|
4487
|
+
// brandedId(...) names → their own zero-cost String extension type. These represent external
|
|
4488
|
+
// nominal strings, never aggregate identity. Collected from VO descriptors and directive payloads.
|
|
4535
4489
|
const brandNames = new Set<string>();
|
|
4536
4490
|
const walkUnitField = (f: ValueObjectFieldDescriptor): void => {
|
|
4537
4491
|
if (f.type === "number" && f.unit !== undefined) unitNames.add(f.unit);
|
|
4538
|
-
// A standalone brandedId declares its own extension type; an idOf REFERENCE does
|
|
4539
|
-
// emits the canonical `<Agg>Id` class (with `.fromRow`), so re-declaring it here dup'd the symbol (co2).
|
|
4492
|
+
// A standalone brandedId declares its own extension type; an idOf REFERENCE does not.
|
|
4540
4493
|
else if (f.type === "string" && f.brand !== undefined && f.idOf === undefined) brandNames.add(f.brand);
|
|
4541
4494
|
else if (f.type === "array") walkUnitField(f.items);
|
|
4542
4495
|
else if (f.type === "map") walkUnitField(f.values);
|
|
@@ -4552,8 +4505,7 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
4552
4505
|
if (schema && typeof schema === "object" && zodTypeName(schema as z.ZodTypeAny) === "ZodObject") {
|
|
4553
4506
|
try {
|
|
4554
4507
|
for (const spec of introspectPayload(schema as z.ZodTypeAny, new Map())) {
|
|
4555
|
-
// A spec whose brand a VO descriptor already registered is a brandedId, not a unit
|
|
4556
|
-
// and neither is an aggregate typed-id (`<Agg>Id`, emitted by the id machinery).
|
|
4508
|
+
// A wrapped spec whose brand a VO descriptor already registered is a brandedId, not a unit.
|
|
4557
4509
|
if (spec.toJson === "wrapped" && !spec.dartType.endsWith("Id") && !brandNames.has(spec.dartType)) {
|
|
4558
4510
|
unitNames.add(spec.dartType);
|
|
4559
4511
|
}
|
|
@@ -4621,8 +4573,19 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
4621
4573
|
domainKey,
|
|
4622
4574
|
);
|
|
4623
4575
|
const refIdClassForAggregate = refIdClassResolver(mod);
|
|
4576
|
+
const packageAggregateDomains = opts.packageAggregateDomains ?? {};
|
|
4624
4577
|
const aggregatesById = new Map<string, AggregateHandle>();
|
|
4625
4578
|
for (const a of mod.aggregates) aggregatesById.set(a.id, a);
|
|
4579
|
+
const aggregateWireTypeForAggregate = (aggregateType: string): string => {
|
|
4580
|
+
const local = aggregatesById.get(aggregateType);
|
|
4581
|
+
if (local !== undefined && !isNamespacedAggregate(local)) return aggregateType;
|
|
4582
|
+
const owner = packageAggregateDomains[aggregateType];
|
|
4583
|
+
if (owner?.namespaced === false) return aggregateType;
|
|
4584
|
+
const domain = owner?.domain ?? (local === undefined ? undefined : domainKey);
|
|
4585
|
+
return domain === undefined
|
|
4586
|
+
? aggregateType
|
|
4587
|
+
: qualifyReadProjectionType(domain, aggregateType);
|
|
4588
|
+
};
|
|
4626
4589
|
const ownIdFieldsByAggregate = collectOwnIdFieldsByAggregate(
|
|
4627
4590
|
mod.aggregates,
|
|
4628
4591
|
codegenDirectives,
|
|
@@ -4637,24 +4600,16 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
4637
4600
|
dartTypes,
|
|
4638
4601
|
refIdClassForAggregate,
|
|
4639
4602
|
ownIdFieldsByAggregate.get(a.id) ?? new Set(),
|
|
4603
|
+
aggregateWireTypeForAggregate,
|
|
4640
4604
|
),
|
|
4641
4605
|
)
|
|
4642
4606
|
.join("\n\n");
|
|
4643
4607
|
// the runtime aggregate-meta registry (lookup by op.aggregateType — generic surfaces).
|
|
4644
4608
|
const aggregateMetaBlock = emitAggregateMetaRegistry(domainKey, localAggregates);
|
|
4645
4609
|
const dirBlock = localDirectives
|
|
4646
|
-
.map((d) => emitDirective(d, domainKey, dartTypes, recursiveTypes.names))
|
|
4610
|
+
.map((d) => emitDirective(d, domainKey, dartTypes, recursiveTypes.names, mod.aggregates))
|
|
4647
4611
|
.join("\n\n");
|
|
4648
4612
|
|
|
4649
|
-
// TYPED IDS + MINTED CREATES (the id-mint keystone, GENERALIZED): a DISTINCT typed
|
|
4650
|
-
// id class for EVERY aggregate (cross-aggregate type-confusion is a compile error;
|
|
4651
|
-
// ref fields name their target's typed id), plus a top-level `create…` factory that
|
|
4652
|
-
// mints THROUGH the kernel (no raw id) for every `.creates` directive carrying a
|
|
4653
|
-
// `mintSeed` + an own-id field.
|
|
4654
|
-
// Minted-create factories for framework directives (if any) live in the shared file — pass only the
|
|
4655
|
-
// LOCAL directives here; framework typed-id classes are likewise skipped (referenced via import).
|
|
4656
|
-
const mintedBlock = emitMintedCreates(mod.aggregates, localDirectives, domainKey, dartTypes, skipAggregateIds);
|
|
4657
|
-
|
|
4658
4613
|
// PROVIDER SDK (the symmetric twin of the peer payload classes): for each declared
|
|
4659
4614
|
// `impureCapability` capability, a typed Obligation/Result/Failure + handler typedef + dispatcher.
|
|
4660
4615
|
// ADDITIVE + omit-when-empty: a domain with no `impureCapabilities` emits nothing (and the
|
|
@@ -4696,6 +4651,7 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
4696
4651
|
combinedsByType.get(a.id) ?? [],
|
|
4697
4652
|
refIdClassForAggregate,
|
|
4698
4653
|
ownIdFieldsByAggregate.get(a.id) ?? new Set(),
|
|
4654
|
+
aggregateWireTypeForAggregate,
|
|
4699
4655
|
),
|
|
4700
4656
|
)
|
|
4701
4657
|
.join("\n\n");
|
|
@@ -4821,7 +4777,7 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
4821
4777
|
`// Regenerate: \`npx tsx src/emit_dart.ts\` from ts_packages/co2-nomos-domains.`,
|
|
4822
4778
|
`//`,
|
|
4823
4779
|
`// ignore_for_file: unnecessary_this, prefer_const_constructors, constant_identifier_names`,
|
|
4824
|
-
`// ignore_for_file: unused_import`,
|
|
4780
|
+
`// ignore_for_file: unused_import, invalid_use_of_internal_member`,
|
|
4825
4781
|
``,
|
|
4826
4782
|
// A nullable plain-`t.json()` read-model slot decode re-stringifies via `jsonEncode`.
|
|
4827
4783
|
...(needsJsonConvert ? [`import 'dart:convert';`] : []),
|
|
@@ -4830,7 +4786,7 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
4830
4786
|
// `../wire.dart` path into the framework (which broke when the generated dir moved
|
|
4831
4787
|
// out of nomos_client into the co-located tenant package). The framework barrel
|
|
4832
4788
|
// re-exports wire/dispatch/subscriptions/provider, so one import covers them all.
|
|
4833
|
-
`import 'package:nomos_client/
|
|
4789
|
+
`import 'package:nomos_client/src/nms_generated_runtime.dart';`,
|
|
4834
4790
|
// FRAMEWORK-SHARED aggregates (RelationTuple, …) are emitted ONCE per package in the shared
|
|
4835
4791
|
// file; import it so this domain's read accessors / ref-id fields resolve those symbols.
|
|
4836
4792
|
...(opts.frameworkImportUri !== undefined ? [`import '${opts.frameworkImportUri}';`] : []),
|
|
@@ -4887,13 +4843,6 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
4887
4843
|
`// ---- public intents (typed DTOs; ship via the generic offer() verb) ----`,
|
|
4888
4844
|
dirBlock,
|
|
4889
4845
|
``,
|
|
4890
|
-
...(mintedBlock !== ""
|
|
4891
|
-
? [
|
|
4892
|
-
`// ---- typed ids + minted creates (kernel id-mint: per-aggregate typed id classes + create… factories) ----`,
|
|
4893
|
-
mintedBlock,
|
|
4894
|
-
``,
|
|
4895
|
-
]
|
|
4896
|
-
: []),
|
|
4897
4846
|
`// ---- read accessors (typed subscriptions over the GitHolon query()/query_by_id() surface) ----`,
|
|
4898
4847
|
readBlock,
|
|
4899
4848
|
``,
|