@githolon/dsl 0.103.11 → 0.103.13
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 +10 -0
- package/dart/lib/src/runner/node-entry.mjs +234 -170
- package/dart/lib/src/runner/offline-install.mjs +111 -123
- package/dart/lib/src/webview_bridge.dart +11 -11
- package/dart/pubspec.yaml +1 -1
- package/dart/test/webview_bridge_test.dart +10 -7
- package/dart_types/CHANGELOG.md +1 -1
- package/dart_types/lib/nomos_types.dart +4 -4
- package/dart_types/lib/src/versioned_nomos_ref.dart +13 -11
- package/package.json +1 -1
- package/runtime/generated-application-host.js +424 -237
- package/src/aggregate.ts +3 -1
- package/src/build_package.ts +77 -6
- package/src/check_package_main.ts +39 -16
- package/src/codegen_dart.ts +185 -48
- package/src/codegen_dart_application.ts +82 -16
- package/src/codegen_dart_client.ts +38 -10
- package/src/codegen_dot.ts +3 -3
- package/src/codegen_proof.ts +24 -2
- package/src/codegen_ts.ts +32 -19
- package/src/compile_package_main.ts +68 -11
- package/src/derived.ts +17 -0
- package/src/directive.ts +65 -0
- package/src/engine_entry.ts +22 -2
- package/src/enum_type.ts +71 -0
- package/src/fields.ts +135 -5
- package/src/flow_gate.ts +2 -1
- package/src/framework/workspaces.ts +73 -0
- package/src/idof_lint.ts +5 -1
- package/src/index.ts +20 -0
- package/src/kernel_capabilities.ts +12 -1
- package/src/law_check.ts +107 -28
- package/src/law_graph.ts +151 -20
- package/src/law_graph_usda.ts +23 -0
- package/src/law_value_type.ts +10 -3
- package/src/lsp_server.ts +26 -16
- package/src/package_config.ts +112 -9
- package/src/package_feature_topology.ts +174 -0
- package/src/plan_source_slice.ts +6 -0
- package/src/report.ts +6 -0
- package/src/stable_ids_for_module.ts +1 -0
- package/src/value_object.ts +9 -2
- package/src/workspace_sharding.ts +4 -4
package/src/codegen_dart.ts
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* generated, so they stay reproducible from the domain definitions.
|
|
30
30
|
*/
|
|
31
31
|
import { z } from "zod";
|
|
32
|
+
import { enumTypeNameOf } from "./enum_type.js";
|
|
32
33
|
import { voMetaOf, collectPayloadValueObjectDescriptors, idOfMetaOf, idOfTargetKindOf, refOfMetaOf, versionedRefOfMetaOf, brandedIdMetaOf, NOMOS_UNIT_BRAND } from "./value_object.js";
|
|
33
34
|
import { recursiveTypeBaseName, zodUnwrapLazy } from "./zod_lazy.js";
|
|
34
35
|
import type { AggregateHandle } from "./aggregate.js";
|
|
@@ -46,7 +47,7 @@ import { finishSum, type AnySum } from "./sum.js";
|
|
|
46
47
|
import { finishExtremum, type AnyExtremum } from "./extremum.js";
|
|
47
48
|
import { finishExists, type AnyExists } from "./exists.js";
|
|
48
49
|
import type { OrderedReadDecl } from "./ordered_read.js";
|
|
49
|
-
import type {
|
|
50
|
+
import type { AnyDerivedDecl } from "./derived.js";
|
|
50
51
|
import type { CombinedDecl } from "./combined.js";
|
|
51
52
|
import { emitProviderSdk, type ImpureCapabilityDecl } from "./codegen_provider_dart.js";
|
|
52
53
|
import type { WorkspaceTypeDecl } from "./workspace_type.js";
|
|
@@ -69,7 +70,10 @@ export function dartActionInputClassName(directive: AnyDirective): string {
|
|
|
69
70
|
if (businessBirth?.aggregateId !== undefined && !isBusinessIntent(directive)) {
|
|
70
71
|
return `Create${concreteClassName(businessBirth.aggregateId)}Input`;
|
|
71
72
|
}
|
|
72
|
-
|
|
73
|
+
const suffix = isBusinessIntent(directive)
|
|
74
|
+
? directive.targetBinding === undefined ? "Input" : "Facts"
|
|
75
|
+
: "Payload";
|
|
76
|
+
return `${cap(directive.id)}${suffix}`;
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
// ---- Zod payload introspection --------------------------------------------
|
|
@@ -564,7 +568,7 @@ function introspectField(
|
|
|
564
568
|
return { name, dartType: "bool", toJson: "scalar" };
|
|
565
569
|
case "ZodEnum": {
|
|
566
570
|
const values = zodEnumValues(zt);
|
|
567
|
-
const key =
|
|
571
|
+
const key = dartEnumLookupKey(zt);
|
|
568
572
|
const dartType = enumDartTypes.get(key) ?? `${cap(name)}Enum`;
|
|
569
573
|
return { name, dartType, toJson: "enum", enumValues: values };
|
|
570
574
|
}
|
|
@@ -695,6 +699,8 @@ interface AggFieldSpec {
|
|
|
695
699
|
* fields like a create's id/name).
|
|
696
700
|
*/
|
|
697
701
|
optional: boolean;
|
|
702
|
+
/** Native read fields which should be preferred for age/status decisions over this raw clock. */
|
|
703
|
+
temporalEnumIds?: readonly string[];
|
|
698
704
|
/**
|
|
699
705
|
* For a `map`-kind field: the inner VALUE field's kind (`field.mapValueKind`). A
|
|
700
706
|
* `"json"`-valued map (`t.map(t.json())`) is surfaced as `Map<String, Object?>` (each
|
|
@@ -1117,8 +1123,9 @@ function aggFieldSpec(
|
|
|
1117
1123
|
return { name, wireName, kind: field.kind, dartType: "bool", optional };
|
|
1118
1124
|
case "enum": {
|
|
1119
1125
|
// Pull the enum values out of the Zod schema (unwrapping `.optional()`).
|
|
1120
|
-
const
|
|
1121
|
-
const
|
|
1126
|
+
const schema = unwrapZod(field.zod);
|
|
1127
|
+
const values = zodEnumValues(schema);
|
|
1128
|
+
const key = dartEnumLookupKey(schema);
|
|
1122
1129
|
const dartType = enumDartTypes.get(key) ?? `${dartTypeNameFromField(wireName)}Enum`;
|
|
1123
1130
|
return { name, wireName, kind: field.kind, dartType, enumValues: values, optional };
|
|
1124
1131
|
}
|
|
@@ -1202,14 +1209,17 @@ function aggregateFieldSpecs(
|
|
|
1202
1209
|
: f.isFromBirth === true
|
|
1203
1210
|
? { ...base, optional: false }
|
|
1204
1211
|
: { ...base, optional: true };
|
|
1212
|
+
const temporalSpec: AggFieldSpec = (f.temporalEnums?.length ?? 0) > 0
|
|
1213
|
+
? { ...baseSpec, temporalEnumIds: f.temporalEnums!.map((read) => read.id) }
|
|
1214
|
+
: baseSpec;
|
|
1205
1215
|
// SURFACED MERGE CONFLICTS (architecture/surfaced_merge_conflict.md): a `.merge(Surfaced(...))`
|
|
1206
1216
|
// field types `SurfacedValue<T>?` and decodes via `SurfacedValue.decode`. ALWAYS nullable —
|
|
1207
1217
|
// even `.fromBirth()` (the wrapper's pending arm has no non-null plain-scalar reading).
|
|
1208
1218
|
// aggregate() already v1-scoped the driver to Lww-class scalar kinds; excludes nullAdmits.
|
|
1209
1219
|
const surfacedSpec: AggFieldSpec =
|
|
1210
1220
|
(f.driver as { kind?: string } | undefined)?.kind === "Surfaced"
|
|
1211
|
-
? { ...
|
|
1212
|
-
:
|
|
1221
|
+
? { ...temporalSpec, optional: true, surfaced: true }
|
|
1222
|
+
: temporalSpec;
|
|
1213
1223
|
// NULL SEMANTICS (null_semantics.md): a null-admitting scalar carries the tri-state slot.
|
|
1214
1224
|
const nmeta = nullMetaOf(f);
|
|
1215
1225
|
const spec: AggFieldSpec = admitsNull(f)
|
|
@@ -1290,12 +1300,14 @@ function dartNameStem(aggregateType: string): string {
|
|
|
1290
1300
|
}
|
|
1291
1301
|
|
|
1292
1302
|
/** Collect all enum value-sets across aggregates + directives, name each ONCE. */
|
|
1293
|
-
function
|
|
1303
|
+
export function collectDartEnums(
|
|
1294
1304
|
aggregates: AggregateHandle[],
|
|
1295
1305
|
directives: AnyDirective[],
|
|
1296
1306
|
domainKey: string,
|
|
1307
|
+
valueObjects: readonly ValueObjectDescriptor[] = [],
|
|
1308
|
+
deriveds: readonly AnyDerivedDecl[] = [],
|
|
1297
1309
|
): { dartTypes: Map<string, string>; decls: string[] } {
|
|
1298
|
-
const dartTypes = new Map<string, string>(); //
|
|
1310
|
+
const dartTypes = new Map<string, string>(); // structural values or declared nominal identity -> DartEnumName
|
|
1299
1311
|
const usedNames = new Set<string>(); // every emitted enum name (collision guard)
|
|
1300
1312
|
const decls: string[] = [];
|
|
1301
1313
|
// Generated enum types are DOMAIN-QUALIFIED (`EstateStatusEnum`,
|
|
@@ -1310,10 +1322,20 @@ function collectEnums(
|
|
|
1310
1322
|
// shared across owners keeps the FIRST owner's name (one identical type, aggregates register
|
|
1311
1323
|
// before payloads so the aggregate's name wins). Numeric suffixes survive only as the
|
|
1312
1324
|
// last-resort collision guard — with owner-qualified bases they should never fire.
|
|
1313
|
-
const register = (
|
|
1314
|
-
|
|
1325
|
+
const register = (
|
|
1326
|
+
preferredName: string,
|
|
1327
|
+
values: readonly string[],
|
|
1328
|
+
owner = "",
|
|
1329
|
+
declaredName?: string,
|
|
1330
|
+
) => {
|
|
1331
|
+
// Anonymous enums remain structural: the same wire vocabulary can share one
|
|
1332
|
+
// generated Dart type. A declared enumType is nominal: its exported name is
|
|
1333
|
+
// part of its identity, so two business concepts never collapse merely because
|
|
1334
|
+
// their current wire values happen to be equal.
|
|
1335
|
+
const key = dartEnumLookupKey(values, declaredName);
|
|
1315
1336
|
if (dartTypes.has(key)) return;
|
|
1316
|
-
const base =
|
|
1337
|
+
const base = declaredName ??
|
|
1338
|
+
`${domainPrefix}${dartTypeNameFromField(owner)}${dartTypeNameFromField(preferredName)}`;
|
|
1317
1339
|
let name = base;
|
|
1318
1340
|
let n = 1;
|
|
1319
1341
|
while (usedNames.has(name)) {
|
|
@@ -1336,29 +1358,83 @@ function collectEnums(
|
|
|
1336
1358
|
`}`,
|
|
1337
1359
|
);
|
|
1338
1360
|
};
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
)) {
|
|
1343
|
-
if (field.
|
|
1344
|
-
|
|
1345
|
-
register(name, zodEnumValues(unwrapZod(field.zod)), agg.id);
|
|
1361
|
+
// Named enum declarations win before anonymous occurrence-derived names, irrespective of source
|
|
1362
|
+
// or aggregate ordering. Reorganising files therefore cannot rename a declared public type.
|
|
1363
|
+
for (const descriptor of valueObjects) {
|
|
1364
|
+
for (const field of voEnumFields(descriptor)) {
|
|
1365
|
+
if (field.declaredName !== undefined) {
|
|
1366
|
+
register(field.fieldName, field.values, field.owner ?? descriptor.name, field.declaredName);
|
|
1346
1367
|
}
|
|
1347
1368
|
}
|
|
1348
1369
|
}
|
|
1349
|
-
for (const
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1370
|
+
for (const namedPass of [true, false]) {
|
|
1371
|
+
for (const agg of aggregates) {
|
|
1372
|
+
for (const [name, field] of Object.entries(
|
|
1373
|
+
agg.fields as Record<string, Field>,
|
|
1374
|
+
)) {
|
|
1375
|
+
if (field.kind === "enum") {
|
|
1376
|
+
const schema = unwrapZod(field.zod);
|
|
1377
|
+
const declaredName = enumTypeNameOf(schema);
|
|
1378
|
+
if ((declaredName !== undefined) !== namedPass) continue;
|
|
1379
|
+
register(name, zodEnumValues(schema), agg.id, declaredName);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
for (const d of directives) {
|
|
1384
|
+
if (zodTypeName(d.payloadSchema as z.ZodTypeAny) !== "ZodObject") continue;
|
|
1385
|
+
const shape = zodObjectShape(d.payloadSchema as z.ZodTypeAny);
|
|
1386
|
+
for (const [name, zt] of Object.entries(shape)) {
|
|
1387
|
+
const inner = unwrapZod(zt as z.ZodTypeAny);
|
|
1388
|
+
if (zodTypeName(inner) === "ZodEnum") {
|
|
1389
|
+
const declaredName = enumTypeNameOf(inner);
|
|
1390
|
+
if ((declaredName !== undefined) !== namedPass) continue;
|
|
1391
|
+
register(
|
|
1392
|
+
name,
|
|
1393
|
+
zodEnumValues(inner),
|
|
1394
|
+
(d as { aggregateId?: string }).aggregateId ?? d.id,
|
|
1395
|
+
declaredName,
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1356
1398
|
}
|
|
1357
1399
|
}
|
|
1358
1400
|
}
|
|
1401
|
+
for (const declaration of deriveds) {
|
|
1402
|
+
if ("kind" in declaration && declaration.kind === "temporalEnum") {
|
|
1403
|
+
register(
|
|
1404
|
+
declaration.id,
|
|
1405
|
+
zodEnumValues(declaration.returns),
|
|
1406
|
+
declaration.of,
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1359
1410
|
return { dartTypes, decls };
|
|
1360
1411
|
}
|
|
1361
1412
|
|
|
1413
|
+
/** Resolve an enum field to the exact nominal Dart type emitted for its value set. */
|
|
1414
|
+
export function dartEnumTypeForField(
|
|
1415
|
+
field: Field | undefined,
|
|
1416
|
+
dartTypes: ReadonlyMap<string, string>,
|
|
1417
|
+
): string | undefined {
|
|
1418
|
+
if (field?.kind !== "enum") return undefined;
|
|
1419
|
+
return dartTypes.get(dartEnumLookupKey(unwrapZod(field.zod)));
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
function dartEnumLookupKey(
|
|
1423
|
+
schemaOrValues: z.ZodTypeAny | readonly string[],
|
|
1424
|
+
declaredName?: string,
|
|
1425
|
+
): string {
|
|
1426
|
+
if (Array.isArray(schemaOrValues)) {
|
|
1427
|
+
return declaredName === undefined
|
|
1428
|
+
? `values:${schemaOrValues.join("|")}`
|
|
1429
|
+
: `declared:${declaredName}`;
|
|
1430
|
+
}
|
|
1431
|
+
const schema = unwrapZod(schemaOrValues as z.ZodTypeAny);
|
|
1432
|
+
const name = enumTypeNameOf(schema);
|
|
1433
|
+
return name === undefined
|
|
1434
|
+
? `values:${zodEnumValues(schema).join("|")}`
|
|
1435
|
+
: `declared:${name}`;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1362
1438
|
/** Dart reserved words that cannot be bare identifiers (enum members / fields). A
|
|
1363
1439
|
* value like `"new"` (feedback status) collides, so it is suffixed (`new_`). */
|
|
1364
1440
|
const DART_RESERVED = new Set([
|
|
@@ -1584,10 +1660,17 @@ function emitAggregate(
|
|
|
1584
1660
|
: []),
|
|
1585
1661
|
...specs.flatMap((s) => {
|
|
1586
1662
|
const doc = s.nullReason !== undefined ? [` /// null semantic: ${s.nullReason}`] : [];
|
|
1663
|
+
const temporalGuidance = s.temporalEnumIds === undefined
|
|
1664
|
+
? []
|
|
1665
|
+
: [
|
|
1666
|
+
` /// Raw captured timestamp. Prefer ${s.temporalEnumIds.map((id) => `[${id}]`).join(" or ")} for age-based business/UI decisions.`,
|
|
1667
|
+
` @Deprecated('Use ${s.temporalEnumIds.join(" or ")} for age/freshness decisions; this raw timestamp is for display and audit.')`,
|
|
1668
|
+
];
|
|
1587
1669
|
// SURFACED MERGE CONFLICTS: the field reads as the plain scalar, or [SurfacedPending]
|
|
1588
1670
|
// while genuinely concurrent writes await a business merge (resolve = an ordinary next write).
|
|
1589
1671
|
if (s.surfaced === true) {
|
|
1590
1672
|
return [
|
|
1673
|
+
...temporalGuidance,
|
|
1591
1674
|
` /// Surfaced merge conflicts: [SurfacedResolved] steady state, [SurfacedPending] under conflict.`,
|
|
1592
1675
|
` final SurfacedValue<${s.dartType}>? ${s.name};`,
|
|
1593
1676
|
];
|
|
@@ -1808,9 +1891,9 @@ const geometryFactoryNameDart: Record<string, string> = {
|
|
|
1808
1891
|
* IDENTITY = THE VO FIELD, not the value set: two identical value-sets in DIFFERENT
|
|
1809
1892
|
* VOs stay separate types (VO names are package-unique, so so are these).
|
|
1810
1893
|
*/
|
|
1811
|
-
function voEnumName(voName: string, fieldName: string): string {
|
|
1894
|
+
function voEnumName(voName: string, fieldName: string, declaredName?: string): string {
|
|
1812
1895
|
// SEMANTIC NAME (co2 feedback): `<VoName><Field>` — `SiteLocationSource`, not `…SourceEnum`.
|
|
1813
|
-
return `${voName}${dartTypeNameFromField(fieldName)}`;
|
|
1896
|
+
return declaredName ?? `${voName}${dartTypeNameFromField(fieldName)}`;
|
|
1814
1897
|
}
|
|
1815
1898
|
|
|
1816
1899
|
/** The tolerant-decode SENTINEL case name — dodge a literal enum value that would
|
|
@@ -1825,8 +1908,8 @@ function voEnumSentinel(values: readonly string[]): string {
|
|
|
1825
1908
|
* a new value added by a later law must NOT crash an old client): `fromWire` maps an
|
|
1826
1909
|
* unrecognized wire string to the [sentinel] case instead of throwing (the deliberate
|
|
1827
1910
|
* divergence from the strict aggregate-enum `fromWire`; VO data crosses law versions). */
|
|
1828
|
-
function emitVoEnum(voName: string, fieldName: string, values: readonly string[]): string {
|
|
1829
|
-
const name = voEnumName(voName, fieldName);
|
|
1911
|
+
function emitVoEnum(voName: string, fieldName: string, values: readonly string[], declaredName?: string): string {
|
|
1912
|
+
const name = voEnumName(voName, fieldName, declaredName);
|
|
1830
1913
|
const sentinel = voEnumSentinel(values);
|
|
1831
1914
|
const variants = values.map((v) => ` ${dartIdent(v)}('${v}')`).join(",\n");
|
|
1832
1915
|
return [
|
|
@@ -1852,10 +1935,25 @@ function emitVoEnum(voName: string, fieldName: string, values: readonly string[]
|
|
|
1852
1935
|
|
|
1853
1936
|
/** Every (fieldName, values) enum pair a VO descriptor carries (arrays recursed —
|
|
1854
1937
|
* an array-of-enum field's element enum is named by the FIELD). */
|
|
1855
|
-
function voEnumFields(d: ValueObjectDescriptor): {
|
|
1856
|
-
|
|
1938
|
+
function voEnumFields(d: ValueObjectDescriptor): {
|
|
1939
|
+
fieldName: string;
|
|
1940
|
+
values: readonly string[];
|
|
1941
|
+
owner?: string;
|
|
1942
|
+
declaredName?: string;
|
|
1943
|
+
}[] {
|
|
1944
|
+
const out: {
|
|
1945
|
+
fieldName: string;
|
|
1946
|
+
values: readonly string[];
|
|
1947
|
+
owner?: string;
|
|
1948
|
+
declaredName?: string;
|
|
1949
|
+
}[] = [];
|
|
1857
1950
|
const walk = (fieldName: string, f: ValueObjectFieldDescriptor, owner?: string): void => {
|
|
1858
|
-
if (f.type === "enum") out.push({
|
|
1951
|
+
if (f.type === "enum") out.push({
|
|
1952
|
+
fieldName,
|
|
1953
|
+
values: f.values,
|
|
1954
|
+
...(owner !== undefined ? { owner } : {}),
|
|
1955
|
+
...(f.name === undefined ? {} : { declaredName: f.name }),
|
|
1956
|
+
});
|
|
1859
1957
|
else if (f.type === "array") walk(fieldName, f.items, owner);
|
|
1860
1958
|
else if (f.type === "map") walk(fieldName, f.values, owner);
|
|
1861
1959
|
};
|
|
@@ -1891,7 +1989,7 @@ function dartTypeOfVoField(f: ValueObjectFieldDescriptor, voName: string, fieldN
|
|
|
1891
1989
|
return "bool";
|
|
1892
1990
|
case "enum":
|
|
1893
1991
|
// A REAL generated Dart enum (`emitVoEnum`), named by the owning VO field.
|
|
1894
|
-
return voEnumName(voName, fieldName);
|
|
1992
|
+
return voEnumName(voName, fieldName, f.name);
|
|
1895
1993
|
case "literal":
|
|
1896
1994
|
return "String"; // surfaced as a constant getter, not a ctor param
|
|
1897
1995
|
case "array":
|
|
@@ -1923,7 +2021,7 @@ function voFromExpr(
|
|
|
1923
2021
|
return f.brand !== undefined ? `${f.brand}(${expr} as String)` : `${expr} as String`;
|
|
1924
2022
|
case "enum":
|
|
1925
2023
|
// TOLERANT typed decode — an unknown wire value becomes the sentinel case.
|
|
1926
|
-
return `${voEnumName(voName, fieldName)}.fromWire(${expr} as String)`;
|
|
2024
|
+
return `${voEnumName(voName, fieldName, f.name)}.fromWire(${expr} as String)`;
|
|
1927
2025
|
case "number":
|
|
1928
2026
|
if (f.unit !== undefined) return `${f.unit}((${expr} as num).toDouble())`;
|
|
1929
2027
|
return f.int === true ? `(${expr} as num).toInt()` : `(${expr} as num).toDouble()`;
|
|
@@ -3316,7 +3414,7 @@ function parseReadModelField(s: AggFieldSpec, aggregate: string): string {
|
|
|
3316
3414
|
function emitReadModel(
|
|
3317
3415
|
agg: AggregateHandle,
|
|
3318
3416
|
enumDartTypes: Map<string, string>,
|
|
3319
|
-
deriveds:
|
|
3417
|
+
deriveds: AnyDerivedDecl[] = [],
|
|
3320
3418
|
combineds: CombinedDecl[] = [],
|
|
3321
3419
|
refIdClassForAggregate: (aggregateType: string) => string = concreteClassName,
|
|
3322
3420
|
ownIdFields: ReadonlySet<string> = new Set(),
|
|
@@ -3347,21 +3445,32 @@ function emitReadModel(
|
|
|
3347
3445
|
` final NomosRef<${concreteClassName(agg.id)}> ref;`,
|
|
3348
3446
|
...specs.flatMap((s) => {
|
|
3349
3447
|
const doc = s.nullReason !== undefined ? [` /// null semantic: ${s.nullReason}`] : [];
|
|
3448
|
+
const temporalGuidance = s.temporalEnumIds === undefined
|
|
3449
|
+
? []
|
|
3450
|
+
: [
|
|
3451
|
+
` /// Raw captured timestamp. Prefer ${s.temporalEnumIds.map((id) => `[${id}]`).join(" or ")} for age-based business/UI decisions.`,
|
|
3452
|
+
` @Deprecated('Use ${s.temporalEnumIds.join(" or ")} for age/freshness decisions; this raw timestamp is for display and audit.')`,
|
|
3453
|
+
];
|
|
3350
3454
|
// SURFACED MERGE CONFLICTS: pending observations surface typed; resolve = ordinary next write.
|
|
3351
3455
|
if (s.surfaced === true) {
|
|
3352
3456
|
return [
|
|
3457
|
+
...temporalGuidance,
|
|
3353
3458
|
` /// Surfaced merge conflicts: [SurfacedResolved] steady state, [SurfacedPending] under conflict.`,
|
|
3354
3459
|
` final SurfacedValue<${readModelDartType(s)}>? ${s.name};`,
|
|
3355
3460
|
];
|
|
3356
3461
|
}
|
|
3357
3462
|
return s.nullAdmits === true
|
|
3358
|
-
? [...doc, ` final NomosSlot<${stripNullable(readModelDartType(s))}> ${s.name};`]
|
|
3359
|
-
: [` final ${readModelDartType(s)}${readModelNullableSuffix(s)} ${s.name};`];
|
|
3463
|
+
? [...doc, ...temporalGuidance, ` final NomosSlot<${stripNullable(readModelDartType(s))}> ${s.name};`]
|
|
3464
|
+
: [...temporalGuidance, ` final ${readModelDartType(s)}${readModelNullableSuffix(s)} ${s.name};`];
|
|
3465
|
+
}),
|
|
3466
|
+
...deriveds.map((d) => {
|
|
3467
|
+
if ("kind" in d && d.kind === "temporalEnum") {
|
|
3468
|
+
const values = zodEnumValues(d.returns);
|
|
3469
|
+
const dartType = enumDartTypes.get(dartEnumLookupKey(d.returns)) ?? `${dartTypeNameFromField(d.id)}Enum`;
|
|
3470
|
+
return ` /// Native temporal enum read field evaluated at the captured read clock.\n final ${dartType} ${d.id};`;
|
|
3471
|
+
}
|
|
3472
|
+
return ` /// Engine-projected DERIVED read field (pure fn of the folded fields; NOT kernel state).\n final ${projectionReturnDartType(d.returns)} ${d.id};`;
|
|
3360
3473
|
}),
|
|
3361
|
-
...deriveds.map(
|
|
3362
|
-
(d) =>
|
|
3363
|
-
` /// Engine-projected DERIVED read field (pure fn of the folded fields; NOT kernel state).\n final ${projectionReturnDartType(d.returns)} ${d.id};`,
|
|
3364
|
-
),
|
|
3365
3474
|
...combineds.map(
|
|
3366
3475
|
(c) =>
|
|
3367
3476
|
` /// Engine-projected COMBINED read field (JOIN across aggregates; NOT kernel state).\n final ${projectionReturnDartType(c.returns)} ${c.id};`,
|
|
@@ -3380,7 +3489,11 @@ function emitReadModel(
|
|
|
3380
3489
|
: ` required this.${s.name},`,
|
|
3381
3490
|
),
|
|
3382
3491
|
// Derived/combined fields are always nullable (null until the engine pass runs).
|
|
3383
|
-
...deriveds.map((d) =>
|
|
3492
|
+
...deriveds.map((d) =>
|
|
3493
|
+
"kind" in d && d.kind === "temporalEnum"
|
|
3494
|
+
? ` required this.${d.id},`
|
|
3495
|
+
: ` this.${d.id},`
|
|
3496
|
+
),
|
|
3384
3497
|
...combineds.map((c) => ` this.${c.id},`),
|
|
3385
3498
|
].join("\n");
|
|
3386
3499
|
|
|
@@ -3418,7 +3531,14 @@ function emitReadModel(
|
|
|
3418
3531
|
: ` ${s.name}: ${parseReadModelField(s, wireType)},`,
|
|
3419
3532
|
),
|
|
3420
3533
|
// Derived/combined fields decode from the engine-produced bare JSON leaf.
|
|
3421
|
-
...deriveds.map((d) =>
|
|
3534
|
+
...deriveds.map((d) => {
|
|
3535
|
+
if ("kind" in d && d.kind === "temporalEnum") {
|
|
3536
|
+
const values = zodEnumValues(d.returns);
|
|
3537
|
+
const dartType = enumDartTypes.get(dartEnumLookupKey(d.returns)) ?? `${dartTypeNameFromField(d.id)}Enum`;
|
|
3538
|
+
return ` ${d.id}: ${dartType}.fromWire(readStr(data, '${agg.id}', '${d.id}')),`;
|
|
3539
|
+
}
|
|
3540
|
+
return ` ${d.id}: ${parseProjectionReturnField(d.id, d.returns)},`;
|
|
3541
|
+
}),
|
|
3422
3542
|
...combineds.map((c) => ` ${c.id}: ${parseProjectionReturnField(c.id, c.returns)},`),
|
|
3423
3543
|
].join("\n");
|
|
3424
3544
|
|
|
@@ -3470,10 +3590,15 @@ function emitDirective(
|
|
|
3470
3590
|
const foreignReferenceInputs = dartForeignReferenceInputs(d, aggregates);
|
|
3471
3591
|
const foreignReferenceIds = new Set(foreignReferenceInputs.map((input) => input.idField));
|
|
3472
3592
|
const principalField = d.businessPrincipalField;
|
|
3593
|
+
const targetBinding = isBusinessIntent(d) ? d.targetBinding : undefined;
|
|
3594
|
+
const targetType = targetBinding === undefined
|
|
3595
|
+
? undefined
|
|
3596
|
+
: `NomosRef<${concreteClassName(targetBinding.aggregateId)}>`;
|
|
3473
3597
|
const derivedFields = new Set([
|
|
3474
3598
|
...recordInputs.flatMap((input) => [input.idField, input.workspaceField]),
|
|
3475
3599
|
...foreignReferenceInputs.map((input) => input.workspaceField),
|
|
3476
3600
|
...(principalField === undefined ? [] : [principalField]),
|
|
3601
|
+
...(targetBinding === undefined ? [] : [targetBinding.field]),
|
|
3477
3602
|
]);
|
|
3478
3603
|
const publicSpecs = specs.filter((spec) => !derivedFields.has(spec.name));
|
|
3479
3604
|
// Optional first-class relationships are OPTIONAL on the wire but REQUIRED
|
|
@@ -3483,6 +3608,9 @@ function emitDirective(
|
|
|
3483
3608
|
const isOptionalReferenceChoice = (spec: PayloadFieldSpec): boolean =>
|
|
3484
3609
|
spec.optional && spec.toJson === "ref";
|
|
3485
3610
|
const internalPayloadArguments = [
|
|
3611
|
+
...(targetBinding === undefined
|
|
3612
|
+
? []
|
|
3613
|
+
: [`required ${targetType} nmsInternalTarget`]),
|
|
3486
3614
|
...(principalField === undefined ? [] : ["required String nmsInternalPrincipal"]),
|
|
3487
3615
|
...(recordInputs.length === 0
|
|
3488
3616
|
? []
|
|
@@ -3541,6 +3669,9 @@ function emitDirective(
|
|
|
3541
3669
|
s.nullAdmits !== true
|
|
3542
3670
|
)
|
|
3543
3671
|
.map((s) => ` '${s.name}': ${toJsonExpr(s, s.name)},`);
|
|
3672
|
+
const targetEntry = targetBinding === undefined
|
|
3673
|
+
? []
|
|
3674
|
+
: [` '${targetBinding.field}': NmsInternalNomosRefs.id(nmsInternalTarget),`];
|
|
3544
3675
|
const optionalLines = specs
|
|
3545
3676
|
.filter((s) =>
|
|
3546
3677
|
!derivedFields.has(s.name) &&
|
|
@@ -3631,6 +3762,7 @@ function emitDirective(
|
|
|
3631
3762
|
` Map<String, Object?> nmsInternalToPayloadJson(${internalPayloadSignature}) {`,
|
|
3632
3763
|
` final data = <String, Object?>{`,
|
|
3633
3764
|
...requiredEntries,
|
|
3765
|
+
...targetEntry,
|
|
3634
3766
|
...requiredForeignReferenceEntries,
|
|
3635
3767
|
...optionalLines,
|
|
3636
3768
|
...(principalField === undefined ? [] : [` '${principalField}': nmsInternalPrincipal,`]),
|
|
@@ -3649,6 +3781,7 @@ function emitDirective(
|
|
|
3649
3781
|
: [
|
|
3650
3782
|
` Map<String, Object?> nmsInternalToPayloadJson(${internalPayloadSignature}) => <String, Object?>{`,
|
|
3651
3783
|
...requiredEntries,
|
|
3784
|
+
...targetEntry,
|
|
3652
3785
|
...requiredForeignReferenceEntries,
|
|
3653
3786
|
...optionalLines,
|
|
3654
3787
|
...(principalField === undefined ? [] : [` '${principalField}': nmsInternalPrincipal,`]),
|
|
@@ -4662,7 +4795,7 @@ export interface DomainModule {
|
|
|
4662
4795
|
* read engine (`nomos_readmodel`) stores each derived value as an ordinary projected
|
|
4663
4796
|
* field, so a reader decodes it like any other field.
|
|
4664
4797
|
*/
|
|
4665
|
-
deriveds?:
|
|
4798
|
+
deriveds?: AnyDerivedDecl[];
|
|
4666
4799
|
/**
|
|
4667
4800
|
* The domain's ENGINE-PROJECTED COMBINED read fields (read-engine: combined read
|
|
4668
4801
|
* fields) — the JOIN analogue of {@link deriveds}. Each is a PURE fn of ONE owner
|
|
@@ -4881,7 +5014,9 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
4881
5014
|
? [
|
|
4882
5015
|
emitVoHelpers(),
|
|
4883
5016
|
...localValueObjects.flatMap((d) => [
|
|
4884
|
-
...voEnumFields(d)
|
|
5017
|
+
...voEnumFields(d)
|
|
5018
|
+
.filter((e) => e.declaredName === undefined)
|
|
5019
|
+
.map((e) => emitVoEnum(e.owner ?? d.name, e.fieldName, e.values)),
|
|
4885
5020
|
emitValueObjectClass(d),
|
|
4886
5021
|
]),
|
|
4887
5022
|
]
|
|
@@ -4906,10 +5041,12 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
4906
5041
|
const localQueries = (mod.queries ?? []).filter((q) => !skipQueryIds.has(q.id));
|
|
4907
5042
|
// Enums are collected from the LOCALLY-emitted symbols only — a framework directive/aggregate's
|
|
4908
5043
|
// enums ride the shared `nomos_framework.dart`, so a domain that skips them emits no dangling enum.
|
|
4909
|
-
const { dartTypes, decls } =
|
|
5044
|
+
const { dartTypes, decls } = collectDartEnums(
|
|
4910
5045
|
localAggregates,
|
|
4911
5046
|
localDirectives,
|
|
4912
5047
|
domainKey,
|
|
5048
|
+
localValueObjects,
|
|
5049
|
+
mod.deriveds ?? [],
|
|
4913
5050
|
);
|
|
4914
5051
|
const refIdClassForAggregate = refIdClassResolver(mod);
|
|
4915
5052
|
const packageAggregateDomains = opts.packageAggregateDomains ?? {};
|
|
@@ -4965,7 +5102,7 @@ export function generateDartDomain(mod: DomainModule, opts: GenerateDartDomainOp
|
|
|
4965
5102
|
// `…ReadModel` ALSO carries the engine-projected derived fields its type declares (e.g.
|
|
4966
5103
|
// `SupportSessionReadModel.isTerminal`), decoded like a normal projected field from the
|
|
4967
5104
|
// row's `data` (the Rust read engine stores it there as a json-leaf).
|
|
4968
|
-
const derivedsByType = new Map<string,
|
|
5105
|
+
const derivedsByType = new Map<string, AnyDerivedDecl[]>();
|
|
4969
5106
|
for (const d of mod.deriveds ?? []) {
|
|
4970
5107
|
const list = derivedsByType.get(d.of) ?? [];
|
|
4971
5108
|
list.push(d);
|