@githolon/dsl 0.58.0 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dart/CHANGELOG.md +7 -0
- package/dart/lib/nomos_client.dart +6 -14
- package/dart/lib/src/provider.dart +1 -1
- package/dart/lib/src/webview_bridge.dart +7 -181
- package/dart/lib/testing.dart +13 -0
- package/dart/pubspec.lock +7 -0
- package/dart/pubspec.yaml +5 -1
- package/dart/pubspec_overrides.yaml +7 -0
- package/dart_types/CHANGELOG.md +6 -0
- package/dart_types/lib/nomos_types.dart +29 -0
- package/dart_types/lib/src/error_info.dart +184 -0
- package/{dart → dart_types}/lib/src/errors.dart +1 -1
- package/dart_types/pubspec.lock +421 -0
- package/dart_types/pubspec.yaml +23 -0
- package/package.json +4 -1
- package/src/codegen_dart.ts +1 -1
- package/src/codegen_dart_client.ts +14 -4
- package/src/codegen_proof.ts +15 -0
- package/src/compile_package_main.ts +531 -110
- package/src/law_check.ts +6 -0
- /package/{dart → dart_types}/lib/src/aggregate_meta.dart +0 -0
- /package/{dart → dart_types}/lib/src/driver.dart +0 -0
- /package/{dart → dart_types}/lib/src/null_slot.dart +0 -0
- /package/{dart → dart_types}/lib/src/offer.dart +0 -0
- /package/{dart → dart_types}/lib/src/schema_validation.dart +0 -0
- /package/{dart → dart_types}/lib/src/subscriptions.dart +0 -0
- /package/{dart → dart_types}/lib/src/wire.dart +0 -0
|
@@ -201,6 +201,35 @@ interface PackageConfig {
|
|
|
201
201
|
* config `name`.
|
|
202
202
|
*/
|
|
203
203
|
readonly barrel?: string;
|
|
204
|
+
/**
|
|
205
|
+
* LAYERED PACKAGES (blast-radius isolation for Flutter frontends). When set, the emit
|
|
206
|
+
* produces THREE sibling pub packages under `out` instead of one flat tree:
|
|
207
|
+
* • `types` — pure data (payload classes, read models, VOs/enums, typed accessors over
|
|
208
|
+
* the injected NomosReads seams, the aggregate-meta registry). Depends ONLY on the
|
|
209
|
+
* pure `nomos_types` core — frontend-safe: no bridge, no sessions, no lifecycle.
|
|
210
|
+
* • `client` — the effectful runtime clients (`<name>.client.dart`, offer methods over
|
|
211
|
+
* NomosBridge). Depends on the types package + `nomos_client`.
|
|
212
|
+
* • `test` — optional; the headless-harness surface for widget/integration tests.
|
|
213
|
+
* The layering line: UI code depends on `types`; composition/runtime code on `client`;
|
|
214
|
+
* generated TYPES may flow to the UI, generated CLIENTS may not.
|
|
215
|
+
* Each package's `pubspec.yaml` is scaffolded ONLY IF MISSING (tenant-owned versioning);
|
|
216
|
+
* barrels + `lib/src/generated/` are generated-owned and rewritten every compile.
|
|
217
|
+
* Requires `support: "package"` (vendored mode contradicts pub-package layering).
|
|
218
|
+
*/
|
|
219
|
+
readonly packages?: {
|
|
220
|
+
readonly types: string;
|
|
221
|
+
readonly client: string;
|
|
222
|
+
readonly test?: string;
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* PUBLIC ALIASES for generated type names (`{ "EstateEstateCustomFieldDefinitionFieldType":
|
|
226
|
+
* "FieldType", … }`). Each entry emits `typedef <alias> = <generated>;` in the TYPES package
|
|
227
|
+
* barrel, so apps import humane names while the generated identity stays stable. Validated
|
|
228
|
+
* fail-closed: the generated name must exist in the emitted types surface, and an alias may
|
|
229
|
+
* not collide with any generated public name or another alias. (Works in the single-package
|
|
230
|
+
* emit too — aliases land in the one package barrel.)
|
|
231
|
+
*/
|
|
232
|
+
readonly names?: Readonly<Record<string, string>>;
|
|
204
233
|
};
|
|
205
234
|
/**
|
|
206
235
|
* OPT-IN layered USD emission (also `--layered` on the CLI). When true, ALSO emit
|
|
@@ -373,40 +402,148 @@ const DART_RELATIVE_IMPORT = `import 'nomos_client.dart';`;
|
|
|
373
402
|
* vendored: nothing the codegen emits references it, and it would drag the external
|
|
374
403
|
* `json_schema` pub dependency into every vendoring app.
|
|
375
404
|
*/
|
|
376
|
-
|
|
405
|
+
// The PURE CORE support files, vendored from the `nomos_types` package (dsl/dart_types/lib/src —
|
|
406
|
+
// the layered split: wire types, offer codec, NomosReads, aggregate meta, error info). Zero
|
|
407
|
+
// external pub deps. `schema_validation.dart` is deliberately NOT vendored: nothing the codegen
|
|
408
|
+
// emits references it, and it would drag the external `json_schema` pub dependency into every
|
|
409
|
+
// vendoring app.
|
|
410
|
+
const DART_SUPPORT_CORE_SRC = [
|
|
377
411
|
"wire.dart",
|
|
378
412
|
"driver.dart",
|
|
379
413
|
"offer.dart",
|
|
380
414
|
"subscriptions.dart",
|
|
381
|
-
"provider.dart",
|
|
382
415
|
// The tri-state slot (NomosSlot<T>) for `nullable`/`clearable` fields (null_semantics.md) — the
|
|
383
416
|
// barrel re-exports it and generated read-model/payload classes reference it, so it must vendor.
|
|
384
|
-
// Zero external pub deps (pure Dart).
|
|
385
417
|
"null_slot.dart",
|
|
386
418
|
// Runtime aggregate-meta support types (NomosAggregateMeta/NomosFieldMeta) — the generated
|
|
387
419
|
// per-domain registry consts reference them through the barrel.
|
|
388
420
|
"aggregate_meta.dart",
|
|
389
|
-
// The
|
|
390
|
-
//
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
// bridge AND the generated `<name>.client.dart` reference these through the barrel,
|
|
394
|
-
// so skipping it left `dart analyze` red on every vendored output (0.34.1). No
|
|
395
|
-
// external pub deps.
|
|
421
|
+
// The data-only error surface (NomosErrorInfo/NomosBridgeError) + the typed diagnostics
|
|
422
|
+
// (nomosErrorFromInfo / MissingOfferAuthority / …) — the vendored bridge AND the generated
|
|
423
|
+
// `<name>.client.dart` reference these through the barrel. No external pub deps.
|
|
424
|
+
"error_info.dart",
|
|
396
425
|
"errors.dart",
|
|
397
426
|
] as const;
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
const
|
|
427
|
+
// The RUNTIME support files, vendored from `nomos_client` (dsl/dart/lib/src): the async WebView
|
|
428
|
+
// bridge the generated `<name>.client.dart` drives, and the provider surface. Their
|
|
429
|
+
// `package:nomos_types/...` imports are rewritten to the local vendored shim (see emitDart).
|
|
430
|
+
// (`home.dart` is NOT vendored — the home-onboarding runtime drags `package:crypto`; nothing the
|
|
431
|
+
// codegen emits references it.)
|
|
432
|
+
const DART_SUPPORT_RUNTIME_SRC = ["provider.dart", "webview_bridge.dart"] as const;
|
|
433
|
+
const DART_SUPPORT_SRC = [...DART_SUPPORT_CORE_SRC, ...DART_SUPPORT_RUNTIME_SRC] as const;
|
|
434
|
+
/** The local shim replacing `package:nomos_types/nomos_types.dart` inside vendored runtime files. */
|
|
435
|
+
const VENDORED_TYPES_SHIM = "nomos_types.dart";
|
|
404
436
|
|
|
405
437
|
/** The generated-owned subdirectory, relative to `dartOut` (the pub package's `lib/`). WIPED and
|
|
406
438
|
* rewritten on every compile — nothing hand-written may live in it. The stable facade (the package
|
|
407
439
|
* barrel) stays at `dartOut/<barrel>.dart`, so a prune/regenerate can never orphan app imports. */
|
|
408
440
|
const DART_GENERATED_SUBDIR = path.join("src", "generated");
|
|
409
441
|
|
|
442
|
+
/** The shared basename of the framework-dedup file emitted once per package. */
|
|
443
|
+
const FRAMEWORK_FILE = "nomos_framework";
|
|
444
|
+
|
|
445
|
+
/** FRAMEWORK-SHARED symbol dedup, shared by the flat emit and the layered-packages emit (Dart-codegen
|
|
446
|
+
* hygiene): framework-owned symbols fold into MANY domains, so their generated Dart would be DEFINED
|
|
447
|
+
* once per domain — a package barrel re-exporting ≥2 domains then COLLIDES. THREE kinds are deduped,
|
|
448
|
+
* each identified by the framework BRAND (object identity, so a tenant symbol that merely shares a
|
|
449
|
+
* name is never misclassified) and emitted EXACTLY ONCE into `nomos_framework.dart`:
|
|
450
|
+
* • AGGREGATES — read-model class, typed-id class, by-id accessors;
|
|
451
|
+
* • DIRECTIVES — the generated payload class (the domain still OFFERS them per-domain);
|
|
452
|
+
* • QUERIES — the top-level `watch<Q>`/`read<Q>` accessors.
|
|
453
|
+
* Plus VALUE OBJECTS used by ≥2 domains (same collision rule). Per-domain files SKIP each and
|
|
454
|
+
* IMPORT the shared file; clients RE-EXPORT it (Dart de-dups the same-library re-export). */
|
|
455
|
+
interface DartSharedSymbols {
|
|
456
|
+
readonly frameworkAggIds: ReadonlySet<string>;
|
|
457
|
+
readonly frameworkDirIds: ReadonlySet<string>;
|
|
458
|
+
readonly frameworkQueryIds: ReadonlySet<string>;
|
|
459
|
+
readonly sharedVoNames: ReadonlySet<string>;
|
|
460
|
+
readonly hasFrameworkSymbols: boolean;
|
|
461
|
+
/** The synthetic module whose emit IS `nomos_framework.dart` — undefined when nothing is shared. */
|
|
462
|
+
readonly frameworkMod: DomainModule | undefined;
|
|
463
|
+
}
|
|
464
|
+
function collectDartSharedSymbols(modules: readonly DomainModule[]): DartSharedSymbols {
|
|
465
|
+
type ModDirective = DomainModule["directives"][number];
|
|
466
|
+
type ModQuery = NonNullable<DomainModule["queries"]>[number];
|
|
467
|
+
const frameworkAggById = new Map<string, AggregateHandle>();
|
|
468
|
+
const frameworkDirById = new Map<string, ModDirective>();
|
|
469
|
+
const frameworkQueryById = new Map<string, ModQuery>();
|
|
470
|
+
for (const mod of modules) {
|
|
471
|
+
for (const a of mod.aggregates) if (isFrameworkAggregate(a)) frameworkAggById.set(a.id, a);
|
|
472
|
+
for (const d of mod.directives) if (isFrameworkDirective(d)) frameworkDirById.set(d.id, d);
|
|
473
|
+
for (const q of mod.queries ?? []) if (isFrameworkQuery(q)) frameworkQueryById.set(q.id, q);
|
|
474
|
+
}
|
|
475
|
+
const frameworkAggs = [...frameworkAggById.values()];
|
|
476
|
+
const frameworkDirs = [...frameworkDirById.values()];
|
|
477
|
+
const frameworkQueries = [...frameworkQueryById.values()];
|
|
478
|
+
const voDomainsByName = new Map<string, number>();
|
|
479
|
+
const voDescriptorByName = new Map<string, NonNullable<DomainModule["valueObjects"]>[number]>();
|
|
480
|
+
for (const mod of modules) {
|
|
481
|
+
for (const d of mod.valueObjects ?? []) {
|
|
482
|
+
voDomainsByName.set(d.name, (voDomainsByName.get(d.name) ?? 0) + 1);
|
|
483
|
+
voDescriptorByName.set(d.name, d);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const sharedVoNames: ReadonlySet<string> = new Set(
|
|
487
|
+
[...voDomainsByName.entries()].filter(([, n]) => n > 1).map(([name]) => name),
|
|
488
|
+
);
|
|
489
|
+
const sharedVos = [...sharedVoNames].sort().map((name) => voDescriptorByName.get(name)!);
|
|
490
|
+
const hasFrameworkSymbols =
|
|
491
|
+
frameworkAggs.length > 0 || frameworkDirs.length > 0 || frameworkQueries.length > 0 || sharedVos.length > 0;
|
|
492
|
+
const frameworkMod: DomainModule | undefined = hasFrameworkSymbols
|
|
493
|
+
? {
|
|
494
|
+
name: FRAMEWORK_FILE,
|
|
495
|
+
domain: FRAMEWORK_FILE,
|
|
496
|
+
aggregates: frameworkAggs,
|
|
497
|
+
directives: frameworkDirs,
|
|
498
|
+
readModelAggregates: frameworkAggs.map((a) => a.id),
|
|
499
|
+
...(frameworkQueries.length > 0 ? { queries: frameworkQueries } : {}),
|
|
500
|
+
// Package-SHARED value objects (used by ≥2 domains) — their class emits here, once.
|
|
501
|
+
...(sharedVos.length > 0 ? { valueObjects: sharedVos } : {}),
|
|
502
|
+
}
|
|
503
|
+
: undefined;
|
|
504
|
+
return {
|
|
505
|
+
frameworkAggIds: new Set(frameworkAggById.keys()),
|
|
506
|
+
frameworkDirIds: new Set(frameworkDirById.keys()),
|
|
507
|
+
frameworkQueryIds: new Set(frameworkQueryById.keys()),
|
|
508
|
+
sharedVoNames,
|
|
509
|
+
hasFrameworkSymbols,
|
|
510
|
+
frameworkMod,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/** Per-domain skip/import options against the shared-symbol dedup — the SAME derivation the flat
|
|
515
|
+
* emit and the layered-packages emit both apply before generateDartDomain / the bridge client. */
|
|
516
|
+
function domainDedupOpts(mod: DomainModule, shared: DartSharedSymbols): {
|
|
517
|
+
readonly modForDart: DomainModule;
|
|
518
|
+
readonly skips: {
|
|
519
|
+
readonly skipAggregateIds: Set<string>;
|
|
520
|
+
readonly skipDirectiveIds: Set<string>;
|
|
521
|
+
readonly skipQueryIds: Set<string>;
|
|
522
|
+
readonly skipValueObjectNames: Set<string>;
|
|
523
|
+
};
|
|
524
|
+
readonly foldsFramework: boolean;
|
|
525
|
+
} {
|
|
526
|
+
const modForDart: DomainModule =
|
|
527
|
+
mod.readModelAggregates === undefined
|
|
528
|
+
? { ...mod, readModelAggregates: mod.aggregates.map((a) => a.id) }
|
|
529
|
+
: mod;
|
|
530
|
+
const skipAggregateIds = new Set(
|
|
531
|
+
modForDart.aggregates.filter((a) => shared.frameworkAggIds.has(a.id)).map((a) => a.id),
|
|
532
|
+
);
|
|
533
|
+
const skipDirectiveIds = new Set(
|
|
534
|
+
modForDart.directives.filter((d) => shared.frameworkDirIds.has(d.id)).map((d) => d.id),
|
|
535
|
+
);
|
|
536
|
+
const skipQueryIds = new Set(
|
|
537
|
+
(modForDart.queries ?? []).filter((q) => shared.frameworkQueryIds.has(q.id)).map((q) => q.id),
|
|
538
|
+
);
|
|
539
|
+
const skipValueObjectNames = new Set(
|
|
540
|
+
(modForDart.valueObjects ?? []).filter((d) => shared.sharedVoNames.has(d.name)).map((d) => d.name),
|
|
541
|
+
);
|
|
542
|
+
const foldsFramework =
|
|
543
|
+
skipAggregateIds.size > 0 || skipDirectiveIds.size > 0 || skipQueryIds.size > 0 || skipValueObjectNames.size > 0;
|
|
544
|
+
return { modForDart, skips: { skipAggregateIds, skipDirectiveIds, skipQueryIds, skipValueObjectNames }, foldsFramework };
|
|
545
|
+
}
|
|
546
|
+
|
|
410
547
|
/** Remove the PRE-barrel flat layout's generated files from `dartOut` root (they now live under
|
|
411
548
|
* `src/generated/`): the vendored support set + the per-domain files. Targeted by exact known
|
|
412
549
|
* name — `dartOut` is typically the tenant package's `lib/`, so never sweep directories. */
|
|
@@ -438,7 +575,9 @@ function emitDart(
|
|
|
438
575
|
supportImport: DartSupportImport = "vendored",
|
|
439
576
|
barrelName = "nomos_domains",
|
|
440
577
|
compat?: NomosCompat,
|
|
578
|
+
names?: Readonly<Record<string, string>>,
|
|
441
579
|
): string[] {
|
|
580
|
+
const domainSources: string[] = []; // for flat-mode alias validation (dart.names)
|
|
442
581
|
const genDir = path.join(dartOut, DART_GENERATED_SUBDIR);
|
|
443
582
|
removeLegacyFlatDartLayout(dartOut, modules.map((m) => m.name));
|
|
444
583
|
rmSync(genDir, { recursive: true, force: true });
|
|
@@ -448,15 +587,45 @@ function emitDart(
|
|
|
448
587
|
// the barrel's schema_validation export (see DART_SUPPORT_SRC).
|
|
449
588
|
if (supportImport === "vendored") {
|
|
450
589
|
mkdirSync(path.join(genDir, "src"), { recursive: true });
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
for (const f of
|
|
458
|
-
|
|
590
|
+
// Core files come from the pure `nomos_types` package, runtime files from `nomos_client`
|
|
591
|
+
// (the layered split). Runtime files import the core via `package:nomos_types/...` — the
|
|
592
|
+
// vendored copy rewrites that to the local shim so the directory stays self-contained.
|
|
593
|
+
const coreDir = path.join(DSL_DIR, "dart_types", "lib", "src");
|
|
594
|
+
const clientDir = path.join(DSL_DIR, "dart", "lib", "src");
|
|
595
|
+
for (const f of DART_SUPPORT_CORE_SRC) copyFileSync(path.join(coreDir, f), path.join(genDir, "src", f));
|
|
596
|
+
for (const f of DART_SUPPORT_RUNTIME_SRC) {
|
|
597
|
+
const src = readFileSync(path.join(clientDir, f), "utf8").replace(
|
|
598
|
+
/import 'package:nomos_types\/nomos_types\.dart'/g,
|
|
599
|
+
`import '${VENDORED_TYPES_SHIM}'`,
|
|
600
|
+
);
|
|
601
|
+
writeFileSync(path.join(genDir, "src", f), src, "utf8");
|
|
459
602
|
}
|
|
603
|
+
// The local shim the rewritten runtime imports resolve through (the vendored twin of the
|
|
604
|
+
// nomos_types barrel — core files only).
|
|
605
|
+
writeFileSync(
|
|
606
|
+
path.join(genDir, "src", VENDORED_TYPES_SHIM),
|
|
607
|
+
[
|
|
608
|
+
"// GENERATED by nomos-compile — the vendored twin of the nomos_types barrel. DO NOT EDIT.",
|
|
609
|
+
"library;",
|
|
610
|
+
"",
|
|
611
|
+
...DART_SUPPORT_CORE_SRC.map((f) => `export '${f}';`),
|
|
612
|
+
"",
|
|
613
|
+
].join("\n"),
|
|
614
|
+
"utf8",
|
|
615
|
+
);
|
|
616
|
+
// The vendored support barrel — explicit (never derived from the real nomos_client barrel,
|
|
617
|
+
// which post-split re-exports the nomos_types PACKAGE the vendored tree must not depend on).
|
|
618
|
+
writeFileSync(
|
|
619
|
+
path.join(genDir, "nomos_client.dart"),
|
|
620
|
+
[
|
|
621
|
+
"// GENERATED by nomos-compile — the vendored nomos_client support barrel. DO NOT EDIT.",
|
|
622
|
+
"library;",
|
|
623
|
+
"",
|
|
624
|
+
...DART_SUPPORT_SRC.map((f) => `export 'src/${f}';`),
|
|
625
|
+
"",
|
|
626
|
+
].join("\n"),
|
|
627
|
+
"utf8",
|
|
628
|
+
);
|
|
460
629
|
}
|
|
461
630
|
// (package-support mode vendors nothing — the genDir wipe above already cleared any prior set)
|
|
462
631
|
|
|
@@ -473,42 +642,8 @@ function emitDart(
|
|
|
473
642
|
// • QUERIES — the top-level `watch<Q>`/`read<Q>` accessors.
|
|
474
643
|
// Per-domain files SKIP each and IMPORT the shared file; clients RE-EXPORT it (Dart de-dups the
|
|
475
644
|
// same-library re-export). So a barrel over all domains needs NO `hide` clause.
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
const frameworkAggById = new Map<string, AggregateHandle>();
|
|
479
|
-
const frameworkDirById = new Map<string, ModDirective>();
|
|
480
|
-
const frameworkQueryById = new Map<string, ModQuery>();
|
|
481
|
-
for (const mod of modules) {
|
|
482
|
-
for (const a of mod.aggregates) if (isFrameworkAggregate(a)) frameworkAggById.set(a.id, a);
|
|
483
|
-
for (const d of mod.directives) if (isFrameworkDirective(d)) frameworkDirById.set(d.id, d);
|
|
484
|
-
for (const q of mod.queries ?? []) if (isFrameworkQuery(q)) frameworkQueryById.set(q.id, q);
|
|
485
|
-
}
|
|
486
|
-
const frameworkAggs = [...frameworkAggById.values()];
|
|
487
|
-
const frameworkDirs = [...frameworkDirById.values()];
|
|
488
|
-
const frameworkQueries = [...frameworkQueryById.values()];
|
|
489
|
-
const frameworkAggIds: ReadonlySet<string> = new Set(frameworkAggById.keys());
|
|
490
|
-
const frameworkDirIds: ReadonlySet<string> = new Set(frameworkDirById.keys());
|
|
491
|
-
const frameworkQueryIds: ReadonlySet<string> = new Set(frameworkQueryById.keys());
|
|
492
|
-
// VALUE OBJECTS shared by ≥2 domains: their generated Dart class emits ONCE in the shared
|
|
493
|
-
// `nomos_framework.dart` (the same collision rule as framework symbols — a barrel re-exporting
|
|
494
|
-
// two domains must not define one VO class twice). A single-domain VO stays in its domain file.
|
|
495
|
-
const voDomainsByName = new Map<string, number>();
|
|
496
|
-
const voDescriptorByName = new Map<string, NonNullable<DomainModule["valueObjects"]>[number]>();
|
|
497
|
-
for (const mod of modules) {
|
|
498
|
-
for (const d of mod.valueObjects ?? []) {
|
|
499
|
-
voDomainsByName.set(d.name, (voDomainsByName.get(d.name) ?? 0) + 1);
|
|
500
|
-
voDescriptorByName.set(d.name, d);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
const sharedVoNames: ReadonlySet<string> = new Set(
|
|
504
|
-
[...voDomainsByName.entries()].filter(([, n]) => n > 1).map(([name]) => name),
|
|
505
|
-
);
|
|
506
|
-
const sharedVos = [...sharedVoNames]
|
|
507
|
-
.sort()
|
|
508
|
-
.map((name) => voDescriptorByName.get(name)!);
|
|
509
|
-
const hasFrameworkSymbols =
|
|
510
|
-
frameworkAggs.length > 0 || frameworkDirs.length > 0 || frameworkQueries.length > 0 || sharedVos.length > 0;
|
|
511
|
-
const FRAMEWORK_FILE = "nomos_framework";
|
|
645
|
+
const shared = collectDartSharedSymbols(modules);
|
|
646
|
+
const { frameworkAggIds, frameworkDirIds, frameworkQueryIds, sharedVoNames, hasFrameworkSymbols } = shared;
|
|
512
647
|
const frameworkImportUri = hasFrameworkSymbols ? `${FRAMEWORK_FILE}.dart` : undefined;
|
|
513
648
|
|
|
514
649
|
const written: string[] = [];
|
|
@@ -525,24 +660,15 @@ function emitDart(
|
|
|
525
660
|
supportImport === "vendored" ? src.replace(DART_PACKAGE_IMPORT, DART_RELATIVE_IMPORT) : src,
|
|
526
661
|
"utf8",
|
|
527
662
|
);
|
|
663
|
+
domainSources.push(src);
|
|
528
664
|
written.push(p);
|
|
529
665
|
return p;
|
|
530
666
|
};
|
|
531
667
|
|
|
532
668
|
// 2a. the shared framework file — the framework aggregates + directives + queries, ONCE. Its own
|
|
533
669
|
// surface is emitted in full (no skip). Imported by every domain that folds any framework symbol.
|
|
534
|
-
if (
|
|
535
|
-
|
|
536
|
-
name: FRAMEWORK_FILE,
|
|
537
|
-
domain: FRAMEWORK_FILE,
|
|
538
|
-
aggregates: frameworkAggs,
|
|
539
|
-
directives: frameworkDirs,
|
|
540
|
-
readModelAggregates: frameworkAggs.map((a) => a.id),
|
|
541
|
-
...(frameworkQueries.length > 0 ? { queries: frameworkQueries } : {}),
|
|
542
|
-
// Package-SHARED value objects (used by ≥2 domains) — their class emits here, once.
|
|
543
|
-
...(sharedVos.length > 0 ? { valueObjects: sharedVos } : {}),
|
|
544
|
-
};
|
|
545
|
-
writeGenerated(FRAMEWORK_FILE, generateDartDomain(frameworkMod));
|
|
670
|
+
if (shared.frameworkMod !== undefined) {
|
|
671
|
+
writeGenerated(FRAMEWORK_FILE, generateDartDomain(shared.frameworkMod));
|
|
546
672
|
}
|
|
547
673
|
|
|
548
674
|
// 2b. one generated file per composed domain — the TYPES (`<name>.dart`) + the async bridge
|
|
@@ -551,32 +677,12 @@ function emitDart(
|
|
|
551
677
|
// Default the read-model allowlist to EVERY aggregate when the domain didn't pin one, so
|
|
552
678
|
// `<name>.dart` carries a read model per aggregate — the bridge client imports them. A domain
|
|
553
679
|
// that pins `readModelAggregates` keeps its choice (byte-identical to before this default).
|
|
554
|
-
const modForDart: DomainModule =
|
|
555
|
-
mod.readModelAggregates === undefined
|
|
556
|
-
? { ...mod, readModelAggregates: mod.aggregates.map((a) => a.id) }
|
|
557
|
-
: mod;
|
|
558
680
|
// Skip framework symbols this domain folds (they live in the shared file); reference them via the
|
|
559
681
|
// framework import. Only pass the import when this domain actually folds a framework symbol.
|
|
560
|
-
const
|
|
561
|
-
modForDart.aggregates.filter((a) => frameworkAggIds.has(a.id)).map((a) => a.id),
|
|
562
|
-
);
|
|
563
|
-
const domainFwDirIds = new Set(
|
|
564
|
-
modForDart.directives.filter((d) => frameworkDirIds.has(d.id)).map((d) => d.id),
|
|
565
|
-
);
|
|
566
|
-
const domainFwQueryIds = new Set(
|
|
567
|
-
(modForDart.queries ?? []).filter((q) => frameworkQueryIds.has(q.id)).map((q) => q.id),
|
|
568
|
-
);
|
|
569
|
-
const domainSharedVoNames = new Set(
|
|
570
|
-
(modForDart.valueObjects ?? []).filter((d) => sharedVoNames.has(d.name)).map((d) => d.name),
|
|
571
|
-
);
|
|
572
|
-
const foldsFramework =
|
|
573
|
-
domainFwAggIds.size > 0 || domainFwDirIds.size > 0 || domainFwQueryIds.size > 0 || domainSharedVoNames.size > 0;
|
|
682
|
+
const { modForDart, skips, foldsFramework } = domainDedupOpts(mod, shared);
|
|
574
683
|
const domainFrameworkImport = foldsFramework ? frameworkImportUri : undefined;
|
|
575
684
|
const src = generateDartDomain(modForDart, {
|
|
576
|
-
|
|
577
|
-
skipDirectiveIds: domainFwDirIds,
|
|
578
|
-
skipQueryIds: domainFwQueryIds,
|
|
579
|
-
skipValueObjectNames: domainSharedVoNames,
|
|
685
|
+
...skips,
|
|
580
686
|
...(domainFrameworkImport !== undefined ? { frameworkImportUri: domainFrameworkImport } : {}),
|
|
581
687
|
});
|
|
582
688
|
writeGenerated(mod.name, src);
|
|
@@ -604,6 +710,10 @@ function emitDart(
|
|
|
604
710
|
"// Regenerated on every compile; generated implementation lives under src/generated/.",
|
|
605
711
|
"library;",
|
|
606
712
|
"",
|
|
713
|
+
...aliasImportLines(names, [
|
|
714
|
+
...(hasFrameworkSymbols ? [`${genRel}/${FRAMEWORK_FILE}.dart`] : []),
|
|
715
|
+
...modules.map((m) => `${genRel}/${m.name}.dart`),
|
|
716
|
+
]),
|
|
607
717
|
supportImport === "vendored"
|
|
608
718
|
? `export '${genRel}/nomos_client.dart';`
|
|
609
719
|
: `export 'package:nomos_client/nomos_client.dart';`,
|
|
@@ -612,6 +722,13 @@ function emitDart(
|
|
|
612
722
|
`export '${genRel}/${m.name}.dart';`,
|
|
613
723
|
`export '${genRel}/${m.name}.client.dart';`,
|
|
614
724
|
]),
|
|
725
|
+
...dartAliasLines(
|
|
726
|
+
names,
|
|
727
|
+
domainSources.reduce((acc, src) => {
|
|
728
|
+
for (const n of declaredDartTypeNames(src)) acc.add(n);
|
|
729
|
+
return acc;
|
|
730
|
+
}, new Set<string>()),
|
|
731
|
+
),
|
|
615
732
|
"",
|
|
616
733
|
];
|
|
617
734
|
const barrelPath = path.join(dartOut, `${barrelName}.dart`);
|
|
@@ -620,6 +737,272 @@ function emitDart(
|
|
|
620
737
|
return written;
|
|
621
738
|
}
|
|
622
739
|
|
|
740
|
+
// ── the LAYERED-PACKAGES Dart emit (dart.packages: blast-radius isolation) ────────
|
|
741
|
+
|
|
742
|
+
/** The pure-core support import generated TYPES files carry in layered-packages mode. */
|
|
743
|
+
const DART_TYPES_IMPORT = `import 'package:nomos_types/nomos_types.dart';`;
|
|
744
|
+
/** Version constraints the scaffolded pubspecs pin — move together with the Dart packages' releases. */
|
|
745
|
+
const NOMOS_TYPES_DEP = "^0.1.0";
|
|
746
|
+
const NOMOS_CLIENT_DEP = "^0.38.0";
|
|
747
|
+
|
|
748
|
+
const isPubPackageName = (s: string): boolean => /^[a-z][a-z0-9_]*$/.test(s);
|
|
749
|
+
|
|
750
|
+
/** `import` lines a barrel needs so its `typedef` aliases can SEE the generated symbols
|
|
751
|
+
* (an `export` alone re-exports but does not bring names into scope). Empty when no aliases. */
|
|
752
|
+
const aliasImportLines = (names: Readonly<Record<string, string>> | undefined, uris: readonly string[]): string[] =>
|
|
753
|
+
names !== undefined && Object.keys(names).length > 0 ? uris.map((u) => `import '${u}';`) : [];
|
|
754
|
+
|
|
755
|
+
/** Declared public type names in generated Dart source (classes, enums, mixins, extension types).
|
|
756
|
+
* The alias validator and the cursed-name report both read this surface. */
|
|
757
|
+
function declaredDartTypeNames(src: string): Set<string> {
|
|
758
|
+
const names = new Set<string>();
|
|
759
|
+
const re = /^(?:abstract\s+|final\s+|sealed\s+)*(?:class|enum|mixin|extension type)\s+([A-Za-z][A-Za-z0-9_]*)/gm;
|
|
760
|
+
for (let m = re.exec(src); m !== null; m = re.exec(src)) {
|
|
761
|
+
if (!m[1]!.startsWith("_")) names.add(m[1]!);
|
|
762
|
+
}
|
|
763
|
+
return names;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/** `typedef <alias> = <generated>;` lines for the barrel, validated FAIL-CLOSED against the emitted
|
|
767
|
+
* types surface: every target must exist, no alias may collide with a generated name or another alias. */
|
|
768
|
+
function dartAliasLines(names: Readonly<Record<string, string>> | undefined, declared: ReadonlySet<string>): string[] {
|
|
769
|
+
if (names === undefined || Object.keys(names).length === 0) return [];
|
|
770
|
+
const seenAliases = new Set<string>();
|
|
771
|
+
const lines = [
|
|
772
|
+
"",
|
|
773
|
+
"// ---- public aliases (dart.names) — humane names over the stable generated identities ----",
|
|
774
|
+
];
|
|
775
|
+
for (const [generated, alias] of Object.entries(names).sort(([a], [b]) => (a < b ? -1 : 1))) {
|
|
776
|
+
if (!declared.has(generated)) {
|
|
777
|
+
fail(
|
|
778
|
+
`dart.names: '${generated}' is not a generated public type in this package's TYPES surface — ` +
|
|
779
|
+
`check the spelling against the emitted src/generated/*.dart (aliases cover types, not client classes)`,
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
if (!/^[A-Z][A-Za-z0-9_]*$/.test(alias)) {
|
|
783
|
+
fail(`dart.names: alias '${alias}' (for '${generated}') must be a public Dart type identifier (UpperCamelCase)`);
|
|
784
|
+
}
|
|
785
|
+
if (declared.has(alias)) {
|
|
786
|
+
fail(`dart.names: alias '${alias}' collides with a generated public type of the same name`);
|
|
787
|
+
}
|
|
788
|
+
if (seenAliases.has(alias)) fail(`dart.names: alias '${alias}' is mapped from two generated names`);
|
|
789
|
+
seenAliases.add(alias);
|
|
790
|
+
lines.push(`typedef ${alias} = ${generated};`);
|
|
791
|
+
}
|
|
792
|
+
return lines;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/** The courtesy CURSED-NAME report: long compound generated names with no public alias. Printed in the
|
|
796
|
+
* compile summary (never a failure) so the `dart.names` map stays honest as the domain grows. */
|
|
797
|
+
function cursedNameHints(declared: ReadonlySet<string>, names: Readonly<Record<string, string>> | undefined): string[] {
|
|
798
|
+
const aliased = new Set(Object.keys(names ?? {}));
|
|
799
|
+
const humps = (s: string): number => (s.match(/[A-Z][a-z0-9]*/g) ?? []).length;
|
|
800
|
+
return [...declared]
|
|
801
|
+
.filter((n) => !aliased.has(n) && !n.startsWith("Nomos") && (n.length >= 28 || humps(n) >= 5))
|
|
802
|
+
.sort((a, b) => b.length - a.length);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/** Scaffold a pubspec ONLY IF MISSING — the tenant owns versioning/publishing; regeneration must
|
|
806
|
+
* never clobber their bumps. Returns true when written. */
|
|
807
|
+
function scaffoldPubspec(pkgDir: string, yaml: string): boolean {
|
|
808
|
+
const p = path.join(pkgDir, "pubspec.yaml");
|
|
809
|
+
if (existsSync(p)) return false;
|
|
810
|
+
mkdirSync(pkgDir, { recursive: true });
|
|
811
|
+
writeFileSync(p, yaml, "utf8");
|
|
812
|
+
return true;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* THE LAYERED-PACKAGES EMIT (`dart.packages`) — three sibling pub packages under `dartOut`:
|
|
817
|
+
* `<types>/` (pure data, depends only on `nomos_types`), `<client>/` (effectful runtime clients,
|
|
818
|
+
* depends on the types package + `nomos_client`), and optionally `<test>/` (the headless-harness
|
|
819
|
+
* surface). Generated code lives under each package's `lib/src/generated/` (wiped per compile)
|
|
820
|
+
* behind a generated-owned barrel `lib/<pkg>.dart`; pubspecs are scaffolded only if missing.
|
|
821
|
+
* TYPES-FILE PURITY is enforced fail-closed: a generated types file may import ONLY the
|
|
822
|
+
* `nomos_types` barrel and the relative `nomos_framework.dart` — any runtime import is a compile
|
|
823
|
+
* failure here, not a review comment downstream.
|
|
824
|
+
*/
|
|
825
|
+
function emitDartPackages(
|
|
826
|
+
modules: readonly DomainModule[],
|
|
827
|
+
dartOut: string,
|
|
828
|
+
domainHash: string,
|
|
829
|
+
packageInterface: Record<string, DomainInterface>,
|
|
830
|
+
packages: { readonly types: string; readonly client: string; readonly test?: string },
|
|
831
|
+
names: Readonly<Record<string, string>> | undefined,
|
|
832
|
+
compat?: NomosCompat,
|
|
833
|
+
): { written: string[]; cursed: string[] } {
|
|
834
|
+
for (const [role, name] of Object.entries(packages)) {
|
|
835
|
+
if (typeof name !== "string" || !isPubPackageName(name)) {
|
|
836
|
+
fail(`dart.packages.${role}: '${String(name)}' is not a valid pub package name (lower_snake_case)`);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
const roleNames = [packages.types, packages.client, ...(packages.test !== undefined ? [packages.test] : [])];
|
|
840
|
+
if (new Set(roleNames).size !== roleNames.length) fail(`dart.packages: types/client/test names must be distinct`);
|
|
841
|
+
|
|
842
|
+
const shared = collectDartSharedSymbols(modules);
|
|
843
|
+
const written: string[] = [];
|
|
844
|
+
const genRel = DART_GENERATED_SUBDIR.split(path.sep).join("/");
|
|
845
|
+
|
|
846
|
+
const pkgDir = (name: string): string => path.join(dartOut, name);
|
|
847
|
+
const freshGenDir = (name: string): string => {
|
|
848
|
+
const g = path.join(pkgDir(name), "lib", DART_GENERATED_SUBDIR);
|
|
849
|
+
rmSync(g, { recursive: true, force: true });
|
|
850
|
+
mkdirSync(g, { recursive: true });
|
|
851
|
+
return g;
|
|
852
|
+
};
|
|
853
|
+
|
|
854
|
+
// ── the TYPES package — pure data, `nomos_types` only ──
|
|
855
|
+
const typesGen = freshGenDir(packages.types);
|
|
856
|
+
const typesSources = new Map<string, string>(); // fileBase → src (for alias validation + purity)
|
|
857
|
+
const writeTypes = (fileBase: string, src: string): void => {
|
|
858
|
+
if (!src.includes(DART_PACKAGE_IMPORT)) {
|
|
859
|
+
fail(`generated Dart for '${fileBase}' does not carry the expected support import — codegen_dart header drifted`);
|
|
860
|
+
}
|
|
861
|
+
const pure = src.replace(DART_PACKAGE_IMPORT, DART_TYPES_IMPORT);
|
|
862
|
+
// PURITY, fail-closed: only the nomos_types barrel + the relative framework file may be imported.
|
|
863
|
+
for (const line of pure.split("\n")) {
|
|
864
|
+
const m = /^import\s+'([^']+)'/.exec(line.trim());
|
|
865
|
+
if (m !== null && m[1] !== "package:nomos_types/nomos_types.dart" && m[1] !== `${FRAMEWORK_FILE}.dart`) {
|
|
866
|
+
fail(
|
|
867
|
+
`types-package purity: generated '${fileBase}.dart' imports '${m[1]}' — a types file may only ` +
|
|
868
|
+
`import the nomos_types core (runtime/bridge surfaces belong in the client package)`,
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
const p = path.join(typesGen, `${fileBase}.dart`);
|
|
873
|
+
writeFileSync(p, pure, "utf8");
|
|
874
|
+
typesSources.set(fileBase, pure);
|
|
875
|
+
written.push(p);
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
if (shared.frameworkMod !== undefined) writeTypes(FRAMEWORK_FILE, generateDartDomain(shared.frameworkMod));
|
|
879
|
+
const perDomain = modules.map((mod) => ({ mod, dd: domainDedupOpts(mod, shared) }));
|
|
880
|
+
for (const { mod, dd } of perDomain) {
|
|
881
|
+
writeTypes(
|
|
882
|
+
mod.name,
|
|
883
|
+
generateDartDomain(dd.modForDart, {
|
|
884
|
+
...dd.skips,
|
|
885
|
+
...(dd.foldsFramework ? { frameworkImportUri: `${FRAMEWORK_FILE}.dart` } : {}),
|
|
886
|
+
}),
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
const declared = new Set<string>();
|
|
891
|
+
for (const src of typesSources.values()) for (const n of declaredDartTypeNames(src)) declared.add(n);
|
|
892
|
+
const typesBarrel = [
|
|
893
|
+
"// GENERATED by nomos-compile — the TYPES package's public entrypoint. DO NOT EDIT.",
|
|
894
|
+
"// Pure data: payload classes, read models, VOs/enums, typed read accessors over the",
|
|
895
|
+
"// injected NomosReads seams. Frontend-safe — no bridge, no sessions, no lifecycle.",
|
|
896
|
+
"library;",
|
|
897
|
+
"",
|
|
898
|
+
...aliasImportLines(names, [...typesSources.keys()].map((f) => `${genRel}/${f}.dart`)),
|
|
899
|
+
`export 'package:nomos_types/nomos_types.dart';`,
|
|
900
|
+
...[...typesSources.keys()].map((f) => `export '${genRel}/${f}.dart';`),
|
|
901
|
+
...dartAliasLines(names, declared),
|
|
902
|
+
"",
|
|
903
|
+
];
|
|
904
|
+
const typesBarrelPath = path.join(pkgDir(packages.types), "lib", `${packages.types}.dart`);
|
|
905
|
+
writeFileSync(typesBarrelPath, typesBarrel.join("\n"), "utf8");
|
|
906
|
+
written.push(typesBarrelPath);
|
|
907
|
+
scaffoldPubspec(
|
|
908
|
+
pkgDir(packages.types),
|
|
909
|
+
[
|
|
910
|
+
`name: ${packages.types}`,
|
|
911
|
+
`description: Generated Nomos domain TYPES — pure data, frontend-safe (no bridge, no runtime).`,
|
|
912
|
+
`version: 0.1.0`,
|
|
913
|
+
`publish_to: none # tenant-owned: remove to publish`,
|
|
914
|
+
`environment:`,
|
|
915
|
+
` sdk: ^3.5.0`,
|
|
916
|
+
`dependencies:`,
|
|
917
|
+
` nomos_types: ${NOMOS_TYPES_DEP}`,
|
|
918
|
+
``,
|
|
919
|
+
].join("\n"),
|
|
920
|
+
);
|
|
921
|
+
|
|
922
|
+
// ── the CLIENT package — effectful runtime, depends on types + nomos_client ──
|
|
923
|
+
const clientGen = freshGenDir(packages.client);
|
|
924
|
+
const typesImportUri = `package:${packages.types}/${packages.types}.dart`;
|
|
925
|
+
for (const { mod, dd } of perDomain) {
|
|
926
|
+
const clientSrc = generateDartBridgeClient(dd.modForDart, {
|
|
927
|
+
domainHash,
|
|
928
|
+
interface: packageInterface[dd.modForDart.domain ?? dd.modForDart.name],
|
|
929
|
+
...(compat !== undefined ? { compat } : {}),
|
|
930
|
+
supportImport: "package",
|
|
931
|
+
typesImportUri,
|
|
932
|
+
});
|
|
933
|
+
const cp = path.join(clientGen, `${mod.name}.client.dart`);
|
|
934
|
+
writeFileSync(cp, clientSrc, "utf8");
|
|
935
|
+
written.push(cp);
|
|
936
|
+
}
|
|
937
|
+
const clientBarrel = [
|
|
938
|
+
"// GENERATED by nomos-compile — the CLIENT package's public entrypoint. DO NOT EDIT.",
|
|
939
|
+
"// Effectful runtime clients (offer methods over NomosBridge). Composition/runtime layer",
|
|
940
|
+
"// only — UI code should depend on the sibling types package, not on this.",
|
|
941
|
+
"library;",
|
|
942
|
+
"",
|
|
943
|
+
`export 'package:nomos_client/nomos_client.dart';`,
|
|
944
|
+
`export '${typesImportUri}';`,
|
|
945
|
+
...perDomain.map(({ mod }) => `export '${genRel}/${mod.name}.client.dart';`),
|
|
946
|
+
"",
|
|
947
|
+
];
|
|
948
|
+
const clientBarrelPath = path.join(pkgDir(packages.client), "lib", `${packages.client}.dart`);
|
|
949
|
+
writeFileSync(clientBarrelPath, clientBarrel.join("\n"), "utf8");
|
|
950
|
+
written.push(clientBarrelPath);
|
|
951
|
+
scaffoldPubspec(
|
|
952
|
+
pkgDir(packages.client),
|
|
953
|
+
[
|
|
954
|
+
`name: ${packages.client}`,
|
|
955
|
+
`description: Generated Nomos domain CLIENTS — the effectful runtime layer over NomosBridge.`,
|
|
956
|
+
`version: 0.1.0`,
|
|
957
|
+
`publish_to: none # tenant-owned: remove to publish`,
|
|
958
|
+
`environment:`,
|
|
959
|
+
` sdk: ^3.5.0`,
|
|
960
|
+
`dependencies:`,
|
|
961
|
+
` nomos_client: ${NOMOS_CLIENT_DEP}`,
|
|
962
|
+
` ${packages.types}:`,
|
|
963
|
+
` path: ../${packages.types} # co-emitted sibling; replace with a hosted constraint when publishing`,
|
|
964
|
+
``,
|
|
965
|
+
].join("\n"),
|
|
966
|
+
);
|
|
967
|
+
|
|
968
|
+
// ── the TEST package (optional) — the headless-harness surface, test-only dependency ──
|
|
969
|
+
if (packages.test !== undefined) {
|
|
970
|
+
const testGen = freshGenDir(packages.test);
|
|
971
|
+
void testGen; // generated fixtures land here as they grow; the barrel is the surface today
|
|
972
|
+
const testBarrel = [
|
|
973
|
+
"// GENERATED by nomos-compile — the TEST package's public entrypoint. DO NOT EDIT.",
|
|
974
|
+
"// The headless-harness surface (NomosTestHarness + the node-runner transport) plus the",
|
|
975
|
+
"// full client layer, for widget/integration tests. Depend on this ONLY from dev_dependencies.",
|
|
976
|
+
"library;",
|
|
977
|
+
"",
|
|
978
|
+
`export 'package:nomos_client/testing.dart';`,
|
|
979
|
+
`export 'package:${packages.client}/${packages.client}.dart';`,
|
|
980
|
+
"",
|
|
981
|
+
];
|
|
982
|
+
const testBarrelPath = path.join(pkgDir(packages.test), "lib", `${packages.test}.dart`);
|
|
983
|
+
writeFileSync(testBarrelPath, testBarrel.join("\n"), "utf8");
|
|
984
|
+
written.push(testBarrelPath);
|
|
985
|
+
scaffoldPubspec(
|
|
986
|
+
pkgDir(packages.test),
|
|
987
|
+
[
|
|
988
|
+
`name: ${packages.test}`,
|
|
989
|
+
`description: Generated Nomos test surface — headless harness + clients. dev_dependencies only.`,
|
|
990
|
+
`version: 0.1.0`,
|
|
991
|
+
`publish_to: none # tenant-owned: remove to publish`,
|
|
992
|
+
`environment:`,
|
|
993
|
+
` sdk: ^3.5.0`,
|
|
994
|
+
`dependencies:`,
|
|
995
|
+
` nomos_client: ${NOMOS_CLIENT_DEP}`,
|
|
996
|
+
` ${packages.client}:`,
|
|
997
|
+
` path: ../${packages.client} # co-emitted sibling; replace with a hosted constraint when publishing`,
|
|
998
|
+
``,
|
|
999
|
+
].join("\n"),
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
return { written, cursed: cursedNameHints(declared, names) };
|
|
1004
|
+
}
|
|
1005
|
+
|
|
623
1006
|
// ── main ──────────────────────────────────────────────────────────────────────────
|
|
624
1007
|
|
|
625
1008
|
async function main(): Promise<void> {
|
|
@@ -1761,31 +2144,53 @@ async function main(): Promise<void> {
|
|
|
1761
2144
|
dartCfg === null ||
|
|
1762
2145
|
(dartCfg.out !== undefined && typeof dartCfg.out !== "string") ||
|
|
1763
2146
|
(dartCfg.support !== undefined && dartCfg.support !== "vendored" && dartCfg.support !== "package") ||
|
|
1764
|
-
(dartCfg.barrel !== undefined && !/^[a-z][a-z0-9_]*$/.test(String(dartCfg.barrel)))
|
|
2147
|
+
(dartCfg.barrel !== undefined && !/^[a-z][a-z0-9_]*$/.test(String(dartCfg.barrel))) ||
|
|
2148
|
+
(dartCfg.names !== undefined &&
|
|
2149
|
+
(typeof dartCfg.names !== "object" || dartCfg.names === null || Array.isArray(dartCfg.names))) ||
|
|
2150
|
+
(dartCfg.packages !== undefined &&
|
|
2151
|
+
(typeof dartCfg.packages !== "object" ||
|
|
2152
|
+
dartCfg.packages === null ||
|
|
2153
|
+
typeof dartCfg.packages.types !== "string" ||
|
|
2154
|
+
typeof dartCfg.packages.client !== "string")))
|
|
1765
2155
|
) {
|
|
1766
2156
|
fail(
|
|
1767
|
-
`config key 'dart' must be false, true, or { out?: "relative/dir", support?: "vendored" | "package", barrel?: "snake_case_name" }; got ${JSON.stringify(dartCfg)}`,
|
|
2157
|
+
`config key 'dart' must be false, true, or { out?: "relative/dir", support?: "vendored" | "package", barrel?: "snake_case_name", packages?: { types, client, test? }, names?: { Generated: "Alias" } }; got ${JSON.stringify(dartCfg)}`,
|
|
2158
|
+
);
|
|
2159
|
+
}
|
|
2160
|
+
const dartPackages = typeof dartCfg === "object" ? dartCfg.packages : undefined;
|
|
2161
|
+
const dartNames = typeof dartCfg === "object" ? dartCfg.names : undefined;
|
|
2162
|
+
if (dartPackages !== undefined && typeof dartCfg === "object" && dartCfg.support === "vendored") {
|
|
2163
|
+
fail(
|
|
2164
|
+
`dart.packages requires support: "package" — layered pub packages depend on the published ` +
|
|
2165
|
+
`nomos_types/nomos_client cores; vendoring contradicts the package boundary`,
|
|
1768
2166
|
);
|
|
1769
2167
|
}
|
|
1770
2168
|
const dartSupportImport: DartSupportImport =
|
|
1771
|
-
typeof dartCfg === "object" && dartCfg.support !== undefined ? dartCfg.support : "vendored";
|
|
2169
|
+
typeof dartCfg === "object" && dartCfg.support !== undefined ? dartCfg.support : dartPackages !== undefined ? "package" : "vendored";
|
|
1772
2170
|
let dartOut: string | undefined;
|
|
1773
2171
|
let dartFiles: string[] = [];
|
|
2172
|
+
let dartCursedHints: string[] = [];
|
|
1774
2173
|
if (dartCfg !== false) {
|
|
1775
2174
|
dartOut = path.resolve(
|
|
1776
2175
|
cfgDir,
|
|
1777
2176
|
typeof dartCfg === "object" && dartCfg.out !== undefined ? dartCfg.out : path.join(outDir, "dart"),
|
|
1778
2177
|
);
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
.
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
2178
|
+
if (dartPackages !== undefined) {
|
|
2179
|
+
const r = emitDartPackages(domainModules, dartOut, domainHash, packageInterface, dartPackages, dartNames, nomosCompat);
|
|
2180
|
+
dartFiles = r.written;
|
|
2181
|
+
dartCursedHints = r.cursed;
|
|
2182
|
+
} else {
|
|
2183
|
+
// The barrel basename: configured, else the snake_cased package name (a valid Dart library
|
|
2184
|
+
// file name — pub packages import `package:<name>/<name>.dart`).
|
|
2185
|
+
const defaultBarrel = cfg.name
|
|
2186
|
+
.toLowerCase()
|
|
2187
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
2188
|
+
.replace(/^_+|_+$/g, "")
|
|
2189
|
+
.replace(/^(?=[0-9])/, "n");
|
|
2190
|
+
const dartBarrel =
|
|
2191
|
+
typeof dartCfg === "object" && dartCfg.barrel !== undefined ? dartCfg.barrel : defaultBarrel;
|
|
2192
|
+
dartFiles = emitDart(domainModules, dartOut, domainHash, packageInterface, dartSupportImport, dartBarrel, nomosCompat, dartNames);
|
|
2193
|
+
}
|
|
1789
2194
|
}
|
|
1790
2195
|
|
|
1791
2196
|
// ── summary ──
|
|
@@ -1865,9 +2270,25 @@ async function main(): Promise<void> {
|
|
|
1865
2270
|
}
|
|
1866
2271
|
}
|
|
1867
2272
|
if (dartOut !== undefined) {
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
2273
|
+
if (dartPackages !== undefined) {
|
|
2274
|
+
const pkgList = [
|
|
2275
|
+
`${dartPackages.types} (types)`,
|
|
2276
|
+
`${dartPackages.client} (client)`,
|
|
2277
|
+
...(dartPackages.test !== undefined ? [`${dartPackages.test} (test)`] : []),
|
|
2278
|
+
].join(" · ");
|
|
2279
|
+
console.log(` dart ${rel(dartOut)}${path.sep} — LAYERED packages: ${pkgList} (${dartFiles.length} generated file(s))`);
|
|
2280
|
+
console.log(` layering line: UI depends on ${dartPackages.types}; only composition/runtime depends on ${dartPackages.client}`);
|
|
2281
|
+
if (dartCursedHints.length > 0) {
|
|
2282
|
+
const shown = dartCursedHints.slice(0, 5).join(", ");
|
|
2283
|
+
console.log(
|
|
2284
|
+
` dart names hint: ${dartCursedHints.length} long generated name(s) lack a public alias — e.g. ${shown}${dartCursedHints.length > 5 ? ", …" : ""}; map them in dart.names`,
|
|
2285
|
+
);
|
|
2286
|
+
}
|
|
2287
|
+
} else {
|
|
2288
|
+
const supportText =
|
|
2289
|
+
dartSupportImport === "vendored" ? "vendored nomos_client support" : "package nomos_client imports";
|
|
2290
|
+
console.log(` dart ${rel(dartOut)}${path.sep} (${dartFiles.length} domain file(s) + ${supportText} — typed Dart for Flutter)`);
|
|
2291
|
+
}
|
|
1871
2292
|
}
|
|
1872
2293
|
console.log(` deploy ${rel(deployPath)}`);
|
|
1873
2294
|
console.log(``);
|