@prisma-next/core-control-plane 0.3.0-dev.5 → 0.3.0-dev.50

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 (93) hide show
  1. package/README.md +35 -1
  2. package/dist/config-types-Ci0VxOEC.d.mts +96 -0
  3. package/dist/config-types-Ci0VxOEC.d.mts.map +1 -0
  4. package/dist/config-types.d.mts +3 -0
  5. package/dist/config-types.mjs +60 -0
  6. package/dist/config-types.mjs.map +1 -0
  7. package/dist/config-validation.d.mts +16 -0
  8. package/dist/config-validation.d.mts.map +1 -0
  9. package/dist/config-validation.mjs +79 -0
  10. package/dist/config-validation.mjs.map +1 -0
  11. package/dist/emission.d.mts +58 -0
  12. package/dist/emission.d.mts.map +1 -0
  13. package/dist/emission.mjs +315 -0
  14. package/dist/emission.mjs.map +1 -0
  15. package/dist/errors-Qlh0sdcb.mjs +276 -0
  16. package/dist/errors-Qlh0sdcb.mjs.map +1 -0
  17. package/dist/errors.d.mts +191 -0
  18. package/dist/errors.d.mts.map +1 -0
  19. package/dist/errors.mjs +3 -0
  20. package/dist/{schema-view.d.ts → schema-view-D9u47nc8.d.mts} +13 -10
  21. package/dist/schema-view-D9u47nc8.d.mts.map +1 -0
  22. package/dist/schema-view.d.mts +2 -0
  23. package/dist/schema-view.mjs +1 -0
  24. package/dist/stack.d.mts +30 -0
  25. package/dist/stack.d.mts.map +1 -0
  26. package/dist/stack.mjs +30 -0
  27. package/dist/stack.mjs.map +1 -0
  28. package/dist/types-CuAauVCJ.d.mts +597 -0
  29. package/dist/types-CuAauVCJ.d.mts.map +1 -0
  30. package/dist/types.d.mts +2 -0
  31. package/dist/types.mjs +1 -0
  32. package/package.json +31 -38
  33. package/src/config-types.ts +30 -35
  34. package/src/config-validation.ts +7 -5
  35. package/src/contract-source-types.ts +28 -0
  36. package/src/emission/canonicalization.ts +68 -21
  37. package/src/emission/emit.ts +82 -21
  38. package/src/emission/hashing.ts +29 -27
  39. package/src/emission/types.ts +13 -2
  40. package/src/errors.ts +24 -5
  41. package/src/exports/config-types.ts +7 -0
  42. package/src/exports/emission.ts +1 -1
  43. package/src/exports/errors.ts +1 -1
  44. package/src/exports/stack.ts +1 -0
  45. package/src/exports/types.ts +1 -0
  46. package/src/migrations.ts +1 -1
  47. package/src/stack.ts +38 -0
  48. package/src/types.ts +41 -17
  49. package/dist/chunk-U5RYT6PT.js +0 -229
  50. package/dist/chunk-U5RYT6PT.js.map +0 -1
  51. package/dist/config-types.d.ts +0 -68
  52. package/dist/config-types.d.ts.map +0 -1
  53. package/dist/config-validation.d.ts +0 -10
  54. package/dist/config-validation.d.ts.map +0 -1
  55. package/dist/emission/canonicalization.d.ts +0 -6
  56. package/dist/emission/canonicalization.d.ts.map +0 -1
  57. package/dist/emission/emit.d.ts +0 -5
  58. package/dist/emission/emit.d.ts.map +0 -1
  59. package/dist/emission/hashing.d.ts +0 -17
  60. package/dist/emission/hashing.d.ts.map +0 -1
  61. package/dist/emission/types.d.ts +0 -16
  62. package/dist/emission/types.d.ts.map +0 -1
  63. package/dist/errors.d.ts +0 -183
  64. package/dist/errors.d.ts.map +0 -1
  65. package/dist/exports/config-types.d.ts +0 -3
  66. package/dist/exports/config-types.d.ts.map +0 -1
  67. package/dist/exports/config-types.js +0 -53
  68. package/dist/exports/config-types.js.map +0 -1
  69. package/dist/exports/config-validation.d.ts +0 -2
  70. package/dist/exports/config-validation.d.ts.map +0 -1
  71. package/dist/exports/config-validation.js +0 -252
  72. package/dist/exports/config-validation.js.map +0 -1
  73. package/dist/exports/emission.d.ts +0 -5
  74. package/dist/exports/emission.d.ts.map +0 -1
  75. package/dist/exports/emission.js +0 -310
  76. package/dist/exports/emission.js.map +0 -1
  77. package/dist/exports/errors.d.ts +0 -3
  78. package/dist/exports/errors.d.ts.map +0 -1
  79. package/dist/exports/errors.js +0 -43
  80. package/dist/exports/errors.js.map +0 -1
  81. package/dist/exports/schema-view.d.ts +0 -2
  82. package/dist/exports/schema-view.d.ts.map +0 -1
  83. package/dist/exports/schema-view.js +0 -1
  84. package/dist/exports/schema-view.js.map +0 -1
  85. package/dist/exports/types.d.ts +0 -2
  86. package/dist/exports/types.d.ts.map +0 -1
  87. package/dist/exports/types.js +0 -1
  88. package/dist/exports/types.js.map +0 -1
  89. package/dist/migrations.d.ts +0 -190
  90. package/dist/migrations.d.ts.map +0 -1
  91. package/dist/schema-view.d.ts.map +0 -1
  92. package/dist/types.d.ts +0 -400
  93. package/dist/types.d.ts.map +0 -1
