@prisma-next/core-control-plane 0.3.0-dev.1 → 0.3.0-dev.100

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 (62) 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/{exports/schema-view.d.ts → schema-view-DObwT8x9.d.mts} +15 -13
  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 +33 -40
  28. package/src/constants.ts +5 -0
  29. package/src/emission/canonicalization.ts +300 -0
  30. package/src/emission/emit.ts +181 -0
  31. package/src/emission/hashing.ts +59 -0
  32. package/src/emission/types.ts +33 -0
  33. package/src/errors.ts +533 -0
  34. package/src/exports/constants.ts +1 -0
  35. package/src/exports/emission.ts +6 -0
  36. package/src/exports/errors.ts +27 -0
  37. package/src/exports/schema-view.ts +1 -0
  38. package/src/exports/stack.ts +1 -0
  39. package/src/exports/types.ts +38 -0
  40. package/src/migrations.ts +273 -0
  41. package/src/schema-view.ts +95 -0
  42. package/src/stack.ts +38 -0
  43. package/src/types.ts +536 -0
  44. package/dist/chunk-U5RYT6PT.js +0 -229
  45. package/dist/chunk-U5RYT6PT.js.map +0 -1
  46. package/dist/exports/config-types.d.ts +0 -70
  47. package/dist/exports/config-types.js +0 -53
  48. package/dist/exports/config-types.js.map +0 -1
  49. package/dist/exports/config-validation.d.ts +0 -18
  50. package/dist/exports/config-validation.js +0 -252
  51. package/dist/exports/config-validation.js.map +0 -1
  52. package/dist/exports/emission.d.ts +0 -42
  53. package/dist/exports/emission.js +0 -310
  54. package/dist/exports/emission.js.map +0 -1
  55. package/dist/exports/errors.d.ts +0 -184
  56. package/dist/exports/errors.js +0 -43
  57. package/dist/exports/errors.js.map +0 -1
  58. package/dist/exports/schema-view.js +0 -1
  59. package/dist/exports/schema-view.js.map +0 -1
  60. package/dist/exports/types.d.ts +0 -589
  61. package/dist/exports/types.js +0 -1
  62. package/dist/exports/types.js.map +0 -1
package/package.json CHANGED
@@ -1,60 +1,53 @@
1
1
  {
2
2
  "name": "@prisma-next/core-control-plane",
3
- "version": "0.3.0-dev.1",
3
+ "version": "0.3.0-dev.100",
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.1",
11
- "@prisma-next/operations": "0.3.0-dev.1",
12
- "@prisma-next/utils": "0.3.0-dev.1"
10
+ "@prisma-next/operations": "0.3.0-dev.100",
11
+ "@prisma-next/utils": "0.3.0-dev.100",
12
+ "@prisma-next/contract": "0.3.0-dev.100"
13
13
  },
14
14
  "devDependencies": {
15
- "@vitest/coverage-v8": "^4.0.0",
16
- "tsup": "^8.3.0",
17
- "typescript": "^5.9.3",
18
- "vitest": "^4.0.16",
15
+ "tsdown": "0.18.4",
16
+ "typescript": "5.9.3",
17
+ "vitest": "4.0.17",
18
+ "@prisma-next/tsdown": "0.0.0",
19
+ "@prisma-next/tsconfig": "0.0.0",
19
20
  "@prisma-next/test-utils": "0.0.1"
20
21
  },
21
22
  "files": [
22
- "dist"
23
+ "dist",
24
+ "src"
23
25
  ],
