@prisma-next/sql-contract-emitter 0.3.0-dev.9 → 0.3.0-pr.100.2
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/dist/index.d.ts +24 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +144 -34
- package/dist/index.js.map +1 -1
- package/package.json +11 -10
- package/src/index.ts +210 -40
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
import type { ContractIR } from '@prisma-next/contract/ir';
|
|
2
|
-
import type { TypesImportSpec, ValidationContext } from '@prisma-next/contract/types';
|
|
3
|
-
import type { ModelDefinition, SqlStorage } from '@prisma-next/sql-contract/types';
|
|
2
|
+
import type { GenerateContractTypesOptions, TypeRenderContext, TypeRenderEntry, TypesImportSpec, ValidationContext } from '@prisma-next/contract/types';
|
|
3
|
+
import type { ModelDefinition, SqlStorage, StorageColumn } from '@prisma-next/sql-contract/types';
|
|
4
4
|
export declare const sqlTargetFamilyHook: {
|
|
5
5
|
readonly id: "sql";
|
|
6
6
|
readonly validateTypes: (ir: ContractIR, _ctx: ValidationContext) => void;
|
|
7
7
|
readonly validateStructure: (ir: ContractIR) => void;
|
|
8
|
-
readonly generateContractTypes: (ir: ContractIR, codecTypeImports: ReadonlyArray<TypesImportSpec>, operationTypeImports: ReadonlyArray<TypesImportSpec
|
|
8
|
+
readonly generateContractTypes: (ir: ContractIR, codecTypeImports: ReadonlyArray<TypesImportSpec>, operationTypeImports: ReadonlyArray<TypesImportSpec>, options?: GenerateContractTypesOptions) => string;
|
|
9
9
|
readonly generateStorageType: (storage: SqlStorage) => string;
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Generates the TypeScript type for storage.types with literal types.
|
|
12
|
+
* This preserves type params as literal values for precise typing.
|
|
13
|
+
*/
|
|
14
|
+
readonly generateStorageTypesType: (types: SqlStorage["types"]) => string;
|
|
15
|
+
/**
|
|
16
|
+
* Serializes a typeParams object to a TypeScript literal type.
|
|
17
|
+
* Converts { length: 1536 } to "{ readonly length: 1536 }".
|
|
18
|
+
*/
|
|
19
|
+
readonly serializeTypeParamsLiteral: (params: Record<string, unknown>) => string;
|
|
20
|
+
/**
|
|
21
|
+
* Serializes a value to a TypeScript literal type expression.
|
|
22
|
+
*/
|
|
23
|
+
readonly serializeValue: (value: unknown) => string;
|
|
24
|
+
readonly generateModelsType: (models: Record<string, ModelDefinition> | undefined, storage: SqlStorage, parameterizedRenderers?: Map<string, TypeRenderEntry>) => string;
|
|
25
|
+
/**
|
|
26
|
+
* Generates the TypeScript type expression for a column.
|
|
27
|
+
* Uses parameterized renderer if the column has typeParams and a matching renderer exists,
|
|
28
|
+
* otherwise falls back to CodecTypes[codecId]['output'].
|
|
29
|
+
*/
|
|
30
|
+
readonly generateColumnType: (column: StorageColumn, storage: SqlStorage, parameterizedRenderers: Map<string, TypeRenderEntry> | undefined, renderCtx: TypeRenderContext) => string;
|
|
11
31
|
readonly generateRelationsType: (relations: Record<string, unknown> | undefined) => string;
|
|
12
32
|
readonly generateMappingsType: (models: Record<string, ModelDefinition> | undefined, storage: SqlStorage, codecTypes: string, operationTypes: string) => string;
|
|
13
33
|
};
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,KAAK,EACV,4BAA4B,EAC5B,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,iBAAiB,EAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EACV,eAAe,EAEf,UAAU,EACV,aAAa,EAGd,MAAM,iCAAiC,CAAC;AAyBzC,eAAO,MAAM,mBAAmB;;iCAGZ,UAAU,QAAQ,iBAAiB,KAAG,IAAI;qCA6BtC,UAAU,KAAG,IAAI;yCAyJjC,UAAU,oBACI,aAAa,CAAC,eAAe,CAAC,wBAC1B,aAAa,CAAC,eAAe,CAAC,YAC1C,4BAA4B,KACrC,MAAM;4CAiFoB,UAAU,KAAG,MAAM;IAyDhD;;;OAGG;+CAC6B,UAAU,CAAC,OAAO,CAAC,KAAG,MAAM;IAkB5D;;;OAGG;kDACgC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,MAAM;IAcnE;;OAEG;qCACmB,OAAO,KAAG,MAAM;0CAiC5B,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,SAAS,WAC1C,UAAU,2BACM,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,KACpD,MAAM;IAgET;;;;OAIG;0CAEO,aAAa,WACZ,UAAU,0BACK,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,SAAS,aACrD,iBAAiB,KAC3B,MAAM;gDAUwB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,KAAG,MAAM;4CAkEnE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,SAAS,WAC1C,UAAU,cACP,MAAM,kBACF,MAAM,KACrB,MAAM;CAsDD,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
import { assertDefined } from "@prisma-next/utils/assertions";
|
|
3
|
+
function resolveColumnTypeParams(column, storage) {
|
|
4
|
+
if (column.typeParams && Object.keys(column.typeParams).length > 0) {
|
|
5
|
+
return column.typeParams;
|
|
6
|
+
}
|
|
7
|
+
if (column.typeRef && storage.types) {
|
|
8
|
+
const typeInstance = storage.types[column.typeRef];
|
|
9
|
+
if (typeInstance?.typeParams) {
|
|
10
|
+
return typeInstance.typeParams;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return void 0;
|
|
14
|
+
}
|
|
2
15
|
var sqlTargetFamilyHook = {
|
|
3
16
|
id: "sql",
|
|
4
17
|
validateTypes(ir, _ctx) {
|
|
@@ -45,9 +58,7 @@ var sqlTargetFamilyHook = {
|
|
|
45
58
|
throw new Error(`Model "${modelName}" references non-existent table "${tableName}"`);
|
|
46
59
|
}
|
|
47
60
|
const table = storage.tables[tableName];
|
|
48
|
-
|
|
49
|
-
throw new Error(`Model "${modelName}" references non-existent table "${tableName}"`);
|
|
50
|
-
}
|
|
61
|
+
assertDefined(table, `Model "${modelName}" references non-existent table "${tableName}"`);
|
|
51
62
|
if (!table.primaryKey) {
|
|
52
63
|
throw new Error(`Model "${modelName}" table "${tableName}" is missing a primary key`);
|
|
53
64
|
}
|
|
@@ -132,11 +143,10 @@ var sqlTargetFamilyHook = {
|
|
|
132
143
|
);
|
|
133
144
|
}
|
|
134
145
|
const referencedTable = storage.tables[fk.references.table];
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
146
|
+
assertDefined(
|
|
147
|
+
referencedTable,
|
|
148
|
+
`Table "${tableName}" foreignKey references non-existent table "${fk.references.table}"`
|
|
149
|
+
);
|
|
140
150
|
const referencedColumnNames = new Set(Object.keys(referencedTable.columns));
|
|
141
151
|
for (const colName of fk.references.columns) {
|
|
142
152
|
if (!referencedColumnNames.has(colName)) {
|
|
@@ -153,41 +163,56 @@ var sqlTargetFamilyHook = {
|
|
|
153
163
|
}
|
|
154
164
|
}
|
|
155
165
|
},
|
|
156
|
-
generateContractTypes(ir, codecTypeImports, operationTypeImports) {
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
(imp) => `import type { ${imp.named} as ${imp.alias} } from '${imp.package}';`
|
|
160
|
-
);
|
|
161
|
-
const codecTypes = codecTypeImports.map((imp) => imp.alias).join(" & ");
|
|
162
|
-
const operationTypes = operationTypeImports.map((imp) => imp.alias).join(" & ");
|
|
166
|
+
generateContractTypes(ir, codecTypeImports, operationTypeImports, options) {
|
|
167
|
+
const parameterizedRenderers = options?.parameterizedRenderers;
|
|
168
|
+
const parameterizedTypeImports = options?.parameterizedTypeImports;
|
|
163
169
|
const storage = ir.storage;
|
|
164
170
|
const models = ir.models;
|
|
171
|
+
const allImports = [...codecTypeImports, ...operationTypeImports];
|
|
172
|
+
if (parameterizedTypeImports) {
|
|
173
|
+
allImports.push(...parameterizedTypeImports);
|
|
174
|
+
}
|
|
175
|
+
const seenImportKeys = /* @__PURE__ */ new Set();
|
|
176
|
+
const uniqueImports = [];
|
|
177
|
+
for (const imp of allImports) {
|
|
178
|
+
const key = `${imp.package}::${imp.named}`;
|
|
179
|
+
if (!seenImportKeys.has(key)) {
|
|
180
|
+
seenImportKeys.add(key);
|
|
181
|
+
uniqueImports.push(imp);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const importLines = uniqueImports.map((imp) => {
|
|
185
|
+
const importClause = imp.named === imp.alias ? imp.named : `${imp.named} as ${imp.alias}`;
|
|
186
|
+
return `import type { ${importClause} } from '${imp.package}';`;
|
|
187
|
+
});
|
|
188
|
+
const codecTypes = codecTypeImports.filter((imp) => imp.named === "CodecTypes").map((imp) => imp.alias).join(" & ");
|
|
189
|
+
const operationTypes = operationTypeImports.filter((imp) => imp.named === "OperationTypes").map((imp) => imp.alias).join(" & ");
|
|
165
190
|
const storageType = this.generateStorageType(storage);
|
|
166
|
-
const modelsType = this.generateModelsType(models, storage);
|
|
191
|
+
const modelsType = this.generateModelsType(models, storage, parameterizedRenderers);
|
|
167
192
|
const relationsType = this.generateRelationsType(ir.relations);
|
|
168
193
|
const mappingsType = this.generateMappingsType(models, storage, codecTypes, operationTypes);
|
|
169
194
|
return `// \u26A0\uFE0F GENERATED FILE - DO NOT EDIT
|
|
170
|
-
// This file is automatically generated by 'prisma-next emit'.
|
|
171
|
-
// To regenerate, run: prisma-next emit
|
|
172
|
-
${importLines.join("\n")}
|
|
195
|
+
// This file is automatically generated by 'prisma-next contract emit'.
|
|
196
|
+
// To regenerate, run: prisma-next contract emit
|
|
197
|
+
${importLines.join("\n")}
|
|
173
198
|
|
|
174
|
-
import type { SqlContract, SqlStorage, SqlMappings, ModelDefinition } from '@prisma-next/sql-contract/types';
|
|
199
|
+
import type { SqlContract, SqlStorage, SqlMappings, ModelDefinition } from '@prisma-next/sql-contract/types';
|
|
175
200
|
|
|
176
|
-
export type CodecTypes = ${codecTypes || "Record<string, never>"};
|
|
177
|
-
export type LaneCodecTypes = CodecTypes;
|
|
178
|
-
export type OperationTypes = ${operationTypes || "Record<string, never>"};
|
|
201
|
+
export type CodecTypes = ${codecTypes || "Record<string, never>"};
|
|
202
|
+
export type LaneCodecTypes = CodecTypes;
|
|
203
|
+
export type OperationTypes = ${operationTypes || "Record<string, never>"};
|
|
179
204
|
|
|
180
|
-
export type Contract = SqlContract<
|
|
205
|
+
export type Contract = SqlContract<
|
|
181
206
|
${storageType},
|
|
182
207
|
${modelsType},
|
|
183
208
|
${relationsType},
|
|
184
209
|
${mappingsType}
|
|
185
|
-
>;
|
|
210
|
+
>;
|
|
186
211
|
|
|
187
|
-
export type Tables = Contract['storage']['tables'];
|
|
188
|
-
export type Models = Contract['models'];
|
|
189
|
-
export type Relations = Contract['relations'];
|
|
190
|
-
`;
|
|
212
|
+
export type Tables = Contract['storage']['tables'];
|
|
213
|
+
export type Models = Contract['models'];
|
|
214
|
+
export type Relations = Contract['relations'];
|
|
215
|
+
`;
|
|
191
216
|
},
|
|
192
217
|
generateStorageType(storage) {
|
|
193
218
|
const tables = [];
|
|
@@ -228,12 +253,81 @@ export type Relations = Contract['relations'];
|
|
|
228
253
|
tableParts.push(`foreignKeys: readonly [${fks}]`);
|
|
229
254
|
tables.push(`readonly ${tableName}: { ${tableParts.join("; ")} }`);
|
|
230
255
|
}
|
|
231
|
-
|
|
256
|
+
const typesType = this.generateStorageTypesType(storage.types);
|
|
257
|
+
return `{ readonly tables: { ${tables.join("; ")} }; readonly types: ${typesType} }`;
|
|
258
|
+
},
|
|
259
|
+
/**
|
|
260
|
+
* Generates the TypeScript type for storage.types with literal types.
|
|
261
|
+
* This preserves type params as literal values for precise typing.
|
|
262
|
+
*/
|
|
263
|
+
generateStorageTypesType(types) {
|
|
264
|
+
if (!types || Object.keys(types).length === 0) {
|
|
265
|
+
return "Record<string, never>";
|
|
266
|
+
}
|
|
267
|
+
const typeEntries = [];
|
|
268
|
+
for (const [typeName, typeInstance] of Object.entries(types)) {
|
|
269
|
+
const codecId = `'${typeInstance.codecId}'`;
|
|
270
|
+
const nativeType = `'${typeInstance.nativeType}'`;
|
|
271
|
+
const typeParamsStr = this.serializeTypeParamsLiteral(typeInstance.typeParams);
|
|
272
|
+
typeEntries.push(
|
|
273
|
+
`readonly ${typeName}: { readonly codecId: ${codecId}; readonly nativeType: ${nativeType}; readonly typeParams: ${typeParamsStr} }`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
return `{ ${typeEntries.join("; ")} }`;
|
|
232
277
|
},
|
|
233
|
-
|
|
278
|
+
/**
|
|
279
|
+
* Serializes a typeParams object to a TypeScript literal type.
|
|
280
|
+
* Converts { length: 1536 } to "{ readonly length: 1536 }".
|
|
281
|
+
*/
|
|
282
|
+
serializeTypeParamsLiteral(params) {
|
|
283
|
+
if (!params || Object.keys(params).length === 0) {
|
|
284
|
+
return "Record<string, never>";
|
|
285
|
+
}
|
|
286
|
+
const entries = [];
|
|
287
|
+
for (const [key, value] of Object.entries(params)) {
|
|
288
|
+
const serialized = this.serializeValue(value);
|
|
289
|
+
entries.push(`readonly ${key}: ${serialized}`);
|
|
290
|
+
}
|
|
291
|
+
return `{ ${entries.join("; ")} }`;
|
|
292
|
+
},
|
|
293
|
+
/**
|
|
294
|
+
* Serializes a value to a TypeScript literal type expression.
|
|
295
|
+
*/
|
|
296
|
+
serializeValue(value) {
|
|
297
|
+
if (value === null) {
|
|
298
|
+
return "null";
|
|
299
|
+
}
|
|
300
|
+
if (value === void 0) {
|
|
301
|
+
return "undefined";
|
|
302
|
+
}
|
|
303
|
+
if (typeof value === "string") {
|
|
304
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
305
|
+
return `'${escaped}'`;
|
|
306
|
+
}
|
|
307
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
308
|
+
return String(value);
|
|
309
|
+
}
|
|
310
|
+
if (typeof value === "bigint") {
|
|
311
|
+
return `${value}n`;
|
|
312
|
+
}
|
|
313
|
+
if (Array.isArray(value)) {
|
|
314
|
+
const items = value.map((v) => this.serializeValue(v)).join(", ");
|
|
315
|
+
return `readonly [${items}]`;
|
|
316
|
+
}
|
|
317
|
+
if (typeof value === "object") {
|
|
318
|
+
const entries = [];
|
|
319
|
+
for (const [k, v] of Object.entries(value)) {
|
|
320
|
+
entries.push(`readonly ${k}: ${this.serializeValue(v)}`);
|
|
321
|
+
}
|
|
322
|
+
return `{ ${entries.join("; ")} }`;
|
|
323
|
+
}
|
|
324
|
+
return "unknown";
|
|
325
|
+
},
|
|
326
|
+
generateModelsType(models, storage, parameterizedRenderers) {
|
|
234
327
|
if (!models) {
|
|
235
328
|
return "Record<string, never>";
|
|
236
329
|
}
|
|
330
|
+
const renderCtx = { codecTypesName: "CodecTypes" };
|
|
237
331
|
const modelTypes = [];
|
|
238
332
|
for (const [modelName, model] of Object.entries(models)) {
|
|
239
333
|
const fields = [];
|
|
@@ -246,9 +340,12 @@ export type Relations = Contract['relations'];
|
|
|
246
340
|
fields.push(`readonly ${fieldName}: { readonly column: '${field.column}' }`);
|
|
247
341
|
continue;
|
|
248
342
|
}
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
343
|
+
const jsType = this.generateColumnType(
|
|
344
|
+
column,
|
|
345
|
+
storage,
|
|
346
|
+
parameterizedRenderers,
|
|
347
|
+
renderCtx
|
|
348
|
+
);
|
|
252
349
|
fields.push(`readonly ${fieldName}: ${jsType}`);
|
|
253
350
|
}
|
|
254
351
|
} else {
|
|
@@ -280,6 +377,19 @@ export type Relations = Contract['relations'];
|
|
|
280
377
|
}
|
|
281
378
|
return `{ ${modelTypes.join("; ")} }`;
|
|
282
379
|
},
|
|
380
|
+
/**
|
|
381
|
+
* Generates the TypeScript type expression for a column.
|
|
382
|
+
* Uses parameterized renderer if the column has typeParams and a matching renderer exists,
|
|
383
|
+
* otherwise falls back to CodecTypes[codecId]['output'].
|
|
384
|
+
*/
|
|
385
|
+
generateColumnType(column, storage, parameterizedRenderers, renderCtx) {
|
|
386
|
+
const typeParams = resolveColumnTypeParams(column, storage);
|
|
387
|
+
const nullable = column.nullable ?? false;
|
|
388
|
+
const fallbackType = `CodecTypes['${column.codecId}']['output']`;
|
|
389
|
+
const renderer = typeParams && parameterizedRenderers?.get(column.codecId);
|
|
390
|
+
const baseType = renderer ? renderer.render(typeParams, renderCtx) : fallbackType;
|
|
391
|
+
return nullable ? `${baseType} | null` : baseType;
|
|
392
|
+
},
|
|
283
393
|
generateRelationsType(relations) {
|
|
284
394
|
if (!relations || Object.keys(relations).length === 0) {
|
|
285
395
|
return "Record<string, never>";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { ContractIR } from '@prisma-next/contract/ir';\nimport type { TypesImportSpec, ValidationContext } from '@prisma-next/contract/types';\nimport type {\n ModelDefinition,\n ModelField,\n SqlStorage,\n StorageTable,\n} from '@prisma-next/sql-contract/types';\n\nexport const sqlTargetFamilyHook = {\n id: 'sql',\n\n validateTypes(ir: ContractIR, _ctx: ValidationContext): void {\n const storage = ir.storage as SqlStorage | undefined;\n if (!storage || !storage.tables) {\n return;\n }\n\n // 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.\n\n const typeIdRegex = /^([^/]+)\\/([^@]+)@(\\d+)$/;\n\n for (const [tableName, tableUnknown] of Object.entries(storage.tables)) {\n const table = tableUnknown as StorageTable;\n for (const [colName, colUnknown] of Object.entries(table.columns)) {\n const col = colUnknown as { codecId?: string };\n const codecId = col.codecId;\n if (!codecId) {\n throw new Error(`Column \"${colName}\" in table \"${tableName}\" is missing codecId`);\n }\n\n const match = codecId.match(typeIdRegex);\n if (!match || !match[1]) {\n throw new Error(\n `Column \"${colName}\" in table \"${tableName}\" has invalid codec ID format \"${codecId}\". Expected format: ns/name@version`,\n );\n }\n }\n }\n },\n\n validateStructure(ir: ContractIR): void {\n if (ir.targetFamily !== 'sql') {\n throw new Error(`Expected targetFamily \"sql\", got \"${ir.targetFamily}\"`);\n }\n\n const storage = ir.storage as SqlStorage | undefined;\n if (!storage || !storage.tables) {\n throw new Error('SQL contract must have storage.tables');\n }\n\n const models = ir.models as Record<string, ModelDefinition> | undefined;\n const tableNames = new Set(Object.keys(storage.tables));\n\n if (models) {\n for (const [modelName, modelUnknown] of Object.entries(models)) {\n const model = modelUnknown as ModelDefinition;\n if (!model.storage?.table) {\n throw new Error(`Model \"${modelName}\" is missing storage.table`);\n }\n\n const tableName = model.storage.table;\n if (!tableNames.has(tableName)) {\n throw new Error(`Model \"${modelName}\" references non-existent table \"${tableName}\"`);\n }\n\n const table = storage.tables[tableName];\n if (!table) {\n throw new Error(`Model \"${modelName}\" references non-existent table \"${tableName}\"`);\n }\n\n if (!table.primaryKey) {\n throw new Error(`Model \"${modelName}\" table \"${tableName}\" is missing a primary key`);\n }\n\n const columnNames = new Set(Object.keys(table.columns));\n if (!model.fields || Object.keys(model.fields).length === 0) {\n throw new Error(`Model \"${modelName}\" is missing fields`);\n }\n\n for (const [fieldName, fieldUnknown] of Object.entries(model.fields)) {\n const field = fieldUnknown as ModelField;\n if (!field.column) {\n throw new Error(`Model \"${modelName}\" field \"${fieldName}\" is missing column property`);\n }\n\n if (!columnNames.has(field.column)) {\n throw new Error(\n `Model \"${modelName}\" field \"${fieldName}\" references non-existent column \"${field.column}\" in table \"${tableName}\"`,\n );\n }\n }\n\n if (!model.relations || typeof model.relations !== 'object') {\n throw new Error(\n `Model \"${modelName}\" is missing required field \"relations\" (must be an object)`,\n );\n }\n }\n }\n\n for (const [tableName, tableUnknown] of Object.entries(storage.tables)) {\n const table = tableUnknown as StorageTable;\n const columnNames = new Set(Object.keys(table.columns));\n\n // Column structure (nullable, nativeType, codecId) and table arrays (uniques, indexes, foreignKeys)\n // are validated by Arktype schema validation - no need to re-check here.\n // We only validate logical consistency (foreign key references, model references, etc.)\n\n if (!Array.isArray(table.uniques)) {\n throw new Error(\n `Table \"${tableName}\" is missing required field \"uniques\" (must be an array)`,\n );\n }\n if (!Array.isArray(table.indexes)) {\n throw new Error(\n `Table \"${tableName}\" is missing required field \"indexes\" (must be an array)`,\n );\n }\n if (!Array.isArray(table.foreignKeys)) {\n throw new Error(\n `Table \"${tableName}\" is missing required field \"foreignKeys\" (must be an array)`,\n );\n }\n\n if (table.primaryKey) {\n for (const colName of table.primaryKey.columns) {\n if (!columnNames.has(colName)) {\n throw new Error(\n `Table \"${tableName}\" primaryKey references non-existent column \"${colName}\"`,\n );\n }\n }\n }\n\n for (const unique of table.uniques) {\n for (const colName of unique.columns) {\n if (!columnNames.has(colName)) {\n throw new Error(\n `Table \"${tableName}\" unique constraint references non-existent column \"${colName}\"`,\n );\n }\n }\n }\n\n for (const index of table.indexes) {\n for (const colName of index.columns) {\n if (!columnNames.has(colName)) {\n throw new Error(\n `Table \"${tableName}\" index references non-existent column \"${colName}\"`,\n );\n }\n }\n }\n\n for (const fk of table.foreignKeys) {\n for (const colName of fk.columns) {\n if (!columnNames.has(colName)) {\n throw new Error(\n `Table \"${tableName}\" foreignKey references non-existent column \"${colName}\"`,\n );\n }\n }\n\n if (!tableNames.has(fk.references.table)) {\n throw new Error(\n `Table \"${tableName}\" foreignKey references non-existent table \"${fk.references.table}\"`,\n );\n }\n\n const referencedTable = storage.tables[fk.references.table];\n if (!referencedTable) {\n throw new Error(\n `Table \"${tableName}\" foreignKey references non-existent table \"${fk.references.table}\"`,\n );\n }\n\n const referencedColumnNames = new Set(Object.keys(referencedTable.columns));\n for (const colName of fk.references.columns) {\n if (!referencedColumnNames.has(colName)) {\n throw new Error(\n `Table \"${tableName}\" foreignKey references non-existent column \"${colName}\" in table \"${fk.references.table}\"`,\n );\n }\n }\n\n if (fk.columns.length !== fk.references.columns.length) {\n throw new Error(\n `Table \"${tableName}\" foreignKey column count (${fk.columns.length}) does not match referenced column count (${fk.references.columns.length})`,\n );\n }\n }\n }\n },\n\n generateContractTypes(\n ir: ContractIR,\n codecTypeImports: ReadonlyArray<TypesImportSpec>,\n operationTypeImports: ReadonlyArray<TypesImportSpec>,\n ): string {\n const allImports = [...codecTypeImports, ...operationTypeImports];\n const importLines = allImports.map(\n (imp) => `import type { ${imp.named} as ${imp.alias} } from '${imp.package}';`,\n );\n\n const codecTypes = codecTypeImports.map((imp) => imp.alias).join(' & ');\n const operationTypes = operationTypeImports.map((imp) => imp.alias).join(' & ');\n\n const storage = ir.storage as SqlStorage;\n const models = ir.models as Record<string, ModelDefinition>;\n\n const storageType = this.generateStorageType(storage);\n const modelsType = this.generateModelsType(models, storage);\n const relationsType = this.generateRelationsType(ir.relations);\n const mappingsType = this.generateMappingsType(models, storage, codecTypes, operationTypes);\n\n return `// ⚠️ GENERATED FILE - DO NOT EDIT\n// This file is automatically generated by 'prisma-next emit'.\n// To regenerate, run: prisma-next emit\n${importLines.join('\\n')}\n\nimport type { SqlContract, SqlStorage, SqlMappings, ModelDefinition } from '@prisma-next/sql-contract/types';\n\nexport type CodecTypes = ${codecTypes || 'Record<string, never>'};\nexport type LaneCodecTypes = CodecTypes;\nexport type OperationTypes = ${operationTypes || 'Record<string, never>'};\n\nexport type Contract = SqlContract<\n ${storageType},\n ${modelsType},\n ${relationsType},\n ${mappingsType}\n>;\n\nexport type Tables = Contract['storage']['tables'];\nexport type Models = Contract['models'];\nexport type Relations = Contract['relations'];\n`;\n },\n\n generateStorageType(storage: SqlStorage): string {\n const tables: string[] = [];\n for (const [tableName, table] of Object.entries(storage.tables)) {\n const columns: string[] = [];\n for (const [colName, col] of Object.entries(table.columns)) {\n const nullable = col.nullable ? 'true' : 'false';\n const nativeType = `'${col.nativeType}'`;\n const codecId = `'${col.codecId}'`;\n columns.push(\n `readonly ${colName}: { readonly nativeType: ${nativeType}; readonly codecId: ${codecId}; readonly nullable: ${nullable} }`,\n );\n }\n\n const tableParts: string[] = [`columns: { ${columns.join('; ')} }`];\n\n if (table.primaryKey) {\n const pkCols = table.primaryKey.columns.map((c) => `'${c}'`).join(', ');\n const pkName = table.primaryKey.name ? `; readonly name: '${table.primaryKey.name}'` : '';\n tableParts.push(`primaryKey: { readonly columns: readonly [${pkCols}]${pkName} }`);\n }\n\n const uniques = table.uniques\n .map((u) => {\n const cols = u.columns.map((c: string) => `'${c}'`).join(', ');\n const name = u.name ? `; readonly name: '${u.name}'` : '';\n return `{ readonly columns: readonly [${cols}]${name} }`;\n })\n .join(', ');\n tableParts.push(`uniques: readonly [${uniques}]`);\n\n const indexes = table.indexes\n .map((i) => {\n const cols = i.columns.map((c: string) => `'${c}'`).join(', ');\n const name = i.name ? `; readonly name: '${i.name}'` : '';\n return `{ readonly columns: readonly [${cols}]${name} }`;\n })\n .join(', ');\n tableParts.push(`indexes: readonly [${indexes}]`);\n\n const fks = table.foreignKeys\n .map((fk) => {\n const cols = fk.columns.map((c: string) => `'${c}'`).join(', ');\n const refCols = fk.references.columns.map((c: string) => `'${c}'`).join(', ');\n const name = fk.name ? `; readonly name: '${fk.name}'` : '';\n return `{ readonly columns: readonly [${cols}]; readonly references: { readonly table: '${fk.references.table}'; readonly columns: readonly [${refCols}] }${name} }`;\n })\n .join(', ');\n tableParts.push(`foreignKeys: readonly [${fks}]`);\n\n tables.push(`readonly ${tableName}: { ${tableParts.join('; ')} }`);\n }\n\n return `{ readonly tables: { ${tables.join('; ')} } }`;\n },\n\n generateModelsType(\n models: Record<string, ModelDefinition> | undefined,\n storage: SqlStorage,\n ): string {\n if (!models) {\n return 'Record<string, never>';\n }\n\n const modelTypes: string[] = [];\n for (const [modelName, model] of Object.entries(models)) {\n const fields: string[] = [];\n const tableName = model.storage.table;\n const table = storage.tables[tableName];\n\n if (table) {\n for (const [fieldName, field] of Object.entries(model.fields)) {\n const column = table.columns[field.column];\n if (!column) {\n fields.push(`readonly ${fieldName}: { readonly column: '${field.column}' }`);\n continue;\n }\n\n const typeId = column.codecId;\n const nullable = column.nullable ?? false;\n const jsType = nullable\n ? `CodecTypes['${typeId}']['output'] | null`\n : `CodecTypes['${typeId}']['output']`;\n\n fields.push(`readonly ${fieldName}: ${jsType}`);\n }\n } else {\n for (const [fieldName, field] of Object.entries(model.fields)) {\n fields.push(`readonly ${fieldName}: { readonly column: '${field.column}' }`);\n }\n }\n\n const relations: string[] = [];\n for (const [relName, rel] of Object.entries(model.relations)) {\n if (typeof rel === 'object' && rel !== null && 'on' in rel) {\n const on = rel.on as { parentCols?: string[]; childCols?: string[] };\n if (on.parentCols && on.childCols) {\n const parentCols = on.parentCols.map((c) => `'${c}'`).join(', ');\n const childCols = on.childCols.map((c) => `'${c}'`).join(', ');\n relations.push(\n `readonly ${relName}: { readonly on: { readonly parentCols: readonly [${parentCols}]; readonly childCols: readonly [${childCols}] } }`,\n );\n }\n }\n }\n\n const modelParts: string[] = [\n `storage: { readonly table: '${tableName}' }`,\n `fields: { ${fields.join('; ')} }`,\n ];\n\n if (relations.length > 0) {\n modelParts.push(`relations: { ${relations.join('; ')} }`);\n }\n\n modelTypes.push(`readonly ${modelName}: { ${modelParts.join('; ')} }`);\n }\n\n return `{ ${modelTypes.join('; ')} }`;\n },\n\n generateRelationsType(relations: Record<string, unknown> | undefined): string {\n if (!relations || Object.keys(relations).length === 0) {\n return 'Record<string, never>';\n }\n\n const tableEntries: string[] = [];\n for (const [tableName, relsValue] of Object.entries(relations)) {\n if (typeof relsValue !== 'object' || relsValue === null) {\n continue;\n }\n const rels = relsValue as Record<string, unknown>;\n const relationEntries: string[] = [];\n for (const [relName, relValue] of Object.entries(rels)) {\n if (typeof relValue !== 'object' || relValue === null) {\n relationEntries.push(`readonly ${relName}: unknown`);\n continue;\n }\n const { to, cardinality, on, through } = relValue as {\n readonly to?: string;\n readonly cardinality?: string;\n readonly on?: {\n readonly parentCols?: readonly string[];\n readonly childCols?: readonly string[];\n };\n readonly through?: {\n readonly table: string;\n readonly parentCols: readonly string[];\n readonly childCols: readonly string[];\n };\n };\n\n const parts: string[] = [];\n if (to) {\n parts.push(`readonly to: '${to}'`);\n }\n if (cardinality) {\n parts.push(`readonly cardinality: '${cardinality}'`);\n }\n if (on?.parentCols && on.childCols) {\n const parentCols = on.parentCols.map((c) => `'${c}'`).join(', ');\n const childCols = on.childCols.map((c) => `'${c}'`).join(', ');\n parts.push(\n `readonly on: { readonly parentCols: readonly [${parentCols}]; readonly childCols: readonly [${childCols}] }`,\n );\n }\n if (through) {\n const parentCols = through.parentCols.map((c) => `'${c}'`).join(', ');\n const childCols = through.childCols.map((c) => `'${c}'`).join(', ');\n parts.push(\n `readonly through: { readonly table: '${through.table}'; readonly parentCols: readonly [${parentCols}]; readonly childCols: readonly [${childCols}] }`,\n );\n }\n\n relationEntries.push(\n parts.length > 0\n ? `readonly ${relName}: { ${parts.join('; ')} }`\n : `readonly ${relName}: unknown`,\n );\n }\n tableEntries.push(`readonly ${tableName}: { ${relationEntries.join('; ')} }`);\n }\n\n return `{ ${tableEntries.join('; ')} }`;\n },\n\n generateMappingsType(\n models: Record<string, ModelDefinition> | undefined,\n storage: SqlStorage,\n codecTypes: string,\n operationTypes: string,\n ): string {\n if (!models) {\n return `SqlMappings & { readonly codecTypes: ${codecTypes || 'Record<string, never>'}; readonly operationTypes: ${operationTypes || 'Record<string, never>'}; }`;\n }\n\n const modelToTable: string[] = [];\n const tableToModel: string[] = [];\n const fieldToColumn: string[] = [];\n const columnToField: string[] = [];\n\n for (const [modelName, model] of Object.entries(models)) {\n const tableName = model.storage.table;\n modelToTable.push(`readonly ${modelName}: '${tableName}'`);\n tableToModel.push(`readonly ${tableName}: '${modelName}'`);\n\n const fieldMap: string[] = [];\n for (const [fieldName, field] of Object.entries(model.fields)) {\n fieldMap.push(`readonly ${fieldName}: '${field.column}'`);\n }\n\n if (fieldMap.length > 0) {\n fieldToColumn.push(`readonly ${modelName}: { ${fieldMap.join('; ')} }`);\n }\n\n if (storage.tables[tableName]) {\n const colMap: string[] = [];\n for (const [fieldName, field] of Object.entries(model.fields)) {\n colMap.push(`readonly ${field.column}: '${fieldName}'`);\n }\n\n if (colMap.length > 0) {\n columnToField.push(`readonly ${tableName}: { ${colMap.join('; ')} }`);\n }\n }\n }\n\n const parts: string[] = [];\n if (modelToTable.length > 0) {\n parts.push(`modelToTable: { ${modelToTable.join('; ')} }`);\n }\n if (tableToModel.length > 0) {\n parts.push(`tableToModel: { ${tableToModel.join('; ')} }`);\n }\n if (fieldToColumn.length > 0) {\n parts.push(`fieldToColumn: { ${fieldToColumn.join('; ')} }`);\n }\n if (columnToField.length > 0) {\n parts.push(`columnToField: { ${columnToField.join('; ')} }`);\n }\n parts.push(`codecTypes: ${codecTypes || 'Record<string, never>'}`);\n parts.push(`operationTypes: ${operationTypes || 'Record<string, never>'}`);\n\n return `{ ${parts.join('; ')} }`;\n },\n} as const;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { ContractIR } from '@prisma-next/contract/ir';\nimport type {\n GenerateContractTypesOptions,\n TypeRenderContext,\n TypeRenderEntry,\n TypesImportSpec,\n ValidationContext,\n} from '@prisma-next/contract/types';\nimport type {\n ModelDefinition,\n ModelField,\n SqlStorage,\n StorageColumn,\n StorageTable,\n StorageTypeInstance,\n} from '@prisma-next/sql-contract/types';\nimport { assertDefined } from '@prisma-next/utils/assertions';\n\n/**\n * Resolves the typeParams for a column, either from inline typeParams or from typeRef.\n * Returns undefined if no typeParams are available.\n */\nfunction resolveColumnTypeParams(\n column: StorageColumn,\n storage: SqlStorage,\n): Record<string, unknown> | undefined {\n // Inline typeParams take precedence\n if (column.typeParams && Object.keys(column.typeParams).length > 0) {\n return column.typeParams;\n }\n // Check typeRef\n if (column.typeRef && storage.types) {\n const typeInstance = storage.types[column.typeRef] as StorageTypeInstance | undefined;\n if (typeInstance?.typeParams) {\n return typeInstance.typeParams;\n }\n }\n return undefined;\n}\n\nexport const sqlTargetFamilyHook = {\n id: 'sql',\n\n validateTypes(ir: ContractIR, _ctx: ValidationContext): void {\n const storage = ir.storage as SqlStorage | undefined;\n if (!storage || !storage.tables) {\n return;\n }\n\n // 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.\n\n const typeIdRegex = /^([^/]+)\\/([^@]+)@(\\d+)$/;\n\n for (const [tableName, tableUnknown] of Object.entries(storage.tables)) {\n const table = tableUnknown as StorageTable;\n for (const [colName, colUnknown] of Object.entries(table.columns)) {\n const col = colUnknown as { codecId?: string };\n const codecId = col.codecId;\n if (!codecId) {\n throw new Error(`Column \"${colName}\" in table \"${tableName}\" is missing codecId`);\n }\n\n const match = codecId.match(typeIdRegex);\n if (!match || !match[1]) {\n throw new Error(\n `Column \"${colName}\" in table \"${tableName}\" has invalid codec ID format \"${codecId}\". Expected format: ns/name@version`,\n );\n }\n }\n }\n },\n\n validateStructure(ir: ContractIR): void {\n if (ir.targetFamily !== 'sql') {\n throw new Error(`Expected targetFamily \"sql\", got \"${ir.targetFamily}\"`);\n }\n\n const storage = ir.storage as SqlStorage | undefined;\n if (!storage || !storage.tables) {\n throw new Error('SQL contract must have storage.tables');\n }\n\n const models = ir.models as Record<string, ModelDefinition> | undefined;\n const tableNames = new Set(Object.keys(storage.tables));\n\n if (models) {\n for (const [modelName, modelUnknown] of Object.entries(models)) {\n const model = modelUnknown as ModelDefinition;\n if (!model.storage?.table) {\n throw new Error(`Model \"${modelName}\" is missing storage.table`);\n }\n\n const tableName = model.storage.table;\n if (!tableNames.has(tableName)) {\n throw new Error(`Model \"${modelName}\" references non-existent table \"${tableName}\"`);\n }\n\n const table: StorageTable | undefined = storage.tables[tableName];\n assertDefined(table, `Model \"${modelName}\" references non-existent table \"${tableName}\"`);\n\n if (!table.primaryKey) {\n throw new Error(`Model \"${modelName}\" table \"${tableName}\" is missing a primary key`);\n }\n\n const columnNames = new Set(Object.keys(table.columns));\n if (!model.fields || Object.keys(model.fields).length === 0) {\n throw new Error(`Model \"${modelName}\" is missing fields`);\n }\n\n for (const [fieldName, fieldUnknown] of Object.entries(model.fields)) {\n const field = fieldUnknown as ModelField;\n if (!field.column) {\n throw new Error(`Model \"${modelName}\" field \"${fieldName}\" is missing column property`);\n }\n\n if (!columnNames.has(field.column)) {\n throw new Error(\n `Model \"${modelName}\" field \"${fieldName}\" references non-existent column \"${field.column}\" in table \"${tableName}\"`,\n );\n }\n }\n\n if (!model.relations || typeof model.relations !== 'object') {\n throw new Error(\n `Model \"${modelName}\" is missing required field \"relations\" (must be an object)`,\n );\n }\n }\n }\n\n for (const [tableName, tableUnknown] of Object.entries(storage.tables)) {\n const table = tableUnknown as StorageTable;\n const columnNames = new Set(Object.keys(table.columns));\n\n // Column structure (nullable, nativeType, codecId) and table arrays (uniques, indexes, foreignKeys)\n // are validated by Arktype schema validation - no need to re-check here.\n // We only validate logical consistency (foreign key references, model references, etc.)\n\n if (!Array.isArray(table.uniques)) {\n throw new Error(\n `Table \"${tableName}\" is missing required field \"uniques\" (must be an array)`,\n );\n }\n if (!Array.isArray(table.indexes)) {\n throw new Error(\n `Table \"${tableName}\" is missing required field \"indexes\" (must be an array)`,\n );\n }\n if (!Array.isArray(table.foreignKeys)) {\n throw new Error(\n `Table \"${tableName}\" is missing required field \"foreignKeys\" (must be an array)`,\n );\n }\n\n if (table.primaryKey) {\n for (const colName of table.primaryKey.columns) {\n if (!columnNames.has(colName)) {\n throw new Error(\n `Table \"${tableName}\" primaryKey references non-existent column \"${colName}\"`,\n );\n }\n }\n }\n\n for (const unique of table.uniques) {\n for (const colName of unique.columns) {\n if (!columnNames.has(colName)) {\n throw new Error(\n `Table \"${tableName}\" unique constraint references non-existent column \"${colName}\"`,\n );\n }\n }\n }\n\n for (const index of table.indexes) {\n for (const colName of index.columns) {\n if (!columnNames.has(colName)) {\n throw new Error(\n `Table \"${tableName}\" index references non-existent column \"${colName}\"`,\n );\n }\n }\n }\n\n for (const fk of table.foreignKeys) {\n for (const colName of fk.columns) {\n if (!columnNames.has(colName)) {\n throw new Error(\n `Table \"${tableName}\" foreignKey references non-existent column \"${colName}\"`,\n );\n }\n }\n\n if (!tableNames.has(fk.references.table)) {\n throw new Error(\n `Table \"${tableName}\" foreignKey references non-existent table \"${fk.references.table}\"`,\n );\n }\n\n // Table existence guaranteed by Set.has() check above\n const referencedTable: StorageTable | undefined = storage.tables[fk.references.table];\n assertDefined(\n referencedTable,\n `Table \"${tableName}\" foreignKey references non-existent table \"${fk.references.table}\"`,\n );\n\n const referencedColumnNames = new Set(Object.keys(referencedTable.columns));\n for (const colName of fk.references.columns) {\n if (!referencedColumnNames.has(colName)) {\n throw new Error(\n `Table \"${tableName}\" foreignKey references non-existent column \"${colName}\" in table \"${fk.references.table}\"`,\n );\n }\n }\n\n if (fk.columns.length !== fk.references.columns.length) {\n throw new Error(\n `Table \"${tableName}\" foreignKey column count (${fk.columns.length}) does not match referenced column count (${fk.references.columns.length})`,\n );\n }\n }\n }\n },\n\n generateContractTypes(\n ir: ContractIR,\n codecTypeImports: ReadonlyArray<TypesImportSpec>,\n operationTypeImports: ReadonlyArray<TypesImportSpec>,\n options?: GenerateContractTypesOptions,\n ): string {\n const parameterizedRenderers = options?.parameterizedRenderers;\n const parameterizedTypeImports = options?.parameterizedTypeImports;\n const storage = ir.storage as SqlStorage;\n const models = ir.models as Record<string, ModelDefinition>;\n\n // Collect all type imports from three sources:\n // 1. Codec type imports (from adapters, targets, and extensions)\n // 2. Operation type imports (from adapters, targets, and extensions)\n // 3. Parameterized type imports (for parameterized codec renderers, may contain duplicates)\n const allImports: TypesImportSpec[] = [...codecTypeImports, ...operationTypeImports];\n\n if (parameterizedTypeImports) {\n allImports.push(...parameterizedTypeImports);\n }\n\n // Deduplicate imports by package+named to avoid duplicate import statements.\n // Strategy: When the same package::named appears multiple times, keep the first\n // occurrence (and its alias); later duplicates with different aliases are silently ignored.\n //\n // Note: uniqueImports must be an array (not a Set) because:\n // - We need to preserve the full TypesImportSpec objects (package, named, alias)\n // - We need to preserve insertion order (first occurrence wins)\n // - seenImportKeys is a Set used only for O(1) duplicate detection\n const seenImportKeys = new Set<string>();\n const uniqueImports: TypesImportSpec[] = [];\n for (const imp of allImports) {\n const key = `${imp.package}::${imp.named}`;\n if (!seenImportKeys.has(key)) {\n seenImportKeys.add(key);\n uniqueImports.push(imp);\n }\n }\n\n // Generate import statements, omitting redundant \"as Alias\" when named === alias\n const importLines = uniqueImports.map((imp) => {\n // Simplify import when named === alias (e.g., `import type { Vector }` instead of `{ Vector as Vector }`)\n const importClause = imp.named === imp.alias ? imp.named : `${imp.named} as ${imp.alias}`;\n return `import type { ${importClause} } from '${imp.package}';`;\n });\n\n // Only intersect actual codec/operation type maps. Extra type-only imports (e.g. Vector<N>) are\n // included in importLines via codecTypeImports but must not be intersected into CodecTypes.\n const codecTypes = codecTypeImports\n .filter((imp) => imp.named === 'CodecTypes')\n .map((imp) => imp.alias)\n .join(' & ');\n const operationTypes = operationTypeImports\n .filter((imp) => imp.named === 'OperationTypes')\n .map((imp) => imp.alias)\n .join(' & ');\n\n const storageType = this.generateStorageType(storage);\n const modelsType = this.generateModelsType(models, storage, parameterizedRenderers);\n const relationsType = this.generateRelationsType(ir.relations);\n const mappingsType = this.generateMappingsType(models, storage, codecTypes, operationTypes);\n\n return `// ⚠️ GENERATED FILE - DO NOT EDIT\n // This file is automatically generated by 'prisma-next contract emit'.\n // To regenerate, run: prisma-next contract emit\n ${importLines.join('\\n')}\n\n import type { SqlContract, SqlStorage, SqlMappings, ModelDefinition } from '@prisma-next/sql-contract/types';\n\n export type CodecTypes = ${codecTypes || 'Record<string, never>'};\n export type LaneCodecTypes = CodecTypes;\n export type OperationTypes = ${operationTypes || 'Record<string, never>'};\n\n export type Contract = SqlContract<\n ${storageType},\n ${modelsType},\n ${relationsType},\n ${mappingsType}\n >;\n\n export type Tables = Contract['storage']['tables'];\n export type Models = Contract['models'];\n export type Relations = Contract['relations'];\n `;\n },\n\n generateStorageType(storage: SqlStorage): string {\n const tables: string[] = [];\n for (const [tableName, table] of Object.entries(storage.tables)) {\n const columns: string[] = [];\n for (const [colName, col] of Object.entries(table.columns)) {\n const nullable = col.nullable ? 'true' : 'false';\n const nativeType = `'${col.nativeType}'`;\n const codecId = `'${col.codecId}'`;\n columns.push(\n `readonly ${colName}: { readonly nativeType: ${nativeType}; readonly codecId: ${codecId}; readonly nullable: ${nullable} }`,\n );\n }\n\n const tableParts: string[] = [`columns: { ${columns.join('; ')} }`];\n\n if (table.primaryKey) {\n const pkCols = table.primaryKey.columns.map((c) => `'${c}'`).join(', ');\n const pkName = table.primaryKey.name ? `; readonly name: '${table.primaryKey.name}'` : '';\n tableParts.push(`primaryKey: { readonly columns: readonly [${pkCols}]${pkName} }`);\n }\n\n const uniques = table.uniques\n .map((u) => {\n const cols = u.columns.map((c: string) => `'${c}'`).join(', ');\n const name = u.name ? `; readonly name: '${u.name}'` : '';\n return `{ readonly columns: readonly [${cols}]${name} }`;\n })\n .join(', ');\n tableParts.push(`uniques: readonly [${uniques}]`);\n\n const indexes = table.indexes\n .map((i) => {\n const cols = i.columns.map((c: string) => `'${c}'`).join(', ');\n const name = i.name ? `; readonly name: '${i.name}'` : '';\n return `{ readonly columns: readonly [${cols}]${name} }`;\n })\n .join(', ');\n tableParts.push(`indexes: readonly [${indexes}]`);\n\n const fks = table.foreignKeys\n .map((fk) => {\n const cols = fk.columns.map((c: string) => `'${c}'`).join(', ');\n const refCols = fk.references.columns.map((c: string) => `'${c}'`).join(', ');\n const name = fk.name ? `; readonly name: '${fk.name}'` : '';\n return `{ readonly columns: readonly [${cols}]; readonly references: { readonly table: '${fk.references.table}'; readonly columns: readonly [${refCols}] }${name} }`;\n })\n .join(', ');\n tableParts.push(`foreignKeys: readonly [${fks}]`);\n\n tables.push(`readonly ${tableName}: { ${tableParts.join('; ')} }`);\n }\n\n const typesType = this.generateStorageTypesType(storage.types);\n\n return `{ readonly tables: { ${tables.join('; ')} }; readonly types: ${typesType} }`;\n },\n\n /**\n * Generates the TypeScript type for storage.types with literal types.\n * This preserves type params as literal values for precise typing.\n */\n generateStorageTypesType(types: SqlStorage['types']): string {\n if (!types || Object.keys(types).length === 0) {\n return 'Record<string, never>';\n }\n\n const typeEntries: string[] = [];\n for (const [typeName, typeInstance] of Object.entries(types)) {\n const codecId = `'${typeInstance.codecId}'`;\n const nativeType = `'${typeInstance.nativeType}'`;\n const typeParamsStr = this.serializeTypeParamsLiteral(typeInstance.typeParams);\n typeEntries.push(\n `readonly ${typeName}: { readonly codecId: ${codecId}; readonly nativeType: ${nativeType}; readonly typeParams: ${typeParamsStr} }`,\n );\n }\n\n return `{ ${typeEntries.join('; ')} }`;\n },\n\n /**\n * Serializes a typeParams object to a TypeScript literal type.\n * Converts { length: 1536 } to \"{ readonly length: 1536 }\".\n */\n serializeTypeParamsLiteral(params: Record<string, unknown>): string {\n if (!params || Object.keys(params).length === 0) {\n return 'Record<string, never>';\n }\n\n const entries: string[] = [];\n for (const [key, value] of Object.entries(params)) {\n const serialized = this.serializeValue(value);\n entries.push(`readonly ${key}: ${serialized}`);\n }\n\n return `{ ${entries.join('; ')} }`;\n },\n\n /**\n * Serializes a value to a TypeScript literal type expression.\n */\n serializeValue(value: unknown): string {\n if (value === null) {\n return 'null';\n }\n if (value === undefined) {\n return 'undefined';\n }\n if (typeof value === 'string') {\n // Escape backslashes first, then single quotes\n const escaped = value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n return `'${escaped}'`;\n }\n if (typeof value === 'number' || typeof value === 'boolean') {\n return String(value);\n }\n if (typeof value === 'bigint') {\n return `${value}n`;\n }\n if (Array.isArray(value)) {\n const items = value.map((v) => this.serializeValue(v)).join(', ');\n return `readonly [${items}]`;\n }\n if (typeof value === 'object') {\n const entries: string[] = [];\n for (const [k, v] of Object.entries(value)) {\n entries.push(`readonly ${k}: ${this.serializeValue(v)}`);\n }\n return `{ ${entries.join('; ')} }`;\n }\n return 'unknown';\n },\n\n generateModelsType(\n models: Record<string, ModelDefinition> | undefined,\n storage: SqlStorage,\n parameterizedRenderers?: Map<string, TypeRenderEntry>,\n ): string {\n if (!models) {\n return 'Record<string, never>';\n }\n\n const renderCtx: TypeRenderContext = { codecTypesName: 'CodecTypes' };\n\n const modelTypes: string[] = [];\n for (const [modelName, model] of Object.entries(models)) {\n const fields: string[] = [];\n const tableName = model.storage.table;\n const table = storage.tables[tableName];\n\n if (table) {\n for (const [fieldName, field] of Object.entries(model.fields)) {\n const column = table.columns[field.column];\n if (!column) {\n fields.push(`readonly ${fieldName}: { readonly column: '${field.column}' }`);\n continue;\n }\n\n const jsType = this.generateColumnType(\n column,\n storage,\n parameterizedRenderers,\n renderCtx,\n );\n fields.push(`readonly ${fieldName}: ${jsType}`);\n }\n } else {\n for (const [fieldName, field] of Object.entries(model.fields)) {\n fields.push(`readonly ${fieldName}: { readonly column: '${field.column}' }`);\n }\n }\n\n const relations: string[] = [];\n for (const [relName, rel] of Object.entries(model.relations)) {\n if (typeof rel === 'object' && rel !== null && 'on' in rel) {\n const on = rel.on as { parentCols?: string[]; childCols?: string[] };\n if (on.parentCols && on.childCols) {\n const parentCols = on.parentCols.map((c) => `'${c}'`).join(', ');\n const childCols = on.childCols.map((c) => `'${c}'`).join(', ');\n relations.push(\n `readonly ${relName}: { readonly on: { readonly parentCols: readonly [${parentCols}]; readonly childCols: readonly [${childCols}] } }`,\n );\n }\n }\n }\n\n const modelParts: string[] = [\n `storage: { readonly table: '${tableName}' }`,\n `fields: { ${fields.join('; ')} }`,\n ];\n\n if (relations.length > 0) {\n modelParts.push(`relations: { ${relations.join('; ')} }`);\n }\n\n modelTypes.push(`readonly ${modelName}: { ${modelParts.join('; ')} }`);\n }\n\n return `{ ${modelTypes.join('; ')} }`;\n },\n\n /**\n * Generates the TypeScript type expression for a column.\n * Uses parameterized renderer if the column has typeParams and a matching renderer exists,\n * otherwise falls back to CodecTypes[codecId]['output'].\n */\n generateColumnType(\n column: StorageColumn,\n storage: SqlStorage,\n parameterizedRenderers: Map<string, TypeRenderEntry> | undefined,\n renderCtx: TypeRenderContext,\n ): string {\n const typeParams = resolveColumnTypeParams(column, storage);\n const nullable = column.nullable ?? false;\n const fallbackType = `CodecTypes['${column.codecId}']['output']`;\n const renderer = typeParams && parameterizedRenderers?.get(column.codecId);\n const baseType = renderer ? renderer.render(typeParams, renderCtx) : fallbackType;\n\n return nullable ? `${baseType} | null` : baseType;\n },\n\n generateRelationsType(relations: Record<string, unknown> | undefined): string {\n if (!relations || Object.keys(relations).length === 0) {\n return 'Record<string, never>';\n }\n\n const tableEntries: string[] = [];\n for (const [tableName, relsValue] of Object.entries(relations)) {\n if (typeof relsValue !== 'object' || relsValue === null) {\n continue;\n }\n const rels = relsValue as Record<string, unknown>;\n const relationEntries: string[] = [];\n for (const [relName, relValue] of Object.entries(rels)) {\n if (typeof relValue !== 'object' || relValue === null) {\n relationEntries.push(`readonly ${relName}: unknown`);\n continue;\n }\n const { to, cardinality, on, through } = relValue as {\n readonly to?: string;\n readonly cardinality?: string;\n readonly on?: {\n readonly parentCols?: readonly string[];\n readonly childCols?: readonly string[];\n };\n readonly through?: {\n readonly table: string;\n readonly parentCols: readonly string[];\n readonly childCols: readonly string[];\n };\n };\n\n const parts: string[] = [];\n if (to) {\n parts.push(`readonly to: '${to}'`);\n }\n if (cardinality) {\n parts.push(`readonly cardinality: '${cardinality}'`);\n }\n if (on?.parentCols && on.childCols) {\n const parentCols = on.parentCols.map((c) => `'${c}'`).join(', ');\n const childCols = on.childCols.map((c) => `'${c}'`).join(', ');\n parts.push(\n `readonly on: { readonly parentCols: readonly [${parentCols}]; readonly childCols: readonly [${childCols}] }`,\n );\n }\n if (through) {\n const parentCols = through.parentCols.map((c) => `'${c}'`).join(', ');\n const childCols = through.childCols.map((c) => `'${c}'`).join(', ');\n parts.push(\n `readonly through: { readonly table: '${through.table}'; readonly parentCols: readonly [${parentCols}]; readonly childCols: readonly [${childCols}] }`,\n );\n }\n\n relationEntries.push(\n parts.length > 0\n ? `readonly ${relName}: { ${parts.join('; ')} }`\n : `readonly ${relName}: unknown`,\n );\n }\n tableEntries.push(`readonly ${tableName}: { ${relationEntries.join('; ')} }`);\n }\n\n return `{ ${tableEntries.join('; ')} }`;\n },\n\n generateMappingsType(\n models: Record<string, ModelDefinition> | undefined,\n storage: SqlStorage,\n codecTypes: string,\n operationTypes: string,\n ): string {\n if (!models) {\n return `SqlMappings & { readonly codecTypes: ${codecTypes || 'Record<string, never>'}; readonly operationTypes: ${operationTypes || 'Record<string, never>'}; }`;\n }\n\n const modelToTable: string[] = [];\n const tableToModel: string[] = [];\n const fieldToColumn: string[] = [];\n const columnToField: string[] = [];\n\n for (const [modelName, model] of Object.entries(models)) {\n const tableName = model.storage.table;\n modelToTable.push(`readonly ${modelName}: '${tableName}'`);\n tableToModel.push(`readonly ${tableName}: '${modelName}'`);\n\n const fieldMap: string[] = [];\n for (const [fieldName, field] of Object.entries(model.fields)) {\n fieldMap.push(`readonly ${fieldName}: '${field.column}'`);\n }\n\n if (fieldMap.length > 0) {\n fieldToColumn.push(`readonly ${modelName}: { ${fieldMap.join('; ')} }`);\n }\n\n if (storage.tables[tableName]) {\n const colMap: string[] = [];\n for (const [fieldName, field] of Object.entries(model.fields)) {\n colMap.push(`readonly ${field.column}: '${fieldName}'`);\n }\n\n if (colMap.length > 0) {\n columnToField.push(`readonly ${tableName}: { ${colMap.join('; ')} }`);\n }\n }\n }\n\n const parts: string[] = [];\n if (modelToTable.length > 0) {\n parts.push(`modelToTable: { ${modelToTable.join('; ')} }`);\n }\n if (tableToModel.length > 0) {\n parts.push(`tableToModel: { ${tableToModel.join('; ')} }`);\n }\n if (fieldToColumn.length > 0) {\n parts.push(`fieldToColumn: { ${fieldToColumn.join('; ')} }`);\n }\n if (columnToField.length > 0) {\n parts.push(`columnToField: { ${columnToField.join('; ')} }`);\n }\n parts.push(`codecTypes: ${codecTypes || 'Record<string, never>'}`);\n parts.push(`operationTypes: ${operationTypes || 'Record<string, never>'}`);\n\n return `{ ${parts.join('; ')} }`;\n },\n} as const;\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/sql-contract-emitter",
|
|
3
|
-
"version": "0.3.0-
|
|
3
|
+
"version": "0.3.0-pr.100.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"description": "SQL emitter hook for Prisma Next",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@prisma-next/contract": "0.3.0-
|
|
9
|
-
"@prisma-next/emitter": "0.3.0-
|
|
10
|
-
"@prisma-next/
|
|
8
|
+
"@prisma-next/contract": "0.3.0-pr.100.2",
|
|
9
|
+
"@prisma-next/emitter": "0.3.0-pr.100.2",
|
|
10
|
+
"@prisma-next/utils": "0.3.0-pr.100.2",
|
|
11
|
+
"@prisma-next/sql-contract": "0.3.0-pr.100.2"
|
|
11
12
|
},
|
|
12
13
|
"devDependencies": {
|
|
13
|
-
"@vitest/coverage-v8": "4.0.16",
|
|
14
14
|
"tsup": "8.5.1",
|
|
15
15
|
"typescript": "5.9.3",
|
|
16
16
|
"vitest": "4.0.16",
|
|
17
|
-
"@prisma-next/test-utils": "0.0.1"
|
|
17
|
+
"@prisma-next/test-utils": "0.0.1",
|
|
18
|
+
"@prisma-next/tsconfig": "0.0.0"
|
|
18
19
|
},
|
|
19
20
|
"files": [
|
|
20
21
|
"dist",
|
|
@@ -31,9 +32,9 @@
|
|
|
31
32
|
"test": "vitest run --passWithNoTests",
|
|
32
33
|
"test:coverage": "vitest run --coverage --passWithNoTests",
|
|
33
34
|
"typecheck": "tsc --project tsconfig.json --noEmit",
|
|
34
|
-
"lint": "biome check . --
|
|
35
|
-
"lint:fix": "biome check --write .
|
|
36
|
-
"lint:fix:unsafe": "biome check --write --unsafe .
|
|
37
|
-
"clean": "
|
|
35
|
+
"lint": "biome check . --error-on-warnings",
|
|
36
|
+
"lint:fix": "biome check --write .",
|
|
37
|
+
"lint:fix:unsafe": "biome check --write --unsafe .",
|
|
38
|
+
"clean": "rm -rf dist dist-tsc dist-tsc-prod coverage .tmp-output"
|
|
38
39
|
}
|
|
39
40
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,42 @@
|
|
|
1
1
|
import type { ContractIR } from '@prisma-next/contract/ir';
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
GenerateContractTypesOptions,
|
|
4
|
+
TypeRenderContext,
|
|
5
|
+
TypeRenderEntry,
|
|
6
|
+
TypesImportSpec,
|
|
7
|
+
ValidationContext,
|
|
8
|
+
} from '@prisma-next/contract/types';
|
|
3
9
|
import type {
|
|
4
10
|
ModelDefinition,
|
|
5
11
|
ModelField,
|
|
6
12
|
SqlStorage,
|
|
13
|
+
StorageColumn,
|
|
7
14
|
StorageTable,
|
|
15
|
+
StorageTypeInstance,
|
|
8
16
|
} from '@prisma-next/sql-contract/types';
|
|
17
|
+
import { assertDefined } from '@prisma-next/utils/assertions';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resolves the typeParams for a column, either from inline typeParams or from typeRef.
|
|
21
|
+
* Returns undefined if no typeParams are available.
|
|
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;
|
|
30
|
+
}
|
|
31
|
+
// Check typeRef
|
|
32
|
+
if (column.typeRef && storage.types) {
|
|
33
|
+
const typeInstance = storage.types[column.typeRef] as StorageTypeInstance | undefined;
|
|
34
|
+
if (typeInstance?.typeParams) {
|
|
35
|
+
return typeInstance.typeParams;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
9
40
|
|
|
10
41
|
export const sqlTargetFamilyHook = {
|
|
11
42
|
id: 'sql',
|
|
@@ -64,10 +95,8 @@ export const sqlTargetFamilyHook = {
|
|
|
64
95
|
throw new Error(`Model "${modelName}" references non-existent table "${tableName}"`);
|
|
65
96
|
}
|
|
66
97
|
|
|
67
|
-
const table = storage.tables[tableName];
|
|
68
|
-
|
|
69
|
-
throw new Error(`Model "${modelName}" references non-existent table "${tableName}"`);
|
|
70
|
-
}
|
|
98
|
+
const table: StorageTable | undefined = storage.tables[tableName];
|
|
99
|
+
assertDefined(table, `Model "${modelName}" references non-existent table "${tableName}"`);
|
|
71
100
|
|
|
72
101
|
if (!table.primaryKey) {
|
|
73
102
|
throw new Error(`Model "${modelName}" table "${tableName}" is missing a primary key`);
|
|
@@ -168,12 +197,12 @@ export const sqlTargetFamilyHook = {
|
|
|
168
197
|
);
|
|
169
198
|
}
|
|
170
199
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
200
|
+
// Table existence guaranteed by Set.has() check above
|
|
201
|
+
const referencedTable: StorageTable | undefined = storage.tables[fk.references.table];
|
|
202
|
+
assertDefined(
|
|
203
|
+
referencedTable,
|
|
204
|
+
`Table "${tableName}" foreignKey references non-existent table "${fk.references.table}"`,
|
|
205
|
+
);
|
|
177
206
|
|
|
178
207
|
const referencedColumnNames = new Set(Object.keys(referencedTable.columns));
|
|
179
208
|
for (const colName of fk.references.columns) {
|
|
@@ -197,45 +226,86 @@ export const sqlTargetFamilyHook = {
|
|
|
197
226
|
ir: ContractIR,
|
|
198
227
|
codecTypeImports: ReadonlyArray<TypesImportSpec>,
|
|
199
228
|
operationTypeImports: ReadonlyArray<TypesImportSpec>,
|
|
229
|
+
options?: GenerateContractTypesOptions,
|
|
200
230
|
): string {
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
(imp) => `import type { ${imp.named} as ${imp.alias} } from '${imp.package}';`,
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
const codecTypes = codecTypeImports.map((imp) => imp.alias).join(' & ');
|
|
207
|
-
const operationTypes = operationTypeImports.map((imp) => imp.alias).join(' & ');
|
|
208
|
-
|
|
231
|
+
const parameterizedRenderers = options?.parameterizedRenderers;
|
|
232
|
+
const parameterizedTypeImports = options?.parameterizedTypeImports;
|
|
209
233
|
const storage = ir.storage as SqlStorage;
|
|
210
234
|
const models = ir.models as Record<string, ModelDefinition>;
|
|
211
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
|
+
// Only intersect actual codec/operation type maps. Extra type-only imports (e.g. Vector<N>) are
|
|
272
|
+
// included in importLines via codecTypeImports but must not be intersected into CodecTypes.
|
|
273
|
+
const codecTypes = codecTypeImports
|
|
274
|
+
.filter((imp) => imp.named === 'CodecTypes')
|
|
275
|
+
.map((imp) => imp.alias)
|
|
276
|
+
.join(' & ');
|
|
277
|
+
const operationTypes = operationTypeImports
|
|
278
|
+
.filter((imp) => imp.named === 'OperationTypes')
|
|
279
|
+
.map((imp) => imp.alias)
|
|
280
|
+
.join(' & ');
|
|
281
|
+
|
|
212
282
|
const storageType = this.generateStorageType(storage);
|
|
213
|
-
const modelsType = this.generateModelsType(models, storage);
|
|
283
|
+
const modelsType = this.generateModelsType(models, storage, parameterizedRenderers);
|
|
214
284
|
const relationsType = this.generateRelationsType(ir.relations);
|
|
215
285
|
const mappingsType = this.generateMappingsType(models, storage, codecTypes, operationTypes);
|
|
216
286
|
|
|
217
287
|
return `// ⚠️ GENERATED FILE - DO NOT EDIT
|
|
218
|
-
// This file is automatically generated by 'prisma-next emit'.
|
|
219
|
-
// To regenerate, run: prisma-next emit
|
|
220
|
-
${importLines.join('\n')}
|
|
288
|
+
// This file is automatically generated by 'prisma-next contract emit'.
|
|
289
|
+
// To regenerate, run: prisma-next contract emit
|
|
290
|
+
${importLines.join('\n')}
|
|
221
291
|
|
|
222
|
-
import type { SqlContract, SqlStorage, SqlMappings, ModelDefinition } from '@prisma-next/sql-contract/types';
|
|
292
|
+
import type { SqlContract, SqlStorage, SqlMappings, ModelDefinition } from '@prisma-next/sql-contract/types';
|
|
223
293
|
|
|
224
|
-
export type CodecTypes = ${codecTypes || 'Record<string, never>'};
|
|
225
|
-
export type LaneCodecTypes = CodecTypes;
|
|
226
|
-
export type OperationTypes = ${operationTypes || 'Record<string, never>'};
|
|
294
|
+
export type CodecTypes = ${codecTypes || 'Record<string, never>'};
|
|
295
|
+
export type LaneCodecTypes = CodecTypes;
|
|
296
|
+
export type OperationTypes = ${operationTypes || 'Record<string, never>'};
|
|
227
297
|
|
|
228
|
-
export type Contract = SqlContract<
|
|
298
|
+
export type Contract = SqlContract<
|
|
229
299
|
${storageType},
|
|
230
300
|
${modelsType},
|
|
231
301
|
${relationsType},
|
|
232
302
|
${mappingsType}
|
|
233
|
-
>;
|
|
303
|
+
>;
|
|
234
304
|
|
|
235
|
-
export type Tables = Contract['storage']['tables'];
|
|
236
|
-
export type Models = Contract['models'];
|
|
237
|
-
export type Relations = Contract['relations'];
|
|
238
|
-
`;
|
|
305
|
+
export type Tables = Contract['storage']['tables'];
|
|
306
|
+
export type Models = Contract['models'];
|
|
307
|
+
export type Relations = Contract['relations'];
|
|
308
|
+
`;
|
|
239
309
|
},
|
|
240
310
|
|
|
241
311
|
generateStorageType(storage: SqlStorage): string {
|
|
@@ -290,17 +360,97 @@ export type Relations = Contract['relations'];
|
|
|
290
360
|
tables.push(`readonly ${tableName}: { ${tableParts.join('; ')} }`);
|
|
291
361
|
}
|
|
292
362
|
|
|
293
|
-
|
|
363
|
+
const typesType = this.generateStorageTypesType(storage.types);
|
|
364
|
+
|
|
365
|
+
return `{ readonly tables: { ${tables.join('; ')} }; readonly types: ${typesType} }`;
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Generates the TypeScript type for storage.types with literal types.
|
|
370
|
+
* This preserves type params as literal values for precise typing.
|
|
371
|
+
*/
|
|
372
|
+
generateStorageTypesType(types: SqlStorage['types']): string {
|
|
373
|
+
if (!types || Object.keys(types).length === 0) {
|
|
374
|
+
return 'Record<string, never>';
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const typeEntries: string[] = [];
|
|
378
|
+
for (const [typeName, typeInstance] of Object.entries(types)) {
|
|
379
|
+
const codecId = `'${typeInstance.codecId}'`;
|
|
380
|
+
const nativeType = `'${typeInstance.nativeType}'`;
|
|
381
|
+
const typeParamsStr = this.serializeTypeParamsLiteral(typeInstance.typeParams);
|
|
382
|
+
typeEntries.push(
|
|
383
|
+
`readonly ${typeName}: { readonly codecId: ${codecId}; readonly nativeType: ${nativeType}; readonly typeParams: ${typeParamsStr} }`,
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return `{ ${typeEntries.join('; ')} }`;
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Serializes a typeParams object to a TypeScript literal type.
|
|
392
|
+
* Converts { length: 1536 } to "{ readonly length: 1536 }".
|
|
393
|
+
*/
|
|
394
|
+
serializeTypeParamsLiteral(params: Record<string, unknown>): string {
|
|
395
|
+
if (!params || Object.keys(params).length === 0) {
|
|
396
|
+
return 'Record<string, never>';
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const entries: string[] = [];
|
|
400
|
+
for (const [key, value] of Object.entries(params)) {
|
|
401
|
+
const serialized = this.serializeValue(value);
|
|
402
|
+
entries.push(`readonly ${key}: ${serialized}`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return `{ ${entries.join('; ')} }`;
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Serializes a value to a TypeScript literal type expression.
|
|
410
|
+
*/
|
|
411
|
+
serializeValue(value: unknown): string {
|
|
412
|
+
if (value === null) {
|
|
413
|
+
return 'null';
|
|
414
|
+
}
|
|
415
|
+
if (value === undefined) {
|
|
416
|
+
return 'undefined';
|
|
417
|
+
}
|
|
418
|
+
if (typeof value === 'string') {
|
|
419
|
+
// Escape backslashes first, then single quotes
|
|
420
|
+
const escaped = value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
421
|
+
return `'${escaped}'`;
|
|
422
|
+
}
|
|
423
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
424
|
+
return String(value);
|
|
425
|
+
}
|
|
426
|
+
if (typeof value === 'bigint') {
|
|
427
|
+
return `${value}n`;
|
|
428
|
+
}
|
|
429
|
+
if (Array.isArray(value)) {
|
|
430
|
+
const items = value.map((v) => this.serializeValue(v)).join(', ');
|
|
431
|
+
return `readonly [${items}]`;
|
|
432
|
+
}
|
|
433
|
+
if (typeof value === 'object') {
|
|
434
|
+
const entries: string[] = [];
|
|
435
|
+
for (const [k, v] of Object.entries(value)) {
|
|
436
|
+
entries.push(`readonly ${k}: ${this.serializeValue(v)}`);
|
|
437
|
+
}
|
|
438
|
+
return `{ ${entries.join('; ')} }`;
|
|
439
|
+
}
|
|
440
|
+
return 'unknown';
|
|
294
441
|
},
|
|
295
442
|
|
|
296
443
|
generateModelsType(
|
|
297
444
|
models: Record<string, ModelDefinition> | undefined,
|
|
298
445
|
storage: SqlStorage,
|
|
446
|
+
parameterizedRenderers?: Map<string, TypeRenderEntry>,
|
|
299
447
|
): string {
|
|
300
448
|
if (!models) {
|
|
301
449
|
return 'Record<string, never>';
|
|
302
450
|
}
|
|
303
451
|
|
|
452
|
+
const renderCtx: TypeRenderContext = { codecTypesName: 'CodecTypes' };
|
|
453
|
+
|
|
304
454
|
const modelTypes: string[] = [];
|
|
305
455
|
for (const [modelName, model] of Object.entries(models)) {
|
|
306
456
|
const fields: string[] = [];
|
|
@@ -315,12 +465,12 @@ export type Relations = Contract['relations'];
|
|
|
315
465
|
continue;
|
|
316
466
|
}
|
|
317
467
|
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
468
|
+
const jsType = this.generateColumnType(
|
|
469
|
+
column,
|
|
470
|
+
storage,
|
|
471
|
+
parameterizedRenderers,
|
|
472
|
+
renderCtx,
|
|
473
|
+
);
|
|
324
474
|
fields.push(`readonly ${fieldName}: ${jsType}`);
|
|
325
475
|
}
|
|
326
476
|
} else {
|
|
@@ -358,6 +508,26 @@ export type Relations = Contract['relations'];
|
|
|
358
508
|
return `{ ${modelTypes.join('; ')} }`;
|
|
359
509
|
},
|
|
360
510
|
|
|
511
|
+
/**
|
|
512
|
+
* Generates the TypeScript type expression for a column.
|
|
513
|
+
* Uses parameterized renderer if the column has typeParams and a matching renderer exists,
|
|
514
|
+
* otherwise falls back to CodecTypes[codecId]['output'].
|
|
515
|
+
*/
|
|
516
|
+
generateColumnType(
|
|
517
|
+
column: StorageColumn,
|
|
518
|
+
storage: SqlStorage,
|
|
519
|
+
parameterizedRenderers: Map<string, TypeRenderEntry> | undefined,
|
|
520
|
+
renderCtx: TypeRenderContext,
|
|
521
|
+
): string {
|
|
522
|
+
const typeParams = resolveColumnTypeParams(column, storage);
|
|
523
|
+
const nullable = column.nullable ?? false;
|
|
524
|
+
const fallbackType = `CodecTypes['${column.codecId}']['output']`;
|
|
525
|
+
const renderer = typeParams && parameterizedRenderers?.get(column.codecId);
|
|
526
|
+
const baseType = renderer ? renderer.render(typeParams, renderCtx) : fallbackType;
|
|
527
|
+
|
|
528
|
+
return nullable ? `${baseType} | null` : baseType;
|
|
529
|
+
},
|
|
530
|
+
|
|
361
531
|
generateRelationsType(relations: Record<string, unknown> | undefined): string {
|
|
362
532
|
if (!relations || Object.keys(relations).length === 0) {
|
|
363
533
|
return 'Record<string, never>';
|