package/package.json CHANGED
@@ -1,61 +1,54 @@
1
1
  {
2
2
  "name": "@prisma-next/core-control-plane",
3
- "version": "0.3.0-dev.5",
3
+ "version": "0.3.0-dev.50",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Control plane domain actions, config types, validation, and error factories for Prisma Next",
7
7
  "dependencies": {
8
8
  "arktype": "^2.1.26",
9
9
  "prettier": "^3.3.3",
10
- "@prisma-next/contract": "0.3.0-dev.5",
11
- "@prisma-next/operations": "0.3.0-dev.5",
12
- "@prisma-next/utils": "0.3.0-dev.5"
10
+ "@prisma-next/contract": "0.3.0-dev.50",
11
+ "@prisma-next/operations": "0.3.0-dev.50",
12
+ "@prisma-next/utils": "0.3.0-dev.50"
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/tsconfig": "0.0.0",
20
+ "@prisma-next/tsdown": "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
- "./emission": {
43
- "types": "./dist/exports/emission.d.ts",
44
- "import": "./dist/exports/emission.js"
45
- },
46
- "./schema-view": {
47
- "types": "./dist/exports/schema-view.d.ts",
48
- "import": "./dist/exports/schema-view.js"
49
- }
30
+ "./config-types": "./dist/config-types.mjs",
31
+ "./config-validation": "./dist/config-validation.mjs",
32
+ "./emission": "./dist/emission.mjs",
33
+ "./errors": "./dist/errors.mjs",
34
+ "./schema-view": "./dist/schema-view.mjs",
35
+ "./stack": "./dist/stack.mjs",
36
+ "./types": "./dist/types.mjs",
37
+ "./package.json": "./package.json"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/prisma/prisma-next.git",
42
+ "directory": "packages/1-framework/1-core/migration/control-plane"
50
43
  },
51
44
  "scripts": {
52
- "build": "tsup --config tsup.config.ts && tsc --project tsconfig.build.json",
45
+ "build": "tsdown",
53
46
  "test": "vitest run --passWithNoTests",
54
47
  "test:coverage": "vitest run --coverage --passWithNoTests",
55
- "typecheck": "tsc --project tsconfig.json --noEmit",
56
- "lint": "biome check . --config-path ../../../../../biome.json --error-on-warnings",
57
- "lint:fix": "biome check --write . --config-path ../../../biome.json",
58
- "lint:fix:unsafe": "biome check --write --unsafe . --config-path ../../../biome.json",
59
- "clean": "node ../../../../../scripts/clean.mjs"
48
+ "typecheck": "tsc --noEmit",
49
+ "lint": "biome check . --error-on-warnings",
50
+ "lint:fix": "biome check --write .",
51
+ "lint:fix:unsafe": "biome check --write --unsafe .",
52
+ "clean": "rm -rf dist dist-tsc dist-tsc-prod coverage .tmp-output"
60
53
  }
61
54
  }
