@prisma-next/core-control-plane 0.3.0-dev.9 → 0.3.0-dev.91

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 (97) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +31 -99
  3. package/dist/constants.d.mts +9 -0
  4. package/dist/constants.d.mts.map +1 -0
  5. package/dist/constants.mjs +10 -0
  6. package/dist/constants.mjs.map +1 -0
  7. package/dist/emission.d.mts +63 -0
  8. package/dist/emission.d.mts.map +1 -0
  9. package/dist/emission.mjs +316 -0
  10. package/dist/emission.mjs.map +1 -0
  11. package/dist/errors.d.mts +232 -0
  12. package/dist/errors.d.mts.map +1 -0
  13. package/dist/errors.mjs +330 -0
  14. package/dist/errors.mjs.map +1 -0
  15. package/dist/{schema-view.d.ts → schema-view-DObwT8x9.d.mts} +17 -14
  16. package/dist/schema-view-DObwT8x9.d.mts.map +1 -0
  17. package/dist/schema-view.d.mts +2 -0
  18. package/dist/schema-view.mjs +1 -0
  19. package/dist/stack.d.mts +30 -0
  20. package/dist/stack.d.mts.map +1 -0
  21. package/dist/stack.mjs +30 -0
  22. package/dist/stack.mjs.map +1 -0
  23. package/dist/types-BArIWumw.d.mts +615 -0
  24. package/dist/types-BArIWumw.d.mts.map +1 -0
  25. package/dist/types.d.mts +2 -0
  26. package/dist/types.mjs +1 -0
  27. package/package.json +30 -42
  28. package/src/constants.ts +5 -0
  29. package/src/emission/canonicalization.ts +68 -21
  30. package/src/emission/emit.ts +84 -21
  31. package/src/emission/hashing.ts +29 -27
  32. package/src/emission/types.ts +18 -2
  33. package/src/errors.ts +127 -20
  34. package/src/exports/constants.ts +1 -0
  35. package/src/exports/emission.ts +1 -1
  36. package/src/exports/errors.ts +6 -1
  37. package/src/exports/types.ts +0 -1
  38. package/src/migrations.ts +27 -1
  39. package/src/schema-view.ts +5 -5
  40. package/src/types.ts +24 -12
  41. package/dist/chunk-473ODD3P.js +0 -14
  42. package/dist/chunk-473ODD3P.js.map +0 -1
  43. package/dist/chunk-U5RYT6PT.js +0 -229
  44. package/dist/chunk-U5RYT6PT.js.map +0 -1
  45. package/dist/config-types.d.ts +0 -68
  46. package/dist/config-types.d.ts.map +0 -1
  47. package/dist/config-validation.d.ts +0 -10
  48. package/dist/config-validation.d.ts.map +0 -1
  49. package/dist/emission/canonicalization.d.ts +0 -6
  50. package/dist/emission/canonicalization.d.ts.map +0 -1
  51. package/dist/emission/emit.d.ts +0 -5
  52. package/dist/emission/emit.d.ts.map +0 -1
  53. package/dist/emission/hashing.d.ts +0 -17
  54. package/dist/emission/hashing.d.ts.map +0 -1
  55. package/dist/emission/types.d.ts +0 -16
  56. package/dist/emission/types.d.ts.map +0 -1
  57. package/dist/errors.d.ts +0 -183
  58. package/dist/errors.d.ts.map +0 -1
  59. package/dist/exports/config-types.d.ts +0 -3
  60. package/dist/exports/config-types.d.ts.map +0 -1
  61. package/dist/exports/config-types.js +0 -53
  62. package/dist/exports/config-types.js.map +0 -1
  63. package/dist/exports/config-validation.d.ts +0 -2
  64. package/dist/exports/config-validation.d.ts.map +0 -1
  65. package/dist/exports/config-validation.js +0 -252
  66. package/dist/exports/config-validation.js.map +0 -1
  67. package/dist/exports/emission.d.ts +0 -5
  68. package/dist/exports/emission.d.ts.map +0 -1
  69. package/dist/exports/emission.js +0 -310
  70. package/dist/exports/emission.js.map +0 -1
  71. package/dist/exports/errors.d.ts +0 -3
  72. package/dist/exports/errors.d.ts.map +0 -1
  73. package/dist/exports/errors.js +0 -43
  74. package/dist/exports/errors.js.map +0 -1
  75. package/dist/exports/schema-view.d.ts +0 -2
  76. package/dist/exports/schema-view.d.ts.map +0 -1
  77. package/dist/exports/schema-view.js +0 -1
  78. package/dist/exports/schema-view.js.map +0 -1
  79. package/dist/exports/stack.d.ts +0 -2
  80. package/dist/exports/stack.d.ts.map +0 -1
  81. package/dist/exports/stack.js +0 -7
  82. package/dist/exports/stack.js.map +0 -1
  83. package/dist/exports/types.d.ts +0 -3
  84. package/dist/exports/types.d.ts.map +0 -1
  85. package/dist/exports/types.js +0 -7
  86. package/dist/exports/types.js.map +0 -1
  87. package/dist/migrations.d.ts +0 -190
  88. package/dist/migrations.d.ts.map +0 -1
  89. package/dist/schema-view.d.ts.map +0 -1
  90. package/dist/stack.d.ts +0 -25
  91. package/dist/stack.d.ts.map +0 -1
  92. package/dist/types.d.ts +0 -411
  93. package/dist/types.d.ts.map +0 -1
  94. package/src/config-types.ts +0 -157
  95. package/src/config-validation.ts +0 -270
  96. package/src/exports/config-types.ts +0 -5
  97. package/src/exports/config-validation.ts +0 -1
