@githolon/dsl 0.108.0 → 0.108.2
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 +5 -0
- package/dart/lib/src/application_framework_calls.dart +299 -0
- package/dart/lib/src/generated_call.dart +96 -0
- package/dart/lib/src/nms_generated_runtime.dart +29 -0
- package/dart/lib/src/node_runner_transport.dart +77 -1
- package/dart/lib/src/result_usda.dart +487 -0
- package/dart/lib/src/runner/node-entry.mjs +6401 -2102
- package/dart/lib/src/runner/offline-install.mjs +1538 -163
- package/dart/lib/src/runtime_v2_gateway.dart +54 -0
- package/dart/lib/src/runtime_v2_transport.dart +117 -0
- package/dart/lib/src/session_bound.dart +7 -2
- package/dart/lib/src/test_harness.dart +193 -112
- package/dart/lib/src/webview_bridge.dart +2120 -569
- package/dart/pubspec.yaml +2 -2
- package/dart/test/acquire_session_credentials_test.dart +1 -1
- package/dart/test/acquire_session_test.dart +68 -14
- package/dart/test/adv_realm_interference_test.dart +6 -4
- package/dart/test/cluster_births_test.dart +11 -8
- package/dart/test/cluster_local_autosync_test.dart +1 -1
- package/dart/test/cluster_scale_test.dart +3 -2
- package/dart/test/dlq_session_surface_test.dart +52 -35
- package/dart/test/first_app_gauntlet_test.dart +1 -1
- package/dart/test/local_sync_inert_test.dart +23 -26
- package/dart/test/offer_authentication_proof_test.dart +193 -0
- package/dart/test/result_usda_test.dart +49 -0
- package/dart/test/session_bound_test.dart +8 -11
- package/dart/test/stale_bundle_test.dart +18 -3
- package/dart/test/test_harness_test.dart +14 -27
- package/dart/test/webview_bridge_test.dart +504 -83
- package/dart_types/CHANGELOG.md +7 -0
- package/dart_types/lib/nomos_kernel_protocol.dart +3 -2
- package/dart_types/lib/src/custody.dart +64 -19
- package/dart_types/lib/src/local_work_rejected.dart +30 -45
- package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pb.dart +169 -0
- package/dart_types/lib/src/proto/nomos/custody/v1/snapshot.pbenum.dart +11 -0
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pb.dart +2021 -234
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbenum.dart +175 -8
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pb.dart +8664 -0
- package/dart_types/lib/src/proto/nomos/runtime/v2/runtime.pbenum.dart +369 -0
- package/dart_types/lib/src/surfaced.dart +30 -25
- package/dart_types/pubspec.yaml +1 -1
- package/dart_types/test/surfaced_test.dart +2 -1
- package/package.json +1 -1
- package/runtime/generated-application-host.js +5736 -2047
- package/src/authoring.ts +3 -2
- package/src/births.ts +21 -0
- package/src/build_package.ts +30 -0
- package/src/business_birth.ts +39 -35
- package/src/codegen_call_usda.ts +1111 -0
- package/src/codegen_dart.ts +207 -282
- package/src/codegen_dart_application.ts +195 -252
- package/src/codegen_dart_client.ts +1978 -593
- package/src/codegen_dart_framework_calls.ts +291 -0
- package/src/codegen_dart_scenario.ts +7 -0
- package/src/codegen_proof.ts +3 -4
- package/src/codegen_provider_dart.ts +21 -7
- package/src/codegen_query_usda.ts +214 -0
- package/src/codegen_result_usda.ts +685 -0
- package/src/codegen_ts.ts +3945 -1111
- package/src/compile_package_main.ts +360 -32
- package/src/directive.ts +19 -4
- package/src/engine_entry.ts +45 -4
- package/src/framework/application_home.ts +2 -0
- package/src/framework/auth_provider_law.ts +262 -20
- package/src/framework/home.ts +32 -28
- package/src/framework/owned_custody.ts +19 -11
- package/src/framework/owned_custody_value.ts +51 -0
- package/src/framework/relations.ts +57 -21
- package/src/framework/signing_identity.ts +34 -0
- package/src/framework/workspaces.ts +31 -9
- package/src/framework_call_definitions.ts +53 -0
- package/src/framework_read_definitions.ts +45 -0
- package/src/identity_frontier.ts +58 -0
- package/src/index.ts +20 -1
- package/src/intent.ts +216 -16
- package/src/law_check.ts +19 -1
- package/src/law_graph.ts +57 -8
- package/src/law_graph_usda.ts +73 -5
- package/src/law_value_type.ts +106 -19
- package/src/read.ts +47 -0
- package/src/read_projection_namespace.ts +7 -0
- package/src/shape_compat.ts +38 -39
- package/src/stable_ids.ts +37 -2
- package/src/stable_ids_for_module.ts +64 -4
- package/src/test_call_usda.ts +83 -0
- package/src/wire.ts +36 -2
- package/src/wire_encode.ts +45 -1
- package/src/workspace_sharing.ts +27 -10
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbjson.dart +0 -6096
- package/dart_types/lib/src/proto/nomos/kernel/v1/kernel.pbserver.dart +0 -50
- package/dart_types/lib/src/proto/nomos/kernel/v1/progress.pbjson.dart +0 -178
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pb.dart +0 -828
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbenum.dart +0 -465
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbjson.dart +0 -573
- package/dart_types/lib/src/proto/nomos/runtime/v1/runtime.pbserver.dart +0 -50
package/src/codegen_dart.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Dart frontend codegen (DSL decision-6: the schema is the single source of
|
|
10
|
-
* truth ->
|
|
10
|
+
* truth -> TypeScript, Protobuf host contracts, and Dart frontend types (here)).
|
|
11
11
|
*
|
|
12
12
|
* Emits, per domain:
|
|
13
13
|
* - a Dart class per aggregate with its typed fields, parsed (`fromJson`) from
|
|
@@ -17,12 +17,10 @@
|
|
|
17
17
|
* `watch…`/one-shot `read…` over the GitHolon `query()`/`query_by_id()`
|
|
18
18
|
* surface (the read half of the offer/query boundary);
|
|
19
19
|
* - a Dart enum per `t.enum([...])` field;
|
|
20
|
-
* - a Dart
|
|
21
|
-
* `
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* - a single generic effectful write verb [`offer`] that ships {domain, intentType,
|
|
25
|
-
* nmsInternalToPayloadJson()} to the GitHolon `author(domain, intentType, payloadJson, actor)`.
|
|
20
|
+
* - a Dart input class per domain action — a pure typed DTO. It builds no `Event`,
|
|
21
|
+
* `Hlc`, generic map, or JSON representation: the companion generated client lowers
|
|
22
|
+
* its fields directly into the canonical typed USDA call.
|
|
23
|
+
* - one effectful write verb [`offer`] carrying those USDA bytes to the GitHolon.
|
|
26
24
|
*
|
|
27
25
|
* It does NOT hand-write the fixed wire types (`Value`/`Op`/`Event`/...): those
|
|
28
26
|
* live hand-written in `dart_types/lib/src/wire.dart`. Only the per-domain shapes are
|
|
@@ -30,7 +28,7 @@
|
|
|
30
28
|
*/
|
|
31
29
|
import { z } from "zod";
|
|
32
30
|
import { enumTypeNameOf } from "./enum_type.js";
|
|
33
|
-
import { voMetaOf, collectPayloadValueObjectDescriptors, idOfMetaOf, idOfTargetKindOf, refOfMetaOf, versionedRefOfMetaOf, brandedIdMetaOf, NOMOS_UNIT_BRAND } from "./value_object.js";
|
|
31
|
+
import { voMetaOf, collectPayloadValueObjectDescriptors, collectValueObjectDescriptors, idOfMetaOf, idOfTargetKindOf, refOfMetaOf, versionedRefOfMetaOf, brandedIdMetaOf, NOMOS_UNIT_BRAND } from "./value_object.js";
|
|
34
32
|
import { recursiveTypeBaseName, zodUnwrapLazy } from "./zod_lazy.js";
|
|
35
33
|
import type { AggregateHandle } from "./aggregate.js";
|
|
36
34
|
import type { WorkspaceInvariantDecl } from "./framework/workspace_invariant.js";
|
|
@@ -78,14 +76,18 @@ export function dartActionInputClassName(directive: AnyDirective): string {
|
|
|
78
76
|
|
|
79
77
|
// ---- Zod payload introspection --------------------------------------------
|
|
80
78
|
|
|
81
|
-
/**
|
|
82
|
-
export type
|
|
79
|
+
/** The concrete Dart representation of a typed payload field. */
|
|
80
|
+
export type DartPayloadShape =
|
|
83
81
|
| "scalar" // String/int/bool — emit the field directly.
|
|
84
82
|
| "enum" // generated enum — emit `.wire`.
|
|
85
83
|
| "strList" // List<String> — emit directly.
|
|
86
|
-
| "
|
|
87
|
-
| "
|
|
88
|
-
| "
|
|
84
|
+
| "wrappedList" // List of nominal scalar wrappers — each element emits `.value`.
|
|
85
|
+
| "refList" // List<NomosRef<T>> — each element emits its canonical id.
|
|
86
|
+
| "record" // anonymous z.object — a structural Dart named record.
|
|
87
|
+
| "recordList" // list of anonymous z.object values — typed Dart records.
|
|
88
|
+
| "stringMap" // a declared z.record(z.string()) — Map<String, String>, encoded field-by-field.
|
|
89
|
+
| "vo" // a declared typed value object.
|
|
90
|
+
| "voList" // a list of declared typed value objects.
|
|
89
91
|
| "wrapped" // a standalone branded-id or unit extension type — emit `.value`.
|
|
90
92
|
| "ref" // an aggregate-specific NomosRef<T> — emit its `.id` wire scalar.
|
|
91
93
|
| "addressRef" // a workspace-qualified NomosRef<T> — preserve its complete typed address.
|
|
@@ -107,22 +109,21 @@ export interface PayloadFieldSpec {
|
|
|
107
109
|
versionedRefSeriesWireType?: string;
|
|
108
110
|
/** for enums: the Dart enum type name + raw values. */
|
|
109
111
|
enumValues?: string[];
|
|
110
|
-
/**
|
|
111
|
-
|
|
112
|
+
/** Concrete generated Dart shape consumed directly by the USDA encoder. */
|
|
113
|
+
shape: DartPayloadShape;
|
|
112
114
|
/** Wire scalar under a nominal wrapper. Keeps string semantics distinct from numeric units. */
|
|
113
115
|
wrappedScalarKind?: "string" | "number";
|
|
114
116
|
/**
|
|
115
117
|
* Whether the payload field is OPTIONAL (Zod `.optional()`/`.default()`). Optional
|
|
116
|
-
* fields are nullable in Dart and
|
|
117
|
-
* engine's `.plan()` sees exactly the present-field set its
|
|
118
|
-
* only emits ops for present fields). Required fields are always emitted.
|
|
118
|
+
* fields are nullable in Dart and omitted from the generated USDA call when null, so
|
|
119
|
+
* the engine's `.plan()` sees exactly the present-field set its schema declares.
|
|
119
120
|
*/
|
|
120
121
|
optional: boolean;
|
|
121
122
|
/**
|
|
122
123
|
* NULL SEMANTICS (null_semantics.md): the payload field ADMITS a domain `null` (its zod is
|
|
123
124
|
* `.nullable()`, a `t.nullable(...)`/`t.clearable(...)` sink). It generates as the sealed
|
|
124
125
|
* tri-state [NomosSlot] so the call site says OMITTED / EXPLICIT-NULL / PRESENT explicitly —
|
|
125
|
-
*
|
|
126
|
+
* the USDA encoder then omits, writes a real `null`, or writes the value accordingly.
|
|
126
127
|
* `false`/absent for a plain field (byte-identical to a pre-feature payload). */
|
|
127
128
|
nullAdmits?: boolean;
|
|
128
129
|
}
|
|
@@ -159,7 +160,7 @@ export function dartForeignReferenceInputs(
|
|
|
159
160
|
const outputs = aggregates.filter((aggregate) => outputIds.has(aggregate.id));
|
|
160
161
|
|
|
161
162
|
return specs.flatMap((spec) => {
|
|
162
|
-
if (spec.
|
|
163
|
+
if (spec.shape !== "ref" || recordInputIds.has(spec.name)) return [];
|
|
163
164
|
const matches = outputs.flatMap((aggregate) => {
|
|
164
165
|
const field = aggregate.fields[spec.name] as Field | undefined;
|
|
165
166
|
if (
|
|
@@ -364,7 +365,7 @@ function isOptionalOrDefault(zt: z.ZodTypeAny): boolean {
|
|
|
364
365
|
* engine's same Zod schema validates, so the call-site type can never truncate a
|
|
365
366
|
* fractional value the engine would accept.
|
|
366
367
|
*/
|
|
367
|
-
function zodNumberIsInt(zt: z.ZodTypeAny): boolean {
|
|
368
|
+
export function zodNumberIsInt(zt: z.ZodTypeAny): boolean {
|
|
368
369
|
const raw = zt as unknown as { isInt?: boolean };
|
|
369
370
|
if (raw.isInt === true) return true;
|
|
370
371
|
const checks = zodDef(zt).checks ?? [];
|
|
@@ -401,6 +402,11 @@ export function introspectPayload(
|
|
|
401
402
|
else optional = true;
|
|
402
403
|
zt = zodInnerType(zt);
|
|
403
404
|
}
|
|
405
|
+
// Literal fields are compiler-owned constants. The canonical USDA encoder emits
|
|
406
|
+
// them from the DSL graph, so exposing them as Dart constructor arguments would
|
|
407
|
+
// widen a literal constraint into an ordinary bool/string/number and ask callers
|
|
408
|
+
// to repeat information the model already knows.
|
|
409
|
+
if (zodTypeName(zt) === "ZodLiteral") continue;
|
|
404
410
|
out.push({
|
|
405
411
|
...introspectField(name, zt, enumDartTypes, lazyNames),
|
|
406
412
|
optional,
|
|
@@ -410,6 +416,20 @@ export function introspectPayload(
|
|
|
410
416
|
return out;
|
|
411
417
|
}
|
|
412
418
|
|
|
419
|
+
function dartAnonymousRecordType(
|
|
420
|
+
schema: z.ZodTypeAny,
|
|
421
|
+
enumDartTypes: Map<string, string>,
|
|
422
|
+
lazyNames?: Map<z.ZodTypeAny, string>,
|
|
423
|
+
): string {
|
|
424
|
+
const fields = introspectPayload(schema, enumDartTypes, lazyNames).map((spec) => {
|
|
425
|
+
if (spec.nullAdmits === true) {
|
|
426
|
+
return `NomosSlot<${stripNullable(spec.dartType)}> ${dartMemberName(spec.name)}`;
|
|
427
|
+
}
|
|
428
|
+
return `${spec.dartType}${spec.optional ? "?" : ""} ${dartMemberName(spec.name)}`;
|
|
429
|
+
});
|
|
430
|
+
return `({${fields.join(", ")}})`;
|
|
431
|
+
}
|
|
432
|
+
|
|
413
433
|
/**
|
|
414
434
|
* The aggregate ids a directive set references via `idOf(Agg)` in its TOP-LEVEL payload fields
|
|
415
435
|
* (dart_codegen_parity.md P2). A cross-domain `idOf` types the field as `NomosRef<SiblingAggregate>`;
|
|
@@ -441,8 +461,8 @@ export function collectPayloadIdOfTargets(directives: readonly { payloadSchema?:
|
|
|
441
461
|
|
|
442
462
|
/** Introspect ONE (already-unwrapped) Zod field into its Dart spec. Handles the
|
|
443
463
|
* four shapes a tenant domain uses: scalar (string/int/bool), enum, string[], and a
|
|
444
|
-
* NESTED object (e.g. attach/detach's `target:{targetType,targetId}`)
|
|
445
|
-
*
|
|
464
|
+
* NESTED object (e.g. attach/detach's `target:{targetType,targetId}`), represented
|
|
465
|
+
* as a structural Dart named record rather than a generic map. */
|
|
446
466
|
function introspectField(
|
|
447
467
|
name: string,
|
|
448
468
|
zt: z.ZodTypeAny,
|
|
@@ -458,7 +478,7 @@ function introspectField(
|
|
|
458
478
|
name,
|
|
459
479
|
dartType: `NomosRef<${target}>`,
|
|
460
480
|
referenceType: addressed.aggregateType,
|
|
461
|
-
|
|
481
|
+
shape: "addressRef",
|
|
462
482
|
};
|
|
463
483
|
}
|
|
464
484
|
const versioned = versionedRefOfMetaOf(zt);
|
|
@@ -472,7 +492,7 @@ function introspectField(
|
|
|
472
492
|
return {
|
|
473
493
|
name,
|
|
474
494
|
dartType: `VersionedNomosRef<${target}, ${series}>`,
|
|
475
|
-
|
|
495
|
+
shape: "versionedRef",
|
|
476
496
|
versionedRefTarget: target,
|
|
477
497
|
versionedRefSeries: series,
|
|
478
498
|
versionedRefCurrentField: versioned.currentField,
|
|
@@ -487,32 +507,27 @@ function introspectField(
|
|
|
487
507
|
};
|
|
488
508
|
}
|
|
489
509
|
if (recipientMetaOf(zt) !== undefined) {
|
|
490
|
-
return { name, dartType: "Recipient<User>",
|
|
510
|
+
return { name, dartType: "Recipient<User>", shape: "recipient" };
|
|
491
511
|
}
|
|
492
|
-
// RECURSIVE `z.lazy
|
|
493
|
-
//
|
|
494
|
-
// `.toJson()`). Without a synthesized class (no map, or a degraded knot) it resolves one level
|
|
495
|
-
// and flows into the opaque-JSON lane below (`Map<String,Object?>`) — the last-resort fallback.
|
|
512
|
+
// RECURSIVE `z.lazy`: the compiler must synthesize a typed recursive class for the knot.
|
|
513
|
+
// There is deliberately no Object?/JSON fallback on the authoring path.
|
|
496
514
|
if (zodTypeName(zt) === "ZodLazy") {
|
|
497
515
|
const cls = lazyNames?.get(zt);
|
|
498
|
-
if (cls !== undefined) return { name, dartType: cls,
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
// express (union, undeclared nested object) falls back to `Object?` rather than throwing.
|
|
516
|
+
if (cls !== undefined) return { name, dartType: cls, shape: "vo" };
|
|
517
|
+
// A recursive payload must have a generated nominal class. Refuse degraded knots instead
|
|
518
|
+
// of silently reopening an Object?/JSON escape hatch.
|
|
502
519
|
const inner = resolveLazy(zt);
|
|
503
|
-
if (zodTypeName(inner) === "ZodLazy")
|
|
504
|
-
|
|
505
|
-
return introspectField(name, inner, enumDartTypes, lazyNames);
|
|
506
|
-
} catch {
|
|
507
|
-
return { name, dartType: "Object?", toJson: "json" };
|
|
520
|
+
if (zodTypeName(inner) === "ZodLazy") {
|
|
521
|
+
throw new Error(`recursive payload field '${name}' has no generated Dart type`);
|
|
508
522
|
}
|
|
523
|
+
return introspectField(name, inner, enumDartTypes, lazyNames);
|
|
509
524
|
}
|
|
510
525
|
// A DECLARED VO/union keeps its name regardless of its zod kind (a union(...) is a
|
|
511
526
|
// z.discriminatedUnion, not a ZodObject — without this it would fall to the throw lane).
|
|
512
527
|
{
|
|
513
528
|
const vo = voMetaOf(zt);
|
|
514
529
|
if (vo !== undefined) {
|
|
515
|
-
return { name, dartType: vo.name,
|
|
530
|
+
return { name, dartType: vo.name, shape: "vo" };
|
|
516
531
|
}
|
|
517
532
|
}
|
|
518
533
|
const tn = zodTypeName(zt);
|
|
@@ -530,21 +545,21 @@ function introspectField(
|
|
|
530
545
|
name,
|
|
531
546
|
dartType: `NomosRef<${targetClass}>`,
|
|
532
547
|
referenceType: idAgg,
|
|
533
|
-
|
|
548
|
+
shape: "ref",
|
|
534
549
|
};
|
|
535
550
|
}
|
|
536
551
|
if (isSha256DigestSchema(zt)) {
|
|
537
|
-
return { name, dartType: "Sha256Digest",
|
|
552
|
+
return { name, dartType: "Sha256Digest", shape: "wrapped", wrappedScalarKind: "string" };
|
|
538
553
|
}
|
|
539
554
|
if (isGitObjectIdSchema(zt)) {
|
|
540
|
-
return { name, dartType: "GitObjectId",
|
|
555
|
+
return { name, dartType: "GitObjectId", shape: "wrapped", wrappedScalarKind: "string" };
|
|
541
556
|
}
|
|
542
557
|
// brandedId("SiteId"): a standalone nominal string → the generated extension type.
|
|
543
558
|
const brand = brandedIdMetaOf(zt);
|
|
544
559
|
if (brand !== undefined) {
|
|
545
|
-
return { name, dartType: brand,
|
|
560
|
+
return { name, dartType: brand, shape: "wrapped", wrappedScalarKind: "string" };
|
|
546
561
|
}
|
|
547
|
-
return { name, dartType: "String",
|
|
562
|
+
return { name, dartType: "String", shape: "scalar" };
|
|
548
563
|
}
|
|
549
564
|
case "ZodNumber":
|
|
550
565
|
// A `z.number()` is a FRACTIONAL double in JS (e.g. catalogues `createListing`
|
|
@@ -556,21 +571,21 @@ function introspectField(
|
|
|
556
571
|
{
|
|
557
572
|
const unitName = (zt as unknown as Record<symbol, unknown>)[NOMOS_UNIT_BRAND];
|
|
558
573
|
if (typeof unitName === "string") {
|
|
559
|
-
return { name, dartType: unitName,
|
|
574
|
+
return { name, dartType: unitName, shape: "wrapped", wrappedScalarKind: "number" };
|
|
560
575
|
}
|
|
561
576
|
}
|
|
562
577
|
return {
|
|
563
578
|
name,
|
|
564
579
|
dartType: zodNumberIsInt(zt) ? "int" : "double",
|
|
565
|
-
|
|
580
|
+
shape: "scalar",
|
|
566
581
|
};
|
|
567
582
|
case "ZodBoolean":
|
|
568
|
-
return { name, dartType: "bool",
|
|
583
|
+
return { name, dartType: "bool", shape: "scalar" };
|
|
569
584
|
case "ZodEnum": {
|
|
570
585
|
const values = zodEnumValues(zt);
|
|
571
586
|
const key = dartEnumLookupKey(zt);
|
|
572
587
|
const dartType = enumDartTypes.get(key) ?? `${cap(name)}Enum`;
|
|
573
|
-
return { name, dartType,
|
|
588
|
+
return { name, dartType, shape: "enum", enumValues: values };
|
|
574
589
|
}
|
|
575
590
|
case "ZodArray": {
|
|
576
591
|
const elem = zodArrayElement(zt);
|
|
@@ -578,18 +593,53 @@ function introspectField(
|
|
|
578
593
|
// with the generated VO class (vo_first_class.md). ANY other element
|
|
579
594
|
// type is arbitrary JSON DATA to the engine, modelled as `List<Object?>`.
|
|
580
595
|
if (zodTypeName(elem) === "ZodString") {
|
|
581
|
-
|
|
596
|
+
const idAgg = idOfMetaOf(elem);
|
|
597
|
+
if (idAgg !== undefined) {
|
|
598
|
+
const targetClass = idOfTargetKindOf(elem) === "contract"
|
|
599
|
+
? contractTypeName(idAgg)
|
|
600
|
+
: concreteClassName(idAgg);
|
|
601
|
+
return {
|
|
602
|
+
name,
|
|
603
|
+
dartType: `List<NomosRef<${targetClass}>>`,
|
|
604
|
+
referenceType: idAgg,
|
|
605
|
+
shape: "refList",
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
if (isSha256DigestSchema(elem)) {
|
|
609
|
+
return { name, dartType: "List<Sha256Digest>", shape: "wrappedList", wrappedScalarKind: "string" };
|
|
610
|
+
}
|
|
611
|
+
if (isGitObjectIdSchema(elem)) {
|
|
612
|
+
return { name, dartType: "List<GitObjectId>", shape: "wrappedList", wrappedScalarKind: "string" };
|
|
613
|
+
}
|
|
614
|
+
const brand = brandedIdMetaOf(elem);
|
|
615
|
+
if (brand !== undefined) {
|
|
616
|
+
return { name, dartType: `List<${brand}>`, shape: "wrappedList", wrappedScalarKind: "string" };
|
|
617
|
+
}
|
|
618
|
+
return { name, dartType: "List<String>", shape: "strList" };
|
|
619
|
+
}
|
|
620
|
+
if (zodTypeName(elem) === "ZodNumber") {
|
|
621
|
+
const unitName = (elem as unknown as Record<symbol, unknown>)[NOMOS_UNIT_BRAND];
|
|
622
|
+
if (typeof unitName === "string") {
|
|
623
|
+
return { name, dartType: `List<${unitName}>`, shape: "wrappedList", wrappedScalarKind: "number" };
|
|
624
|
+
}
|
|
582
625
|
}
|
|
583
626
|
// A list of a RECURSIVE `z.lazy` knot (a synthesized class) is a typed `List<Class>`.
|
|
584
627
|
const elemCls = zodTypeName(elem) === "ZodLazy" ? lazyNames?.get(elem) : undefined;
|
|
585
628
|
if (elemCls !== undefined) {
|
|
586
|
-
return { name, dartType: `List<${elemCls}>`,
|
|
629
|
+
return { name, dartType: `List<${elemCls}>`, shape: "voList" };
|
|
587
630
|
}
|
|
588
631
|
const elemVo = voMetaOf(elem);
|
|
589
632
|
if (elemVo !== undefined) {
|
|
590
|
-
return { name, dartType: `List<${elemVo.name}>`,
|
|
633
|
+
return { name, dartType: `List<${elemVo.name}>`, shape: "voList" };
|
|
591
634
|
}
|
|
592
|
-
|
|
635
|
+
if (zodTypeName(elem) === "ZodObject") {
|
|
636
|
+
return {
|
|
637
|
+
name,
|
|
638
|
+
dartType: `List<${dartAnonymousRecordType(elem, enumDartTypes, lazyNames)}>`,
|
|
639
|
+
shape: "recordList",
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
throw new Error(`payload list '${name}' needs a generated concrete Dart element type`);
|
|
593
643
|
}
|
|
594
644
|
case "ZodObject": {
|
|
595
645
|
// A DECLARED VALUE OBJECT keeps its name: the payload field is the generated VO class
|
|
@@ -597,24 +647,23 @@ function introspectField(
|
|
|
597
647
|
// own `toJson()`. An anonymous `z.object(...)` stays the raw-map lane below.
|
|
598
648
|
const vo = voMetaOf(zt);
|
|
599
649
|
if (vo !== undefined) {
|
|
600
|
-
return { name, dartType: vo.name,
|
|
650
|
+
return { name, dartType: vo.name, shape: "vo" };
|
|
601
651
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
652
|
+
return {
|
|
653
|
+
name,
|
|
654
|
+
dartType: dartAnonymousRecordType(zt, enumDartTypes, lazyNames),
|
|
655
|
+
shape: "record",
|
|
656
|
+
};
|
|
606
657
|
}
|
|
607
658
|
case "ZodRecord": {
|
|
608
659
|
const valueType = zodRecordValueType(zt);
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
return { name, dartType, toJson: "json" };
|
|
660
|
+
if (valueType === undefined || zodTypeName(valueType) !== "ZodString") {
|
|
661
|
+
throw new Error(`payload map '${name}' needs a generated concrete Dart value type`);
|
|
662
|
+
}
|
|
663
|
+
return { name, dartType: "Map<String, String>", shape: "stringMap" };
|
|
614
664
|
}
|
|
615
665
|
case "ZodThing":
|
|
616
|
-
|
|
617
|
-
return { name, dartType: "Map<String, Object?>", toJson: "json" };
|
|
666
|
+
throw new Error(`payload field '${name}' is an unstructured thing; declare its typed schema`);
|
|
618
667
|
default:
|
|
619
668
|
throw new Error(`unsupported payload field type '${tn}' for '${name}'`);
|
|
620
669
|
}
|
|
@@ -638,7 +687,7 @@ function directiveDoc(d: AnyDirective): string {
|
|
|
638
687
|
|
|
639
688
|
type MaterializedFieldKind = Exclude<FieldKind, "hasMany">;
|
|
640
689
|
|
|
641
|
-
interface AggFieldSpec {
|
|
690
|
+
export interface AggFieldSpec {
|
|
642
691
|
/** Dart-safe field/property name exposed on generated classes. */
|
|
643
692
|
name: string;
|
|
644
693
|
/** Canonical wire/readmodel key declared by the DSL, e.g. `spec.domainHash`. */
|
|
@@ -879,7 +928,7 @@ function dartImportLine(imp: DartImport): string {
|
|
|
879
928
|
return `import '${imp.uri}'${prefix};`;
|
|
880
929
|
}
|
|
881
930
|
|
|
882
|
-
function refIdClassResolver(mod: DomainModule): (aggregateType: string) => string {
|
|
931
|
+
export function refIdClassResolver(mod: DomainModule): (aggregateType: string) => string {
|
|
883
932
|
const imports = new Map<string, DartRefImport>();
|
|
884
933
|
for (const imp of mod.dartRefImports ?? []) {
|
|
885
934
|
imports.set(imp.aggregateType, imp);
|
|
@@ -1164,7 +1213,7 @@ function aggFieldSpec(
|
|
|
1164
1213
|
}
|
|
1165
1214
|
}
|
|
1166
1215
|
|
|
1167
|
-
function aggregateFieldSpecs(
|
|
1216
|
+
export function aggregateFieldSpecs(
|
|
1168
1217
|
agg: AggregateHandle,
|
|
1169
1218
|
enumDartTypes: Map<string, string>,
|
|
1170
1219
|
refIdClassForAggregate: (aggregateType: string) => string = concreteClassName,
|
|
@@ -1381,21 +1430,31 @@ export function collectDartEnums(
|
|
|
1381
1430
|
}
|
|
1382
1431
|
}
|
|
1383
1432
|
for (const d of directives) {
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
if (zodTypeName(inner) === "ZodEnum") {
|
|
1433
|
+
const walkPayloadEnums = (schema: z.ZodTypeAny, path: readonly string[]): void => {
|
|
1434
|
+
const inner = unwrapZod(schema);
|
|
1435
|
+
const kind = zodTypeName(inner);
|
|
1436
|
+
if (kind === "ZodEnum") {
|
|
1389
1437
|
const declaredName = enumTypeNameOf(inner);
|
|
1390
|
-
if ((declaredName !== undefined) !== namedPass)
|
|
1438
|
+
if ((declaredName !== undefined) !== namedPass) return;
|
|
1439
|
+
const fieldName = path.at(-1) ?? d.id;
|
|
1391
1440
|
register(
|
|
1392
|
-
|
|
1441
|
+
fieldName,
|
|
1393
1442
|
zodEnumValues(inner),
|
|
1394
|
-
(d as { aggregateId?: string }).aggregateId ?? d.id,
|
|
1443
|
+
`${(d as { aggregateId?: string }).aggregateId ?? d.id}${path.length > 1 ? cap(path.slice(0, -1).join("_")) : ""}`,
|
|
1395
1444
|
declaredName,
|
|
1396
1445
|
);
|
|
1446
|
+
return;
|
|
1397
1447
|
}
|
|
1398
|
-
|
|
1448
|
+
if (kind === "ZodArray") {
|
|
1449
|
+
walkPayloadEnums(zodArrayElement(inner), path);
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
if (kind !== "ZodObject") return;
|
|
1453
|
+
for (const [name, child] of Object.entries(zodObjectShape(inner))) {
|
|
1454
|
+
walkPayloadEnums(child as z.ZodTypeAny, [...path, name]);
|
|
1455
|
+
}
|
|
1456
|
+
};
|
|
1457
|
+
walkPayloadEnums(d.payloadSchema as z.ZodTypeAny, []);
|
|
1399
1458
|
}
|
|
1400
1459
|
}
|
|
1401
1460
|
for (const declaration of deriveds) {
|
|
@@ -1419,7 +1478,7 @@ export function dartEnumTypeForField(
|
|
|
1419
1478
|
return dartTypes.get(dartEnumLookupKey(unwrapZod(field.zod)));
|
|
1420
1479
|
}
|
|
1421
1480
|
|
|
1422
|
-
function dartEnumLookupKey(
|
|
1481
|
+
export function dartEnumLookupKey(
|
|
1423
1482
|
schemaOrValues: z.ZodTypeAny | readonly string[],
|
|
1424
1483
|
declaredName?: string,
|
|
1425
1484
|
): string {
|
|
@@ -1969,7 +2028,7 @@ function voEnumFields(d: ValueObjectDescriptor): {
|
|
|
1969
2028
|
}
|
|
1970
2029
|
|
|
1971
2030
|
/** The generated arm class name of one union arm (`Shape` + `circle` → `ShapeCircle`). */
|
|
1972
|
-
function unionArmClassName(unionName: string, armKey: string): string {
|
|
2031
|
+
export function unionArmClassName(unionName: string, armKey: string): string {
|
|
1973
2032
|
return `${unionName}${armKey[0]!.toUpperCase()}${armKey.slice(1)}`;
|
|
1974
2033
|
}
|
|
1975
2034
|
|
|
@@ -2073,12 +2132,16 @@ function voToJsonExpr(f: ValueObjectFieldDescriptor, expr: string, depth = 0): s
|
|
|
2073
2132
|
|
|
2074
2133
|
/** Deep json equality/hash helpers shared by every generated VO class — emitted ONCE
|
|
2075
2134
|
* per file when the file emits ≥1 VO class (`toJson()`-based value equality). */
|
|
2076
|
-
function emitVoHelpers(): string {
|
|
2135
|
+
function emitVoHelpers(includeCopyWithSentinel: boolean): string {
|
|
2077
2136
|
return [
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2137
|
+
...(includeCopyWithSentinel
|
|
2138
|
+
? [
|
|
2139
|
+
`/// The copyWith ABSENT-vs-null sentinel: an omitted optional param keeps the current`,
|
|
2140
|
+
`/// value; an explicit \`null\` clears it. (Dart cannot distinguish the two otherwise.)`,
|
|
2141
|
+
`const Object _voUnset = Object();`,
|
|
2142
|
+
``,
|
|
2143
|
+
]
|
|
2144
|
+
: []),
|
|
2082
2145
|
`/// Deep json equality for generated value-object classes (toJson-based).`,
|
|
2083
2146
|
`bool _voJsonEq(Object? a, Object? b) {`,
|
|
2084
2147
|
` if (a is Map && b is Map) {`,
|
|
@@ -3753,15 +3816,11 @@ function emitDirective(
|
|
|
3753
3816
|
const specs = introspectPayload(d.payloadSchema as z.ZodTypeAny, enumDartTypes, lazyNames);
|
|
3754
3817
|
const recordInputs = dartBusinessRecordInputs(d, aggregates);
|
|
3755
3818
|
const foreignReferenceInputs = dartForeignReferenceInputs(d, aggregates);
|
|
3756
|
-
const foreignReferenceIds = new Set(foreignReferenceInputs.map((input) => input.idField));
|
|
3757
3819
|
const principalField = d.businessPrincipalField;
|
|
3758
3820
|
// targetBinding is declared on AnyDirective — honour it everywhere (client method,
|
|
3759
3821
|
// payload class, application surface) or the three emitters disagree and the
|
|
3760
3822
|
// generated Dart cannot compile.
|
|
3761
3823
|
const targetBinding = (d as { targetBinding?: import("./directive.js").DirectiveTargetBinding }).targetBinding;
|
|
3762
|
-
const targetType = targetBinding === undefined
|
|
3763
|
-
? undefined
|
|
3764
|
-
: `NomosRef<${concreteClassName(targetBinding.aggregateId)}>`;
|
|
3765
3824
|
const derivedFields = new Set([
|
|
3766
3825
|
...recordInputs.flatMap((input) => [input.idField, input.workspaceField]),
|
|
3767
3826
|
...foreignReferenceInputs.map((input) => input.workspaceField),
|
|
@@ -3774,33 +3833,18 @@ function emitDirective(
|
|
|
3774
3833
|
// makes an accidentally forgotten relationship indistinguishable from a
|
|
3775
3834
|
// deliberate omission. The generated sum type keeps those meanings apart.
|
|
3776
3835
|
const isOptionalReferenceChoice = (spec: PayloadFieldSpec): boolean =>
|
|
3777
|
-
spec.optional && spec.
|
|
3778
|
-
const internalPayloadArguments = [
|
|
3779
|
-
...(targetBinding === undefined
|
|
3780
|
-
? []
|
|
3781
|
-
: [`required ${targetType} nmsInternalTarget`]),
|
|
3782
|
-
...(principalField === undefined ? [] : ["required String nmsInternalPrincipal"]),
|
|
3783
|
-
...(recordInputs.length === 0
|
|
3784
|
-
? []
|
|
3785
|
-
: [
|
|
3786
|
-
"required String Function(Object record) nmsInternalLocationOf",
|
|
3787
|
-
"required String Function(Object record) nmsInternalIdOf",
|
|
3788
|
-
]),
|
|
3789
|
-
];
|
|
3790
|
-
const internalPayloadSignature = internalPayloadArguments.length === 0
|
|
3791
|
-
? ""
|
|
3792
|
-
: `{${internalPayloadArguments.join(", ")}}`;
|
|
3836
|
+
spec.optional && spec.shape === "ref";
|
|
3793
3837
|
|
|
3794
|
-
// Field
|
|
3795
|
-
//
|
|
3838
|
+
// Field declarations stay fully typed; optional fields are omitted directly by
|
|
3839
|
+
// the USDA encoder while required fields remain non-null constructor inputs.
|
|
3796
3840
|
const fieldDecls = [
|
|
3797
3841
|
...publicSpecs
|
|
3798
3842
|
.map((s) =>
|
|
3799
3843
|
s.nullAdmits === true
|
|
3800
|
-
? ` final NomosSlot<${stripNullable(s.dartType)}> ${s.name};`
|
|
3844
|
+
? ` final NomosSlot<${stripNullable(s.dartType)}> ${dartMemberName(s.name)};`
|
|
3801
3845
|
: isOptionalReferenceChoice(s)
|
|
3802
|
-
? ` final NomosOptionalRef<${s.dartType.replace(/^NomosRef<(.*)>$/, "$1")}> ${s.name};`
|
|
3803
|
-
: ` final ${s.dartType}${s.optional ? "?" : ""} ${s.name};`,
|
|
3846
|
+
? ` final NomosOptionalRef<${s.dartType.replace(/^NomosRef<(.*)>$/, "$1")}> ${dartMemberName(s.name)};`
|
|
3847
|
+
: ` final ${s.dartType}${s.optional ? "?" : ""} ${dartMemberName(s.name)};`,
|
|
3804
3848
|
),
|
|
3805
3849
|
...recordInputs.map((input) => ` final ${input.dartType} ${input.argument};`),
|
|
3806
3850
|
].join("\n");
|
|
@@ -3810,10 +3854,10 @@ function emitDirective(
|
|
|
3810
3854
|
// A null-admitting slot defaults to Omitted (the call site chooses Present/ExplicitNull);
|
|
3811
3855
|
// an optional field defaults to null; a required field is `required`.
|
|
3812
3856
|
s.nullAdmits === true
|
|
3813
|
-
? ` this.${s.name} = const NomosSlot.omitted(),`
|
|
3857
|
+
? ` this.${dartMemberName(s.name)} = const NomosSlot.omitted(),`
|
|
3814
3858
|
: isOptionalReferenceChoice(s)
|
|
3815
|
-
? ` required this.${s.name},`
|
|
3816
|
-
: ` ${s.optional ? "" : "required "}this.${s.name},`,
|
|
3859
|
+
? ` required this.${dartMemberName(s.name)},`
|
|
3860
|
+
: ` ${s.optional ? "" : "required "}this.${dartMemberName(s.name)},`,
|
|
3817
3861
|
),
|
|
3818
3862
|
...recordInputs.map((input) => ` required this.${input.argument},`),
|
|
3819
3863
|
].join("\n");
|
|
@@ -3825,90 +3869,6 @@ function emitDirective(
|
|
|
3825
3869
|
? [` const ${className}({`, ctorParams, ` });`]
|
|
3826
3870
|
: [` const ${className}();`];
|
|
3827
3871
|
|
|
3828
|
-
// toPayloadJson: serialise only typed fields to data. No Event, no Hlc; the
|
|
3829
|
-
// GitHolon executes the installed domain plan. Optional
|
|
3830
|
-
// fields are written only when present (mirrors the engine's Zod: ops are emitted
|
|
3831
|
-
// only for present fields).
|
|
3832
|
-
const requiredEntries = specs
|
|
3833
|
-
.filter((s) =>
|
|
3834
|
-
!derivedFields.has(s.name) &&
|
|
3835
|
-
!foreignReferenceIds.has(s.name) &&
|
|
3836
|
-
!s.optional &&
|
|
3837
|
-
s.nullAdmits !== true
|
|
3838
|
-
)
|
|
3839
|
-
.map((s) => ` '${s.name}': ${toJsonExpr(s, s.name)},`);
|
|
3840
|
-
const targetEntry = targetBinding === undefined
|
|
3841
|
-
? []
|
|
3842
|
-
: [` '${targetBinding.field}': NmsInternalNomosRefs.id(nmsInternalTarget),`];
|
|
3843
|
-
const optionalLines = specs
|
|
3844
|
-
.filter((s) =>
|
|
3845
|
-
!derivedFields.has(s.name) &&
|
|
3846
|
-
s.optional &&
|
|
3847
|
-
s.nullAdmits !== true &&
|
|
3848
|
-
!isOptionalReferenceChoice(s)
|
|
3849
|
-
)
|
|
3850
|
-
.map(
|
|
3851
|
-
(s) =>
|
|
3852
|
-
` if (${s.name} != null) '${s.name}': ${toJsonExpr(s, `${s.name}!`)},`,
|
|
3853
|
-
);
|
|
3854
|
-
// NULL SEMANTICS: a null-admitting field writes through its slot — Omitted writes nothing,
|
|
3855
|
-
// ExplicitNull writes a real `null`, Present writes the encoded value (null_semantics.md).
|
|
3856
|
-
const slotSpecs = specs.filter((s) => !derivedFields.has(s.name) && s.nullAdmits === true);
|
|
3857
|
-
const slotWriteLines = slotSpecs.map(
|
|
3858
|
-
(s) => ` ${s.name}.writeTo(data, '${s.name}', (v) => ${toJsonExpr(s, "v")});`,
|
|
3859
|
-
);
|
|
3860
|
-
const optionalReferenceSpecs = specs.filter(
|
|
3861
|
-
(s) =>
|
|
3862
|
-
!derivedFields.has(s.name) &&
|
|
3863
|
-
!foreignReferenceIds.has(s.name) &&
|
|
3864
|
-
isOptionalReferenceChoice(s),
|
|
3865
|
-
);
|
|
3866
|
-
const optionalReferenceWriteLines = optionalReferenceSpecs.map(
|
|
3867
|
-
(s) => ` ${s.name}.writeTo(data, '${s.name}', (reference) => NmsInternalNomosRefs.id(reference));`,
|
|
3868
|
-
);
|
|
3869
|
-
const requiredForeignReferenceInputs = foreignReferenceInputs.filter((input) => !input.optional);
|
|
3870
|
-
const requiredWorkspaceSources = new Map<string, DartForeignReferenceInput>();
|
|
3871
|
-
const requiredForeignReferenceEntries = requiredForeignReferenceInputs.flatMap((input) => {
|
|
3872
|
-
const first = requiredWorkspaceSources.get(input.workspaceField);
|
|
3873
|
-
if (first === undefined) requiredWorkspaceSources.set(input.workspaceField, input);
|
|
3874
|
-
return [
|
|
3875
|
-
...(first === undefined
|
|
3876
|
-
? [` '${input.workspaceField}': NmsInternalNomosRefs.workspace(${input.idField}),`]
|
|
3877
|
-
: []),
|
|
3878
|
-
` '${input.idField}': NmsInternalNomosRefs.id(${input.idField}),`,
|
|
3879
|
-
];
|
|
3880
|
-
});
|
|
3881
|
-
const requiredForeignReferenceValidationLines = requiredForeignReferenceInputs.flatMap((input) => {
|
|
3882
|
-
const first = requiredWorkspaceSources.get(input.workspaceField)!;
|
|
3883
|
-
if (first.idField === input.idField) return [];
|
|
3884
|
-
return [
|
|
3885
|
-
` if (NmsInternalNomosRefs.workspace(${input.idField}) != NmsInternalNomosRefs.workspace(${first.idField})) {`,
|
|
3886
|
-
` throw ArgumentError('Foreign references for ${input.workspaceField} must belong to the same workspace.');`,
|
|
3887
|
-
` }`,
|
|
3888
|
-
];
|
|
3889
|
-
});
|
|
3890
|
-
const optionalForeignReferenceWriteLines = foreignReferenceInputs
|
|
3891
|
-
.filter((input) => input.optional)
|
|
3892
|
-
.flatMap((input) => [
|
|
3893
|
-
` ${input.idField}.when(`,
|
|
3894
|
-
` absent: () {},`,
|
|
3895
|
-
` present: (reference) {`,
|
|
3896
|
-
` final workspace = NmsInternalNomosRefs.workspace(reference);`,
|
|
3897
|
-
` final existingWorkspace = data['${input.workspaceField}'];`,
|
|
3898
|
-
` if (existingWorkspace != null && existingWorkspace != workspace) {`,
|
|
3899
|
-
` throw ArgumentError('Foreign references for ${input.workspaceField} must belong to the same workspace.');`,
|
|
3900
|
-
` }`,
|
|
3901
|
-
` data['${input.workspaceField}'] = workspace;`,
|
|
3902
|
-
` data['${input.idField}'] = NmsInternalNomosRefs.id(reference);`,
|
|
3903
|
-
` },`,
|
|
3904
|
-
` );`,
|
|
3905
|
-
]);
|
|
3906
|
-
const hasPostWrites =
|
|
3907
|
-
slotSpecs.length > 0 ||
|
|
3908
|
-
optionalReferenceSpecs.length > 0 ||
|
|
3909
|
-
requiredForeignReferenceValidationLines.length > 0 ||
|
|
3910
|
-
optionalForeignReferenceWriteLines.length > 0;
|
|
3911
|
-
|
|
3912
3872
|
const docFirst = directiveDoc(d);
|
|
3913
3873
|
|
|
3914
3874
|
return [
|
|
@@ -3923,72 +3883,10 @@ function emitDirective(
|
|
|
3923
3883
|
` /// ${docFirst}`,
|
|
3924
3884
|
...ctorLines,
|
|
3925
3885
|
``,
|
|
3926
|
-
` /// Nomos implementation seam used only by the generated application package.`,
|
|
3927
|
-
` /// Tenant code should pass this object to the generated domain application instead.`,
|
|
3928
|
-
...(hasPostWrites
|
|
3929
|
-
? [
|
|
3930
|
-
` Map<String, Object?> nmsInternalToPayloadJson(${internalPayloadSignature}) {`,
|
|
3931
|
-
` final data = <String, Object?>{`,
|
|
3932
|
-
...requiredEntries,
|
|
3933
|
-
...targetEntry,
|
|
3934
|
-
...requiredForeignReferenceEntries,
|
|
3935
|
-
...optionalLines,
|
|
3936
|
-
...(principalField === undefined ? [] : [` '${principalField}': nmsInternalPrincipal,`]),
|
|
3937
|
-
...recordInputs.flatMap((input) => [
|
|
3938
|
-
` '${input.workspaceField}': nmsInternalLocationOf(${input.argument}),`,
|
|
3939
|
-
` '${input.idField}': nmsInternalIdOf(${input.argument}),`,
|
|
3940
|
-
]),
|
|
3941
|
-
` };`,
|
|
3942
|
-
...requiredForeignReferenceValidationLines,
|
|
3943
|
-
...slotWriteLines,
|
|
3944
|
-
...optionalReferenceWriteLines,
|
|
3945
|
-
...optionalForeignReferenceWriteLines,
|
|
3946
|
-
` return data;`,
|
|
3947
|
-
` }`,
|
|
3948
|
-
]
|
|
3949
|
-
: [
|
|
3950
|
-
` Map<String, Object?> nmsInternalToPayloadJson(${internalPayloadSignature}) => <String, Object?>{`,
|
|
3951
|
-
...requiredEntries,
|
|
3952
|
-
...targetEntry,
|
|
3953
|
-
...requiredForeignReferenceEntries,
|
|
3954
|
-
...optionalLines,
|
|
3955
|
-
...(principalField === undefined ? [] : [` '${principalField}': nmsInternalPrincipal,`]),
|
|
3956
|
-
...recordInputs.flatMap((input) => [
|
|
3957
|
-
` '${input.workspaceField}': nmsInternalLocationOf(${input.argument}),`,
|
|
3958
|
-
` '${input.idField}': nmsInternalIdOf(${input.argument}),`,
|
|
3959
|
-
]),
|
|
3960
|
-
` };`,
|
|
3961
|
-
]),
|
|
3962
3886
|
`}`,
|
|
3963
3887
|
].join("\n");
|
|
3964
3888
|
}
|
|
3965
3889
|
|
|
3966
|
-
/** Expression that serialises ONE typed payload field into its JSON value. */
|
|
3967
|
-
export function toJsonExpr(spec: PayloadFieldSpec, accessor: string): string {
|
|
3968
|
-
switch (spec.toJson) {
|
|
3969
|
-
case "scalar":
|
|
3970
|
-
case "strList":
|
|
3971
|
-
case "json":
|
|
3972
|
-
return accessor;
|
|
3973
|
-
case "enum":
|
|
3974
|
-
return `${accessor}.wire`;
|
|
3975
|
-
case "vo":
|
|
3976
|
-
return `${accessor}.toJson()`;
|
|
3977
|
-
case "voList":
|
|
3978
|
-
return `${accessor}.map((e) => e.toJson()).toList()`;
|
|
3979
|
-
case "wrapped":
|
|
3980
|
-
return `${accessor}.value`;
|
|
3981
|
-
case "ref":
|
|
3982
|
-
return `NmsInternalNomosRefs.id(${accessor})`;
|
|
3983
|
-
case "addressRef":
|
|
3984
|
-
return `NmsInternalNomosRefs.toJson(${accessor})`;
|
|
3985
|
-
case "versionedRef":
|
|
3986
|
-
return `{'pinned': NmsInternalNomosRefs.toJson(${accessor}.pinned), 'series': NmsInternalNomosRefs.toJson(${accessor}.series)}`;
|
|
3987
|
-
case "recipient":
|
|
3988
|
-
return `NmsInternalRecipients.address(${accessor})`;
|
|
3989
|
-
}
|
|
3990
|
-
}
|
|
3991
|
-
|
|
3992
3890
|
// ── RECURSIVE `z.lazy` payloads → synthesized Dart classes (full support) ─────────
|
|
3993
3891
|
//
|
|
3994
3892
|
// A self-referencing payload schema (`TreeNode = z.lazy(() => z.object({ label, children:
|
|
@@ -4714,7 +4612,7 @@ function sampleFromZod(zt: z.ZodTypeAny): unknown {
|
|
|
4714
4612
|
}
|
|
4715
4613
|
}
|
|
4716
4614
|
|
|
4717
|
-
function collectOwnIdFieldsByAggregate(
|
|
4615
|
+
export function collectOwnIdFieldsByAggregate(
|
|
4718
4616
|
aggregates: AggregateHandle[],
|
|
4719
4617
|
directives: AnyDirective[],
|
|
4720
4618
|
enumDartTypes: Map<string, string>,
|
|
@@ -5060,6 +4958,14 @@ export interface GenerateDartDomainOptions {
|
|
|
5060
4958
|
* module carries locally.
|
|
5061
4959
|
*/
|
|
5062
4960
|
readonly skipValueObjectNames?: ReadonlySet<string>;
|
|
4961
|
+
/**
|
|
4962
|
+
* Additional compiler-proven value-object closure to emit in this file. The package
|
|
4963
|
+
* composer uses this only for its synthetic shared types layer: ordinary domains
|
|
4964
|
+
* still derive their public vocabulary from public aggregates and payloads, so a
|
|
4965
|
+
* private framework aggregate cannot leak a nominal type into tenant Dart merely
|
|
4966
|
+
* because it was present in the composed law.
|
|
4967
|
+
*/
|
|
4968
|
+
readonly additionalValueObjects?: readonly ValueObjectDescriptor[];
|
|
5063
4969
|
}
|
|
5064
4970
|
|
|
5065
4971
|
/** Generate the full Dart source for one domain module. */
|
|
@@ -5068,9 +4974,31 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
5068
4974
|
(a) => (a as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
|
|
5069
4975
|
);
|
|
5070
4976
|
const publicAggregateIds = new Set(publicAggregates.map((a) => a.id));
|
|
4977
|
+
// Recompute the value vocabulary after removing framework-internal aggregates.
|
|
4978
|
+
// `mod.valueObjects` describes the whole composed law, so carrying it through
|
|
4979
|
+
// here would publish private framework values merely because they coexist with
|
|
4980
|
+
// the tenant domain. Payload-only public values are collected below from the
|
|
4981
|
+
// public directives, preserving every type reachable from the application API.
|
|
4982
|
+
const publicAggregateValueObjectsByName = new Map<string, ValueObjectDescriptor>();
|
|
4983
|
+
for (const descriptor of [
|
|
4984
|
+
...collectValueObjectDescriptors(publicAggregates),
|
|
4985
|
+
...(opts.additionalValueObjects ?? []),
|
|
4986
|
+
]) {
|
|
4987
|
+
const prior = publicAggregateValueObjectsByName.get(descriptor.name);
|
|
4988
|
+
if (prior !== undefined && JSON.stringify(prior) !== JSON.stringify(descriptor)) {
|
|
4989
|
+
throw new Error(
|
|
4990
|
+
`value object '${descriptor.name}' has DIVERGENT public/shared Dart shapes — one name, one shape`,
|
|
4991
|
+
);
|
|
4992
|
+
}
|
|
4993
|
+
publicAggregateValueObjectsByName.set(descriptor.name, descriptor);
|
|
4994
|
+
}
|
|
4995
|
+
const publicAggregateValueObjects = [...publicAggregateValueObjectsByName.values()].sort(
|
|
4996
|
+
(a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0),
|
|
4997
|
+
);
|
|
5071
4998
|
mod = {
|
|
5072
4999
|
...mod,
|
|
5073
5000
|
aggregates: publicAggregates,
|
|
5001
|
+
valueObjects: publicAggregateValueObjects,
|
|
5074
5002
|
directives: (mod.applicationIntents ?? mod.directives).filter(
|
|
5075
5003
|
(d) => (d as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
|
|
5076
5004
|
),
|
|
@@ -5149,7 +5077,7 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
5149
5077
|
try {
|
|
5150
5078
|
for (const spec of introspectPayload(schema as z.ZodTypeAny, new Map())) {
|
|
5151
5079
|
// A wrapped spec whose brand a VO descriptor already registered is a brandedId, not a unit.
|
|
5152
|
-
if (spec.
|
|
5080
|
+
if (spec.shape === "wrapped" && spec.wrappedScalarKind === "number") {
|
|
5153
5081
|
unitNames.add(spec.dartType);
|
|
5154
5082
|
}
|
|
5155
5083
|
}
|
|
@@ -5178,12 +5106,18 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
5178
5106
|
`/// passing a bare String (or another brand) where [${b}] is required is a COMPILE error.`,
|
|
5179
5107
|
`extension type const ${b}(String value) {}`,
|
|
5180
5108
|
].join("\n")).join("\n\n");
|
|
5109
|
+
const needsVoCopyWithSentinel = localValueObjects.some((descriptor) =>
|
|
5110
|
+
Object.values(descriptor.fields ?? {}).some((field) => field.optional === true)
|
|
5111
|
+
|| Object.values(descriptor.union ?? {}).some((arm) =>
|
|
5112
|
+
"fields" in arm && Object.values(arm.fields).some((field) => field.optional === true),
|
|
5113
|
+
)
|
|
5114
|
+
);
|
|
5181
5115
|
const voBlock = [
|
|
5182
5116
|
...(unitBlock !== "" ? [unitBlock] : []),
|
|
5183
5117
|
...(brandBlock !== "" ? [brandBlock] : []),
|
|
5184
5118
|
...(localValueObjects.length > 0
|
|
5185
5119
|
? [
|
|
5186
|
-
emitVoHelpers(),
|
|
5120
|
+
emitVoHelpers(needsVoCopyWithSentinel),
|
|
5187
5121
|
...localValueObjects.flatMap((d) => [
|
|
5188
5122
|
...voEnumFields(d)
|
|
5189
5123
|
.filter((e) => e.declaredName === undefined)
|
|
@@ -5501,16 +5435,14 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
5501
5435
|
]
|
|
5502
5436
|
: []),
|
|
5503
5437
|
...helperBlock,
|
|
5504
|
-
// NOTE: role UI-hint helpers (canEdit/canView) are emitted into the CLIENT package by dartRoleHelpers
|
|
5505
|
-
// NOT here — they need dart:convert, which the types-package purity gate forbids.
|
|
5438
|
+
// NOTE: role UI-hint helpers (canEdit/canView) are emitted into the CLIENT package by dartRoleHelpers.
|
|
5506
5439
|
].join("\n");
|
|
5507
5440
|
}
|
|
5508
5441
|
|
|
5509
5442
|
/** The role UI-hint helpers (`canEdit<Agg>`/`canView<Agg>` + `_holdsAnyRelation`) for the CLIENT package — a
|
|
5510
|
-
* pure client-side
|
|
5511
|
-
*
|
|
5512
|
-
*
|
|
5513
|
-
* and ensures a `dart:convert` import. Returns "" when the domain declares no roles (era-safe, no import). */
|
|
5443
|
+
* pure client-side mirror of the kernel ReBAC check over the compiler-generated RelationTuple projection.
|
|
5444
|
+
* Every tuple and subject is nominally typed from the same DSL graph; there is no JSON leaf or map-shaped
|
|
5445
|
+
* boundary. INFORMATIONAL only (the one wasm gate is still authoritative). */
|
|
5514
5446
|
export function dartRoleHelpers(mod: DomainModule): string {
|
|
5515
5447
|
const editRels = new Map<string, Set<string>>();
|
|
5516
5448
|
const viewRels = new Map<string, Set<string>>();
|
|
@@ -5525,21 +5457,14 @@ export function dartRoleHelpers(mod: DomainModule): string {
|
|
|
5525
5457
|
if (editRels.size === 0 && viewRels.size === 0) return "";
|
|
5526
5458
|
const lines: string[] = [
|
|
5527
5459
|
`/// True iff [principal] holds ANY of [relations] as an ACTIVE tuple in [rows] (or a \`user:*\` wildcard).`,
|
|
5528
|
-
`bool _holdsAnyRelation(List<
|
|
5460
|
+
`bool _holdsAnyRelation(List<RelationTupleReadModel>? rows, Set<String> relations, String principal) {`,
|
|
5529
5461
|
` for (final r in rows ?? const []) {`,
|
|
5530
|
-
` final
|
|
5531
|
-
`
|
|
5532
|
-
`
|
|
5533
|
-
`
|
|
5534
|
-
` final subjects = data['subjects'];`,
|
|
5535
|
-
` if (subjects is! Map) continue;`,
|
|
5536
|
-
` for (final entry in subjects.entries) {`,
|
|
5537
|
-
` final subj = entry.key.toString();`,
|
|
5538
|
-
` dynamic leaf = entry.value;`,
|
|
5539
|
-
` if (leaf is String) { try { leaf = jsonDecode(leaf); } catch (_) { leaf = const {}; } }`,
|
|
5540
|
-
` final status = leaf is Map ? leaf['status'] : null;`,
|
|
5541
|
-
` if (status != null && status != 'active') continue;`,
|
|
5462
|
+
` if (r.relation case final relation? when relations.contains(relation)) {`,
|
|
5463
|
+
` for (final entry in r.subjects.entries) {`,
|
|
5464
|
+
` final subj = entry.key;`,
|
|
5465
|
+
` if (entry.value.status != RelationshipSubjectStatus.active) continue;`,
|
|
5542
5466
|
` if (subj == 'user:\$principal' || subj == 'user:*') return true;`,
|
|
5467
|
+
` }`,
|
|
5543
5468
|
` }`,
|
|
5544
5469
|
` }`,
|
|
5545
5470
|
` return false;`,
|
|
@@ -5550,14 +5475,14 @@ export function dartRoleHelpers(mod: DomainModule): string {
|
|
|
5550
5475
|
lines.push(
|
|
5551
5476
|
`/// UI HINT — may [principal] EDIT a ${agg}? Mirrors the kernel's ReBAC check; INFORMATIONAL only`,
|
|
5552
5477
|
`/// (the one wasm gate is the authority and refuses regardless).`,
|
|
5553
|
-
`bool canEdit${cap(agg)}(List<
|
|
5478
|
+
`bool canEdit${cap(agg)}(List<RelationTupleReadModel>? relations, String principal) =>`,
|
|
5554
5479
|
` _holdsAnyRelation(relations, ${dartSet(rels)}, principal);`,
|
|
5555
5480
|
);
|
|
5556
5481
|
}
|
|
5557
5482
|
for (const [agg, rels] of [...viewRels].sort((a, b) => (a[0] < b[0] ? -1 : 1))) {
|
|
5558
5483
|
lines.push(
|
|
5559
5484
|
`/// UI HINT — may [principal] VIEW a ${agg}? Mirrors the kernel's ReBAC read gate; INFORMATIONAL only.`,
|
|
5560
|
-
`bool canView${cap(agg)}(List<
|
|
5485
|
+
`bool canView${cap(agg)}(List<RelationTupleReadModel>? relations, String principal) =>`,
|
|
5561
5486
|
` _holdsAnyRelation(relations, ${dartSet(rels)}, principal);`,
|
|
5562
5487
|
);
|
|
5563
5488
|
}
|