@supabase/postgrest-typegen 0.3.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -21
- package/dist/generation/index.d.ts +0 -1
- package/dist/generation/index.js +0 -1
- package/dist/generation/typescript.d.ts +2 -1
- package/dist/generation/typescript.js +205 -400
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/introspection/index.js +3 -1
- package/dist/rpc.d.ts +2 -0
- package/dist/rpc.js +223 -0
- package/dist/sort.js +23 -0
- package/dist/types.d.ts +151 -0
- package/dist/types.js +91 -0
- package/package.json +5 -4
- package/src/generation/index.ts +0 -5
- package/src/generation/typescript.ts +214 -472
- package/src/index.ts +1 -0
- package/src/introspection/index.ts +3 -1
- package/src/rpc.ts +299 -0
- package/src/sort.ts +31 -0
- package/src/types.ts +101 -0
- package/dist/generation/constants.d.ts +0 -9
- package/dist/generation/constants.js +0 -10
- package/dist/generation/swift.d.ts +0 -11
- package/dist/generation/swift.js +0 -358
- package/src/generation/constants.ts +0 -11
- package/src/generation/swift.ts +0 -531
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { VALID_FUNCTION_ARGS_MODE, VALID_UNNAMED_FUNCTION_ARG_TYPES, } from "./constants.js";
|
|
2
1
|
import { compareStrings } from "../collation.js";
|
|
2
|
+
import { resolveRpcFunctions } from "../rpc.js";
|
|
3
3
|
const loadOxfmt = async () => {
|
|
4
4
|
try {
|
|
5
5
|
return await import("oxfmt");
|
|
@@ -28,8 +28,13 @@ const defaultFormat = async (code) => {
|
|
|
28
28
|
* can't be expressed by the single-collection `sortGeneratorMetadata` pass.
|
|
29
29
|
*/
|
|
30
30
|
export const generateTypescript = async (metadata, opts = {}) => {
|
|
31
|
-
const { schemas, tables, foreignTables, views, materializedViews, columns, relationships,
|
|
31
|
+
const { schemas, tables, foreignTables, views, materializedViews, columns, relationships, types, } = metadata;
|
|
32
|
+
// Documents predating the collection (still valid version 1) resolve it here.
|
|
33
|
+
const rpcFunctions = metadata.rpcFunctions ?? resolveRpcFunctions(metadata);
|
|
32
34
|
const { detectOneToOneRelationships = false, postgrestVersion, defaultSchema = "public", format = defaultFormat, } = opts;
|
|
35
|
+
const resolvedDefaultSchema = schemas.some(({ name }) => name === defaultSchema) || !schemas[0]
|
|
36
|
+
? defaultSchema
|
|
37
|
+
: schemas[0].name;
|
|
33
38
|
// Every relation-typed value resolves against the same collections, so build
|
|
34
39
|
// the lookup context once rather than reassembling it at each call site.
|
|
35
40
|
const typeContext = {
|
|
@@ -47,8 +52,7 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
47
52
|
{ tables: [], views: [], functions: [], enums: [], compositeTypes: [] },
|
|
48
53
|
]));
|
|
49
54
|
const columnsByTableId = {};
|
|
50
|
-
const
|
|
51
|
-
const relationTypeByIds = new Map();
|
|
55
|
+
const relationsById = new Map();
|
|
52
56
|
// group types by id for quicker lookup
|
|
53
57
|
const typesById = new Map();
|
|
54
58
|
const tablesLike = [
|
|
@@ -59,7 +63,7 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
59
63
|
];
|
|
60
64
|
for (const tableLike of tablesLike) {
|
|
61
65
|
columnsByTableId[tableLike.id] = [];
|
|
62
|
-
|
|
66
|
+
relationsById.set(tableLike.id, tableLike);
|
|
63
67
|
}
|
|
64
68
|
for (const column of columns) {
|
|
65
69
|
columnsByTableId[column.table_id]?.push(column);
|
|
@@ -80,10 +84,6 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
80
84
|
};
|
|
81
85
|
for (const type of types) {
|
|
82
86
|
typesById.set(type.id, type);
|
|
83
|
-
// Save all the types that are relation types for quicker lookup
|
|
84
|
-
if (type.type_relation_id) {
|
|
85
|
-
relationTypeByIds.set(type.id, type);
|
|
86
|
-
}
|
|
87
87
|
if (type.enums.length > 0) {
|
|
88
88
|
introspectionBySchema[type.schema]?.enums.push(type);
|
|
89
89
|
}
|
|
@@ -146,51 +146,8 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
146
146
|
relationships: getRelationships(materializedView),
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (!relationId)
|
|
152
|
-
return null;
|
|
153
|
-
const tableName = tablesNamesByTableId[relationId];
|
|
154
|
-
if (tableName)
|
|
155
|
-
return tableName;
|
|
156
|
-
// if it's a composite type we use the type name as relation name to allow sub-selecting fields of the composite type
|
|
157
|
-
const reltype = returnTypeId ? relationTypeByIds.get(returnTypeId) : null;
|
|
158
|
-
return reltype ? reltype.name : null;
|
|
159
|
-
};
|
|
160
|
-
for (const func of functions) {
|
|
161
|
-
const schemaIntrospection = introspectionBySchema[func.schema];
|
|
162
|
-
if (schemaIntrospection) {
|
|
163
|
-
// Get all input args (in, inout, variadic modes)
|
|
164
|
-
const inArgs = func.args.filter(({ mode }) => VALID_FUNCTION_ARGS_MODE.has(mode));
|
|
165
|
-
const soleInArg = inArgs.length === 1 ? inArgs[0] : undefined;
|
|
166
|
-
if (
|
|
167
|
-
// Case 1: Function has no parameters
|
|
168
|
-
inArgs.length === 0 ||
|
|
169
|
-
// Case 2: All input args are named
|
|
170
|
-
!inArgs.some(({ name }) => name === "") ||
|
|
171
|
-
// Case 3: All unnamed args have default values AND are valid types
|
|
172
|
-
inArgs.every((arg) => {
|
|
173
|
-
if (arg.name === "") {
|
|
174
|
-
return (arg.has_default &&
|
|
175
|
-
VALID_UNNAMED_FUNCTION_ARG_TYPES.has(arg.type_id));
|
|
176
|
-
}
|
|
177
|
-
return true;
|
|
178
|
-
}) ||
|
|
179
|
-
// Case 4: Single unnamed parameter of valid type (json, jsonb, text)
|
|
180
|
-
// Exclude all functions definitions that have only one single argument unnamed argument that isn't
|
|
181
|
-
// a json/jsonb/text as it won't be considered by PostgREST
|
|
182
|
-
(soleInArg &&
|
|
183
|
-
soleInArg.name === "" &&
|
|
184
|
-
(VALID_UNNAMED_FUNCTION_ARG_TYPES.has(soleInArg.type_id) ||
|
|
185
|
-
// OR if the function have a single unnamed args which is another table (embeded function)
|
|
186
|
-
(relationTypeByIds.get(soleInArg.type_id) &&
|
|
187
|
-
getTableNameFromRelationId(func.return_type_relation_id, func.return_type_id)) ||
|
|
188
|
-
// OR if the function takes a table row but doesn't qualify as embedded (for error reporting)
|
|
189
|
-
(relationTypeByIds.get(soleInArg.type_id) &&
|
|
190
|
-
!getTableNameFromRelationId(func.return_type_relation_id, func.return_type_id))))) {
|
|
191
|
-
schemaIntrospection.functions.push({ fn: func, inArgs });
|
|
192
|
-
}
|
|
193
|
-
}
|
|
149
|
+
for (const fn of rpcFunctions) {
|
|
150
|
+
introspectionBySchema[fn.schema]?.functions.push(fn);
|
|
194
151
|
}
|
|
195
152
|
// These per-schema groups MERGE multiple metadata collections — `.tables`
|
|
196
153
|
// combines tables + foreign tables, `.views` combines views + materialized
|
|
@@ -199,83 +156,42 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
199
156
|
for (const schemaIntrospection of Object.values(introspectionBySchema)) {
|
|
200
157
|
schemaIntrospection.tables.sort((a, b) => compareStrings(a.table.name, b.table.name));
|
|
201
158
|
schemaIntrospection.views.sort((a, b) => compareStrings(a.view.name, b.view.name));
|
|
202
|
-
schemaIntrospection.functions.sort((a, b) => compareStrings(a.fn.name, b.fn.name));
|
|
203
159
|
schemaIntrospection.enums.sort((a, b) => compareStrings(a.name, b.name));
|
|
204
160
|
schemaIntrospection.compositeTypes.sort((a, b) => compareStrings(a.name, b.name));
|
|
205
161
|
}
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const relationType = relationTypeByIds.get(soleArg.type_id);
|
|
227
|
-
// Only add SetofOptions for functions with table arguments (embedded functions)
|
|
228
|
-
// or specific functions that RETURNS table-name
|
|
229
|
-
if (relationType) {
|
|
230
|
-
const sourceTable = relationType.format;
|
|
231
|
-
// Case 1: Standard embedded function with proper setof detection
|
|
232
|
-
if (returnsSetOfTable && returnTableName) {
|
|
233
|
-
setofOptionsInfo = `SetofOptions: {
|
|
234
|
-
from: ${JSON.stringify(sourceTable)}
|
|
235
|
-
to: ${JSON.stringify(returnTableName)}
|
|
236
|
-
isOneToOne: ${Boolean(!returnsMultipleRows)}
|
|
237
|
-
isSetofReturn: true
|
|
238
|
-
}`;
|
|
239
|
-
} // Case 2: Handle RETURNS table-name those are always a one to one relationship
|
|
240
|
-
else if (returnTableName && !returnsSetOfTable) {
|
|
241
|
-
const targetTable = returnTableName;
|
|
242
|
-
setofOptionsInfo = `SetofOptions: {
|
|
243
|
-
from: ${JSON.stringify(sourceTable)}
|
|
244
|
-
to: ${JSON.stringify(targetTable)}
|
|
245
|
-
isOneToOne: true
|
|
246
|
-
isSetofReturn: false
|
|
247
|
-
}`;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
// A ROWS 1 estimate may only collapse the array type when SetofOptions is also emitted
|
|
252
|
-
// (returnTableName resolved): postgrest-js relies on isOneToOne to restore the array shape
|
|
253
|
-
// for direct RPC calls. Without that metadata PostgREST still returns an array, so the
|
|
254
|
-
// collapse would make the type diverge from the actual response.
|
|
255
|
-
const collapsibleSingleRow = !returnsMultipleRows && returnTableName !== null;
|
|
256
|
-
return `${returnType}${fn.is_set_returning_function && !collapsibleSingleRow ? "[]" : ""}
|
|
257
|
-
${setofOptionsInfo ? `${setofOptionsInfo}` : ""}`;
|
|
162
|
+
const referencedTsType = (schema, { format, type_schema }) => format === null || type_schema === null
|
|
163
|
+
? "unknown"
|
|
164
|
+
: pgTypeToTsType(schema, format, typeContext, type_schema);
|
|
165
|
+
const getFunctionTsReturnType = (overload, returnType) => {
|
|
166
|
+
const { setof_options } = overload;
|
|
167
|
+
const setofOptionsInfo = setof_options
|
|
168
|
+
? `SetofOptions: {
|
|
169
|
+
from: ${JSON.stringify(setof_options.from)}
|
|
170
|
+
to: ${JSON.stringify(setof_options.to)}
|
|
171
|
+
isOneToOne: ${setof_options.is_one_to_one}
|
|
172
|
+
isSetofReturn: ${setof_options.is_setof_return}
|
|
173
|
+
}`
|
|
174
|
+
: "";
|
|
175
|
+
// A ROWS 1 estimate may only collapse the array type when SetofOptions is also emitted:
|
|
176
|
+
// postgrest-js relies on isOneToOne to restore the array shape for direct RPC calls.
|
|
177
|
+
// Without that metadata PostgREST still returns an array, so the collapse would make
|
|
178
|
+
// the type diverge from the actual response.
|
|
179
|
+
const collapsibleSingleRow = setof_options?.is_one_to_one === true;
|
|
180
|
+
return `${returnType}${overload.is_set_returning && !collapsibleSingleRow ? "[]" : ""}
|
|
181
|
+
${setofOptionsInfo}`;
|
|
258
182
|
};
|
|
259
|
-
const getFunctionReturnType = (schema,
|
|
183
|
+
const getFunctionReturnType = (schema, { returns }) => {
|
|
260
184
|
// Case 1: `returns table`.
|
|
261
|
-
|
|
262
|
-
if (tableArgs.length > 0) {
|
|
263
|
-
const argsNameAndType = tableArgs.map(({ name, type_id }) => {
|
|
264
|
-
const type = typesById.get(type_id);
|
|
265
|
-
let tsType = "unknown";
|
|
266
|
-
if (type) {
|
|
267
|
-
tsType = pgTypeToTsType(schema, type.name, typeContext, type.schema);
|
|
268
|
-
}
|
|
269
|
-
return { name, type: tsType };
|
|
270
|
-
});
|
|
185
|
+
if (returns.columns) {
|
|
271
186
|
return `{
|
|
272
|
-
${
|
|
187
|
+
${returns.columns.map((column) => `${JSON.stringify(column.name)}: ${referencedTsType(schema, column)}`)}
|
|
273
188
|
}`;
|
|
274
189
|
}
|
|
275
|
-
// Case 2: returns a relation
|
|
276
|
-
const relation =
|
|
277
|
-
|
|
278
|
-
|
|
190
|
+
// Case 2: returns a row type of a relation in this schema.
|
|
191
|
+
const relation = returns.relation_id === null
|
|
192
|
+
? undefined
|
|
193
|
+
: relationsById.get(returns.relation_id);
|
|
194
|
+
if (relation && relation.schema === schema.name) {
|
|
279
195
|
return `{
|
|
280
196
|
${getColumns(relation.id)
|
|
281
197
|
.map((column) => generateColumnTsDefinition(schema, {
|
|
@@ -289,130 +205,41 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
289
205
|
}`;
|
|
290
206
|
}
|
|
291
207
|
// Case 3: returns base/array/composite/enum type.
|
|
292
|
-
|
|
293
|
-
if (type) {
|
|
294
|
-
return pgTypeToTsType(schema, type.name, typeContext, type.schema);
|
|
295
|
-
}
|
|
296
|
-
return "unknown";
|
|
208
|
+
return referencedTsType(schema, returns);
|
|
297
209
|
};
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
return false;
|
|
308
|
-
};
|
|
309
|
-
// Check for generic conflict cases that need error reporting
|
|
310
|
-
const getConflictError = (schema, fns, fn, inArgs) => {
|
|
311
|
-
// If there is a single function definition, there is no conflict
|
|
312
|
-
if (fns.length <= 1)
|
|
313
|
-
return null;
|
|
314
|
-
const soleInArg = inArgs.length === 1 ? inArgs[0] : undefined;
|
|
315
|
-
// Generic conflict detection patterns
|
|
316
|
-
// Pattern 1: No-args vs default-args conflicts
|
|
317
|
-
if (inArgs.length === 0) {
|
|
318
|
-
const conflictingFns = fns.filter(({ fn: otherFn, inArgs: otherInArgs }) => {
|
|
319
|
-
if (otherFn === fn)
|
|
320
|
-
return false;
|
|
321
|
-
const otherSoleInArg = otherInArgs.length === 1 ? otherInArgs[0] : undefined;
|
|
322
|
-
return (otherSoleInArg &&
|
|
323
|
-
otherSoleInArg.name === "" &&
|
|
324
|
-
otherSoleInArg.has_default);
|
|
325
|
-
});
|
|
326
|
-
const conflictingFn = conflictingFns[0];
|
|
327
|
-
if (conflictingFn) {
|
|
328
|
-
const returnTypeName = typesById.get(conflictingFn.fn.return_type_id)?.name || "unknown";
|
|
329
|
-
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`;
|
|
330
|
-
}
|
|
210
|
+
const getArgTsType = (schema, arg) => {
|
|
211
|
+
const tsType = referencedTsType(schema, arg);
|
|
212
|
+
// A row-typed parameter takes the relation's columns: PostgREST fills the
|
|
213
|
+
// keys it does not receive with NULL, and the computed fields are never
|
|
214
|
+
// input. A named parameter is typed without them. An unnamed one keeps the
|
|
215
|
+
// plain `Row` reference, which is how postgrest-js versions predating
|
|
216
|
+
// `ComputedFields` recognise the function as a computed field.
|
|
217
|
+
if (arg.name === "" || arg.format === null || arg.type_schema === null) {
|
|
218
|
+
return tsType;
|
|
331
219
|
}
|
|
332
|
-
|
|
333
|
-
if (
|
|
334
|
-
|
|
335
|
-
if (otherFn === fn)
|
|
336
|
-
return false;
|
|
337
|
-
const otherSoleInArg = otherInArgs.length === 1 ? otherInArgs[0] : undefined;
|
|
338
|
-
return (otherSoleInArg &&
|
|
339
|
-
otherSoleInArg.name === soleInArg.name &&
|
|
340
|
-
otherSoleInArg.type_id !== soleInArg.type_id);
|
|
341
|
-
});
|
|
342
|
-
if (conflictingFns.length > 0) {
|
|
343
|
-
const allConflictingFunctions = [{ fn, inArgs }, ...conflictingFns];
|
|
344
|
-
const conflictList = allConflictingFunctions
|
|
345
|
-
.sort((a, b) => {
|
|
346
|
-
const aArgs = a.inArgs;
|
|
347
|
-
const bArgs = b.inArgs;
|
|
348
|
-
return (aArgs[0]?.type_id || 0) - (bArgs[0]?.type_id || 0);
|
|
349
|
-
})
|
|
350
|
-
.map((f) => {
|
|
351
|
-
const args = f.inArgs;
|
|
352
|
-
return `${schema.name}.${fn.name}(${args
|
|
353
|
-
.map((a) => `${a.name || ""} => ${typesById.get(a.type_id)?.name || "unknown"}`)
|
|
354
|
-
.join(", ")})`;
|
|
355
|
-
})
|
|
356
|
-
.join(", ");
|
|
357
|
-
return `Could not choose the best candidate function between: ${conflictList}. Try renaming the parameters or the function itself in the database so function overloading can be resolved`;
|
|
358
|
-
}
|
|
220
|
+
const relationReference = findRelationReference(arg.format, typeContext, arg.type_schema);
|
|
221
|
+
if (relationReference && tsType === `${relationReference}['Row']`) {
|
|
222
|
+
return `Omit<${tsType}, ${relationReference}['ComputedFields']>`;
|
|
359
223
|
}
|
|
360
|
-
return
|
|
224
|
+
return tsType;
|
|
361
225
|
};
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
226
|
+
const getArgsTsType = (schema, args) => `{ ${args.map((arg) => `${JSON.stringify(arg.name)}${arg.has_default ? "?" : ""}: ${getArgTsType(schema, arg)}`)} }`;
|
|
227
|
+
const getFunctionSignatures = (schema, fn) => {
|
|
228
|
+
return fn.overloads
|
|
229
|
+
.map((overload) => {
|
|
365
230
|
// `never` would be wrong here: postgrest-js treats `Args extends { '': Row }`
|
|
366
231
|
// as a computed field (`never extends T` is always true, so same-named table
|
|
367
232
|
// columns get omitted from select results), and an object type with a required
|
|
368
233
|
// `never` property is uninhabited for any tool doing sound type math on the
|
|
369
234
|
// generated types. `Record<PropertyKey, never>` is the accurate type for
|
|
370
235
|
// "callable with no arguments".
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const type = typesById.get(type_id);
|
|
379
|
-
let tsType = "unknown";
|
|
380
|
-
if (type) {
|
|
381
|
-
tsType = pgTypeToTsType(schema, type.name, typeContext, type.schema);
|
|
382
|
-
}
|
|
383
|
-
return { name, type: tsType, has_default };
|
|
384
|
-
});
|
|
385
|
-
argsType = `{ ${argsNameAndType.map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? "?" : ""}: ${type}`)} }`;
|
|
386
|
-
}
|
|
387
|
-
returnType = `{ error: true } & ${JSON.stringify(conflictError)}`;
|
|
388
|
-
}
|
|
389
|
-
else if (hasTableRowError(fn, inArgs)) {
|
|
390
|
-
// Special case for computed fields returning scalars functions
|
|
391
|
-
if (inArgs.length > 0) {
|
|
392
|
-
const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => {
|
|
393
|
-
const type = typesById.get(type_id);
|
|
394
|
-
let tsType = "unknown";
|
|
395
|
-
if (type) {
|
|
396
|
-
tsType = pgTypeToTsType(schema, type.name, typeContext, type.schema);
|
|
397
|
-
}
|
|
398
|
-
return { name, type: tsType, has_default };
|
|
399
|
-
});
|
|
400
|
-
argsType = `{ ${argsNameAndType.map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? "?" : ""}: ${type}`)} }`;
|
|
401
|
-
}
|
|
402
|
-
returnType = `{ error: true } & ${JSON.stringify(`the function ${schema.name}.${fn.name} with parameter or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache`)}`;
|
|
403
|
-
}
|
|
404
|
-
else if (inArgs.length > 0) {
|
|
405
|
-
const argsNameAndType = inArgs.map(({ name, type_id, has_default }) => {
|
|
406
|
-
const type = typesById.get(type_id);
|
|
407
|
-
let tsType = "unknown";
|
|
408
|
-
if (type) {
|
|
409
|
-
tsType = pgTypeToTsType(schema, type.name, typeContext, type.schema);
|
|
410
|
-
}
|
|
411
|
-
return { name, type: tsType, has_default };
|
|
412
|
-
});
|
|
413
|
-
argsType = `{ ${argsNameAndType.map(({ name, type, has_default }) => `${JSON.stringify(name)}${has_default ? "?" : ""}: ${type}`)} }`;
|
|
414
|
-
}
|
|
415
|
-
return `{ Args: ${argsType}; Returns: ${getFunctionTsReturnType(fn, returnType)} }`;
|
|
236
|
+
const argsType = overload.args.length > 0
|
|
237
|
+
? getArgsTsType(schema, overload.args)
|
|
238
|
+
: "Record<PropertyKey, never>";
|
|
239
|
+
const returnType = overload.error === null
|
|
240
|
+
? getFunctionReturnType(schema, overload)
|
|
241
|
+
: `{ error: true } & ${JSON.stringify(overload.error)}`;
|
|
242
|
+
return `{ Args: ${argsType}; Returns: ${getFunctionTsReturnType(overload, returnType)} }`;
|
|
416
243
|
})
|
|
417
244
|
.join(" |\n");
|
|
418
245
|
};
|
|
@@ -439,38 +266,21 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
439
266
|
return `${JSON.stringify(column.name)}${column.is_optional ? "?" : ""}: ${generateNullableUnionTsType(pgTypeToTsType(schema, column.format, context, column.type_schema), column.is_nullable)}`;
|
|
440
267
|
}
|
|
441
268
|
/**
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
* in `Row`.
|
|
447
|
-
*
|
|
448
|
-
* Matching on the argument's type OID rather than on `argument_types` is what
|
|
449
|
-
* makes named parameters work. `argument_types` is
|
|
450
|
-
* `pg_get_function_arguments()`, which renders the parameter *name* when the
|
|
451
|
-
* parameter has one — `category category` rather than `category` — and
|
|
452
|
-
* schema-qualifies the type when it is not visible in `search_path`. Both
|
|
453
|
-
* forms failed a bare string comparison against the relation name, so
|
|
454
|
-
* `create function name_translated(category category)` silently lost its
|
|
455
|
-
* computed field while the unnamed `name_translated(category)` kept it.
|
|
456
|
-
* See supabase/postgres-meta#1034.
|
|
269
|
+
* The computed fields of a relation, in function name order. A computed
|
|
270
|
+
* field is a function taking a single argument of the relation's own
|
|
271
|
+
* composite type, so it is selectable as if it were a column and belongs in
|
|
272
|
+
* `Row`; `resolveRpcFunctions` marks them with `computed_field_of`.
|
|
457
273
|
*/
|
|
458
|
-
const
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
// Fall back to the historical comparison when the argument's type is not in
|
|
470
|
-
// the metadata (callers may pass a reduced `types` list), so a computed
|
|
471
|
-
// field that resolved before keeps resolving.
|
|
472
|
-
return fn.argument_types === relation.name;
|
|
473
|
-
};
|
|
274
|
+
const computedFieldsOf = (schemaFunctions, relation) => schemaFunctions.flatMap((fn) => {
|
|
275
|
+
const overload = fn.overloads.find(({ computed_field_of }) => computed_field_of === relation.id);
|
|
276
|
+
return overload ? [{ name: fn.name, overload }] : [];
|
|
277
|
+
});
|
|
278
|
+
const computedFieldTsDefinition = (schema, { name, overload }) => `${JSON.stringify(name)}: ${generateNullableUnionTsType(getFunctionReturnType(schema, overload), true)}`;
|
|
279
|
+
// `ComputedFields` names the `Row` keys PostgREST leaves out of `select('*')`,
|
|
280
|
+
// so postgrest-js does not have to infer them from `Functions`.
|
|
281
|
+
const computedFieldsTsType = (computedFields) => computedFields.length === 0
|
|
282
|
+
? "never"
|
|
283
|
+
: computedFields.map(({ name }) => JSON.stringify(name)).join(" | ");
|
|
474
284
|
let output = `
|
|
475
285
|
export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[]
|
|
476
286
|
|
|
@@ -482,138 +292,126 @@ export type Database = {
|
|
|
482
292
|
Tables: {
|
|
483
293
|
${schemaTables.length === 0
|
|
484
294
|
? "[_ in never]: never"
|
|
485
|
-
: schemaTables.map(({ table, relationships
|
|
295
|
+
: schemaTables.map(({ table, relationships }) => {
|
|
296
|
+
const computedFields = computedFieldsOf(schemaFunctions, table);
|
|
297
|
+
return `${JSON.stringify(table.name)}: {
|
|
486
298
|
Row: {
|
|
487
299
|
${[
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
.map(({ fn }) => {
|
|
498
|
-
return `${JSON.stringify(fn.name)}: ${generateNullableUnionTsType(getFunctionReturnType(schema, fn), true)}`;
|
|
499
|
-
}),
|
|
500
|
-
]}
|
|
300
|
+
...getColumns(table.id).map((column) => generateColumnTsDefinition(schema, {
|
|
301
|
+
name: column.name,
|
|
302
|
+
format: column.format,
|
|
303
|
+
type_schema: column.type_schema,
|
|
304
|
+
is_nullable: column.is_nullable,
|
|
305
|
+
is_optional: false,
|
|
306
|
+
}, typeContext)),
|
|
307
|
+
...computedFields.map((computedField) => computedFieldTsDefinition(schema, computedField)),
|
|
308
|
+
]}
|
|
501
309
|
}
|
|
310
|
+
ComputedFields: ${computedFieldsTsType(computedFields)}
|
|
502
311
|
Insert: {
|
|
503
312
|
${getColumns(table.id).map((column) => {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
313
|
+
if (column.identity_generation === "ALWAYS" ||
|
|
314
|
+
column.is_generated) {
|
|
315
|
+
return `${JSON.stringify(column.name)}?: never`;
|
|
316
|
+
}
|
|
317
|
+
return generateColumnTsDefinition(schema, {
|
|
318
|
+
name: column.name,
|
|
319
|
+
format: column.format,
|
|
320
|
+
type_schema: column.type_schema,
|
|
321
|
+
is_nullable: column.is_nullable,
|
|
322
|
+
is_optional: column.is_nullable ||
|
|
323
|
+
column.is_identity ||
|
|
324
|
+
column.default_value !== null,
|
|
325
|
+
}, typeContext);
|
|
326
|
+
})}
|
|
518
327
|
}
|
|
519
328
|
Update: {
|
|
520
329
|
${getColumns(table.id).map((column) => {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
330
|
+
if (column.identity_generation === "ALWAYS" ||
|
|
331
|
+
column.is_generated) {
|
|
332
|
+
return `${JSON.stringify(column.name)}?: never`;
|
|
333
|
+
}
|
|
334
|
+
return generateColumnTsDefinition(schema, {
|
|
335
|
+
name: column.name,
|
|
336
|
+
format: column.format,
|
|
337
|
+
type_schema: column.type_schema,
|
|
338
|
+
is_nullable: column.is_nullable,
|
|
339
|
+
is_optional: true,
|
|
340
|
+
}, typeContext);
|
|
341
|
+
})}
|
|
533
342
|
}
|
|
534
343
|
Relationships: [
|
|
535
344
|
${relationships.map(generateRelationshiptTsDefinition)}
|
|
536
345
|
]
|
|
537
|
-
}
|
|
346
|
+
}`;
|
|
347
|
+
})}
|
|
538
348
|
}
|
|
539
349
|
Views: {
|
|
540
350
|
${schemaViews.length === 0
|
|
541
351
|
? "[_ in never]: never"
|
|
542
|
-
: schemaViews.map(({ view, relationships
|
|
352
|
+
: schemaViews.map(({ view, relationships }) => {
|
|
353
|
+
const computedFields = computedFieldsOf(schemaFunctions, view);
|
|
354
|
+
return `${JSON.stringify(view.name)}: {
|
|
543
355
|
Row: {
|
|
544
356
|
${[
|
|
545
|
-
|
|
546
|
-
name: column.name,
|
|
547
|
-
format: column.format,
|
|
548
|
-
type_schema: column.type_schema,
|
|
549
|
-
is_nullable: column.is_nullable,
|
|
550
|
-
is_optional: false,
|
|
551
|
-
}, typeContext)),
|
|
552
|
-
...schemaFunctions
|
|
553
|
-
.filter(({ fn, inArgs }) => isComputedFieldOf(fn, inArgs, view))
|
|
554
|
-
.map(({ fn }) => `${JSON.stringify(fn.name)}: ${generateNullableUnionTsType(getFunctionReturnType(schema, fn), true)}`),
|
|
555
|
-
]}
|
|
556
|
-
}
|
|
557
|
-
${
|
|
558
|
-
// Metadata predating the trigger-aware flags (still valid
|
|
559
|
-
// version 1 documents) falls back to the old gate.
|
|
560
|
-
(view.is_insert_enabled ?? view.is_updatable)
|
|
561
|
-
? `Insert: {
|
|
562
|
-
${getColumns(view.id).map((column) => {
|
|
563
|
-
if (!column.is_updatable) {
|
|
564
|
-
return `${JSON.stringify(column.name)}?: never`;
|
|
565
|
-
}
|
|
566
|
-
return generateColumnTsDefinition(schema, {
|
|
357
|
+
...getColumns(view.id).map((column) => generateColumnTsDefinition(schema, {
|
|
567
358
|
name: column.name,
|
|
568
359
|
format: column.format,
|
|
569
360
|
type_schema: column.type_schema,
|
|
570
|
-
is_nullable:
|
|
571
|
-
is_optional:
|
|
572
|
-
}, typeContext)
|
|
573
|
-
|
|
361
|
+
is_nullable: column.is_nullable,
|
|
362
|
+
is_optional: false,
|
|
363
|
+
}, typeContext)),
|
|
364
|
+
...computedFields.map((computedField) => computedFieldTsDefinition(schema, computedField)),
|
|
365
|
+
]}
|
|
366
|
+
}
|
|
367
|
+
ComputedFields: ${computedFieldsTsType(computedFields)}
|
|
368
|
+
${
|
|
369
|
+
// Metadata predating the trigger-aware flags (still valid
|
|
370
|
+
// version 1 documents) falls back to the old gate.
|
|
371
|
+
(view.is_insert_enabled ?? view.is_updatable)
|
|
372
|
+
? `Insert: {
|
|
373
|
+
${getColumns(view.id).map((column) => {
|
|
374
|
+
if (!column.is_updatable) {
|
|
375
|
+
return `${JSON.stringify(column.name)}?: never`;
|
|
376
|
+
}
|
|
377
|
+
return generateColumnTsDefinition(schema, {
|
|
378
|
+
name: column.name,
|
|
379
|
+
format: column.format,
|
|
380
|
+
type_schema: column.type_schema,
|
|
381
|
+
is_nullable: true,
|
|
382
|
+
is_optional: true,
|
|
383
|
+
}, typeContext);
|
|
384
|
+
})}
|
|
574
385
|
}
|
|
575
386
|
`
|
|
576
|
-
|
|
577
|
-
|
|
387
|
+
: ""}${(view.is_update_enabled ?? view.is_updatable)
|
|
388
|
+
? `Update: {
|
|
578
389
|
${getColumns(view.id).map((column) => {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
390
|
+
if (!column.is_updatable) {
|
|
391
|
+
return `${JSON.stringify(column.name)}?: never`;
|
|
392
|
+
}
|
|
393
|
+
return generateColumnTsDefinition(schema, {
|
|
394
|
+
name: column.name,
|
|
395
|
+
format: column.format,
|
|
396
|
+
type_schema: column.type_schema,
|
|
397
|
+
is_nullable: true,
|
|
398
|
+
is_optional: true,
|
|
399
|
+
}, typeContext);
|
|
400
|
+
})}
|
|
590
401
|
}
|
|
591
402
|
`
|
|
592
|
-
|
|
403
|
+
: ""}Relationships: [
|
|
593
404
|
${relationships.map(generateRelationshiptTsDefinition)}
|
|
594
405
|
]
|
|
595
|
-
}
|
|
406
|
+
}`;
|
|
407
|
+
})}
|
|
596
408
|
}
|
|
597
409
|
Functions: {
|
|
598
|
-
${
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
(acc[curr.fn.name] ??= []).push(curr);
|
|
604
|
-
return acc;
|
|
605
|
-
}, {});
|
|
606
|
-
for (const fns of Object.values(schemaFunctionsGroupedByName)) {
|
|
607
|
-
fns.sort((a, b) => compareStrings(a.fn.argument_types, b.fn.argument_types) ||
|
|
608
|
-
compareStrings(a.fn.return_type, b.fn.return_type));
|
|
609
|
-
}
|
|
610
|
-
return Object.entries(schemaFunctionsGroupedByName)
|
|
611
|
-
.map(([fnName, fns]) => {
|
|
612
|
-
const functionSignatures = getFunctionSignatures(schema, fns);
|
|
613
|
-
return `${JSON.stringify(fnName)}:\n${functionSignatures}`;
|
|
614
|
-
})
|
|
615
|
-
.join(",\n");
|
|
616
|
-
})()}
|
|
410
|
+
${schemaFunctions.length === 0
|
|
411
|
+
? "[_ in never]: never"
|
|
412
|
+
: schemaFunctions
|
|
413
|
+
.map((fn) => `${JSON.stringify(fn.name)}:\n${getFunctionSignatures(schema, fn)}`)
|
|
414
|
+
.join(",\n")}
|
|
617
415
|
}
|
|
618
416
|
Enums: {
|
|
619
417
|
${schemaEnums.length === 0
|
|
@@ -642,7 +440,7 @@ export type Database = {
|
|
|
642
440
|
|
|
643
441
|
type DatabaseWithoutInternals = Omit<Database, '__InternalSupabase'>
|
|
644
442
|
|
|
645
|
-
type DefaultSchema = DatabaseWithoutInternals[Extract<keyof Database, ${JSON.stringify(
|
|
443
|
+
type DefaultSchema = DatabaseWithoutInternals[Extract<keyof Database, ${JSON.stringify(resolvedDefaultSchema)}>]
|
|
646
444
|
|
|
647
445
|
export type Tables<
|
|
648
446
|
DefaultSchemaTableNameOrOptions extends
|
|
@@ -769,7 +567,7 @@ export const pgTypeToTsType = (schema, pgType, context,
|
|
|
769
567
|
// schema being generated) when not provided, which is only a guess, so pass
|
|
770
568
|
// it whenever the owning schema is known.
|
|
771
569
|
typeSchema) => {
|
|
772
|
-
const { types, schemas
|
|
570
|
+
const { types, schemas } = context;
|
|
773
571
|
if (pgType === "bool") {
|
|
774
572
|
return "boolean";
|
|
775
573
|
}
|
|
@@ -824,34 +622,41 @@ typeSchema) => {
|
|
|
824
622
|
}
|
|
825
623
|
return "unknown";
|
|
826
624
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
625
|
+
const relationReference = findRelationReference(pgType, context, preferredSchema);
|
|
626
|
+
return relationReference ? `${relationReference}['Row']` : "unknown";
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
/**
|
|
630
|
+
* The `Database[schema][kind][name]` reference of the relation whose row type
|
|
631
|
+
* is `pgType`, or `undefined` when no generated relation has that type.
|
|
632
|
+
*
|
|
633
|
+
* A relation-typed value carries only a bare type name, so several relations
|
|
634
|
+
* can share it and `preferredSchema` is what disambiguates them. Match on the
|
|
635
|
+
* schema across every relation kind before falling back to kind order,
|
|
636
|
+
* otherwise a relation in an unrelated schema outranks the one the caller
|
|
637
|
+
* meant. Foreign tables are generated under `Tables` and materialized views
|
|
638
|
+
* under `Views`, alongside their plain counterparts.
|
|
639
|
+
*/
|
|
640
|
+
const findRelationReference = (pgType, context, preferredSchema) => {
|
|
641
|
+
const { schemas, tables, views, foreignTables = [], materializedViews = [], } = context;
|
|
642
|
+
const relationKinds = [
|
|
643
|
+
["Tables", tables],
|
|
644
|
+
["Tables", foreignTables],
|
|
645
|
+
["Views", views],
|
|
646
|
+
["Views", materializedViews],
|
|
647
|
+
];
|
|
648
|
+
const findRelation = (matches) => {
|
|
649
|
+
for (const [kind, relations] of relationKinds) {
|
|
650
|
+
const relation = relations.find(matches);
|
|
651
|
+
if (relation)
|
|
652
|
+
return { kind, relation };
|
|
854
653
|
}
|
|
855
|
-
return
|
|
654
|
+
return undefined;
|
|
655
|
+
};
|
|
656
|
+
const match = findRelation(({ name, schema: relationSchema }) => name === pgType && relationSchema === preferredSchema) ?? findRelation(({ name }) => name === pgType);
|
|
657
|
+
if (!match || !schemas.some(({ name }) => name === match.relation.schema)) {
|
|
658
|
+
return undefined;
|
|
856
659
|
}
|
|
660
|
+
const { kind, relation } = match;
|
|
661
|
+
return `Database[${JSON.stringify(relation.schema)}]['${kind}'][${JSON.stringify(relation.name)}]`;
|
|
857
662
|
};
|