26
+ "engines": {
27
+ "node": ">=20"
28
+ },
24
29
  "exports": {
25
- "./config-types": {
26
- "types": "./dist/exports/config-types.d.ts",
27
- "import": "./dist/exports/config-types.js"
28
- },
29
- "./config-validation": {
30
- "types": "./dist/exports/config-validation.d.ts",
31
- "import": "./dist/exports/config-validation.js"
32
- },
33
- "./errors": {
34
- "types": "./dist/exports/errors.d.ts",
35
- "import": "./dist/exports/errors.js"
36
- },
37
- "./types": {
38
- "types": "./dist/exports/types.d.ts",
39
- "import": "./dist/exports/types.js"
40
- },
41
- "./emission": {
42
- "types": "./dist/exports/emission.d.ts",
43
- "import": "./dist/exports/emission.js"
44
- },
45
- "./schema-view": {
46
- "types": "./dist/exports/schema-view.d.ts",
47
- "import": "./dist/exports/schema-view.js"
48
- }
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"
49
42
  },
50
43
  "scripts": {
51
- "build": "tsup --config tsup.config.ts",
44
+ "build": "tsdown",
52
45
  "test": "vitest run --passWithNoTests",
53
46
  "test:coverage": "vitest run --coverage --passWithNoTests",
54
- "typecheck": "tsc --project tsconfig.json --noEmit",
55
- "lint": "biome check . --config-path ../../../../../biome.json --error-on-warnings",
56
- "lint:fix": "biome check --write . --config-path ../../../biome.json",
57
- "lint:fix:unsafe": "biome check --write --unsafe . --config-path ../../../biome.json",
58
- "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"
59
52
  }
