@githolon/dsl 0.102.3 → 0.102.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@githolon/dsl",
3
- "version": "0.102.3",
3
+ "version": "0.102.4",
4
4
  "type": "module",
5
5
  "description": "Nomos 2 domain-authoring DSL: aggregates + directives in TS, executed and encoded to the Rust kernel's wire shapes.",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -1707,7 +1707,7 @@ function dartTypeOfVoField(f: ValueObjectFieldDescriptor, voName: string, fieldN
1707
1707
  switch (f.type) {
1708
1708
  case "string":
1709
1709
  return f.idOf !== undefined
1710
- ? `NomosRef<${concreteClassName(f.idOf)}>`
1710
+ ? `NomosRef<${f.idOfContract === true ? contractTypeName(f.idOf) : concreteClassName(f.idOf)}>`
1711
1711
  : f.brand ?? "String"; // a brandedId(...) → the generated extension-type wrapper
1712
1712
  case "number":
1713
1713
  // a UNIT-tagged number is the generated zero-cost extension type; `.int()` is Dart int.
@@ -1742,7 +1742,7 @@ function voFromExpr(
1742
1742
  switch (f.type) {
1743
1743
  case "string":
1744
1744
  if (f.idOf !== undefined) {
1745
- const target = concreteClassName(f.idOf);
1745
+ const target = f.idOfContract === true ? contractTypeName(f.idOf) : concreteClassName(f.idOf);
1746
1746
  return `NmsInternalNomosRefs.create<${target}>(workspace: '', type: '${f.idOf}', id: ${expr} as String)`;
1747
1747
  }
1748
1748
  return f.brand !== undefined ? `${f.brand}(${expr} as String)` : `${expr} as String`;
@@ -721,6 +721,11 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
721
721
  const targetFile = owner?.name ?? mod.name;
722
722
  const targetApplication = dartApplicationClassName(targetDomain);
723
723
  const opener = `nomosInternalOpen${targetApplication}`;
724
+ const rootAggregate = aggregates.get(birth.aggregateId);
725
+ const rootType = concreteClassName(birth.aggregateId);
726
+ const rootCollection = member(plural(rootAggregate === undefined
727
+ ? birth.aggregateId
728
+ : concreteClassName(rootAggregate.id)));
724
729
  if (targetFile !== mod.name) {
725
730
  addSibling(`${targetFile}.application.dart`, targetApplication);
726
731
  addSibling(`${targetFile}.application.dart`, opener);
@@ -735,7 +740,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
735
740
  ` /// or call a lower-level client.`,
736
741
  ` Future<${targetApplication}> ${method}(${dartActionInputClassName(directive)} facts) async {`,
737
742
  ` final reference = await _client.${method}(facts);`,
738
- ` return ${opener}(_client.bridge, reference, _client.actor);`,
743
+ opts.supportImport === "package"
744
+ ? ` return ${rootCollection}._nmsOpenReference(reference);`
745
+ : ` return ${opener}(_client.bridge, reference, _client.actor);`,
739
746
  ` }`,
740
747
  ``,
741
748
  );
@@ -744,31 +751,47 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
744
751
  ` /// Give one ${birth.aggregateId} its own durable life.`,
745
752
  ` Future<${targetApplication}> birth(Create${cap(camel(birth.aggregateId))}Input facts) async {`,
746
753
  ` final reference = await _client.birth(facts);`,
747
- ` return ${opener}(_client.bridge, reference, _client.actor);`,
754
+ opts.supportImport === "package"
755
+ ? ` return _nmsOpenReference(reference);`
756
+ : ` return ${opener}(_client.bridge, reference, _client.actor);`,
748
757
  ` }`,
749
758
  ``,
750
759
  ]);
751
760
  }
752
761
  if (opts.supportImport === "package") {
753
762
  usesWorkspaceName = true;
754
- const rootAggregate = aggregates.get(birth.aggregateId);
755
763
  const rootModel = rootAggregate === undefined
756
764
  ? `${concreteClassName(birth.aggregateId)}ReadModel`
757
765
  : readModelClassName(rootAggregate.id);
758
- const rootType = concreteClassName(birth.aggregateId);
759
- const rootCollection = member(plural(rootAggregate === undefined
760
- ? birth.aggregateId
761
- : concreteClassName(rootAggregate.id)));
762
766
  addCollectionMember(birth.aggregateId, "open", [
767
+ ` final Map<String, Future<${targetApplication}>> _nmsApplications = {};`,
768
+ ``,
769
+ ` Future<${targetApplication}> _nmsOpenReference(NomosRef<${rootType}> reference) {`,
770
+ ` final workspace = NmsInternalNomosRefs.workspace(reference);`,
771
+ ` final existing = _nmsApplications[workspace];`,
772
+ ` if (existing != null) return existing;`,
773
+ ` late final Future<${targetApplication}> opened;`,
774
+ ` opened = (() async {`,
775
+ ` try {`,
776
+ ` return await ${opener}(_client.bridge, reference, _client.actor);`,
777
+ ` } catch (_) {`,
778
+ ` if (identical(_nmsApplications[workspace], opened)) {`,
779
+ ` _nmsApplications.remove(workspace);`,
780
+ ` }`,
781
+ ` rethrow;`,
782
+ ` }`,
783
+ ` })();`,
784
+ ` _nmsApplications[workspace] = opened;`,
785
+ ` return opened;`,
786
+ ` }`,
787
+ ``,
763
788
  ` /// Open the ${birth.aggregateId} whose ${birth.keyField} is [key].`,
764
- ` Future<${targetApplication}> open(String key) => ${opener}(`,
765
- ` _client.bridge,`,
789
+ ` Future<${targetApplication}> open(String key) => _nmsOpenReference(`,
766
790
  ` NmsInternalNomosRefs.create<${rootType}>(`,
767
791
  ` workspace: deriveWorkspaceName(${JSON.stringify(birth.namespace)}, key),`,
768
792
  ` type: ${JSON.stringify(birth.aggregateId)},`,
769
793
  ` id: ${JSON.stringify(birth.rootId)},`,
770
794
  ` ),`,
771
- ` _client.actor,`,
772
795
  ` );`,
773
796
  ``,
774
797
  ]);
@@ -790,14 +813,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
790
813
  ` if (raw is! Map) continue;`,
791
814
  ` final child = (raw['child'] ?? '').toString();`,
792
815
  ` if (!child.startsWith(${JSON.stringify(`${birth.namespace}-`)})) continue;`,
793
- ` final application = await ${opener}(`,
794
- ` _client.bridge,`,
795
- ` NmsInternalNomosRefs.create<${rootType}>(workspace: child, type: ${JSON.stringify(birth.aggregateId)}, id: ${JSON.stringify(birth.rootId)}),`,
796
- ` _client.actor,`,
797
- ` );`,
798
- ` final record = await application.${rootCollection}.find(`,
799
- ` NmsInternalNomosRefs.create<${rootType}>(workspace: child, type: ${JSON.stringify(birth.aggregateId)}, id: ${JSON.stringify(birth.rootId)}),`,
800
- ` );`,
816
+ ` final reference = NmsInternalNomosRefs.create<${rootType}>(workspace: child, type: ${JSON.stringify(birth.aggregateId)}, id: ${JSON.stringify(birth.rootId)});`,
817
+ ` final application = await _nmsOpenReference(reference);`,
818
+ ` final record = await application.${rootCollection}.find(reference);`,
801
819
  ` if (record != null) records.add(record);`,
802
820
  ` }`,
803
821
  ` return records;`,
@@ -833,14 +851,9 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
833
851
  ` object != 'workspace:self' ||`,
834
852
  ` prunedAt.isNotEmpty ||`,
835
853
  ` !seen.add(workspace)) continue;`,
836
- ` final application = await ${opener}(`,
837
- ` _client.bridge,`,
838
- ` NmsInternalNomosRefs.create<${rootType}>(workspace: workspace, type: ${JSON.stringify(birth.aggregateId)}, id: ${JSON.stringify(birth.rootId)}),`,
839
- ` _client.actor,`,
840
- ` );`,
841
- ` final record = await application.${rootCollection}.find(`,
842
- ` NmsInternalNomosRefs.create<${rootType}>(workspace: workspace, type: ${JSON.stringify(birth.aggregateId)}, id: ${JSON.stringify(birth.rootId)}),`,
843
- ` );`,
854
+ ` final reference = NmsInternalNomosRefs.create<${rootType}>(workspace: workspace, type: ${JSON.stringify(birth.aggregateId)}, id: ${JSON.stringify(birth.rootId)});`,
855
+ ` final application = await _nmsOpenReference(reference);`,
856
+ ` final record = await application.${rootCollection}.find(reference);`,
844
857
  ` if (record != null) records.add(record);`,
845
858
  ` }`,
846
859
  ` return records;`,
@@ -855,10 +868,8 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
855
868
  ` /// Continue working in the business application that produced [record].`,
856
869
  ` Future<${targetApplication}> applicationOf(${rootModel} record) {`,
857
870
  ` final origin = NmsInternalRecordOrigins.require(record);`,
858
- ` return ${opener}(`,
859
- ` _client.bridge,`,
871
+ ` return _nmsOpenReference(`,
860
872
  ` NmsInternalNomosRefs.create<${rootType}>(workspace: origin.workspace, type: ${JSON.stringify(birth.aggregateId)}, id: origin.id),`,
861
- ` _client.actor,`,
862
873
  ` );`,
863
874
  ` }`,
864
875
  ``,
@@ -1028,6 +1039,55 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1028
1039
  (candidate as { businessBirthInternal?: unknown }).businessBirthInternal !== true,
1029
1040
  )) {
1030
1041
  const sourceModel = readModelClassName(aggregate.id);
1042
+ for (const contractId of aggregate.implementsContracts ?? []) {
1043
+ const contract = contractTypeName(contractId);
1044
+ const method = member(`as${contract}`);
1045
+ addCollectionMember(aggregate.id, method, [
1046
+ ` /// Use [record] wherever another domain declares [${contract}].`,
1047
+ ` ///`,
1048
+ ` /// Nomos preserves the record's workspace-qualified identity while exposing only`,
1049
+ ` /// the contract that this aggregate explicitly implements. This does not grant access.`,
1050
+ ` NomosRef<${contract}> ${method}(${sourceModel} record) {`,
1051
+ ` final reference = record.ref;`,
1052
+ ` _nmsRequireReference(reference, ${JSON.stringify(aggregate.id)}, '');`,
1053
+ ` return NmsInternalNomosRefs.create<${contract}>(`,
1054
+ ` workspace: reference.workspace,`,
1055
+ ` type: ${JSON.stringify(contractId)},`,
1056
+ ` id: reference.id,`,
1057
+ ` );`,
1058
+ ` }`,
1059
+ ``,
1060
+ ]);
1061
+ const resolveMethod = member(`resolve${contract}`);
1062
+ addCollectionMember(aggregate.id, resolveMethod, [
1063
+ ` /// Resolve [reference] as the concrete ${sourceModel} that implements [${contract}].`,
1064
+ ` ///`,
1065
+ ` /// Use this after following a generated cross-application reference and opening`,
1066
+ ` /// its business application. Nomos checks the declared contract and access on read.`,
1067
+ ` Future<${sourceModel}?> ${resolveMethod}(ForeignRef<${contract}> reference) {`,
1068
+ ` _nmsRequireReference(reference.ref, ${JSON.stringify(contractId)}, '');`,
1069
+ ` return find(NmsInternalNomosRefs.create<${concreteClassName(aggregate.id)}>(`,
1070
+ ` workspace: reference.workspace,`,
1071
+ ` type: ${JSON.stringify(aggregate.id)},`,
1072
+ ` id: reference.id,`,
1073
+ ` ));`,
1074
+ ` }`,
1075
+ ``,
1076
+ ]);
1077
+ const watchMethod = member(`watch${contract}`);
1078
+ addCollectionMember(aggregate.id, watchMethod, [
1079
+ ` /// React when the concrete record behind [reference] changes.`,
1080
+ ` Stream<${sourceModel}?> ${watchMethod}(ForeignRef<${contract}> reference) {`,
1081
+ ` _nmsRequireReference(reference.ref, ${JSON.stringify(contractId)}, '');`,
1082
+ ` return watchOne(NmsInternalNomosRefs.create<${concreteClassName(aggregate.id)}>(`,
1083
+ ` workspace: reference.workspace,`,
1084
+ ` type: ${JSON.stringify(aggregate.id)},`,
1085
+ ` id: reference.id,`,
1086
+ ` ));`,
1087
+ ` }`,
1088
+ ``,
1089
+ ]);
1090
+ }
1031
1091
  const sameDomainRefs = Object.entries(aggregate.fields).filter(([, field]) =>
1032
1092
  field.kind === "ref" &&
1033
1093
  field.foreignRef === undefined &&
@@ -1301,7 +1361,7 @@ export function generateDartApplication(mod: DomainModule, opts: DartApplication
1301
1361
  `/// a repository or parallel application state.`,
1302
1362
  `final class ${collection} {`,
1303
1363
  ` final ${client} _client;`,
1304
- ` const ${collection}._(this._client);`,
1364
+ ` ${collection}._(this._client);`,
1305
1365
  ``,
1306
1366
  ...members,
1307
1367
  `}`,
package/src/codegen_ts.ts CHANGED
@@ -598,7 +598,7 @@ function tsTypeOfVoField(f: ValueObjectFieldDescriptor): string {
598
598
  switch (f.type) {
599
599
  case "string":
600
600
  return f.idOf !== undefined
601
- ? `NomosRef<${f.idOf}>`
601
+ ? `NomosRef<${f.idOfContract === true ? contractTypeName(f.idOf) : f.idOf}>`
602
602
  : f.brand ?? "string"; // a brandedId(...) → the emitted branded alias
603
603
  case "number":
604
604
  return "number"; // `.int()`/units are Dart-side nominal products; TS keeps one number
package/src/fields.ts CHANGED
@@ -171,7 +171,14 @@ export type ValueObjectFieldDescriptor =
171
171
  /** A plain string, OR a BRANDED nominal string when `brand` is present (a `brandedId(...)`
172
172
  * — the generated TS branded alias / Dart extension-type wrapper's name). Wire-identical
173
173
  * to a plain string; the brand is a compile-time nominal tag only. */
174
- | { readonly type: "string"; readonly brand?: string; readonly idOf?: string; readonly optional?: true }
174
+ | {
175
+ readonly type: "string";
176
+ readonly brand?: string;
177
+ readonly idOf?: string;
178
+ /** Present only for an `idOf(recordContract(...))` target. Aggregate is the default. */
179
+ readonly idOfContract?: true;
180
+ readonly optional?: true;
181
+ }
175
182
  /** `int: true` when the zod carried `.int()` (Dart `int`, TS `number`); `unit` when the field
176
183
  * was declared through `unit(...)` — the generated Dart extension-type wrapper's name. */
177
184
  | { readonly type: "number"; readonly int?: true; readonly unit?: string; readonly optional?: true }
@@ -396,12 +396,17 @@ function describeZod(
396
396
  switch (zodKind(schema)) {
397
397
  case "string": {
398
398
  const brandedBrand = (schema as unknown as Record<symbol, unknown>)[NOMOS_BRANDED_ID_BRAND];
399
- const idOfAgg = (schema as unknown as Record<symbol, unknown>)[NOMOS_IDOF_BRAND];
399
+ const idOfTarget = idOfMetaOf(schema);
400
400
  // idOf(Agg) REFERENCES the aggregate's canonical `NomosRef<Agg>`. Carry `idOf` in the stable
401
401
  // descriptor so every emitter can recover the aggregate type while the wire remains a string.
402
402
  // A standalone `brandedId("X")` remains an unrelated nominal string and declares its own wrapper.
403
- if (typeof idOfAgg === "string") {
404
- return { type: "string", brand: `${idOfAgg}Id`, idOf: idOfAgg };
403
+ if (idOfTarget !== undefined) {
404
+ return {
405
+ type: "string",
406
+ brand: `${idOfTarget}Id`,
407
+ idOf: idOfTarget,
408
+ ...(idOfTargetKindOf(schema) === "contract" ? { idOfContract: true as const } : {}),
409
+ };
405
410
  }
406
411
  return typeof brandedBrand === "string" ? { type: "string", brand: brandedBrand } : { type: "string" };
407
412
  }