@prisma-next/contract 0.11.0-dev.67 → 0.11.0-dev.69

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.
Files changed (56) hide show
  1. package/dist/{canonicalization-C3dTO0j1.d.mts → canonicalization-DFE0HJkI.d.mts} +2 -2
  2. package/dist/canonicalization-DFE0HJkI.d.mts.map +1 -0
  3. package/dist/canonicalization-path-match-b2jFuEso.mjs +25 -0
  4. package/dist/canonicalization-path-match-b2jFuEso.mjs.map +1 -0
  5. package/dist/{contract-types-CZPm4Ooy.d.mts → contract-types-xgwKtd7y.d.mts} +13 -74
  6. package/dist/contract-types-xgwKtd7y.d.mts.map +1 -0
  7. package/dist/contract-validation-error-ClZaKqMW.mjs +20 -0
  8. package/dist/contract-validation-error-ClZaKqMW.mjs.map +1 -0
  9. package/dist/contract-validation-error-T5LH4DW-.d.mts +13 -0
  10. package/dist/contract-validation-error-T5LH4DW-.d.mts.map +1 -0
  11. package/dist/contract-validation-error.d.mts +2 -10
  12. package/dist/contract-validation-error.mjs +2 -2
  13. package/dist/domain-envelope-4hyFtJ4_.d.mts +110 -0
  14. package/dist/domain-envelope-4hyFtJ4_.d.mts.map +1 -0
  15. package/dist/hashing-utils.d.mts +1 -1
  16. package/dist/hashing-utils.mjs +1 -22
  17. package/dist/hashing-utils.mjs.map +1 -1
  18. package/dist/hashing.d.mts +2 -2
  19. package/dist/hashing.mjs +175 -1
  20. package/dist/hashing.mjs.map +1 -0
  21. package/dist/namespace-id-CVpkSFUK.mjs +9 -0
  22. package/dist/namespace-id-CVpkSFUK.mjs.map +1 -0
  23. package/dist/types.d.mts +4 -3
  24. package/dist/types.mjs +73 -7
  25. package/dist/types.mjs.map +1 -1
  26. package/dist/validate-domain.d.mts +2 -6
  27. package/dist/validate-domain.d.mts.map +1 -1
  28. package/dist/validate-domain.mjs +98 -61
  29. package/dist/validate-domain.mjs.map +1 -1
  30. package/package.json +4 -6
  31. package/src/canonicalization.ts +41 -25
  32. package/src/contract-types.ts +31 -6
  33. package/src/contract-validation-error.ts +7 -0
  34. package/src/domain-envelope.ts +87 -0
  35. package/src/domain-types.ts +8 -12
  36. package/src/exports/contract-validation-error.ts +1 -0
  37. package/src/exports/types.ts +18 -1
  38. package/src/hashing.ts +1 -1
  39. package/src/validate-domain.ts +154 -93
  40. package/dist/canonicalization-C3dTO0j1.d.mts.map +0 -1
  41. package/dist/contract-types-CZPm4Ooy.d.mts.map +0 -1
  42. package/dist/contract-validation-error-Dp2vHZt5.mjs +0 -14
  43. package/dist/contract-validation-error-Dp2vHZt5.mjs.map +0 -1
  44. package/dist/contract-validation-error.d.mts.map +0 -1
  45. package/dist/cross-reference-t0TDbBTP.d.mts +0 -18
  46. package/dist/cross-reference-t0TDbBTP.d.mts.map +0 -1
  47. package/dist/hashing-C25nwocN.mjs +0 -151
  48. package/dist/hashing-C25nwocN.mjs.map +0 -1
  49. package/dist/testing.d.mts +0 -36
  50. package/dist/testing.d.mts.map +0 -1
  51. package/dist/testing.mjs +0 -65
  52. package/dist/testing.mjs.map +0 -1
  53. package/dist/types-CVGwkRLa.mjs +0 -46
  54. package/dist/types-CVGwkRLa.mjs.map +0 -1
  55. package/src/exports/testing.ts +0 -1
  56. package/src/testing-factories.ts +0 -121
@@ -1,7 +1,7 @@
1
1
  import { isArrayEqual } from '@prisma-next/utils/array-equal';
