@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.
Files changed (58) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +43 -254
  3. package/dist/contract-types-MYdoYIIh.d.mts +314 -0
  4. package/dist/contract-types-MYdoYIIh.d.mts.map +1 -0
  5. package/dist/hashing-CyaA_Qvf.mjs +196 -0
  6. package/dist/hashing-CyaA_Qvf.mjs.map +1 -0
  7. package/dist/hashing.d.mts +29 -0
  8. package/dist/hashing.d.mts.map +1 -0
  9. package/dist/hashing.mjs +3 -0
  10. package/dist/testing.d.mts +29 -0
  11. package/dist/testing.d.mts.map +1 -0
  12. package/dist/testing.mjs +58 -0
  13. package/dist/testing.mjs.map +1 -0
  14. package/dist/types-aMyNgejf.mjs +14 -0
  15. package/dist/types-aMyNgejf.mjs.map +1 -0
  16. package/dist/types.d.mts +2 -2
  17. package/dist/types.mjs +2 -10
  18. package/dist/validate-contract.d.mts +37 -0
  19. package/dist/validate-contract.d.mts.map +1 -0
  20. package/dist/validate-contract.mjs +3 -0
  21. package/dist/validate-domain-CpCcTlqJ.mjs +165 -0
  22. package/dist/validate-domain-CpCcTlqJ.mjs.map +1 -0
  23. package/dist/validate-domain.d.mts +24 -0
  24. package/dist/validate-domain.d.mts.map +1 -0
  25. package/dist/validate-domain.mjs +3 -0
  26. package/package.json +15 -8
  27. package/schemas/data-contract-document-v1.json +5 -5
  28. package/src/canonicalization.ts +263 -0
  29. package/src/contract-types.ts +55 -0
  30. package/src/domain-types.ts +95 -0
  31. package/src/exports/hashing.ts +2 -0
  32. package/src/exports/testing.ts +1 -0
  33. package/src/exports/types.ts +36 -20
  34. package/src/exports/validate-contract.ts +6 -0
  35. package/src/exports/validate-domain.ts +5 -0
  36. package/src/hashing.ts +53 -0
  37. package/src/testing-factories.ts +101 -0
  38. package/src/types.ts +102 -248
  39. package/src/validate-contract.ts +101 -0
  40. package/src/validate-domain.ts +227 -0
  41. package/dist/framework-components-B-XOtXw3.d.mts +0 -854
  42. package/dist/framework-components-B-XOtXw3.d.mts.map +0 -1
  43. package/dist/framework-components.d.mts +0 -2
  44. package/dist/framework-components.mjs +0 -70
  45. package/dist/framework-components.mjs.map +0 -1
  46. package/dist/ir-B8zNqals.d.mts +0 -79
  47. package/dist/ir-B8zNqals.d.mts.map +0 -1
  48. package/dist/ir.d.mts +0 -2
  49. package/dist/ir.mjs +0 -46
  50. package/dist/ir.mjs.map +0 -1
  51. package/dist/pack-manifest-types.d.mts +0 -2
  52. package/dist/pack-manifest-types.mjs +0 -1
  53. package/dist/types.mjs.map +0 -1
  54. package/src/exports/framework-components.ts +0 -36
  55. package/src/exports/ir.ts +0 -1
  56. package/src/exports/pack-manifest-types.ts +0 -6
  57. package/src/framework-components.ts +0 -717
  58. package/src/ir.ts +0 -113
package/src/types.ts CHANGED
@@ -1,17 +1,60 @@
1
- import type { OperationRegistry } from '@prisma-next/operations';
2
- import type { RenderTypeContext } from './framework-components';
3
- import type { ContractIR } from './ir';
1
+ /**
2
+ * Unique symbol used as the key for branding types.
3
+ */
4
+ export const $: unique symbol = Symbol('__prisma_next_brand__');
4
5
 
5
- export interface ContractBase {
6
- readonly schemaVersion: string;
7
- readonly target: string;
8
- readonly targetFamily: string;
9
- readonly coreHash: string;
10
- readonly profileHash?: string;
11
- readonly capabilities: Record<string, Record<string, boolean>>;
12
- readonly extensionPacks: Record<string, unknown>;
13
- readonly meta: Record<string, unknown>;
14
- readonly sources: Record<string, Source>;
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' | 'cuid' | 'objectId';
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 coreHash: string;
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 coreHash: string;
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
+ }