60
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;
@@ -0,0 +1,300 @@
1
+ import { bigintJsonReplacer } from '@prisma-next/contract/types';
2
+ import { isArrayEqual } from '@prisma-next/utils/array-equal';
3
+ import { ifDefined } from '@prisma-next/utils/defined';
4
+
5
+ type NormalizedContract = {
6
+ schemaVersion: string;
7
+ targetFamily: string;
8
+ target: string;
9
+ storageHash?: string;
10
+ executionHash?: string;
11
+ profileHash?: string;
12
+ models: Record<string, unknown>;
13
+ relations: Record<string, unknown>;
14
+ storage: Record<string, unknown>;
15
+ execution?: Record<string, unknown>;
16
+ extensionPacks: Record<string, unknown>;
17
+ capabilities: Record<string, Record<string, boolean>>;
18
+ meta: 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;
35
+ };
36
+
37
+ const TOP_LEVEL_ORDER = [
38
+ 'schemaVersion',
39
+ 'canonicalVersion',
40
+ 'targetFamily',
41
+ 'target',
42
+ 'storageHash',
43
+ 'executionHash',
44
+ 'profileHash',
45
+ 'models',
46
+ 'relations',
47
+ 'storage',
48
+ 'execution',
49
+ 'capabilities',
50
+ 'extensionPacks',
51
+ 'meta',
52
+ ] as const;
53
+
54
+ function isDefaultValue(value: unknown): boolean {
55
+ if (value === false) return true;
56
+ if (value === null) return false;
57
+ if (value instanceof Date) return false;
58
+ if (Array.isArray(value) && value.length === 0) return true;
59
+ if (typeof value === 'object' && value !== null) {
60
+ const keys = Object.keys(value);
61
+ return keys.length === 0;
62
+ }
63
+ return false;
64
+ }
65
+
66
+ function omitDefaults(obj: unknown, path: readonly string[]): unknown {
67
+ if (obj === null || typeof obj !== 'object') {
68
+ return obj;
69
+ }
70
+
71
+ if (obj instanceof Date) {
72
+ return obj;
73
+ }
74
+
75
+ if (Array.isArray(obj)) {
76
+ return obj.map((item) => omitDefaults(item, path));
77
+ }
78
+
79
+ const result: Record<string, unknown> = {};
80
+
81
+ for (const [key, value] of Object.entries(obj)) {
82
+ const currentPath = [...path, key];
83
+
84
+ // Exclude metadata fields from canonicalization
85
+ if (key === '_generated') {
86
+ continue;
87
+ }
88
+
89
+ if (key === 'nullable' && value === false) {
90
+ continue;
91
+ }
92
+
93
+ if (key === 'generated' && value === false) {
94
+ continue;
95
+ }
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
+
104
+ if (isDefaultValue(value)) {
105
+ const isRequiredModels = isArrayEqual(currentPath, ['models']);
106
+ const isRequiredTables = isArrayEqual(currentPath, ['storage', 'tables']);
107
+ const isRequiredRelations = isArrayEqual(currentPath, ['relations']);
108
+ const isRequiredExtensionPacks = isArrayEqual(currentPath, ['extensionPacks']);
109
+ const isRequiredCapabilities = isArrayEqual(currentPath, ['capabilities']);
110
+ const isRequiredMeta = isArrayEqual(currentPath, ['meta']);
111
+ const isRequiredExecutionDefaults = isArrayEqual(currentPath, [
112
+ 'execution',
113
+ 'mutations',
114
+ 'defaults',
115
+ ]);
116
+ const isExtensionNamespace = currentPath.length === 2 && currentPath[0] === 'extensionPacks';
117
+ const isModelRelations =
118
+ currentPath.length === 3 &&
119
+ isArrayEqual([currentPath[0], currentPath[2]], ['models', 'relations']);
120
+ const isTableUniques =
121
+ currentPath.length === 4 &&
122
+ isArrayEqual(
123
+ [currentPath[0], currentPath[1], currentPath[3]],
124
+ ['storage', 'tables', 'uniques'],
125
+ );
126
+ const isTableIndexes =
127
+ currentPath.length === 4 &&
128
+ isArrayEqual(
129
+ [currentPath[0], currentPath[1], currentPath[3]],
130
+ ['storage', 'tables', 'indexes'],
131
+ );
132
+ const isTableForeignKeys =
133
+ currentPath.length === 4 &&
134
+ isArrayEqual(
135
+ [currentPath[0], currentPath[1], currentPath[3]],
136
+ ['storage', 'tables', 'foreignKeys'],
137
+ );
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
+
149
+ if (
150
+ !isRequiredModels &&
151
+ !isRequiredTables &&
152
+ !isRequiredRelations &&
153
+ !isRequiredExtensionPacks &&
154
+ !isRequiredCapabilities &&
155
+ !isRequiredMeta &&
156
+ !isRequiredExecutionDefaults &&
157
+ !isExtensionNamespace &&
158
+ !isModelRelations &&
159
+ !isTableUniques &&
160
+ !isTableIndexes &&
161
+ !isTableForeignKeys &&
162
+ !isFkBooleanField
163
+ ) {
164
+ continue;
165
+ }
166
+ }
167
+
168
+ result[key] = omitDefaults(value, currentPath);
169
+ }
170
+
171
+ return result;
172
+ }
173
+
174
+ function sortObjectKeys(obj: unknown): unknown {
175
+ if (obj === null || typeof obj !== 'object') {
176
+ return obj;
177
+ }
178
+
179
+ if (obj instanceof Date) {
180
+ return obj;
181
+ }
182
+
183
+ if (Array.isArray(obj)) {
184
+ return obj.map((item) => sortObjectKeys(item));
185
+ }
186
+
187
+ const sorted: Record<string, unknown> = {};
188
+ const keys = Object.keys(obj).sort();
189
+ for (const key of keys) {
190
+ sorted[key] = sortObjectKeys((obj as Record<string, unknown>)[key]);
191
+ }
192
+
193
+ return sorted;
194
+ }
195
+
196
+ type StorageObject = {
197
+ tables?: Record<string, unknown>;
198
+ [key: string]: unknown;
199
+ };
200
+
201
+ type TableObject = {
202
+ indexes?: unknown[];
203
+ uniques?: unknown[];
204
+ [key: string]: unknown;
205
+ };
206
+
207
+ function sortIndexesAndUniques(storage: unknown): unknown {
208
+ if (!storage || typeof storage !== 'object') {
209
+ return storage;
210
+ }
211
+
212
+ const storageObj = storage as StorageObject;
213
+ if (!storageObj.tables || typeof storageObj.tables !== 'object') {
214
+ return storage;
215
+ }
216
+
217
+ const tables = storageObj.tables;
218
+ const result: StorageObject = { ...storageObj };
219
+
220
+ result.tables = {};
221
+ // Sort table names to ensure deterministic ordering
222
+ const sortedTableNames = Object.keys(tables).sort();
223
+ for (const tableName of sortedTableNames) {
224
+ const table = tables[tableName];
225
+ if (!table || typeof table !== 'object') {
226
+ result.tables[tableName] = table;
227
+ continue;
228
+ }
229
+
230
+ const tableObj = table as TableObject;
231
+ const sortedTable: TableObject = { ...tableObj };
232
+
233
+ if (Array.isArray(tableObj.indexes)) {
234
+ sortedTable.indexes = [...tableObj.indexes].sort((a, b) => {
235
+ const nameA = (a as { name?: string })?.name || '';
236
+ const nameB = (b as { name?: string })?.name || '';
237
+ return nameA.localeCompare(nameB);
238
+ });
239
+ }
240
+
241
+ if (Array.isArray(tableObj.uniques)) {
242
+ sortedTable.uniques = [...tableObj.uniques].sort((a, b) => {
243
+ const nameA = (a as { name?: string })?.name || '';
244
+ const nameB = (b as { name?: string })?.name || '';
245
+ return nameA.localeCompare(nameB);
246
+ });
247
+ }
248
+
249
+ result.tables[tableName] = sortedTable;
250
+ }
251
+
252
+ return result;
253
+ }
254
+
255
+ function orderTopLevel(obj: Record<string, unknown>): Record<string, unknown> {
256
+ const ordered: Record<string, unknown> = {};
257
+ const remaining = new Set(Object.keys(obj));
258
+
259
+ for (const key of TOP_LEVEL_ORDER) {
260
+ if (remaining.has(key)) {
261
+ ordered[key] = obj[key];
262
+ remaining.delete(key);
263
+ }
264
+ }
265
+
266
+ for (const key of Array.from(remaining).sort()) {
267
+ ordered[key] = obj[key];
268
+ }
269
+
270
+ return ordered;
271
+ }
272
+
273
+ export function canonicalizeContract(ir: CanonicalContractInput): string {
274
+ const normalized: NormalizedContract = {
275
+ schemaVersion: ir.schemaVersion,
276
+ targetFamily: ir.targetFamily,
277
+ target: ir.target,
278
+ models: ir.models,
279
+ relations: ir.relations,
280
+ storage: ir.storage,
281
+ ...ifDefined('execution', ir.execution),
282
+ extensionPacks: ir.extensionPacks,
283
+ capabilities: ir.capabilities,
284
+ meta: ir.meta,
285
+ };
286
+ Object.assign(
287
+ normalized,
288
+ ifDefined('storageHash', ir.storageHash),
289
+ ifDefined('executionHash', ir.executionHash),
290
+ ifDefined('profileHash', ir.profileHash),
291
+ );
292
+
293
+ const withDefaultsOmitted = omitDefaults(normalized, []) as NormalizedContract;
294
+ const withSortedIndexes = sortIndexesAndUniques(withDefaultsOmitted.storage);
295
+ const withSortedStorage = { ...withDefaultsOmitted, storage: withSortedIndexes };
296
+ const withSortedKeys = sortObjectKeys(withSortedStorage) as Record<string, unknown>;
297
+ const withOrderedTopLevel = orderTopLevel(withSortedKeys);
298
+
299
+ return JSON.stringify(withOrderedTopLevel, bigintJsonReplacer, 2);
300
+ }
@@ -0,0 +1,181 @@
1
+ import type { ContractIR } from '@prisma-next/contract/ir';
2
+ import type { TargetFamilyHook, ValidationContext } from '@prisma-next/contract/types';
3
+ import { ifDefined } from '@prisma-next/utils/defined';
4
+ import { type } from 'arktype';
5
+ import { format } from 'prettier';
6
+ import { canonicalizeContract } from './canonicalization';
7
+ import { computeExecutionHash, computeProfileHash, computeStorageHash } from './hashing';
8
+ import type { EmitOptions, EmitResult } from './types';
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
+
45
+ function validateCoreStructure(ir: ContractIR): void {
46
+ if (!ir.targetFamily) {
47
+ throw new Error('ContractIR must have targetFamily');
48
+ }
49
+ if (!ir.target) {
50
+ throw new Error('ContractIR must have target');
51
+ }
52
+ if (!ir.schemaVersion) {
53
+ throw new Error('ContractIR must have schemaVersion');
54
+ }
55
+ if (!ir.models || typeof ir.models !== 'object') {
56
+ throw new Error('ContractIR must have models');
57
+ }
58
+ if (!ir.storage || typeof ir.storage !== 'object') {
59
+ throw new Error('ContractIR must have storage');
60
+ }
61
+ if (!ir.relations || typeof ir.relations !== 'object') {
62
+ throw new Error('ContractIR must have relations');
63
+ }
64
+ if (!ir.extensionPacks || typeof ir.extensionPacks !== 'object') {
65
+ throw new Error('ContractIR must have extensionPacks');
66
+ }
67
+ if (!ir.capabilities || typeof ir.capabilities !== 'object') {
68
+ throw new Error('ContractIR must have capabilities');
69
+ }
70
+ if (!ir.meta || typeof ir.meta !== 'object') {
71
+ throw new Error('ContractIR must have meta');
72
+ }
73
+ }
74
+
75
+ export async function emit(
76
+ ir: ContractIR,
77
+ options: EmitOptions,
78
+ targetFamily: TargetFamilyHook,
79
+ ): Promise<EmitResult> {
80
+ const {
81
+ operationRegistry,
82
+ codecTypeImports,
83
+ operationTypeImports,
84
+ extensionIds,
85
+ parameterizedRenderers,
86
+ parameterizedTypeImports,
87
+ queryOperationTypeImports,
88
+ } = options;
89
+
90
+ validateCoreStructure(ir);
91
+
92
+ const ctx: ValidationContext = {
93
+ ...ifDefined('operationRegistry', operationRegistry),
94
+ ...ifDefined('codecTypeImports', codecTypeImports),
95
+ ...ifDefined('operationTypeImports', operationTypeImports),
96
+ ...ifDefined('extensionIds', extensionIds),
97
+ };
98
+ targetFamily.validateTypes(ir, ctx);
99
+
100
+ targetFamily.validateStructure(ir);
101
+
102
+ const canonicalContract = {
103
+ schemaVersion: ir.schemaVersion,
104
+ targetFamily: ir.targetFamily,
105
+ target: ir.target,
106
+ models: ir.models,
107
+ relations: ir.relations,
108
+ storage: ir.storage,
109
+ ...ifDefined('execution', ir.execution),
110
+ extensionPacks: ir.extensionPacks,
111
+ capabilities: ir.capabilities,
112
+ meta: ir.meta,
113
+ };
114
+ assertCanonicalArtifactShape(canonicalContract);
115
+
116
+ const storageHash = computeStorageHash(canonicalContract);
117
+ const executionHash = canonicalContract.execution
118
+ ? computeExecutionHash(canonicalContract)
119
+ : undefined;
120
+ const profileHash = computeProfileHash(canonicalContract);
121
+
122
+ const contractWithHashes = {
123
+ ...canonicalContract,
124
+ storageHash,
125
+ ...ifDefined('executionHash', executionHash),
126
+ profileHash,
127
+ };
128
+
129
+ // Add _generated metadata to indicate this is a generated artifact
130
+ // This ensures consistency between CLI emit and programmatic emit
131
+ // Always add/update _generated with standard content for consistency
132
+ const contractJsonObj = JSON.parse(canonicalizeContract(contractWithHashes)) as Record<
133
+ string,
134
+ unknown
135
+ >;
136
+ const contractJsonWithMeta = {
137
+ ...contractJsonObj,
138
+ _generated: {
139
+ warning: '⚠️ GENERATED FILE - DO NOT EDIT',
140
+ message: 'This file is automatically generated by "prisma-next contract emit".',
141
+ regenerate: 'To regenerate, run: prisma-next contract emit',
142
+ },
143
+ };
144
+ const contractJsonString = JSON.stringify(contractJsonWithMeta, null, 2);
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
+ };
160
+ const contractDtsRaw = targetFamily.generateContractTypes(
161
+ ir,
162
+ codecTypeImports ?? [],
163
+ operationTypeImports ?? [],
164
+ contractTypeHashes,
165
+ generateOptions,
166
+ );
167
+ const contractDts = await format(contractDtsRaw, {
168
+ parser: 'typescript',
169
+ singleQuote: true,
170
+ semi: true,
171
+ printWidth: 100,
172
+ });
173
+
174
+ return {
175
+ contractJson: contractJsonString,
176
+ contractDts,
177
+ storageHash,
178
+ ...ifDefined('executionHash', executionHash),
179
+ profileHash,
180
+ };
181
+ }
@@ -0,0 +1,59 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { ifDefined } from '@prisma-next/utils/defined';
3
+ import type { CanonicalContractInput } from './canonicalization';
4
+ import { canonicalizeContract } from './canonicalization';
5
+
6
+ function computeHash(content: string): string {
7
+ const hash = createHash('sha256');
8
+ hash.update(content);
9
+ return `sha256:${hash.digest('hex')}`;
10
+ }
11
+
12
+ export function computeStorageHash(contract: CanonicalContractInput): string {
13
+ const storageContract = {
14
+ schemaVersion: contract.schemaVersion,
15
+ targetFamily: contract.targetFamily,
16
+ target: contract.target,
17
+ storage: contract.storage,
18
+ models: {},
19
+ relations: {},
20
+ extensionPacks: {},
21
+ capabilities: {},
22
+ meta: {},
23
+ };
24
+ const canonical = canonicalizeContract(storageContract);
25
+ return computeHash(canonical);
26
+ }
27
+
28
+ export function computeProfileHash(contract: CanonicalContractInput): string {
29
+ const profileContract = {
30
+ schemaVersion: contract.schemaVersion,
31
+ targetFamily: contract.targetFamily,
32
+ target: contract.target,
33
+ models: {},
34
+ relations: {},
35
+ storage: {},
36
+ extensionPacks: {},
37
+ capabilities: contract.capabilities,
38
+ meta: {},
39
+ };
40
+ const canonical = canonicalizeContract(profileContract);
41
+ return computeHash(canonical);
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
+ }
@@ -0,0 +1,33 @@
1
+ import type { TypeRenderEntry, TypesImportSpec } from '@prisma-next/contract/types';
2
+ import type { OperationRegistry } from '@prisma-next/operations';
3
+
4
+ export interface EmitOptions {
5
+ readonly outputDir: string;
6
+ readonly operationRegistry?: OperationRegistry;
7
+ readonly codecTypeImports?: ReadonlyArray<TypesImportSpec>;
8
+ readonly operationTypeImports?: ReadonlyArray<TypesImportSpec>;
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>;
25
+ }
26
+
27
+ export interface EmitResult {
28
+ readonly contractJson: string;
29
+ readonly contractDts: string;
30
+ readonly storageHash: string;
31
+ readonly executionHash?: string;
32
+ readonly profileHash: string;
33
+ }