@prisma-next/sql-contract-emitter 0.9.0 → 0.10.0-dev.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.mts +1 -22
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +127 -68
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -9
- package/src/index.ts +303 -203
package/dist/index.d.mts
CHANGED
|
@@ -1,27 +1,6 @@
|
|
|
1
1
|
import { Contract, ContractModel } from "@prisma-next/contract/types";
|
|
2
|
+
import { GenerateContractTypesOptions, ValidationContext } from "@prisma-next/framework-components/emission";
|
|
2
3
|
|
|
3
|
-
//#region ../../../1-framework/1-core/framework-components/dist/types-import-spec-BxI5cSQy.d.mts
|
|
4
|
-
//#region src/shared/types-import-spec.d.ts
|
|
5
|
-
/**
|
|
6
|
-
* Specifies how to import TypeScript types from a package.
|
|
7
|
-
* Used in extension pack manifests to declare codec and operation type imports.
|
|
8
|
-
*/
|
|
9
|
-
interface TypesImportSpec {
|
|
10
|
-
readonly package: string;
|
|
11
|
-
readonly named: string;
|
|
12
|
-
readonly alias: string;
|
|
13
|
-
} //#endregion
|
|
14
|
-
//#endregion
|
|
15
|
-
//#region ../../../1-framework/1-core/framework-components/dist/emission-types-CMv_053d.d.mts
|
|
16
|
-
//#region src/control/emission-types.d.ts
|
|
17
|
-
interface GenerateContractTypesOptions {
|
|
18
|
-
readonly queryOperationTypeImports?: ReadonlyArray<TypesImportSpec>;
|
|
19
|
-
}
|
|
20
|
-
interface ValidationContext {
|
|
21
|
-
readonly codecTypeImports?: ReadonlyArray<TypesImportSpec>;
|
|
22
|
-
readonly extensionIds?: ReadonlyArray<string>;
|
|
23
|
-
}
|
|
24
|
-
//#endregion
|
|
25
4
|
//#region src/index.d.ts
|
|
26
5
|
declare const sqlEmission: {
|
|
27
6
|
readonly id: "sql";
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;cAkEa,WAAA;EAAA;qCAGa,QAAA,EAAQ,IAAA,EAAQ,iBAAA;EAAA,uCA6BZ,QAAA;EAAA,yCAmJE,QAAA,EAAQ,mBAAA;EAAA,wDAQK,KAAA,EAAS,aAAA;EAAA,sDAoBhC,SAAA,UACD,KAAA,EACV,aAAA,EAAa,QAAA,EACV,QAAA,KACT,MAAA;EAAA;4CA+C4B,4BAAA;EAAA;0DAwBY,YAAA;AAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { serializeObjectKey, serializeValue } from "@prisma-next/emitter/domain-type-generation";
|
|
2
|
+
import { UNBOUND_NAMESPACE_ID } from "@prisma-next/framework-components/ir";
|
|
2
3
|
import { isPostgresEnumStorageEntry } from "@prisma-next/sql-contract/types";
|
|
3
|
-
import { assertDefined } from "@prisma-next/utils/assertions";
|
|
4
4
|
//#region src/index.ts
|
|
5
5
|
function serializeTypeParamsLiteral(params) {
|
|
6
6
|
if (!params || Object.keys(params).length === 0) return "Record<string, never>";
|
|
@@ -8,13 +8,39 @@ function serializeTypeParamsLiteral(params) {
|
|
|
8
8
|
for (const [key, value] of Object.entries(params)) entries.push(`readonly ${serializeObjectKey(key)}: ${serializeValue(value)}`);
|
|
9
9
|
return `{ ${entries.join("; ")} }`;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* Name-only lookup that walks every namespace until it finds a table
|
|
13
|
+
* with the given name. Survives this slice only for the two model-side
|
|
14
|
+
* call sites whose `SqlModelStorage.table` is still a bare name without
|
|
15
|
+
* a namespace coordinate; both call sites — and this helper — are
|
|
16
|
+
* deleted by TML-2584, which promotes `SqlModelStorage` to carry the
|
|
17
|
+
* model's namespace id so model→table resolution can use explicit
|
|
18
|
+
* coordinates like the FK-ref site already does.
|
|
19
|
+
*/
|
|
20
|
+
function findSqlTable(storage, tableName) {
|
|
21
|
+
for (const [namespaceId, ns] of Object.entries(storage.namespaces)) {
|
|
22
|
+
const table = ns.tables[tableName];
|
|
23
|
+
if (table !== void 0) return {
|
|
24
|
+
table,
|
|
25
|
+
namespaceId
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function assertUniqueSqlTableNames(storage) {
|
|
30
|
+
const seen = /* @__PURE__ */ new Map();
|
|
31
|
+
for (const [namespaceId, ns] of Object.entries(storage.namespaces)) for (const tableName of Object.keys(ns.tables)) {
|
|
32
|
+
const existing = seen.get(tableName);
|
|
33
|
+
if (existing !== void 0 && existing !== namespaceId) throw new Error(`Duplicate table name "${tableName}" in namespaces "${existing}" and "${namespaceId}"`);
|
|
34
|
+
seen.set(tableName, namespaceId);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
11
37
|
const sqlEmission = {
|
|
12
38
|
id: "sql",
|
|
13
39
|
validateTypes(contract, _ctx) {
|
|
14
40
|
const storage = contract.storage;
|
|
15
|
-
if (!storage?.
|
|
41
|
+
if (!storage?.namespaces) return;
|
|
16
42
|
const typeIdRegex = /^([^/]+)\/([^@]+)@(\d+)$/;
|
|
17
|
-
for (const [tableName, tableUnknown] of Object.entries(
|
|
43
|
+
for (const ns of Object.values(storage.namespaces)) for (const [tableName, tableUnknown] of Object.entries(ns.tables)) {
|
|
18
44
|
const table = tableUnknown;
|
|
19
45
|
for (const [colName, colUnknown] of Object.entries(table.columns)) {
|
|
20
46
|
const codecId = colUnknown.codecId;
|
|
@@ -26,15 +52,17 @@ const sqlEmission = {
|
|
|
26
52
|
validateStructure(contract) {
|
|
27
53
|
if (contract.targetFamily !== "sql") throw new Error(`Expected targetFamily "sql", got "${contract.targetFamily}"`);
|
|
28
54
|
const storage = contract.storage;
|
|
29
|
-
if (!storage?.
|
|
55
|
+
if (!storage?.namespaces) throw new Error("SQL contract must have storage.namespaces");
|
|
56
|
+
assertUniqueSqlTableNames(storage);
|
|
30
57
|
const models = contract.models;
|
|
31
|
-
const tableNames = new Set(
|
|
58
|
+
const tableNames = /* @__PURE__ */ new Set();
|
|
59
|
+
for (const ns of Object.values(storage.namespaces)) for (const t of Object.keys(ns.tables)) tableNames.add(t);
|
|
32
60
|
if (models) for (const [modelName, model] of Object.entries(models)) {
|
|
33
61
|
if (!model.storage?.table) throw new Error(`Model "${modelName}" is missing storage.table`);
|
|
34
62
|
const tableName = model.storage.table;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
63
|
+
const located = findSqlTable(storage, tableName);
|
|
64
|
+
if (!located) throw new Error(`Model "${modelName}" references non-existent table "${tableName}"`);
|
|
65
|
+
const { table } = located;
|
|
38
66
|
const columnNames = new Set(Object.keys(table.columns));
|
|
39
67
|
const storageFields = model.storage.fields;
|
|
40
68
|
if (!storageFields || Object.keys(storageFields).length === 0) throw new Error(`Model "${modelName}" is missing storage.fields`);
|
|
@@ -44,7 +72,7 @@ const sqlEmission = {
|
|
|
44
72
|
}
|
|
45
73
|
if (!model.relations || typeof model.relations !== "object") throw new Error(`Model "${modelName}" is missing required field "relations" (must be an object)`);
|
|
46
74
|
}
|
|
47
|
-
for (const [tableName, tableUnknown] of Object.entries(
|
|
75
|
+
for (const ns of Object.values(storage.namespaces)) for (const [tableName, tableUnknown] of Object.entries(ns.tables)) {
|
|
48
76
|
const table = tableUnknown;
|
|
49
77
|
const columnNames = new Set(Object.keys(table.columns));
|
|
50
78
|
if (!Array.isArray(table.uniques)) throw new Error(`Table "${tableName}" is missing required field "uniques" (must be an array)`);
|
|
@@ -56,56 +84,20 @@ const sqlEmission = {
|
|
|
56
84
|
for (const unique of table.uniques) for (const colName of unique.columns) if (!columnNames.has(colName)) throw new Error(`Table "${tableName}" unique constraint references non-existent column "${colName}"`);
|
|
57
85
|
for (const index of table.indexes) for (const colName of index.columns) if (!columnNames.has(colName)) throw new Error(`Table "${tableName}" index references non-existent column "${colName}"`);
|
|
58
86
|
for (const fk of table.foreignKeys) {
|
|
59
|
-
for (const colName of fk.columns) if (!columnNames.has(colName)) throw new Error(`Table "${tableName}" foreignKey references non-existent column "${colName}"`);
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
assertDefined(referencedTable, `Table "${tableName}" foreignKey references non-existent table "${fk.references.table}"`);
|
|
87
|
+
for (const colName of fk.source.columns) if (!columnNames.has(colName)) throw new Error(`Table "${tableName}" foreignKey references non-existent column "${colName}"`);
|
|
88
|
+
const referencedTable = storage.namespaces[fk.target.namespaceId]?.tables[fk.target.tableName];
|
|
89
|
+
if (!referencedTable) throw new Error(`Table "${tableName}" foreignKey references non-existent table "${fk.target.tableName}" in namespace "${fk.target.namespaceId}"`);
|
|
63
90
|
const referencedColumnNames = new Set(Object.keys(referencedTable.columns));
|
|
64
|
-
for (const colName of fk.
|
|
65
|
-
if (fk.columns.length !== fk.
|
|
91
|
+
for (const colName of fk.target.columns) if (!referencedColumnNames.has(colName)) throw new Error(`Table "${tableName}" foreignKey references non-existent column "${colName}" in table "${fk.target.tableName}"`);
|
|
92
|
+
if (fk.source.columns.length !== fk.target.columns.length) throw new Error(`Table "${tableName}" foreignKey column count (${fk.source.columns.length}) does not match referenced column count (${fk.target.columns.length})`);
|
|
66
93
|
}
|
|
67
94
|
}
|
|
68
95
|
},
|
|
69
96
|
generateStorageType(contract, storageHashTypeName) {
|
|
70
97
|
const storage = contract.storage;
|
|
71
|
-
const tables = [];
|
|
72
|
-
for (const [tableName, table] of Object.entries(storage.tables).sort(([a], [b]) => a.localeCompare(b))) {
|
|
73
|
-
const columns = [];
|
|
74
|
-
for (const [colName, col] of Object.entries(table.columns)) {
|
|
75
|
-
const nullable = col.nullable ? "true" : "false";
|
|
76
|
-
const nativeType = serializeValue(col.nativeType);
|
|
77
|
-
const codecId = serializeValue(col.codecId);
|
|
78
|
-
const defaultSpec = col.default ? col.default.kind === "literal" ? `; readonly default: { readonly kind: 'literal'; readonly value: DefaultLiteralValue<${codecId}, ${serializeValue(col.default.value)}> }` : `; readonly default: { readonly kind: 'function'; readonly expression: ${serializeValue(col.default.expression)} }` : "";
|
|
79
|
-
const typeParamsSpec = col.typeParams && Object.keys(col.typeParams).length > 0 ? `; readonly typeParams: ${serializeTypeParamsLiteral(col.typeParams)}` : "";
|
|
80
|
-
const typeRefSpec = col.typeRef ? `; readonly typeRef: ${serializeValue(col.typeRef)}` : "";
|
|
81
|
-
columns.push(`readonly ${colName}: { readonly nativeType: ${nativeType}; readonly codecId: ${codecId}; readonly nullable: ${nullable}${defaultSpec}${typeParamsSpec}${typeRefSpec} }`);
|
|
82
|
-
}
|
|
83
|
-
const tableParts = [`columns: { ${columns.join("; ")} }`];
|
|
84
|
-
if (table.primaryKey) {
|
|
85
|
-
const pkCols = table.primaryKey.columns.map((c) => serializeValue(c)).join(", ");
|
|
86
|
-
const pkName = table.primaryKey.name ? `; readonly name: ${serializeValue(table.primaryKey.name)}` : "";
|
|
87
|
-
tableParts.push(`primaryKey: { readonly columns: readonly [${pkCols}]${pkName} }`);
|
|
88
|
-
}
|
|
89
|
-
const uniques = table.uniques.map((u) => {
|
|
90
|
-
return `{ readonly columns: readonly [${u.columns.map((c) => serializeValue(c)).join(", ")}]${u.name ? `; readonly name: ${serializeValue(u.name)}` : ""} }`;
|
|
91
|
-
}).join(", ");
|
|
92
|
-
tableParts.push(`uniques: readonly [${uniques}]`);
|
|
93
|
-
const indexes = table.indexes.map((i) => {
|
|
94
|
-
return `{ readonly columns: readonly [${i.columns.map((c) => serializeValue(c)).join(", ")}]${i.name !== void 0 ? `; readonly name: ${serializeValue(i.name)}` : ""}${i.type !== void 0 ? `; readonly type: ${serializeValue(i.type)}` : ""}${i.options !== void 0 ? `; readonly options: ${serializeValue(i.options)}` : ""} }`;
|
|
95
|
-
}).join(", ");
|
|
96
|
-
tableParts.push(`indexes: readonly [${indexes}]`);
|
|
97
|
-
const fks = table.foreignKeys.map((fk) => {
|
|
98
|
-
const cols = fk.columns.map((c) => serializeValue(c)).join(", ");
|
|
99
|
-
const refCols = fk.references.columns.map((c) => serializeValue(c)).join(", ");
|
|
100
|
-
const name = fk.name ? `; readonly name: ${serializeValue(fk.name)}` : "";
|
|
101
|
-
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} }`;
|
|
102
|
-
}).join(", ");
|
|
103
|
-
tableParts.push(`foreignKeys: readonly [${fks}]`);
|
|
104
|
-
tables.push(`readonly ${tableName}: { ${tableParts.join("; ")} }`);
|
|
105
|
-
}
|
|
106
|
-
const typesType = generateStorageTypesType(storage.types);
|
|
107
98
|
const namespacesType = generateStorageNamespacesType(storage.namespaces);
|
|
108
|
-
|
|
99
|
+
const docTypes = generateDocumentScopedStorageTypesType(storage.types);
|
|
100
|
+
return `{ readonly namespaces: ${namespacesType}${docTypes === void 0 ? "" : `; readonly types: ${docTypes}`}; readonly storageHash: ${storageHashTypeName} }`;
|
|
109
101
|
},
|
|
110
102
|
generateModelStorageType(_modelName, model) {
|
|
111
103
|
const sqlModel = model;
|
|
@@ -126,12 +118,15 @@ const sqlEmission = {
|
|
|
126
118
|
const storage = contract.storage;
|
|
127
119
|
if (!storage) return void 0;
|
|
128
120
|
const tableName = sqlModel.storage.table;
|
|
129
|
-
const
|
|
130
|
-
if (!
|
|
131
|
-
const column = table.columns[storageField.column];
|
|
121
|
+
const located = findSqlTable(storage, tableName);
|
|
122
|
+
if (!located) return void 0;
|
|
123
|
+
const column = located.table.columns[storageField.column];
|
|
132
124
|
if (!column) return void 0;
|
|
133
125
|
if (column.typeRef) {
|
|
134
|
-
const
|
|
126
|
+
const ns = storage.namespaces[located.namespaceId];
|
|
127
|
+
const fromNamespace = (ns !== void 0 && "types" in ns ? ns.types : void 0)?.[column.typeRef];
|
|
128
|
+
const fromDocument = storage.types?.[column.typeRef];
|
|
129
|
+
const typeInstance = fromNamespace ?? fromDocument;
|
|
135
130
|
if (typeInstance === void 0) return void 0;
|
|
136
131
|
if (isPostgresEnumStorageEntry(typeInstance)) return { values: typeInstance.values };
|
|
137
132
|
return typeInstance.typeParams;
|
|
@@ -164,24 +159,18 @@ const sqlEmission = {
|
|
|
164
159
|
return [
|
|
165
160
|
`export type Contract = ContractWithTypeMaps<${contractBaseName}, ${typeMapsName}>;`,
|
|
166
161
|
"",
|
|
167
|
-
"export type
|
|
162
|
+
"export type Namespaces = Contract['storage']['namespaces'];",
|
|
168
163
|
"export type Models = Contract['models'];"
|
|
169
164
|
].join("\n");
|
|
170
165
|
}
|
|
171
166
|
};
|
|
172
|
-
function
|
|
173
|
-
if (!types || Object.keys(types).length === 0) return
|
|
167
|
+
function generateDocumentScopedStorageTypesType(types) {
|
|
168
|
+
if (!types || Object.keys(types).length === 0) return;
|
|
174
169
|
const typeEntries = [];
|
|
175
170
|
for (const [typeName, typeInstance] of Object.entries(types)) {
|
|
176
|
-
if (isPostgresEnumStorageEntry(typeInstance)) {
|
|
177
|
-
const codecId = serializeValue(typeInstance.codecId);
|
|
178
|
-
const nativeType = serializeValue(typeInstance.nativeType);
|
|
179
|
-
const typeParamsStr = serializeTypeParamsLiteral({ values: typeInstance.values });
|
|
180
|
-
typeEntries.push(`readonly ${typeName}: { readonly kind: 'codec-instance'; readonly codecId: ${codecId}; readonly nativeType: ${nativeType}; readonly typeParams: ${typeParamsStr} }`);
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
171
|
+
if (isPostgresEnumStorageEntry(typeInstance)) throw new Error(`Document-scoped storage.types entry "${typeName}" is a postgres-enum; enums belong under storage.namespaces[namespaceId].types`);
|
|
183
172
|
const codecInstanceShape = typeInstance;
|
|
184
|
-
if (typeof codecInstanceShape.codecId !== "string" || typeof codecInstanceShape.nativeType !== "string") throw new Error(`Unknown storage type kind for "${typeName}"; expected a codec-instance triple
|
|
173
|
+
if (typeof codecInstanceShape.codecId !== "string" || typeof codecInstanceShape.nativeType !== "string") throw new Error(`Unknown storage type kind for "${typeName}" in document-scoped storage.types; expected a codec-instance triple.`);
|
|
185
174
|
const codecId = serializeValue(codecInstanceShape.codecId);
|
|
186
175
|
const nativeType = serializeValue(codecInstanceShape.nativeType);
|
|
187
176
|
const typeParamsStr = serializeTypeParamsLiteral(codecInstanceShape.typeParams);
|
|
@@ -189,11 +178,81 @@ function generateStorageTypesType(types) {
|
|
|
189
178
|
}
|
|
190
179
|
return `{ ${typeEntries.join("; ")} }`;
|
|
191
180
|
}
|
|
181
|
+
function generatePostgresNamespaceTypesType(types) {
|
|
182
|
+
if (Object.keys(types).length === 0) return "Record<string, never>";
|
|
183
|
+
const typeEntries = [];
|
|
184
|
+
for (const [typeName, typeInstance] of Object.entries(types)) {
|
|
185
|
+
if (isPostgresEnumStorageEntry(typeInstance)) {
|
|
186
|
+
const codecId = serializeValue(typeInstance.codecId);
|
|
187
|
+
const nativeType = serializeValue(typeInstance.nativeType);
|
|
188
|
+
const name = serializeValue(typeInstance.name);
|
|
189
|
+
const valuesLiteral = typeInstance.values.map((v) => serializeValue(v)).join(", ");
|
|
190
|
+
typeEntries.push(`readonly ${serializeObjectKey(typeName)}: { readonly kind: 'postgres-enum'; readonly name: ${name}; readonly nativeType: ${nativeType}; readonly codecId: ${codecId}; readonly values: readonly [${valuesLiteral}] }`);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
throw new Error(`Unknown namespace storage type kind for "${typeName}"; expected postgres-enum in namespace.types.`);
|
|
194
|
+
}
|
|
195
|
+
return `{ ${typeEntries.join("; ")} }`;
|
|
196
|
+
}
|
|
197
|
+
function isPostgresSchemaNamespace(ns) {
|
|
198
|
+
return ns.kind === "schema" && "types" in ns && typeof ns.types === "object" && ns.types !== null;
|
|
199
|
+
}
|
|
200
|
+
function namespaceSerializedKind(ns) {
|
|
201
|
+
const kind = ns.kind;
|
|
202
|
+
if (kind === "schema") return `readonly kind: '${ns.id === UNBOUND_NAMESPACE_ID ? "postgres-unbound-schema" : "postgres-schema"}'`;
|
|
203
|
+
if (typeof kind === "string") return `readonly kind: ${serializeValue(kind)}`;
|
|
204
|
+
}
|
|
205
|
+
function generateTableLiteralType(table) {
|
|
206
|
+
const columns = [];
|
|
207
|
+
for (const [colName, col] of Object.entries(table.columns)) {
|
|
208
|
+
const nullable = col.nullable ? "true" : "false";
|
|
209
|
+
const nativeType = serializeValue(col.nativeType);
|
|
210
|
+
const codecId = serializeValue(col.codecId);
|
|
211
|
+
const defaultSpec = col.default ? col.default.kind === "literal" ? `; readonly default: { readonly kind: 'literal'; readonly value: DefaultLiteralValue<${codecId}, ${serializeValue(col.default.value)}> }` : `; readonly default: { readonly kind: 'function'; readonly expression: ${serializeValue(col.default.expression)} }` : "";
|
|
212
|
+
const typeParamsSpec = col.typeParams && Object.keys(col.typeParams).length > 0 ? `; readonly typeParams: ${serializeTypeParamsLiteral(col.typeParams)}` : "";
|
|
213
|
+
const typeRefSpec = col.typeRef ? `; readonly typeRef: ${serializeValue(col.typeRef)}` : "";
|
|
214
|
+
columns.push(`readonly ${colName}: { readonly nativeType: ${nativeType}; readonly codecId: ${codecId}; readonly nullable: ${nullable}${defaultSpec}${typeParamsSpec}${typeRefSpec} }`);
|
|
215
|
+
}
|
|
216
|
+
const tableParts = [`columns: { ${columns.join("; ")} }`];
|
|
217
|
+
if (table.primaryKey) {
|
|
218
|
+
const pkCols = table.primaryKey.columns.map((c) => serializeValue(c)).join(", ");
|
|
219
|
+
const pkName = table.primaryKey.name ? `; readonly name: ${serializeValue(table.primaryKey.name)}` : "";
|
|
220
|
+
tableParts.push(`primaryKey: { readonly columns: readonly [${pkCols}]${pkName} }`);
|
|
221
|
+
}
|
|
222
|
+
const uniques = table.uniques.map((u) => {
|
|
223
|
+
return `{ readonly columns: readonly [${u.columns.map((c) => serializeValue(c)).join(", ")}]${u.name ? `; readonly name: ${serializeValue(u.name)}` : ""} }`;
|
|
224
|
+
}).join(", ");
|
|
225
|
+
tableParts.push(`uniques: readonly [${uniques}]`);
|
|
226
|
+
const indexes = table.indexes.map((i) => {
|
|
227
|
+
return `{ readonly columns: readonly [${i.columns.map((c) => serializeValue(c)).join(", ")}]${i.name !== void 0 ? `; readonly name: ${serializeValue(i.name)}` : ""}${i.type !== void 0 ? `; readonly type: ${serializeValue(i.type)}` : ""}${i.options !== void 0 ? `; readonly options: ${serializeValue(i.options)}` : ""} }`;
|
|
228
|
+
}).join(", ");
|
|
229
|
+
tableParts.push(`indexes: readonly [${indexes}]`);
|
|
230
|
+
const fks = table.foreignKeys.map((fk) => {
|
|
231
|
+
const srcCols = fk.source.columns.map((c) => serializeValue(c)).join(", ");
|
|
232
|
+
const tgtCols = fk.target.columns.map((c) => serializeValue(c)).join(", ");
|
|
233
|
+
const name = fk.name ? `; readonly name: ${serializeValue(fk.name)}` : "";
|
|
234
|
+
return `{ readonly source: ${`{ readonly namespaceId: ${serializeValue(fk.source.namespaceId)}; readonly tableName: ${serializeValue(fk.source.tableName)}; readonly columns: readonly [${srcCols}] }`}; readonly target: ${`{ readonly namespaceId: ${serializeValue(fk.target.namespaceId)}; readonly tableName: ${serializeValue(fk.target.tableName)}; readonly columns: readonly [${tgtCols}] }`}${name}; readonly constraint: ${fk.constraint}; readonly index: ${fk.index} }`;
|
|
235
|
+
}).join(", ");
|
|
236
|
+
tableParts.push(`foreignKeys: readonly [${fks}]`);
|
|
237
|
+
return `{ ${tableParts.join("; ")} }`;
|
|
238
|
+
}
|
|
239
|
+
function generateTablesMapType(tables) {
|
|
240
|
+
const tableEntries = [];
|
|
241
|
+
for (const [tableName, table] of Object.entries(tables).sort(([a], [b]) => a.localeCompare(b))) tableEntries.push(`readonly ${tableName}: ${generateTableLiteralType(table)}`);
|
|
242
|
+
if (tableEntries.length === 0) return "{}";
|
|
243
|
+
return `{ ${tableEntries.join("; ")} }`;
|
|
244
|
+
}
|
|
192
245
|
function generateStorageNamespacesType(namespaces) {
|
|
193
246
|
const entries = Object.entries(namespaces ?? {}).sort(([a], [b]) => a.localeCompare(b));
|
|
194
247
|
if (entries.length === 0) return "Record<string, never>";
|
|
195
248
|
const parts = [];
|
|
196
|
-
for (const [name, ns] of entries)
|
|
249
|
+
for (const [name, ns] of entries) {
|
|
250
|
+
const kindPart = namespaceSerializedKind(ns);
|
|
251
|
+
const kindSuffix = kindPart ? `; ${kindPart}` : "";
|
|
252
|
+
const tablesType = generateTablesMapType(ns.tables);
|
|
253
|
+
const typesClause = isPostgresSchemaNamespace(ns) ? `; readonly types: ${generatePostgresNamespaceTypesType(ns.types)}` : "";
|
|
254
|
+
parts.push(`readonly ${serializeObjectKey(name)}: { readonly id: ${serializeValue(ns.id)}${kindSuffix}; readonly tables: ${tablesType}${typesClause} }`);
|
|
255
|
+
}
|
|
197
256
|
return `{ ${parts.join("; ")} }`;
|
|
198
257
|
}
|
|
199
258
|
//#endregion
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["col","codecShape"],"sources":["../src/index.ts"],"sourcesContent":["import type { Contract, ContractModel } from '@prisma-next/contract/types';\nimport { serializeObjectKey, serializeValue } from '@prisma-next/emitter/domain-type-generation';\nimport type {\n GenerateContractTypesOptions,\n ValidationContext,\n} from '@prisma-next/framework-components/emission';\nimport {\n isPostgresEnumStorageEntry,\n type SqlModelStorage,\n type SqlStorage,\n type StorageTable,\n type StorageTypeInstance,\n} from '@prisma-next/sql-contract/types';\nimport { assertDefined } from '@prisma-next/utils/assertions';\n\nfunction serializeTypeParamsLiteral(params: Record<string, unknown> | undefined): 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 entries.push(`readonly ${serializeObjectKey(key)}: ${serializeValue(value)}`);\n }\n\n return `{ ${entries.join('; ')} }`;\n}\n\nexport const sqlEmission = {\n id: 'sql',\n\n validateTypes(contract: Contract, _ctx: ValidationContext): void {\n const storage = contract.storage as unknown as SqlStorage | undefined;\n if (!storage?.tables) {\n return;\n }\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?.[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(contract: Contract): void {\n if (contract.targetFamily !== 'sql') {\n throw new Error(`Expected targetFamily \"sql\", got \"${contract.targetFamily}\"`);\n }\n\n const storage = contract.storage as unknown as SqlStorage | undefined;\n if (!storage?.tables) {\n throw new Error('SQL contract must have storage.tables');\n }\n\n const models = contract.models as Record<string, ContractModel<SqlModelStorage>>;\n const tableNames = new Set(Object.keys(storage.tables));\n\n if (models) {\n for (const [modelName, model] of Object.entries(models)) {\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 const columnNames = new Set(Object.keys(table.columns));\n const storageFields = model.storage.fields;\n if (!storageFields || Object.keys(storageFields).length === 0) {\n throw new Error(`Model \"${modelName}\" is missing storage.fields`);\n }\n\n for (const [fieldName, field] of Object.entries(storageFields)) {\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 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: 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 generateStorageType(contract: Contract, storageHashTypeName: string): string {\n const storage = contract.storage as unknown as SqlStorage;\n const tables: string[] = [];\n for (const [tableName, table] of Object.entries(storage.tables).sort(([a], [b]) =>\n a.localeCompare(b),\n )) {\n const columns: string[] = [];\n for (const [colName, col] of Object.entries(table.columns)) {\n const nullable = col.nullable ? 'true' : 'false';\n const nativeType = serializeValue(col.nativeType);\n const codecId = serializeValue(col.codecId);\n const defaultSpec = col.default\n ? col.default.kind === 'literal'\n ? `; readonly default: { readonly kind: 'literal'; readonly value: DefaultLiteralValue<${codecId}, ${serializeValue(\n col.default.value,\n )}> }`\n : `; readonly default: { readonly kind: 'function'; readonly expression: ${serializeValue(\n col.default.expression,\n )} }`\n : '';\n const typeParamsSpec =\n col.typeParams && Object.keys(col.typeParams).length > 0\n ? `; readonly typeParams: ${serializeTypeParamsLiteral(col.typeParams)}`\n : '';\n const typeRefSpec = col.typeRef ? `; readonly typeRef: ${serializeValue(col.typeRef)}` : '';\n columns.push(\n `readonly ${colName}: { readonly nativeType: ${nativeType}; readonly codecId: ${codecId}; readonly nullable: ${nullable}${defaultSpec}${typeParamsSpec}${typeRefSpec} }`,\n );\n }\n\n const tableParts: string[] = [`columns: { ${columns.join('; ')} }`];\n\n if (table.primaryKey) {\n const pkCols = table.primaryKey.columns.map((c) => serializeValue(c)).join(', ');\n const pkName = table.primaryKey.name\n ? `; readonly name: ${serializeValue(table.primaryKey.name)}`\n : '';\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) => serializeValue(c)).join(', ');\n const name = u.name ? `; readonly name: ${serializeValue(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) => serializeValue(c)).join(', ');\n const name = i.name !== undefined ? `; readonly name: ${serializeValue(i.name)}` : '';\n const indexType =\n i.type !== undefined ? `; readonly type: ${serializeValue(i.type)}` : '';\n const indexOptions =\n i.options !== undefined ? `; readonly options: ${serializeValue(i.options)}` : '';\n return `{ readonly columns: readonly [${cols}]${name}${indexType}${indexOptions} }`;\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) => serializeValue(c)).join(', ');\n const refCols = fk.references.columns.map((c: string) => serializeValue(c)).join(', ');\n const name = fk.name ? `; readonly name: ${serializeValue(fk.name)}` : '';\n 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} }`;\n })\n .join(', ');\n tableParts.push(`foreignKeys: readonly [${fks}]`);\n\n tables.push(`readonly ${tableName}: { ${tableParts.join('; ')} }`);\n }\n\n const typesType = generateStorageTypesType(storage.types);\n\n const namespacesType = generateStorageNamespacesType(storage.namespaces);\n\n return `{ readonly tables: { ${tables.join('; ')} }; readonly types: ${typesType}; readonly namespaces: ${namespacesType}; readonly storageHash: ${storageHashTypeName} }`;\n },\n\n generateModelStorageType(_modelName: string, model: ContractModel): string {\n const sqlModel = model as ContractModel<SqlModelStorage>;\n const tableName = sqlModel.storage.table;\n const storageFields = sqlModel.storage.fields;\n\n const storageParts = [`readonly table: ${serializeValue(tableName)}`];\n if (Object.keys(storageFields).length > 0) {\n const fieldParts: string[] = [];\n for (const [fieldName, field] of Object.entries(storageFields)) {\n fieldParts.push(\n `readonly ${serializeObjectKey(fieldName)}: { readonly column: ${serializeValue(field.column)} }`,\n );\n }\n storageParts.push(`readonly fields: { ${fieldParts.join('; ')} }`);\n }\n\n return `{ ${storageParts.join('; ')} }`;\n },\n\n resolveFieldTypeParams(\n _modelName: string,\n fieldName: string,\n model: ContractModel,\n contract: Contract,\n ): Record<string, unknown> | undefined {\n const sqlModel = model as ContractModel<SqlModelStorage>;\n const storageField = sqlModel.storage?.fields?.[fieldName];\n if (!storageField) return undefined;\n\n const storage = contract.storage as unknown as SqlStorage | undefined;\n if (!storage) return undefined;\n\n const tableName = sqlModel.storage.table;\n const table: StorageTable | undefined = storage.tables[tableName];\n if (!table) return undefined;\n\n const column = table.columns[storageField.column];\n if (!column) return undefined;\n\n if (column.typeRef) {\n const typeInstance = storage.types?.[column.typeRef];\n if (typeInstance === undefined) return undefined;\n if (isPostgresEnumStorageEntry(typeInstance)) {\n return { values: typeInstance.values };\n }\n // Fall back to structural codec-triple access when the literal\n // bypasses the runtime normaliser (e.g. test fixtures or\n // hand-written descriptor inputs that omit the\n // `kind: 'codec-instance'` discriminator).\n const codecShape = typeInstance as Partial<StorageTypeInstance>;\n return codecShape.typeParams;\n }\n return column.typeParams;\n },\n\n getFamilyImports(): string[] {\n return [\n 'import type {',\n ' ContractWithTypeMaps,',\n ' TypeMaps as TypeMapsType,',\n \"} from '@prisma-next/sql-contract/types';\",\n ];\n },\n\n getFamilyTypeAliases(options?: GenerateContractTypesOptions): string {\n const queryOperationTypeImports = options?.queryOperationTypeImports ?? [];\n const queryOperationAliases = queryOperationTypeImports\n .filter((imp) => imp.named === 'QueryOperationTypes')\n .map((imp) => `${imp.alias}<CodecTypes>`);\n const queryOperationTypes =\n queryOperationAliases.length > 0\n ? queryOperationAliases.join(' & ')\n : 'Record<string, never>';\n\n return [\n 'export type LaneCodecTypes = CodecTypes;',\n `export type QueryOperationTypes = ${queryOperationTypes};`,\n 'type DefaultLiteralValue<CodecId extends string, _Encoded> =',\n ' CodecId extends keyof CodecTypes',\n \" ? CodecTypes[CodecId]['output']\",\n ' : _Encoded;',\n ].join('\\n');\n },\n\n getTypeMapsExpression(): string {\n return 'TypeMapsType<CodecTypes, QueryOperationTypes, FieldOutputTypes, FieldInputTypes>';\n },\n\n getContractWrapper(contractBaseName: string, typeMapsName: string): string {\n return [\n `export type Contract = ContractWithTypeMaps<${contractBaseName}, ${typeMapsName}>;`,\n '',\n \"export type Tables = Contract['storage']['tables'];\",\n \"export type Models = Contract['models'];\",\n ].join('\\n');\n },\n} as const;\n\nfunction 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 if (isPostgresEnumStorageEntry(typeInstance)) {\n const codecId = serializeValue(\n // `codecBinding.codecId` lives on the live IR-class instance;\n // raw JSON envelopes carry `codecId` as an enumerable own\n // property. Read the structural-shape field so the emitter\n // works against both runtime forms.\n typeInstance.codecId,\n );\n const nativeType = serializeValue(typeInstance.nativeType);\n const typeParamsStr = serializeTypeParamsLiteral({\n values: typeInstance.values as unknown as readonly unknown[],\n });\n // Emit the resolved codec view (kind: 'codec-instance') so the\n // emitted .d.ts shape stays uniform across slot variants and\n // satisfies the polymorphic slot's structural alphabet. The\n // persisted JSON envelope still carries the IR's narrower kind\n // discriminator; the d.ts is the type-level codec-resolved view.\n typeEntries.push(\n `readonly ${typeName}: { readonly kind: 'codec-instance'; readonly codecId: ${codecId}; readonly nativeType: ${nativeType}; readonly typeParams: ${typeParamsStr} }`,\n );\n continue;\n }\n // The slot is polymorphic at the framework level; codec-instance\n // entries are the only non-IR-class kind today. The runtime\n // `SqlStorage` constructor stamps `kind: 'codec-instance'` on\n // plain codec triples; the emitter is forgiving about literal\n // inputs that bypass the constructor (test fixtures, hand-written\n // descriptors) and treats anything with the codec-triple shape as\n // a codec-instance.\n const codecInstanceShape = typeInstance as Partial<StorageTypeInstance>;\n if (\n typeof codecInstanceShape.codecId !== 'string' ||\n typeof codecInstanceShape.nativeType !== 'string'\n ) {\n throw new Error(\n `Unknown storage type kind for \"${typeName}\"; expected a codec-instance triple or a known IR-class kind.`,\n );\n }\n const codecId = serializeValue(codecInstanceShape.codecId);\n const nativeType = serializeValue(codecInstanceShape.nativeType);\n const typeParamsStr = serializeTypeParamsLiteral(codecInstanceShape.typeParams);\n typeEntries.push(\n `readonly ${typeName}: { readonly kind: 'codec-instance'; readonly codecId: ${codecId}; readonly nativeType: ${nativeType}; readonly typeParams: ${typeParamsStr} }`,\n );\n }\n\n return `{ ${typeEntries.join('; ')} }`;\n}\n\nfunction generateStorageNamespacesType(namespaces: SqlStorage['namespaces']): string {\n const entries = Object.entries(namespaces ?? {}).sort(([a], [b]) => a.localeCompare(b));\n if (entries.length === 0) {\n return 'Record<string, never>';\n }\n const parts: string[] = [];\n for (const [name, ns] of entries) {\n parts.push(`readonly ${serializeObjectKey(name)}: { readonly id: ${serializeValue(ns.id)} }`);\n }\n return `{ ${parts.join('; ')} }`;\n}\n"],"mappings":";;;;AAeA,SAAS,2BAA2B,QAAqD;CACvF,IAAI,CAAC,UAAU,OAAO,KAAK,OAAO,CAAC,WAAW,GAC5C,OAAO;CAGT,MAAM,UAAoB,EAAE;CAC5B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,EAC/C,QAAQ,KAAK,YAAY,mBAAmB,IAAI,CAAC,IAAI,eAAe,MAAM,GAAG;CAG/E,OAAO,KAAK,QAAQ,KAAK,KAAK,CAAC;;AAGjC,MAAa,cAAc;CACzB,IAAI;CAEJ,cAAc,UAAoB,MAA+B;EAC/D,MAAM,UAAU,SAAS;EACzB,IAAI,CAAC,SAAS,QACZ;EAGF,MAAM,cAAc;EAEpB,KAAK,MAAM,CAAC,WAAW,iBAAiB,OAAO,QAAQ,QAAQ,OAAO,EAAE;GACtE,MAAM,QAAQ;GACd,KAAK,MAAM,CAAC,SAAS,eAAe,OAAO,QAAQ,MAAM,QAAQ,EAAE;IAEjE,MAAM,UAAUA,WAAI;IACpB,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,WAAW,QAAQ,cAAc,UAAU,sBAAsB;IAInF,IAAI,CADU,QAAQ,MAAM,YAClB,GAAG,IACX,MAAM,IAAI,MACR,WAAW,QAAQ,cAAc,UAAU,iCAAiC,QAAQ,qCACrF;;;;CAMT,kBAAkB,UAA0B;EAC1C,IAAI,SAAS,iBAAiB,OAC5B,MAAM,IAAI,MAAM,qCAAqC,SAAS,aAAa,GAAG;EAGhF,MAAM,UAAU,SAAS;EACzB,IAAI,CAAC,SAAS,QACZ,MAAM,IAAI,MAAM,wCAAwC;EAG1D,MAAM,SAAS,SAAS;EACxB,MAAM,aAAa,IAAI,IAAI,OAAO,KAAK,QAAQ,OAAO,CAAC;EAEvD,IAAI,QACF,KAAK,MAAM,CAAC,WAAW,UAAU,OAAO,QAAQ,OAAO,EAAE;GACvD,IAAI,CAAC,MAAM,SAAS,OAClB,MAAM,IAAI,MAAM,UAAU,UAAU,4BAA4B;GAGlE,MAAM,YAAY,MAAM,QAAQ;GAChC,IAAI,CAAC,WAAW,IAAI,UAAU,EAC5B,MAAM,IAAI,MAAM,UAAU,UAAU,mCAAmC,UAAU,GAAG;GAGtF,MAAM,QAAkC,QAAQ,OAAO;GACvD,cAAc,OAAO,UAAU,UAAU,mCAAmC,UAAU,GAAG;GAEzF,MAAM,cAAc,IAAI,IAAI,OAAO,KAAK,MAAM,QAAQ,CAAC;GACvD,MAAM,gBAAgB,MAAM,QAAQ;GACpC,IAAI,CAAC,iBAAiB,OAAO,KAAK,cAAc,CAAC,WAAW,GAC1D,MAAM,IAAI,MAAM,UAAU,UAAU,6BAA6B;GAGnE,KAAK,MAAM,CAAC,WAAW,UAAU,OAAO,QAAQ,cAAc,EAAE;IAC9D,IAAI,CAAC,MAAM,QACT,MAAM,IAAI,MAAM,UAAU,UAAU,WAAW,UAAU,8BAA8B;IAGzF,IAAI,CAAC,YAAY,IAAI,MAAM,OAAO,EAChC,MAAM,IAAI,MACR,UAAU,UAAU,WAAW,UAAU,oCAAoC,MAAM,OAAO,cAAc,UAAU,GACnH;;GAIL,IAAI,CAAC,MAAM,aAAa,OAAO,MAAM,cAAc,UACjD,MAAM,IAAI,MACR,UAAU,UAAU,6DACrB;;EAKP,KAAK,MAAM,CAAC,WAAW,iBAAiB,OAAO,QAAQ,QAAQ,OAAO,EAAE;GACtE,MAAM,QAAQ;GACd,MAAM,cAAc,IAAI,IAAI,OAAO,KAAK,MAAM,QAAQ,CAAC;GAEvD,IAAI,CAAC,MAAM,QAAQ,MAAM,QAAQ,EAC/B,MAAM,IAAI,MACR,UAAU,UAAU,0DACrB;GAEH,IAAI,CAAC,MAAM,QAAQ,MAAM,QAAQ,EAC/B,MAAM,IAAI,MACR,UAAU,UAAU,0DACrB;GAEH,IAAI,CAAC,MAAM,QAAQ,MAAM,YAAY,EACnC,MAAM,IAAI,MACR,UAAU,UAAU,8DACrB;GAGH,IAAI,MAAM;SACH,MAAM,WAAW,MAAM,WAAW,SACrC,IAAI,CAAC,YAAY,IAAI,QAAQ,EAC3B,MAAM,IAAI,MACR,UAAU,UAAU,+CAA+C,QAAQ,GAC5E;;GAKP,KAAK,MAAM,UAAU,MAAM,SACzB,KAAK,MAAM,WAAW,OAAO,SAC3B,IAAI,CAAC,YAAY,IAAI,QAAQ,EAC3B,MAAM,IAAI,MACR,UAAU,UAAU,sDAAsD,QAAQ,GACnF;GAKP,KAAK,MAAM,SAAS,MAAM,SACxB,KAAK,MAAM,WAAW,MAAM,SAC1B,IAAI,CAAC,YAAY,IAAI,QAAQ,EAC3B,MAAM,IAAI,MACR,UAAU,UAAU,0CAA0C,QAAQ,GACvE;GAKP,KAAK,MAAM,MAAM,MAAM,aAAa;IAClC,KAAK,MAAM,WAAW,GAAG,SACvB,IAAI,CAAC,YAAY,IAAI,QAAQ,EAC3B,MAAM,IAAI,MACR,UAAU,UAAU,+CAA+C,QAAQ,GAC5E;IAIL,IAAI,CAAC,WAAW,IAAI,GAAG,WAAW,MAAM,EACtC,MAAM,IAAI,MACR,UAAU,UAAU,8CAA8C,GAAG,WAAW,MAAM,GACvF;IAGH,MAAM,kBAA4C,QAAQ,OAAO,GAAG,WAAW;IAC/E,cACE,iBACA,UAAU,UAAU,8CAA8C,GAAG,WAAW,MAAM,GACvF;IAED,MAAM,wBAAwB,IAAI,IAAI,OAAO,KAAK,gBAAgB,QAAQ,CAAC;IAC3E,KAAK,MAAM,WAAW,GAAG,WAAW,SAClC,IAAI,CAAC,sBAAsB,IAAI,QAAQ,EACrC,MAAM,IAAI,MACR,UAAU,UAAU,+CAA+C,QAAQ,cAAc,GAAG,WAAW,MAAM,GAC9G;IAIL,IAAI,GAAG,QAAQ,WAAW,GAAG,WAAW,QAAQ,QAC9C,MAAM,IAAI,MACR,UAAU,UAAU,6BAA6B,GAAG,QAAQ,OAAO,4CAA4C,GAAG,WAAW,QAAQ,OAAO,GAC7I;;;;CAMT,oBAAoB,UAAoB,qBAAqC;EAC3E,MAAM,UAAU,SAAS;EACzB,MAAM,SAAmB,EAAE;EAC3B,KAAK,MAAM,CAAC,WAAW,UAAU,OAAO,QAAQ,QAAQ,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAC1E,EAAE,cAAc,EAAE,CACnB,EAAE;GACD,MAAM,UAAoB,EAAE;GAC5B,KAAK,MAAM,CAAC,SAAS,QAAQ,OAAO,QAAQ,MAAM,QAAQ,EAAE;IAC1D,MAAM,WAAW,IAAI,WAAW,SAAS;IACzC,MAAM,aAAa,eAAe,IAAI,WAAW;IACjD,MAAM,UAAU,eAAe,IAAI,QAAQ;IAC3C,MAAM,cAAc,IAAI,UACpB,IAAI,QAAQ,SAAS,YACnB,uFAAuF,QAAQ,IAAI,eACjG,IAAI,QAAQ,MACb,CAAC,OACF,yEAAyE,eACvE,IAAI,QAAQ,WACb,CAAC,MACJ;IACJ,MAAM,iBACJ,IAAI,cAAc,OAAO,KAAK,IAAI,WAAW,CAAC,SAAS,IACnD,0BAA0B,2BAA2B,IAAI,WAAW,KACpE;IACN,MAAM,cAAc,IAAI,UAAU,uBAAuB,eAAe,IAAI,QAAQ,KAAK;IACzF,QAAQ,KACN,YAAY,QAAQ,2BAA2B,WAAW,sBAAsB,QAAQ,uBAAuB,WAAW,cAAc,iBAAiB,YAAY,IACtK;;GAGH,MAAM,aAAuB,CAAC,cAAc,QAAQ,KAAK,KAAK,CAAC,IAAI;GAEnE,IAAI,MAAM,YAAY;IACpB,MAAM,SAAS,MAAM,WAAW,QAAQ,KAAK,MAAM,eAAe,EAAE,CAAC,CAAC,KAAK,KAAK;IAChF,MAAM,SAAS,MAAM,WAAW,OAC5B,oBAAoB,eAAe,MAAM,WAAW,KAAK,KACzD;IACJ,WAAW,KAAK,6CAA6C,OAAO,GAAG,OAAO,IAAI;;GAGpF,MAAM,UAAU,MAAM,QACnB,KAAK,MAAM;IAGV,OAAO,iCAFM,EAAE,QAAQ,KAAK,MAAc,eAAe,EAAE,CAAC,CAAC,KAAK,KAEtB,CAAC,GADhC,EAAE,OAAO,oBAAoB,eAAe,EAAE,KAAK,KAAK,GAChB;KACrD,CACD,KAAK,KAAK;GACb,WAAW,KAAK,sBAAsB,QAAQ,GAAG;GAEjD,MAAM,UAAU,MAAM,QACnB,KAAK,MAAM;IAOV,OAAO,iCANM,EAAE,QAAQ,KAAK,MAAc,eAAe,EAAE,CAAC,CAAC,KAAK,KAMtB,CAAC,GALhC,EAAE,SAAS,KAAA,IAAY,oBAAoB,eAAe,EAAE,KAAK,KAAK,KAEjF,EAAE,SAAS,KAAA,IAAY,oBAAoB,eAAe,EAAE,KAAK,KAAK,KAEtE,EAAE,YAAY,KAAA,IAAY,uBAAuB,eAAe,EAAE,QAAQ,KAAK,GACD;KAChF,CACD,KAAK,KAAK;GACb,WAAW,KAAK,sBAAsB,QAAQ,GAAG;GAEjD,MAAM,MAAM,MAAM,YACf,KAAK,OAAO;IACX,MAAM,OAAO,GAAG,QAAQ,KAAK,MAAc,eAAe,EAAE,CAAC,CAAC,KAAK,KAAK;IACxE,MAAM,UAAU,GAAG,WAAW,QAAQ,KAAK,MAAc,eAAe,EAAE,CAAC,CAAC,KAAK,KAAK;IACtF,MAAM,OAAO,GAAG,OAAO,oBAAoB,eAAe,GAAG,KAAK,KAAK;IACvE,OAAO,iCAAiC,KAAK,4CAA4C,eAAe,GAAG,WAAW,MAAM,CAAC,gCAAgC,QAAQ,KAAK,KAAK,yBAAyB,GAAG,WAAW,oBAAoB,GAAG,MAAM;KACnP,CACD,KAAK,KAAK;GACb,WAAW,KAAK,0BAA0B,IAAI,GAAG;GAEjD,OAAO,KAAK,YAAY,UAAU,MAAM,WAAW,KAAK,KAAK,CAAC,IAAI;;EAGpE,MAAM,YAAY,yBAAyB,QAAQ,MAAM;EAEzD,MAAM,iBAAiB,8BAA8B,QAAQ,WAAW;EAExE,OAAO,wBAAwB,OAAO,KAAK,KAAK,CAAC,sBAAsB,UAAU,yBAAyB,eAAe,0BAA0B,oBAAoB;;CAGzK,yBAAyB,YAAoB,OAA8B;EACzE,MAAM,WAAW;EACjB,MAAM,YAAY,SAAS,QAAQ;EACnC,MAAM,gBAAgB,SAAS,QAAQ;EAEvC,MAAM,eAAe,CAAC,mBAAmB,eAAe,UAAU,GAAG;EACrE,IAAI,OAAO,KAAK,cAAc,CAAC,SAAS,GAAG;GACzC,MAAM,aAAuB,EAAE;GAC/B,KAAK,MAAM,CAAC,WAAW,UAAU,OAAO,QAAQ,cAAc,EAC5D,WAAW,KACT,YAAY,mBAAmB,UAAU,CAAC,uBAAuB,eAAe,MAAM,OAAO,CAAC,IAC/F;GAEH,aAAa,KAAK,sBAAsB,WAAW,KAAK,KAAK,CAAC,IAAI;;EAGpE,OAAO,KAAK,aAAa,KAAK,KAAK,CAAC;;CAGtC,uBACE,YACA,WACA,OACA,UACqC;EACrC,MAAM,WAAW;EACjB,MAAM,eAAe,SAAS,SAAS,SAAS;EAChD,IAAI,CAAC,cAAc,OAAO,KAAA;EAE1B,MAAM,UAAU,SAAS;EACzB,IAAI,CAAC,SAAS,OAAO,KAAA;EAErB,MAAM,YAAY,SAAS,QAAQ;EACnC,MAAM,QAAkC,QAAQ,OAAO;EACvD,IAAI,CAAC,OAAO,OAAO,KAAA;EAEnB,MAAM,SAAS,MAAM,QAAQ,aAAa;EAC1C,IAAI,CAAC,QAAQ,OAAO,KAAA;EAEpB,IAAI,OAAO,SAAS;GAClB,MAAM,eAAe,QAAQ,QAAQ,OAAO;GAC5C,IAAI,iBAAiB,KAAA,GAAW,OAAO,KAAA;GACvC,IAAI,2BAA2B,aAAa,EAC1C,OAAO,EAAE,QAAQ,aAAa,QAAQ;GAOxC,OAAOC,aAAW;;EAEpB,OAAO,OAAO;;CAGhB,mBAA6B;EAC3B,OAAO;GACL;GACA;GACA;GACA;GACD;;CAGH,qBAAqB,SAAgD;EAEnE,MAAM,yBAD4B,SAAS,6BAA6B,EAAE,EAEvE,QAAQ,QAAQ,IAAI,UAAU,sBAAsB,CACpD,KAAK,QAAQ,GAAG,IAAI,MAAM,cAAc;EAM3C,OAAO;GACL;GACA,qCANA,sBAAsB,SAAS,IAC3B,sBAAsB,KAAK,MAAM,GACjC,wBAIqD;GACzD;GACA;GACA;GACA;GACD,CAAC,KAAK,KAAK;;CAGd,wBAAgC;EAC9B,OAAO;;CAGT,mBAAmB,kBAA0B,cAA8B;EACzE,OAAO;GACL,+CAA+C,iBAAiB,IAAI,aAAa;GACjF;GACA;GACA;GACD,CAAC,KAAK,KAAK;;CAEf;AAED,SAAS,yBAAyB,OAAoC;CACpE,IAAI,CAAC,SAAS,OAAO,KAAK,MAAM,CAAC,WAAW,GAC1C,OAAO;CAGT,MAAM,cAAwB,EAAE;CAChC,KAAK,MAAM,CAAC,UAAU,iBAAiB,OAAO,QAAQ,MAAM,EAAE;EAC5D,IAAI,2BAA2B,aAAa,EAAE;GAC5C,MAAM,UAAU,eAKd,aAAa,QACd;GACD,MAAM,aAAa,eAAe,aAAa,WAAW;GAC1D,MAAM,gBAAgB,2BAA2B,EAC/C,QAAQ,aAAa,QACtB,CAAC;GAMF,YAAY,KACV,YAAY,SAAS,yDAAyD,QAAQ,yBAAyB,WAAW,yBAAyB,cAAc,IAClK;GACD;;EASF,MAAM,qBAAqB;EAC3B,IACE,OAAO,mBAAmB,YAAY,YACtC,OAAO,mBAAmB,eAAe,UAEzC,MAAM,IAAI,MACR,kCAAkC,SAAS,+DAC5C;EAEH,MAAM,UAAU,eAAe,mBAAmB,QAAQ;EAC1D,MAAM,aAAa,eAAe,mBAAmB,WAAW;EAChE,MAAM,gBAAgB,2BAA2B,mBAAmB,WAAW;EAC/E,YAAY,KACV,YAAY,SAAS,yDAAyD,QAAQ,yBAAyB,WAAW,yBAAyB,cAAc,IAClK;;CAGH,OAAO,KAAK,YAAY,KAAK,KAAK,CAAC;;AAGrC,SAAS,8BAA8B,YAA8C;CACnF,MAAM,UAAU,OAAO,QAAQ,cAAc,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC;CACvF,IAAI,QAAQ,WAAW,GACrB,OAAO;CAET,MAAM,QAAkB,EAAE;CAC1B,KAAK,MAAM,CAAC,MAAM,OAAO,SACvB,MAAM,KAAK,YAAY,mBAAmB,KAAK,CAAC,mBAAmB,eAAe,GAAG,GAAG,CAAC,IAAI;CAE/F,OAAO,KAAK,MAAM,KAAK,KAAK,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["col","codecShape"],"sources":["../src/index.ts"],"sourcesContent":["import type { Contract, ContractModel } from '@prisma-next/contract/types';\nimport { serializeObjectKey, serializeValue } from '@prisma-next/emitter/domain-type-generation';\nimport type {\n GenerateContractTypesOptions,\n ValidationContext,\n} from '@prisma-next/framework-components/emission';\nimport { type Namespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';\nimport {\n isPostgresEnumStorageEntry,\n type PostgresEnumStorageEntry,\n type SqlModelStorage,\n type SqlStorage,\n type StorageTable,\n type StorageTypeInstance,\n} from '@prisma-next/sql-contract/types';\n\nfunction serializeTypeParamsLiteral(params: Record<string, unknown> | undefined): 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 entries.push(`readonly ${serializeObjectKey(key)}: ${serializeValue(value)}`);\n }\n\n return `{ ${entries.join('; ')} }`;\n}\n\n/**\n * Name-only lookup that walks every namespace until it finds a table\n * with the given name. Survives this slice only for the two model-side\n * call sites whose `SqlModelStorage.table` is still a bare name without\n * a namespace coordinate; both call sites — and this helper — are\n * deleted by TML-2584, which promotes `SqlModelStorage` to carry the\n * model's namespace id so model→table resolution can use explicit\n * coordinates like the FK-ref site already does.\n */\nfunction findSqlTable(\n storage: SqlStorage,\n tableName: string,\n): { readonly table: StorageTable; readonly namespaceId: string } | undefined {\n for (const [namespaceId, ns] of Object.entries(storage.namespaces)) {\n const table = ns.tables[tableName] as StorageTable | undefined;\n if (table !== undefined) {\n return { table, namespaceId };\n }\n }\n return undefined;\n}\n\nfunction assertUniqueSqlTableNames(storage: SqlStorage): void {\n const seen = new Map<string, string>();\n for (const [namespaceId, ns] of Object.entries(storage.namespaces)) {\n for (const tableName of Object.keys(ns.tables)) {\n const existing = seen.get(tableName);\n if (existing !== undefined && existing !== namespaceId) {\n throw new Error(\n `Duplicate table name \"${tableName}\" in namespaces \"${existing}\" and \"${namespaceId}\"`,\n );\n }\n seen.set(tableName, namespaceId);\n }\n }\n}\n\nexport const sqlEmission = {\n id: 'sql',\n\n validateTypes(contract: Contract, _ctx: ValidationContext): void {\n const storage = contract.storage as unknown as SqlStorage | undefined;\n if (!storage?.namespaces) {\n return;\n }\n\n const typeIdRegex = /^([^/]+)\\/([^@]+)@(\\d+)$/;\n\n for (const ns of Object.values(storage.namespaces)) {\n for (const [tableName, tableUnknown] of Object.entries(ns.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?.[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\n validateStructure(contract: Contract): void {\n if (contract.targetFamily !== 'sql') {\n throw new Error(`Expected targetFamily \"sql\", got \"${contract.targetFamily}\"`);\n }\n\n const storage = contract.storage as unknown as SqlStorage | undefined;\n if (!storage?.namespaces) {\n throw new Error('SQL contract must have storage.namespaces');\n }\n\n assertUniqueSqlTableNames(storage);\n\n const models = contract.models as Record<string, ContractModel<SqlModelStorage>>;\n const tableNames = new Set<string>();\n for (const ns of Object.values(storage.namespaces)) {\n for (const t of Object.keys(ns.tables)) {\n tableNames.add(t);\n }\n }\n\n if (models) {\n for (const [modelName, model] of Object.entries(models)) {\n if (!model.storage?.table) {\n throw new Error(`Model \"${modelName}\" is missing storage.table`);\n }\n\n const tableName = model.storage.table;\n const located = findSqlTable(storage, tableName);\n if (!located) {\n throw new Error(`Model \"${modelName}\" references non-existent table \"${tableName}\"`);\n }\n\n const { table } = located;\n const columnNames = new Set(Object.keys(table.columns));\n const storageFields = model.storage.fields;\n if (!storageFields || Object.keys(storageFields).length === 0) {\n throw new Error(`Model \"${modelName}\" is missing storage.fields`);\n }\n\n for (const [fieldName, field] of Object.entries(storageFields)) {\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 ns of Object.values(storage.namespaces)) {\n for (const [tableName, tableUnknown] of Object.entries(ns.tables)) {\n const table = tableUnknown as StorageTable;\n const columnNames = new Set(Object.keys(table.columns));\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.source.columns) {\n if (!columnNames.has(colName)) {\n throw new Error(\n `Table \"${tableName}\" foreignKey references non-existent column \"${colName}\"`,\n );\n }\n }\n\n const referencedTable = storage.namespaces[fk.target.namespaceId]?.tables[\n fk.target.tableName\n ] as StorageTable | undefined;\n if (!referencedTable) {\n throw new Error(\n `Table \"${tableName}\" foreignKey references non-existent table \"${fk.target.tableName}\" in namespace \"${fk.target.namespaceId}\"`,\n );\n }\n\n const referencedColumnNames = new Set(Object.keys(referencedTable.columns));\n for (const colName of fk.target.columns) {\n if (!referencedColumnNames.has(colName)) {\n throw new Error(\n `Table \"${tableName}\" foreignKey references non-existent column \"${colName}\" in table \"${fk.target.tableName}\"`,\n );\n }\n }\n\n if (fk.source.columns.length !== fk.target.columns.length) {\n throw new Error(\n `Table \"${tableName}\" foreignKey column count (${fk.source.columns.length}) does not match referenced column count (${fk.target.columns.length})`,\n );\n }\n }\n }\n }\n },\n\n generateStorageType(contract: Contract, storageHashTypeName: string): string {\n const storage = contract.storage as unknown as SqlStorage;\n const namespacesType = generateStorageNamespacesType(storage.namespaces);\n const docTypes = generateDocumentScopedStorageTypesType(storage.types);\n const typesClause = docTypes === undefined ? '' : `; readonly types: ${docTypes}`;\n return `{ readonly namespaces: ${namespacesType}${typesClause}; readonly storageHash: ${storageHashTypeName} }`;\n },\n\n generateModelStorageType(_modelName: string, model: ContractModel): string {\n const sqlModel = model as ContractModel<SqlModelStorage>;\n const tableName = sqlModel.storage.table;\n const storageFields = sqlModel.storage.fields;\n\n const storageParts = [`readonly table: ${serializeValue(tableName)}`];\n if (Object.keys(storageFields).length > 0) {\n const fieldParts: string[] = [];\n for (const [fieldName, field] of Object.entries(storageFields)) {\n fieldParts.push(\n `readonly ${serializeObjectKey(fieldName)}: { readonly column: ${serializeValue(field.column)} }`,\n );\n }\n storageParts.push(`readonly fields: { ${fieldParts.join('; ')} }`);\n }\n\n return `{ ${storageParts.join('; ')} }`;\n },\n\n resolveFieldTypeParams(\n _modelName: string,\n fieldName: string,\n model: ContractModel,\n contract: Contract,\n ): Record<string, unknown> | undefined {\n const sqlModel = model as ContractModel<SqlModelStorage>;\n const storageField = sqlModel.storage?.fields?.[fieldName];\n if (!storageField) return undefined;\n\n const storage = contract.storage as unknown as SqlStorage | undefined;\n if (!storage) return undefined;\n\n const tableName = sqlModel.storage.table;\n const located = findSqlTable(storage, tableName);\n if (!located) return undefined;\n\n const column = located.table.columns[storageField.column];\n if (!column) return undefined;\n\n if (column.typeRef) {\n const ns = storage.namespaces[located.namespaceId];\n const nsTypes =\n ns !== undefined && 'types' in ns\n ? (\n ns as {\n types?: Readonly<Record<string, PostgresEnumStorageEntry | StorageTypeInstance>>;\n }\n ).types\n : undefined;\n const fromNamespace = nsTypes?.[column.typeRef];\n const fromDocument = storage.types?.[column.typeRef];\n const typeInstance = fromNamespace ?? fromDocument;\n if (typeInstance === undefined) return undefined;\n if (isPostgresEnumStorageEntry(typeInstance)) {\n return { values: typeInstance.values };\n }\n const codecShape = typeInstance as Partial<StorageTypeInstance>;\n return codecShape.typeParams;\n }\n return column.typeParams;\n },\n\n getFamilyImports(): string[] {\n return [\n 'import type {',\n ' ContractWithTypeMaps,',\n ' TypeMaps as TypeMapsType,',\n \"} from '@prisma-next/sql-contract/types';\",\n ];\n },\n\n getFamilyTypeAliases(options?: GenerateContractTypesOptions): string {\n const queryOperationTypeImports = options?.queryOperationTypeImports ?? [];\n const queryOperationAliases = queryOperationTypeImports\n .filter((imp) => imp.named === 'QueryOperationTypes')\n .map((imp) => `${imp.alias}<CodecTypes>`);\n const queryOperationTypes =\n queryOperationAliases.length > 0\n ? queryOperationAliases.join(' & ')\n : 'Record<string, never>';\n\n return [\n 'export type LaneCodecTypes = CodecTypes;',\n `export type QueryOperationTypes = ${queryOperationTypes};`,\n 'type DefaultLiteralValue<CodecId extends string, _Encoded> =',\n ' CodecId extends keyof CodecTypes',\n \" ? CodecTypes[CodecId]['output']\",\n ' : _Encoded;',\n ].join('\\n');\n },\n\n getTypeMapsExpression(): string {\n return 'TypeMapsType<CodecTypes, QueryOperationTypes, FieldOutputTypes, FieldInputTypes>';\n },\n\n getContractWrapper(contractBaseName: string, typeMapsName: string): string {\n return [\n `export type Contract = ContractWithTypeMaps<${contractBaseName}, ${typeMapsName}>;`,\n '',\n \"export type Namespaces = Contract['storage']['namespaces'];\",\n \"export type Models = Contract['models'];\",\n ].join('\\n');\n },\n} as const;\n\nfunction generateDocumentScopedStorageTypesType(types: SqlStorage['types']): string | undefined {\n if (!types || Object.keys(types).length === 0) {\n return undefined;\n }\n\n const typeEntries: string[] = [];\n for (const [typeName, typeInstance] of Object.entries(types)) {\n if (isPostgresEnumStorageEntry(typeInstance)) {\n throw new Error(\n `Document-scoped storage.types entry \"${typeName}\" is a postgres-enum; enums belong under storage.namespaces[namespaceId].types`,\n );\n }\n const codecInstanceShape = typeInstance as Partial<StorageTypeInstance>;\n if (\n typeof codecInstanceShape.codecId !== 'string' ||\n typeof codecInstanceShape.nativeType !== 'string'\n ) {\n throw new Error(\n `Unknown storage type kind for \"${typeName}\" in document-scoped storage.types; expected a codec-instance triple.`,\n );\n }\n const codecId = serializeValue(codecInstanceShape.codecId);\n const nativeType = serializeValue(codecInstanceShape.nativeType);\n const typeParamsStr = serializeTypeParamsLiteral(codecInstanceShape.typeParams);\n typeEntries.push(\n `readonly ${typeName}: { readonly kind: 'codec-instance'; readonly codecId: ${codecId}; readonly nativeType: ${nativeType}; readonly typeParams: ${typeParamsStr} }`,\n );\n }\n\n return `{ ${typeEntries.join('; ')} }`;\n}\n\nfunction generatePostgresNamespaceTypesType(\n types: Readonly<Record<string, PostgresEnumStorageEntry | StorageTypeInstance>>,\n): string {\n if (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 if (isPostgresEnumStorageEntry(typeInstance)) {\n const codecId = serializeValue(typeInstance.codecId);\n const nativeType = serializeValue(typeInstance.nativeType);\n const name = serializeValue(typeInstance.name);\n const valuesLiteral = typeInstance.values.map((v) => serializeValue(v)).join(', ');\n typeEntries.push(\n `readonly ${serializeObjectKey(typeName)}: { readonly kind: 'postgres-enum'; readonly name: ${name}; readonly nativeType: ${nativeType}; readonly codecId: ${codecId}; readonly values: readonly [${valuesLiteral}] }`,\n );\n continue;\n }\n throw new Error(\n `Unknown namespace storage type kind for \"${typeName}\"; expected postgres-enum in namespace.types.`,\n );\n }\n return `{ ${typeEntries.join('; ')} }`;\n}\n\nfunction isPostgresSchemaNamespace(ns: Namespace): ns is Namespace & {\n readonly types: Readonly<Record<string, PostgresEnumStorageEntry | StorageTypeInstance>>;\n} {\n return (\n (ns as { kind?: unknown }).kind === 'schema' &&\n 'types' in ns &&\n typeof (ns as { types?: unknown }).types === 'object' &&\n (ns as { types?: unknown }).types !== null\n );\n}\n\nfunction namespaceSerializedKind(ns: Namespace): string | undefined {\n const kind = (ns as { kind?: unknown }).kind;\n if (kind === 'schema') {\n const id = ns.id;\n const lit = id === UNBOUND_NAMESPACE_ID ? 'postgres-unbound-schema' : 'postgres-schema';\n return `readonly kind: '${lit}'`;\n }\n if (typeof kind === 'string') {\n return `readonly kind: ${serializeValue(kind)}`;\n }\n return undefined;\n}\n\nfunction generateTableLiteralType(table: StorageTable): string {\n const columns: string[] = [];\n for (const [colName, col] of Object.entries(table.columns)) {\n const nullable = col.nullable ? 'true' : 'false';\n const nativeType = serializeValue(col.nativeType);\n const codecId = serializeValue(col.codecId);\n const defaultSpec = col.default\n ? col.default.kind === 'literal'\n ? `; readonly default: { readonly kind: 'literal'; readonly value: DefaultLiteralValue<${codecId}, ${serializeValue(\n col.default.value,\n )}> }`\n : `; readonly default: { readonly kind: 'function'; readonly expression: ${serializeValue(\n col.default.expression,\n )} }`\n : '';\n const typeParamsSpec =\n col.typeParams && Object.keys(col.typeParams).length > 0\n ? `; readonly typeParams: ${serializeTypeParamsLiteral(col.typeParams)}`\n : '';\n const typeRefSpec = col.typeRef ? `; readonly typeRef: ${serializeValue(col.typeRef)}` : '';\n columns.push(\n `readonly ${colName}: { readonly nativeType: ${nativeType}; readonly codecId: ${codecId}; readonly nullable: ${nullable}${defaultSpec}${typeParamsSpec}${typeRefSpec} }`,\n );\n }\n\n const tableParts: string[] = [`columns: { ${columns.join('; ')} }`];\n\n if (table.primaryKey) {\n const pkCols = table.primaryKey.columns.map((c) => serializeValue(c)).join(', ');\n const pkName = table.primaryKey.name\n ? `; readonly name: ${serializeValue(table.primaryKey.name)}`\n : '';\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) => serializeValue(c)).join(', ');\n const name = u.name ? `; readonly name: ${serializeValue(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) => serializeValue(c)).join(', ');\n const name = i.name !== undefined ? `; readonly name: ${serializeValue(i.name)}` : '';\n const indexType = i.type !== undefined ? `; readonly type: ${serializeValue(i.type)}` : '';\n const indexOptions =\n i.options !== undefined ? `; readonly options: ${serializeValue(i.options)}` : '';\n return `{ readonly columns: readonly [${cols}]${name}${indexType}${indexOptions} }`;\n })\n .join(', ');\n tableParts.push(`indexes: readonly [${indexes}]`);\n\n const fks = table.foreignKeys\n .map((fk) => {\n const srcCols = fk.source.columns.map((c: string) => serializeValue(c)).join(', ');\n const tgtCols = fk.target.columns.map((c: string) => serializeValue(c)).join(', ');\n const name = fk.name ? `; readonly name: ${serializeValue(fk.name)}` : '';\n const srcRef = `{ readonly namespaceId: ${serializeValue(fk.source.namespaceId)}; readonly tableName: ${serializeValue(fk.source.tableName)}; readonly columns: readonly [${srcCols}] }`;\n const tgtRef = `{ readonly namespaceId: ${serializeValue(fk.target.namespaceId)}; readonly tableName: ${serializeValue(fk.target.tableName)}; readonly columns: readonly [${tgtCols}] }`;\n return `{ readonly source: ${srcRef}; readonly target: ${tgtRef}${name}; readonly constraint: ${fk.constraint}; readonly index: ${fk.index} }`;\n })\n .join(', ');\n tableParts.push(`foreignKeys: readonly [${fks}]`);\n\n return `{ ${tableParts.join('; ')} }`;\n}\n\nfunction generateTablesMapType(tables: Readonly<Record<string, StorageTable>>): string {\n const tableEntries: string[] = [];\n for (const [tableName, table] of Object.entries(tables).sort(([a], [b]) => a.localeCompare(b))) {\n tableEntries.push(`readonly ${tableName}: ${generateTableLiteralType(table)}`);\n }\n if (tableEntries.length === 0) {\n // Empty namespaces must emit `{}` (whose `keyof` is `never`), not\n // `Record<string, never>` (whose `keyof` is `string`). The latter\n // collapses `Db<C>` to a string-indexed shape and erases literal\n // table-name inference at every consumer site that walks all\n // namespaces (e.g. `db.sql.<tableName>`).\n return '{}';\n }\n return `{ ${tableEntries.join('; ')} }`;\n}\n\nfunction generateStorageNamespacesType(namespaces: SqlStorage['namespaces']): string {\n const entries = Object.entries(namespaces ?? {}).sort(([a], [b]) => a.localeCompare(b));\n if (entries.length === 0) {\n return 'Record<string, never>';\n }\n const parts: string[] = [];\n for (const [name, ns] of entries) {\n const kindPart = namespaceSerializedKind(ns);\n const kindSuffix = kindPart ? `; ${kindPart}` : '';\n const tablesType = generateTablesMapType(ns.tables as Readonly<Record<string, StorageTable>>);\n const isPg = isPostgresSchemaNamespace(ns);\n const typesClause = isPg\n ? `; readonly types: ${generatePostgresNamespaceTypesType(ns.types)}`\n : '';\n parts.push(\n `readonly ${serializeObjectKey(name)}: { readonly id: ${serializeValue(ns.id)}${kindSuffix}; readonly tables: ${tablesType}${typesClause} }`,\n );\n }\n return `{ ${parts.join('; ')} }`;\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/sql-contract-emitter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0-dev.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"description": "SQL emitter hook for Prisma Next",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@prisma-next/contract": "0.
|
|
10
|
-
"@prisma-next/emitter": "0.
|
|
11
|
-
"@prisma-next/
|
|
12
|
-
"@prisma-next/
|
|
9
|
+
"@prisma-next/contract": "0.10.0-dev.2",
|
|
10
|
+
"@prisma-next/emitter": "0.10.0-dev.2",
|
|
11
|
+
"@prisma-next/framework-components": "0.10.0-dev.2",
|
|
12
|
+
"@prisma-next/sql-contract": "0.10.0-dev.2",
|
|
13
|
+
"@prisma-next/utils": "0.10.0-dev.2"
|
|
13
14
|
},
|
|
14
15
|
"devDependencies": {
|
|
15
|
-
"@prisma-next/
|
|
16
|
-
"@prisma-next/
|
|
17
|
-
"@prisma-next/
|
|
18
|
-
"@prisma-next/tsdown": "0.9.0",
|
|
16
|
+
"@prisma-next/test-utils": "0.10.0-dev.2",
|
|
17
|
+
"@prisma-next/tsconfig": "0.10.0-dev.2",
|
|
18
|
+
"@prisma-next/tsdown": "0.10.0-dev.2",
|
|
19
19
|
"tsdown": "0.22.0",
|
|
20
20
|
"typescript": "5.9.3",
|
|
21
21
|
"vitest": "4.1.6"
|
package/src/index.ts
CHANGED
|
@@ -4,14 +4,15 @@ import type {
|
|
|
4
4
|
GenerateContractTypesOptions,
|
|
5
5
|
ValidationContext,
|
|
6
6
|
} from '@prisma-next/framework-components/emission';
|
|
7
|
+
import { type Namespace, UNBOUND_NAMESPACE_ID } from '@prisma-next/framework-components/ir';
|
|
7
8
|
import {
|
|
8
9
|
isPostgresEnumStorageEntry,
|
|
10
|
+
type PostgresEnumStorageEntry,
|
|
9
11
|
type SqlModelStorage,
|
|
10
12
|
type SqlStorage,
|
|
11
13
|
type StorageTable,
|
|
12
14
|
type StorageTypeInstance,
|
|
13
15
|
} from '@prisma-next/sql-contract/types';
|
|
14
|
-
import { assertDefined } from '@prisma-next/utils/assertions';
|
|
15
16
|
|
|
16
17
|
function serializeTypeParamsLiteral(params: Record<string, unknown> | undefined): string {
|
|
17
18
|
if (!params || Object.keys(params).length === 0) {
|
|
@@ -26,31 +27,70 @@ function serializeTypeParamsLiteral(params: Record<string, unknown> | undefined)
|
|
|
26
27
|
return `{ ${entries.join('; ')} }`;
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Name-only lookup that walks every namespace until it finds a table
|
|
32
|
+
* with the given name. Survives this slice only for the two model-side
|
|
33
|
+
* call sites whose `SqlModelStorage.table` is still a bare name without
|
|
34
|
+
* a namespace coordinate; both call sites — and this helper — are
|
|
35
|
+
* deleted by TML-2584, which promotes `SqlModelStorage` to carry the
|
|
36
|
+
* model's namespace id so model→table resolution can use explicit
|
|
37
|
+
* coordinates like the FK-ref site already does.
|
|
38
|
+
*/
|
|
39
|
+
function findSqlTable(
|
|
40
|
+
storage: SqlStorage,
|
|
41
|
+
tableName: string,
|
|
42
|
+
): { readonly table: StorageTable; readonly namespaceId: string } | undefined {
|
|
43
|
+
for (const [namespaceId, ns] of Object.entries(storage.namespaces)) {
|
|
44
|
+
const table = ns.tables[tableName] as StorageTable | undefined;
|
|
45
|
+
if (table !== undefined) {
|
|
46
|
+
return { table, namespaceId };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function assertUniqueSqlTableNames(storage: SqlStorage): void {
|
|
53
|
+
const seen = new Map<string, string>();
|
|
54
|
+
for (const [namespaceId, ns] of Object.entries(storage.namespaces)) {
|
|
55
|
+
for (const tableName of Object.keys(ns.tables)) {
|
|
56
|
+
const existing = seen.get(tableName);
|
|
57
|
+
if (existing !== undefined && existing !== namespaceId) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Duplicate table name "${tableName}" in namespaces "${existing}" and "${namespaceId}"`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
seen.set(tableName, namespaceId);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
29
67
|
export const sqlEmission = {
|
|
30
68
|
id: 'sql',
|
|
31
69
|
|
|
32
70
|
validateTypes(contract: Contract, _ctx: ValidationContext): void {
|
|
33
71
|
const storage = contract.storage as unknown as SqlStorage | undefined;
|
|
34
|
-
if (!storage?.
|
|
72
|
+
if (!storage?.namespaces) {
|
|
35
73
|
return;
|
|
36
74
|
}
|
|
37
75
|
|
|
38
76
|
const typeIdRegex = /^([^/]+)\/([^@]+)@(\d+)$/;
|
|
39
77
|
|
|
40
|
-
for (const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
78
|
+
for (const ns of Object.values(storage.namespaces)) {
|
|
79
|
+
for (const [tableName, tableUnknown] of Object.entries(ns.tables)) {
|
|
80
|
+
const table = tableUnknown as StorageTable;
|
|
81
|
+
for (const [colName, colUnknown] of Object.entries(table.columns)) {
|
|
82
|
+
const col = colUnknown as { codecId?: string };
|
|
83
|
+
const codecId = col.codecId;
|
|
84
|
+
if (!codecId) {
|
|
85
|
+
throw new Error(`Column "${colName}" in table "${tableName}" is missing codecId`);
|
|
86
|
+
}
|
|
48
87
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
88
|
+
const match = codecId.match(typeIdRegex);
|
|
89
|
+
if (!match?.[1]) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Column "${colName}" in table "${tableName}" has invalid codec ID format "${codecId}". Expected format: ns/name@version`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
54
94
|
}
|
|
55
95
|
}
|
|
56
96
|
}
|
|
@@ -62,12 +102,19 @@ export const sqlEmission = {
|
|
|
62
102
|
}
|
|
63
103
|
|
|
64
104
|
const storage = contract.storage as unknown as SqlStorage | undefined;
|
|
65
|
-
if (!storage?.
|
|
66
|
-
throw new Error('SQL contract must have storage.
|
|
105
|
+
if (!storage?.namespaces) {
|
|
106
|
+
throw new Error('SQL contract must have storage.namespaces');
|
|
67
107
|
}
|
|
68
108
|
|
|
109
|
+
assertUniqueSqlTableNames(storage);
|
|
110
|
+
|
|
69
111
|
const models = contract.models as Record<string, ContractModel<SqlModelStorage>>;
|
|
70
|
-
const tableNames = new Set(
|
|
112
|
+
const tableNames = new Set<string>();
|
|
113
|
+
for (const ns of Object.values(storage.namespaces)) {
|
|
114
|
+
for (const t of Object.keys(ns.tables)) {
|
|
115
|
+
tableNames.add(t);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
71
118
|
|
|
72
119
|
if (models) {
|
|
73
120
|
for (const [modelName, model] of Object.entries(models)) {
|
|
@@ -76,13 +123,12 @@ export const sqlEmission = {
|
|
|
76
123
|
}
|
|
77
124
|
|
|
78
125
|
const tableName = model.storage.table;
|
|
79
|
-
|
|
126
|
+
const located = findSqlTable(storage, tableName);
|
|
127
|
+
if (!located) {
|
|
80
128
|
throw new Error(`Model "${modelName}" references non-existent table "${tableName}"`);
|
|
81
129
|
}
|
|
82
130
|
|
|
83
|
-
const table
|
|
84
|
-
assertDefined(table, `Model "${modelName}" references non-existent table "${tableName}"`);
|
|
85
|
-
|
|
131
|
+
const { table } = located;
|
|
86
132
|
const columnNames = new Set(Object.keys(table.columns));
|
|
87
133
|
const storageFields = model.storage.fields;
|
|
88
134
|
if (!storageFields || Object.keys(storageFields).length === 0) {
|
|
@@ -109,175 +155,100 @@ export const sqlEmission = {
|
|
|
109
155
|
}
|
|
110
156
|
}
|
|
111
157
|
|
|
112
|
-
for (const
|
|
113
|
-
const
|
|
114
|
-
|
|
158
|
+
for (const ns of Object.values(storage.namespaces)) {
|
|
159
|
+
for (const [tableName, tableUnknown] of Object.entries(ns.tables)) {
|
|
160
|
+
const table = tableUnknown as StorageTable;
|
|
161
|
+
const columnNames = new Set(Object.keys(table.columns));
|
|
115
162
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
163
|
+
if (!Array.isArray(table.uniques)) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Table "${tableName}" is missing required field "uniques" (must be an array)`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (!Array.isArray(table.indexes)) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Table "${tableName}" is missing required field "indexes" (must be an array)`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
if (!Array.isArray(table.foreignKeys)) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`Table "${tableName}" is missing required field "foreignKeys" (must be an array)`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
131
178
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
179
|
+
if (table.primaryKey) {
|
|
180
|
+
for (const colName of table.primaryKey.columns) {
|
|
181
|
+
if (!columnNames.has(colName)) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`Table "${tableName}" primaryKey references non-existent column "${colName}"`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
138
186
|
}
|
|
139
187
|
}
|
|
140
|
-
}
|
|
141
188
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
189
|
+
for (const unique of table.uniques) {
|
|
190
|
+
for (const colName of unique.columns) {
|
|
191
|
+
if (!columnNames.has(colName)) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`Table "${tableName}" unique constraint references non-existent column "${colName}"`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
148
196
|
}
|
|
149
197
|
}
|
|
150
|
-
}
|
|
151
198
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
199
|
+
for (const index of table.indexes) {
|
|
200
|
+
for (const colName of index.columns) {
|
|
201
|
+
if (!columnNames.has(colName)) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`Table "${tableName}" index references non-existent column "${colName}"`,
|
|
204
|
+
);
|
|
205
|
+
}
|
|
158
206
|
}
|
|
159
207
|
}
|
|
160
|
-
}
|
|
161
208
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
209
|
+
for (const fk of table.foreignKeys) {
|
|
210
|
+
for (const colName of fk.source.columns) {
|
|
211
|
+
if (!columnNames.has(colName)) {
|
|
212
|
+
throw new Error(
|
|
213
|
+
`Table "${tableName}" foreignKey references non-existent column "${colName}"`,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const referencedTable = storage.namespaces[fk.target.namespaceId]?.tables[
|
|
219
|
+
fk.target.tableName
|
|
220
|
+
] as StorageTable | undefined;
|
|
221
|
+
if (!referencedTable) {
|
|
165
222
|
throw new Error(
|
|
166
|
-
`Table "${tableName}" foreignKey references non-existent
|
|
223
|
+
`Table "${tableName}" foreignKey references non-existent table "${fk.target.tableName}" in namespace "${fk.target.namespaceId}"`,
|
|
167
224
|
);
|
|
168
225
|
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (!tableNames.has(fk.references.table)) {
|
|
172
|
-
throw new Error(
|
|
173
|
-
`Table "${tableName}" foreignKey references non-existent table "${fk.references.table}"`,
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
226
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
227
|
+
const referencedColumnNames = new Set(Object.keys(referencedTable.columns));
|
|
228
|
+
for (const colName of fk.target.columns) {
|
|
229
|
+
if (!referencedColumnNames.has(colName)) {
|
|
230
|
+
throw new Error(
|
|
231
|
+
`Table "${tableName}" foreignKey references non-existent column "${colName}" in table "${fk.target.tableName}"`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
182
235
|
|
|
183
|
-
|
|
184
|
-
for (const colName of fk.references.columns) {
|
|
185
|
-
if (!referencedColumnNames.has(colName)) {
|
|
236
|
+
if (fk.source.columns.length !== fk.target.columns.length) {
|
|
186
237
|
throw new Error(
|
|
187
|
-
`Table "${tableName}" foreignKey
|
|
238
|
+
`Table "${tableName}" foreignKey column count (${fk.source.columns.length}) does not match referenced column count (${fk.target.columns.length})`,
|
|
188
239
|
);
|
|
189
240
|
}
|
|
190
241
|
}
|
|
191
|
-
|
|
192
|
-
if (fk.columns.length !== fk.references.columns.length) {
|
|
193
|
-
throw new Error(
|
|
194
|
-
`Table "${tableName}" foreignKey column count (${fk.columns.length}) does not match referenced column count (${fk.references.columns.length})`,
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
242
|
}
|
|
198
243
|
}
|
|
199
244
|
},
|
|
200
245
|
|
|
201
246
|
generateStorageType(contract: Contract, storageHashTypeName: string): string {
|
|
202
247
|
const storage = contract.storage as unknown as SqlStorage;
|
|
203
|
-
const tables: string[] = [];
|
|
204
|
-
for (const [tableName, table] of Object.entries(storage.tables).sort(([a], [b]) =>
|
|
205
|
-
a.localeCompare(b),
|
|
206
|
-
)) {
|
|
207
|
-
const columns: string[] = [];
|
|
208
|
-
for (const [colName, col] of Object.entries(table.columns)) {
|
|
209
|
-
const nullable = col.nullable ? 'true' : 'false';
|
|
210
|
-
const nativeType = serializeValue(col.nativeType);
|
|
211
|
-
const codecId = serializeValue(col.codecId);
|
|
212
|
-
const defaultSpec = col.default
|
|
213
|
-
? col.default.kind === 'literal'
|
|
214
|
-
? `; readonly default: { readonly kind: 'literal'; readonly value: DefaultLiteralValue<${codecId}, ${serializeValue(
|
|
215
|
-
col.default.value,
|
|
216
|
-
)}> }`
|
|
217
|
-
: `; readonly default: { readonly kind: 'function'; readonly expression: ${serializeValue(
|
|
218
|
-
col.default.expression,
|
|
219
|
-
)} }`
|
|
220
|
-
: '';
|
|
221
|
-
const typeParamsSpec =
|
|
222
|
-
col.typeParams && Object.keys(col.typeParams).length > 0
|
|
223
|
-
? `; readonly typeParams: ${serializeTypeParamsLiteral(col.typeParams)}`
|
|
224
|
-
: '';
|
|
225
|
-
const typeRefSpec = col.typeRef ? `; readonly typeRef: ${serializeValue(col.typeRef)}` : '';
|
|
226
|
-
columns.push(
|
|
227
|
-
`readonly ${colName}: { readonly nativeType: ${nativeType}; readonly codecId: ${codecId}; readonly nullable: ${nullable}${defaultSpec}${typeParamsSpec}${typeRefSpec} }`,
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const tableParts: string[] = [`columns: { ${columns.join('; ')} }`];
|
|
232
|
-
|
|
233
|
-
if (table.primaryKey) {
|
|
234
|
-
const pkCols = table.primaryKey.columns.map((c) => serializeValue(c)).join(', ');
|
|
235
|
-
const pkName = table.primaryKey.name
|
|
236
|
-
? `; readonly name: ${serializeValue(table.primaryKey.name)}`
|
|
237
|
-
: '';
|
|
238
|
-
tableParts.push(`primaryKey: { readonly columns: readonly [${pkCols}]${pkName} }`);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const uniques = table.uniques
|
|
242
|
-
.map((u) => {
|
|
243
|
-
const cols = u.columns.map((c: string) => serializeValue(c)).join(', ');
|
|
244
|
-
const name = u.name ? `; readonly name: ${serializeValue(u.name)}` : '';
|
|
245
|
-
return `{ readonly columns: readonly [${cols}]${name} }`;
|
|
246
|
-
})
|
|
247
|
-
.join(', ');
|
|
248
|
-
tableParts.push(`uniques: readonly [${uniques}]`);
|
|
249
|
-
|
|
250
|
-
const indexes = table.indexes
|
|
251
|
-
.map((i) => {
|
|
252
|
-
const cols = i.columns.map((c: string) => serializeValue(c)).join(', ');
|
|
253
|
-
const name = i.name !== undefined ? `; readonly name: ${serializeValue(i.name)}` : '';
|
|
254
|
-
const indexType =
|
|
255
|
-
i.type !== undefined ? `; readonly type: ${serializeValue(i.type)}` : '';
|
|
256
|
-
const indexOptions =
|
|
257
|
-
i.options !== undefined ? `; readonly options: ${serializeValue(i.options)}` : '';
|
|
258
|
-
return `{ readonly columns: readonly [${cols}]${name}${indexType}${indexOptions} }`;
|
|
259
|
-
})
|
|
260
|
-
.join(', ');
|
|
261
|
-
tableParts.push(`indexes: readonly [${indexes}]`);
|
|
262
|
-
|
|
263
|
-
const fks = table.foreignKeys
|
|
264
|
-
.map((fk) => {
|
|
265
|
-
const cols = fk.columns.map((c: string) => serializeValue(c)).join(', ');
|
|
266
|
-
const refCols = fk.references.columns.map((c: string) => serializeValue(c)).join(', ');
|
|
267
|
-
const name = fk.name ? `; readonly name: ${serializeValue(fk.name)}` : '';
|
|
268
|
-
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} }`;
|
|
269
|
-
})
|
|
270
|
-
.join(', ');
|
|
271
|
-
tableParts.push(`foreignKeys: readonly [${fks}]`);
|
|
272
|
-
|
|
273
|
-
tables.push(`readonly ${tableName}: { ${tableParts.join('; ')} }`);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const typesType = generateStorageTypesType(storage.types);
|
|
277
|
-
|
|
278
248
|
const namespacesType = generateStorageNamespacesType(storage.namespaces);
|
|
279
|
-
|
|
280
|
-
|
|
249
|
+
const docTypes = generateDocumentScopedStorageTypesType(storage.types);
|
|
250
|
+
const typesClause = docTypes === undefined ? '' : `; readonly types: ${docTypes}`;
|
|
251
|
+
return `{ readonly namespaces: ${namespacesType}${typesClause}; readonly storageHash: ${storageHashTypeName} }`;
|
|
281
252
|
},
|
|
282
253
|
|
|
283
254
|
generateModelStorageType(_modelName: string, model: ContractModel): string {
|
|
@@ -313,22 +284,29 @@ export const sqlEmission = {
|
|
|
313
284
|
if (!storage) return undefined;
|
|
314
285
|
|
|
315
286
|
const tableName = sqlModel.storage.table;
|
|
316
|
-
const
|
|
317
|
-
if (!
|
|
287
|
+
const located = findSqlTable(storage, tableName);
|
|
288
|
+
if (!located) return undefined;
|
|
318
289
|
|
|
319
|
-
const column = table.columns[storageField.column];
|
|
290
|
+
const column = located.table.columns[storageField.column];
|
|
320
291
|
if (!column) return undefined;
|
|
321
292
|
|
|
322
293
|
if (column.typeRef) {
|
|
323
|
-
const
|
|
294
|
+
const ns = storage.namespaces[located.namespaceId];
|
|
295
|
+
const nsTypes =
|
|
296
|
+
ns !== undefined && 'types' in ns
|
|
297
|
+
? (
|
|
298
|
+
ns as {
|
|
299
|
+
types?: Readonly<Record<string, PostgresEnumStorageEntry | StorageTypeInstance>>;
|
|
300
|
+
}
|
|
301
|
+
).types
|
|
302
|
+
: undefined;
|
|
303
|
+
const fromNamespace = nsTypes?.[column.typeRef];
|
|
304
|
+
const fromDocument = storage.types?.[column.typeRef];
|
|
305
|
+
const typeInstance = fromNamespace ?? fromDocument;
|
|
324
306
|
if (typeInstance === undefined) return undefined;
|
|
325
307
|
if (isPostgresEnumStorageEntry(typeInstance)) {
|
|
326
308
|
return { values: typeInstance.values };
|
|
327
309
|
}
|
|
328
|
-
// Fall back to structural codec-triple access when the literal
|
|
329
|
-
// bypasses the runtime normaliser (e.g. test fixtures or
|
|
330
|
-
// hand-written descriptor inputs that omit the
|
|
331
|
-
// `kind: 'codec-instance'` discriminator).
|
|
332
310
|
const codecShape = typeInstance as Partial<StorageTypeInstance>;
|
|
333
311
|
return codecShape.typeParams;
|
|
334
312
|
}
|
|
@@ -372,55 +350,31 @@ export const sqlEmission = {
|
|
|
372
350
|
return [
|
|
373
351
|
`export type Contract = ContractWithTypeMaps<${contractBaseName}, ${typeMapsName}>;`,
|
|
374
352
|
'',
|
|
375
|
-
"export type
|
|
353
|
+
"export type Namespaces = Contract['storage']['namespaces'];",
|
|
376
354
|
"export type Models = Contract['models'];",
|
|
377
355
|
].join('\n');
|
|
378
356
|
},
|
|
379
357
|
} as const;
|
|
380
358
|
|
|
381
|
-
function
|
|
359
|
+
function generateDocumentScopedStorageTypesType(types: SqlStorage['types']): string | undefined {
|
|
382
360
|
if (!types || Object.keys(types).length === 0) {
|
|
383
|
-
return
|
|
361
|
+
return undefined;
|
|
384
362
|
}
|
|
385
363
|
|
|
386
364
|
const typeEntries: string[] = [];
|
|
387
365
|
for (const [typeName, typeInstance] of Object.entries(types)) {
|
|
388
366
|
if (isPostgresEnumStorageEntry(typeInstance)) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
// raw JSON envelopes carry `codecId` as an enumerable own
|
|
392
|
-
// property. Read the structural-shape field so the emitter
|
|
393
|
-
// works against both runtime forms.
|
|
394
|
-
typeInstance.codecId,
|
|
395
|
-
);
|
|
396
|
-
const nativeType = serializeValue(typeInstance.nativeType);
|
|
397
|
-
const typeParamsStr = serializeTypeParamsLiteral({
|
|
398
|
-
values: typeInstance.values as unknown as readonly unknown[],
|
|
399
|
-
});
|
|
400
|
-
// Emit the resolved codec view (kind: 'codec-instance') so the
|
|
401
|
-
// emitted .d.ts shape stays uniform across slot variants and
|
|
402
|
-
// satisfies the polymorphic slot's structural alphabet. The
|
|
403
|
-
// persisted JSON envelope still carries the IR's narrower kind
|
|
404
|
-
// discriminator; the d.ts is the type-level codec-resolved view.
|
|
405
|
-
typeEntries.push(
|
|
406
|
-
`readonly ${typeName}: { readonly kind: 'codec-instance'; readonly codecId: ${codecId}; readonly nativeType: ${nativeType}; readonly typeParams: ${typeParamsStr} }`,
|
|
367
|
+
throw new Error(
|
|
368
|
+
`Document-scoped storage.types entry "${typeName}" is a postgres-enum; enums belong under storage.namespaces[namespaceId].types`,
|
|
407
369
|
);
|
|
408
|
-
continue;
|
|
409
370
|
}
|
|
410
|
-
// The slot is polymorphic at the framework level; codec-instance
|
|
411
|
-
// entries are the only non-IR-class kind today. The runtime
|
|
412
|
-
// `SqlStorage` constructor stamps `kind: 'codec-instance'` on
|
|
413
|
-
// plain codec triples; the emitter is forgiving about literal
|
|
414
|
-
// inputs that bypass the constructor (test fixtures, hand-written
|
|
415
|
-
// descriptors) and treats anything with the codec-triple shape as
|
|
416
|
-
// a codec-instance.
|
|
417
371
|
const codecInstanceShape = typeInstance as Partial<StorageTypeInstance>;
|
|
418
372
|
if (
|
|
419
373
|
typeof codecInstanceShape.codecId !== 'string' ||
|
|
420
374
|
typeof codecInstanceShape.nativeType !== 'string'
|
|
421
375
|
) {
|
|
422
376
|
throw new Error(
|
|
423
|
-
`Unknown storage type kind for "${typeName}"; expected a codec-instance triple
|
|
377
|
+
`Unknown storage type kind for "${typeName}" in document-scoped storage.types; expected a codec-instance triple.`,
|
|
424
378
|
);
|
|
425
379
|
}
|
|
426
380
|
const codecId = serializeValue(codecInstanceShape.codecId);
|
|
@@ -434,6 +388,143 @@ function generateStorageTypesType(types: SqlStorage['types']): string {
|
|
|
434
388
|
return `{ ${typeEntries.join('; ')} }`;
|
|
435
389
|
}
|
|
436
390
|
|
|
391
|
+
function generatePostgresNamespaceTypesType(
|
|
392
|
+
types: Readonly<Record<string, PostgresEnumStorageEntry | StorageTypeInstance>>,
|
|
393
|
+
): string {
|
|
394
|
+
if (Object.keys(types).length === 0) {
|
|
395
|
+
return 'Record<string, never>';
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const typeEntries: string[] = [];
|
|
399
|
+
for (const [typeName, typeInstance] of Object.entries(types)) {
|
|
400
|
+
if (isPostgresEnumStorageEntry(typeInstance)) {
|
|
401
|
+
const codecId = serializeValue(typeInstance.codecId);
|
|
402
|
+
const nativeType = serializeValue(typeInstance.nativeType);
|
|
403
|
+
const name = serializeValue(typeInstance.name);
|
|
404
|
+
const valuesLiteral = typeInstance.values.map((v) => serializeValue(v)).join(', ');
|
|
405
|
+
typeEntries.push(
|
|
406
|
+
`readonly ${serializeObjectKey(typeName)}: { readonly kind: 'postgres-enum'; readonly name: ${name}; readonly nativeType: ${nativeType}; readonly codecId: ${codecId}; readonly values: readonly [${valuesLiteral}] }`,
|
|
407
|
+
);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
throw new Error(
|
|
411
|
+
`Unknown namespace storage type kind for "${typeName}"; expected postgres-enum in namespace.types.`,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
return `{ ${typeEntries.join('; ')} }`;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function isPostgresSchemaNamespace(ns: Namespace): ns is Namespace & {
|
|
418
|
+
readonly types: Readonly<Record<string, PostgresEnumStorageEntry | StorageTypeInstance>>;
|
|
419
|
+
} {
|
|
420
|
+
return (
|
|
421
|
+
(ns as { kind?: unknown }).kind === 'schema' &&
|
|
422
|
+
'types' in ns &&
|
|
423
|
+
typeof (ns as { types?: unknown }).types === 'object' &&
|
|
424
|
+
(ns as { types?: unknown }).types !== null
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function namespaceSerializedKind(ns: Namespace): string | undefined {
|
|
429
|
+
const kind = (ns as { kind?: unknown }).kind;
|
|
430
|
+
if (kind === 'schema') {
|
|
431
|
+
const id = ns.id;
|
|
432
|
+
const lit = id === UNBOUND_NAMESPACE_ID ? 'postgres-unbound-schema' : 'postgres-schema';
|
|
433
|
+
return `readonly kind: '${lit}'`;
|
|
434
|
+
}
|
|
435
|
+
if (typeof kind === 'string') {
|
|
436
|
+
return `readonly kind: ${serializeValue(kind)}`;
|
|
437
|
+
}
|
|
438
|
+
return undefined;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function generateTableLiteralType(table: StorageTable): string {
|
|
442
|
+
const columns: string[] = [];
|
|
443
|
+
for (const [colName, col] of Object.entries(table.columns)) {
|
|
444
|
+
const nullable = col.nullable ? 'true' : 'false';
|
|
445
|
+
const nativeType = serializeValue(col.nativeType);
|
|
446
|
+
const codecId = serializeValue(col.codecId);
|
|
447
|
+
const defaultSpec = col.default
|
|
448
|
+
? col.default.kind === 'literal'
|
|
449
|
+
? `; readonly default: { readonly kind: 'literal'; readonly value: DefaultLiteralValue<${codecId}, ${serializeValue(
|
|
450
|
+
col.default.value,
|
|
451
|
+
)}> }`
|
|
452
|
+
: `; readonly default: { readonly kind: 'function'; readonly expression: ${serializeValue(
|
|
453
|
+
col.default.expression,
|
|
454
|
+
)} }`
|
|
455
|
+
: '';
|
|
456
|
+
const typeParamsSpec =
|
|
457
|
+
col.typeParams && Object.keys(col.typeParams).length > 0
|
|
458
|
+
? `; readonly typeParams: ${serializeTypeParamsLiteral(col.typeParams)}`
|
|
459
|
+
: '';
|
|
460
|
+
const typeRefSpec = col.typeRef ? `; readonly typeRef: ${serializeValue(col.typeRef)}` : '';
|
|
461
|
+
columns.push(
|
|
462
|
+
`readonly ${colName}: { readonly nativeType: ${nativeType}; readonly codecId: ${codecId}; readonly nullable: ${nullable}${defaultSpec}${typeParamsSpec}${typeRefSpec} }`,
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const tableParts: string[] = [`columns: { ${columns.join('; ')} }`];
|
|
467
|
+
|
|
468
|
+
if (table.primaryKey) {
|
|
469
|
+
const pkCols = table.primaryKey.columns.map((c) => serializeValue(c)).join(', ');
|
|
470
|
+
const pkName = table.primaryKey.name
|
|
471
|
+
? `; readonly name: ${serializeValue(table.primaryKey.name)}`
|
|
472
|
+
: '';
|
|
473
|
+
tableParts.push(`primaryKey: { readonly columns: readonly [${pkCols}]${pkName} }`);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const uniques = table.uniques
|
|
477
|
+
.map((u) => {
|
|
478
|
+
const cols = u.columns.map((c: string) => serializeValue(c)).join(', ');
|
|
479
|
+
const name = u.name ? `; readonly name: ${serializeValue(u.name)}` : '';
|
|
480
|
+
return `{ readonly columns: readonly [${cols}]${name} }`;
|
|
481
|
+
})
|
|
482
|
+
.join(', ');
|
|
483
|
+
tableParts.push(`uniques: readonly [${uniques}]`);
|
|
484
|
+
|
|
485
|
+
const indexes = table.indexes
|
|
486
|
+
.map((i) => {
|
|
487
|
+
const cols = i.columns.map((c: string) => serializeValue(c)).join(', ');
|
|
488
|
+
const name = i.name !== undefined ? `; readonly name: ${serializeValue(i.name)}` : '';
|
|
489
|
+
const indexType = i.type !== undefined ? `; readonly type: ${serializeValue(i.type)}` : '';
|
|
490
|
+
const indexOptions =
|
|
491
|
+
i.options !== undefined ? `; readonly options: ${serializeValue(i.options)}` : '';
|
|
492
|
+
return `{ readonly columns: readonly [${cols}]${name}${indexType}${indexOptions} }`;
|
|
493
|
+
})
|
|
494
|
+
.join(', ');
|
|
495
|
+
tableParts.push(`indexes: readonly [${indexes}]`);
|
|
496
|
+
|
|
497
|
+
const fks = table.foreignKeys
|
|
498
|
+
.map((fk) => {
|
|
499
|
+
const srcCols = fk.source.columns.map((c: string) => serializeValue(c)).join(', ');
|
|
500
|
+
const tgtCols = fk.target.columns.map((c: string) => serializeValue(c)).join(', ');
|
|
501
|
+
const name = fk.name ? `; readonly name: ${serializeValue(fk.name)}` : '';
|
|
502
|
+
const srcRef = `{ readonly namespaceId: ${serializeValue(fk.source.namespaceId)}; readonly tableName: ${serializeValue(fk.source.tableName)}; readonly columns: readonly [${srcCols}] }`;
|
|
503
|
+
const tgtRef = `{ readonly namespaceId: ${serializeValue(fk.target.namespaceId)}; readonly tableName: ${serializeValue(fk.target.tableName)}; readonly columns: readonly [${tgtCols}] }`;
|
|
504
|
+
return `{ readonly source: ${srcRef}; readonly target: ${tgtRef}${name}; readonly constraint: ${fk.constraint}; readonly index: ${fk.index} }`;
|
|
505
|
+
})
|
|
506
|
+
.join(', ');
|
|
507
|
+
tableParts.push(`foreignKeys: readonly [${fks}]`);
|
|
508
|
+
|
|
509
|
+
return `{ ${tableParts.join('; ')} }`;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function generateTablesMapType(tables: Readonly<Record<string, StorageTable>>): string {
|
|
513
|
+
const tableEntries: string[] = [];
|
|
514
|
+
for (const [tableName, table] of Object.entries(tables).sort(([a], [b]) => a.localeCompare(b))) {
|
|
515
|
+
tableEntries.push(`readonly ${tableName}: ${generateTableLiteralType(table)}`);
|
|
516
|
+
}
|
|
517
|
+
if (tableEntries.length === 0) {
|
|
518
|
+
// Empty namespaces must emit `{}` (whose `keyof` is `never`), not
|
|
519
|
+
// `Record<string, never>` (whose `keyof` is `string`). The latter
|
|
520
|
+
// collapses `Db<C>` to a string-indexed shape and erases literal
|
|
521
|
+
// table-name inference at every consumer site that walks all
|
|
522
|
+
// namespaces (e.g. `db.sql.<tableName>`).
|
|
523
|
+
return '{}';
|
|
524
|
+
}
|
|
525
|
+
return `{ ${tableEntries.join('; ')} }`;
|
|
526
|
+
}
|
|
527
|
+
|
|
437
528
|
function generateStorageNamespacesType(namespaces: SqlStorage['namespaces']): string {
|
|
438
529
|
const entries = Object.entries(namespaces ?? {}).sort(([a], [b]) => a.localeCompare(b));
|
|
439
530
|
if (entries.length === 0) {
|
|
@@ -441,7 +532,16 @@ function generateStorageNamespacesType(namespaces: SqlStorage['namespaces']): st
|
|
|
441
532
|
}
|
|
442
533
|
const parts: string[] = [];
|
|
443
534
|
for (const [name, ns] of entries) {
|
|
444
|
-
|
|
535
|
+
const kindPart = namespaceSerializedKind(ns);
|
|
536
|
+
const kindSuffix = kindPart ? `; ${kindPart}` : '';
|
|
537
|
+
const tablesType = generateTablesMapType(ns.tables as Readonly<Record<string, StorageTable>>);
|
|
538
|
+
const isPg = isPostgresSchemaNamespace(ns);
|
|
539
|
+
const typesClause = isPg
|
|
540
|
+
? `; readonly types: ${generatePostgresNamespaceTypesType(ns.types)}`
|
|
541
|
+
: '';
|
|
542
|
+
parts.push(
|
|
543
|
+
`readonly ${serializeObjectKey(name)}: { readonly id: ${serializeValue(ns.id)}${kindSuffix}; readonly tables: ${tablesType}${typesClause} }`,
|
|
544
|
+
);
|
|
445
545
|
}
|
|
446
546
|
return `{ ${parts.join('; ')} }`;
|
|
447
547
|
}
|