@prisma-next/core-control-plane 0.3.0-dev.5 → 0.3.0-dev.51
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.
- package/README.md +35 -1
- package/dist/config-types-Ci0VxOEC.d.mts +96 -0
- package/dist/config-types-Ci0VxOEC.d.mts.map +1 -0
- package/dist/config-types.d.mts +3 -0
- package/dist/config-types.mjs +60 -0
- package/dist/config-types.mjs.map +1 -0
- package/dist/config-validation.d.mts +16 -0
- package/dist/config-validation.d.mts.map +1 -0
- package/dist/config-validation.mjs +79 -0
- package/dist/config-validation.mjs.map +1 -0
- package/dist/emission.d.mts +58 -0
- package/dist/emission.d.mts.map +1 -0
- package/dist/emission.mjs +315 -0
- package/dist/emission.mjs.map +1 -0
- package/dist/errors-Qlh0sdcb.mjs +276 -0
- package/dist/errors-Qlh0sdcb.mjs.map +1 -0
- package/dist/errors.d.mts +191 -0
- package/dist/errors.d.mts.map +1 -0
- package/dist/errors.mjs +3 -0
- package/dist/{schema-view.d.ts → schema-view-D9u47nc8.d.mts} +13 -10
- package/dist/schema-view-D9u47nc8.d.mts.map +1 -0
- package/dist/schema-view.d.mts +2 -0
- package/dist/schema-view.mjs +1 -0
- package/dist/stack.d.mts +30 -0
- package/dist/stack.d.mts.map +1 -0
- package/dist/stack.mjs +30 -0
- package/dist/stack.mjs.map +1 -0
- package/dist/types-CuAauVCJ.d.mts +597 -0
- package/dist/types-CuAauVCJ.d.mts.map +1 -0
- package/dist/types.d.mts +2 -0
- package/dist/types.mjs +1 -0
- package/package.json +31 -38
- package/src/config-types.ts +30 -35
- package/src/config-validation.ts +7 -5
- package/src/contract-source-types.ts +28 -0
- package/src/emission/canonicalization.ts +68 -21
- package/src/emission/emit.ts +82 -21
- package/src/emission/hashing.ts +29 -27
- package/src/emission/types.ts +13 -2
- package/src/errors.ts +24 -5
- package/src/exports/config-types.ts +7 -0
- package/src/exports/emission.ts +1 -1
- package/src/exports/errors.ts +1 -1
- package/src/exports/stack.ts +1 -0
- package/src/exports/types.ts +1 -0
- package/src/migrations.ts +1 -1
- package/src/stack.ts +38 -0
- package/src/types.ts +41 -17
- package/dist/chunk-U5RYT6PT.js +0 -229
- package/dist/chunk-U5RYT6PT.js.map +0 -1
- package/dist/config-types.d.ts +0 -68
- package/dist/config-types.d.ts.map +0 -1
- package/dist/config-validation.d.ts +0 -10
- package/dist/config-validation.d.ts.map +0 -1
- package/dist/emission/canonicalization.d.ts +0 -6
- package/dist/emission/canonicalization.d.ts.map +0 -1
- package/dist/emission/emit.d.ts +0 -5
- package/dist/emission/emit.d.ts.map +0 -1
- package/dist/emission/hashing.d.ts +0 -17
- package/dist/emission/hashing.d.ts.map +0 -1
- package/dist/emission/types.d.ts +0 -16
- package/dist/emission/types.d.ts.map +0 -1
- package/dist/errors.d.ts +0 -183
- package/dist/errors.d.ts.map +0 -1
- package/dist/exports/config-types.d.ts +0 -3
- package/dist/exports/config-types.d.ts.map +0 -1
- package/dist/exports/config-types.js +0 -53
- package/dist/exports/config-types.js.map +0 -1
- package/dist/exports/config-validation.d.ts +0 -2
- package/dist/exports/config-validation.d.ts.map +0 -1
- package/dist/exports/config-validation.js +0 -252
- package/dist/exports/config-validation.js.map +0 -1
- package/dist/exports/emission.d.ts +0 -5
- package/dist/exports/emission.d.ts.map +0 -1
- package/dist/exports/emission.js +0 -310
- package/dist/exports/emission.js.map +0 -1
- package/dist/exports/errors.d.ts +0 -3
- package/dist/exports/errors.d.ts.map +0 -1
- package/dist/exports/errors.js +0 -43
- package/dist/exports/errors.js.map +0 -1
- package/dist/exports/schema-view.d.ts +0 -2
- package/dist/exports/schema-view.d.ts.map +0 -1
- package/dist/exports/schema-view.js +0 -1
- package/dist/exports/schema-view.js.map +0 -1
- package/dist/exports/types.d.ts +0 -2
- package/dist/exports/types.d.ts.map +0 -1
- package/dist/exports/types.js +0 -1
- package/dist/exports/types.js.map +0 -1
- package/dist/migrations.d.ts +0 -190
- package/dist/migrations.d.ts.map +0 -1
- package/dist/schema-view.d.ts.map +0 -1
- package/dist/types.d.ts +0 -400
- 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.
|
|
3
|
+
"version": "0.3.0-dev.51",
|
|
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.
|
|
11
|
-
"@prisma-next/
|
|
12
|
-
"@prisma-next/
|
|
10
|
+
"@prisma-next/contract": "0.3.0-dev.51",
|
|
11
|
+
"@prisma-next/utils": "0.3.0-dev.51",
|
|
12
|
+
"@prisma-next/operations": "0.3.0-dev.51"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"
|
|
16
|
-
"tsup": "8.5.1",
|
|
15
|
+
"tsdown": "0.18.4",
|
|
17
16
|
"typescript": "5.9.3",
|
|
18
|
-
"vitest": "4.0.
|
|
19
|
-
"@prisma-next/
|
|
17
|
+
"vitest": "4.0.17",
|
|
18
|
+
"@prisma-next/tsconfig": "0.0.0",
|
|
19
|
+
"@prisma-next/test-utils": "0.0.1",
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"./
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
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": "
|
|
45
|
+
"build": "tsdown",
|
|
53
46
|
"test": "vitest run --passWithNoTests",
|
|
54
47
|
"test:coverage": "vitest run --coverage --passWithNoTests",
|
|
55
|
-
"typecheck": "tsc --
|
|
56
|
-
"lint": "biome check . --
|
|
57
|
-
"lint:fix": "biome check --write .
|
|
58
|
-
"lint:fix:unsafe": "biome check --write --unsafe .
|
|
59
|
-
"clean": "
|
|
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
|
}
|
package/src/config-types.ts
CHANGED
|
@@ -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.
|
|
24
|
-
*
|
|
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:
|
|
26
|
+
readonly source: ContractSourceProvider;
|
|
27
27
|
/**
|
|
28
28
|
* Path to contract.json artifact. Defaults to 'src/prisma/contract.json'.
|
|
29
|
-
*
|
|
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
|
-
|
|
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
|
|
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', //
|
|
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
|
|
129
|
+
// Validate contract.source provider function shape at runtime.
|
|
120
130
|
const source = config.contract.source;
|
|
121
|
-
if (
|
|
122
|
-
source
|
|
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
|
package/src/config-validation.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
'
|
|
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
|
|
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
|
-
!
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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,
|
|
299
|
+
return JSON.stringify(withOrderedTopLevel, bigintJsonReplacer, 2);
|
|
253
300
|
}
|
package/src/emission/emit.ts
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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
|
|
52
|
-
...(codecTypeImports
|
|
53
|
-
...(operationTypeImports
|
|
54
|
-
...(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
|
|
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
|
-
|
|
71
|
-
|
|
112
|
+
};
|
|
113
|
+
assertCanonicalArtifactShape(canonicalContract);
|
|
72
114
|
|
|
73
|
-
const
|
|
74
|
-
const
|
|
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
|
|
77
|
-
...
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
175
|
+
storageHash,
|
|
176
|
+
...ifDefined('executionHash', executionHash),
|
|
116
177
|
profileHash,
|
|
117
178
|
};
|
|
118
179
|
}
|