@supabase/postgrest-typegen 0.2.4 → 0.2.5
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/generation/go.js +2 -3
- package/dist/generation/python.js +21 -13
- package/dist/generation/swift.js +4 -4
- package/dist/generation/typescript.js +98 -89
- package/package.json +3 -1
- package/src/generation/go.ts +2 -3
- package/src/generation/python.ts +24 -13
- package/src/generation/swift.ts +4 -4
- package/src/generation/typescript.ts +105 -93
package/dist/generation/go.js
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export const generateGo = ({ schemas, tables, views, materializedViews, columns, types, }) => {
|
|
6
6
|
const columnsByTableId = columns.reduce((acc, curr) => {
|
|
7
|
-
acc[curr.table_id] ??= [];
|
|
8
|
-
acc[curr.table_id].push(curr);
|
|
7
|
+
(acc[curr.table_id] ??= []).push(curr);
|
|
9
8
|
return acc;
|
|
10
9
|
}, {});
|
|
11
10
|
const compositeTypes = types.filter((type) => type.attributes.length > 0);
|
|
@@ -50,7 +49,7 @@ function formatForGoTypeName(name) {
|
|
|
50
49
|
.split(/[^a-zA-Z0-9]/)
|
|
51
50
|
.map((word) => {
|
|
52
51
|
if (word) {
|
|
53
|
-
return `${word
|
|
52
|
+
return `${word.charAt(0).toUpperCase()}${word.slice(1)}`;
|
|
54
53
|
}
|
|
55
54
|
else {
|
|
56
55
|
return "";
|
|
@@ -55,8 +55,7 @@ class PythonContext {
|
|
|
55
55
|
this.schemas = Object.fromEntries(schemas.map((schema) => [schema.name, schema]));
|
|
56
56
|
this.types = Object.fromEntries(types.map((type) => [qualifiedTypeName(type.schema, type.name), type]));
|
|
57
57
|
this.columns = columns.reduce((acc, curr) => {
|
|
58
|
-
acc[curr.table_id] ??= [];
|
|
59
|
-
acc[curr.table_id].push(curr);
|
|
58
|
+
(acc[curr.table_id] ??= []).push(curr);
|
|
60
59
|
return acc;
|
|
61
60
|
}, {});
|
|
62
61
|
this.user_enums = Object.fromEntries(types
|
|
@@ -66,16 +65,25 @@ class PythonContext {
|
|
|
66
65
|
new PythonEnum(type),
|
|
67
66
|
]));
|
|
68
67
|
}
|
|
68
|
+
getSchema(name) {
|
|
69
|
+
const schema = this.schemas[name];
|
|
70
|
+
if (!schema) {
|
|
71
|
+
throw new Error(`Schema ${JSON.stringify(name)} is missing from the generator metadata`);
|
|
72
|
+
}
|
|
73
|
+
return schema;
|
|
74
|
+
}
|
|
69
75
|
resolveTypeName(name, typeSchema) {
|
|
70
76
|
const qualified = qualifiedTypeName(typeSchema, name);
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
const userEnum = this.user_enums[qualified];
|
|
78
|
+
if (userEnum) {
|
|
79
|
+
return userEnum.name;
|
|
73
80
|
}
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
const mappedType = PY_TYPE_MAP[name];
|
|
82
|
+
if (mappedType !== undefined) {
|
|
83
|
+
return mappedType;
|
|
76
84
|
}
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
const type = this.types[qualified];
|
|
86
|
+
if (type) {
|
|
79
87
|
return `${formatForPyClassName(type.schema)}${formatForPyClassName(type.name)}`;
|
|
80
88
|
}
|
|
81
89
|
return "Any";
|
|
@@ -108,7 +116,7 @@ class PythonContext {
|
|
|
108
116
|
// Postgres, so every field is inherently nullable.
|
|
109
117
|
return new PythonBaseModelAttr(attribute.name, type, true);
|
|
110
118
|
});
|
|
111
|
-
const schema = this.
|
|
119
|
+
const schema = this.getSchema(type.schema);
|
|
112
120
|
return new PythonBaseModel(type.name, schema, attributeEntries);
|
|
113
121
|
}
|
|
114
122
|
columnsToClassAttrs(table_id) {
|
|
@@ -129,7 +137,7 @@ class PythonContext {
|
|
|
129
137
|
});
|
|
130
138
|
}
|
|
131
139
|
tableToClass(table) {
|
|
132
|
-
const schema = this.
|
|
140
|
+
const schema = this.getSchema(table.schema);
|
|
133
141
|
const select = new PythonBaseModel(table.name, schema, this.columnsToClassAttrs(table.id));
|
|
134
142
|
const insert = new PythonTypedDict(table.name, "Insert", schema, this.columnsToDictAttrs(table.id, false));
|
|
135
143
|
const update = new PythonTypedDict(table.name, "Update", schema, this.columnsToDictAttrs(table.id, true));
|
|
@@ -137,11 +145,11 @@ class PythonContext {
|
|
|
137
145
|
}
|
|
138
146
|
viewToClass(view) {
|
|
139
147
|
const attributes = this.columnsToClassAttrs(view.id);
|
|
140
|
-
return new PythonBaseModel(view.name, this.
|
|
148
|
+
return new PythonBaseModel(view.name, this.getSchema(view.schema), attributes);
|
|
141
149
|
}
|
|
142
150
|
matViewToClass(matview) {
|
|
143
151
|
const attributes = this.columnsToClassAttrs(matview.id);
|
|
144
|
-
return new PythonBaseModel(matview.name, this.
|
|
152
|
+
return new PythonBaseModel(matview.name, this.getSchema(matview.schema), attributes);
|
|
145
153
|
}
|
|
146
154
|
}
|
|
147
155
|
const qualifiedTypeName = (schema, name) => `${schema}.${name}`;
|
|
@@ -332,7 +340,7 @@ function formatForPyClassName(name) {
|
|
|
332
340
|
.split(/[^a-zA-Z0-9]/)
|
|
333
341
|
.map((word) => {
|
|
334
342
|
if (word) {
|
|
335
|
-
return `${word
|
|
343
|
+
return `${word.charAt(0).toUpperCase()}${word.slice(1)}`;
|
|
336
344
|
}
|
|
337
345
|
else {
|
|
338
346
|
return "";
|
package/dist/generation/swift.js
CHANGED
|
@@ -177,9 +177,9 @@ export const generateSwift = (metadata, opts = {}) => {
|
|
|
177
177
|
t.id,
|
|
178
178
|
[],
|
|
179
179
|
]));
|
|
180
|
-
columns
|
|
181
|
-
.
|
|
182
|
-
|
|
180
|
+
for (const column of columns) {
|
|
181
|
+
columnsByTableId[column.table_id]?.push(column);
|
|
182
|
+
}
|
|
183
183
|
let output = [
|
|
184
184
|
"import Foundation",
|
|
185
185
|
"import Supabase",
|
|
@@ -322,7 +322,7 @@ function formatForSwiftTypeName(name) {
|
|
|
322
322
|
.split(/[^a-zA-Z0-9]+/)
|
|
323
323
|
.map((word) => {
|
|
324
324
|
if (word) {
|
|
325
|
-
return `${word
|
|
325
|
+
return `${word.charAt(0).toUpperCase()}${word.slice(1)}`;
|
|
326
326
|
}
|
|
327
327
|
else {
|
|
328
328
|
return "";
|
|
@@ -59,23 +59,33 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
59
59
|
tablesNamesByTableId[tableLike.id] = tableLike.name;
|
|
60
60
|
}
|
|
61
61
|
for (const column of columns) {
|
|
62
|
-
|
|
63
|
-
columnsByTableId[column.table_id].push(column);
|
|
64
|
-
}
|
|
62
|
+
columnsByTableId[column.table_id]?.push(column);
|
|
65
63
|
}
|
|
64
|
+
const getColumns = (tableId) => {
|
|
65
|
+
const tableColumns = columnsByTableId[tableId];
|
|
66
|
+
if (!tableColumns) {
|
|
67
|
+
throw new Error(`Relation ${tableId} is missing from the generator metadata`);
|
|
68
|
+
}
|
|
69
|
+
return tableColumns;
|
|
70
|
+
};
|
|
71
|
+
const getSchemaIntrospection = (schemaName) => {
|
|
72
|
+
const schemaIntrospection = introspectionBySchema[schemaName];
|
|
73
|
+
if (!schemaIntrospection) {
|
|
74
|
+
throw new Error(`Schema ${JSON.stringify(schemaName)} is missing from the generator metadata`);
|
|
75
|
+
}
|
|
76
|
+
return schemaIntrospection;
|
|
77
|
+
};
|
|
66
78
|
for (const type of types) {
|
|
67
79
|
typesById.set(type.id, type);
|
|
68
80
|
// Save all the types that are relation types for quicker lookup
|
|
69
81
|
if (type.type_relation_id) {
|
|
70
82
|
relationTypeByIds.set(type.id, type);
|
|
71
83
|
}
|
|
72
|
-
if (type.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
introspectionBySchema[type.schema].compositeTypes.push(type);
|
|
78
|
-
}
|
|
84
|
+
if (type.enums.length > 0) {
|
|
85
|
+
introspectionBySchema[type.schema]?.enums.push(type);
|
|
86
|
+
}
|
|
87
|
+
if (type.attributes.length > 0) {
|
|
88
|
+
introspectionBySchema[type.schema]?.compositeTypes.push(type);
|
|
79
89
|
}
|
|
80
90
|
}
|
|
81
91
|
const relationshipsByRelation = new Map();
|
|
@@ -105,56 +115,51 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
105
115
|
}`;
|
|
106
116
|
}
|
|
107
117
|
for (const table of tables) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
});
|
|
113
|
-
}
|
|
118
|
+
introspectionBySchema[table.schema]?.tables.push({
|
|
119
|
+
table,
|
|
120
|
+
relationships: getRelationships(table),
|
|
121
|
+
});
|
|
114
122
|
}
|
|
115
123
|
for (const table of foreignTables) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
});
|
|
121
|
-
}
|
|
124
|
+
introspectionBySchema[table.schema]?.tables.push({
|
|
125
|
+
table,
|
|
126
|
+
relationships: getRelationships(table),
|
|
127
|
+
});
|
|
122
128
|
}
|
|
123
129
|
for (const view of views) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
+
introspectionBySchema[view.schema]?.views.push({
|
|
131
|
+
view,
|
|
132
|
+
relationships: getRelationships(view),
|
|
133
|
+
});
|
|
130
134
|
}
|
|
131
135
|
for (const materializedView of materializedViews) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
});
|
|
142
|
-
}
|
|
136
|
+
introspectionBySchema[materializedView.schema]?.views.push({
|
|
137
|
+
view: {
|
|
138
|
+
...materializedView,
|
|
139
|
+
is_updatable: false,
|
|
140
|
+
is_insert_enabled: false,
|
|
141
|
+
is_update_enabled: false,
|
|
142
|
+
},
|
|
143
|
+
relationships: getRelationships(materializedView),
|
|
144
|
+
});
|
|
143
145
|
}
|
|
144
146
|
// Helper function to get table/view name from relation id
|
|
145
147
|
const getTableNameFromRelationId = (relationId, returnTypeId) => {
|
|
146
148
|
if (!relationId)
|
|
147
149
|
return null;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
+
const tableName = tablesNamesByTableId[relationId];
|
|
151
|
+
if (tableName)
|
|
152
|
+
return tableName;
|
|
150
153
|
// if it's a composite type we use the type name as relation name to allow sub-selecting fields of the composite type
|
|
151
154
|
const reltype = returnTypeId ? relationTypeByIds.get(returnTypeId) : null;
|
|
152
155
|
return reltype ? reltype.name : null;
|
|
153
156
|
};
|
|
154
157
|
for (const func of functions) {
|
|
155
|
-
|
|
158
|
+
const schemaIntrospection = introspectionBySchema[func.schema];
|
|
159
|
+
if (schemaIntrospection) {
|
|
156
160
|
// Get all input args (in, inout, variadic modes)
|
|
157
161
|
const inArgs = func.args.filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode));
|
|
162
|
+
const soleInArg = inArgs.length === 1 ? inArgs[0] : undefined;
|
|
158
163
|
if (
|
|
159
164
|
// Case 1: Function has no parameters
|
|
160
165
|
inArgs.length === 0 ||
|
|
@@ -171,16 +176,16 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
171
176
|
// Case 4: Single unnamed parameter of valid type (json, jsonb, text)
|
|
172
177
|
// Exclude all functions definitions that have only one single argument unnamed argument that isn't
|
|
173
178
|
// a json/jsonb/text as it won't be considered by PostgREST
|
|
174
|
-
(
|
|
175
|
-
|
|
176
|
-
(VALID_UNNAMED_FUNCTION_ARG_TYPES.has(
|
|
179
|
+
(soleInArg &&
|
|
180
|
+
soleInArg.name === "" &&
|
|
181
|
+
(VALID_UNNAMED_FUNCTION_ARG_TYPES.has(soleInArg.type_id) ||
|
|
177
182
|
// OR if the function have a single unnamed args which is another table (embeded function)
|
|
178
|
-
(relationTypeByIds.get(
|
|
183
|
+
(relationTypeByIds.get(soleInArg.type_id) &&
|
|
179
184
|
getTableNameFromRelationId(func.return_type_relation_id, func.return_type_id)) ||
|
|
180
185
|
// OR if the function takes a table row but doesn't qualify as embedded (for error reporting)
|
|
181
|
-
(relationTypeByIds.get(
|
|
186
|
+
(relationTypeByIds.get(soleInArg.type_id) &&
|
|
182
187
|
!getTableNameFromRelationId(func.return_type_relation_id, func.return_type_id))))) {
|
|
183
|
-
|
|
188
|
+
schemaIntrospection.functions.push({ fn: func, inArgs });
|
|
184
189
|
}
|
|
185
190
|
}
|
|
186
191
|
}
|
|
@@ -188,12 +193,12 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
188
193
|
// combines tables + foreign tables, `.views` combines views + materialized
|
|
189
194
|
// views — so the cross-collection name sort can't be expressed by the
|
|
190
195
|
// single-collection `sortGeneratorMetadata` pass and stays here.
|
|
191
|
-
for (const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
196
|
+
for (const schemaIntrospection of Object.values(introspectionBySchema)) {
|
|
197
|
+
schemaIntrospection.tables.sort((a, b) => compareStrings(a.table.name, b.table.name));
|
|
198
|
+
schemaIntrospection.views.sort((a, b) => compareStrings(a.view.name, b.view.name));
|
|
199
|
+
schemaIntrospection.functions.sort((a, b) => compareStrings(a.fn.name, b.fn.name));
|
|
200
|
+
schemaIntrospection.enums.sort((a, b) => compareStrings(a.name, b.name));
|
|
201
|
+
schemaIntrospection.compositeTypes.sort((a, b) => compareStrings(a.name, b.name));
|
|
197
202
|
}
|
|
198
203
|
const getFunctionTsReturnType = (fn, returnType) => {
|
|
199
204
|
// Determine if this function should have SetofOptions
|
|
@@ -213,8 +218,9 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
213
218
|
}
|
|
214
219
|
// Case 2: if the function has a single table argument, we need to add SetofOptions to allow selecting sub fields of the table
|
|
215
220
|
// and set the right "from" and "to" values to allow selecting from a table row
|
|
216
|
-
|
|
217
|
-
|
|
221
|
+
const soleArg = fn.args.length === 1 ? fn.args[0] : undefined;
|
|
222
|
+
if (soleArg) {
|
|
223
|
+
const relationType = relationTypeByIds.get(soleArg.type_id);
|
|
218
224
|
// Only add SetofOptions for functions with table arguments (embedded functions)
|
|
219
225
|
// or specific functions that RETURNS table-name
|
|
220
226
|
if (relationType) {
|
|
@@ -268,7 +274,7 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
268
274
|
introspectionBySchema[schema.name]?.views.find(({ view: { id } }) => id === fn.return_type_relation_id)?.view;
|
|
269
275
|
if (relation) {
|
|
270
276
|
return `{
|
|
271
|
-
${
|
|
277
|
+
${getColumns(relation.id)
|
|
272
278
|
.map((column) => generateColumnTsDefinition(schema, {
|
|
273
279
|
name: column.name,
|
|
274
280
|
format: column.format,
|
|
@@ -288,9 +294,10 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
288
294
|
};
|
|
289
295
|
// Special error case for functions that take table row but don't qualify as embedded functions
|
|
290
296
|
const hasTableRowError = (fn, inArgs) => {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
297
|
+
const soleInArg = inArgs.length === 1 ? inArgs[0] : undefined;
|
|
298
|
+
if (soleInArg &&
|
|
299
|
+
soleInArg.name === "" &&
|
|
300
|
+
relationTypeByIds.get(soleInArg.type_id) &&
|
|
294
301
|
!getTableNameFromRelationId(fn.return_type_relation_id, fn.return_type_id)) {
|
|
295
302
|
return true;
|
|
296
303
|
}
|
|
@@ -301,30 +308,33 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
301
308
|
// If there is a single function definition, there is no conflict
|
|
302
309
|
if (fns.length <= 1)
|
|
303
310
|
return null;
|
|
311
|
+
const soleInArg = inArgs.length === 1 ? inArgs[0] : undefined;
|
|
304
312
|
// Generic conflict detection patterns
|
|
305
313
|
// Pattern 1: No-args vs default-args conflicts
|
|
306
314
|
if (inArgs.length === 0) {
|
|
307
315
|
const conflictingFns = fns.filter(({ fn: otherFn, inArgs: otherInArgs }) => {
|
|
308
316
|
if (otherFn === fn)
|
|
309
317
|
return false;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
318
|
+
const otherSoleInArg = otherInArgs.length === 1 ? otherInArgs[0] : undefined;
|
|
319
|
+
return (otherSoleInArg &&
|
|
320
|
+
otherSoleInArg.name === "" &&
|
|
321
|
+
otherSoleInArg.has_default);
|
|
313
322
|
});
|
|
314
|
-
|
|
315
|
-
|
|
323
|
+
const conflictingFn = conflictingFns[0];
|
|
324
|
+
if (conflictingFn) {
|
|
316
325
|
const returnTypeName = typesById.get(conflictingFn.fn.return_type_id)?.name || "unknown";
|
|
317
326
|
return `Could not choose the best candidate function between: ${schema.name}.${fn.name}(), ${schema.name}.${fn.name}( => ${returnTypeName}). Try renaming the parameters or the function itself in the database so function overloading can be resolved`;
|
|
318
327
|
}
|
|
319
328
|
}
|
|
320
329
|
// Pattern 2: Same parameter name but different types (unresolvable overloads)
|
|
321
|
-
if (
|
|
330
|
+
if (soleInArg && soleInArg.name !== "") {
|
|
322
331
|
const conflictingFns = fns.filter(({ fn: otherFn, inArgs: otherInArgs }) => {
|
|
323
332
|
if (otherFn === fn)
|
|
324
333
|
return false;
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
334
|
+
const otherSoleInArg = otherInArgs.length === 1 ? otherInArgs[0] : undefined;
|
|
335
|
+
return (otherSoleInArg &&
|
|
336
|
+
otherSoleInArg.name === soleInArg.name &&
|
|
337
|
+
otherSoleInArg.type_id !== soleInArg.type_id);
|
|
328
338
|
});
|
|
329
339
|
if (conflictingFns.length > 0) {
|
|
330
340
|
const allConflictingFunctions = [{ fn, inArgs }, ...conflictingFns];
|
|
@@ -443,11 +453,12 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
443
453
|
* See supabase/postgres-meta#1034.
|
|
444
454
|
*/
|
|
445
455
|
const isComputedFieldOf = (fn, inArgs, relation) => {
|
|
446
|
-
|
|
456
|
+
const soleInArg = inArgs.length === 1 ? inArgs[0] : undefined;
|
|
457
|
+
if (soleInArg) {
|
|
447
458
|
// `relationTypeByIds` is already "types backed by a relation", so a hit
|
|
448
459
|
// identifies the relation regardless of how the argument was spelled.
|
|
449
460
|
// A `variadic` argument is an array type and never appears here.
|
|
450
|
-
const argType = relationTypeByIds.get(
|
|
461
|
+
const argType = relationTypeByIds.get(soleInArg.type_id);
|
|
451
462
|
if (argType) {
|
|
452
463
|
return argType.type_relation_id === relation.id;
|
|
453
464
|
}
|
|
@@ -463,7 +474,7 @@ export type Json = string | number | boolean | null | { [key: string]: Json | un
|
|
|
463
474
|
export type Database = {
|
|
464
475
|
${internal_supabase_schema}
|
|
465
476
|
${schemas.map((schema) => {
|
|
466
|
-
const { tables: schemaTables, views: schemaViews, functions: schemaFunctions, enums: schemaEnums, compositeTypes: schemaCompositeTypes, } =
|
|
477
|
+
const { tables: schemaTables, views: schemaViews, functions: schemaFunctions, enums: schemaEnums, compositeTypes: schemaCompositeTypes, } = getSchemaIntrospection(schema.name);
|
|
467
478
|
return `${JSON.stringify(schema.name)}: {
|
|
468
479
|
Tables: {
|
|
469
480
|
${schemaTables.length === 0
|
|
@@ -471,7 +482,7 @@ export type Database = {
|
|
|
471
482
|
: schemaTables.map(({ table, relationships, }) => `${JSON.stringify(table.name)}: {
|
|
472
483
|
Row: {
|
|
473
484
|
${[
|
|
474
|
-
...
|
|
485
|
+
...getColumns(table.id).map((column) => generateColumnTsDefinition(schema, {
|
|
475
486
|
name: column.name,
|
|
476
487
|
format: column.format,
|
|
477
488
|
type_schema: column.type_schema,
|
|
@@ -486,7 +497,7 @@ export type Database = {
|
|
|
486
497
|
]}
|
|
487
498
|
}
|
|
488
499
|
Insert: {
|
|
489
|
-
${
|
|
500
|
+
${getColumns(table.id).map((column) => {
|
|
490
501
|
if (column.identity_generation === "ALWAYS" ||
|
|
491
502
|
column.is_generated) {
|
|
492
503
|
return `${JSON.stringify(column.name)}?: never`;
|
|
@@ -503,7 +514,7 @@ export type Database = {
|
|
|
503
514
|
})}
|
|
504
515
|
}
|
|
505
516
|
Update: {
|
|
506
|
-
${
|
|
517
|
+
${getColumns(table.id).map((column) => {
|
|
507
518
|
if (column.identity_generation === "ALWAYS" ||
|
|
508
519
|
column.is_generated) {
|
|
509
520
|
return `${JSON.stringify(column.name)}?: never`;
|
|
@@ -528,7 +539,7 @@ export type Database = {
|
|
|
528
539
|
: schemaViews.map(({ view, relationships, }) => `${JSON.stringify(view.name)}: {
|
|
529
540
|
Row: {
|
|
530
541
|
${[
|
|
531
|
-
...
|
|
542
|
+
...getColumns(view.id).map((column) => generateColumnTsDefinition(schema, {
|
|
532
543
|
name: column.name,
|
|
533
544
|
format: column.format,
|
|
534
545
|
type_schema: column.type_schema,
|
|
@@ -545,7 +556,7 @@ export type Database = {
|
|
|
545
556
|
// version 1 documents) falls back to the old gate.
|
|
546
557
|
(view.is_insert_enabled ?? view.is_updatable)
|
|
547
558
|
? `Insert: {
|
|
548
|
-
${
|
|
559
|
+
${getColumns(view.id).map((column) => {
|
|
549
560
|
if (!column.is_updatable) {
|
|
550
561
|
return `${JSON.stringify(column.name)}?: never`;
|
|
551
562
|
}
|
|
@@ -561,7 +572,7 @@ export type Database = {
|
|
|
561
572
|
`
|
|
562
573
|
: ""}${(view.is_update_enabled ?? view.is_updatable)
|
|
563
574
|
? `Update: {
|
|
564
|
-
${
|
|
575
|
+
${getColumns(view.id).map((column) => {
|
|
565
576
|
if (!column.is_updatable) {
|
|
566
577
|
return `${JSON.stringify(column.name)}?: never`;
|
|
567
578
|
}
|
|
@@ -586,12 +597,11 @@ export type Database = {
|
|
|
586
597
|
return "[_ in never]: never";
|
|
587
598
|
}
|
|
588
599
|
const schemaFunctionsGroupedByName = schemaFunctions.reduce((acc, curr) => {
|
|
589
|
-
acc[curr.fn.name] ??= [];
|
|
590
|
-
acc[curr.fn.name].push(curr);
|
|
600
|
+
(acc[curr.fn.name] ??= []).push(curr);
|
|
591
601
|
return acc;
|
|
592
602
|
}, {});
|
|
593
|
-
for (const
|
|
594
|
-
|
|
603
|
+
for (const fns of Object.values(schemaFunctionsGroupedByName)) {
|
|
604
|
+
fns.sort((a, b) => compareStrings(a.fn.argument_types, b.fn.argument_types) ||
|
|
595
605
|
compareStrings(a.fn.return_type, b.fn.return_type));
|
|
596
606
|
}
|
|
597
607
|
return Object.entries(schemaFunctionsGroupedByName)
|
|
@@ -734,7 +744,7 @@ export type CompositeTypes<
|
|
|
734
744
|
|
|
735
745
|
export const Constants = {
|
|
736
746
|
${schemas.map((schema) => {
|
|
737
|
-
const schemaEnums =
|
|
747
|
+
const schemaEnums = getSchemaIntrospection(schema.name).enums;
|
|
738
748
|
return `${JSON.stringify(schema.name)}: {
|
|
739
749
|
Enums: {
|
|
740
750
|
${schemaEnums.map((enum_) => `${JSON.stringify(enum_.name)}: [${enum_.enums
|
|
@@ -795,18 +805,17 @@ typeSchema) => {
|
|
|
795
805
|
else {
|
|
796
806
|
const preferredSchema = typeSchema ?? schema.name;
|
|
797
807
|
const enumTypes = types.filter((type) => type.name === pgType && type.enums.length > 0);
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
enumTypes[0];
|
|
808
|
+
const enumType = enumTypes.find((type) => type.schema === preferredSchema) ?? enumTypes[0];
|
|
809
|
+
if (enumType) {
|
|
801
810
|
if (schemas.some(({ name }) => name === enumType.schema)) {
|
|
802
811
|
return `Database[${JSON.stringify(enumType.schema)}]['Enums'][${JSON.stringify(enumType.name)}]`;
|
|
803
812
|
}
|
|
804
813
|
return enumType.enums.map((variant) => JSON.stringify(variant)).join("|");
|
|
805
814
|
}
|
|
806
815
|
const compositeTypes = types.filter((type) => type.name === pgType && type.attributes.length > 0);
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
816
|
+
const compositeType = compositeTypes.find((type) => type.schema === preferredSchema) ??
|
|
817
|
+
compositeTypes[0];
|
|
818
|
+
if (compositeType) {
|
|
810
819
|
if (schemas.some(({ name }) => name === compositeType.schema)) {
|
|
811
820
|
return `Database[${JSON.stringify(compositeType.schema)}]['CompositeTypes'][${JSON.stringify(compositeType.name)}]`;
|
|
812
821
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supabase/postgrest-typegen",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Type generation for PostgREST from PostgreSQL schemas",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codegen",
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"scripts": {
|
|
56
56
|
"build": "tsc --project tsconfig.build.json",
|
|
57
57
|
"check-types": "tsc --noEmit",
|
|
58
|
+
"check-types:consumer": "tsc --project tsconfig.consumer.json",
|
|
58
59
|
"format-and-lint": "oxfmt --check . && oxlint --deny-warnings src test scripts",
|
|
59
60
|
"format-and-lint:fix": "oxfmt . && oxlint --fix src test scripts",
|
|
60
61
|
"knip": "knip",
|
|
@@ -66,6 +67,7 @@
|
|
|
66
67
|
},
|
|
67
68
|
"devDependencies": {
|
|
68
69
|
"@testcontainers/postgresql": "^12.1.0",
|
|
70
|
+
"@tsconfig/bun": "^1.0.11",
|
|
69
71
|
"@tsconfig/node-ts": "^23.6.4",
|
|
70
72
|
"@tsconfig/node24": "^24.0.5",
|
|
71
73
|
"@types/bun": "^1.4.0",
|
package/src/generation/go.ts
CHANGED
|
@@ -24,8 +24,7 @@ export const generateGo = ({
|
|
|
24
24
|
}: GeneratorMetadata): string => {
|
|
25
25
|
const columnsByTableId = columns.reduce(
|
|
26
26
|
(acc, curr) => {
|
|
27
|
-
acc[curr.table_id] ??= [];
|
|
28
|
-
acc[curr.table_id].push(curr);
|
|
27
|
+
(acc[curr.table_id] ??= []).push(curr);
|
|
29
28
|
return acc;
|
|
30
29
|
},
|
|
31
30
|
{} as Record<string, PostgresColumn[]>,
|
|
@@ -110,7 +109,7 @@ function formatForGoTypeName(name: string): string {
|
|
|
110
109
|
.split(/[^a-zA-Z0-9]/)
|
|
111
110
|
.map((word) => {
|
|
112
111
|
if (word) {
|
|
113
|
-
return `${word
|
|
112
|
+
return `${word.charAt(0).toUpperCase()}${word.slice(1)}`;
|
|
114
113
|
} else {
|
|
115
114
|
return "";
|
|
116
115
|
}
|
package/src/generation/python.ts
CHANGED
|
@@ -93,8 +93,7 @@ class PythonContext {
|
|
|
93
93
|
);
|
|
94
94
|
this.columns = columns.reduce(
|
|
95
95
|
(acc, curr) => {
|
|
96
|
-
acc[curr.table_id] ??= [];
|
|
97
|
-
acc[curr.table_id].push(curr);
|
|
96
|
+
(acc[curr.table_id] ??= []).push(curr);
|
|
98
97
|
return acc;
|
|
99
98
|
},
|
|
100
99
|
{} as Record<number, PostgresColumn[]>,
|
|
@@ -109,16 +108,28 @@ class PythonContext {
|
|
|
109
108
|
);
|
|
110
109
|
}
|
|
111
110
|
|
|
111
|
+
getSchema(name: string): PostgresSchema {
|
|
112
|
+
const schema = this.schemas[name];
|
|
113
|
+
if (!schema) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Schema ${JSON.stringify(name)} is missing from the generator metadata`,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return schema;
|
|
119
|
+
}
|
|
120
|
+
|
|
112
121
|
resolveTypeName(name: string, typeSchema: string): string {
|
|
113
122
|
const qualified = qualifiedTypeName(typeSchema, name);
|
|
114
|
-
|
|
115
|
-
|
|
123
|
+
const userEnum = this.user_enums[qualified];
|
|
124
|
+
if (userEnum) {
|
|
125
|
+
return userEnum.name;
|
|
116
126
|
}
|
|
117
|
-
|
|
118
|
-
|
|
127
|
+
const mappedType = PY_TYPE_MAP[name];
|
|
128
|
+
if (mappedType !== undefined) {
|
|
129
|
+
return mappedType;
|
|
119
130
|
}
|
|
120
|
-
|
|
121
|
-
|
|
131
|
+
const type = this.types[qualified];
|
|
132
|
+
if (type) {
|
|
122
133
|
return `${formatForPyClassName(type.schema)}${formatForPyClassName(type.name)}`;
|
|
123
134
|
}
|
|
124
135
|
return "Any";
|
|
@@ -158,7 +169,7 @@ class PythonContext {
|
|
|
158
169
|
},
|
|
159
170
|
);
|
|
160
171
|
|
|
161
|
-
const schema = this.
|
|
172
|
+
const schema = this.getSchema(type.schema);
|
|
162
173
|
return new PythonBaseModel(type.name, schema, attributeEntries);
|
|
163
174
|
}
|
|
164
175
|
|
|
@@ -192,7 +203,7 @@ class PythonContext {
|
|
|
192
203
|
tableToClass(
|
|
193
204
|
table: PostgresTable,
|
|
194
205
|
): [PythonBaseModel, PythonTypedDict, PythonTypedDict] {
|
|
195
|
-
const schema = this.
|
|
206
|
+
const schema = this.getSchema(table.schema);
|
|
196
207
|
const select = new PythonBaseModel(
|
|
197
208
|
table.name,
|
|
198
209
|
schema,
|
|
@@ -217,7 +228,7 @@ class PythonContext {
|
|
|
217
228
|
const attributes = this.columnsToClassAttrs(view.id);
|
|
218
229
|
return new PythonBaseModel(
|
|
219
230
|
view.name,
|
|
220
|
-
this.
|
|
231
|
+
this.getSchema(view.schema),
|
|
221
232
|
attributes,
|
|
222
233
|
);
|
|
223
234
|
}
|
|
@@ -226,7 +237,7 @@ class PythonContext {
|
|
|
226
237
|
const attributes = this.columnsToClassAttrs(matview.id);
|
|
227
238
|
return new PythonBaseModel(
|
|
228
239
|
matview.name,
|
|
229
|
-
this.
|
|
240
|
+
this.getSchema(matview.schema),
|
|
230
241
|
attributes,
|
|
231
242
|
);
|
|
232
243
|
}
|
|
@@ -463,7 +474,7 @@ function formatForPyClassName(name: string): string {
|
|
|
463
474
|
.split(/[^a-zA-Z0-9]/)
|
|
464
475
|
.map((word) => {
|
|
465
476
|
if (word) {
|
|
466
|
-
return `${word
|
|
477
|
+
return `${word.charAt(0).toUpperCase()}${word.slice(1)}`;
|
|
467
478
|
} else {
|
|
468
479
|
return "";
|
|
469
480
|
}
|
package/src/generation/swift.ts
CHANGED
|
@@ -303,9 +303,9 @@ export const generateSwift = (
|
|
|
303
303
|
]),
|
|
304
304
|
);
|
|
305
305
|
|
|
306
|
-
columns
|
|
307
|
-
.
|
|
308
|
-
|
|
306
|
+
for (const column of columns) {
|
|
307
|
+
columnsByTableId[column.table_id]?.push(column);
|
|
308
|
+
}
|
|
309
309
|
|
|
310
310
|
let output = [
|
|
311
311
|
"import Foundation",
|
|
@@ -492,7 +492,7 @@ function formatForSwiftTypeName(name: string): string {
|
|
|
492
492
|
.split(/[^a-zA-Z0-9]+/)
|
|
493
493
|
.map((word) => {
|
|
494
494
|
if (word) {
|
|
495
|
-
return `${word
|
|
495
|
+
return `${word.charAt(0).toUpperCase()}${word.slice(1)}`;
|
|
496
496
|
} else {
|
|
497
497
|
return "";
|
|
498
498
|
}
|
|
@@ -162,10 +162,26 @@ export const generateTypescript = async (
|
|
|
162
162
|
tablesNamesByTableId[tableLike.id] = tableLike.name;
|
|
163
163
|
}
|
|
164
164
|
for (const column of columns) {
|
|
165
|
-
|
|
166
|
-
columnsByTableId[column.table_id].push(column);
|
|
167
|
-
}
|
|
165
|
+
columnsByTableId[column.table_id]?.push(column);
|
|
168
166
|
}
|
|
167
|
+
const getColumns = (tableId: number): PostgresColumn[] => {
|
|
168
|
+
const tableColumns = columnsByTableId[tableId];
|
|
169
|
+
if (!tableColumns) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
`Relation ${tableId} is missing from the generator metadata`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
return tableColumns;
|
|
175
|
+
};
|
|
176
|
+
const getSchemaIntrospection = (schemaName: string) => {
|
|
177
|
+
const schemaIntrospection = introspectionBySchema[schemaName];
|
|
178
|
+
if (!schemaIntrospection) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`Schema ${JSON.stringify(schemaName)} is missing from the generator metadata`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return schemaIntrospection;
|
|
184
|
+
};
|
|
169
185
|
|
|
170
186
|
for (const type of types) {
|
|
171
187
|
typesById.set(type.id, type);
|
|
@@ -173,13 +189,11 @@ export const generateTypescript = async (
|
|
|
173
189
|
if (type.type_relation_id) {
|
|
174
190
|
relationTypeByIds.set(type.id, type);
|
|
175
191
|
}
|
|
176
|
-
if (type.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
introspectionBySchema[type.schema].compositeTypes.push(type);
|
|
182
|
-
}
|
|
192
|
+
if (type.enums.length > 0) {
|
|
193
|
+
introspectionBySchema[type.schema]?.enums.push(type);
|
|
194
|
+
}
|
|
195
|
+
if (type.attributes.length > 0) {
|
|
196
|
+
introspectionBySchema[type.schema]?.compositeTypes.push(type);
|
|
183
197
|
}
|
|
184
198
|
}
|
|
185
199
|
|
|
@@ -233,41 +247,33 @@ export const generateTypescript = async (
|
|
|
233
247
|
}
|
|
234
248
|
|
|
235
249
|
for (const table of tables) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
});
|
|
241
|
-
}
|
|
250
|
+
introspectionBySchema[table.schema]?.tables.push({
|
|
251
|
+
table,
|
|
252
|
+
relationships: getRelationships(table),
|
|
253
|
+
});
|
|
242
254
|
}
|
|
243
255
|
for (const table of foreignTables) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
});
|
|
249
|
-
}
|
|
256
|
+
introspectionBySchema[table.schema]?.tables.push({
|
|
257
|
+
table,
|
|
258
|
+
relationships: getRelationships(table),
|
|
259
|
+
});
|
|
250
260
|
}
|
|
251
261
|
for (const view of views) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
});
|
|
257
|
-
}
|
|
262
|
+
introspectionBySchema[view.schema]?.views.push({
|
|
263
|
+
view,
|
|
264
|
+
relationships: getRelationships(view),
|
|
265
|
+
});
|
|
258
266
|
}
|
|
259
267
|
for (const materializedView of materializedViews) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
});
|
|
270
|
-
}
|
|
268
|
+
introspectionBySchema[materializedView.schema]?.views.push({
|
|
269
|
+
view: {
|
|
270
|
+
...materializedView,
|
|
271
|
+
is_updatable: false,
|
|
272
|
+
is_insert_enabled: false,
|
|
273
|
+
is_update_enabled: false,
|
|
274
|
+
},
|
|
275
|
+
relationships: getRelationships(materializedView),
|
|
276
|
+
});
|
|
271
277
|
}
|
|
272
278
|
// Helper function to get table/view name from relation id
|
|
273
279
|
const getTableNameFromRelationId = (
|
|
@@ -276,20 +282,22 @@ export const generateTypescript = async (
|
|
|
276
282
|
): string | null => {
|
|
277
283
|
if (!relationId) return null;
|
|
278
284
|
|
|
279
|
-
|
|
280
|
-
|
|
285
|
+
const tableName = tablesNamesByTableId[relationId];
|
|
286
|
+
if (tableName) return tableName;
|
|
281
287
|
// if it's a composite type we use the type name as relation name to allow sub-selecting fields of the composite type
|
|
282
288
|
const reltype = returnTypeId ? relationTypeByIds.get(returnTypeId) : null;
|
|
283
289
|
return reltype ? reltype.name : null;
|
|
284
290
|
};
|
|
285
291
|
|
|
286
292
|
for (const func of functions) {
|
|
287
|
-
|
|
293
|
+
const schemaIntrospection = introspectionBySchema[func.schema];
|
|
294
|
+
if (schemaIntrospection) {
|
|
288
295
|
// Get all input args (in, inout, variadic modes)
|
|
289
296
|
const inArgs = func.args.filter(({ mode }) =>
|
|
290
297
|
VALID_FUNCTION_ARGS_MODE.has(mode),
|
|
291
298
|
);
|
|
292
299
|
|
|
300
|
+
const soleInArg = inArgs.length === 1 ? inArgs[0] : undefined;
|
|
293
301
|
if (
|
|
294
302
|
// Case 1: Function has no parameters
|
|
295
303
|
inArgs.length === 0 ||
|
|
@@ -308,23 +316,23 @@ export const generateTypescript = async (
|
|
|
308
316
|
// Case 4: Single unnamed parameter of valid type (json, jsonb, text)
|
|
309
317
|
// Exclude all functions definitions that have only one single argument unnamed argument that isn't
|
|
310
318
|
// a json/jsonb/text as it won't be considered by PostgREST
|
|
311
|
-
(
|
|
312
|
-
|
|
313
|
-
(VALID_UNNAMED_FUNCTION_ARG_TYPES.has(
|
|
319
|
+
(soleInArg &&
|
|
320
|
+
soleInArg.name === "" &&
|
|
321
|
+
(VALID_UNNAMED_FUNCTION_ARG_TYPES.has(soleInArg.type_id) ||
|
|
314
322
|
// OR if the function have a single unnamed args which is another table (embeded function)
|
|
315
|
-
(relationTypeByIds.get(
|
|
323
|
+
(relationTypeByIds.get(soleInArg.type_id) &&
|
|
316
324
|
getTableNameFromRelationId(
|
|
317
325
|
func.return_type_relation_id,
|
|
318
326
|
func.return_type_id,
|
|
319
327
|
)) ||
|
|
320
328
|
// OR if the function takes a table row but doesn't qualify as embedded (for error reporting)
|
|
321
|
-
(relationTypeByIds.get(
|
|
329
|
+
(relationTypeByIds.get(soleInArg.type_id) &&
|
|
322
330
|
!getTableNameFromRelationId(
|
|
323
331
|
func.return_type_relation_id,
|
|
324
332
|
func.return_type_id,
|
|
325
333
|
))))
|
|
326
334
|
) {
|
|
327
|
-
|
|
335
|
+
schemaIntrospection.functions.push({ fn: func, inArgs });
|
|
328
336
|
}
|
|
329
337
|
}
|
|
330
338
|
}
|
|
@@ -332,20 +340,18 @@ export const generateTypescript = async (
|
|
|
332
340
|
// combines tables + foreign tables, `.views` combines views + materialized
|
|
333
341
|
// views — so the cross-collection name sort can't be expressed by the
|
|
334
342
|
// single-collection `sortGeneratorMetadata` pass and stays here.
|
|
335
|
-
for (const
|
|
336
|
-
|
|
343
|
+
for (const schemaIntrospection of Object.values(introspectionBySchema)) {
|
|
344
|
+
schemaIntrospection.tables.sort((a, b) =>
|
|
337
345
|
compareStrings(a.table.name, b.table.name),
|
|
338
346
|
);
|
|
339
|
-
|
|
347
|
+
schemaIntrospection.views.sort((a, b) =>
|
|
340
348
|
compareStrings(a.view.name, b.view.name),
|
|
341
349
|
);
|
|
342
|
-
|
|
350
|
+
schemaIntrospection.functions.sort((a, b) =>
|
|
343
351
|
compareStrings(a.fn.name, b.fn.name),
|
|
344
352
|
);
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
);
|
|
348
|
-
introspectionBySchema[schema].compositeTypes.sort((a, b) =>
|
|
353
|
+
schemaIntrospection.enums.sort((a, b) => compareStrings(a.name, b.name));
|
|
354
|
+
schemaIntrospection.compositeTypes.sort((a, b) =>
|
|
349
355
|
compareStrings(a.name, b.name),
|
|
350
356
|
);
|
|
351
357
|
}
|
|
@@ -376,8 +382,9 @@ export const generateTypescript = async (
|
|
|
376
382
|
}
|
|
377
383
|
// Case 2: if the function has a single table argument, we need to add SetofOptions to allow selecting sub fields of the table
|
|
378
384
|
// and set the right "from" and "to" values to allow selecting from a table row
|
|
379
|
-
|
|
380
|
-
|
|
385
|
+
const soleArg = fn.args.length === 1 ? fn.args[0] : undefined;
|
|
386
|
+
if (soleArg) {
|
|
387
|
+
const relationType = relationTypeByIds.get(soleArg.type_id);
|
|
381
388
|
|
|
382
389
|
// Only add SetofOptions for functions with table arguments (embedded functions)
|
|
383
390
|
// or specific functions that RETURNS table-name
|
|
@@ -445,7 +452,7 @@ export const generateTypescript = async (
|
|
|
445
452
|
)?.view;
|
|
446
453
|
if (relation) {
|
|
447
454
|
return `{
|
|
448
|
-
${
|
|
455
|
+
${getColumns(relation.id)
|
|
449
456
|
.map((column) =>
|
|
450
457
|
generateColumnTsDefinition(
|
|
451
458
|
schema,
|
|
@@ -476,10 +483,11 @@ export const generateTypescript = async (
|
|
|
476
483
|
fn: PostgresFunction,
|
|
477
484
|
inArgs: PostgresFunction["args"],
|
|
478
485
|
) => {
|
|
486
|
+
const soleInArg = inArgs.length === 1 ? inArgs[0] : undefined;
|
|
479
487
|
if (
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
relationTypeByIds.get(
|
|
488
|
+
soleInArg &&
|
|
489
|
+
soleInArg.name === "" &&
|
|
490
|
+
relationTypeByIds.get(soleInArg.type_id) &&
|
|
483
491
|
!getTableNameFromRelationId(fn.return_type_relation_id, fn.return_type_id)
|
|
484
492
|
) {
|
|
485
493
|
return true;
|
|
@@ -496,6 +504,7 @@ export const generateTypescript = async (
|
|
|
496
504
|
) => {
|
|
497
505
|
// If there is a single function definition, there is no conflict
|
|
498
506
|
if (fns.length <= 1) return null;
|
|
507
|
+
const soleInArg = inArgs.length === 1 ? inArgs[0] : undefined;
|
|
499
508
|
|
|
500
509
|
// Generic conflict detection patterns
|
|
501
510
|
// Pattern 1: No-args vs default-args conflicts
|
|
@@ -503,16 +512,18 @@ export const generateTypescript = async (
|
|
|
503
512
|
const conflictingFns = fns.filter(
|
|
504
513
|
({ fn: otherFn, inArgs: otherInArgs }) => {
|
|
505
514
|
if (otherFn === fn) return false;
|
|
515
|
+
const otherSoleInArg =
|
|
516
|
+
otherInArgs.length === 1 ? otherInArgs[0] : undefined;
|
|
506
517
|
return (
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
518
|
+
otherSoleInArg &&
|
|
519
|
+
otherSoleInArg.name === "" &&
|
|
520
|
+
otherSoleInArg.has_default
|
|
510
521
|
);
|
|
511
522
|
},
|
|
512
523
|
);
|
|
513
524
|
|
|
514
|
-
|
|
515
|
-
|
|
525
|
+
const conflictingFn = conflictingFns[0];
|
|
526
|
+
if (conflictingFn) {
|
|
516
527
|
const returnTypeName =
|
|
517
528
|
typesById.get(conflictingFn.fn.return_type_id)?.name || "unknown";
|
|
518
529
|
return `Could not choose the best candidate function between: ${schema.name}.${fn.name}(), ${schema.name}.${fn.name}( => ${returnTypeName}). Try renaming the parameters or the function itself in the database so function overloading can be resolved`;
|
|
@@ -520,14 +531,16 @@ export const generateTypescript = async (
|
|
|
520
531
|
}
|
|
521
532
|
|
|
522
533
|
// Pattern 2: Same parameter name but different types (unresolvable overloads)
|
|
523
|
-
if (
|
|
534
|
+
if (soleInArg && soleInArg.name !== "") {
|
|
524
535
|
const conflictingFns = fns.filter(
|
|
525
536
|
({ fn: otherFn, inArgs: otherInArgs }) => {
|
|
526
537
|
if (otherFn === fn) return false;
|
|
538
|
+
const otherSoleInArg =
|
|
539
|
+
otherInArgs.length === 1 ? otherInArgs[0] : undefined;
|
|
527
540
|
return (
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
541
|
+
otherSoleInArg &&
|
|
542
|
+
otherSoleInArg.name === soleInArg.name &&
|
|
543
|
+
otherSoleInArg.type_id !== soleInArg.type_id
|
|
531
544
|
);
|
|
532
545
|
},
|
|
533
546
|
);
|
|
@@ -711,11 +724,12 @@ export const generateTypescript = async (
|
|
|
711
724
|
inArgs: PostgresFunction["args"],
|
|
712
725
|
relation: { id: number; name: string },
|
|
713
726
|
) => {
|
|
714
|
-
|
|
727
|
+
const soleInArg = inArgs.length === 1 ? inArgs[0] : undefined;
|
|
728
|
+
if (soleInArg) {
|
|
715
729
|
// `relationTypeByIds` is already "types backed by a relation", so a hit
|
|
716
730
|
// identifies the relation regardless of how the argument was spelled.
|
|
717
731
|
// A `variadic` argument is an array type and never appears here.
|
|
718
|
-
const argType = relationTypeByIds.get(
|
|
732
|
+
const argType = relationTypeByIds.get(soleInArg.type_id);
|
|
719
733
|
if (argType) {
|
|
720
734
|
return argType.type_relation_id === relation.id;
|
|
721
735
|
}
|
|
@@ -738,7 +752,7 @@ export type Database = {
|
|
|
738
752
|
functions: schemaFunctions,
|
|
739
753
|
enums: schemaEnums,
|
|
740
754
|
compositeTypes: schemaCompositeTypes,
|
|
741
|
-
} =
|
|
755
|
+
} = getSchemaIntrospection(schema.name);
|
|
742
756
|
return `${JSON.stringify(schema.name)}: {
|
|
743
757
|
Tables: {
|
|
744
758
|
${
|
|
@@ -751,7 +765,7 @@ export type Database = {
|
|
|
751
765
|
}) => `${JSON.stringify(table.name)}: {
|
|
752
766
|
Row: {
|
|
753
767
|
${[
|
|
754
|
-
...
|
|
768
|
+
...getColumns(table.id).map((column) =>
|
|
755
769
|
generateColumnTsDefinition(
|
|
756
770
|
schema,
|
|
757
771
|
{
|
|
@@ -777,7 +791,7 @@ export type Database = {
|
|
|
777
791
|
]}
|
|
778
792
|
}
|
|
779
793
|
Insert: {
|
|
780
|
-
${
|
|
794
|
+
${getColumns(table.id).map((column) => {
|
|
781
795
|
if (
|
|
782
796
|
column.identity_generation === "ALWAYS" ||
|
|
783
797
|
column.is_generated
|
|
@@ -801,7 +815,7 @@ export type Database = {
|
|
|
801
815
|
})}
|
|
802
816
|
}
|
|
803
817
|
Update: {
|
|
804
|
-
${
|
|
818
|
+
${getColumns(table.id).map((column) => {
|
|
805
819
|
if (
|
|
806
820
|
column.identity_generation === "ALWAYS" ||
|
|
807
821
|
column.is_generated
|
|
@@ -840,7 +854,7 @@ export type Database = {
|
|
|
840
854
|
}) => `${JSON.stringify(view.name)}: {
|
|
841
855
|
Row: {
|
|
842
856
|
${[
|
|
843
|
-
...
|
|
857
|
+
...getColumns(view.id).map((column) =>
|
|
844
858
|
generateColumnTsDefinition(
|
|
845
859
|
schema,
|
|
846
860
|
{
|
|
@@ -871,7 +885,7 @@ export type Database = {
|
|
|
871
885
|
// version 1 documents) falls back to the old gate.
|
|
872
886
|
(view.is_insert_enabled ?? view.is_updatable)
|
|
873
887
|
? `Insert: {
|
|
874
|
-
${
|
|
888
|
+
${getColumns(view.id).map((column) => {
|
|
875
889
|
if (!column.is_updatable) {
|
|
876
890
|
return `${JSON.stringify(column.name)}?: never`;
|
|
877
891
|
}
|
|
@@ -893,7 +907,7 @@ export type Database = {
|
|
|
893
907
|
}${
|
|
894
908
|
(view.is_update_enabled ?? view.is_updatable)
|
|
895
909
|
? `Update: {
|
|
896
|
-
${
|
|
910
|
+
${getColumns(view.id).map((column) => {
|
|
897
911
|
if (!column.is_updatable) {
|
|
898
912
|
return `${JSON.stringify(column.name)}?: never`;
|
|
899
913
|
}
|
|
@@ -926,14 +940,13 @@ export type Database = {
|
|
|
926
940
|
}
|
|
927
941
|
const schemaFunctionsGroupedByName = schemaFunctions.reduce(
|
|
928
942
|
(acc, curr) => {
|
|
929
|
-
acc[curr.fn.name] ??= [];
|
|
930
|
-
acc[curr.fn.name].push(curr);
|
|
943
|
+
(acc[curr.fn.name] ??= []).push(curr);
|
|
931
944
|
return acc;
|
|
932
945
|
},
|
|
933
946
|
{} as Record<string, typeof schemaFunctions>,
|
|
934
947
|
);
|
|
935
|
-
for (const
|
|
936
|
-
|
|
948
|
+
for (const fns of Object.values(schemaFunctionsGroupedByName)) {
|
|
949
|
+
fns.sort(
|
|
937
950
|
(a, b) =>
|
|
938
951
|
compareStrings(a.fn.argument_types, b.fn.argument_types) ||
|
|
939
952
|
compareStrings(a.fn.return_type, b.fn.return_type),
|
|
@@ -1100,7 +1113,7 @@ export type CompositeTypes<
|
|
|
1100
1113
|
|
|
1101
1114
|
export const Constants = {
|
|
1102
1115
|
${schemas.map((schema) => {
|
|
1103
|
-
const schemaEnums =
|
|
1116
|
+
const schemaEnums = getSchemaIntrospection(schema.name).enums;
|
|
1104
1117
|
return `${JSON.stringify(schema.name)}: {
|
|
1105
1118
|
Enums: {
|
|
1106
1119
|
${schemaEnums.map(
|
|
@@ -1176,10 +1189,9 @@ export const pgTypeToTsType = (
|
|
|
1176
1189
|
const enumTypes = types.filter(
|
|
1177
1190
|
(type) => type.name === pgType && type.enums.length > 0,
|
|
1178
1191
|
);
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
enumTypes[0];
|
|
1192
|
+
const enumType =
|
|
1193
|
+
enumTypes.find((type) => type.schema === preferredSchema) ?? enumTypes[0];
|
|
1194
|
+
if (enumType) {
|
|
1183
1195
|
if (schemas.some(({ name }) => name === enumType.schema)) {
|
|
1184
1196
|
return `Database[${JSON.stringify(enumType.schema)}]['Enums'][${JSON.stringify(
|
|
1185
1197
|
enumType.name,
|
|
@@ -1191,10 +1203,10 @@ export const pgTypeToTsType = (
|
|
|
1191
1203
|
const compositeTypes = types.filter(
|
|
1192
1204
|
(type) => type.name === pgType && type.attributes.length > 0,
|
|
1193
1205
|
);
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1206
|
+
const compositeType =
|
|
1207
|
+
compositeTypes.find((type) => type.schema === preferredSchema) ??
|
|
1208
|
+
compositeTypes[0];
|
|
1209
|
+
if (compositeType) {
|
|
1198
1210
|
if (schemas.some(({ name }) => name === compositeType.schema)) {
|
|
1199
1211
|
return `Database[${JSON.stringify(
|
|
1200
1212
|
compositeType.schema,
|