@prisma-next/sql-contract-emitter 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 +2 -2
- package/dist/index.d.mts +18 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +167 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +22 -14
- package/src/index.ts +132 -422
- package/dist/index.d.ts +0 -34
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -489
- package/dist/index.js.map +0 -1
package/src/index.ts
CHANGED
|
@@ -1,54 +1,38 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Contract, ContractModel } from '@prisma-next/contract/types';
|
|
2
|
+
import {
|
|
3
|
+
generateCodecTypeIntersection,
|
|
4
|
+
serializeObjectKey,
|
|
5
|
+
serializeValue,
|
|
6
|
+
} from '@prisma-next/emitter/domain-type-generation';
|
|
2
7
|
import type {
|
|
3
8
|
GenerateContractTypesOptions,
|
|
4
|
-
TypeRenderContext,
|
|
5
|
-
TypeRenderEntry,
|
|
6
|
-
TypesImportSpec,
|
|
7
9
|
ValidationContext,
|
|
8
|
-
} from '@prisma-next/
|
|
9
|
-
import type {
|
|
10
|
-
ModelDefinition,
|
|
11
|
-
ModelField,
|
|
12
|
-
SqlStorage,
|
|
13
|
-
StorageColumn,
|
|
14
|
-
StorageTable,
|
|
15
|
-
StorageTypeInstance,
|
|
16
|
-
} from '@prisma-next/sql-contract/types';
|
|
10
|
+
} from '@prisma-next/framework-components/emission';
|
|
11
|
+
import type { SqlModelStorage, SqlStorage, StorageTable } from '@prisma-next/sql-contract/types';
|
|
17
12
|
import { assertDefined } from '@prisma-next/utils/assertions';
|
|
18
13
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*/
|
|
23
|
-
function resolveColumnTypeParams(
|
|
24
|
-
column: StorageColumn,
|
|
25
|
-
storage: SqlStorage,
|
|
26
|
-
): Record<string, unknown> | undefined {
|
|
27
|
-
// Inline typeParams take precedence
|
|
28
|
-
if (column.typeParams && Object.keys(column.typeParams).length > 0) {
|
|
29
|
-
return column.typeParams;
|
|
14
|
+
function serializeTypeParamsLiteral(params: Record<string, unknown> | undefined): string {
|
|
15
|
+
if (!params || Object.keys(params).length === 0) {
|
|
16
|
+
return 'Record<string, never>';
|
|
30
17
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return typeInstance.typeParams;
|
|
36
|
-
}
|
|
18
|
+
|
|
19
|
+
const entries: string[] = [];
|
|
20
|
+
for (const [key, value] of Object.entries(params)) {
|
|
21
|
+
entries.push(`readonly ${serializeObjectKey(key)}: ${serializeValue(value)}`);
|
|
37
22
|
}
|
|
38
|
-
|
|
23
|
+
|
|
24
|
+
return `{ ${entries.join('; ')} }`;
|
|
39
25
|
}
|
|
40
26
|
|
|
41
|
-
export const
|
|
27
|
+
export const sqlEmission = {
|
|
42
28
|
id: 'sql',
|
|
43
29
|
|
|
44
|
-
validateTypes(
|
|
45
|
-
const storage =
|
|
30
|
+
validateTypes(contract: Contract, _ctx: ValidationContext): void {
|
|
31
|
+
const storage = contract.storage as unknown as SqlStorage | undefined;
|
|
46
32
|
if (!storage || !storage.tables) {
|
|
47
33
|
return;
|
|
48
34
|
}
|
|
49
35
|
|
|
50
|
-
// Validate codec ID format (ns/name@version). Adapter-provided codecs are available regardless of contract.extensionPacks (which is for framework extensions); TypeScript prevents invalid usage and runtime validates availability.
|
|
51
|
-
|
|
52
36
|
const typeIdRegex = /^([^/]+)\/([^@]+)@(\d+)$/;
|
|
53
37
|
|
|
54
38
|
for (const [tableName, tableUnknown] of Object.entries(storage.tables)) {
|
|
@@ -70,22 +54,21 @@ export const sqlTargetFamilyHook = {
|
|
|
70
54
|
}
|
|
71
55
|
},
|
|
72
56
|
|
|
73
|
-
validateStructure(
|
|
74
|
-
if (
|
|
75
|
-
throw new Error(`Expected targetFamily "sql", got "${
|
|
57
|
+
validateStructure(contract: Contract): void {
|
|
58
|
+
if (contract.targetFamily !== 'sql') {
|
|
59
|
+
throw new Error(`Expected targetFamily "sql", got "${contract.targetFamily}"`);
|
|
76
60
|
}
|
|
77
61
|
|
|
78
|
-
const storage =
|
|
62
|
+
const storage = contract.storage as unknown as SqlStorage | undefined;
|
|
79
63
|
if (!storage || !storage.tables) {
|
|
80
64
|
throw new Error('SQL contract must have storage.tables');
|
|
81
65
|
}
|
|
82
66
|
|
|
83
|
-
const models =
|
|
67
|
+
const models = contract.models as Record<string, ContractModel<SqlModelStorage>>;
|
|
84
68
|
const tableNames = new Set(Object.keys(storage.tables));
|
|
85
69
|
|
|
86
70
|
if (models) {
|
|
87
|
-
for (const [modelName,
|
|
88
|
-
const model = modelUnknown as ModelDefinition;
|
|
71
|
+
for (const [modelName, model] of Object.entries(models)) {
|
|
89
72
|
if (!model.storage?.table) {
|
|
90
73
|
throw new Error(`Model "${modelName}" is missing storage.table`);
|
|
91
74
|
}
|
|
@@ -103,12 +86,12 @@ export const sqlTargetFamilyHook = {
|
|
|
103
86
|
}
|
|
104
87
|
|
|
105
88
|
const columnNames = new Set(Object.keys(table.columns));
|
|
106
|
-
|
|
107
|
-
|
|
89
|
+
const storageFields = model.storage.fields;
|
|
90
|
+
if (!storageFields || Object.keys(storageFields).length === 0) {
|
|
91
|
+
throw new Error(`Model "${modelName}" is missing storage.fields`);
|
|
108
92
|
}
|
|
109
93
|
|
|
110
|
-
for (const [fieldName,
|
|
111
|
-
const field = fieldUnknown as ModelField;
|
|
94
|
+
for (const [fieldName, field] of Object.entries(storageFields)) {
|
|
112
95
|
if (!field.column) {
|
|
113
96
|
throw new Error(`Model "${modelName}" field "${fieldName}" is missing column property`);
|
|
114
97
|
}
|
|
@@ -132,10 +115,6 @@ export const sqlTargetFamilyHook = {
|
|
|
132
115
|
const table = tableUnknown as StorageTable;
|
|
133
116
|
const columnNames = new Set(Object.keys(table.columns));
|
|
134
117
|
|
|
135
|
-
// Column structure (nullable, nativeType, codecId) and table arrays (uniques, indexes, foreignKeys)
|
|
136
|
-
// are validated by Arktype schema validation - no need to re-check here.
|
|
137
|
-
// We only validate logical consistency (foreign key references, model references, etc.)
|
|
138
|
-
|
|
139
118
|
if (!Array.isArray(table.uniques)) {
|
|
140
119
|
throw new Error(
|
|
141
120
|
`Table "${tableName}" is missing required field "uniques" (must be an array)`,
|
|
@@ -197,7 +176,6 @@ export const sqlTargetFamilyHook = {
|
|
|
197
176
|
);
|
|
198
177
|
}
|
|
199
178
|
|
|
200
|
-
// Table existence guaranteed by Set.has() check above
|
|
201
179
|
const referencedTable: StorageTable | undefined = storage.tables[fk.references.table];
|
|
202
180
|
assertDefined(
|
|
203
181
|
referencedTable,
|
|
@@ -222,109 +200,50 @@ export const sqlTargetFamilyHook = {
|
|
|
222
200
|
}
|
|
223
201
|
},
|
|
224
202
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
codecTypeImports: ReadonlyArray<TypesImportSpec>,
|
|
228
|
-
operationTypeImports: ReadonlyArray<TypesImportSpec>,
|
|
229
|
-
options?: GenerateContractTypesOptions,
|
|
230
|
-
): string {
|
|
231
|
-
const parameterizedRenderers = options?.parameterizedRenderers;
|
|
232
|
-
const parameterizedTypeImports = options?.parameterizedTypeImports;
|
|
233
|
-
const storage = ir.storage as SqlStorage;
|
|
234
|
-
const models = ir.models as Record<string, ModelDefinition>;
|
|
235
|
-
|
|
236
|
-
// Collect all type imports from three sources:
|
|
237
|
-
// 1. Codec type imports (from adapters, targets, and extensions)
|
|
238
|
-
// 2. Operation type imports (from adapters, targets, and extensions)
|
|
239
|
-
// 3. Parameterized type imports (for parameterized codec renderers, may contain duplicates)
|
|
240
|
-
const allImports: TypesImportSpec[] = [...codecTypeImports, ...operationTypeImports];
|
|
241
|
-
|
|
242
|
-
if (parameterizedTypeImports) {
|
|
243
|
-
allImports.push(...parameterizedTypeImports);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Deduplicate imports by package+named to avoid duplicate import statements.
|
|
247
|
-
// Strategy: When the same package::named appears multiple times, keep the first
|
|
248
|
-
// occurrence (and its alias); later duplicates with different aliases are silently ignored.
|
|
249
|
-
//
|
|
250
|
-
// Note: uniqueImports must be an array (not a Set) because:
|
|
251
|
-
// - We need to preserve the full TypesImportSpec objects (package, named, alias)
|
|
252
|
-
// - We need to preserve insertion order (first occurrence wins)
|
|
253
|
-
// - seenImportKeys is a Set used only for O(1) duplicate detection
|
|
254
|
-
const seenImportKeys = new Set<string>();
|
|
255
|
-
const uniqueImports: TypesImportSpec[] = [];
|
|
256
|
-
for (const imp of allImports) {
|
|
257
|
-
const key = `${imp.package}::${imp.named}`;
|
|
258
|
-
if (!seenImportKeys.has(key)) {
|
|
259
|
-
seenImportKeys.add(key);
|
|
260
|
-
uniqueImports.push(imp);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Generate import statements, omitting redundant "as Alias" when named === alias
|
|
265
|
-
const importLines = uniqueImports.map((imp) => {
|
|
266
|
-
// Simplify import when named === alias (e.g., `import type { Vector }` instead of `{ Vector as Vector }`)
|
|
267
|
-
const importClause = imp.named === imp.alias ? imp.named : `${imp.named} as ${imp.alias}`;
|
|
268
|
-
return `import type { ${importClause} } from '${imp.package}';`;
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
const codecTypes = codecTypeImports.map((imp) => imp.alias).join(' & ');
|
|
272
|
-
const operationTypes = operationTypeImports.map((imp) => imp.alias).join(' & ');
|
|
273
|
-
|
|
274
|
-
const storageType = this.generateStorageType(storage);
|
|
275
|
-
const modelsType = this.generateModelsType(models, storage, parameterizedRenderers);
|
|
276
|
-
const relationsType = this.generateRelationsType(ir.relations);
|
|
277
|
-
const mappingsType = this.generateMappingsType(models, storage, codecTypes, operationTypes);
|
|
278
|
-
|
|
279
|
-
return `// ⚠️ GENERATED FILE - DO NOT EDIT
|
|
280
|
-
// This file is automatically generated by 'prisma-next contract emit'.
|
|
281
|
-
// To regenerate, run: prisma-next contract emit
|
|
282
|
-
${importLines.join('\n')}
|
|
283
|
-
|
|
284
|
-
import type { SqlContract, SqlStorage, SqlMappings, ModelDefinition } from '@prisma-next/sql-contract/types';
|
|
285
|
-
|
|
286
|
-
export type CodecTypes = ${codecTypes || 'Record<string, never>'};
|
|
287
|
-
export type LaneCodecTypes = CodecTypes;
|
|
288
|
-
export type OperationTypes = ${operationTypes || 'Record<string, never>'};
|
|
289
|
-
|
|
290
|
-
export type Contract = SqlContract<
|
|
291
|
-
${storageType},
|
|
292
|
-
${modelsType},
|
|
293
|
-
${relationsType},
|
|
294
|
-
${mappingsType}
|
|
295
|
-
>;
|
|
296
|
-
|
|
297
|
-
export type Tables = Contract['storage']['tables'];
|
|
298
|
-
export type Models = Contract['models'];
|
|
299
|
-
export type Relations = Contract['relations'];
|
|
300
|
-
`;
|
|
301
|
-
},
|
|
302
|
-
|
|
303
|
-
generateStorageType(storage: SqlStorage): string {
|
|
203
|
+
generateStorageType(contract: Contract, storageHashTypeName: string): string {
|
|
204
|
+
const storage = contract.storage as unknown as SqlStorage;
|
|
304
205
|
const tables: string[] = [];
|
|
305
|
-
for (const [tableName, table] of Object.entries(storage.tables))
|
|
206
|
+
for (const [tableName, table] of Object.entries(storage.tables).sort(([a], [b]) =>
|
|
207
|
+
a.localeCompare(b),
|
|
208
|
+
)) {
|
|
306
209
|
const columns: string[] = [];
|
|
307
210
|
for (const [colName, col] of Object.entries(table.columns)) {
|
|
308
211
|
const nullable = col.nullable ? 'true' : 'false';
|
|
309
|
-
const nativeType =
|
|
310
|
-
const codecId =
|
|
212
|
+
const nativeType = serializeValue(col.nativeType);
|
|
213
|
+
const codecId = serializeValue(col.codecId);
|
|
214
|
+
const defaultSpec = col.default
|
|
215
|
+
? col.default.kind === 'literal'
|
|
216
|
+
? `; readonly default: { readonly kind: 'literal'; readonly value: DefaultLiteralValue<${codecId}, ${serializeValue(
|
|
217
|
+
col.default.value,
|
|
218
|
+
)}> }`
|
|
219
|
+
: `; readonly default: { readonly kind: 'function'; readonly expression: ${serializeValue(
|
|
220
|
+
col.default.expression,
|
|
221
|
+
)} }`
|
|
222
|
+
: '';
|
|
223
|
+
const typeParamsSpec =
|
|
224
|
+
col.typeParams && Object.keys(col.typeParams).length > 0
|
|
225
|
+
? `; readonly typeParams: ${serializeTypeParamsLiteral(col.typeParams)}`
|
|
226
|
+
: '';
|
|
227
|
+
const typeRefSpec = col.typeRef ? `; readonly typeRef: ${serializeValue(col.typeRef)}` : '';
|
|
311
228
|
columns.push(
|
|
312
|
-
`readonly ${colName}: { readonly nativeType: ${nativeType}; readonly codecId: ${codecId}; readonly nullable: ${nullable} }`,
|
|
229
|
+
`readonly ${colName}: { readonly nativeType: ${nativeType}; readonly codecId: ${codecId}; readonly nullable: ${nullable}${defaultSpec}${typeParamsSpec}${typeRefSpec} }`,
|
|
313
230
|
);
|
|
314
231
|
}
|
|
315
232
|
|
|
316
233
|
const tableParts: string[] = [`columns: { ${columns.join('; ')} }`];
|
|
317
234
|
|
|
318
235
|
if (table.primaryKey) {
|
|
319
|
-
const pkCols = table.primaryKey.columns.map((c) =>
|
|
320
|
-
const pkName = table.primaryKey.name
|
|
236
|
+
const pkCols = table.primaryKey.columns.map((c) => serializeValue(c)).join(', ');
|
|
237
|
+
const pkName = table.primaryKey.name
|
|
238
|
+
? `; readonly name: ${serializeValue(table.primaryKey.name)}`
|
|
239
|
+
: '';
|
|
321
240
|
tableParts.push(`primaryKey: { readonly columns: readonly [${pkCols}]${pkName} }`);
|
|
322
241
|
}
|
|
323
242
|
|
|
324
243
|
const uniques = table.uniques
|
|
325
244
|
.map((u) => {
|
|
326
|
-
const cols = u.columns.map((c: string) =>
|
|
327
|
-
const name = u.name ? `; readonly name:
|
|
245
|
+
const cols = u.columns.map((c: string) => serializeValue(c)).join(', ');
|
|
246
|
+
const name = u.name ? `; readonly name: ${serializeValue(u.name)}` : '';
|
|
328
247
|
return `{ readonly columns: readonly [${cols}]${name} }`;
|
|
329
248
|
})
|
|
330
249
|
.join(', ');
|
|
@@ -332,19 +251,22 @@ export const sqlTargetFamilyHook = {
|
|
|
332
251
|
|
|
333
252
|
const indexes = table.indexes
|
|
334
253
|
.map((i) => {
|
|
335
|
-
const cols = i.columns.map((c: string) =>
|
|
336
|
-
const name = i.name ? `; readonly name:
|
|
337
|
-
|
|
254
|
+
const cols = i.columns.map((c: string) => serializeValue(c)).join(', ');
|
|
255
|
+
const name = i.name ? `; readonly name: ${serializeValue(i.name)}` : '';
|
|
256
|
+
const using = i.using !== undefined ? `; readonly using: ${serializeValue(i.using)}` : '';
|
|
257
|
+
const config =
|
|
258
|
+
i.config !== undefined ? `; readonly config: ${serializeValue(i.config)}` : '';
|
|
259
|
+
return `{ readonly columns: readonly [${cols}]${name}${using}${config} }`;
|
|
338
260
|
})
|
|
339
261
|
.join(', ');
|
|
340
262
|
tableParts.push(`indexes: readonly [${indexes}]`);
|
|
341
263
|
|
|
342
264
|
const fks = table.foreignKeys
|
|
343
265
|
.map((fk) => {
|
|
344
|
-
const cols = fk.columns.map((c: string) =>
|
|
345
|
-
const refCols = fk.references.columns.map((c: string) =>
|
|
346
|
-
const name = fk.name ? `; readonly name:
|
|
347
|
-
return `{ readonly columns: readonly [${cols}]; readonly references: { readonly table:
|
|
266
|
+
const cols = fk.columns.map((c: string) => serializeValue(c)).join(', ');
|
|
267
|
+
const refCols = fk.references.columns.map((c: string) => serializeValue(c)).join(', ');
|
|
268
|
+
const name = fk.name ? `; readonly name: ${serializeValue(fk.name)}` : '';
|
|
269
|
+
return `{ readonly columns: readonly [${cols}]; readonly references: { readonly table: ${serializeValue(fk.references.table)}; readonly columns: readonly [${refCols}] }${name}; readonly constraint: ${fk.constraint}; readonly index: ${fk.index} }`;
|
|
348
270
|
})
|
|
349
271
|
.join(', ');
|
|
350
272
|
tableParts.push(`foreignKeys: readonly [${fks}]`);
|
|
@@ -352,296 +274,84 @@ export const sqlTargetFamilyHook = {
|
|
|
352
274
|
tables.push(`readonly ${tableName}: { ${tableParts.join('; ')} }`);
|
|
353
275
|
}
|
|
354
276
|
|
|
355
|
-
const typesType =
|
|
277
|
+
const typesType = generateStorageTypesType(storage.types);
|
|
356
278
|
|
|
357
|
-
return `{ readonly tables: { ${tables.join('; ')} }; readonly types: ${typesType} }`;
|
|
279
|
+
return `{ readonly tables: { ${tables.join('; ')} }; readonly types: ${typesType}; readonly storageHash: ${storageHashTypeName} }`;
|
|
358
280
|
},
|
|
359
281
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
typeEntries.push(
|
|
375
|
-
`readonly ${typeName}: { readonly codecId: ${codecId}; readonly nativeType: ${nativeType}; readonly typeParams: ${typeParamsStr} }`,
|
|
376
|
-
);
|
|
282
|
+
generateModelStorageType(_modelName: string, model: ContractModel): string {
|
|
283
|
+
const sqlModel = model as ContractModel<SqlModelStorage>;
|
|
284
|
+
const tableName = sqlModel.storage.table;
|
|
285
|
+
const storageFields = sqlModel.storage.fields;
|
|
286
|
+
|
|
287
|
+
const storageParts = [`readonly table: ${serializeValue(tableName)}`];
|
|
288
|
+
if (Object.keys(storageFields).length > 0) {
|
|
289
|
+
const fieldParts: string[] = [];
|
|
290
|
+
for (const [fieldName, field] of Object.entries(storageFields)) {
|
|
291
|
+
fieldParts.push(
|
|
292
|
+
`readonly ${serializeObjectKey(fieldName)}: { readonly column: ${serializeValue(field.column)} }`,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
storageParts.push(`readonly fields: { ${fieldParts.join('; ')} }`);
|
|
377
296
|
}
|
|
378
297
|
|
|
379
|
-
return `{ ${
|
|
298
|
+
return `{ ${storageParts.join('; ')} }`;
|
|
380
299
|
},
|
|
381
300
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
const entries: string[] = [];
|
|
392
|
-
for (const [key, value] of Object.entries(params)) {
|
|
393
|
-
const serialized = this.serializeValue(value);
|
|
394
|
-
entries.push(`readonly ${key}: ${serialized}`);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
return `{ ${entries.join('; ')} }`;
|
|
301
|
+
getFamilyImports(): string[] {
|
|
302
|
+
return [
|
|
303
|
+
'import type {',
|
|
304
|
+
' ContractWithTypeMaps,',
|
|
305
|
+
' TypeMaps as TypeMapsType,',
|
|
306
|
+
"} from '@prisma-next/sql-contract/types';",
|
|
307
|
+
];
|
|
398
308
|
},
|
|
399
309
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
416
|
-
return String(value);
|
|
417
|
-
}
|
|
418
|
-
if (typeof value === 'bigint') {
|
|
419
|
-
return `${value}n`;
|
|
420
|
-
}
|
|
421
|
-
if (Array.isArray(value)) {
|
|
422
|
-
const items = value.map((v) => this.serializeValue(v)).join(', ');
|
|
423
|
-
return `readonly [${items}]`;
|
|
424
|
-
}
|
|
425
|
-
if (typeof value === 'object') {
|
|
426
|
-
const entries: string[] = [];
|
|
427
|
-
for (const [k, v] of Object.entries(value)) {
|
|
428
|
-
entries.push(`readonly ${k}: ${this.serializeValue(v)}`);
|
|
429
|
-
}
|
|
430
|
-
return `{ ${entries.join('; ')} }`;
|
|
431
|
-
}
|
|
432
|
-
return 'unknown';
|
|
310
|
+
getFamilyTypeAliases(options?: GenerateContractTypesOptions): string {
|
|
311
|
+
const queryOperationTypeImports = options?.queryOperationTypeImports ?? [];
|
|
312
|
+
const queryOperationTypes = generateCodecTypeIntersection(
|
|
313
|
+
queryOperationTypeImports,
|
|
314
|
+
'QueryOperationTypes',
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
return [
|
|
318
|
+
'export type LaneCodecTypes = CodecTypes;',
|
|
319
|
+
`export type QueryOperationTypes = ${queryOperationTypes};`,
|
|
320
|
+
'type DefaultLiteralValue<CodecId extends string, _Encoded> =',
|
|
321
|
+
' CodecId extends keyof CodecTypes',
|
|
322
|
+
" ? CodecTypes[CodecId]['output']",
|
|
323
|
+
' : _Encoded;',
|
|
324
|
+
].join('\n');
|
|
433
325
|
},
|
|
434
326
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
storage: SqlStorage,
|
|
438
|
-
parameterizedRenderers?: Map<string, TypeRenderEntry>,
|
|
439
|
-
): string {
|
|
440
|
-
if (!models) {
|
|
441
|
-
return 'Record<string, never>';
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
const renderCtx: TypeRenderContext = { codecTypesName: 'CodecTypes' };
|
|
445
|
-
|
|
446
|
-
const modelTypes: string[] = [];
|
|
447
|
-
for (const [modelName, model] of Object.entries(models)) {
|
|
448
|
-
const fields: string[] = [];
|
|
449
|
-
const tableName = model.storage.table;
|
|
450
|
-
const table = storage.tables[tableName];
|
|
451
|
-
|
|
452
|
-
if (table) {
|
|
453
|
-
for (const [fieldName, field] of Object.entries(model.fields)) {
|
|
454
|
-
const column = table.columns[field.column];
|
|
455
|
-
if (!column) {
|
|
456
|
-
fields.push(`readonly ${fieldName}: { readonly column: '${field.column}' }`);
|
|
457
|
-
continue;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
const jsType = this.generateColumnType(
|
|
461
|
-
column,
|
|
462
|
-
storage,
|
|
463
|
-
parameterizedRenderers,
|
|
464
|
-
renderCtx,
|
|
465
|
-
);
|
|
466
|
-
fields.push(`readonly ${fieldName}: ${jsType}`);
|
|
467
|
-
}
|
|
468
|
-
} else {
|
|
469
|
-
for (const [fieldName, field] of Object.entries(model.fields)) {
|
|
470
|
-
fields.push(`readonly ${fieldName}: { readonly column: '${field.column}' }`);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
const relations: string[] = [];
|
|
475
|
-
for (const [relName, rel] of Object.entries(model.relations)) {
|
|
476
|
-
if (typeof rel === 'object' && rel !== null && 'on' in rel) {
|
|
477
|
-
const on = rel.on as { parentCols?: string[]; childCols?: string[] };
|
|
478
|
-
if (on.parentCols && on.childCols) {
|
|
479
|
-
const parentCols = on.parentCols.map((c) => `'${c}'`).join(', ');
|
|
480
|
-
const childCols = on.childCols.map((c) => `'${c}'`).join(', ');
|
|
481
|
-
relations.push(
|
|
482
|
-
`readonly ${relName}: { readonly on: { readonly parentCols: readonly [${parentCols}]; readonly childCols: readonly [${childCols}] } }`,
|
|
483
|
-
);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
const modelParts: string[] = [
|
|
489
|
-
`storage: { readonly table: '${tableName}' }`,
|
|
490
|
-
`fields: { ${fields.join('; ')} }`,
|
|
491
|
-
];
|
|
492
|
-
|
|
493
|
-
if (relations.length > 0) {
|
|
494
|
-
modelParts.push(`relations: { ${relations.join('; ')} }`);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
modelTypes.push(`readonly ${modelName}: { ${modelParts.join('; ')} }`);
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
return `{ ${modelTypes.join('; ')} }`;
|
|
327
|
+
getTypeMapsExpression(): string {
|
|
328
|
+
return 'TypeMapsType<CodecTypes, OperationTypes, QueryOperationTypes, FieldOutputTypes, FieldInputTypes>';
|
|
501
329
|
},
|
|
502
330
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
storage: SqlStorage,
|
|
511
|
-
parameterizedRenderers: Map<string, TypeRenderEntry> | undefined,
|
|
512
|
-
renderCtx: TypeRenderContext,
|
|
513
|
-
): string {
|
|
514
|
-
const typeParams = resolveColumnTypeParams(column, storage);
|
|
515
|
-
const nullable = column.nullable ?? false;
|
|
516
|
-
const fallbackType = `CodecTypes['${column.codecId}']['output']`;
|
|
517
|
-
const renderer = typeParams && parameterizedRenderers?.get(column.codecId);
|
|
518
|
-
const baseType = renderer ? renderer.render(typeParams, renderCtx) : fallbackType;
|
|
519
|
-
|
|
520
|
-
return nullable ? `${baseType} | null` : baseType;
|
|
331
|
+
getContractWrapper(contractBaseName: string, typeMapsName: string): string {
|
|
332
|
+
return [
|
|
333
|
+
`export type Contract = ContractWithTypeMaps<${contractBaseName}, ${typeMapsName}>;`,
|
|
334
|
+
'',
|
|
335
|
+
"export type Tables = Contract['storage']['tables'];",
|
|
336
|
+
"export type Models = Contract['models'];",
|
|
337
|
+
].join('\n');
|
|
521
338
|
},
|
|
339
|
+
} as const;
|
|
522
340
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
const tableEntries: string[] = [];
|
|
529
|
-
for (const [tableName, relsValue] of Object.entries(relations)) {
|
|
530
|
-
if (typeof relsValue !== 'object' || relsValue === null) {
|
|
531
|
-
continue;
|
|
532
|
-
}
|
|
533
|
-
const rels = relsValue as Record<string, unknown>;
|
|
534
|
-
const relationEntries: string[] = [];
|
|
535
|
-
for (const [relName, relValue] of Object.entries(rels)) {
|
|
536
|
-
if (typeof relValue !== 'object' || relValue === null) {
|
|
537
|
-
relationEntries.push(`readonly ${relName}: unknown`);
|
|
538
|
-
continue;
|
|
539
|
-
}
|
|
540
|
-
const { to, cardinality, on, through } = relValue as {
|
|
541
|
-
readonly to?: string;
|
|
542
|
-
readonly cardinality?: string;
|
|
543
|
-
readonly on?: {
|
|
544
|
-
readonly parentCols?: readonly string[];
|
|
545
|
-
readonly childCols?: readonly string[];
|
|
546
|
-
};
|
|
547
|
-
readonly through?: {
|
|
548
|
-
readonly table: string;
|
|
549
|
-
readonly parentCols: readonly string[];
|
|
550
|
-
readonly childCols: readonly string[];
|
|
551
|
-
};
|
|
552
|
-
};
|
|
553
|
-
|
|
554
|
-
const parts: string[] = [];
|
|
555
|
-
if (to) {
|
|
556
|
-
parts.push(`readonly to: '${to}'`);
|
|
557
|
-
}
|
|
558
|
-
if (cardinality) {
|
|
559
|
-
parts.push(`readonly cardinality: '${cardinality}'`);
|
|
560
|
-
}
|
|
561
|
-
if (on?.parentCols && on.childCols) {
|
|
562
|
-
const parentCols = on.parentCols.map((c) => `'${c}'`).join(', ');
|
|
563
|
-
const childCols = on.childCols.map((c) => `'${c}'`).join(', ');
|
|
564
|
-
parts.push(
|
|
565
|
-
`readonly on: { readonly parentCols: readonly [${parentCols}]; readonly childCols: readonly [${childCols}] }`,
|
|
566
|
-
);
|
|
567
|
-
}
|
|
568
|
-
if (through) {
|
|
569
|
-
const parentCols = through.parentCols.map((c) => `'${c}'`).join(', ');
|
|
570
|
-
const childCols = through.childCols.map((c) => `'${c}'`).join(', ');
|
|
571
|
-
parts.push(
|
|
572
|
-
`readonly through: { readonly table: '${through.table}'; readonly parentCols: readonly [${parentCols}]; readonly childCols: readonly [${childCols}] }`,
|
|
573
|
-
);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
relationEntries.push(
|
|
577
|
-
parts.length > 0
|
|
578
|
-
? `readonly ${relName}: { ${parts.join('; ')} }`
|
|
579
|
-
: `readonly ${relName}: unknown`,
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
tableEntries.push(`readonly ${tableName}: { ${relationEntries.join('; ')} }`);
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
return `{ ${tableEntries.join('; ')} }`;
|
|
586
|
-
},
|
|
587
|
-
|
|
588
|
-
generateMappingsType(
|
|
589
|
-
models: Record<string, ModelDefinition> | undefined,
|
|
590
|
-
storage: SqlStorage,
|
|
591
|
-
codecTypes: string,
|
|
592
|
-
operationTypes: string,
|
|
593
|
-
): string {
|
|
594
|
-
if (!models) {
|
|
595
|
-
return `SqlMappings & { readonly codecTypes: ${codecTypes || 'Record<string, never>'}; readonly operationTypes: ${operationTypes || 'Record<string, never>'}; }`;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
const modelToTable: string[] = [];
|
|
599
|
-
const tableToModel: string[] = [];
|
|
600
|
-
const fieldToColumn: string[] = [];
|
|
601
|
-
const columnToField: string[] = [];
|
|
602
|
-
|
|
603
|
-
for (const [modelName, model] of Object.entries(models)) {
|
|
604
|
-
const tableName = model.storage.table;
|
|
605
|
-
modelToTable.push(`readonly ${modelName}: '${tableName}'`);
|
|
606
|
-
tableToModel.push(`readonly ${tableName}: '${modelName}'`);
|
|
607
|
-
|
|
608
|
-
const fieldMap: string[] = [];
|
|
609
|
-
for (const [fieldName, field] of Object.entries(model.fields)) {
|
|
610
|
-
fieldMap.push(`readonly ${fieldName}: '${field.column}'`);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
if (fieldMap.length > 0) {
|
|
614
|
-
fieldToColumn.push(`readonly ${modelName}: { ${fieldMap.join('; ')} }`);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
if (storage.tables[tableName]) {
|
|
618
|
-
const colMap: string[] = [];
|
|
619
|
-
for (const [fieldName, field] of Object.entries(model.fields)) {
|
|
620
|
-
colMap.push(`readonly ${field.column}: '${fieldName}'`);
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
if (colMap.length > 0) {
|
|
624
|
-
columnToField.push(`readonly ${tableName}: { ${colMap.join('; ')} }`);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
}
|
|
341
|
+
function generateStorageTypesType(types: SqlStorage['types']): string {
|
|
342
|
+
if (!types || Object.keys(types).length === 0) {
|
|
343
|
+
return 'Record<string, never>';
|
|
344
|
+
}
|
|
628
345
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
639
|
-
if (columnToField.length > 0) {
|
|
640
|
-
parts.push(`columnToField: { ${columnToField.join('; ')} }`);
|
|
641
|
-
}
|
|
642
|
-
parts.push(`codecTypes: ${codecTypes || 'Record<string, never>'}`);
|
|
643
|
-
parts.push(`operationTypes: ${operationTypes || 'Record<string, never>'}`);
|
|
346
|
+
const typeEntries: string[] = [];
|
|
347
|
+
for (const [typeName, typeInstance] of Object.entries(types)) {
|
|
348
|
+
const codecId = serializeValue(typeInstance.codecId);
|
|
349
|
+
const nativeType = serializeValue(typeInstance.nativeType);
|
|
350
|
+
const typeParamsStr = serializeTypeParamsLiteral(typeInstance.typeParams);
|
|
351
|
+
typeEntries.push(
|
|
352
|
+
`readonly ${typeName}: { readonly codecId: ${codecId}; readonly nativeType: ${nativeType}; readonly typeParams: ${typeParamsStr} }`,
|
|
353
|
+
);
|
|
354
|
+
}
|
|
644
355
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
} as const;
|
|
356
|
+
return `{ ${typeEntries.join('; ')} }`;
|
|
357
|
+
}
|