@prisma-next/contract 0.3.0-pr.99.5 → 0.3.0
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/LICENSE +201 -0
- package/README.md +43 -254
- package/dist/contract-types-MYdoYIIh.d.mts +314 -0
- package/dist/contract-types-MYdoYIIh.d.mts.map +1 -0
- package/dist/hashing-CyaA_Qvf.mjs +196 -0
- package/dist/hashing-CyaA_Qvf.mjs.map +1 -0
- package/dist/hashing.d.mts +29 -0
- package/dist/hashing.d.mts.map +1 -0
- package/dist/hashing.mjs +3 -0
- package/dist/testing.d.mts +29 -0
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.mjs +58 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/types-aMyNgejf.mjs +14 -0
- package/dist/types-aMyNgejf.mjs.map +1 -0
- package/dist/types.d.mts +2 -2
- package/dist/types.mjs +2 -10
- package/dist/validate-contract.d.mts +37 -0
- package/dist/validate-contract.d.mts.map +1 -0
- package/dist/validate-contract.mjs +3 -0
- package/dist/validate-domain-CpCcTlqJ.mjs +165 -0
- package/dist/validate-domain-CpCcTlqJ.mjs.map +1 -0
- package/dist/validate-domain.d.mts +24 -0
- package/dist/validate-domain.d.mts.map +1 -0
- package/dist/validate-domain.mjs +3 -0
- package/package.json +15 -8
- package/schemas/data-contract-document-v1.json +5 -5
- package/src/canonicalization.ts +263 -0
- package/src/contract-types.ts +55 -0
- package/src/domain-types.ts +95 -0
- package/src/exports/hashing.ts +2 -0
- package/src/exports/testing.ts +1 -0
- package/src/exports/types.ts +36 -20
- package/src/exports/validate-contract.ts +6 -0
- package/src/exports/validate-domain.ts +5 -0
- package/src/hashing.ts +53 -0
- package/src/testing-factories.ts +101 -0
- package/src/types.ts +102 -248
- package/src/validate-contract.ts +101 -0
- package/src/validate-domain.ts +227 -0
- package/dist/framework-components-B-XOtXw3.d.mts +0 -854
- package/dist/framework-components-B-XOtXw3.d.mts.map +0 -1
- package/dist/framework-components.d.mts +0 -2
- package/dist/framework-components.mjs +0 -70
- package/dist/framework-components.mjs.map +0 -1
- package/dist/ir-B8zNqals.d.mts +0 -79
- package/dist/ir-B8zNqals.d.mts.map +0 -1
- package/dist/ir.d.mts +0 -2
- package/dist/ir.mjs +0 -46
- package/dist/ir.mjs.map +0 -1
- package/dist/pack-manifest-types.d.mts +0 -2
- package/dist/pack-manifest-types.mjs +0 -1
- package/dist/types.mjs.map +0 -1
- package/src/exports/framework-components.ts +0 -36
- package/src/exports/ir.ts +0 -1
- package/src/exports/pack-manifest-types.ts +0 -6
- package/src/framework-components.ts +0 -717
- package/src/ir.ts +0 -113
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { isArrayEqual } from '@prisma-next/utils/array-equal';
|
|
2
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
3
|
+
|
|
4
|
+
import type { Contract } from './contract-types';
|
|
5
|
+
|
|
6
|
+
const TOP_LEVEL_ORDER = [
|
|
7
|
+
'schemaVersion',
|
|
8
|
+
'canonicalVersion',
|
|
9
|
+
'targetFamily',
|
|
10
|
+
'target',
|
|
11
|
+
'profileHash',
|
|
12
|
+
'roots',
|
|
13
|
+
'models',
|
|
14
|
+
'valueObjects',
|
|
15
|
+
'storage',
|
|
16
|
+
'execution',
|
|
17
|
+
'capabilities',
|
|
18
|
+
'extensionPacks',
|
|
19
|
+
'meta',
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
function isDefaultValue(value: unknown): boolean {
|
|
23
|
+
if (value === false) return true;
|
|
24
|
+
if (value === null) return false;
|
|
25
|
+
if (Array.isArray(value) && value.length === 0) return true;
|
|
26
|
+
if (typeof value === 'object' && value !== null) {
|
|
27
|
+
const keys = Object.keys(value);
|
|
28
|
+
return keys.length === 0;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function omitDefaults(obj: unknown, path: readonly string[]): unknown {
|
|
34
|
+
if (obj === null || typeof obj !== 'object') {
|
|
35
|
+
return obj;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (Array.isArray(obj)) {
|
|
39
|
+
return obj.map((item) => omitDefaults(item, path));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const result: Record<string, unknown> = {};
|
|
43
|
+
|
|
44
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
45
|
+
const currentPath = [...path, key];
|
|
46
|
+
|
|
47
|
+
if (key === '_generated') {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (key === 'generated' && value === false) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if ((key === 'onDelete' || key === 'onUpdate') && value === 'noAction') {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (isDefaultValue(value)) {
|
|
60
|
+
const isRequiredModels = isArrayEqual(currentPath, ['models']);
|
|
61
|
+
const isRequiredTables = isArrayEqual(currentPath, ['storage', 'tables']);
|
|
62
|
+
const isRequiredCollections = isArrayEqual(currentPath, ['storage', 'collections']);
|
|
63
|
+
const isCollectionEntry =
|
|
64
|
+
currentPath.length === 3 &&
|
|
65
|
+
isArrayEqual([currentPath[0], currentPath[1]], ['storage', 'collections']);
|
|
66
|
+
const isRequiredRoots = isArrayEqual(currentPath, ['roots']);
|
|
67
|
+
const isRequiredExtensionPacks = isArrayEqual(currentPath, ['extensionPacks']);
|
|
68
|
+
const isRequiredCapabilities = isArrayEqual(currentPath, ['capabilities']);
|
|
69
|
+
const isRequiredMeta = isArrayEqual(currentPath, ['meta']);
|
|
70
|
+
const isRequiredExecutionDefaults = isArrayEqual(currentPath, [
|
|
71
|
+
'execution',
|
|
72
|
+
'mutations',
|
|
73
|
+
'defaults',
|
|
74
|
+
]);
|
|
75
|
+
const isExtensionNamespace = currentPath.length === 2 && currentPath[0] === 'extensionPacks';
|
|
76
|
+
const isModelRelations =
|
|
77
|
+
currentPath.length === 3 &&
|
|
78
|
+
isArrayEqual([currentPath[0], currentPath[2]], ['models', 'relations']);
|
|
79
|
+
const isModelStorage =
|
|
80
|
+
currentPath.length === 3 &&
|
|
81
|
+
isArrayEqual([currentPath[0], currentPath[2]], ['models', 'storage']);
|
|
82
|
+
const isTableUniques =
|
|
83
|
+
currentPath.length === 4 &&
|
|
84
|
+
isArrayEqual(
|
|
85
|
+
[currentPath[0], currentPath[1], currentPath[3]],
|
|
86
|
+
['storage', 'tables', 'uniques'],
|
|
87
|
+
);
|
|
88
|
+
const isTableIndexes =
|
|
89
|
+
currentPath.length === 4 &&
|
|
90
|
+
isArrayEqual(
|
|
91
|
+
[currentPath[0], currentPath[1], currentPath[3]],
|
|
92
|
+
['storage', 'tables', 'indexes'],
|
|
93
|
+
);
|
|
94
|
+
const isTableForeignKeys =
|
|
95
|
+
currentPath.length === 4 &&
|
|
96
|
+
isArrayEqual(
|
|
97
|
+
[currentPath[0], currentPath[1], currentPath[3]],
|
|
98
|
+
['storage', 'tables', 'foreignKeys'],
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const isFkBooleanField =
|
|
102
|
+
currentPath.length === 5 &&
|
|
103
|
+
currentPath[0] === 'storage' &&
|
|
104
|
+
currentPath[1] === 'tables' &&
|
|
105
|
+
currentPath[3] === 'foreignKeys' &&
|
|
106
|
+
(key === 'constraint' || key === 'index');
|
|
107
|
+
|
|
108
|
+
const isNullableField = key === 'nullable';
|
|
109
|
+
|
|
110
|
+
if (
|
|
111
|
+
!isRequiredModels &&
|
|
112
|
+
!isRequiredTables &&
|
|
113
|
+
!isRequiredCollections &&
|
|
114
|
+
!isCollectionEntry &&
|
|
115
|
+
!isRequiredRoots &&
|
|
116
|
+
!isRequiredExtensionPacks &&
|
|
117
|
+
!isRequiredCapabilities &&
|
|
118
|
+
!isRequiredMeta &&
|
|
119
|
+
!isRequiredExecutionDefaults &&
|
|
120
|
+
!isExtensionNamespace &&
|
|
121
|
+
!isModelRelations &&
|
|
122
|
+
!isModelStorage &&
|
|
123
|
+
!isTableUniques &&
|
|
124
|
+
!isTableIndexes &&
|
|
125
|
+
!isTableForeignKeys &&
|
|
126
|
+
!isFkBooleanField &&
|
|
127
|
+
!isNullableField
|
|
128
|
+
) {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
result[key] = omitDefaults(value, currentPath);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function sortObjectKeys(obj: unknown): unknown {
|
|
140
|
+
if (obj === null || typeof obj !== 'object') {
|
|
141
|
+
return obj;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (Array.isArray(obj)) {
|
|
145
|
+
return obj.map((item) => sortObjectKeys(item));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const sorted: Record<string, unknown> = {};
|
|
149
|
+
const keys = Object.keys(obj).sort();
|
|
150
|
+
for (const key of keys) {
|
|
151
|
+
sorted[key] = sortObjectKeys((obj as Record<string, unknown>)[key]);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return sorted;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
type StorageObject = {
|
|
158
|
+
tables?: Record<string, unknown>;
|
|
159
|
+
[key: string]: unknown;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
type TableObject = {
|
|
163
|
+
indexes?: unknown[];
|
|
164
|
+
uniques?: unknown[];
|
|
165
|
+
[key: string]: unknown;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
function sortIndexesAndUniques(storage: unknown): unknown {
|
|
169
|
+
if (!storage || typeof storage !== 'object') {
|
|
170
|
+
return storage;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const storageObj = storage as StorageObject;
|
|
174
|
+
if (!storageObj.tables || typeof storageObj.tables !== 'object') {
|
|
175
|
+
return storage;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const tables = storageObj.tables;
|
|
179
|
+
const result: StorageObject = { ...storageObj };
|
|
180
|
+
|
|
181
|
+
result.tables = {};
|
|
182
|
+
const sortedTableNames = Object.keys(tables).sort();
|
|
183
|
+
for (const tableName of sortedTableNames) {
|
|
184
|
+
const table = tables[tableName];
|
|
185
|
+
if (!table || typeof table !== 'object') {
|
|
186
|
+
result.tables[tableName] = table;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const tableObj = table as TableObject;
|
|
191
|
+
const sortedTable: TableObject = { ...tableObj };
|
|
192
|
+
|
|
193
|
+
if (Array.isArray(tableObj.indexes)) {
|
|
194
|
+
sortedTable.indexes = [...tableObj.indexes].sort((a, b) => {
|
|
195
|
+
const nameA = (a as { name?: string })?.name || '';
|
|
196
|
+
const nameB = (b as { name?: string })?.name || '';
|
|
197
|
+
return nameA.localeCompare(nameB);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (Array.isArray(tableObj.uniques)) {
|
|
202
|
+
sortedTable.uniques = [...tableObj.uniques].sort((a, b) => {
|
|
203
|
+
const nameA = (a as { name?: string })?.name || '';
|
|
204
|
+
const nameB = (b as { name?: string })?.name || '';
|
|
205
|
+
return nameA.localeCompare(nameB);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
result.tables[tableName] = sortedTable;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function orderTopLevel(obj: Record<string, unknown>): Record<string, unknown> {
|
|
216
|
+
const ordered: Record<string, unknown> = {};
|
|
217
|
+
const remaining = new Set(Object.keys(obj));
|
|
218
|
+
|
|
219
|
+
for (const key of TOP_LEVEL_ORDER) {
|
|
220
|
+
if (remaining.has(key)) {
|
|
221
|
+
ordered[key] = obj[key];
|
|
222
|
+
remaining.delete(key);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
for (const key of Array.from(remaining).sort()) {
|
|
227
|
+
ordered[key] = obj[key];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return ordered;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function canonicalizeContractToObject(
|
|
234
|
+
contract: Contract,
|
|
235
|
+
options?: { schemaVersion?: string },
|
|
236
|
+
): Record<string, unknown> {
|
|
237
|
+
const normalized: Record<string, unknown> = {
|
|
238
|
+
...ifDefined('schemaVersion', options?.schemaVersion),
|
|
239
|
+
targetFamily: contract.targetFamily,
|
|
240
|
+
target: contract.target,
|
|
241
|
+
profileHash: contract.profileHash,
|
|
242
|
+
roots: contract.roots,
|
|
243
|
+
models: contract.models,
|
|
244
|
+
...ifDefined('valueObjects', contract.valueObjects),
|
|
245
|
+
storage: contract.storage,
|
|
246
|
+
...ifDefined('execution', contract.execution),
|
|
247
|
+
extensionPacks: contract.extensionPacks,
|
|
248
|
+
capabilities: contract.capabilities,
|
|
249
|
+
meta: contract.meta,
|
|
250
|
+
};
|
|
251
|
+
const withDefaultsOmitted = omitDefaults(normalized, []) as Record<string, unknown>;
|
|
252
|
+
const withSortedIndexes = sortIndexesAndUniques(withDefaultsOmitted['storage']);
|
|
253
|
+
const withSortedStorage = { ...withDefaultsOmitted, storage: withSortedIndexes };
|
|
254
|
+
const withSortedKeys = sortObjectKeys(withSortedStorage) as Record<string, unknown>;
|
|
255
|
+
return orderTopLevel(withSortedKeys);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function canonicalizeContract(
|
|
259
|
+
contract: Contract,
|
|
260
|
+
options?: { schemaVersion?: string },
|
|
261
|
+
): string {
|
|
262
|
+
return JSON.stringify(canonicalizeContractToObject(contract, options), null, 2);
|
|
263
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ContractModelBase, ContractValueObject } from './domain-types';
|
|
2
|
+
import type {
|
|
3
|
+
ExecutionHashBase,
|
|
4
|
+
ExecutionMutationDefault,
|
|
5
|
+
ProfileHashBase,
|
|
6
|
+
StorageBase,
|
|
7
|
+
} from './types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Execution section for the unified contract (ADR 182).
|
|
11
|
+
*
|
|
12
|
+
* Unlike the legacy {@link import('./types').ExecutionSection}, this type
|
|
13
|
+
* requires `executionHash` — when an execution section is present, its
|
|
14
|
+
* hash must be too (consistent with `StorageBase.storageHash`).
|
|
15
|
+
*
|
|
16
|
+
* @template THash Literal hash string type for type-safe hash tracking.
|
|
17
|
+
*/
|
|
18
|
+
export type ContractExecutionSection<THash extends string = string> = {
|
|
19
|
+
readonly executionHash: ExecutionHashBase<THash>;
|
|
20
|
+
readonly mutations: {
|
|
21
|
+
readonly defaults: ReadonlyArray<ExecutionMutationDefault>;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Unified contract representation (ADR 182).
|
|
27
|
+
*
|
|
28
|
+
* A `Contract` is the canonical in-memory representation of a data contract.
|
|
29
|
+
* It is model-first (domain models carry their own storage bridge) and
|
|
30
|
+
* family-parameterized (SQL, Mongo, etc. specialize via `TStorage` and model
|
|
31
|
+
* storage generics on `ContractModel`).
|
|
32
|
+
*
|
|
33
|
+
* JSON persistence fields (`schemaVersion`, `sources`) are not represented
|
|
34
|
+
* here — they are handled at the serialization boundary.
|
|
35
|
+
*
|
|
36
|
+
* @template TStorage Family-specific storage block (extends {@link StorageBase}).
|
|
37
|
+
* @template TModels Record of model name → {@link ContractModel} with
|
|
38
|
+
* family-specific model storage.
|
|
39
|
+
*/
|
|
40
|
+
export interface Contract<
|
|
41
|
+
TStorage extends StorageBase = StorageBase,
|
|
42
|
+
TModels extends Record<string, ContractModelBase> = Record<string, ContractModelBase>,
|
|
43
|
+
> {
|
|
44
|
+
readonly target: string;
|
|
45
|
+
readonly targetFamily: string;
|
|
46
|
+
readonly roots: Record<string, string>;
|
|
47
|
+
readonly models: TModels;
|
|
48
|
+
readonly valueObjects?: Record<string, ContractValueObject>;
|
|
49
|
+
readonly storage: TStorage;
|
|
50
|
+
readonly capabilities: Record<string, Record<string, boolean>>;
|
|
51
|
+
readonly extensionPacks: Record<string, unknown>;
|
|
52
|
+
readonly execution?: ContractExecutionSection;
|
|
53
|
+
readonly profileHash: ProfileHashBase<string>;
|
|
54
|
+
readonly meta: Record<string, unknown>;
|
|
55
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export type ScalarFieldType = {
|
|
2
|
+
readonly kind: 'scalar';
|
|
3
|
+
readonly codecId: string;
|
|
4
|
+
readonly typeParams?: Record<string, unknown>;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type ValueObjectFieldType = {
|
|
8
|
+
readonly kind: 'valueObject';
|
|
9
|
+
readonly name: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type UnionFieldType = {
|
|
13
|
+
readonly kind: 'union';
|
|
14
|
+
readonly members: ReadonlyArray<ScalarFieldType | ValueObjectFieldType>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type ContractFieldType = ScalarFieldType | ValueObjectFieldType | UnionFieldType;
|
|
18
|
+
|
|
19
|
+
export type ContractField = {
|
|
20
|
+
readonly nullable: boolean;
|
|
21
|
+
readonly type: ContractFieldType;
|
|
22
|
+
readonly many?: true;
|
|
23
|
+
readonly dict?: true;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type ContractRelationOn = {
|
|
27
|
+
readonly localFields: readonly string[];
|
|
28
|
+
readonly targetFields: readonly string[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type ContractReferenceRelation = {
|
|
32
|
+
readonly to: string;
|
|
33
|
+
readonly cardinality: '1:1' | '1:N' | 'N:1';
|
|
34
|
+
readonly on: ContractRelationOn;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type ContractEmbedRelation = {
|
|
38
|
+
readonly to: string;
|
|
39
|
+
readonly cardinality: '1:1' | '1:N';
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type ContractRelation = ContractReferenceRelation | ContractEmbedRelation;
|
|
43
|
+
|
|
44
|
+
export type ContractDiscriminator = {
|
|
45
|
+
readonly field: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type ContractVariantEntry = {
|
|
49
|
+
readonly value: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type ContractValueObject = {
|
|
53
|
+
readonly fields: Record<string, ContractField>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type ModelStorageBase = Readonly<Record<string, unknown>>;
|
|
57
|
+
|
|
58
|
+
export interface ContractModelBase<TModelStorage extends ModelStorageBase = ModelStorageBase> {
|
|
59
|
+
readonly fields: Record<string, ContractField>;
|
|
60
|
+
readonly relations: Record<string, ContractRelation>;
|
|
61
|
+
readonly storage: TModelStorage;
|
|
62
|
+
readonly discriminator?: ContractDiscriminator;
|
|
63
|
+
readonly variants?: Record<string, ContractVariantEntry>;
|
|
64
|
+
readonly base?: string;
|
|
65
|
+
readonly owner?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ContractModel<TModelStorage extends ModelStorageBase = ModelStorageBase>
|
|
69
|
+
extends ContractModelBase<TModelStorage> {
|
|
70
|
+
readonly fields: Record<string, ContractField>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Relation key helpers ─────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
type HasModelsWithRelations = {
|
|
76
|
+
readonly models: Record<string, { readonly relations: Record<string, ContractRelation> }>;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type ReferenceRelationKeys<
|
|
80
|
+
TContract extends HasModelsWithRelations,
|
|
81
|
+
ModelName extends string & keyof TContract['models'],
|
|
82
|
+
> = {
|
|
83
|
+
[K in keyof TContract['models'][ModelName]['relations']]: TContract['models'][ModelName]['relations'][K] extends ContractReferenceRelation
|
|
84
|
+
? K
|
|
85
|
+
: never;
|
|
86
|
+
}[keyof TContract['models'][ModelName]['relations']];
|
|
87
|
+
|
|
88
|
+
export type EmbedRelationKeys<
|
|
89
|
+
TContract extends HasModelsWithRelations,
|
|
90
|
+
ModelName extends string & keyof TContract['models'],
|
|
91
|
+
> = {
|
|
92
|
+
[K in keyof TContract['models'][ModelName]['relations']]: TContract['models'][ModelName]['relations'][K] extends ContractReferenceRelation
|
|
93
|
+
? never
|
|
94
|
+
: K;
|
|
95
|
+
}[keyof TContract['models'][ModelName]['relations']];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createContract, createSqlContract, DUMMY_HASH } from '../testing-factories';
|
package/src/exports/types.ts
CHANGED
|
@@ -1,33 +1,49 @@
|
|
|
1
|
-
|
|
2
|
-
// Document family types
|
|
3
|
-
// Plan types - target-family agnostic execution types
|
|
4
|
-
// Emitter types (moved from @prisma-next/emitter)
|
|
5
|
-
// Parameterized codec descriptor types
|
|
1
|
+
export type { Contract, ContractExecutionSection } from '../contract-types';
|
|
6
2
|
export type {
|
|
7
|
-
|
|
3
|
+
ContractDiscriminator,
|
|
4
|
+
ContractEmbedRelation,
|
|
5
|
+
ContractField,
|
|
6
|
+
ContractFieldType,
|
|
7
|
+
ContractModel,
|
|
8
|
+
ContractModelBase,
|
|
9
|
+
ContractReferenceRelation,
|
|
10
|
+
ContractRelation,
|
|
11
|
+
ContractRelationOn,
|
|
12
|
+
ContractValueObject,
|
|
13
|
+
ContractVariantEntry,
|
|
14
|
+
EmbedRelationKeys,
|
|
15
|
+
ModelStorageBase,
|
|
16
|
+
ReferenceRelationKeys,
|
|
17
|
+
ScalarFieldType,
|
|
18
|
+
UnionFieldType,
|
|
19
|
+
ValueObjectFieldType,
|
|
20
|
+
} from '../domain-types';
|
|
21
|
+
export type {
|
|
22
|
+
$,
|
|
23
|
+
Brand,
|
|
24
|
+
ColumnDefault,
|
|
25
|
+
ColumnDefaultLiteralInputValue,
|
|
26
|
+
ColumnDefaultLiteralValue,
|
|
8
27
|
ContractMarkerRecord,
|
|
9
28
|
DocCollection,
|
|
10
29
|
DocIndex,
|
|
11
|
-
|
|
12
|
-
|
|
30
|
+
ExecutionHashBase,
|
|
31
|
+
ExecutionMutationDefault,
|
|
32
|
+
ExecutionMutationDefaultValue,
|
|
13
33
|
ExecutionPlan,
|
|
34
|
+
ExecutionSection,
|
|
14
35
|
Expr,
|
|
15
36
|
FieldType,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
37
|
+
GeneratedValueSpec,
|
|
38
|
+
JsonPrimitive,
|
|
39
|
+
JsonValue,
|
|
19
40
|
ParamDescriptor,
|
|
20
|
-
ParameterizedCodecDescriptor,
|
|
21
41
|
PlanMeta,
|
|
22
42
|
PlanRefs,
|
|
43
|
+
ProfileHashBase,
|
|
23
44
|
ResultType,
|
|
24
45
|
Source,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
TypeRenderEntry,
|
|
28
|
-
TypeRenderer,
|
|
29
|
-
TypesImportSpec,
|
|
30
|
-
ValidationContext,
|
|
46
|
+
StorageBase,
|
|
47
|
+
StorageHashBase,
|
|
31
48
|
} from '../types';
|
|
32
|
-
|
|
33
|
-
export { isDocumentContract } from '../types';
|
|
49
|
+
export { coreHash, executionHash, profileHash } from '../types';
|
package/src/hashing.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { canonicalizeContract } from './canonicalization';
|
|
3
|
+
import type { Contract } from './contract-types';
|
|
4
|
+
import type { ExecutionHashBase, ProfileHashBase, StorageHashBase } from './types';
|
|
5
|
+
|
|
6
|
+
const SCHEMA_VERSION = '1';
|
|
7
|
+
|
|
8
|
+
function sha256(content: string): string {
|
|
9
|
+
const hash = createHash('sha256');
|
|
10
|
+
hash.update(content);
|
|
11
|
+
return `sha256:${hash.digest('hex')}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function hashContract(section: Record<string, unknown>): string {
|
|
15
|
+
const contract = {
|
|
16
|
+
targetFamily: section['targetFamily'],
|
|
17
|
+
target: section['target'],
|
|
18
|
+
roots: {},
|
|
19
|
+
models: {},
|
|
20
|
+
storage: section['storage'] ?? {},
|
|
21
|
+
execution: section['execution'],
|
|
22
|
+
extensionPacks: {},
|
|
23
|
+
capabilities: section['capabilities'] ?? {},
|
|
24
|
+
meta: {},
|
|
25
|
+
profileHash: '',
|
|
26
|
+
...section,
|
|
27
|
+
} as Contract;
|
|
28
|
+
return canonicalizeContract(contract, { schemaVersion: SCHEMA_VERSION });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function computeStorageHash(args: {
|
|
32
|
+
target: string;
|
|
33
|
+
targetFamily: string;
|
|
34
|
+
storage: Record<string, unknown>;
|
|
35
|
+
}): StorageHashBase<string> {
|
|
36
|
+
return sha256(hashContract(args)) as StorageHashBase<string>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function computeExecutionHash(args: {
|
|
40
|
+
target: string;
|
|
41
|
+
targetFamily: string;
|
|
42
|
+
execution: Record<string, unknown>;
|
|
43
|
+
}): ExecutionHashBase<string> {
|
|
44
|
+
return sha256(hashContract(args)) as ExecutionHashBase<string>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function computeProfileHash(args: {
|
|
48
|
+
target: string;
|
|
49
|
+
targetFamily: string;
|
|
50
|
+
capabilities: Record<string, Record<string, boolean>>;
|
|
51
|
+
}): ProfileHashBase<string> {
|
|
52
|
+
return sha256(hashContract(args)) as ProfileHashBase<string>;
|
|
53
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { ifDefined } from '@prisma-next/utils/defined';
|
|
2
|
+
import type { Contract } from './contract-types';
|
|
3
|
+
import type {
|
|
4
|
+
ContractModel,
|
|
5
|
+
ContractModelBase,
|
|
6
|
+
ContractValueObject,
|
|
7
|
+
ModelStorageBase,
|
|
8
|
+
} from './domain-types';
|
|
9
|
+
import { computeExecutionHash, computeProfileHash, computeStorageHash } from './hashing';
|
|
10
|
+
import type { ExecutionSection, ProfileHashBase, StorageBase } from './types';
|
|
11
|
+
import { coreHash } from './types';
|
|
12
|
+
|
|
13
|
+
type ContractOverrides<
|
|
14
|
+
TStorage extends StorageBase = StorageBase,
|
|
15
|
+
TModels extends Record<string, ContractModelBase> = Record<string, ContractModel>,
|
|
16
|
+
> = {
|
|
17
|
+
target?: string;
|
|
18
|
+
targetFamily?: string;
|
|
19
|
+
roots?: Record<string, string>;
|
|
20
|
+
models?: TModels;
|
|
21
|
+
storage?: Omit<TStorage, 'storageHash'>;
|
|
22
|
+
valueObjects?: Record<string, ContractValueObject>;
|
|
23
|
+
capabilities?: Record<string, Record<string, boolean>>;
|
|
24
|
+
extensionPacks?: Record<string, unknown>;
|
|
25
|
+
execution?: Omit<ExecutionSection, 'executionHash'>;
|
|
26
|
+
profileHash?: ProfileHashBase<string>;
|
|
27
|
+
meta?: Record<string, unknown>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const DUMMY_HASH = coreHash('sha256:test');
|
|
31
|
+
|
|
32
|
+
export function createContract<
|
|
33
|
+
TStorage extends StorageBase = StorageBase,
|
|
34
|
+
TModels extends Record<string, ContractModelBase> = Record<string, ContractModel>,
|
|
35
|
+
>(overrides: ContractOverrides<TStorage, TModels> = {}): Contract<TStorage, TModels> {
|
|
36
|
+
const target = overrides.target ?? 'postgres';
|
|
37
|
+
const targetFamily = overrides.targetFamily ?? 'sql';
|
|
38
|
+
const capabilities = overrides.capabilities ?? {};
|
|
39
|
+
|
|
40
|
+
const rawStorage =
|
|
41
|
+
overrides.storage ?? ({ tables: {} } as unknown as Omit<TStorage, 'storageHash'>);
|
|
42
|
+
|
|
43
|
+
const storageHash = computeStorageHash({
|
|
44
|
+
target,
|
|
45
|
+
targetFamily,
|
|
46
|
+
storage: rawStorage as Record<string, unknown>,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const storage = {
|
|
50
|
+
...rawStorage,
|
|
51
|
+
storageHash,
|
|
52
|
+
} as TStorage;
|
|
53
|
+
|
|
54
|
+
const computedProfileHash =
|
|
55
|
+
overrides.profileHash ?? computeProfileHash({ target, targetFamily, capabilities });
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
target,
|
|
59
|
+
targetFamily,
|
|
60
|
+
roots: overrides.roots ?? {},
|
|
61
|
+
models: (overrides.models ?? {}) as TModels,
|
|
62
|
+
...ifDefined('valueObjects', overrides.valueObjects),
|
|
63
|
+
storage,
|
|
64
|
+
capabilities,
|
|
65
|
+
extensionPacks: overrides.extensionPacks ?? {},
|
|
66
|
+
...(overrides.execution !== undefined
|
|
67
|
+
? {
|
|
68
|
+
execution: {
|
|
69
|
+
...overrides.execution,
|
|
70
|
+
executionHash: computeExecutionHash({
|
|
71
|
+
target,
|
|
72
|
+
targetFamily,
|
|
73
|
+
execution: overrides.execution,
|
|
74
|
+
}),
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
: {}),
|
|
78
|
+
profileHash: computedProfileHash,
|
|
79
|
+
meta: overrides.meta ?? {},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
type SqlStorageLike = StorageBase & {
|
|
84
|
+
readonly tables: Record<string, unknown>;
|
|
85
|
+
readonly types?: Record<string, unknown>;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
type SqlModelLike = ContractModel<ModelStorageBase & { table: string }>;
|
|
89
|
+
|
|
90
|
+
export function createSqlContract(
|
|
91
|
+
overrides: ContractOverrides<SqlStorageLike, Record<string, SqlModelLike>> = {},
|
|
92
|
+
): Contract<SqlStorageLike, Record<string, SqlModelLike>> {
|
|
93
|
+
return createContract<SqlStorageLike, Record<string, SqlModelLike>>({
|
|
94
|
+
target: 'postgres',
|
|
95
|
+
targetFamily: 'sql',
|
|
96
|
+
storage: overrides.storage ?? { tables: {} },
|
|
97
|
+
...overrides,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export { DUMMY_HASH };
|