@prisma-next/contract 0.3.0-pr.99.6 → 0.3.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/LICENSE +201 -0
- package/README.md +43 -254
- package/dist/contract-types-MYdoYIIh.d.mts +314 -0
- package/dist/contract-types-MYdoYIIh.d.mts.map +1 -0
- package/dist/hashing-CyaA_Qvf.mjs +196 -0
- package/dist/hashing-CyaA_Qvf.mjs.map +1 -0
- package/dist/hashing.d.mts +29 -0
- package/dist/hashing.d.mts.map +1 -0
- package/dist/hashing.mjs +3 -0
- package/dist/testing.d.mts +29 -0
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.mjs +58 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/types-aMyNgejf.mjs +14 -0
- package/dist/types-aMyNgejf.mjs.map +1 -0
- package/dist/types.d.mts +2 -2
- package/dist/types.mjs +2 -10
- package/dist/validate-contract.d.mts +37 -0
- package/dist/validate-contract.d.mts.map +1 -0
- package/dist/validate-contract.mjs +3 -0
- package/dist/validate-domain-CpCcTlqJ.mjs +165 -0
- package/dist/validate-domain-CpCcTlqJ.mjs.map +1 -0
- package/dist/validate-domain.d.mts +24 -0
- package/dist/validate-domain.d.mts.map +1 -0
- package/dist/validate-domain.mjs +3 -0
- package/package.json +15 -8
- package/schemas/data-contract-document-v1.json +5 -5
- package/src/canonicalization.ts +263 -0
- package/src/contract-types.ts +55 -0
- package/src/domain-types.ts +95 -0
- package/src/exports/hashing.ts +2 -0
- package/src/exports/testing.ts +1 -0
- package/src/exports/types.ts +36 -20
- package/src/exports/validate-contract.ts +6 -0
- package/src/exports/validate-domain.ts +5 -0
- package/src/hashing.ts +53 -0
- package/src/testing-factories.ts +101 -0
- package/src/types.ts +102 -248
- package/src/validate-contract.ts +101 -0
- package/src/validate-domain.ts +227 -0
- package/dist/framework-components-B-XOtXw3.d.mts +0 -854
- package/dist/framework-components-B-XOtXw3.d.mts.map +0 -1
- package/dist/framework-components.d.mts +0 -2
- package/dist/framework-components.mjs +0 -70
- package/dist/framework-components.mjs.map +0 -1
- package/dist/ir-B8zNqals.d.mts +0 -79
- package/dist/ir-B8zNqals.d.mts.map +0 -1
- package/dist/ir.d.mts +0 -2
- package/dist/ir.mjs +0 -46
- package/dist/ir.mjs.map +0 -1
- package/dist/pack-manifest-types.d.mts +0 -2
- package/dist/pack-manifest-types.mjs +0 -1
- package/dist/types.mjs.map +0 -1
- package/src/exports/framework-components.ts +0 -36
- package/src/exports/ir.ts +0 -1
- package/src/exports/pack-manifest-types.ts +0 -6
- package/src/framework-components.ts +0 -717
- package/src/ir.ts +0 -113
package/src/types.ts
CHANGED
|
@@ -1,17 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Unique symbol used as the key for branding types.
|
|
3
|
+
*/
|
|
4
|
+
export const $: unique symbol = Symbol('__prisma_next_brand__');
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
6
|
+
/**
|
|
7
|
+
* A helper type to brand a given type with a unique identifier.
|
|
8
|
+
*
|
|
9
|
+
* @template TKey Text used as the brand key.
|
|
10
|
+
* @template TValue Optional value associated with the brand key. Defaults to `true`.
|
|
11
|
+
*/
|
|
12
|
+
export type Brand<TKey extends string | number | symbol, TValue = true> = {
|
|
13
|
+
[$]: {
|
|
14
|
+
[K in TKey]: TValue;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Base type for storage contract hashes.
|
|
20
|
+
* Emitted contract.d.ts files use this with the hash value as a type parameter:
|
|
21
|
+
* `type StorageHash = StorageHashBase<'sha256:abc123...'>`
|
|
22
|
+
*/
|
|
23
|
+
export type StorageHashBase<THash extends string> = THash & Brand<'StorageHash'>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Base type for execution contract hashes.
|
|
27
|
+
* Emitted contract.d.ts files use this with the hash value as a type parameter:
|
|
28
|
+
* `type ExecutionHash = ExecutionHashBase<'sha256:def456...'>`
|
|
29
|
+
*/
|
|
30
|
+
export type ExecutionHashBase<THash extends string> = THash & Brand<'ExecutionHash'>;
|
|
31
|
+
|
|
32
|
+
export function executionHash<const T extends string>(value: T): ExecutionHashBase<T> {
|
|
33
|
+
return value as ExecutionHashBase<T>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function coreHash<const T extends string>(value: T): StorageHashBase<T> {
|
|
37
|
+
return value as StorageHashBase<T>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Base type for profile contract hashes.
|
|
42
|
+
* Emitted contract.d.ts files use this with the hash value as a type parameter:
|
|
43
|
+
* `type ProfileHash = ProfileHashBase<'sha256:def456...'>`
|
|
44
|
+
*/
|
|
45
|
+
export type ProfileHashBase<THash extends string> = THash & Brand<'ProfileHash'>;
|
|
46
|
+
|
|
47
|
+
export function profileHash<const T extends string>(value: T): ProfileHashBase<T> {
|
|
48
|
+
return value as ProfileHashBase<T>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Base type for family-specific storage blocks.
|
|
53
|
+
* Family storage types (SqlStorage, MongoStorage, etc.) extend this to carry the
|
|
54
|
+
* storage hash alongside family-specific data (tables, collections, etc.).
|
|
55
|
+
*/
|
|
56
|
+
export interface StorageBase<THash extends string = string> {
|
|
57
|
+
readonly storageHash: StorageHashBase<THash>;
|
|
15
58
|
}
|
|
16
59
|
|
|
17
60
|
export interface FieldType {
|
|
@@ -21,6 +64,48 @@ export interface FieldType {
|
|
|
21
64
|
readonly properties?: Record<string, FieldType>;
|
|
22
65
|
}
|
|
23
66
|
|
|
67
|
+
export type GeneratedValueSpec = {
|
|
68
|
+
readonly id: string;
|
|
69
|
+
readonly params?: Record<string, unknown>;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
73
|
+
|
|
74
|
+
export type JsonValue =
|
|
75
|
+
| JsonPrimitive
|
|
76
|
+
| { readonly [key: string]: JsonValue }
|
|
77
|
+
| readonly JsonValue[];
|
|
78
|
+
|
|
79
|
+
export type ColumnDefaultLiteralValue = JsonValue;
|
|
80
|
+
|
|
81
|
+
export type ColumnDefaultLiteralInputValue = ColumnDefaultLiteralValue | Date;
|
|
82
|
+
|
|
83
|
+
export type ColumnDefault =
|
|
84
|
+
| {
|
|
85
|
+
readonly kind: 'literal';
|
|
86
|
+
readonly value: ColumnDefaultLiteralInputValue;
|
|
87
|
+
}
|
|
88
|
+
| { readonly kind: 'function'; readonly expression: string };
|
|
89
|
+
|
|
90
|
+
export type ExecutionMutationDefaultValue = {
|
|
91
|
+
readonly kind: 'generator';
|
|
92
|
+
readonly id: GeneratedValueSpec['id'];
|
|
93
|
+
readonly params?: Record<string, unknown>;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type ExecutionMutationDefault = {
|
|
97
|
+
readonly ref: { readonly table: string; readonly column: string };
|
|
98
|
+
readonly onCreate?: ExecutionMutationDefaultValue;
|
|
99
|
+
readonly onUpdate?: ExecutionMutationDefaultValue;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export type ExecutionSection<THash extends string = string> = {
|
|
103
|
+
readonly executionHash: ExecutionHashBase<THash>;
|
|
104
|
+
readonly mutations: {
|
|
105
|
+
readonly defaults: ReadonlyArray<ExecutionMutationDefault>;
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
24
109
|
export interface Source {
|
|
25
110
|
readonly readOnly: boolean;
|
|
26
111
|
readonly projection: Record<string, FieldType>;
|
|
@@ -43,25 +128,13 @@ export type Expr =
|
|
|
43
128
|
export interface DocCollection {
|
|
44
129
|
readonly name: string;
|
|
45
130
|
readonly id?: {
|
|
46
|
-
readonly strategy: 'auto' | 'client' | 'uuid' | '
|
|
131
|
+
readonly strategy: 'auto' | 'client' | 'uuid' | 'objectId';
|
|
47
132
|
};
|
|
48
133
|
readonly fields: Record<string, FieldType>;
|
|
49
134
|
readonly indexes?: ReadonlyArray<DocIndex>;
|
|
50
135
|
readonly readOnly?: boolean;
|
|
51
136
|
}
|
|
52
137
|
|
|
53
|
-
export interface DocumentStorage {
|
|
54
|
-
readonly document: {
|
|
55
|
-
readonly collections: Record<string, DocCollection>;
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface DocumentContract extends ContractBase {
|
|
60
|
-
// Accept string to work with JSON imports; runtime validation ensures 'document'
|
|
61
|
-
readonly targetFamily: string;
|
|
62
|
-
readonly storage: DocumentStorage;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
138
|
// Plan types - target-family agnostic execution types
|
|
66
139
|
export interface ParamDescriptor {
|
|
67
140
|
readonly index?: number;
|
|
@@ -69,7 +142,7 @@ export interface ParamDescriptor {
|
|
|
69
142
|
readonly codecId?: string;
|
|
70
143
|
readonly nativeType?: string;
|
|
71
144
|
readonly nullable?: boolean;
|
|
72
|
-
readonly source: 'dsl' | 'raw';
|
|
145
|
+
readonly source: 'dsl' | 'raw' | 'lane';
|
|
73
146
|
readonly refs?: { table: string; column: string };
|
|
74
147
|
}
|
|
75
148
|
|
|
@@ -86,7 +159,7 @@ export interface PlanRefs {
|
|
|
86
159
|
export interface PlanMeta {
|
|
87
160
|
readonly target: string;
|
|
88
161
|
readonly targetFamily?: string;
|
|
89
|
-
readonly
|
|
162
|
+
readonly storageHash: string;
|
|
90
163
|
readonly profileHash?: string;
|
|
91
164
|
readonly lane: string;
|
|
92
165
|
readonly annotations?: {
|
|
@@ -135,24 +208,12 @@ export interface ExecutionPlan<Row = unknown, Ast = unknown> {
|
|
|
135
208
|
export type ResultType<P> =
|
|
136
209
|
P extends ExecutionPlan<infer R, unknown> ? R : P extends { readonly _Row?: infer R } ? R : never;
|
|
137
210
|
|
|
138
|
-
/**
|
|
139
|
-
* Type guard to check if a contract is a Document contract
|
|
140
|
-
*/
|
|
141
|
-
export function isDocumentContract(contract: unknown): contract is DocumentContract {
|
|
142
|
-
return (
|
|
143
|
-
typeof contract === 'object' &&
|
|
144
|
-
contract !== null &&
|
|
145
|
-
'targetFamily' in contract &&
|
|
146
|
-
contract.targetFamily === 'document'
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
211
|
/**
|
|
151
212
|
* Contract marker record stored in the database.
|
|
152
213
|
* Represents the current contract identity for a database.
|
|
153
214
|
*/
|
|
154
215
|
export interface ContractMarkerRecord {
|
|
155
|
-
readonly
|
|
216
|
+
readonly storageHash: string;
|
|
156
217
|
readonly profileHash: string;
|
|
157
218
|
readonly contractJson: unknown | null;
|
|
158
219
|
readonly canonicalVersion: number | null;
|
|
@@ -160,210 +221,3 @@ export interface ContractMarkerRecord {
|
|
|
160
221
|
readonly appTag: string | null;
|
|
161
222
|
readonly meta: Record<string, unknown>;
|
|
162
223
|
}
|
|
163
|
-
|
|
164
|
-
// Emitter types - moved from @prisma-next/emitter to shared location
|
|
165
|
-
/**
|
|
166
|
-
* Specifies how to import TypeScript types from a package.
|
|
167
|
-
* Used in extension pack manifests to declare codec and operation type imports.
|
|
168
|
-
*/
|
|
169
|
-
export interface TypesImportSpec {
|
|
170
|
-
readonly package: string;
|
|
171
|
-
readonly named: string;
|
|
172
|
-
readonly alias: string;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Validation context passed to TargetFamilyHook.validateTypes().
|
|
177
|
-
* Contains pre-assembled operation registry, type imports, and extension IDs.
|
|
178
|
-
*/
|
|
179
|
-
export interface ValidationContext {
|
|
180
|
-
readonly operationRegistry?: OperationRegistry;
|
|
181
|
-
readonly codecTypeImports?: ReadonlyArray<TypesImportSpec>;
|
|
182
|
-
readonly operationTypeImports?: ReadonlyArray<TypesImportSpec>;
|
|
183
|
-
readonly extensionIds?: ReadonlyArray<string>;
|
|
184
|
-
/**
|
|
185
|
-
* Parameterized codec descriptors collected from adapters and extensions.
|
|
186
|
-
* Map of codecId → descriptor for quick lookup during type generation.
|
|
187
|
-
*/
|
|
188
|
-
readonly parameterizedCodecs?: Map<string, ParameterizedCodecDescriptor>;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Context for rendering parameterized types during contract.d.ts generation.
|
|
193
|
-
* Passed to type renderers so they can reference CodecTypes by name.
|
|
194
|
-
*/
|
|
195
|
-
export interface TypeRenderContext {
|
|
196
|
-
readonly codecTypesName: string;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* A normalized type renderer for parameterized codecs.
|
|
201
|
-
* This is the interface expected by TargetFamilyHook.generateContractTypes.
|
|
202
|
-
*/
|
|
203
|
-
export interface TypeRenderEntry {
|
|
204
|
-
readonly codecId: string;
|
|
205
|
-
readonly render: (params: Record<string, unknown>, ctx: TypeRenderContext) => string;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Additional options for generateContractTypes.
|
|
210
|
-
*/
|
|
211
|
-
export interface GenerateContractTypesOptions {
|
|
212
|
-
/**
|
|
213
|
-
* Normalized parameterized type renderers, keyed by codecId.
|
|
214
|
-
* When a column has typeParams and a renderer exists for its codecId,
|
|
215
|
-
* the renderer is called to produce the TypeScript type expression.
|
|
216
|
-
*/
|
|
217
|
-
readonly parameterizedRenderers?: Map<string, TypeRenderEntry>;
|
|
218
|
-
/**
|
|
219
|
-
* Type imports for parameterized codecs.
|
|
220
|
-
* These are merged with codec and operation type imports in contract.d.ts.
|
|
221
|
-
*/
|
|
222
|
-
readonly parameterizedTypeImports?: ReadonlyArray<TypesImportSpec>;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* SPI interface for target family hooks that extend emission behavior.
|
|
227
|
-
* Implemented by family-specific emitter hooks (e.g., SQL family).
|
|
228
|
-
*/
|
|
229
|
-
export interface TargetFamilyHook {
|
|
230
|
-
readonly id: string;
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Validates that all type IDs in the contract come from referenced extension packs.
|
|
234
|
-
* @param ir - Contract IR to validate
|
|
235
|
-
* @param ctx - Validation context with operation registry and extension IDs
|
|
236
|
-
*/
|
|
237
|
-
validateTypes(ir: ContractIR, ctx: ValidationContext): void;
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Validates family-specific contract structure.
|
|
241
|
-
* @param ir - Contract IR to validate
|
|
242
|
-
*/
|
|
243
|
-
validateStructure(ir: ContractIR): void;
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Generates contract.d.ts file content.
|
|
247
|
-
* @param ir - Contract IR
|
|
248
|
-
* @param codecTypeImports - Array of codec type import specs
|
|
249
|
-
* @param operationTypeImports - Array of operation type import specs
|
|
250
|
-
* @param options - Additional options including parameterized type renderers
|
|
251
|
-
* @returns Generated TypeScript type definitions as string
|
|
252
|
-
*/
|
|
253
|
-
generateContractTypes(
|
|
254
|
-
ir: ContractIR,
|
|
255
|
-
codecTypeImports: ReadonlyArray<TypesImportSpec>,
|
|
256
|
-
operationTypeImports: ReadonlyArray<TypesImportSpec>,
|
|
257
|
-
options?: GenerateContractTypesOptions,
|
|
258
|
-
): string;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Extension pack manifest types - moved from @prisma-next/core-control-plane to shared location
|
|
262
|
-
export type ArgSpecManifest =
|
|
263
|
-
| { readonly kind: 'typeId'; readonly type: string }
|
|
264
|
-
| { readonly kind: 'param' }
|
|
265
|
-
| { readonly kind: 'literal' };
|
|
266
|
-
|
|
267
|
-
export type ReturnSpecManifest =
|
|
268
|
-
| { readonly kind: 'typeId'; readonly type: string }
|
|
269
|
-
| { readonly kind: 'builtin'; readonly type: 'number' | 'boolean' | 'string' };
|
|
270
|
-
|
|
271
|
-
export interface LoweringSpecManifest {
|
|
272
|
-
readonly targetFamily: 'sql';
|
|
273
|
-
readonly strategy: 'infix' | 'function';
|
|
274
|
-
readonly template: string;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
export interface OperationManifest {
|
|
278
|
-
readonly for: string;
|
|
279
|
-
readonly method: string;
|
|
280
|
-
readonly args: ReadonlyArray<ArgSpecManifest>;
|
|
281
|
-
readonly returns: ReturnSpecManifest;
|
|
282
|
-
readonly lowering: LoweringSpecManifest;
|
|
283
|
-
readonly capabilities?: ReadonlyArray<string>;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// ============================================================================
|
|
287
|
-
// Parameterized Codec Descriptor Types
|
|
288
|
-
// ============================================================================
|
|
289
|
-
//
|
|
290
|
-
// Types for codecs that support type parameters (e.g., Vector<1536>, Decimal<2>).
|
|
291
|
-
// These enable precise TypeScript types for parameterized columns without
|
|
292
|
-
// coupling the SQL family emitter to specific adapter codec IDs.
|
|
293
|
-
//
|
|
294
|
-
// ============================================================================
|
|
295
|
-
|
|
296
|
-
// Re-export RenderTypeContext so it's available alongside TypeRenderer
|
|
297
|
-
export type { RenderTypeContext };
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Declarative type renderer that produces a TypeScript type expression.
|
|
301
|
-
*
|
|
302
|
-
* Renderers can be:
|
|
303
|
-
* - A template string with `{{paramName}}` placeholders (e.g., `Vector<{{length}}>`)
|
|
304
|
-
* - A function that receives typeParams and context and returns a type expression
|
|
305
|
-
*
|
|
306
|
-
* **Prefer template strings** for most cases:
|
|
307
|
-
* - Templates are JSON-serializable (safe for pack-ref metadata)
|
|
308
|
-
* - Templates can be statically analyzed by tooling
|
|
309
|
-
*
|
|
310
|
-
* Function renderers are allowed but have tradeoffs:
|
|
311
|
-
* - Require runtime execution during emission (the emitter runs code)
|
|
312
|
-
* - Not JSON-serializable (can't be stored in contract.json)
|
|
313
|
-
* - The emitted artifacts (contract.json, contract.d.ts) still contain no
|
|
314
|
-
* executable code - this constraint applies to outputs, not the emission process
|
|
315
|
-
*/
|
|
316
|
-
export type TypeRenderer =
|
|
317
|
-
| string
|
|
318
|
-
| ((params: Record<string, unknown>, ctx: RenderTypeContext) => string);
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Descriptor for a codec that supports type parameters.
|
|
322
|
-
*
|
|
323
|
-
* Parameterized codecs allow columns to carry additional metadata (typeParams)
|
|
324
|
-
* that affects the generated TypeScript types. For example:
|
|
325
|
-
* - A vector codec can use `{ length: 1536 }` to generate `Vector<1536>`
|
|
326
|
-
* - A decimal codec can use `{ precision: 10, scale: 2 }` to generate `Decimal<10, 2>`
|
|
327
|
-
*
|
|
328
|
-
* The SQL family emitter uses these descriptors to generate precise types
|
|
329
|
-
* without hard-coding knowledge of specific codec IDs.
|
|
330
|
-
*
|
|
331
|
-
* @example
|
|
332
|
-
* ```typescript
|
|
333
|
-
* const vectorCodecDescriptor: ParameterizedCodecDescriptor = {
|
|
334
|
-
* codecId: 'pg/vector@1',
|
|
335
|
-
* outputTypeRenderer: 'Vector<{{length}}>',
|
|
336
|
-
* // Optional: paramsSchema for runtime validation
|
|
337
|
-
* };
|
|
338
|
-
* ```
|
|
339
|
-
*/
|
|
340
|
-
export interface ParameterizedCodecDescriptor {
|
|
341
|
-
/** The codec ID this descriptor applies to (e.g., 'pg/vector@1') */
|
|
342
|
-
readonly codecId: string;
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Renderer for the output (read) type.
|
|
346
|
-
* Can be a template string or function.
|
|
347
|
-
*
|
|
348
|
-
* This is the primary renderer used by SQL emission to generate
|
|
349
|
-
* model field types in contract.d.ts.
|
|
350
|
-
*/
|
|
351
|
-
readonly outputTypeRenderer: TypeRenderer;
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Optional renderer for the input (write) type.
|
|
355
|
-
* If not provided, outputTypeRenderer is used for both.
|
|
356
|
-
*
|
|
357
|
-
* **Reserved for future use**: Currently, SQL emission only uses
|
|
358
|
-
* outputTypeRenderer. This field is defined for future support of
|
|
359
|
-
* asymmetric codecs where input and output types differ (e.g., a
|
|
360
|
-
* codec that accepts `string | number` but always returns `number`).
|
|
361
|
-
*/
|
|
362
|
-
readonly inputTypeRenderer?: TypeRenderer;
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Optional import spec for types used by this codec's renderers.
|
|
366
|
-
* The emitter will add this import to contract.d.ts.
|
|
367
|
-
*/
|
|
368
|
-
readonly typesImport?: TypesImportSpec;
|
|
369
|
-
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { type } from 'arktype';
|
|
2
|
+
import type { Contract } from './contract-types';
|
|
3
|
+
import type { DomainContractShape } from './validate-domain';
|
|
4
|
+
import { validateContractDomain } from './validate-domain';
|
|
5
|
+
|
|
6
|
+
export type ContractValidationPhase = 'structural' | 'domain' | 'storage';
|
|
7
|
+
|
|
8
|
+
export class ContractValidationError extends Error {
|
|
9
|
+
readonly code = 'CONTRACT.VALIDATION_FAILED';
|
|
10
|
+
readonly phase: ContractValidationPhase;
|
|
11
|
+
|
|
12
|
+
constructor(message: string, phase: ContractValidationPhase) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'ContractValidationError';
|
|
15
|
+
this.phase = phase;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Family-provided storage validator.
|
|
21
|
+
* SQL validates tables/columns/FKs; Mongo validates collections/embedding.
|
|
22
|
+
*/
|
|
23
|
+
export type StorageValidator = (contract: Contract) => void;
|
|
24
|
+
|
|
25
|
+
const ContractSchema = type({
|
|
26
|
+
target: 'string',
|
|
27
|
+
targetFamily: 'string',
|
|
28
|
+
roots: 'Record<string, string>',
|
|
29
|
+
models: 'Record<string, unknown>',
|
|
30
|
+
'valueObjects?': 'Record<string, unknown>',
|
|
31
|
+
storage: 'Record<string, unknown>',
|
|
32
|
+
capabilities: 'Record<string, Record<string, boolean>>',
|
|
33
|
+
extensionPacks: 'Record<string, unknown>',
|
|
34
|
+
meta: 'Record<string, unknown>',
|
|
35
|
+
'execution?': {
|
|
36
|
+
'executionHash?': 'string',
|
|
37
|
+
mutations: {
|
|
38
|
+
defaults: 'unknown[]',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
profileHash: 'string',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function stripPersistenceFields(raw: Record<string, unknown>): Record<string, unknown> {
|
|
45
|
+
const { schemaVersion: _, _generated: _g, ...rest } = raw;
|
|
46
|
+
return rest;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function extractDomainShape(contract: Contract): DomainContractShape {
|
|
50
|
+
return {
|
|
51
|
+
roots: contract.roots,
|
|
52
|
+
models: contract.models,
|
|
53
|
+
...(contract.valueObjects ? { valueObjects: contract.valueObjects } : {}),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Framework-level contract validation (ADR 182).
|
|
59
|
+
*
|
|
60
|
+
* Three-pass validation:
|
|
61
|
+
* 1. **Structural validation** (arktype): verifies required fields exist with
|
|
62
|
+
* correct base types.
|
|
63
|
+
* 2. **Domain validation** (framework-owned): roots, relation targets,
|
|
64
|
+
* variant/base consistency, discriminators, ownership, orphans.
|
|
65
|
+
* 3. **Storage validation** (family-provided): SQL validates tables/columns/FKs;
|
|
66
|
+
* Mongo validates collections/embedding.
|
|
67
|
+
*
|
|
68
|
+
* JSON persistence fields (`schemaVersion`, `_generated`) are stripped before
|
|
69
|
+
* validation — they are not part of the in-memory contract representation.
|
|
70
|
+
*
|
|
71
|
+
* @template TContract The fully-typed contract type (preserves literal types).
|
|
72
|
+
* @param value Raw contract value (e.g. parsed from JSON).
|
|
73
|
+
* @param storageValidator Family-specific storage validation function.
|
|
74
|
+
* @returns The validated contract with full literal types.
|
|
75
|
+
*/
|
|
76
|
+
export function validateContract<TContract extends Contract>(
|
|
77
|
+
value: unknown,
|
|
78
|
+
storageValidator: StorageValidator,
|
|
79
|
+
): TContract {
|
|
80
|
+
if (typeof value !== 'object' || value === null) {
|
|
81
|
+
throw new ContractValidationError('Contract must be a non-null object', 'structural');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const stripped = stripPersistenceFields(value as Record<string, unknown>);
|
|
85
|
+
|
|
86
|
+
const parsed = ContractSchema(stripped);
|
|
87
|
+
if (parsed instanceof type.errors) {
|
|
88
|
+
throw new ContractValidationError(
|
|
89
|
+
`Invalid contract structure: ${parsed.summary}`,
|
|
90
|
+
'structural',
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const contract = parsed as unknown as Contract;
|
|
95
|
+
|
|
96
|
+
validateContractDomain(extractDomainShape(contract));
|
|
97
|
+
|
|
98
|
+
storageValidator(contract);
|
|
99
|
+
|
|
100
|
+
return contract as unknown as TContract;
|
|
101
|
+
}
|