2
2
  import { ifDefined } from '@prisma-next/utils/defined';
3
3
  import type { JsonObject } from '@prisma-next/utils/json';
4
-
4
+ import { matchesPathPattern, type PathPattern } from './canonicalization-path-match';
5
5
  import type { Contract } from './contract-types';
6
6
 
7
7
  /**
@@ -33,6 +33,30 @@ export type PreserveEmptyPredicate = (path: readonly string[]) => boolean;
33
33
  */
34
34
  export type StorageSort = (storage: unknown) => unknown;
35
35
 
36
+ const DOMAIN_NAMESPACE_SLOT_PATTERN = ['domain', 'namespaces', '*'] as const satisfies PathPattern;
37
+ const DOMAIN_MODELS_CONTAINER_PATTERN = [
38
+ 'domain',
39
+ 'namespaces',
40
+ '*',
41
+ 'models',
42
+ ] as const satisfies PathPattern;
43
+ const DOMAIN_MODEL_RELATIONS_PATTERN = [
44
+ 'domain',
45
+ 'namespaces',
46
+ '*',
47
+ 'models',
48
+ '*',
49
+ 'relations',
50
+ ] as const satisfies PathPattern;
51
+ const DOMAIN_MODEL_STORAGE_PATTERN = [
52
+ 'domain',
53
+ 'namespaces',
54
+ '*',
55
+ 'models',
56
+ '*',
57
+ 'storage',
58
+ ] as const satisfies PathPattern;
59
+
36
60
  const TOP_LEVEL_ORDER = [
37
61
  'schemaVersion',
38
62
  'canonicalVersion',
@@ -40,8 +64,6 @@ const TOP_LEVEL_ORDER = [
40
64
  'target',
41
65
  'profileHash',
42
66
  'roots',
43
- 'models',
44
- 'valueObjects',
45
67
  'domain',
46
68
  'storage',
47
69
  'execution',
@@ -92,9 +114,14 @@ function omitDefaults(
92
114
  }
93
115
 
94
116
  if (isDefaultValue(value)) {
95
- const isRequiredModels = isArrayEqual(currentPath, ['models']);
96
- const isRequiredNamespaces = isArrayEqual(currentPath, ['storage', 'namespaces']);
97
- const isNamespaceSlot =
117
+ const isRequiredDomainNamespaces = isArrayEqual(currentPath, ['domain', 'namespaces']);
118
+ const isDomainNamespaceSlot = matchesPathPattern(currentPath, DOMAIN_NAMESPACE_SLOT_PATTERN);
119
+ const isRequiredDomainModels = matchesPathPattern(
120
+ currentPath,
121
+ DOMAIN_MODELS_CONTAINER_PATTERN,
122
+ );
123
+ const isRequiredStorageNamespaces = isArrayEqual(currentPath, ['storage', 'namespaces']);
124
+ const isStorageNamespaceSlot =
98
125
  currentPath.length === 3 &&
99
126
  isArrayEqual([currentPath[0], currentPath[1]], ['storage', 'namespaces']);
100
127
  const isRequiredRoots = isArrayEqual(currentPath, ['roots']);
@@ -107,27 +134,19 @@ function omitDefaults(
107
134
  'defaults',
108
135
  ]);
109
136
  const isExtensionNamespace = currentPath.length === 2 && currentPath[0] === 'extensionPacks';
110
- const isModelRelations =
111
- currentPath.length === 3 &&
112
- isArrayEqual([currentPath[0], currentPath[2]], ['models', 'relations']);
113
- const isModelStorage =
114
- currentPath.length === 3 &&
115
- isArrayEqual([currentPath[0], currentPath[2]], ['models', 'storage']);
116
-
117
- const isDomainUnboundTypeParams =
118
- currentPath.length === 5 &&
119
- currentPath[0] === 'domain' &&
120
- currentPath[2] === 'types' &&
121
- key === 'typeParams';
137
+ const isModelRelations = matchesPathPattern(currentPath, DOMAIN_MODEL_RELATIONS_PATTERN);
138
+ const isModelStorage = matchesPathPattern(currentPath, DOMAIN_MODEL_STORAGE_PATTERN);
122
139
 
123
140
  const isNullableField = key === 'nullable';
124
141
 
125
142
  const isFamilyPreserved = shouldPreserveEmpty?.(currentPath) ?? false;
126
143
 
127
144
  if (
128
- !isRequiredModels &&
129
- !isRequiredNamespaces &&
130
- !isNamespaceSlot &&
145
+ !isRequiredDomainNamespaces &&
146
+ !isDomainNamespaceSlot &&
147
+ !isRequiredDomainModels &&
148
+ !isRequiredStorageNamespaces &&
149
+ !isStorageNamespaceSlot &&
131
150
  !isRequiredRoots &&
132
151
  !isRequiredExtensionPacks &&
133
152
  !isRequiredCapabilities &&
@@ -137,7 +156,6 @@ function omitDefaults(
137
156
  !isModelRelations &&
138
157
  !isModelStorage &&
139
158
  !isNullableField &&
140
- !isDomainUnboundTypeParams &&
141
159
  !isFamilyPreserved
142
160
  ) {
143
161
  continue;
@@ -232,9 +250,7 @@ export function canonicalizeContractToObject(
232
250
  target: serialized['target'],
233
251
  profileHash: serialized['profileHash'],
234
252
  roots: serialized['roots'],
235
- models: serialized['models'],
236
- ...ifDefined('valueObjects', serialized['valueObjects']),
237
- ...ifDefined('domain', serialized['domain']),
253
+ domain: serialized['domain'],
238
254
  storage: serialized['storage'],
239
255
  ...ifDefined('execution', serialized['execution']),
240
256
  extensionPacks: serialized['extensionPacks'],
@@ -1,4 +1,5 @@
1
1
  import type { CrossReference } from './cross-reference';
2
+ import type { ApplicationDomain } from './domain-envelope';
2
3
  import type { ContractModelBase, ContractValueObject } from './domain-types';
3
4
  import type {
4
5
  ExecutionHashBase,
@@ -45,14 +46,11 @@ export interface Contract<
45
46
  readonly target: string;
46
47
  readonly targetFamily: string;
47
48
  readonly roots: Record<string, CrossReference>;
48
- readonly models: TModels;
49
- readonly valueObjects?: Record<string, ContractValueObject>;
50
49
  /**
51
- * Domain plane keyed as `domain[plane][namespaceId][entityKind][entityName]`.
52
- * Optional until downstream slices populate it; when absent the field is
53
- * omitted from the on-disk envelope so emitted contracts stay byte-stable.
50
+ * Application plane (ADR 221): `domain.namespaces.<nsId>.{ models, valueObjects }`.
51
+ * `TModels` types the union of model entries across namespaces for family DSL inference.
54
52
  */
55
- readonly domain?: Record<string, Record<string, Record<string, unknown>>>;
53
+ readonly domain: ApplicationDomain<TModels>;
56
54
  readonly storage: TStorage;
57
55
  readonly capabilities: Record<string, Record<string, boolean>>;
58
56
  readonly extensionPacks: Record<string, unknown>;
@@ -60,3 +58,30 @@ export interface Contract<
60
58
  readonly profileHash: ProfileHashBase<string>;
61
59
  readonly meta: Record<string, unknown>;
62
60
  }
61
+
62
+ export type ContractModelsMap<TContract extends Contract> =
63
+ TContract extends Contract<StorageBase, infer TModels> ? TModels : never;
64
+
65
+ type ExactlyOneKey<T extends Record<string, unknown>> = keyof T extends infer Only extends keyof T
66
+ ? [keyof T] extends [Only]
67
+ ? Only
68
+ : never
69
+ : never;
70
+
71
+ type NamespaceValueObjectsOf<TNamespace> = TNamespace extends {
72
+ readonly valueObjects?: infer VO;
73
+ }
74
+ ? VO extends Record<string, ContractValueObject>
75
+ ? VO
76
+ : Record<never, never>
77
+ : Record<never, never>;
78
+
79
+ /** Value-object map for the contract's sole domain namespace (type-level single-namespace projection). */
80
+ export type ContractValueObjectsMap<TContract extends Contract> =
81
+ NamespaceValueObjectsOf<
82
+ TContract['domain']['namespaces'][ExactlyOneKey<TContract['domain']['namespaces']>]
83
+ > extends infer Projected
84
+ ? Projected extends Record<string, ContractValueObject>
85
+ ? Projected
86
+ : Record<never, never>
87
+ : Record<never, never>;
@@ -10,3 +10,10 @@ export class ContractValidationError extends Error {
10
10
  this.phase = phase;
11
11
  }
12
12
  }
13
+
14
+ export class DomainNamespaceResolutionError extends Error {
15
+ constructor(message: string) {
16
+ super(message);
17
+ this.name = 'DomainNamespaceResolutionError';
18
+ }
19
+ }
@@ -0,0 +1,87 @@
1
+ import { DomainNamespaceResolutionError } from './contract-validation-error';
2
+ import type { ContractModelBase, ContractValueObject } from './domain-types';
3
+
4
+ export const UNBOUND_DOMAIN_NAMESPACE_ID = '__unbound__' as const;
5
+
6
+ /**
7
+ * One namespace's application-domain entities — models and optional value
8
+ * objects keyed by entity name within that namespace coordinate.
9
+ */
10
+ export interface ApplicationDomainNamespace<
11
+ TModels extends Record<string, ContractModelBase> = Record<string, ContractModelBase>,
12
+ > {
13
+ readonly models: TModels;
14
+ readonly valueObjects?: Record<string, ContractValueObject>;
15
+ }
16
+
17
+ /**
18
+ * Application-domain envelope: entity content keyed by namespace id.
19
+ * Mirrors the storage plane's `namespaces` segment (ADR 221).
20
+ */
21
+ export interface ApplicationDomain<
22
+ TModels extends Record<string, ContractModelBase> = Record<string, ContractModelBase>,
23
+ > {
24
+ readonly namespaces: Readonly<Record<string, ApplicationDomainNamespace<TModels>>>;
25
+ }
26
+
27
+ export type ContractWithDomain = {
28
+ readonly domain: ApplicationDomain;
29
+ };
30
+
31
+ export function resolveSingleDomainNamespaceId(
32
+ domain: ApplicationDomain,
33
+ namespaceId?: string,
34
+ ): string {
35
+ if (namespaceId !== undefined) {
36
+ if (!Object.hasOwn(domain.namespaces, namespaceId)) {
37
+ throw new DomainNamespaceResolutionError(
38
+ `domain namespace "${namespaceId}" is not present on the contract`,
39
+ );
40
+ }
41
+ return namespaceId;
42
+ }
43
+
44
+ const namespaceIds = Object.keys(domain.namespaces);
45
+ if (namespaceIds.length === 0) {
46
+ throw new DomainNamespaceResolutionError('domain has no namespaces');
47
+ }
48
+ if (namespaceIds.length > 1) {
49
+ throw new DomainNamespaceResolutionError(
50
+ `expected exactly one domain namespace, found ${namespaceIds.length} (${namespaceIds.join(', ')})`,
51
+ );
52
+ }
53
+ const [soleNamespaceId] = namespaceIds;
54
+ if (soleNamespaceId === undefined) {
55
+ throw new DomainNamespaceResolutionError('domain has no namespaces');
56
+ }
57
+ return soleNamespaceId;
58
+ }
59
+
60
+ // Transitional single-namespace projection; pending runtime-qualification slice.
61
+ export function contractModels<TModels extends Record<string, ContractModelBase>>(
62
+ contract: { readonly domain: ApplicationDomain<TModels> },
63
+ namespaceId?: string,
64
+ ): TModels {
65
+ const resolved = resolveSingleDomainNamespaceId(contract.domain, namespaceId);
66
+ const domainNamespace = contract.domain.namespaces[resolved];
67
+ if (domainNamespace === undefined) {
68
+ throw new DomainNamespaceResolutionError(
69
+ `domain namespace "${resolved}" is not present on the contract`,
70
+ );
71
+ }
72
+ return domainNamespace.models;
73
+ }
74
+
75
+ export function contractValueObjects<TModels extends Record<string, ContractModelBase>>(
76
+ contract: { readonly domain: ApplicationDomain<TModels> },
77
+ namespaceId?: string,
78
+ ): Record<string, ContractValueObject> | undefined {
79
+ const resolved = resolveSingleDomainNamespaceId(contract.domain, namespaceId);
80
+ const domainNamespace = contract.domain.namespaces[resolved];
81
+ if (domainNamespace === undefined) {
82
+ throw new DomainNamespaceResolutionError(
83
+ `domain namespace "${resolved}" is not present on the contract`,
84
+ );
85
+ }
86
+ return domainNamespace.valueObjects;
87
+ }
@@ -74,24 +74,20 @@ export interface ContractModel<TModelStorage extends ModelStorageBase = ModelSto
74
74
 
75
75
  // ── Relation key helpers ─────────────────────────────────────────────────────
76
76
 
77
- type HasModelsWithRelations = {
78
- readonly models: Record<string, { readonly relations: Record<string, ContractRelation> }>;
79
- };
80
-
81
77
  export type ReferenceRelationKeys<
82
- TContract extends HasModelsWithRelations,
83
- ModelName extends string & keyof TContract['models'],
78
+ TModels extends Record<string, { readonly relations: Record<string, ContractRelation> }>,
79
+ ModelName extends string & keyof TModels,
84
80
  > = {
85
- [K in keyof TContract['models'][ModelName]['relations']]: TContract['models'][ModelName]['relations'][K] extends ContractReferenceRelation
81
+ [K in keyof TModels[ModelName]['relations']]: TModels[ModelName]['relations'][K] extends ContractReferenceRelation
86
82
  ? K
87
83
  : never;
88
- }[keyof TContract['models'][ModelName]['relations']];
84
+ }[keyof TModels[ModelName]['relations']];
89
85
 
90
86
  export type EmbedRelationKeys<
91
- TContract extends HasModelsWithRelations,
92
- ModelName extends string & keyof TContract['models'],
87
+ TModels extends Record<string, { readonly relations: Record<string, ContractRelation> }>,
88
+ ModelName extends string & keyof TModels,
93
89
  > = {
94
- [K in keyof TContract['models'][ModelName]['relations']]: TContract['models'][ModelName]['relations'][K] extends ContractReferenceRelation
90
+ [K in keyof TModels[ModelName]['relations']]: TModels[ModelName]['relations'][K] extends ContractReferenceRelation
95
91
  ? never
96
92
  : K;
97
- }[keyof TContract['models'][ModelName]['relations']];
93
+ }[keyof TModels[ModelName]['relations']];
@@ -1,4 +1,5 @@
1
1
  export {
2
2
  ContractValidationError,
3
3
  type ContractValidationPhase,
4
+ DomainNamespaceResolutionError,
4
5
  } from '../contract-validation-error';
@@ -1,6 +1,23 @@
1
- export type { Contract, ContractExecutionSection } from '../contract-types';
1
+ export type {
2
+ Contract,
3
+ ContractExecutionSection,
4
+ ContractModelsMap,
5
+ ContractValueObjectsMap,
6
+ } from '../contract-types';
7
+ export { DomainNamespaceResolutionError } from '../contract-validation-error';
2
8
  export type { CrossReference } from '../cross-reference';
3
9
  export { CrossReferenceSchema, crossRef } from '../cross-reference';
10
+ export type {
11
+ ApplicationDomain,
12
+ ApplicationDomainNamespace,
13
+ ContractWithDomain,
14
+ } from '../domain-envelope';
15
+ export {
16
+ contractModels,
17
+ contractValueObjects,
18
+ resolveSingleDomainNamespaceId,
19
+ UNBOUND_DOMAIN_NAMESPACE_ID,
20
+ } from '../domain-envelope';
4
21
  export type {
5
22
  ContractDiscriminator,
6
23
  ContractEmbedRelation,
package/src/hashing.ts CHANGED
@@ -34,7 +34,7 @@ function hashContract(section: HashContractSection): string {
34
34
  targetFamily: sectionData['targetFamily'],
35
35
  target: sectionData['target'],
36
36
  roots: {},
37
- models: {},
37
+ domain: { namespaces: {} },
38
38
  storage: sectionData['storage'] ?? {},
39
39
  execution: sectionData['execution'],
40
40
  extensionPacks: {},