@@ -1,5 +1,5 @@
1
- import { dirname, join } from 'node:path';
2
1
  import { type } from 'arktype';
2
+ import type { ContractSourceProvider } from './contract-source-types';
3
3
  import type {
4
4
  ControlAdapterDescriptor,
5
5
  ControlDriverDescriptor,
@@ -20,21 +20,15 @@ export type CliDriver = ControlDriverInstance<string, string>;
20
20
  */
21
21
  export interface ContractConfig {
22
22
  /**
23
- * Contract source. Can be a value or a function that returns a value (sync or async).
24
- * If a function, it will be called to resolve the contract.
23
+ * Contract source provider. The provider is always async and must return
24
+ * a Result containing either ContractIR or structured diagnostics.
25
25
  */
26
- readonly source: unknown | (() => unknown | Promise<unknown>);
26
+ readonly source: ContractSourceProvider;
27
27
  /**
28
28
  * Path to contract.json artifact. Defaults to 'src/prisma/contract.json'.
29
- * This is the canonical location where other CLI commands can find the contract JSON.
29
+ * The .d.ts types file will be colocated (e.g., contract.json contract.d.ts).
30
30
  */
31
31
  readonly output?: string;
32
- /**
33
- * Path to contract.d.ts artifact. Defaults to output with .d.ts extension.
34
- * If output ends with .json, replaces .json with .d.ts.
35
- * Otherwise, appends .d.ts to the directory containing output.
36
- */
37
- readonly types?: string;
38
32
  }
39
33
 
40
34
  /**
@@ -43,10 +37,12 @@ export interface ContractConfig {
43
37
  *
44
38
  * @template TFamilyId - The family ID (e.g., 'sql', 'document')
45
39
  * @template TTargetId - The target ID (e.g., 'postgres', 'mysql')
40
+ * @template TConnection - The driver connection input type (defaults to `unknown` for config flexibility)
46
41
  */
47
42
  export interface PrismaNextConfig<
48
43
  TFamilyId extends string = string,
49
44
  TTargetId extends string = string,
45
+ TConnection = unknown,
50
46
  > {
51
47
  readonly family: ControlFamilyDescriptor<TFamilyId>;
52
48
  readonly target: ControlTargetDescriptor<TFamilyId, TTargetId>;
@@ -56,10 +52,25 @@ export interface PrismaNextConfig<
56
52
  * Driver descriptor for DB-connected CLI commands.
57
53
  * Required for DB-connected commands (e.g., db verify).
58
54
  * Optional for commands that don't need database access (e.g., emit).
55
+ * The driver's connection type matches the TConnection config parameter.
56
+ */
57
+ readonly driver?: ControlDriverDescriptor<
58
+ TFamilyId,
59
+ TTargetId,
60
+ ControlDriverInstance<TFamilyId, TTargetId>,
61
+ TConnection
62
+ >;
63
+ /**
64
+ * Database connection configuration.
65
+ * The connection type is driver-specific (e.g., URL string for Postgres).
59
66
  */
60
- readonly driver?: ControlDriverDescriptor<TFamilyId, TTargetId>;
61
67
  readonly db?: {
62
- readonly url?: string;
68
+ /**
69
+ * Driver-specific connection input.
70
+ * For Postgres: a connection string (URL).
71
+ * For other drivers: may be a structured object.
72
+ */
73
+ readonly connection?: TConnection;
63
74
  };
64
75
  /**
65
76
  * Contract configuration. Specifies source and artifact locations.
@@ -70,12 +81,12 @@ export interface PrismaNextConfig<
70
81
 
71
82
  /**
72
83
  * Arktype schema for ContractConfig validation.
73
- * Validates that source is present and output/types are strings when provided.
84
+ * Validates presence/shape only.
85
+ * contract.source is validated as a provider function at runtime in defineConfig().
74
86
  */
75
87
  const ContractConfigSchema = type({
76
- source: 'unknown', // Can be value or function - runtime check needed
88
+ source: 'unknown', // Runtime check enforces provider function shape
77
89
  'output?': 'string',
78
- 'types?': 'string',
79
90
  });
80
91
 
81
92
  /**
@@ -98,7 +109,6 @@ const PrismaNextConfigSchema = type({
98
109
  *
99
110
  * Normalization:
100
111
  * - contract.output defaults to 'src/prisma/contract.json' if missing
101
- * - contract.types defaults to output with .d.ts extension if missing
102
112
  *
103
113
  * @param config - Raw config input from user
104
114
  * @returns Normalized config IR with defaults applied
@@ -116,33 +126,18 @@ export function defineConfig<TFamilyId extends string = string, TTargetId extend
116
126
 
117
127
  // Normalize contract config if present
118
128
  if (config.contract) {
119
- // Validate contract.source is a value or function (runtime check)
129
+ // Validate contract.source provider function shape at runtime.
120
130
  const source = config.contract.source;
121
- if (
122
- source !== null &&
123
- typeof source !== 'object' &&
124
- typeof source !== 'function' &&
125
- typeof source !== 'string' &&
126
- typeof source !== 'number' &&
127
- typeof source !== 'boolean'
128
- ) {
129
- throw new Error(
130
- 'Config.contract.source must be a value (object, string, number, boolean, null) or a function',
131
- );
131
+ if (typeof source !== 'function') {
132
+ throw new Error('Config.contract.source must be a provider function');
132
133
  }
133
134
 
134
135
  // Apply defaults
135
136
  const output = config.contract.output ?? 'src/prisma/contract.json';
136
- const types =
137
- config.contract.types ??
138
- (output.endsWith('.json')
139
- ? `${output.slice(0, -5)}.d.ts`
140
- : join(dirname(output), 'contract.d.ts'));
141
137
 
142
138
  const normalizedContract: ContractConfig = {
143
139
  source: config.contract.source,
144
140
  output,
145
- types,
146
141
  };
147
142
 
148
143
  // Return normalized config
@@ -256,15 +256,17 @@ export function validateConfig(config: unknown): asserts config is PrismaNextCon
256
256
  why: 'Config.contract.source is required when contract is provided',
257
257
  });
258
258
  }
259
+
260
+ if (typeof contract['source'] !== 'function') {
261
+ throw errorConfigValidation('contract.source', {
262
+ why: 'Config.contract.source must be a provider function',
263
+ });
264
+ }
265
+
259
266
  if (contract['output'] !== undefined && typeof contract['output'] !== 'string') {
260
267
  throw errorConfigValidation('contract.output', {
261
268
  why: 'Config.contract.output must be a string when provided',
262
269
  });
263
270
  }
264
- if (contract['types'] !== undefined && typeof contract['types'] !== 'string') {
265
- throw errorConfigValidation('contract.types', {
266
- why: 'Config.contract.types must be a string when provided',
267
- });
268
- }
269
271
  }
270
272
  }
@@ -0,0 +1,28 @@
1
+ import type { ContractIR } from '@prisma-next/contract/ir';
2
+ import type { Result } from '@prisma-next/utils/result';
3
+
4
+ export interface ContractSourceDiagnosticPosition {
5
+ readonly offset: number;
6
+ readonly line: number;
7
+ readonly column: number;
8
+ }
9
+
10
+ export interface ContractSourceDiagnosticSpan {
11
+ readonly start: ContractSourceDiagnosticPosition;
12
+ readonly end: ContractSourceDiagnosticPosition;
13
+ }
14
+
15
+ export interface ContractSourceDiagnostic {
16
+ readonly code: string;
17
+ readonly message: string;
18
+ readonly sourceId?: string;
19
+ readonly span?: ContractSourceDiagnosticSpan;
20
+ }
21
+
22
+ export interface ContractSourceDiagnostics {
23
+ readonly summary: string;
24
+ readonly diagnostics: readonly ContractSourceDiagnostic[];
25
+ readonly meta?: Record<string, unknown>;
26
+ }
27
+
28
+ export type ContractSourceProvider = () => Promise<Result<ContractIR, ContractSourceDiagnostics>>;
@@ -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
  }