11import type { ContractModelBase , ContractValueObject } from './domain-types' ;
2+ import type { NamespaceId } from './namespace-id' ;
23
34export const UNBOUND_DOMAIN_NAMESPACE_ID = '__unbound__' as const ;
45
@@ -23,62 +24,53 @@ export interface DomainPlane<
2324 readonly namespaces : Readonly < Record < string , DomainNamespace < TModels > > > ;
2425}
2526
26- export type DomainContractSlice = {
27+ export type ContractWithDomain = {
2728 readonly domain : DomainPlane ;
2829} ;
2930
30- /** Pre-domain-envelope contract root still carrying flat `models` / `valueObjects`. */
31- export type LegacyFlatDomainRoot = {
32- readonly models ?: Record < string , ContractModelBase > ;
33- readonly valueObjects ?: Record < string , ContractValueObject > ;
34- } ;
35-
36- export type DomainContractInput = DomainContractSlice | LegacyFlatDomainRoot ;
37-
38- function domainNamespacesOf ( contract : DomainContractInput ) : DomainPlane [ 'namespaces' ] | undefined {
39- if ( ! ( 'domain' in contract ) || contract . domain ?. namespaces === undefined ) {
40- return undefined ;
31+ export class DomainNamespaceResolutionError extends Error {
32+ constructor ( message : string ) {
33+ super ( message ) ;
34+ this . name = 'DomainNamespaceResolutionError' ;
4135 }
42- return contract . domain . namespaces ;
43- }
44-
45- function isLegacyFlatDomainRoot ( contract : DomainContractInput ) : contract is LegacyFlatDomainRoot {
46- return domainNamespacesOf ( contract ) === undefined ;
4736}
4837
49- export function contractModels ( contract : DomainContractInput ) : Record < string , ContractModelBase > {
50- const namespaces = domainNamespacesOf ( contract ) ;
51- if ( namespaces !== undefined ) {
52- const merged : Record < string , ContractModelBase > = { } ;
53- for ( const ns of Object . values ( namespaces ) ) {
54- Object . assign ( merged , ns . models ) ;
38+ export function resolveSingleDomainNamespaceId ( domain : DomainPlane , namespaceId ?: string ) : string {
39+ if ( namespaceId !== undefined ) {
40+ if ( ! Object . hasOwn ( domain . namespaces , namespaceId ) ) {
41+ throw new DomainNamespaceResolutionError (
42+ `domain namespace " ${ namespaceId } " is not present on the contract` ,
43+ ) ;
5544 }
56- return merged ;
45+ return namespaceId ;
5746 }
58- if ( isLegacyFlatDomainRoot ( contract ) ) {
59- return contract . models ?? { } ;
47+
48+ const namespaceIds = Object . keys ( domain . namespaces ) ;
49+ if ( namespaceIds . length === 0 ) {
50+ throw new DomainNamespaceResolutionError ( 'domain has no namespaces' ) ;
51+ }
52+ if ( namespaceIds . length > 1 ) {
53+ throw new DomainNamespaceResolutionError (
54+ `expected exactly one domain namespace, found ${ namespaceIds . length } (${ namespaceIds . join ( ', ' ) } )` ,
55+ ) ;
6056 }
61- return { } ;
57+ return namespaceIds [ 0 ] ! ;
58+ }
59+
60+ export function contractModels (
61+ contract : ContractWithDomain ,
62+ namespaceId ?: string ,
63+ ) : Record < string , ContractModelBase > {
64+ const resolved = resolveSingleDomainNamespaceId ( contract . domain , namespaceId ) ;
65+ return contract . domain . namespaces [ resolved ] ! . models ;
6266}
6367
6468export function contractValueObjects (
65- contract : DomainContractInput ,
69+ contract : ContractWithDomain ,
70+ namespaceId ?: string ,
6671) : Record < string , ContractValueObject > | undefined {
67- const namespaces = domainNamespacesOf ( contract ) ;
68- if ( namespaces !== undefined ) {
69- const merged : Record < string , ContractValueObject > = { } ;
70- let any = false ;
71- for ( const ns of Object . values ( namespaces ) ) {
72- if ( ns . valueObjects === undefined ) continue ;
73- any = true ;
74- Object . assign ( merged , ns . valueObjects ) ;
75- }
76- return any ? merged : undefined ;
77- }
78- if ( isLegacyFlatDomainRoot ( contract ) ) {
79- return contract . valueObjects ;
80- }
81- return undefined ;
72+ const resolved = resolveSingleDomainNamespaceId ( contract . domain , namespaceId ) ;
73+ return contract . domain . namespaces [ resolved ] ! . valueObjects ;
8274}
8375
8476export function buildDomainPlaneFromFlat ( params : {
@@ -97,35 +89,6 @@ export function buildDomainPlaneFromFlat(params: {
9789 } ;
9890}
9991
100- /**
101- * Lifts a legacy flat `models` / `valueObjects` contract root into
102- * `domain.namespaces.__unbound__` when the domain envelope is absent.
103- */
104- export function normalizeLegacyDomainRoot ( value : Record < string , unknown > ) : Record < string , unknown > {
105- if ( value [ 'domain' ] !== undefined && value [ 'domain' ] !== null ) {
106- return value ;
107- }
108- const models = value [ 'models' ] ;
109- if ( models === undefined || typeof models !== 'object' || models === null ) {
110- return value ;
111- }
112- const valueObjects = value [ 'valueObjects' ] ;
113- const namespace : Record < string , unknown > = { models } ;
114- if (
115- valueObjects !== undefined &&
116- typeof valueObjects === 'object' &&
117- valueObjects !== null &&
118- ! Array . isArray ( valueObjects )
119- ) {
120- namespace [ 'valueObjects' ] = valueObjects ;
121- }
122- const { models : _m , valueObjects : _vo , ...rest } = value ;
123- return {
124- ...rest ,
125- domain : {
126- namespaces : {
127- [ UNBOUND_DOMAIN_NAMESPACE_ID ] : namespace ,
128- } ,
129- } ,
130- } ;
92+ export function modelCoordinateKey ( namespace : NamespaceId , model : string ) : string {
93+ return `${ namespace } :${ model } ` ;
13194}
0 commit comments