@prisma-next/contract 0.11.0-dev.68 → 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.
- package/dist/{canonicalization-C3dTO0j1.d.mts → canonicalization-DFE0HJkI.d.mts} +2 -2
- package/dist/canonicalization-DFE0HJkI.d.mts.map +1 -0
- package/dist/canonicalization-path-match-b2jFuEso.mjs +25 -0
- package/dist/canonicalization-path-match-b2jFuEso.mjs.map +1 -0
- package/dist/{contract-types-CZPm4Ooy.d.mts → contract-types-xgwKtd7y.d.mts} +13 -74
- package/dist/contract-types-xgwKtd7y.d.mts.map +1 -0
- package/dist/contract-validation-error-ClZaKqMW.mjs +20 -0
- package/dist/contract-validation-error-ClZaKqMW.mjs.map +1 -0
- package/dist/contract-validation-error-T5LH4DW-.d.mts +13 -0
- package/dist/contract-validation-error-T5LH4DW-.d.mts.map +1 -0
- package/dist/contract-validation-error.d.mts +2 -10
- package/dist/contract-validation-error.mjs +2 -2
- package/dist/domain-envelope-4hyFtJ4_.d.mts +110 -0
- package/dist/domain-envelope-4hyFtJ4_.d.mts.map +1 -0
- package/dist/hashing-utils.d.mts +1 -1
- package/dist/hashing-utils.mjs +1 -22
- package/dist/hashing-utils.mjs.map +1 -1
- package/dist/hashing.d.mts +2 -2
- package/dist/hashing.mjs +175 -1
- package/dist/hashing.mjs.map +1 -0
- package/dist/namespace-id-CVpkSFUK.mjs +9 -0
- package/dist/namespace-id-CVpkSFUK.mjs.map +1 -0
- package/dist/types.d.mts +4 -3
- package/dist/types.mjs +73 -7
- package/dist/types.mjs.map +1 -1
- package/dist/validate-domain.d.mts +2 -6
- package/dist/validate-domain.d.mts.map +1 -1
- package/dist/validate-domain.mjs +98 -61
- package/dist/validate-domain.mjs.map +1 -1
- package/package.json +4 -6
- package/src/canonicalization.ts +41 -25
- package/src/contract-types.ts +31 -6
- package/src/contract-validation-error.ts +7 -0
- package/src/domain-envelope.ts +87 -0
- package/src/domain-types.ts +8 -12
- package/src/exports/contract-validation-error.ts +1 -0
- package/src/exports/types.ts +18 -1
- package/src/hashing.ts +1 -1
- package/src/validate-domain.ts +154 -93
- package/dist/canonicalization-C3dTO0j1.d.mts.map +0 -1
- package/dist/contract-types-CZPm4Ooy.d.mts.map +0 -1
- package/dist/contract-validation-error-Dp2vHZt5.mjs +0 -14
- package/dist/contract-validation-error-Dp2vHZt5.mjs.map +0 -1
- package/dist/contract-validation-error.d.mts.map +0 -1
- package/dist/cross-reference-t0TDbBTP.d.mts +0 -18
- package/dist/cross-reference-t0TDbBTP.d.mts.map +0 -1
- package/dist/hashing-C25nwocN.mjs +0 -151
- package/dist/hashing-C25nwocN.mjs.map +0 -1
- package/dist/testing.d.mts +0 -36
- package/dist/testing.d.mts.map +0 -1
- package/dist/testing.mjs +0 -65
- package/dist/testing.mjs.map +0 -1
- package/dist/types-CVGwkRLa.mjs +0 -46
- package/dist/types-CVGwkRLa.mjs.map +0 -1
- package/src/exports/testing.ts +0 -1
- package/src/testing-factories.ts +0 -121
package/src/canonicalization.ts
CHANGED
|
@@ -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
|
|
96
|
-
const
|
|
97
|
-
const
|
|
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
|
-
|
|
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
|
-
!
|
|
129
|
-
!
|
|
130
|
-
!
|
|
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
|
-
|
|
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'],
|
package/src/contract-types.ts
CHANGED
|
@@ -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
|
-
*
|
|
52
|
-
*
|
|
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
|
|
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
|
+
}
|
package/src/domain-types.ts
CHANGED
|
@@ -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
|
-
|
|
83
|
-
ModelName extends string & keyof
|
|
78
|
+
TModels extends Record<string, { readonly relations: Record<string, ContractRelation> }>,
|
|
79
|
+
ModelName extends string & keyof TModels,
|
|
84
80
|
> = {
|
|
85
|
-
[K in keyof
|
|
81
|
+
[K in keyof TModels[ModelName]['relations']]: TModels[ModelName]['relations'][K] extends ContractReferenceRelation
|
|
86
82
|
? K
|
|
87
83
|
: never;
|
|
88
|
-
}[keyof
|
|
84
|
+
}[keyof TModels[ModelName]['relations']];
|
|
89
85
|
|
|
90
86
|
export type EmbedRelationKeys<
|
|
91
|
-
|
|
92
|
-
ModelName extends string & keyof
|
|
87
|
+
TModels extends Record<string, { readonly relations: Record<string, ContractRelation> }>,
|
|
88
|
+
ModelName extends string & keyof TModels,
|
|
93
89
|
> = {
|
|
94
|
-
[K in keyof
|
|
90
|
+
[K in keyof TModels[ModelName]['relations']]: TModels[ModelName]['relations'][K] extends ContractReferenceRelation
|
|
95
91
|
? never
|
|
96
92
|
: K;
|
|
97
|
-
}[keyof
|
|
93
|
+
}[keyof TModels[ModelName]['relations']];
|
package/src/exports/types.ts
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
|
-
export type {
|
|
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
|
-
|
|
37
|
+
domain: { namespaces: {} },
|
|
38
38
|
storage: sectionData['storage'] ?? {},
|
|
39
39
|
execution: sectionData['execution'],
|
|
40
40
|
extensionPacks: {},
|