package/package.json CHANGED
@@ -1,65 +1,53 @@
1
1
  {
2
2
  "name": "@prisma-next/core-control-plane",
3
- "version": "0.3.0-dev.9",
3
+ "version": "0.3.0-dev.91",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
- "description": "Control plane domain actions, config types, validation, and error factories for Prisma Next",
6
+ "description": "Control-plane migration/emission primitives and structured error utilities",
7
7
  "dependencies": {
8
8
  "arktype": "^2.1.26",
9
9
  "prettier": "^3.3.3",
10
- "@prisma-next/contract": "0.3.0-dev.9",
11
- "@prisma-next/operations": "0.3.0-dev.9",
12
- "@prisma-next/utils": "0.3.0-dev.9"
10
+ "@prisma-next/contract": "0.3.0-dev.91",
11
+ "@prisma-next/operations": "0.3.0-dev.91",
12
+ "@prisma-next/utils": "0.3.0-dev.91"
13
13
  },
14
14
  "devDependencies": {
15
- "@vitest/coverage-v8": "4.0.16",
16
- "tsup": "8.5.1",
15
+ "tsdown": "0.18.4",
17
16
  "typescript": "5.9.3",
18
- "vitest": "4.0.16",
17
+ "vitest": "4.0.17",
18
+ "@prisma-next/tsconfig": "0.0.0",
19
+ "@prisma-next/tsdown": "0.0.0",
19
20
  "@prisma-next/test-utils": "0.0.1"
20
21
  },
21
22
  "files": [
22
23
  "dist",
23
24
  "src"
24
25
  ],
26
+ "engines": {
27
+ "node": ">=20"
28
+ },
25
29
  "exports": {
26
- "./config-types": {
27
- "types": "./dist/exports/config-types.d.ts",
28
- "import": "./dist/exports/config-types.js"
29
- },
30
- "./config-validation": {
31
- "types": "./dist/exports/config-validation.d.ts",
32
- "import": "./dist/exports/config-validation.js"
33
- },
34
- "./errors": {
35
- "types": "./dist/exports/errors.d.ts",
36
- "import": "./dist/exports/errors.js"
37
- },
38
- "./types": {
39
- "types": "./dist/exports/types.d.ts",
40
- "import": "./dist/exports/types.js"
41
- },
42
- "./stack": {
43
- "types": "./dist/exports/stack.d.ts",
44
- "import": "./dist/exports/stack.js"
45
- },
46
- "./emission": {
47
- "types": "./dist/exports/emission.d.ts",
48
- "import": "./dist/exports/emission.js"
49
- },
50
- "./schema-view": {
51
- "types": "./dist/exports/schema-view.d.ts",
52
- "import": "./dist/exports/schema-view.js"
53
- }
30
+ "./constants": "./dist/constants.mjs",
31
+ "./emission": "./dist/emission.mjs",
32
+ "./errors": "./dist/errors.mjs",
33
+ "./schema-view": "./dist/schema-view.mjs",
34
+ "./stack": "./dist/stack.mjs",
35
+ "./types": "./dist/types.mjs",
36
+ "./package.json": "./package.json"
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/prisma/prisma-next.git",
41
+ "directory": "packages/1-framework/1-core/migration/control-plane"
54
42
  },
55
43
  "scripts": {
56
- "build": "tsup --config tsup.config.ts && tsc --project tsconfig.build.json",
44
+ "build": "tsdown",
57
45
  "test": "vitest run --passWithNoTests",
58
46
  "test:coverage": "vitest run --coverage --passWithNoTests",
59
- "typecheck": "tsc --project tsconfig.json --noEmit",
60
- "lint": "biome check . --config-path ../../../../../biome.json --error-on-warnings",
61
- "lint:fix": "biome check --write . --config-path ../../../biome.json",
62
- "lint:fix:unsafe": "biome check --write --unsafe . --config-path ../../../biome.json",
63
- "clean": "node ../../../../../scripts/clean.mjs"
47
+ "typecheck": "tsc --noEmit",
48
+ "lint": "biome check . --error-on-warnings",
49
+ "lint:fix": "biome check --write .",
50
+ "lint:fix:unsafe": "biome check --write --unsafe .",
51
+ "clean": "rm -rf dist dist-tsc dist-tsc-prod coverage .tmp-output"
64
52
  }
65
53
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Sentinel value representing the absence of a contract (empty/new project).
3
+ * This is a human-readable marker, not a real SHA-256 hash.
4
+ */
5
+ export const EMPTY_CONTRACT_HASH = 'sha256:empty' as const;
@@ -1,19 +1,37 @@
1
- import type { ContractIR } from '@prisma-next/contract/ir';
1
+ import { bigintJsonReplacer } from '@prisma-next/contract/types';
2
2
  import { isArrayEqual } from '@prisma-next/utils/array-equal';
3
+ import { ifDefined } from '@prisma-next/utils/defined';
3
4
 
4
5
  type NormalizedContract = {
5
6
  schemaVersion: string;
6
7
  targetFamily: string;
7
8
  target: string;
8
- coreHash?: string;
9
+ storageHash?: string;
10
+ executionHash?: string;
9
11
  profileHash?: string;
10
12
  models: Record<string, unknown>;
11
13
  relations: Record<string, unknown>;
12
14
  storage: Record<string, unknown>;
15
+ execution?: Record<string, unknown>;
13
16
  extensionPacks: Record<string, unknown>;
14
17
  capabilities: Record<string, Record<string, boolean>>;
15
18
  meta: Record<string, unknown>;
16
- sources: Record<string, unknown>;
19
+ };
20
+
21
+ export type CanonicalContractInput = {
22
+ schemaVersion: string;
23
+ targetFamily: string;
24
+ target: string;
25
+ models: Record<string, unknown>;
26
+ relations: Record<string, unknown>;
27
+ storage: Record<string, unknown>;
28
+ execution?: Record<string, unknown>;
29
+ extensionPacks: Record<string, unknown>;
30
+ capabilities: Record<string, Record<string, boolean>>;
31
+ meta: Record<string, unknown>;
32
+ storageHash?: string;
33
+ executionHash?: string;
34
+ profileHash?: string;
17
35
  };
18
36
 
19
37
  const TOP_LEVEL_ORDER = [
@@ -21,19 +39,22 @@ const TOP_LEVEL_ORDER = [
21
39
  'canonicalVersion',
22
40
  'targetFamily',
23
41
  'target',
24
- 'coreHash',
42
+ 'storageHash',
43
+ 'executionHash',
25
44
  'profileHash',
26
45
  'models',
46
+ 'relations',
27
47
  'storage',
48
+ 'execution',
28
49
  'capabilities',
29
50
  'extensionPacks',
30
51
  'meta',
31
- 'sources',
32
52
  ] as const;
33
53
 
34
54
  function isDefaultValue(value: unknown): boolean {
35
55
  if (value === false) return true;
36
56
  if (value === null) return false;
57
+ if (value instanceof Date) return false;
37
58
  if (Array.isArray(value) && value.length === 0) return true;
38
59
  if (typeof value === 'object' && value !== null) {
39
60
  const keys = Object.keys(value);
@@ -47,6 +68,10 @@ function omitDefaults(obj: unknown, path: readonly string[]): unknown {
47
68
  return obj;
48
69
  }
49
70
 
71
+ if (obj instanceof Date) {
72
+ return obj;
73
+ }
74
+
50
75
  if (Array.isArray(obj)) {
51
76
  return obj.map((item) => omitDefaults(item, path));
52
77
  }
@@ -69,6 +94,13 @@ function omitDefaults(obj: unknown, path: readonly string[]): unknown {
69
94
  continue;
70
95
  }
71
96
 
97
+ // Strip 'noAction' referential actions (the database default) for hash stability.
98
+ // A contract with explicit `onDelete: 'noAction'` is semantically identical to
99
+ // one that omits `onDelete` entirely, so they should produce the same hash.
100
+ if ((key === 'onDelete' || key === 'onUpdate') && value === 'noAction') {
101
+ continue;
102
+ }
103
+
72
104
  if (isDefaultValue(value)) {
73
105
  const isRequiredModels = isArrayEqual(currentPath, ['models']);
74
106
  const isRequiredTables = isArrayEqual(currentPath, ['storage', 'tables']);
@@ -76,7 +108,11 @@ function omitDefaults(obj: unknown, path: readonly string[]): unknown {
76
108
  const isRequiredExtensionPacks = isArrayEqual(currentPath, ['extensionPacks']);
77
109
  const isRequiredCapabilities = isArrayEqual(currentPath, ['capabilities']);
78
110
  const isRequiredMeta = isArrayEqual(currentPath, ['meta']);
79
- const isRequiredSources = isArrayEqual(currentPath, ['sources']);
111
+ const isRequiredExecutionDefaults = isArrayEqual(currentPath, [
112
+ 'execution',
113
+ 'mutations',
114
+ 'defaults',
115
+ ]);
80
116
  const isExtensionNamespace = currentPath.length === 2 && currentPath[0] === 'extensionPacks';
81
117
  const isModelRelations =
82
118
  currentPath.length === 3 &&
@@ -100,6 +136,16 @@ function omitDefaults(obj: unknown, path: readonly string[]): unknown {
100
136
  ['storage', 'tables', 'foreignKeys'],
101
137
  );
102
138
 
139
+ // Preserve per-FK `constraint` and `index` booleans (even when `false`)
140
+ // so that hash distinguishes `false` from absent.
141
+ // Path: ['storage', 'tables', <tableName>, 'foreignKeys', 'constraint' | 'index']
142
+ const isFkBooleanField =
143
+ currentPath.length === 5 &&
144
+ currentPath[0] === 'storage' &&
145
+ currentPath[1] === 'tables' &&
146
+ currentPath[3] === 'foreignKeys' &&
147
+ (key === 'constraint' || key === 'index');
148
+
103
149
  if (
104
150
  !isRequiredModels &&
105
151
  !isRequiredTables &&
@@ -107,12 +153,13 @@ function omitDefaults(obj: unknown, path: readonly string[]): unknown {
107
153
  !isRequiredExtensionPacks &&
108
154
  !isRequiredCapabilities &&
109
155
  !isRequiredMeta &&
110
- !isRequiredSources &&
156
+ !isRequiredExecutionDefaults &&
111
157
  !isExtensionNamespace &&
112
158
  !isModelRelations &&
113
159
  !isTableUniques &&
114
160
  !isTableIndexes &&
115
- !isTableForeignKeys
161
+ !isTableForeignKeys &&
162
+ !isFkBooleanField
116
163
  ) {
117
164
  continue;
118
165
  }
@@ -129,6 +176,10 @@ function sortObjectKeys(obj: unknown): unknown {
129
176
  return obj;
130
177
  }
131
178
 
179
+ if (obj instanceof Date) {
180
+ return obj;
181
+ }
182
+
132
183
  if (Array.isArray(obj)) {
133
184
  return obj.map((item) => sortObjectKeys(item));
134
185
  }
@@ -219,9 +270,7 @@ function orderTopLevel(obj: Record<string, unknown>): Record<string, unknown> {
219
270
  return ordered;
220
271
  }
221
272
 
222
- export function canonicalizeContract(
223
- ir: ContractIR & { coreHash?: string; profileHash?: string },
224
- ): string {
273
+ export function canonicalizeContract(ir: CanonicalContractInput): string {
225
274
  const normalized: NormalizedContract = {
226
275
  schemaVersion: ir.schemaVersion,
227
276
  targetFamily: ir.targetFamily,
@@ -229,19 +278,17 @@ export function canonicalizeContract(
229
278
  models: ir.models,
230
279
  relations: ir.relations,
231
280
  storage: ir.storage,
281
+ ...ifDefined('execution', ir.execution),
232
282
  extensionPacks: ir.extensionPacks,
233
283
  capabilities: ir.capabilities,
234
284
  meta: ir.meta,
235
- sources: ir.sources,
236
285
  };
237
-
238
- if (ir.coreHash !== undefined) {
239
- normalized.coreHash = ir.coreHash;
240
- }
241
-
242
- if (ir.profileHash !== undefined) {
243
- normalized.profileHash = ir.profileHash;
244
- }
286
+ Object.assign(
287
+ normalized,
288
+ ifDefined('storageHash', ir.storageHash),
289
+ ifDefined('executionHash', ir.executionHash),
290
+ ifDefined('profileHash', ir.profileHash),
291
+ );
245
292
 
246
293
  const withDefaultsOmitted = omitDefaults(normalized, []) as NormalizedContract;
247
294
  const withSortedIndexes = sortIndexesAndUniques(withDefaultsOmitted.storage);
@@ -249,5 +296,5 @@ export function canonicalizeContract(
249
296
  const withSortedKeys = sortObjectKeys(withSortedStorage) as Record<string, unknown>;
250
297
  const withOrderedTopLevel = orderTopLevel(withSortedKeys);
251
298
 
252
- return JSON.stringify(withOrderedTopLevel, null, 2);
299
+ return JSON.stringify(withOrderedTopLevel, bigintJsonReplacer, 2);
253
300
  }
@@ -1,10 +1,47 @@
1
1
  import type { ContractIR } from '@prisma-next/contract/ir';
2
2
  import type { TargetFamilyHook, ValidationContext } from '@prisma-next/contract/types';
3
+ import { ifDefined } from '@prisma-next/utils/defined';
4
+ import { type } from 'arktype';
3
5
  import { format } from 'prettier';
4
6
  import { canonicalizeContract } from './canonicalization';
5
- import { computeCoreHash, computeProfileHash } from './hashing';
7
+ import { computeExecutionHash, computeProfileHash, computeStorageHash } from './hashing';
6
8
  import type { EmitOptions, EmitResult } from './types';
7
9
 
10
+ const CanonicalMetaSchema = type({
11
+ '[string]': 'unknown',
12
+ });
13
+
14
+ const CanonicalContractSchema = type({
15
+ '+': 'reject',
16
+ schemaVersion: 'string',
17
+ targetFamily: 'string',
18
+ target: 'string',
19
+ models: type({ '[string]': 'unknown' }),
20
+ relations: type({ '[string]': 'unknown' }),
21
+ storage: type({ '[string]': 'unknown' }),
22
+ 'execution?': type({ '[string]': 'unknown' }),
23
+ extensionPacks: type({ '[string]': 'unknown' }),
24
+ capabilities: type({
25
+ '[string]': type({
26
+ '[string]': 'boolean',
27
+ }),
28
+ }),
29
+ meta: CanonicalMetaSchema,
30
+ });
31
+
32
+ function assertCanonicalArtifactShape(value: unknown): void {
33
+ const result = CanonicalContractSchema(value);
34
+ if (result instanceof type.errors) {
35
+ const issues = result
36
+ .map((error) => {
37
+ const path = error.path?.toString() ?? '<root>';
38
+ return `${path}: ${error.message}`;
39
+ })
40
+ .join('; ');
41
+ throw new Error(`ContractIR canonical artifact validation failed: ${issues}`);
42
+ }
43
+ }
44
+
8
45
  function validateCoreStructure(ir: ContractIR): void {
9
46
  if (!ir.targetFamily) {
10
47
  throw new Error('ContractIR must have targetFamily');
@@ -33,9 +70,6 @@ function validateCoreStructure(ir: ContractIR): void {
33
70
  if (!ir.meta || typeof ir.meta !== 'object') {
34
71
  throw new Error('ContractIR must have meta');
35
72
  }
36
- if (!ir.sources || typeof ir.sources !== 'object') {
37
- throw new Error('ContractIR must have sources');
38
- }
39
73
  }
40
74
 
41
75
  export async function emit(
@@ -43,40 +77,52 @@ export async function emit(
43
77
  options: EmitOptions,
44
78
  targetFamily: TargetFamilyHook,
45
79
  ): Promise<EmitResult> {
46
- const { operationRegistry, codecTypeImports, operationTypeImports, extensionIds } = options;
80
+ const {
81
+ operationRegistry,
82
+ codecTypeImports,
83
+ operationTypeImports,
84
+ extensionIds,
85
+ parameterizedRenderers,
86
+ parameterizedTypeImports,
87
+ queryOperationTypeImports,
88
+ } = options;
47
89
 
48
90
  validateCoreStructure(ir);
49
91
 
50
92
  const ctx: ValidationContext = {
51
- ...(operationRegistry ? { operationRegistry } : {}),
52
- ...(codecTypeImports ? { codecTypeImports } : {}),
53
- ...(operationTypeImports ? { operationTypeImports } : {}),
54
- ...(extensionIds ? { extensionIds } : {}),
93
+ ...ifDefined('operationRegistry', operationRegistry),
94
+ ...ifDefined('codecTypeImports', codecTypeImports),
95
+ ...ifDefined('operationTypeImports', operationTypeImports),
96
+ ...ifDefined('extensionIds', extensionIds),
55
97
  };
56
98
  targetFamily.validateTypes(ir, ctx);
57
99
 
58
100
  targetFamily.validateStructure(ir);
59
101
 
60
- const contractJson = {
102
+ const canonicalContract = {
61
103
  schemaVersion: ir.schemaVersion,
62
104
  targetFamily: ir.targetFamily,
63
105
  target: ir.target,
64
106
  models: ir.models,
65
107
  relations: ir.relations,
66
108
  storage: ir.storage,
109
+ ...ifDefined('execution', ir.execution),
67
110
  extensionPacks: ir.extensionPacks,
68
111
  capabilities: ir.capabilities,
69
112
  meta: ir.meta,
70
- sources: ir.sources,
71
- } as const;
113
+ };
114
+ assertCanonicalArtifactShape(canonicalContract);
72
115
 
73
- const coreHash = computeCoreHash(contractJson);
74
- const profileHash = computeProfileHash(contractJson);
116
+ const storageHash = computeStorageHash(canonicalContract);
117
+ const executionHash = canonicalContract.execution
118
+ ? computeExecutionHash(canonicalContract)
119
+ : undefined;
120
+ const profileHash = computeProfileHash(canonicalContract);
75
121
 
76
- const contractWithHashes: ContractIR & { coreHash?: string; profileHash?: string } = {
77
- ...ir,
78
- schemaVersion: contractJson.schemaVersion,
79
- coreHash,
122
+ const contractWithHashes = {
123
+ ...canonicalContract,
124
+ storageHash,
125
+ ...ifDefined('executionHash', executionHash),
80
126
  profileHash,
81
127
  };
82
128
 
@@ -91,16 +137,32 @@ export async function emit(
91
137
  ...contractJsonObj,
92
138
  _generated: {
93
139
  warning: '⚠️ GENERATED FILE - DO NOT EDIT',
94
- message: 'This file is automatically generated by "prisma-next emit".',
95
- regenerate: 'To regenerate, run: prisma-next emit',
140
+ message: 'This file is automatically generated by "prisma-next contract emit".',
141
+ regenerate: 'To regenerate, run: prisma-next contract emit',
96
142
  },
97
143
  };
98
144
  const contractJsonString = JSON.stringify(contractJsonWithMeta, null, 2);
99
145
 
146
+ const generateOptions =
147
+ parameterizedRenderers || parameterizedTypeImports || queryOperationTypeImports
148
+ ? {
149
+ ...ifDefined('parameterizedRenderers', parameterizedRenderers),
150
+ ...ifDefined('parameterizedTypeImports', parameterizedTypeImports),
151
+ ...ifDefined('queryOperationTypeImports', queryOperationTypeImports),
152
+ }
153
+ : undefined;
154
+
155
+ const contractTypeHashes = {
156
+ storageHash,
157
+ ...ifDefined('executionHash', executionHash),
158
+ profileHash,
159
+ };
100
160
  const contractDtsRaw = targetFamily.generateContractTypes(
101
161
  ir,
102
162
  codecTypeImports ?? [],
103
163
  operationTypeImports ?? [],
164
+ contractTypeHashes,
165
+ generateOptions,
104
166
  );
105
167
  const contractDts = await format(contractDtsRaw, {
106
168
  parser: 'typescript',
@@ -112,7 +174,8 @@ export async function emit(
112
174
  return {
113
175
  contractJson: contractJsonString,
114
176
  contractDts,
115
- coreHash,
177
+ storageHash,
178
+ ...ifDefined('executionHash', executionHash),
116
179
  profileHash,
117
180
  };
118
181
  }
@@ -1,46 +1,32 @@
1
1
  import { createHash } from 'node:crypto';
2
- import type { ContractIR } from '@prisma-next/contract/ir';
2
+ import { ifDefined } from '@prisma-next/utils/defined';
3
+ import type { CanonicalContractInput } from './canonicalization';
3
4
  import { canonicalizeContract } from './canonicalization';
4
5
 
5
- type ContractInput = {
6
- schemaVersion: string;
7
- targetFamily: string;
8
- target: string;
9
- models: Record<string, unknown>;
10
- relations: Record<string, unknown>;
11
- storage: Record<string, unknown>;
12
- extensionPacks: Record<string, unknown>;
13
- sources: Record<string, unknown>;
14
- capabilities: Record<string, Record<string, boolean>>;
15
- meta: Record<string, unknown>;
16
- [key: string]: unknown;
17
- };
18
-
19
6
  function computeHash(content: string): string {
20
7
  const hash = createHash('sha256');
21
8
  hash.update(content);
22
9
  return `sha256:${hash.digest('hex')}`;
23
10
  }
24
11
 
25
- export function computeCoreHash(contract: ContractInput): string {
26
- const coreContract: ContractIR = {
12
+ export function computeStorageHash(contract: CanonicalContractInput): string {
13
+ const storageContract = {
27
14
  schemaVersion: contract.schemaVersion,
28
15
  targetFamily: contract.targetFamily,
29
16
  target: contract.target,
30
- models: contract.models,
31
- relations: contract.relations,
32
17
  storage: contract.storage,
33
- extensionPacks: contract.extensionPacks,
34
- sources: contract.sources,
35
- capabilities: contract.capabilities,
36
- meta: contract.meta,
18
+ models: {},
19
+ relations: {},
20
+ extensionPacks: {},
21
+ capabilities: {},
22
+ meta: {},
37
23
  };
38
- const canonical = canonicalizeContract(coreContract);
24
+ const canonical = canonicalizeContract(storageContract);
39
25
  return computeHash(canonical);
40
26
  }
41
27
 
42
- export function computeProfileHash(contract: ContractInput): string {
43
- const profileContract: ContractIR = {
28
+ export function computeProfileHash(contract: CanonicalContractInput): string {
29
+ const profileContract = {
44
30
  schemaVersion: contract.schemaVersion,
45
31
  targetFamily: contract.targetFamily,
46
32
  target: contract.target,
@@ -50,8 +36,24 @@ export function computeProfileHash(contract: ContractInput): string {
50
36
  extensionPacks: {},
51
37
  capabilities: contract.capabilities,
52
38
  meta: {},
53
- sources: {},
54
39
  };
55
40
  const canonical = canonicalizeContract(profileContract);
56
41
  return computeHash(canonical);
57
42
  }
43
+
44
+ export function computeExecutionHash(contract: CanonicalContractInput): string {
45
+ const executionContract = {
46
+ schemaVersion: contract.schemaVersion,
47
+ targetFamily: contract.targetFamily,
48
+ target: contract.target,
49
+ models: {},
50
+ relations: {},
51
+ storage: {},
52
+ extensionPacks: {},
53
+ capabilities: {},
54
+ meta: {},
55
+ ...ifDefined('execution', contract.execution),
56
+ };
57
+ const canonical = canonicalizeContract(executionContract);
58
+ return computeHash(canonical);
59
+ }
@@ -1,4 +1,4 @@
1
- import type { TypesImportSpec } from '@prisma-next/contract/types';
1
+ import type { TypeRenderEntry, TypesImportSpec } from '@prisma-next/contract/types';
2
2
  import type { OperationRegistry } from '@prisma-next/operations';
3
3
 
4
4
  export interface EmitOptions {
@@ -7,11 +7,27 @@ export interface EmitOptions {
7
7
  readonly codecTypeImports?: ReadonlyArray<TypesImportSpec>;
8
8
  readonly operationTypeImports?: ReadonlyArray<TypesImportSpec>;
9
9
  readonly extensionIds?: ReadonlyArray<string>;
10
+ /**
11
+ * Normalized parameterized type renderers, keyed by codecId.
12
+ * These are extracted from descriptors and normalized during assembly.
13
+ */
14
+ readonly parameterizedRenderers?: Map<string, TypeRenderEntry>;
15
+ /**
16
+ * Type imports for parameterized codecs.
17
+ * These are added to contract.d.ts alongside codec and operation type imports.
18
+ */
19
+ readonly parameterizedTypeImports?: ReadonlyArray<TypesImportSpec>;
20
+ /**
21
+ * Query operation type imports for the query builder.
22
+ * Flat operation signatures keyed by operation name.
23
+ */
24
+ readonly queryOperationTypeImports?: ReadonlyArray<TypesImportSpec>;
10
25
  }
11
26
 
12
27
  export interface EmitResult {
13
28
  readonly contractJson: string;
14
29
  readonly contractDts: string;
15
- readonly coreHash: string;
30
+ readonly storageHash: string;
31
+ readonly executionHash?: string;
16
32
  readonly profileHash: string;
17
33
  }