@prisma-next/contract 0.3.0-dev.14 → 0.3.0-dev.140
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 +42 -6
- package/dist/hashing-CVS9sXxd.mjs +215 -0
- package/dist/hashing-CVS9sXxd.mjs.map +1 -0
- package/dist/hashing.d.mts +38 -0
- package/dist/hashing.d.mts.map +1 -0
- package/dist/hashing.mjs +3 -0
- package/dist/testing.d.mts +28 -0
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.mjs +56 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/types-D-iOS0Ks.d.mts +511 -0
- package/dist/types-D-iOS0Ks.d.mts.map +1 -0
- package/dist/types-DokLaU9G.mjs +30 -0
- package/dist/types-DokLaU9G.mjs.map +1 -0
- package/dist/types.d.mts +2 -0
- package/dist/types.mjs +3 -0
- package/dist/validate-contract.d.mts +35 -0
- package/dist/validate-contract.d.mts.map +1 -0
- package/dist/validate-contract.mjs +61 -0
- package/dist/validate-contract.mjs.map +1 -0
- package/dist/validate-domain-CTQiBiei.mjs +84 -0
- package/dist/validate-domain-CTQiBiei.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 +25 -25
- package/schemas/data-contract-document-v1.json +5 -5
- package/src/canonicalization.ts +286 -0
- package/src/contract-types.ts +54 -0
- package/src/domain-types.ts +85 -0
- package/src/exports/hashing.ts +6 -0
- package/src/exports/testing.ts +1 -0
- package/src/exports/types.ts +54 -7
- package/src/exports/validate-contract.ts +5 -0
- package/src/exports/validate-domain.ts +6 -0
- package/src/hashing.ts +69 -0
- package/src/testing-factories.ts +93 -0
- package/src/types.ts +292 -35
- package/src/validate-contract.ts +93 -0
- package/src/validate-domain.ts +205 -0
- package/dist/exports/framework-components.d.ts +0 -3
- package/dist/exports/framework-components.d.ts.map +0 -1
- package/dist/exports/framework-components.js +0 -24
- package/dist/exports/framework-components.js.map +0 -1
- package/dist/exports/ir.d.ts +0 -2
- package/dist/exports/ir.d.ts.map +0 -1
- package/dist/exports/ir.js +0 -35
- package/dist/exports/ir.js.map +0 -1
- package/dist/exports/pack-manifest-types.d.ts +0 -2
- package/dist/exports/pack-manifest-types.d.ts.map +0 -1
- package/dist/exports/pack-manifest-types.js +0 -1
- package/dist/exports/pack-manifest-types.js.map +0 -1
- package/dist/exports/types.d.ts +0 -3
- package/dist/exports/types.d.ts.map +0 -1
- package/dist/exports/types.js +0 -8
- package/dist/exports/types.js.map +0 -1
- package/dist/framework-components.d.ts +0 -408
- package/dist/framework-components.d.ts.map +0 -1
- package/dist/ir.d.ts +0 -76
- package/dist/ir.d.ts.map +0 -1
- package/dist/types.d.ts +0 -222
- package/dist/types.d.ts.map +0 -1
- package/src/exports/framework-components.ts +0 -26
- package/src/exports/ir.ts +0 -1
- package/src/exports/pack-manifest-types.ts +0 -6
- package/src/framework-components.ts +0 -525
- package/src/ir.ts +0 -113
package/src/types.ts
CHANGED
|
@@ -1,16 +1,88 @@
|
|
|
1
1
|
import type { OperationRegistry } from '@prisma-next/operations';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Contract } from './contract-types';
|
|
3
|
+
import type { DomainModel } from './domain-types';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Unique symbol used as the key for branding types.
|
|
7
|
+
*/
|
|
8
|
+
export const $: unique symbol = Symbol('__prisma_next_brand__');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* A helper type to brand a given type with a unique identifier.
|
|
12
|
+
*
|
|
13
|
+
* @template TKey Text used as the brand key.
|
|
14
|
+
* @template TValue Optional value associated with the brand key. Defaults to `true`.
|
|
15
|
+
*/
|
|
16
|
+
export type Brand<TKey extends string | number | symbol, TValue = true> = {
|
|
17
|
+
[$]: {
|
|
18
|
+
[K in TKey]: TValue;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Context passed to type renderers during contract.d.ts generation.
|
|
24
|
+
*/
|
|
25
|
+
export interface RenderTypeContext {
|
|
26
|
+
/** The name of the CodecTypes type alias (typically 'CodecTypes') */
|
|
27
|
+
readonly codecTypesName: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Base type for storage contract hashes.
|
|
32
|
+
* Emitted contract.d.ts files use this with the hash value as a type parameter:
|
|
33
|
+
* `type StorageHash = StorageHashBase<'sha256:abc123...'>`
|
|
34
|
+
*/
|
|
35
|
+
export type StorageHashBase<THash extends string> = THash & Brand<'StorageHash'>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Base type for execution contract hashes.
|
|
39
|
+
* Emitted contract.d.ts files use this with the hash value as a type parameter:
|
|
40
|
+
* `type ExecutionHash = ExecutionHashBase<'sha256:def456...'>`
|
|
41
|
+
*/
|
|
42
|
+
export type ExecutionHashBase<THash extends string> = THash & Brand<'ExecutionHash'>;
|
|
43
|
+
|
|
44
|
+
export function coreHash<const T extends string>(value: T): StorageHashBase<T> {
|
|
45
|
+
return value as StorageHashBase<T>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Base type for profile contract hashes.
|
|
50
|
+
* Emitted contract.d.ts files use this with the hash value as a type parameter:
|
|
51
|
+
* `type ProfileHash = ProfileHashBase<'sha256:def456...'>`
|
|
52
|
+
*/
|
|
53
|
+
export type ProfileHashBase<THash extends string> = THash & Brand<'ProfileHash'>;
|
|
54
|
+
|
|
55
|
+
export function profileHash<const T extends string>(value: T): ProfileHashBase<T> {
|
|
56
|
+
return value as ProfileHashBase<T>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Base type for family-specific storage blocks.
|
|
61
|
+
* Family storage types (SqlStorage, MongoStorage, etc.) extend this to carry the
|
|
62
|
+
* storage hash alongside family-specific data (tables, collections, etc.).
|
|
63
|
+
*/
|
|
64
|
+
export interface StorageBase<THash extends string = string> {
|
|
65
|
+
readonly storageHash: StorageHashBase<THash>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ContractBase<
|
|
69
|
+
TStorageHash extends StorageHashBase<string> = StorageHashBase<string>,
|
|
70
|
+
TExecutionHash extends ExecutionHashBase<string> = ExecutionHashBase<string>,
|
|
71
|
+
TProfileHash extends ProfileHashBase<string> = ProfileHashBase<string>,
|
|
72
|
+
> {
|
|
5
73
|
readonly schemaVersion: string;
|
|
6
74
|
readonly target: string;
|
|
7
75
|
readonly targetFamily: string;
|
|
8
|
-
readonly
|
|
9
|
-
readonly
|
|
76
|
+
readonly storageHash: TStorageHash;
|
|
77
|
+
readonly executionHash?: TExecutionHash | undefined;
|
|
78
|
+
readonly profileHash?: TProfileHash | undefined;
|
|
10
79
|
readonly capabilities: Record<string, Record<string, boolean>>;
|
|
11
80
|
readonly extensionPacks: Record<string, unknown>;
|
|
12
81
|
readonly meta: Record<string, unknown>;
|
|
13
82
|
readonly sources: Record<string, Source>;
|
|
83
|
+
readonly execution?: ExecutionSection;
|
|
84
|
+
readonly roots: Record<string, string>;
|
|
85
|
+
readonly models: Record<string, DomainModel>;
|
|
14
86
|
}
|
|
15
87
|
|
|
16
88
|
export interface FieldType {
|
|
@@ -20,6 +92,78 @@ export interface FieldType {
|
|
|
20
92
|
readonly properties?: Record<string, FieldType>;
|
|
21
93
|
}
|
|
22
94
|
|
|
95
|
+
export type GeneratedValueSpec = {
|
|
96
|
+
readonly id: string;
|
|
97
|
+
readonly params?: Record<string, unknown>;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
101
|
+
|
|
102
|
+
export type JsonValue =
|
|
103
|
+
| JsonPrimitive
|
|
104
|
+
| { readonly [key: string]: JsonValue }
|
|
105
|
+
| readonly JsonValue[];
|
|
106
|
+
|
|
107
|
+
export type TaggedBigInt = { readonly $type: 'bigint'; readonly value: string };
|
|
108
|
+
|
|
109
|
+
export function isTaggedBigInt(value: unknown): value is TaggedBigInt {
|
|
110
|
+
return (
|
|
111
|
+
typeof value === 'object' &&
|
|
112
|
+
value !== null &&
|
|
113
|
+
(value as { $type?: unknown }).$type === 'bigint' &&
|
|
114
|
+
typeof (value as { value?: unknown }).value === 'string'
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function bigintJsonReplacer(_key: string, value: unknown): unknown {
|
|
119
|
+
if (typeof value === 'bigint') {
|
|
120
|
+
return { $type: 'bigint', value: value.toString() } satisfies TaggedBigInt;
|
|
121
|
+
}
|
|
122
|
+
return value;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export type TaggedRaw = { readonly $type: 'raw'; readonly value: JsonValue };
|
|
126
|
+
|
|
127
|
+
export function isTaggedRaw(value: unknown): value is TaggedRaw {
|
|
128
|
+
return (
|
|
129
|
+
typeof value === 'object' &&
|
|
130
|
+
value !== null &&
|
|
131
|
+
(value as { $type?: unknown }).$type === 'raw' &&
|
|
132
|
+
'value' in (value as object)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export type TaggedLiteralValue = TaggedBigInt | TaggedRaw;
|
|
137
|
+
|
|
138
|
+
export type ColumnDefaultLiteralValue = JsonValue | TaggedLiteralValue;
|
|
139
|
+
|
|
140
|
+
export type ColumnDefaultLiteralInputValue = ColumnDefaultLiteralValue | bigint | Date;
|
|
141
|
+
|
|
142
|
+
export type ColumnDefault =
|
|
143
|
+
| {
|
|
144
|
+
readonly kind: 'literal';
|
|
145
|
+
readonly value: ColumnDefaultLiteralInputValue;
|
|
146
|
+
}
|
|
147
|
+
| { readonly kind: 'function'; readonly expression: string };
|
|
148
|
+
|
|
149
|
+
export type ExecutionMutationDefaultValue = {
|
|
150
|
+
readonly kind: 'generator';
|
|
151
|
+
readonly id: GeneratedValueSpec['id'];
|
|
152
|
+
readonly params?: Record<string, unknown>;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export type ExecutionMutationDefault = {
|
|
156
|
+
readonly ref: { readonly table: string; readonly column: string };
|
|
157
|
+
readonly onCreate?: ExecutionMutationDefaultValue;
|
|
158
|
+
readonly onUpdate?: ExecutionMutationDefaultValue;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export type ExecutionSection = {
|
|
162
|
+
readonly mutations: {
|
|
163
|
+
readonly defaults: ReadonlyArray<ExecutionMutationDefault>;
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
|
|
23
167
|
export interface Source {
|
|
24
168
|
readonly readOnly: boolean;
|
|
25
169
|
readonly projection: Record<string, FieldType>;
|
|
@@ -42,7 +186,7 @@ export type Expr =
|
|
|
42
186
|
export interface DocCollection {
|
|
43
187
|
readonly name: string;
|
|
44
188
|
readonly id?: {
|
|
45
|
-
readonly strategy: 'auto' | 'client' | 'uuid' | '
|
|
189
|
+
readonly strategy: 'auto' | 'client' | 'uuid' | 'objectId';
|
|
46
190
|
};
|
|
47
191
|
readonly fields: Record<string, FieldType>;
|
|
48
192
|
readonly indexes?: ReadonlyArray<DocIndex>;
|
|
@@ -55,7 +199,11 @@ export interface DocumentStorage {
|
|
|
55
199
|
};
|
|
56
200
|
}
|
|
57
201
|
|
|
58
|
-
export interface DocumentContract
|
|
202
|
+
export interface DocumentContract<
|
|
203
|
+
TStorageHash extends StorageHashBase<string> = StorageHashBase<string>,
|
|
204
|
+
TExecutionHash extends ExecutionHashBase<string> = ExecutionHashBase<string>,
|
|
205
|
+
TProfileHash extends ProfileHashBase<string> = ProfileHashBase<string>,
|
|
206
|
+
> extends ContractBase<TStorageHash, TExecutionHash, TProfileHash> {
|
|
59
207
|
// Accept string to work with JSON imports; runtime validation ensures 'document'
|
|
60
208
|
readonly targetFamily: string;
|
|
61
209
|
readonly storage: DocumentStorage;
|
|
@@ -68,7 +216,7 @@ export interface ParamDescriptor {
|
|
|
68
216
|
readonly codecId?: string;
|
|
69
217
|
readonly nativeType?: string;
|
|
70
218
|
readonly nullable?: boolean;
|
|
71
|
-
readonly source: 'dsl' | 'raw';
|
|
219
|
+
readonly source: 'dsl' | 'raw' | 'lane';
|
|
72
220
|
readonly refs?: { table: string; column: string };
|
|
73
221
|
}
|
|
74
222
|
|
|
@@ -85,7 +233,7 @@ export interface PlanRefs {
|
|
|
85
233
|
export interface PlanMeta {
|
|
86
234
|
readonly target: string;
|
|
87
235
|
readonly targetFamily?: string;
|
|
88
|
-
readonly
|
|
236
|
+
readonly storageHash: string;
|
|
89
237
|
readonly profileHash?: string;
|
|
90
238
|
readonly lane: string;
|
|
91
239
|
readonly annotations?: {
|
|
@@ -151,7 +299,7 @@ export function isDocumentContract(contract: unknown): contract is DocumentContr
|
|
|
151
299
|
* Represents the current contract identity for a database.
|
|
152
300
|
*/
|
|
153
301
|
export interface ContractMarkerRecord {
|
|
154
|
-
readonly
|
|
302
|
+
readonly storageHash: string;
|
|
155
303
|
readonly profileHash: string;
|
|
156
304
|
readonly contractJson: unknown | null;
|
|
157
305
|
readonly canonicalVersion: number | null;
|
|
@@ -180,6 +328,50 @@ export interface ValidationContext {
|
|
|
180
328
|
readonly codecTypeImports?: ReadonlyArray<TypesImportSpec>;
|
|
181
329
|
readonly operationTypeImports?: ReadonlyArray<TypesImportSpec>;
|
|
182
330
|
readonly extensionIds?: ReadonlyArray<string>;
|
|
331
|
+
/**
|
|
332
|
+
* Parameterized codec descriptors collected from adapters and extensions.
|
|
333
|
+
* Map of codecId → descriptor for quick lookup during type generation.
|
|
334
|
+
*/
|
|
335
|
+
readonly parameterizedCodecs?: Map<string, ParameterizedCodecDescriptor>;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Context for rendering parameterized types during contract.d.ts generation.
|
|
340
|
+
* Passed to type renderers so they can reference CodecTypes by name.
|
|
341
|
+
*/
|
|
342
|
+
export interface TypeRenderContext {
|
|
343
|
+
readonly codecTypesName: string;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* A normalized type renderer for parameterized codecs.
|
|
348
|
+
* This is the interface expected by TargetFamilyHook.generateContractTypes.
|
|
349
|
+
*/
|
|
350
|
+
export interface TypeRenderEntry {
|
|
351
|
+
readonly codecId: string;
|
|
352
|
+
readonly render: (params: Record<string, unknown>, ctx: TypeRenderContext) => string;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Additional options for generateContractTypes.
|
|
357
|
+
*/
|
|
358
|
+
export interface GenerateContractTypesOptions {
|
|
359
|
+
/**
|
|
360
|
+
* Normalized parameterized type renderers, keyed by codecId.
|
|
361
|
+
* When a column has typeParams and a renderer exists for its codecId,
|
|
362
|
+
* the renderer is called to produce the TypeScript type expression.
|
|
363
|
+
*/
|
|
364
|
+
readonly parameterizedRenderers?: Map<string, TypeRenderEntry>;
|
|
365
|
+
/**
|
|
366
|
+
* Type imports for parameterized codecs.
|
|
367
|
+
* These are merged with codec and operation type imports in contract.d.ts.
|
|
368
|
+
*/
|
|
369
|
+
readonly parameterizedTypeImports?: ReadonlyArray<TypesImportSpec>;
|
|
370
|
+
/**
|
|
371
|
+
* Query operation type imports for the query builder.
|
|
372
|
+
* Flat operation signatures keyed by operation name, emitted as standalone QueryOperationTypes.
|
|
373
|
+
*/
|
|
374
|
+
readonly queryOperationTypeImports?: ReadonlyArray<TypesImportSpec>;
|
|
183
375
|
}
|
|
184
376
|
|
|
185
377
|
/**
|
|
@@ -191,52 +383,117 @@ export interface TargetFamilyHook {
|
|
|
191
383
|
|
|
192
384
|
/**
|
|
193
385
|
* Validates that all type IDs in the contract come from referenced extension packs.
|
|
194
|
-
* @param
|
|
386
|
+
* @param contract - Contract to validate
|
|
195
387
|
* @param ctx - Validation context with operation registry and extension IDs
|
|
196
388
|
*/
|
|
197
|
-
validateTypes(
|
|
389
|
+
validateTypes(contract: Contract, ctx: ValidationContext): void;
|
|
198
390
|
|
|
199
391
|
/**
|
|
200
392
|
* Validates family-specific contract structure.
|
|
201
|
-
* @param
|
|
393
|
+
* @param contract - Contract to validate
|
|
202
394
|
*/
|
|
203
|
-
validateStructure(
|
|
395
|
+
validateStructure(contract: Contract): void;
|
|
204
396
|
|
|
205
397
|
/**
|
|
206
398
|
* Generates contract.d.ts file content.
|
|
207
|
-
* @param
|
|
399
|
+
* @param contract - Contract
|
|
208
400
|
* @param codecTypeImports - Array of codec type import specs
|
|
209
401
|
* @param operationTypeImports - Array of operation type import specs
|
|
402
|
+
* @param hashes - Contract hash values (storageHash, executionHash, profileHash)
|
|
403
|
+
* @param options - Additional options including parameterized type renderers
|
|
210
404
|
* @returns Generated TypeScript type definitions as string
|
|
211
405
|
*/
|
|
212
406
|
generateContractTypes(
|
|
213
|
-
|
|
407
|
+
contract: Contract,
|
|
214
408
|
codecTypeImports: ReadonlyArray<TypesImportSpec>,
|
|
215
409
|
operationTypeImports: ReadonlyArray<TypesImportSpec>,
|
|
410
|
+
hashes: {
|
|
411
|
+
readonly storageHash: string;
|
|
412
|
+
readonly executionHash?: string;
|
|
413
|
+
readonly profileHash: string;
|
|
414
|
+
},
|
|
415
|
+
options?: GenerateContractTypesOptions,
|
|
216
416
|
): string;
|
|
217
417
|
}
|
|
218
418
|
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
419
|
+
// ============================================================================
|
|
420
|
+
// Parameterized Codec Descriptor Types
|
|
421
|
+
// ============================================================================
|
|
422
|
+
//
|
|
423
|
+
// Types for codecs that support type parameters (e.g., Vector<1536>, Decimal<2>).
|
|
424
|
+
// These enable precise TypeScript types for parameterized columns without
|
|
425
|
+
// coupling the SQL family emitter to specific adapter codec IDs.
|
|
426
|
+
//
|
|
427
|
+
// ============================================================================
|
|
224
428
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
429
|
+
/**
|
|
430
|
+
* Declarative type renderer that produces a TypeScript type expression.
|
|
431
|
+
*
|
|
432
|
+
* Renderers can be:
|
|
433
|
+
* - A template string with `{{paramName}}` placeholders (e.g., `Vector<{{length}}>`)
|
|
434
|
+
* - A function that receives typeParams and context and returns a type expression
|
|
435
|
+
*
|
|
436
|
+
* **Prefer template strings** for most cases:
|
|
437
|
+
* - Templates are JSON-serializable (safe for pack-ref metadata)
|
|
438
|
+
* - Templates can be statically analyzed by tooling
|
|
439
|
+
*
|
|
440
|
+
* Function renderers are allowed but have tradeoffs:
|
|
441
|
+
* - Require runtime execution during emission (the emitter runs code)
|
|
442
|
+
* - Not JSON-serializable (can't be stored in contract.json)
|
|
443
|
+
* - The emitted artifacts (contract.json, contract.d.ts) still contain no
|
|
444
|
+
* executable code - this constraint applies to outputs, not the emission process
|
|
445
|
+
*/
|
|
446
|
+
export type TypeRenderer =
|
|
447
|
+
| string
|
|
448
|
+
| ((params: Record<string, unknown>, ctx: RenderTypeContext) => string);
|
|
228
449
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
450
|
+
/**
|
|
451
|
+
* Descriptor for a codec that supports type parameters.
|
|
452
|
+
*
|
|
453
|
+
* Parameterized codecs allow columns to carry additional metadata (typeParams)
|
|
454
|
+
* that affects the generated TypeScript types. For example:
|
|
455
|
+
* - A vector codec can use `{ length: 1536 }` to generate `Vector<1536>`
|
|
456
|
+
* - A decimal codec can use `{ precision: 10, scale: 2 }` to generate `Decimal<10, 2>`
|
|
457
|
+
*
|
|
458
|
+
* The SQL family emitter uses these descriptors to generate precise types
|
|
459
|
+
* without hard-coding knowledge of specific codec IDs.
|
|
460
|
+
*
|
|
461
|
+
* @example
|
|
462
|
+
* ```typescript
|
|
463
|
+
* const vectorCodecDescriptor: ParameterizedCodecDescriptor = {
|
|
464
|
+
* codecId: 'pg/vector@1',
|
|
465
|
+
* outputTypeRenderer: 'Vector<{{length}}>',
|
|
466
|
+
* // Optional: paramsSchema for runtime validation
|
|
467
|
+
* };
|
|
468
|
+
* ```
|
|
469
|
+
*/
|
|
470
|
+
export interface ParameterizedCodecDescriptor {
|
|
471
|
+
/** The codec ID this descriptor applies to (e.g., 'pg/vector@1') */
|
|
472
|
+
readonly codecId: string;
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Renderer for the output (read) type.
|
|
476
|
+
* Can be a template string or function.
|
|
477
|
+
*
|
|
478
|
+
* This is the primary renderer used by SQL emission to generate
|
|
479
|
+
* model field types in contract.d.ts.
|
|
480
|
+
*/
|
|
481
|
+
readonly outputTypeRenderer: TypeRenderer;
|
|
234
482
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
483
|
+
/**
|
|
484
|
+
* Optional renderer for the input (write) type.
|
|
485
|
+
* If not provided, outputTypeRenderer is used for both.
|
|
486
|
+
*
|
|
487
|
+
* **Reserved for future use**: Currently, SQL emission only uses
|
|
488
|
+
* outputTypeRenderer. This field is defined for future support of
|
|
489
|
+
* asymmetric codecs where input and output types differ (e.g., a
|
|
490
|
+
* codec that accepts `string | number` but always returns `number`).
|
|
491
|
+
*/
|
|
492
|
+
readonly inputTypeRenderer?: TypeRenderer;
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Optional import spec for types used by this codec's renderers.
|
|
496
|
+
* The emitter will add this import to contract.d.ts.
|
|
497
|
+
*/
|
|
498
|
+
readonly typesImport?: TypesImportSpec;
|
|
242
499
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { type } from 'arktype';
|
|
2
|
+
import type { Contract } from './contract-types';
|
|
3
|
+
import type { DomainContractShape, DomainValidationResult } from './validate-domain';
|
|
4
|
+
import { validateContractDomain } from './validate-domain';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Family-provided storage validator.
|
|
8
|
+
* SQL validates tables/columns/FKs; Mongo validates collections/embedding.
|
|
9
|
+
*/
|
|
10
|
+
export type StorageValidator = (contract: Contract) => void;
|
|
11
|
+
|
|
12
|
+
export interface ValidateContractResult {
|
|
13
|
+
readonly warnings: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const ContractSchema = type({
|
|
17
|
+
target: 'string',
|
|
18
|
+
targetFamily: 'string',
|
|
19
|
+
roots: 'Record<string, string>',
|
|
20
|
+
models: 'Record<string, unknown>',
|
|
21
|
+
storage: 'Record<string, unknown>',
|
|
22
|
+
capabilities: 'Record<string, Record<string, boolean>>',
|
|
23
|
+
extensionPacks: 'Record<string, unknown>',
|
|
24
|
+
meta: 'Record<string, unknown>',
|
|
25
|
+
'execution?': {
|
|
26
|
+
'executionHash?': 'string',
|
|
27
|
+
mutations: {
|
|
28
|
+
defaults: 'unknown[]',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
'profileHash?': 'string',
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
function stripPersistenceFields(raw: Record<string, unknown>): Record<string, unknown> {
|
|
35
|
+
const { schemaVersion: _, sources: _s, ...rest } = raw;
|
|
36
|
+
return rest;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function extractDomainShape(contract: Contract): DomainContractShape {
|
|
40
|
+
return {
|
|
41
|
+
roots: contract.roots,
|
|
42
|
+
models: contract.models,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Framework-level contract validation (ADR 182).
|
|
48
|
+
*
|
|
49
|
+
* Three-pass validation:
|
|
50
|
+
* 1. **Structural validation** (arktype): verifies required fields exist with
|
|
51
|
+
* correct base types.
|
|
52
|
+
* 2. **Domain validation** (framework-owned): roots, relation targets,
|
|
53
|
+
* variant/base consistency, discriminators, ownership, orphans.
|
|
54
|
+
* 3. **Storage validation** (family-provided): SQL validates tables/columns/FKs;
|
|
55
|
+
* Mongo validates collections/embedding.
|
|
56
|
+
*
|
|
57
|
+
* JSON persistence fields (`schemaVersion`, `sources`) are stripped before
|
|
58
|
+
* validation — they are not part of the in-memory contract representation.
|
|
59
|
+
*
|
|
60
|
+
* @template TContract The fully-typed contract type (preserves literal types).
|
|
61
|
+
* @param value Raw contract value (e.g. parsed from JSON).
|
|
62
|
+
* @param storageValidator Family-specific storage validation function.
|
|
63
|
+
* @returns The validated contract with full literal types.
|
|
64
|
+
*/
|
|
65
|
+
export function validateContract<TContract extends Contract>(
|
|
66
|
+
value: unknown,
|
|
67
|
+
storageValidator: StorageValidator,
|
|
68
|
+
): TContract & ValidateContractResult {
|
|
69
|
+
if (typeof value !== 'object' || value === null) {
|
|
70
|
+
throw new Error('Contract must be a non-null object');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const stripped = stripPersistenceFields(value as Record<string, unknown>);
|
|
74
|
+
|
|
75
|
+
const parsed = ContractSchema(stripped);
|
|
76
|
+
if (parsed instanceof type.errors) {
|
|
77
|
+
throw new Error(`Invalid contract structure: ${parsed.summary}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Arktype verified the structural shape; Contract adds branded hash types and
|
|
81
|
+
// ContractModel generics that can't be expressed in the schema.
|
|
82
|
+
const contract = parsed as unknown as Contract;
|
|
83
|
+
|
|
84
|
+
const domainResult: DomainValidationResult = validateContractDomain(extractDomainShape(contract));
|
|
85
|
+
|
|
86
|
+
storageValidator(contract);
|
|
87
|
+
|
|
88
|
+
// TContract narrows Contract with literal types from the caller's contract.d.ts;
|
|
89
|
+
// the runtime object is the same — the cast preserves the caller's type parameter.
|
|
90
|
+
return Object.assign(contract as unknown as TContract, {
|
|
91
|
+
warnings: domainResult.warnings,
|
|
92
|
+
});
|
|
93
|
+
}
|