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

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 +58 -0
  8. package/dist/emission.d.mts.map +1 -0
  9. package/dist/emission.mjs +315 -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 +31 -43
  28. package/src/constants.ts +5 -0
  29. package/src/emission/canonicalization.ts +68 -21
  30. package/src/emission/emit.ts +82 -21
  31. package/src/emission/hashing.ts +29 -27
  32. package/src/emission/types.ts +13 -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.90",
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.90",
11
+ "@prisma-next/utils": "0.3.0-dev.90",
12
+ "@prisma-next/operations": "0.3.0-dev.90"
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",
19
- "@prisma-next/test-utils": "0.0.1"
17
+ "vitest": "4.0.17",
18
+ "@prisma-next/test-utils": "0.0.1",
19
+ "@prisma-next/tsdown": "0.0.0",
20
+ "@prisma-next/tsconfig": "0.0.0"
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,51 @@ 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
+ } = options;
47
88
 
48
89
  validateCoreStructure(ir);
49
90
 
50
91
  const ctx: ValidationContext = {
51
- ...(operationRegistry ? { operationRegistry } : {}),
52
- ...(codecTypeImports ? { codecTypeImports } : {}),
53
- ...(operationTypeImports ? { operationTypeImports } : {}),
54
- ...(extensionIds ? { extensionIds } : {}),
92
+ ...ifDefined('operationRegistry', operationRegistry),
93
+ ...ifDefined('codecTypeImports', codecTypeImports),
94
+ ...ifDefined('operationTypeImports', operationTypeImports),
95
+ ...ifDefined('extensionIds', extensionIds),
55
96
  };
56
97
  targetFamily.validateTypes(ir, ctx);
57
98
 
58
99
  targetFamily.validateStructure(ir);
59
100
 
60
- const contractJson = {
101
+ const canonicalContract = {
61
102
  schemaVersion: ir.schemaVersion,
62
103
  targetFamily: ir.targetFamily,
63
104
  target: ir.target,
64
105
  models: ir.models,
65
106
  relations: ir.relations,
66
107
  storage: ir.storage,
108
+ ...ifDefined('execution', ir.execution),
67
109
  extensionPacks: ir.extensionPacks,
68
110
  capabilities: ir.capabilities,
69
111
  meta: ir.meta,
70
- sources: ir.sources,
71
- } as const;
112
+ };
113
+ assertCanonicalArtifactShape(canonicalContract);
72
114
 
73
- const coreHash = computeCoreHash(contractJson);
74
- const profileHash = computeProfileHash(contractJson);
115
+ const storageHash = computeStorageHash(canonicalContract);
116
+ const executionHash = canonicalContract.execution
117
+ ? computeExecutionHash(canonicalContract)
118
+ : undefined;
119
+ const profileHash = computeProfileHash(canonicalContract);
75
120
 
76
- const contractWithHashes: ContractIR & { coreHash?: string; profileHash?: string } = {
77
- ...ir,
78
- schemaVersion: contractJson.schemaVersion,
79
- coreHash,
121
+ const contractWithHashes = {
122
+ ...canonicalContract,
123
+ storageHash,
124
+ ...ifDefined('executionHash', executionHash),
80
125
  profileHash,
81
126
  };
82
127
 
@@ -91,16 +136,31 @@ export async function emit(
91
136
  ...contractJsonObj,
92
137
  _generated: {
93
138
  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',
139
+ message: 'This file is automatically generated by "prisma-next contract emit".',
140
+ regenerate: 'To regenerate, run: prisma-next contract emit',
96
141
  },
97
142
  };
98
143
  const contractJsonString = JSON.stringify(contractJsonWithMeta, null, 2);
99
144
 
145
+ const generateOptions =
146
+ parameterizedRenderers || parameterizedTypeImports
147
+ ? {
148
+ ...ifDefined('parameterizedRenderers', parameterizedRenderers),
149
+ ...ifDefined('parameterizedTypeImports', parameterizedTypeImports),
150
+ }
151
+ : undefined;
152
+
153
+ const contractTypeHashes = {
154
+ storageHash,
155
+ ...ifDefined('executionHash', executionHash),
156
+ profileHash,
157
+ };
100
158
  const contractDtsRaw = targetFamily.generateContractTypes(
101
159
  ir,
102
160
  codecTypeImports ?? [],
103
161
  operationTypeImports ?? [],
162
+ contractTypeHashes,
163
+ generateOptions,
104
164
  );
105
165
  const contractDts = await format(contractDtsRaw, {
106
166
  parser: 'typescript',
@@ -112,7 +172,8 @@ export async function emit(
112
172
  return {
113
173
  contractJson: contractJsonString,
114
174
  contractDts,
115
- coreHash,
175
+ storageHash,
176
+ ...ifDefined('executionHash', executionHash),
116
177
  profileHash,
117
178
  };
118
179
  }
@@ -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,22 @@ 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>;
10
20
  }
11
21
 
12
22
  export interface EmitResult {
13
23
  readonly contractJson: string;
14
24
  readonly contractDts: string;
15
- readonly coreHash: string;
25
+ readonly storageHash: string;
26
+ readonly executionHash?: string;
16
27
  readonly profileHash: string;
17
28
  }