@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
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
export type {
|
|
2
|
-
ColumnBuilder,
|
|
3
2
|
ComposedAuthoringHelpers,
|
|
3
|
+
ContractInput,
|
|
4
|
+
ContractModelBuilder,
|
|
4
5
|
ScalarFieldBuilder,
|
|
5
|
-
StagedContractInput,
|
|
6
|
-
StagedModelBuilder,
|
|
7
6
|
} from '../contract-builder';
|
|
8
7
|
export {
|
|
9
|
-
|
|
8
|
+
buildSqlContractFromDefinition,
|
|
10
9
|
defineContract,
|
|
11
10
|
field,
|
|
12
11
|
model,
|
|
13
12
|
rel,
|
|
14
13
|
} from '../contract-builder';
|
|
15
14
|
export type {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} from '../
|
|
15
|
+
ContractDefinition,
|
|
16
|
+
FieldNode,
|
|
17
|
+
ForeignKeyNode,
|
|
18
|
+
IndexNode,
|
|
19
|
+
ModelNode,
|
|
20
|
+
PrimaryKeyNode,
|
|
21
|
+
RelationNode,
|
|
22
|
+
UniqueConstraintNode,
|
|
23
|
+
} from '../contract-definition';
|
|
@@ -1,475 +0,0 @@
|
|
|
1
|
-
import type { ContractIR } from '@prisma-next/contract/ir';
|
|
2
|
-
import type {
|
|
3
|
-
ColumnDefault,
|
|
4
|
-
ColumnDefaultLiteralInputValue,
|
|
5
|
-
ColumnDefaultLiteralValue,
|
|
6
|
-
ExecutionMutationDefault,
|
|
7
|
-
ExecutionMutationDefaultValue,
|
|
8
|
-
TaggedRaw,
|
|
9
|
-
} from '@prisma-next/contract/types';
|
|
10
|
-
import type {
|
|
11
|
-
ColumnBuilderState,
|
|
12
|
-
ContractBuilderState,
|
|
13
|
-
ModelBuilderState,
|
|
14
|
-
RelationDefinition,
|
|
15
|
-
TableBuilderState,
|
|
16
|
-
} from '@prisma-next/contract-authoring';
|
|
17
|
-
import {
|
|
18
|
-
applyFkDefaults,
|
|
19
|
-
type SqlStorage,
|
|
20
|
-
type StorageTypeInstance,
|
|
21
|
-
} from '@prisma-next/sql-contract/types';
|
|
22
|
-
import { validateStorageSemantics } from '@prisma-next/sql-contract/validators';
|
|
23
|
-
import { ifDefined } from '@prisma-next/utils/defined';
|
|
24
|
-
import type { SqlSemanticContractDefinition, SqlSemanticModelNode } from './semantic-contract';
|
|
25
|
-
|
|
26
|
-
type RuntimeTableState = TableBuilderState<
|
|
27
|
-
string,
|
|
28
|
-
Record<string, ColumnBuilderState<string, boolean, string>>,
|
|
29
|
-
readonly string[] | undefined
|
|
30
|
-
>;
|
|
31
|
-
|
|
32
|
-
type RuntimeModelState = ModelBuilderState<
|
|
33
|
-
string,
|
|
34
|
-
string,
|
|
35
|
-
Record<string, string>,
|
|
36
|
-
Record<string, RelationDefinition>
|
|
37
|
-
>;
|
|
38
|
-
|
|
39
|
-
export type RuntimeBuilderState = ContractBuilderState<
|
|
40
|
-
string | undefined,
|
|
41
|
-
Record<string, RuntimeTableState>,
|
|
42
|
-
Record<string, RuntimeModelState>,
|
|
43
|
-
string | undefined,
|
|
44
|
-
Record<string, unknown> | undefined,
|
|
45
|
-
Record<string, Record<string, boolean>> | undefined
|
|
46
|
-
>;
|
|
47
|
-
|
|
48
|
-
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
49
|
-
if (typeof value !== 'object' || value === null) return false;
|
|
50
|
-
const proto = Object.getPrototypeOf(value);
|
|
51
|
-
return proto === Object.prototype || proto === null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function isJsonValue(value: unknown): value is ColumnDefaultLiteralValue {
|
|
55
|
-
if (value === null) return true;
|
|
56
|
-
const valueType = typeof value;
|
|
57
|
-
if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') return true;
|
|
58
|
-
if (Array.isArray(value)) {
|
|
59
|
-
return value.every((item) => isJsonValue(item));
|
|
60
|
-
}
|
|
61
|
-
if (isPlainObject(value)) {
|
|
62
|
-
return Object.values(value).every((item) => isJsonValue(item));
|
|
63
|
-
}
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function encodeDefaultLiteralValue(
|
|
68
|
-
value: ColumnDefaultLiteralInputValue,
|
|
69
|
-
): ColumnDefaultLiteralValue {
|
|
70
|
-
if (typeof value === 'bigint') {
|
|
71
|
-
return { $type: 'bigint', value: value.toString() };
|
|
72
|
-
}
|
|
73
|
-
if (value instanceof Date) {
|
|
74
|
-
return value.toISOString();
|
|
75
|
-
}
|
|
76
|
-
if (isJsonValue(value)) {
|
|
77
|
-
if (isPlainObject(value) && '$type' in value) {
|
|
78
|
-
return { $type: 'raw', value } satisfies TaggedRaw;
|
|
79
|
-
}
|
|
80
|
-
return value;
|
|
81
|
-
}
|
|
82
|
-
throw new Error(
|
|
83
|
-
'Unsupported column default literal value: expected JSON-safe value, bigint, or Date.',
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function encodeColumnDefault(defaultInput: ColumnDefault): ColumnDefault {
|
|
88
|
-
if (defaultInput.kind === 'function') {
|
|
89
|
-
return { kind: 'function', expression: defaultInput.expression };
|
|
90
|
-
}
|
|
91
|
-
return { kind: 'literal', value: encodeDefaultLiteralValue(defaultInput.value) };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function assertStorageSemantics(storage: SqlStorage): void {
|
|
95
|
-
const semanticErrors = validateStorageSemantics(storage);
|
|
96
|
-
if (semanticErrors.length > 0) {
|
|
97
|
-
throw new Error(`Contract semantic validation failed: ${semanticErrors.join('; ')}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function buildContractIR(state: RuntimeBuilderState): ContractIR {
|
|
102
|
-
if (!state.target) {
|
|
103
|
-
throw new Error('target is required. Call .target() before .build()');
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const target = state.target;
|
|
107
|
-
|
|
108
|
-
const storageTables: Record<
|
|
109
|
-
string,
|
|
110
|
-
{
|
|
111
|
-
columns: Record<string, unknown>;
|
|
112
|
-
uniques: unknown[];
|
|
113
|
-
indexes: unknown[];
|
|
114
|
-
foreignKeys: unknown[];
|
|
115
|
-
primaryKey?: unknown;
|
|
116
|
-
}
|
|
117
|
-
> = {};
|
|
118
|
-
const executionDefaults: ExecutionMutationDefault[] = [];
|
|
119
|
-
|
|
120
|
-
for (const tableName of Object.keys(state.tables)) {
|
|
121
|
-
const tableState = state.tables[tableName];
|
|
122
|
-
if (!tableState) continue;
|
|
123
|
-
|
|
124
|
-
const columns: Record<string, unknown> = {};
|
|
125
|
-
|
|
126
|
-
for (const columnName in tableState.columns) {
|
|
127
|
-
const columnState = tableState.columns[columnName];
|
|
128
|
-
if (!columnState) continue;
|
|
129
|
-
|
|
130
|
-
const encodedDefault =
|
|
131
|
-
columnState.default !== undefined
|
|
132
|
-
? encodeColumnDefault(columnState.default as ColumnDefault)
|
|
133
|
-
: undefined;
|
|
134
|
-
|
|
135
|
-
columns[columnName] = {
|
|
136
|
-
nativeType: columnState.nativeType,
|
|
137
|
-
codecId: columnState.type,
|
|
138
|
-
nullable: columnState.nullable ?? false,
|
|
139
|
-
...ifDefined('typeParams', columnState.typeParams),
|
|
140
|
-
...ifDefined('default', encodedDefault),
|
|
141
|
-
...ifDefined('typeRef', columnState.typeRef),
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
if ('executionDefault' in columnState && columnState.executionDefault) {
|
|
145
|
-
executionDefaults.push({
|
|
146
|
-
ref: { table: tableName, column: columnName },
|
|
147
|
-
onCreate: columnState.executionDefault as ExecutionMutationDefaultValue,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const uniques = (tableState.uniques ?? []).map((u) => ({
|
|
153
|
-
columns: u.columns,
|
|
154
|
-
...(u.name ? { name: u.name } : {}),
|
|
155
|
-
}));
|
|
156
|
-
|
|
157
|
-
const indexes = (tableState.indexes ?? []).map((i) => ({
|
|
158
|
-
columns: i.columns,
|
|
159
|
-
...(i.name ? { name: i.name } : {}),
|
|
160
|
-
...(i.using ? { using: i.using } : {}),
|
|
161
|
-
...(i.config ? { config: i.config } : {}),
|
|
162
|
-
}));
|
|
163
|
-
|
|
164
|
-
const foreignKeys = (tableState.foreignKeys ?? []).map((fk) => ({
|
|
165
|
-
columns: fk.columns,
|
|
166
|
-
references: fk.references,
|
|
167
|
-
...applyFkDefaults(fk, state.foreignKeyDefaults),
|
|
168
|
-
...(fk.name ? { name: fk.name } : {}),
|
|
169
|
-
...(fk.onDelete !== undefined ? { onDelete: fk.onDelete } : {}),
|
|
170
|
-
...(fk.onUpdate !== undefined ? { onUpdate: fk.onUpdate } : {}),
|
|
171
|
-
}));
|
|
172
|
-
|
|
173
|
-
storageTables[tableName] = {
|
|
174
|
-
columns,
|
|
175
|
-
uniques,
|
|
176
|
-
indexes,
|
|
177
|
-
foreignKeys,
|
|
178
|
-
...(tableState.primaryKey
|
|
179
|
-
? {
|
|
180
|
-
primaryKey: {
|
|
181
|
-
columns: tableState.primaryKey,
|
|
182
|
-
...(tableState.primaryKeyName ? { name: tableState.primaryKeyName } : {}),
|
|
183
|
-
},
|
|
184
|
-
}
|
|
185
|
-
: {}),
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const storageTypes = (state.storageTypes ?? {}) as Record<string, StorageTypeInstance>;
|
|
190
|
-
const storage = {
|
|
191
|
-
tables: storageTables,
|
|
192
|
-
types: storageTypes,
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
const execution =
|
|
196
|
-
executionDefaults.length > 0
|
|
197
|
-
? {
|
|
198
|
-
mutations: {
|
|
199
|
-
defaults: executionDefaults.sort((a, b) => {
|
|
200
|
-
const tableCompare = a.ref.table.localeCompare(b.ref.table);
|
|
201
|
-
if (tableCompare !== 0) {
|
|
202
|
-
return tableCompare;
|
|
203
|
-
}
|
|
204
|
-
return a.ref.column.localeCompare(b.ref.column);
|
|
205
|
-
}),
|
|
206
|
-
},
|
|
207
|
-
}
|
|
208
|
-
: undefined;
|
|
209
|
-
|
|
210
|
-
const models: Record<string, Record<string, unknown>> = {};
|
|
211
|
-
|
|
212
|
-
for (const modelName in state.models) {
|
|
213
|
-
const modelState = state.models[modelName];
|
|
214
|
-
if (!modelState) continue;
|
|
215
|
-
|
|
216
|
-
const tableName = modelState.table;
|
|
217
|
-
const tableState = state.tables[tableName];
|
|
218
|
-
const tableColumns = tableState
|
|
219
|
-
? (tableState.columns as Record<string, { type: string; nullable?: boolean }>)
|
|
220
|
-
: {};
|
|
221
|
-
|
|
222
|
-
const storageFields: Record<string, { readonly column: string }> = {};
|
|
223
|
-
const domainFields: Record<string, Record<string, unknown>> = {};
|
|
224
|
-
|
|
225
|
-
for (const fieldName in modelState.fields) {
|
|
226
|
-
const columnName = modelState.fields[fieldName];
|
|
227
|
-
if (!columnName) continue;
|
|
228
|
-
|
|
229
|
-
storageFields[fieldName] = { column: columnName };
|
|
230
|
-
|
|
231
|
-
const column = tableColumns[columnName];
|
|
232
|
-
if (column) {
|
|
233
|
-
domainFields[fieldName] = {
|
|
234
|
-
codecId: column.type,
|
|
235
|
-
nullable: column.nullable ?? false,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const modelRelations: Record<string, Record<string, unknown>> = {};
|
|
241
|
-
if (modelState.relations) {
|
|
242
|
-
for (const relName in modelState.relations) {
|
|
243
|
-
const rel = modelState.relations[relName];
|
|
244
|
-
if (!rel) continue;
|
|
245
|
-
modelRelations[relName] = {
|
|
246
|
-
to: rel.to,
|
|
247
|
-
cardinality: rel.cardinality,
|
|
248
|
-
on: {
|
|
249
|
-
localFields: rel.on.parentCols,
|
|
250
|
-
targetFields: rel.on.childCols,
|
|
251
|
-
},
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
models[modelName] = {
|
|
257
|
-
storage: {
|
|
258
|
-
table: tableName,
|
|
259
|
-
fields: storageFields,
|
|
260
|
-
},
|
|
261
|
-
fields: domainFields,
|
|
262
|
-
relations: modelRelations,
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const extensionNamespaces = state.extensionNamespaces ?? [];
|
|
267
|
-
const extensionPacks: Record<string, unknown> = { ...(state.extensionPacks || {}) };
|
|
268
|
-
for (const namespace of extensionNamespaces) {
|
|
269
|
-
if (!Object.hasOwn(extensionPacks, namespace)) {
|
|
270
|
-
extensionPacks[namespace] = {};
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const contract = {
|
|
275
|
-
schemaVersion: '1' as const,
|
|
276
|
-
target,
|
|
277
|
-
targetFamily: 'sql' as const,
|
|
278
|
-
storageHash: state.storageHash || 'sha256:ts-builder-placeholder',
|
|
279
|
-
models,
|
|
280
|
-
roots: {},
|
|
281
|
-
storage,
|
|
282
|
-
...(execution ? { execution } : {}),
|
|
283
|
-
extensionPacks,
|
|
284
|
-
capabilities: state.capabilities || {},
|
|
285
|
-
meta: {},
|
|
286
|
-
sources: {},
|
|
287
|
-
};
|
|
288
|
-
|
|
289
|
-
assertStorageSemantics(contract.storage as SqlStorage);
|
|
290
|
-
|
|
291
|
-
return contract as ContractIR;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function assertKnownTargetModel(
|
|
295
|
-
modelsByName: ReadonlyMap<string, SqlSemanticModelNode>,
|
|
296
|
-
sourceModelName: string,
|
|
297
|
-
targetModelName: string,
|
|
298
|
-
context: string,
|
|
299
|
-
): SqlSemanticModelNode {
|
|
300
|
-
const targetModel = modelsByName.get(targetModelName);
|
|
301
|
-
if (!targetModel) {
|
|
302
|
-
throw new Error(
|
|
303
|
-
`${context} on model "${sourceModelName}" references unknown model "${targetModelName}"`,
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
return targetModel;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function assertTargetTableMatches(
|
|
310
|
-
sourceModelName: string,
|
|
311
|
-
targetModel: SqlSemanticModelNode,
|
|
312
|
-
referencedTableName: string,
|
|
313
|
-
context: string,
|
|
314
|
-
): void {
|
|
315
|
-
if (targetModel.tableName !== referencedTableName) {
|
|
316
|
-
throw new Error(
|
|
317
|
-
`${context} on model "${sourceModelName}" references table "${referencedTableName}" but model "${targetModel.modelName}" maps to "${targetModel.tableName}"`,
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
export function buildSqlContractFromSemanticDefinition(
|
|
323
|
-
definition: SqlSemanticContractDefinition,
|
|
324
|
-
): ContractIR {
|
|
325
|
-
const modelsByName = new Map(definition.models.map((m) => [m.modelName, m]));
|
|
326
|
-
|
|
327
|
-
const tables: Record<string, RuntimeTableState> = {};
|
|
328
|
-
for (const model of definition.models) {
|
|
329
|
-
const columns: Record<string, ColumnBuilderState<string, boolean, string>> = {};
|
|
330
|
-
|
|
331
|
-
for (const field of model.fields) {
|
|
332
|
-
if (field.executionDefault) {
|
|
333
|
-
if (field.default !== undefined) {
|
|
334
|
-
throw new Error(
|
|
335
|
-
`Field "${model.modelName}.${field.fieldName}" cannot define both default and executionDefault.`,
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
if (field.nullable) {
|
|
339
|
-
throw new Error(
|
|
340
|
-
`Field "${model.modelName}.${field.fieldName}" cannot be nullable when executionDefault is present.`,
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
columns[field.columnName] = {
|
|
344
|
-
name: field.columnName,
|
|
345
|
-
type: field.descriptor.codecId,
|
|
346
|
-
nativeType: field.descriptor.nativeType,
|
|
347
|
-
nullable: false,
|
|
348
|
-
...ifDefined('typeParams', field.descriptor.typeParams),
|
|
349
|
-
...ifDefined('typeRef', field.descriptor.typeRef),
|
|
350
|
-
executionDefault: field.executionDefault,
|
|
351
|
-
} as ColumnBuilderState<string, false, string>;
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
columns[field.columnName] = {
|
|
356
|
-
name: field.columnName,
|
|
357
|
-
type: field.descriptor.codecId,
|
|
358
|
-
nativeType: field.descriptor.nativeType,
|
|
359
|
-
nullable: field.nullable,
|
|
360
|
-
...ifDefined('typeParams', field.descriptor.typeParams),
|
|
361
|
-
...ifDefined('typeRef', field.descriptor.typeRef),
|
|
362
|
-
...ifDefined('default', field.default),
|
|
363
|
-
} as ColumnBuilderState<string, boolean, string>;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
if (model.id) {
|
|
367
|
-
const fieldsByColumnName = new Map(model.fields.map((field) => [field.columnName, field]));
|
|
368
|
-
for (const columnName of model.id.columns) {
|
|
369
|
-
const field = fieldsByColumnName.get(columnName);
|
|
370
|
-
if (field?.nullable) {
|
|
371
|
-
throw new Error(
|
|
372
|
-
`Model "${model.modelName}" uses nullable field "${field.fieldName}" in its identity.`,
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const foreignKeys = (model.foreignKeys ?? []).map((fk) => {
|
|
379
|
-
const targetModel = assertKnownTargetModel(
|
|
380
|
-
modelsByName,
|
|
381
|
-
model.modelName,
|
|
382
|
-
fk.references.model,
|
|
383
|
-
'Foreign key',
|
|
384
|
-
);
|
|
385
|
-
assertTargetTableMatches(model.modelName, targetModel, fk.references.table, 'Foreign key');
|
|
386
|
-
return {
|
|
387
|
-
columns: fk.columns,
|
|
388
|
-
references: { table: fk.references.table, columns: fk.references.columns },
|
|
389
|
-
...ifDefined('name', fk.name),
|
|
390
|
-
...ifDefined('onDelete', fk.onDelete),
|
|
391
|
-
...ifDefined('onUpdate', fk.onUpdate),
|
|
392
|
-
...ifDefined('constraint', fk.constraint),
|
|
393
|
-
...ifDefined('index', fk.index),
|
|
394
|
-
};
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
tables[model.tableName] = {
|
|
398
|
-
name: model.tableName,
|
|
399
|
-
columns,
|
|
400
|
-
...(model.id ? { primaryKey: model.id.columns } : {}),
|
|
401
|
-
...(model.id?.name ? { primaryKeyName: model.id.name } : {}),
|
|
402
|
-
uniques: model.uniques ?? [],
|
|
403
|
-
indexes: model.indexes ?? [],
|
|
404
|
-
foreignKeys,
|
|
405
|
-
} as RuntimeTableState;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
const modelStates: Record<string, RuntimeModelState> = {};
|
|
409
|
-
for (const model of definition.models) {
|
|
410
|
-
const fields: Record<string, string> = {};
|
|
411
|
-
for (const field of model.fields) {
|
|
412
|
-
fields[field.fieldName] = field.columnName;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const relations: Record<string, RelationDefinition> = {};
|
|
416
|
-
for (const relation of model.relations ?? []) {
|
|
417
|
-
const targetModel = assertKnownTargetModel(
|
|
418
|
-
modelsByName,
|
|
419
|
-
model.modelName,
|
|
420
|
-
relation.toModel,
|
|
421
|
-
'Relation',
|
|
422
|
-
);
|
|
423
|
-
assertTargetTableMatches(model.modelName, targetModel, relation.toTable, 'Relation');
|
|
424
|
-
|
|
425
|
-
if (relation.cardinality === 'N:M' && !relation.through) {
|
|
426
|
-
throw new Error(
|
|
427
|
-
`Relation "${model.modelName}.${relation.fieldName}" with cardinality "N:M" requires through metadata`,
|
|
428
|
-
);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
relations[relation.fieldName] = {
|
|
432
|
-
to: relation.toModel,
|
|
433
|
-
cardinality: relation.cardinality,
|
|
434
|
-
on: {
|
|
435
|
-
parentCols: relation.on.parentColumns,
|
|
436
|
-
childCols: relation.on.childColumns,
|
|
437
|
-
},
|
|
438
|
-
...(relation.through
|
|
439
|
-
? {
|
|
440
|
-
through: {
|
|
441
|
-
table: relation.through.table,
|
|
442
|
-
parentCols: relation.through.parentColumns,
|
|
443
|
-
childCols: relation.through.childColumns,
|
|
444
|
-
},
|
|
445
|
-
}
|
|
446
|
-
: undefined),
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
modelStates[model.modelName] = {
|
|
451
|
-
name: model.modelName,
|
|
452
|
-
table: model.tableName,
|
|
453
|
-
fields,
|
|
454
|
-
relations,
|
|
455
|
-
};
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
const extensionNamespaces = definition.extensionPacks
|
|
459
|
-
? Object.values(definition.extensionPacks).map((pack) => pack.id)
|
|
460
|
-
: undefined;
|
|
461
|
-
|
|
462
|
-
const state: RuntimeBuilderState = {
|
|
463
|
-
target: definition.target.targetId,
|
|
464
|
-
tables,
|
|
465
|
-
models: modelStates,
|
|
466
|
-
...ifDefined('storageTypes', definition.storageTypes),
|
|
467
|
-
...ifDefined('storageHash', definition.storageHash),
|
|
468
|
-
...ifDefined('extensionPacks', definition.extensionPacks),
|
|
469
|
-
...ifDefined('capabilities', definition.capabilities),
|
|
470
|
-
...ifDefined('foreignKeyDefaults', definition.foreignKeyDefaults),
|
|
471
|
-
...ifDefined('extensionNamespaces', extensionNamespaces),
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
return buildContractIR(state);
|
|
475
|
-
}
|