@prisma-next/sql-contract-ts 0.3.0-dev.135 → 0.3.0-dev.146
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 +107 -154
- package/dist/config-types.d.mts +2 -2
- package/dist/config-types.d.mts.map +1 -1
- package/dist/config-types.mjs +2 -2
- package/dist/config-types.mjs.map +1 -1
- package/dist/contract-builder.d.mts +192 -236
- package/dist/contract-builder.d.mts.map +1 -1
- package/dist/contract-builder.mjs +317 -422
- package/dist/contract-builder.mjs.map +1 -1
- package/package.json +8 -6
- package/schemas/data-contract-sql-v1.json +6 -6
- package/src/authoring-helper-runtime.ts +2 -2
- package/src/authoring-type-utils.ts +2 -2
- package/src/build-contract.ts +463 -0
- package/src/composed-authoring-helpers.ts +8 -6
- package/src/config-types.ts +3 -3
- package/src/contract-builder.ts +122 -636
- package/src/{semantic-contract.ts → contract-definition.ts} +33 -16
- package/src/{staged-contract-dsl.ts → contract-dsl.ts} +30 -28
- package/src/{staged-contract-lowering.ts → contract-lowering.ts} +46 -48
- package/src/{staged-contract-types.ts → contract-types.ts} +185 -145
- package/src/{staged-contract-warnings.ts → contract-warnings.ts} +18 -21
- package/src/exports/contract-builder.ts +12 -13
- package/src/contract-ir-builder.ts +0 -475
- package/src/contract.ts +0 -475
package/src/contract.ts
DELETED
|
@@ -1,475 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
PrimaryKey,
|
|
3
|
-
SqlContract,
|
|
4
|
-
SqlStorage,
|
|
5
|
-
StorageTypeInstance,
|
|
6
|
-
UniqueConstraint,
|
|
7
|
-
} from '@prisma-next/sql-contract/types';
|
|
8
|
-
import { decodeContractDefaults } from '@prisma-next/sql-contract/validate';
|
|
9
|
-
import {
|
|
10
|
-
ColumnDefaultSchema,
|
|
11
|
-
ForeignKeySchema,
|
|
12
|
-
IndexSchema,
|
|
13
|
-
validateStorageSemantics,
|
|
14
|
-
} from '@prisma-next/sql-contract/validators';
|
|
15
|
-
import { type } from 'arktype';
|
|
16
|
-
import type { O } from 'ts-toolbelt';
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Structural validation schema for SqlContract using Arktype.
|
|
20
|
-
* This validates the shape and types of the contract structure.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
const StorageColumnSchema = type({
|
|
24
|
-
nativeType: 'string',
|
|
25
|
-
codecId: 'string',
|
|
26
|
-
nullable: 'boolean',
|
|
27
|
-
'typeParams?': 'Record<string, unknown>',
|
|
28
|
-
'typeRef?': 'string',
|
|
29
|
-
'default?': ColumnDefaultSchema,
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const StorageTypeInstanceSchema = type.declare<StorageTypeInstance>().type({
|
|
33
|
-
codecId: 'string',
|
|
34
|
-
nativeType: 'string',
|
|
35
|
-
typeParams: 'Record<string, unknown>',
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const PrimaryKeySchema = type.declare<PrimaryKey>().type({
|
|
39
|
-
columns: type.string.array().readonly(),
|
|
40
|
-
'name?': 'string',
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
const UniqueConstraintSchema = type.declare<UniqueConstraint>().type({
|
|
44
|
-
columns: type.string.array().readonly(),
|
|
45
|
-
'name?': 'string',
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const StorageTableSchema = type({
|
|
49
|
-
columns: type({ '[string]': StorageColumnSchema }),
|
|
50
|
-
'primaryKey?': PrimaryKeySchema,
|
|
51
|
-
uniques: UniqueConstraintSchema.array().readonly(),
|
|
52
|
-
indexes: IndexSchema.array().readonly(),
|
|
53
|
-
foreignKeys: ForeignKeySchema.array().readonly(),
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const StorageSchema = type({
|
|
57
|
-
tables: type({ '[string]': StorageTableSchema }),
|
|
58
|
-
'types?': type({ '[string]': StorageTypeInstanceSchema }),
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const ModelFieldSchema = type({
|
|
62
|
-
'nullable?': 'boolean',
|
|
63
|
-
'codecId?': 'string',
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const ModelStorageFieldSchema = type({
|
|
67
|
-
column: 'string',
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const ModelStorageSchema = type({
|
|
71
|
-
table: 'string',
|
|
72
|
-
'fields?': type({ '[string]': ModelStorageFieldSchema }),
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const ModelSchema = type({
|
|
76
|
-
storage: ModelStorageSchema,
|
|
77
|
-
fields: type({ '[string]': ModelFieldSchema }),
|
|
78
|
-
relations: type({ '[string]': 'unknown' }),
|
|
79
|
-
'owner?': 'string',
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const GeneratorIdSchema = type('string').narrow((value, ctx) => {
|
|
83
|
-
return /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(value) ? true : ctx.mustBe('a flat generator id');
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const ExecutionMutationDefaultValueSchema = type({
|
|
87
|
-
kind: "'generator'",
|
|
88
|
-
id: GeneratorIdSchema,
|
|
89
|
-
'params?': 'Record<string, unknown>',
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const ExecutionMutationDefaultSchema = type({
|
|
93
|
-
ref: {
|
|
94
|
-
table: 'string',
|
|
95
|
-
column: 'string',
|
|
96
|
-
},
|
|
97
|
-
'onCreate?': ExecutionMutationDefaultValueSchema,
|
|
98
|
-
'onUpdate?': ExecutionMutationDefaultValueSchema,
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
const ExecutionSchema = type({
|
|
102
|
-
mutations: {
|
|
103
|
-
defaults: ExecutionMutationDefaultSchema.array().readonly(),
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Complete SqlContract schema for structural validation.
|
|
109
|
-
* This validates the entire contract structure at once.
|
|
110
|
-
*/
|
|
111
|
-
const SqlContractSchema = type({
|
|
112
|
-
'schemaVersion?': "'1'",
|
|
113
|
-
target: 'string',
|
|
114
|
-
targetFamily: "'sql'",
|
|
115
|
-
storageHash: 'string',
|
|
116
|
-
'executionHash?': 'string',
|
|
117
|
-
'profileHash?': 'string',
|
|
118
|
-
'capabilities?': 'Record<string, Record<string, boolean>>',
|
|
119
|
-
'extensionPacks?': 'Record<string, unknown>',
|
|
120
|
-
'meta?': 'Record<string, unknown>',
|
|
121
|
-
'sources?': 'Record<string, unknown>',
|
|
122
|
-
'roots?': 'Record<string, string>',
|
|
123
|
-
models: type({ '[string]': ModelSchema }),
|
|
124
|
-
storage: StorageSchema,
|
|
125
|
-
'execution?': ExecutionSchema,
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Validates the structural shape of a SqlContract using Arktype.
|
|
130
|
-
*
|
|
131
|
-
* **Responsibility: Validation Only**
|
|
132
|
-
* This function validates that the contract has the correct structure and types.
|
|
133
|
-
* It does NOT normalize the contract - normalization must happen in the contract builder.
|
|
134
|
-
*
|
|
135
|
-
* The contract passed to this function must already be normalized (all required fields present).
|
|
136
|
-
* If normalization is needed, it should be done by the contract builder before calling this function.
|
|
137
|
-
*
|
|
138
|
-
* This ensures all required fields are present and have the correct types.
|
|
139
|
-
*
|
|
140
|
-
* @param value - The contract value to validate (typically from a JSON import)
|
|
141
|
-
* @returns The validated contract if structure is valid
|
|
142
|
-
* @throws Error if the contract structure is invalid
|
|
143
|
-
*/
|
|
144
|
-
function validateContractStructure<T extends SqlContract<SqlStorage>>(
|
|
145
|
-
value: unknown,
|
|
146
|
-
): O.Overwrite<T, { targetFamily: 'sql' }> {
|
|
147
|
-
// Check targetFamily first to provide a clear error message for unsupported target families
|
|
148
|
-
const rawValue = value as { targetFamily?: string };
|
|
149
|
-
if (rawValue.targetFamily !== undefined && rawValue.targetFamily !== 'sql') {
|
|
150
|
-
/* c8 ignore next */
|
|
151
|
-
throw new Error(`Unsupported target family: ${rawValue.targetFamily}`);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const contractResult = SqlContractSchema(value);
|
|
155
|
-
|
|
156
|
-
if (contractResult instanceof type.errors) {
|
|
157
|
-
const messages = contractResult.map((p: { message: string }) => p.message).join('; ');
|
|
158
|
-
throw new Error(`Contract structural validation failed: ${messages}`);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// After validation, contractResult matches the schema and preserves the input structure
|
|
162
|
-
// TypeScript needs an assertion here due to exactOptionalPropertyTypes differences
|
|
163
|
-
// between Arktype's inferred type and the generic T, but runtime-wise they're compatible
|
|
164
|
-
return contractResult as O.Overwrite<T, { targetFamily: 'sql' }>;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Validates logical consistency of a **structurally validated** SqlContract.
|
|
169
|
-
* This checks that references (e.g., foreign keys, primary keys, uniques) point to storage objects that already exist.
|
|
170
|
-
* Structural validation is expected to have already completed before this helper runs.
|
|
171
|
-
*
|
|
172
|
-
* Rule: keep this focused on structural consistency only; capability/feature
|
|
173
|
-
* gating (e.g., defaults.*) belongs in migration/runtime verification, not here.
|
|
174
|
-
*
|
|
175
|
-
* @param structurallyValidatedContract - The contract whose structure has already been validated
|
|
176
|
-
* @throws Error if logical validation fails
|
|
177
|
-
*/
|
|
178
|
-
function validateContractLogic(structurallyValidatedContract: SqlContract<SqlStorage>): void {
|
|
179
|
-
const { storage, models } = structurallyValidatedContract;
|
|
180
|
-
const tableNames = new Set(Object.keys(storage.tables));
|
|
181
|
-
|
|
182
|
-
// Validate storage.types if present
|
|
183
|
-
if (storage.types) {
|
|
184
|
-
for (const [typeName, typeInstance] of Object.entries(storage.types)) {
|
|
185
|
-
// Validate typeParams is not an array (arrays are objects in JS but not valid here)
|
|
186
|
-
if (Array.isArray(typeInstance.typeParams)) {
|
|
187
|
-
throw new Error(
|
|
188
|
-
`Type instance "${typeName}" has invalid typeParams: must be a plain object, not an array`,
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Validate columns in all tables
|
|
195
|
-
for (const [tableName, table] of Object.entries(storage.tables)) {
|
|
196
|
-
for (const [columnName, column] of Object.entries(table.columns)) {
|
|
197
|
-
// Validate typeParams and typeRef are mutually exclusive
|
|
198
|
-
if (column.typeParams !== undefined && column.typeRef !== undefined) {
|
|
199
|
-
throw new Error(
|
|
200
|
-
`Column "${columnName}" in table "${tableName}" has both typeParams and typeRef; these are mutually exclusive`,
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Validate typeParams is not an array (arrays are objects in JS but not valid here)
|
|
205
|
-
if (column.typeParams !== undefined && Array.isArray(column.typeParams)) {
|
|
206
|
-
throw new Error(
|
|
207
|
-
`Column "${columnName}" in table "${tableName}" has invalid typeParams: must be a plain object, not an array`,
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Validate NOT NULL columns do not have literal null defaults
|
|
212
|
-
if (!column.nullable && column.default?.kind === 'literal' && column.default.value === null) {
|
|
213
|
-
throw new Error(
|
|
214
|
-
`Table "${tableName}" column "${columnName}" is NOT NULL but has a literal null default`,
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Validate typeRef points to an existing storage.types key and matches codecId/nativeType
|
|
219
|
-
if (column.typeRef !== undefined) {
|
|
220
|
-
const referencedType = storage.types?.[column.typeRef];
|
|
221
|
-
if (!referencedType) {
|
|
222
|
-
throw new Error(
|
|
223
|
-
`Column "${columnName}" in table "${tableName}" references non-existent type instance "${column.typeRef}" (not found in storage.types)`,
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (column.codecId !== referencedType.codecId) {
|
|
228
|
-
throw new Error(
|
|
229
|
-
`Column "${columnName}" in table "${tableName}" has codecId "${column.codecId}" but references type instance "${column.typeRef}" with codecId "${referencedType.codecId}"`,
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (column.nativeType !== referencedType.nativeType) {
|
|
234
|
-
throw new Error(
|
|
235
|
-
`Column "${columnName}" in table "${tableName}" has nativeType "${column.nativeType}" but references type instance "${column.typeRef}" with nativeType "${referencedType.nativeType}"`,
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
for (const [modelName, modelUnknown] of Object.entries(models)) {
|
|
243
|
-
const model = modelUnknown as {
|
|
244
|
-
storage?: { table?: string; fields?: Record<string, { column?: string }> };
|
|
245
|
-
fields?: Record<string, unknown>;
|
|
246
|
-
relations?: Record<string, unknown>;
|
|
247
|
-
};
|
|
248
|
-
if (!model.storage?.table) {
|
|
249
|
-
/* c8 ignore next */
|
|
250
|
-
throw new Error(`Model "${modelName}" is missing storage.table`);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const tableName = model.storage.table;
|
|
254
|
-
|
|
255
|
-
if (!tableNames.has(tableName)) {
|
|
256
|
-
/* c8 ignore next */
|
|
257
|
-
throw new Error(`Model "${modelName}" references non-existent table "${tableName}"`);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const table = storage.tables[tableName];
|
|
261
|
-
if (!table) {
|
|
262
|
-
/* c8 ignore next */
|
|
263
|
-
throw new Error(`Model "${modelName}" references non-existent table "${tableName}"`);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (!table.primaryKey) {
|
|
267
|
-
/* c8 ignore next */
|
|
268
|
-
throw new Error(`Model "${modelName}" table "${tableName}" is missing a primary key`);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const columnNames = new Set(Object.keys(table.columns));
|
|
272
|
-
|
|
273
|
-
const storageFields = model.storage.fields;
|
|
274
|
-
if (storageFields) {
|
|
275
|
-
for (const [fieldName, storageField] of Object.entries(storageFields)) {
|
|
276
|
-
if (!storageField.column) {
|
|
277
|
-
/* c8 ignore next */
|
|
278
|
-
throw new Error(`Model "${modelName}" field "${fieldName}" is missing column property`);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (!columnNames.has(storageField.column)) {
|
|
282
|
-
/* c8 ignore next */
|
|
283
|
-
throw new Error(
|
|
284
|
-
`Model "${modelName}" field "${fieldName}" references non-existent column "${storageField.column}" in table "${tableName}"`,
|
|
285
|
-
);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (model.relations) {
|
|
291
|
-
for (const [relationName, relation] of Object.entries(model.relations)) {
|
|
292
|
-
if (
|
|
293
|
-
typeof relation === 'object' &&
|
|
294
|
-
relation !== null &&
|
|
295
|
-
'on' in relation &&
|
|
296
|
-
'to' in relation
|
|
297
|
-
) {
|
|
298
|
-
const on = relation.on as { localFields?: string[]; targetFields?: string[] };
|
|
299
|
-
const cardinality = (relation as { cardinality?: string }).cardinality;
|
|
300
|
-
const localFields = on.localFields;
|
|
301
|
-
const targetFields = on.targetFields;
|
|
302
|
-
if (!localFields || !targetFields) {
|
|
303
|
-
throw new Error(
|
|
304
|
-
`Model "${modelName}" relation "${relationName}" uses unsupported relation format (expected localFields/targetFields)`,
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
if (cardinality === '1:N') {
|
|
308
|
-
continue;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const hasMatchingFk = table.foreignKeys?.some((fk) => {
|
|
312
|
-
return (
|
|
313
|
-
fk.columns.length === localFields.length &&
|
|
314
|
-
fk.columns.every((col, i) => col === localFields[i]) &&
|
|
315
|
-
fk.references.table &&
|
|
316
|
-
fk.references.columns.length === targetFields.length &&
|
|
317
|
-
fk.references.columns.every((col, i) => col === targetFields[i])
|
|
318
|
-
);
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
if (!hasMatchingFk) {
|
|
322
|
-
/* c8 ignore next */
|
|
323
|
-
throw new Error(
|
|
324
|
-
`Model "${modelName}" relation "${relationName}" does not have a corresponding foreign key in table "${tableName}"`,
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
for (const [tableName, table] of Object.entries(storage.tables)) {
|
|
333
|
-
const columnNames = new Set(Object.keys(table.columns));
|
|
334
|
-
|
|
335
|
-
// Validate primaryKey references existing columns
|
|
336
|
-
if (table.primaryKey) {
|
|
337
|
-
for (const colName of table.primaryKey.columns) {
|
|
338
|
-
if (!columnNames.has(colName)) {
|
|
339
|
-
/* c8 ignore next */
|
|
340
|
-
throw new Error(
|
|
341
|
-
`Table "${tableName}" primaryKey references non-existent column "${colName}"`,
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Validate unique constraints reference existing columns
|
|
348
|
-
for (const unique of table.uniques) {
|
|
349
|
-
for (const colName of unique.columns) {
|
|
350
|
-
if (!columnNames.has(colName)) {
|
|
351
|
-
/* c8 ignore next */
|
|
352
|
-
throw new Error(
|
|
353
|
-
`Table "${tableName}" unique constraint references non-existent column "${colName}"`,
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Validate indexes reference existing columns
|
|
360
|
-
for (const index of table.indexes) {
|
|
361
|
-
for (const colName of index.columns) {
|
|
362
|
-
if (!columnNames.has(colName)) {
|
|
363
|
-
/* c8 ignore next */
|
|
364
|
-
throw new Error(`Table "${tableName}" index references non-existent column "${colName}"`);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Validate foreignKeys reference existing tables and columns
|
|
370
|
-
for (const fk of table.foreignKeys) {
|
|
371
|
-
// Validate FK columns exist in the referencing table
|
|
372
|
-
for (const colName of fk.columns) {
|
|
373
|
-
if (!columnNames.has(colName)) {
|
|
374
|
-
/* c8 ignore next */
|
|
375
|
-
throw new Error(
|
|
376
|
-
`Table "${tableName}" foreignKey references non-existent column "${colName}"`,
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Validate referenced table exists
|
|
382
|
-
if (!tableNames.has(fk.references.table)) {
|
|
383
|
-
/* c8 ignore next */
|
|
384
|
-
throw new Error(
|
|
385
|
-
`Table "${tableName}" foreignKey references non-existent table "${fk.references.table}"`,
|
|
386
|
-
);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// Validate referenced columns exist in the referenced table
|
|
390
|
-
const referencedTable = storage.tables[fk.references.table];
|
|
391
|
-
if (!referencedTable) {
|
|
392
|
-
/* c8 ignore next */
|
|
393
|
-
throw new Error(
|
|
394
|
-
`Table "${tableName}" foreignKey references non-existent table "${fk.references.table}"`,
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
const referencedColumnNames = new Set(Object.keys(referencedTable.columns));
|
|
398
|
-
|
|
399
|
-
for (const colName of fk.references.columns) {
|
|
400
|
-
if (!referencedColumnNames.has(colName)) {
|
|
401
|
-
/* c8 ignore next */
|
|
402
|
-
throw new Error(
|
|
403
|
-
`Table "${tableName}" foreignKey references non-existent column "${colName}" in table "${fk.references.table}"`,
|
|
404
|
-
);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if (fk.columns.length !== fk.references.columns.length) {
|
|
409
|
-
/* c8 ignore next */
|
|
410
|
-
throw new Error(
|
|
411
|
-
`Table "${tableName}" foreignKey column count (${fk.columns.length}) does not match referenced column count (${fk.references.columns.length})`,
|
|
412
|
-
);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
import { normalizeContract } from '@prisma-next/sql-contract/validate';
|
|
419
|
-
export { normalizeContract };
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* Validates that a JSON import conforms to the SqlContract structure
|
|
423
|
-
* and returns a fully typed SqlContract.
|
|
424
|
-
*
|
|
425
|
-
* This function is specifically for validating JSON imports (e.g., from contract.json).
|
|
426
|
-
* Contracts created via the builder API (defineContract) are already valid and should
|
|
427
|
-
* not be passed to this function - use them directly without validation.
|
|
428
|
-
*
|
|
429
|
-
* Performs both structural validation (using Arktype) and logical validation
|
|
430
|
-
* (ensuring all references are valid).
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
* The type parameter `TContract` must be a fully-typed contract type (e.g., from `contract.d.ts`),
|
|
434
|
-
* NOT a generic `SqlContract<SqlStorage>`.
|
|
435
|
-
*
|
|
436
|
-
* **Correct:**
|
|
437
|
-
* ```typescript
|
|
438
|
-
* import type { Contract } from './contract.d';
|
|
439
|
-
* const contract = validateContract<Contract>(contractJson);
|
|
440
|
-
* ```
|
|
441
|
-
*
|
|
442
|
-
* **Incorrect:**
|
|
443
|
-
* ```typescript
|
|
444
|
-
* import type { SqlContract, SqlStorage } from '@prisma-next/sql-contract/types';
|
|
445
|
-
* const contract = validateContract<SqlContract<SqlStorage>>(contractJson);
|
|
446
|
-
* // ❌ Types will be inferred as 'unknown' - this won't work!
|
|
447
|
-
* ```
|
|
448
|
-
*
|
|
449
|
-
* The type parameter provides the specific table structure, column types, and model definitions.
|
|
450
|
-
* This function validates the runtime structure matches the type, but does not infer types
|
|
451
|
-
* from JSON (as JSON imports lose literal type information).
|
|
452
|
-
*
|
|
453
|
-
* @param value - The contract value to validate (must be from a JSON import, not a builder)
|
|
454
|
-
* @returns A validated contract matching the TContract type
|
|
455
|
-
* @throws Error if the contract structure or logic is invalid
|
|
456
|
-
*/
|
|
457
|
-
export function validateContract<TContract extends SqlContract<SqlStorage>>(
|
|
458
|
-
value: unknown,
|
|
459
|
-
): TContract {
|
|
460
|
-
// Normalize contract first (add defaults for missing fields)
|
|
461
|
-
const normalized = normalizeContract(value);
|
|
462
|
-
|
|
463
|
-
const structurallyValid = validateContractStructure<SqlContract<SqlStorage>>(normalized);
|
|
464
|
-
|
|
465
|
-
const contractForValidation = structurallyValid as SqlContract<SqlStorage>;
|
|
466
|
-
|
|
467
|
-
validateContractLogic(contractForValidation);
|
|
468
|
-
|
|
469
|
-
const semanticErrors = validateStorageSemantics(contractForValidation.storage);
|
|
470
|
-
if (semanticErrors.length > 0) {
|
|
471
|
-
throw new Error(`Contract semantic validation failed: ${semanticErrors.join('; ')}`);
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
return decodeContractDefaults(contractForValidation) as TContract;
|
|
475
|
-
}
|