@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
|
@@ -7,18 +7,17 @@ import type {
|
|
|
7
7
|
GeneratorMetadata,
|
|
8
8
|
PostgresColumn,
|
|
9
9
|
PostgresForeignTable,
|
|
10
|
-
PostgresFunction,
|
|
11
10
|
PostgresMaterializedView,
|
|
12
11
|
PostgresSchema,
|
|
13
12
|
PostgresTable,
|
|
14
13
|
PostgresType,
|
|
15
14
|
PostgresView,
|
|
15
|
+
RpcFunction,
|
|
16
|
+
RpcFunctionOverload,
|
|
17
|
+
RpcTypeReference,
|
|
16
18
|
} from "../types.ts";
|
|
17
|
-
import {
|
|
18
|
-
VALID_FUNCTION_ARGS_MODE,
|
|
19
|
-
VALID_UNNAMED_FUNCTION_ARG_TYPES,
|
|
20
|
-
} from "./constants.ts";
|
|
21
19
|
import { compareStrings } from "../collation.ts";
|
|
20
|
+
import { resolveRpcFunctions } from "../rpc.ts";
|
|
22
21
|
|
|
23
22
|
export interface GenerateTypescriptOptions {
|
|
24
23
|
/**
|
|
@@ -34,7 +33,8 @@ export interface GenerateTypescriptOptions {
|
|
|
34
33
|
/**
|
|
35
34
|
* Schema treated as the default for the generated `Tables`/`Enums`/etc.
|
|
36
35
|
* helper types. Replaces postgres-meta's `GENERATE_TYPES_DEFAULT_SCHEMA`
|
|
37
|
-
* env read. Default `'public'`.
|
|
36
|
+
* env read. Default `'public'`. When it is not among the generated schemas,
|
|
37
|
+
* the first generated schema is used instead, so the helpers keep resolving.
|
|
38
38
|
*/
|
|
39
39
|
defaultSchema?: string;
|
|
40
40
|
/**
|
|
@@ -119,15 +119,20 @@ export const generateTypescript = async (
|
|
|
119
119
|
materializedViews,
|
|
120
120
|
columns,
|
|
121
121
|
relationships,
|
|
122
|
-
functions,
|
|
123
122
|
types,
|
|
124
123
|
} = metadata;
|
|
124
|
+
// Documents predating the collection (still valid version 1) resolve it here.
|
|
125
|
+
const rpcFunctions = metadata.rpcFunctions ?? resolveRpcFunctions(metadata);
|
|
125
126
|
const {
|
|
126
127
|
detectOneToOneRelationships = false,
|
|
127
128
|
postgrestVersion,
|
|
128
129
|
defaultSchema = "public",
|
|
129
130
|
format = defaultFormat,
|
|
130
131
|
} = opts;
|
|
132
|
+
const resolvedDefaultSchema =
|
|
133
|
+
schemas.some(({ name }) => name === defaultSchema) || !schemas[0]
|
|
134
|
+
? defaultSchema
|
|
135
|
+
: schemas[0].name;
|
|
131
136
|
// Every relation-typed value resolves against the same collections, so build
|
|
132
137
|
// the lookup context once rather than reassembling it at each call site.
|
|
133
138
|
const typeContext: TypeResolutionContext = {
|
|
@@ -149,7 +154,7 @@ export const generateTypescript = async (
|
|
|
149
154
|
view: PostgresView;
|
|
150
155
|
relationships: TsRelationship[];
|
|
151
156
|
}[];
|
|
152
|
-
functions:
|
|
157
|
+
functions: RpcFunction[];
|
|
153
158
|
enums: PostgresType[];
|
|
154
159
|
compositeTypes: PostgresType[];
|
|
155
160
|
}>(
|
|
@@ -159,8 +164,7 @@ export const generateTypescript = async (
|
|
|
159
164
|
]),
|
|
160
165
|
);
|
|
161
166
|
const columnsByTableId: Record<number, PostgresColumn[]> = {};
|
|
162
|
-
const
|
|
163
|
-
const relationTypeByIds = new Map<number, (typeof types)[number]>();
|
|
167
|
+
const relationsById = new Map<number, { id: number; schema: string }>();
|
|
164
168
|
// group types by id for quicker lookup
|
|
165
169
|
const typesById = new Map<number, (typeof types)[number]>();
|
|
166
170
|
const tablesLike = [
|
|
@@ -172,7 +176,7 @@ export const generateTypescript = async (
|
|
|
172
176
|
|
|
173
177
|
for (const tableLike of tablesLike) {
|
|
174
178
|
columnsByTableId[tableLike.id] = [];
|
|
175
|
-
|
|
179
|
+
relationsById.set(tableLike.id, tableLike);
|
|
176
180
|
}
|
|
177
181
|
for (const column of columns) {
|
|
178
182
|
columnsByTableId[column.table_id]?.push(column);
|
|
@@ -198,10 +202,6 @@ export const generateTypescript = async (
|
|
|
198
202
|
|
|
199
203
|
for (const type of types) {
|
|
200
204
|
typesById.set(type.id, type);
|
|
201
|
-
// Save all the types that are relation types for quicker lookup
|
|
202
|
-
if (type.type_relation_id) {
|
|
203
|
-
relationTypeByIds.set(type.id, type);
|
|
204
|
-
}
|
|
205
205
|
if (type.enums.length > 0) {
|
|
206
206
|
introspectionBySchema[type.schema]?.enums.push(type);
|
|
207
207
|
}
|
|
@@ -288,66 +288,8 @@ export const generateTypescript = async (
|
|
|
288
288
|
relationships: getRelationships(materializedView),
|
|
289
289
|
});
|
|
290
290
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
relationId: number | null,
|
|
294
|
-
returnTypeId: number | null,
|
|
295
|
-
): string | null => {
|
|
296
|
-
if (!relationId) return null;
|
|
297
|
-
|
|
298
|
-
const tableName = tablesNamesByTableId[relationId];
|
|
299
|
-
if (tableName) return tableName;
|
|
300
|
-
// if it's a composite type we use the type name as relation name to allow sub-selecting fields of the composite type
|
|
301
|
-
const reltype = returnTypeId ? relationTypeByIds.get(returnTypeId) : null;
|
|
302
|
-
return reltype ? reltype.name : null;
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
for (const func of functions) {
|
|
306
|
-
const schemaIntrospection = introspectionBySchema[func.schema];
|
|
307
|
-
if (schemaIntrospection) {
|
|
308
|
-
// Get all input args (in, inout, variadic modes)
|
|
309
|
-
const inArgs = func.args.filter(({ mode }) =>
|
|
310
|
-
VALID_FUNCTION_ARGS_MODE.has(mode),
|
|
311
|
-
);
|
|
312
|
-
|
|
313
|
-
const soleInArg = inArgs.length === 1 ? inArgs[0] : undefined;
|
|
314
|
-
if (
|
|
315
|
-
// Case 1: Function has no parameters
|
|
316
|
-
inArgs.length === 0 ||
|
|
317
|
-
// Case 2: All input args are named
|
|
318
|
-
!inArgs.some(({ name }) => name === "") ||
|
|
319
|
-
// Case 3: All unnamed args have default values AND are valid types
|
|
320
|
-
inArgs.every((arg) => {
|
|
321
|
-
if (arg.name === "") {
|
|
322
|
-
return (
|
|
323
|
-
arg.has_default &&
|
|
324
|
-
VALID_UNNAMED_FUNCTION_ARG_TYPES.has(arg.type_id)
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
return true;
|
|
328
|
-
}) ||
|
|
329
|
-
// Case 4: Single unnamed parameter of valid type (json, jsonb, text)
|
|
330
|
-
// Exclude all functions definitions that have only one single argument unnamed argument that isn't
|
|
331
|
-
// a json/jsonb/text as it won't be considered by PostgREST
|
|
332
|
-
(soleInArg &&
|
|
333
|
-
soleInArg.name === "" &&
|
|
334
|
-
(VALID_UNNAMED_FUNCTION_ARG_TYPES.has(soleInArg.type_id) ||
|
|
335
|
-
// OR if the function have a single unnamed args which is another table (embeded function)
|
|
336
|
-
(relationTypeByIds.get(soleInArg.type_id) &&
|
|
337
|
-
getTableNameFromRelationId(
|
|
338
|
-
func.return_type_relation_id,
|
|
339
|
-
func.return_type_id,
|
|
340
|
-
)) ||
|
|
341
|
-
// OR if the function takes a table row but doesn't qualify as embedded (for error reporting)
|
|
342
|
-
(relationTypeByIds.get(soleInArg.type_id) &&
|
|
343
|
-
!getTableNameFromRelationId(
|
|
344
|
-
func.return_type_relation_id,
|
|
345
|
-
func.return_type_id,
|
|
346
|
-
))))
|
|
347
|
-
) {
|
|
348
|
-
schemaIntrospection.functions.push({ fn: func, inArgs });
|
|
349
|
-
}
|
|
350
|
-
}
|
|
291
|
+
for (const fn of rpcFunctions) {
|
|
292
|
+
introspectionBySchema[fn.schema]?.functions.push(fn);
|
|
351
293
|
}
|
|
352
294
|
// These per-schema groups MERGE multiple metadata collections — `.tables`
|
|
353
295
|
// combines tables + foreign tables, `.views` combines views + materialized
|
|
@@ -360,110 +302,62 @@ export const generateTypescript = async (
|
|
|
360
302
|
schemaIntrospection.views.sort((a, b) =>
|
|
361
303
|
compareStrings(a.view.name, b.view.name),
|
|
362
304
|
);
|
|
363
|
-
schemaIntrospection.functions.sort((a, b) =>
|
|
364
|
-
compareStrings(a.fn.name, b.fn.name),
|
|
365
|
-
);
|
|
366
305
|
schemaIntrospection.enums.sort((a, b) => compareStrings(a.name, b.name));
|
|
367
306
|
schemaIntrospection.compositeTypes.sort((a, b) =>
|
|
368
307
|
compareStrings(a.name, b.name),
|
|
369
308
|
);
|
|
370
309
|
}
|
|
371
310
|
|
|
311
|
+
const referencedTsType = (
|
|
312
|
+
schema: PostgresSchema,
|
|
313
|
+
{ format, type_schema }: RpcTypeReference,
|
|
314
|
+
) =>
|
|
315
|
+
format === null || type_schema === null
|
|
316
|
+
? "unknown"
|
|
317
|
+
: pgTypeToTsType(schema, format, typeContext, type_schema);
|
|
318
|
+
|
|
372
319
|
const getFunctionTsReturnType = (
|
|
373
|
-
|
|
320
|
+
overload: RpcFunctionOverload,
|
|
374
321
|
returnType: string,
|
|
375
322
|
) => {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
isOneToOne: ${Boolean(!returnsMultipleRows)}
|
|
393
|
-
isSetofReturn: ${fn.is_set_returning_function}
|
|
394
|
-
}`;
|
|
395
|
-
}
|
|
396
|
-
// Case 2: if the function has a single table argument, we need to add SetofOptions to allow selecting sub fields of the table
|
|
397
|
-
// and set the right "from" and "to" values to allow selecting from a table row
|
|
398
|
-
const soleArg = fn.args.length === 1 ? fn.args[0] : undefined;
|
|
399
|
-
if (soleArg) {
|
|
400
|
-
const relationType = relationTypeByIds.get(soleArg.type_id);
|
|
401
|
-
|
|
402
|
-
// Only add SetofOptions for functions with table arguments (embedded functions)
|
|
403
|
-
// or specific functions that RETURNS table-name
|
|
404
|
-
if (relationType) {
|
|
405
|
-
const sourceTable = relationType.format;
|
|
406
|
-
// Case 1: Standard embedded function with proper setof detection
|
|
407
|
-
if (returnsSetOfTable && returnTableName) {
|
|
408
|
-
setofOptionsInfo = `SetofOptions: {
|
|
409
|
-
from: ${JSON.stringify(sourceTable)}
|
|
410
|
-
to: ${JSON.stringify(returnTableName)}
|
|
411
|
-
isOneToOne: ${Boolean(!returnsMultipleRows)}
|
|
412
|
-
isSetofReturn: true
|
|
413
|
-
}`;
|
|
414
|
-
} // Case 2: Handle RETURNS table-name those are always a one to one relationship
|
|
415
|
-
else if (returnTableName && !returnsSetOfTable) {
|
|
416
|
-
const targetTable = returnTableName;
|
|
417
|
-
setofOptionsInfo = `SetofOptions: {
|
|
418
|
-
from: ${JSON.stringify(sourceTable)}
|
|
419
|
-
to: ${JSON.stringify(targetTable)}
|
|
420
|
-
isOneToOne: true
|
|
421
|
-
isSetofReturn: false
|
|
422
|
-
}`;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// A ROWS 1 estimate may only collapse the array type when SetofOptions is also emitted
|
|
428
|
-
// (returnTableName resolved): postgrest-js relies on isOneToOne to restore the array shape
|
|
429
|
-
// for direct RPC calls. Without that metadata PostgREST still returns an array, so the
|
|
430
|
-
// collapse would make the type diverge from the actual response.
|
|
431
|
-
const collapsibleSingleRow =
|
|
432
|
-
!returnsMultipleRows && returnTableName !== null;
|
|
433
|
-
return `${returnType}${fn.is_set_returning_function && !collapsibleSingleRow ? "[]" : ""}
|
|
434
|
-
${setofOptionsInfo ? `${setofOptionsInfo}` : ""}`;
|
|
323
|
+
const { setof_options } = overload;
|
|
324
|
+
const setofOptionsInfo = setof_options
|
|
325
|
+
? `SetofOptions: {
|
|
326
|
+
from: ${JSON.stringify(setof_options.from)}
|
|
327
|
+
to: ${JSON.stringify(setof_options.to)}
|
|
328
|
+
isOneToOne: ${setof_options.is_one_to_one}
|
|
329
|
+
isSetofReturn: ${setof_options.is_setof_return}
|
|
330
|
+
}`
|
|
331
|
+
: "";
|
|
332
|
+
// A ROWS 1 estimate may only collapse the array type when SetofOptions is also emitted:
|
|
333
|
+
// postgrest-js relies on isOneToOne to restore the array shape for direct RPC calls.
|
|
334
|
+
// Without that metadata PostgREST still returns an array, so the collapse would make
|
|
335
|
+
// the type diverge from the actual response.
|
|
336
|
+
const collapsibleSingleRow = setof_options?.is_one_to_one === true;
|
|
337
|
+
return `${returnType}${overload.is_set_returning && !collapsibleSingleRow ? "[]" : ""}
|
|
338
|
+
${setofOptionsInfo}`;
|
|
435
339
|
};
|
|
436
340
|
|
|
437
341
|
const getFunctionReturnType = (
|
|
438
342
|
schema: PostgresSchema,
|
|
439
|
-
|
|
343
|
+
{ returns }: RpcFunctionOverload,
|
|
440
344
|
): string => {
|
|
441
345
|
// Case 1: `returns table`.
|
|
442
|
-
|
|
443
|
-
if (tableArgs.length > 0) {
|
|
444
|
-
const argsNameAndType = tableArgs.map(({ name, type_id }) => {
|
|
445
|
-
const type = typesById.get(type_id);
|
|
446
|
-
let tsType = "unknown";
|
|
447
|
-
if (type) {
|
|
448
|
-
tsType = pgTypeToTsType(schema, type.name, typeContext, type.schema);
|
|
449
|
-
}
|
|
450
|
-
return { name, type: tsType };
|
|
451
|
-
});
|
|
452
|
-
|
|
346
|
+
if (returns.columns) {
|
|
453
347
|
return `{
|
|
454
|
-
${
|
|
348
|
+
${returns.columns.map(
|
|
349
|
+
(column) =>
|
|
350
|
+
`${JSON.stringify(column.name)}: ${referencedTsType(schema, column)}`,
|
|
351
|
+
)}
|
|
455
352
|
}`;
|
|
456
353
|
}
|
|
457
354
|
|
|
458
|
-
// Case 2: returns a relation
|
|
355
|
+
// Case 2: returns a row type of a relation in this schema.
|
|
459
356
|
const relation =
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
({ view: { id } }) => id === fn.return_type_relation_id,
|
|
465
|
-
)?.view;
|
|
466
|
-
if (relation) {
|
|
357
|
+
returns.relation_id === null
|
|
358
|
+
? undefined
|
|
359
|
+
: relationsById.get(returns.relation_id);
|
|
360
|
+
if (relation && relation.schema === schema.name) {
|
|
467
361
|
return `{
|
|
468
362
|
${getColumns(relation.id)
|
|
469
363
|
.map((column) =>
|
|
@@ -484,195 +378,61 @@ export const generateTypescript = async (
|
|
|
484
378
|
}
|
|
485
379
|
|
|
486
380
|
// Case 3: returns base/array/composite/enum type.
|
|
487
|
-
|
|
488
|
-
if (type) {
|
|
489
|
-
return pgTypeToTsType(schema, type.name, typeContext, type.schema);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
return "unknown";
|
|
493
|
-
};
|
|
494
|
-
// Special error case for functions that take table row but don't qualify as embedded functions
|
|
495
|
-
const hasTableRowError = (
|
|
496
|
-
fn: PostgresFunction,
|
|
497
|
-
inArgs: PostgresFunction["args"],
|
|
498
|
-
) => {
|
|
499
|
-
const soleInArg = inArgs.length === 1 ? inArgs[0] : undefined;
|
|
500
|
-
if (
|
|
501
|
-
soleInArg &&
|
|
502
|
-
soleInArg.name === "" &&
|
|
503
|
-
relationTypeByIds.get(soleInArg.type_id) &&
|
|
504
|
-
!getTableNameFromRelationId(fn.return_type_relation_id, fn.return_type_id)
|
|
505
|
-
) {
|
|
506
|
-
return true;
|
|
507
|
-
}
|
|
508
|
-
return false;
|
|
381
|
+
return referencedTsType(schema, returns);
|
|
509
382
|
};
|
|
510
383
|
|
|
511
|
-
|
|
512
|
-
const getConflictError = (
|
|
384
|
+
const getArgTsType = (
|
|
513
385
|
schema: PostgresSchema,
|
|
514
|
-
|
|
515
|
-
fn: PostgresFunction,
|
|
516
|
-
inArgs: PostgresFunction["args"],
|
|
386
|
+
arg: RpcFunctionOverload["args"][number],
|
|
517
387
|
) => {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
//
|
|
523
|
-
//
|
|
524
|
-
if (
|
|
525
|
-
|
|
526
|
-
({ fn: otherFn, inArgs: otherInArgs }) => {
|
|
527
|
-
if (otherFn === fn) return false;
|
|
528
|
-
const otherSoleInArg =
|
|
529
|
-
otherInArgs.length === 1 ? otherInArgs[0] : undefined;
|
|
530
|
-
return (
|
|
531
|
-
otherSoleInArg &&
|
|
532
|
-
otherSoleInArg.name === "" &&
|
|
533
|
-
otherSoleInArg.has_default
|
|
534
|
-
);
|
|
535
|
-
},
|
|
536
|
-
);
|
|
537
|
-
|
|
538
|
-
const conflictingFn = conflictingFns[0];
|
|
539
|
-
if (conflictingFn) {
|
|
540
|
-
const returnTypeName =
|
|
541
|
-
typesById.get(conflictingFn.fn.return_type_id)?.name || "unknown";
|
|
542
|
-
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`;
|
|
543
|
-
}
|
|
388
|
+
const tsType = referencedTsType(schema, arg);
|
|
389
|
+
// A row-typed parameter takes the relation's columns: PostgREST fills the
|
|
390
|
+
// keys it does not receive with NULL, and the computed fields are never
|
|
391
|
+
// input. A named parameter is typed without them. An unnamed one keeps the
|
|
392
|
+
// plain `Row` reference, which is how postgrest-js versions predating
|
|
393
|
+
// `ComputedFields` recognise the function as a computed field.
|
|
394
|
+
if (arg.name === "" || arg.format === null || arg.type_schema === null) {
|
|
395
|
+
return tsType;
|
|
544
396
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
otherInArgs.length === 1 ? otherInArgs[0] : undefined;
|
|
553
|
-
return (
|
|
554
|
-
otherSoleInArg &&
|
|
555
|
-
otherSoleInArg.name === soleInArg.name &&
|
|
556
|
-
otherSoleInArg.type_id !== soleInArg.type_id
|
|
557
|
-
);
|
|
558
|
-
},
|
|
559
|
-
);
|
|
560
|
-
|
|
561
|
-
if (conflictingFns.length > 0) {
|
|
562
|
-
const allConflictingFunctions = [{ fn, inArgs }, ...conflictingFns];
|
|
563
|
-
const conflictList = allConflictingFunctions
|
|
564
|
-
.sort((a, b) => {
|
|
565
|
-
const aArgs = a.inArgs;
|
|
566
|
-
const bArgs = b.inArgs;
|
|
567
|
-
return (aArgs[0]?.type_id || 0) - (bArgs[0]?.type_id || 0);
|
|
568
|
-
})
|
|
569
|
-
.map((f) => {
|
|
570
|
-
const args = f.inArgs;
|
|
571
|
-
return `${schema.name}.${fn.name}(${args
|
|
572
|
-
.map(
|
|
573
|
-
(a) =>
|
|
574
|
-
`${a.name || ""} => ${typesById.get(a.type_id)?.name || "unknown"}`,
|
|
575
|
-
)
|
|
576
|
-
.join(", ")})`;
|
|
577
|
-
})
|
|
578
|
-
.join(", ");
|
|
579
|
-
|
|
580
|
-
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`;
|
|
581
|
-
}
|
|
397
|
+
const relationReference = findRelationReference(
|
|
398
|
+
arg.format,
|
|
399
|
+
typeContext,
|
|
400
|
+
arg.type_schema,
|
|
401
|
+
);
|
|
402
|
+
if (relationReference && tsType === `${relationReference}['Row']`) {
|
|
403
|
+
return `Omit<${tsType}, ${relationReference}['ComputedFields']>`;
|
|
582
404
|
}
|
|
583
|
-
|
|
584
|
-
return null;
|
|
405
|
+
return tsType;
|
|
585
406
|
};
|
|
586
407
|
|
|
587
|
-
const
|
|
408
|
+
const getArgsTsType = (
|
|
588
409
|
schema: PostgresSchema,
|
|
589
|
-
|
|
590
|
-
) =>
|
|
591
|
-
|
|
592
|
-
|
|
410
|
+
args: RpcFunctionOverload["args"],
|
|
411
|
+
) =>
|
|
412
|
+
`{ ${args.map(
|
|
413
|
+
(arg) =>
|
|
414
|
+
`${JSON.stringify(arg.name)}${arg.has_default ? "?" : ""}: ${getArgTsType(schema, arg)}`,
|
|
415
|
+
)} }`;
|
|
416
|
+
|
|
417
|
+
const getFunctionSignatures = (schema: PostgresSchema, fn: RpcFunction) => {
|
|
418
|
+
return fn.overloads
|
|
419
|
+
.map((overload) => {
|
|
593
420
|
// `never` would be wrong here: postgrest-js treats `Args extends { '': Row }`
|
|
594
421
|
// as a computed field (`never extends T` is always true, so same-named table
|
|
595
422
|
// columns get omitted from select results), and an object type with a required
|
|
596
423
|
// `never` property is uninhabited for any tool doing sound type math on the
|
|
597
424
|
// generated types. `Record<PropertyKey, never>` is the accurate type for
|
|
598
425
|
// "callable with no arguments".
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
let tsType = "unknown";
|
|
610
|
-
if (type) {
|
|
611
|
-
tsType = pgTypeToTsType(
|
|
612
|
-
schema,
|
|
613
|
-
type.name,
|
|
614
|
-
typeContext,
|
|
615
|
-
type.schema,
|
|
616
|
-
);
|
|
617
|
-
}
|
|
618
|
-
return { name, type: tsType, has_default };
|
|
619
|
-
},
|
|
620
|
-
);
|
|
621
|
-
argsType = `{ ${argsNameAndType.map(
|
|
622
|
-
({ name, type, has_default }) =>
|
|
623
|
-
`${JSON.stringify(name)}${has_default ? "?" : ""}: ${type}`,
|
|
624
|
-
)} }`;
|
|
625
|
-
}
|
|
626
|
-
returnType = `{ error: true } & ${JSON.stringify(conflictError)}`;
|
|
627
|
-
} else if (hasTableRowError(fn, inArgs)) {
|
|
628
|
-
// Special case for computed fields returning scalars functions
|
|
629
|
-
if (inArgs.length > 0) {
|
|
630
|
-
const argsNameAndType = inArgs.map(
|
|
631
|
-
({ name, type_id, has_default }) => {
|
|
632
|
-
const type = typesById.get(type_id);
|
|
633
|
-
let tsType = "unknown";
|
|
634
|
-
if (type) {
|
|
635
|
-
tsType = pgTypeToTsType(
|
|
636
|
-
schema,
|
|
637
|
-
type.name,
|
|
638
|
-
typeContext,
|
|
639
|
-
type.schema,
|
|
640
|
-
);
|
|
641
|
-
}
|
|
642
|
-
return { name, type: tsType, has_default };
|
|
643
|
-
},
|
|
644
|
-
);
|
|
645
|
-
argsType = `{ ${argsNameAndType.map(
|
|
646
|
-
({ name, type, has_default }) =>
|
|
647
|
-
`${JSON.stringify(name)}${has_default ? "?" : ""}: ${type}`,
|
|
648
|
-
)} }`;
|
|
649
|
-
}
|
|
650
|
-
returnType = `{ error: true } & ${JSON.stringify(
|
|
651
|
-
`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`,
|
|
652
|
-
)}`;
|
|
653
|
-
} else if (inArgs.length > 0) {
|
|
654
|
-
const argsNameAndType = inArgs.map(
|
|
655
|
-
({ name, type_id, has_default }) => {
|
|
656
|
-
const type = typesById.get(type_id);
|
|
657
|
-
let tsType = "unknown";
|
|
658
|
-
if (type) {
|
|
659
|
-
tsType = pgTypeToTsType(
|
|
660
|
-
schema,
|
|
661
|
-
type.name,
|
|
662
|
-
typeContext,
|
|
663
|
-
type.schema,
|
|
664
|
-
);
|
|
665
|
-
}
|
|
666
|
-
return { name, type: tsType, has_default };
|
|
667
|
-
},
|
|
668
|
-
);
|
|
669
|
-
argsType = `{ ${argsNameAndType.map(
|
|
670
|
-
({ name, type, has_default }) =>
|
|
671
|
-
`${JSON.stringify(name)}${has_default ? "?" : ""}: ${type}`,
|
|
672
|
-
)} }`;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
return `{ Args: ${argsType}; Returns: ${getFunctionTsReturnType(fn, returnType)} }`;
|
|
426
|
+
const argsType =
|
|
427
|
+
overload.args.length > 0
|
|
428
|
+
? getArgsTsType(schema, overload.args)
|
|
429
|
+
: "Record<PropertyKey, never>";
|
|
430
|
+
const returnType =
|
|
431
|
+
overload.error === null
|
|
432
|
+
? getFunctionReturnType(schema, overload)
|
|
433
|
+
: `{ error: true } & ${JSON.stringify(overload.error)}`;
|
|
434
|
+
|
|
435
|
+
return `{ Args: ${argsType}; Returns: ${getFunctionTsReturnType(overload, returnType)} }`;
|
|
676
436
|
})
|
|
677
437
|
.join(" |\n");
|
|
678
438
|
};
|
|
@@ -716,42 +476,37 @@ export const generateTypescript = async (
|
|
|
716
476
|
}
|
|
717
477
|
|
|
718
478
|
/**
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
-
*
|
|
723
|
-
* in `Row`.
|
|
724
|
-
*
|
|
725
|
-
* Matching on the argument's type OID rather than on `argument_types` is what
|
|
726
|
-
* makes named parameters work. `argument_types` is
|
|
727
|
-
* `pg_get_function_arguments()`, which renders the parameter *name* when the
|
|
728
|
-
* parameter has one — `category category` rather than `category` — and
|
|
729
|
-
* schema-qualifies the type when it is not visible in `search_path`. Both
|
|
730
|
-
* forms failed a bare string comparison against the relation name, so
|
|
731
|
-
* `create function name_translated(category category)` silently lost its
|
|
732
|
-
* computed field while the unnamed `name_translated(category)` kept it.
|
|
733
|
-
* See supabase/postgres-meta#1034.
|
|
479
|
+
* The computed fields of a relation, in function name order. A computed
|
|
480
|
+
* field is a function taking a single argument of the relation's own
|
|
481
|
+
* composite type, so it is selectable as if it were a column and belongs in
|
|
482
|
+
* `Row`; `resolveRpcFunctions` marks them with `computed_field_of`.
|
|
734
483
|
*/
|
|
735
|
-
const
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
484
|
+
const computedFieldsOf = (
|
|
485
|
+
schemaFunctions: RpcFunction[],
|
|
486
|
+
relation: { id: number },
|
|
487
|
+
) =>
|
|
488
|
+
schemaFunctions.flatMap((fn) => {
|
|
489
|
+
const overload = fn.overloads.find(
|
|
490
|
+
({ computed_field_of }) => computed_field_of === relation.id,
|
|
491
|
+
);
|
|
492
|
+
return overload ? [{ name: fn.name, overload }] : [];
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
const computedFieldTsDefinition = (
|
|
496
|
+
schema: PostgresSchema,
|
|
497
|
+
{ name, overload }: { name: string; overload: RpcFunctionOverload },
|
|
498
|
+
) =>
|
|
499
|
+
`${JSON.stringify(name)}: ${generateNullableUnionTsType(
|
|
500
|
+
getFunctionReturnType(schema, overload),
|
|
501
|
+
true,
|
|
502
|
+
)}`;
|
|
503
|
+
|
|
504
|
+
// `ComputedFields` names the `Row` keys PostgREST leaves out of `select('*')`,
|
|
505
|
+
// so postgrest-js does not have to infer them from `Functions`.
|
|
506
|
+
const computedFieldsTsType = (computedFields: { name: string }[]) =>
|
|
507
|
+
computedFields.length === 0
|
|
508
|
+
? "never"
|
|
509
|
+
: computedFields.map(({ name }) => JSON.stringify(name)).join(" | ");
|
|
755
510
|
|
|
756
511
|
let output = `
|
|
757
512
|
export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[]
|
|
@@ -771,11 +526,12 @@ export type Database = {
|
|
|
771
526
|
${
|
|
772
527
|
schemaTables.length === 0
|
|
773
528
|
? "[_ in never]: never"
|
|
774
|
-
: schemaTables.map(
|
|
775
|
-
(
|
|
529
|
+
: schemaTables.map(({ table, relationships }) => {
|
|
530
|
+
const computedFields = computedFieldsOf(
|
|
531
|
+
schemaFunctions,
|
|
776
532
|
table,
|
|
777
|
-
|
|
778
|
-
|
|
533
|
+
);
|
|
534
|
+
return `${JSON.stringify(table.name)}: {
|
|
779
535
|
Row: {
|
|
780
536
|
${[
|
|
781
537
|
...getColumns(table.id).map((column) =>
|
|
@@ -791,18 +547,12 @@ export type Database = {
|
|
|
791
547
|
typeContext,
|
|
792
548
|
),
|
|
793
549
|
),
|
|
794
|
-
...
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
)
|
|
798
|
-
.map(({ fn }) => {
|
|
799
|
-
return `${JSON.stringify(fn.name)}: ${generateNullableUnionTsType(
|
|
800
|
-
getFunctionReturnType(schema, fn),
|
|
801
|
-
true,
|
|
802
|
-
)}`;
|
|
803
|
-
}),
|
|
550
|
+
...computedFields.map((computedField) =>
|
|
551
|
+
computedFieldTsDefinition(schema, computedField),
|
|
552
|
+
),
|
|
804
553
|
]}
|
|
805
554
|
}
|
|
555
|
+
ComputedFields: ${computedFieldsTsType(computedFields)}
|
|
806
556
|
Insert: {
|
|
807
557
|
${getColumns(table.id).map((column) => {
|
|
808
558
|
if (
|
|
@@ -852,19 +602,20 @@ export type Database = {
|
|
|
852
602
|
Relationships: [
|
|
853
603
|
${relationships.map(generateRelationshiptTsDefinition)}
|
|
854
604
|
]
|
|
855
|
-
}
|
|
856
|
-
)
|
|
605
|
+
}`;
|
|
606
|
+
})
|
|
857
607
|
}
|
|
858
608
|
}
|
|
859
609
|
Views: {
|
|
860
610
|
${
|
|
861
611
|
schemaViews.length === 0
|
|
862
612
|
? "[_ in never]: never"
|
|
863
|
-
: schemaViews.map(
|
|
864
|
-
(
|
|
613
|
+
: schemaViews.map(({ view, relationships }) => {
|
|
614
|
+
const computedFields = computedFieldsOf(
|
|
615
|
+
schemaFunctions,
|
|
865
616
|
view,
|
|
866
|
-
|
|
867
|
-
|
|
617
|
+
);
|
|
618
|
+
return `${JSON.stringify(view.name)}: {
|
|
868
619
|
Row: {
|
|
869
620
|
${[
|
|
870
621
|
...getColumns(view.id).map((column) =>
|
|
@@ -880,19 +631,12 @@ export type Database = {
|
|
|
880
631
|
typeContext,
|
|
881
632
|
),
|
|
882
633
|
),
|
|
883
|
-
...
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
)
|
|
887
|
-
.map(
|
|
888
|
-
({ fn }) =>
|
|
889
|
-
`${JSON.stringify(fn.name)}: ${generateNullableUnionTsType(
|
|
890
|
-
getFunctionReturnType(schema, fn),
|
|
891
|
-
true,
|
|
892
|
-
)}`,
|
|
893
|
-
),
|
|
634
|
+
...computedFields.map((computedField) =>
|
|
635
|
+
computedFieldTsDefinition(schema, computedField),
|
|
636
|
+
),
|
|
894
637
|
]}
|
|
895
638
|
}
|
|
639
|
+
ComputedFields: ${computedFieldsTsType(computedFields)}
|
|
896
640
|
${
|
|
897
641
|
// Metadata predating the trigger-aware flags (still valid
|
|
898
642
|
// version 1 documents) falls back to the old gate.
|
|
@@ -942,37 +686,21 @@ export type Database = {
|
|
|
942
686
|
}Relationships: [
|
|
943
687
|
${relationships.map(generateRelationshiptTsDefinition)}
|
|
944
688
|
]
|
|
945
|
-
}
|
|
946
|
-
)
|
|
689
|
+
}`;
|
|
690
|
+
})
|
|
947
691
|
}
|
|
948
692
|
}
|
|
949
693
|
Functions: {
|
|
950
|
-
${
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
);
|
|
961
|
-
for (const fns of Object.values(schemaFunctionsGroupedByName)) {
|
|
962
|
-
fns.sort(
|
|
963
|
-
(a, b) =>
|
|
964
|
-
compareStrings(a.fn.argument_types, b.fn.argument_types) ||
|
|
965
|
-
compareStrings(a.fn.return_type, b.fn.return_type),
|
|
966
|
-
);
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
return Object.entries(schemaFunctionsGroupedByName)
|
|
970
|
-
.map(([fnName, fns]) => {
|
|
971
|
-
const functionSignatures = getFunctionSignatures(schema, fns);
|
|
972
|
-
return `${JSON.stringify(fnName)}:\n${functionSignatures}`;
|
|
973
|
-
})
|
|
974
|
-
.join(",\n");
|
|
975
|
-
})()}
|
|
694
|
+
${
|
|
695
|
+
schemaFunctions.length === 0
|
|
696
|
+
? "[_ in never]: never"
|
|
697
|
+
: schemaFunctions
|
|
698
|
+
.map(
|
|
699
|
+
(fn) =>
|
|
700
|
+
`${JSON.stringify(fn.name)}:\n${getFunctionSignatures(schema, fn)}`,
|
|
701
|
+
)
|
|
702
|
+
.join(",\n")
|
|
703
|
+
}
|
|
976
704
|
}
|
|
977
705
|
Enums: {
|
|
978
706
|
${
|
|
@@ -1020,7 +748,7 @@ export type Database = {
|
|
|
1020
748
|
type DatabaseWithoutInternals = Omit<Database, '__InternalSupabase'>
|
|
1021
749
|
|
|
1022
750
|
type DefaultSchema = DatabaseWithoutInternals[Extract<keyof Database, ${JSON.stringify(
|
|
1023
|
-
|
|
751
|
+
resolvedDefaultSchema,
|
|
1024
752
|
)}>]
|
|
1025
753
|
|
|
1026
754
|
export type Tables<
|
|
@@ -1157,14 +885,7 @@ export const pgTypeToTsType = (
|
|
|
1157
885
|
// it whenever the owning schema is known.
|
|
1158
886
|
typeSchema?: string,
|
|
1159
887
|
): string => {
|
|
1160
|
-
const {
|
|
1161
|
-
types,
|
|
1162
|
-
schemas,
|
|
1163
|
-
tables,
|
|
1164
|
-
views,
|
|
1165
|
-
foreignTables = [],
|
|
1166
|
-
materializedViews = [],
|
|
1167
|
-
} = context;
|
|
888
|
+
const { types, schemas } = context;
|
|
1168
889
|
if (pgType === "bool") {
|
|
1169
890
|
return "boolean";
|
|
1170
891
|
} else if (
|
|
@@ -1228,42 +949,63 @@ export const pgTypeToTsType = (
|
|
|
1228
949
|
return "unknown";
|
|
1229
950
|
}
|
|
1230
951
|
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
["Tables", foreignTables],
|
|
1240
|
-
["Views", views],
|
|
1241
|
-
["Views", materializedViews],
|
|
1242
|
-
] as const;
|
|
1243
|
-
const findRelation = (
|
|
1244
|
-
matches: (relation: { name: string; schema: string }) => boolean,
|
|
1245
|
-
) => {
|
|
1246
|
-
for (const [kind, relations] of relationKinds) {
|
|
1247
|
-
const relation = relations.find(matches);
|
|
1248
|
-
if (relation) return { kind, relation };
|
|
1249
|
-
}
|
|
1250
|
-
return undefined;
|
|
1251
|
-
};
|
|
1252
|
-
const match =
|
|
1253
|
-
findRelation(
|
|
1254
|
-
({ name, schema: relationSchema }) =>
|
|
1255
|
-
name === pgType && relationSchema === preferredSchema,
|
|
1256
|
-
) ?? findRelation(({ name }) => name === pgType);
|
|
1257
|
-
if (match) {
|
|
1258
|
-
const { kind, relation } = match;
|
|
1259
|
-
if (schemas.some(({ name }) => name === relation.schema)) {
|
|
1260
|
-
return `Database[${JSON.stringify(relation.schema)}]['${kind}'][${JSON.stringify(
|
|
1261
|
-
relation.name,
|
|
1262
|
-
)}]['Row']`;
|
|
1263
|
-
}
|
|
1264
|
-
return "unknown";
|
|
1265
|
-
}
|
|
952
|
+
const relationReference = findRelationReference(
|
|
953
|
+
pgType,
|
|
954
|
+
context,
|
|
955
|
+
preferredSchema,
|
|
956
|
+
);
|
|
957
|
+
return relationReference ? `${relationReference}['Row']` : "unknown";
|
|
958
|
+
}
|
|
959
|
+
};
|
|
1266
960
|
|
|
1267
|
-
|
|
961
|
+
/**
|
|
962
|
+
* The `Database[schema][kind][name]` reference of the relation whose row type
|
|
963
|
+
* is `pgType`, or `undefined` when no generated relation has that type.
|
|
964
|
+
*
|
|
965
|
+
* A relation-typed value carries only a bare type name, so several relations
|
|
966
|
+
* can share it and `preferredSchema` is what disambiguates them. Match on the
|
|
967
|
+
* schema across every relation kind before falling back to kind order,
|
|
968
|
+
* otherwise a relation in an unrelated schema outranks the one the caller
|
|
969
|
+
* meant. Foreign tables are generated under `Tables` and materialized views
|
|
970
|
+
* under `Views`, alongside their plain counterparts.
|
|
971
|
+
*/
|
|
972
|
+
const findRelationReference = (
|
|
973
|
+
pgType: string,
|
|
974
|
+
context: TypeResolutionContext,
|
|
975
|
+
preferredSchema: string,
|
|
976
|
+
): string | undefined => {
|
|
977
|
+
const {
|
|
978
|
+
schemas,
|
|
979
|
+
tables,
|
|
980
|
+
views,
|
|
981
|
+
foreignTables = [],
|
|
982
|
+
materializedViews = [],
|
|
983
|
+
} = context;
|
|
984
|
+
const relationKinds = [
|
|
985
|
+
["Tables", tables],
|
|
986
|
+
["Tables", foreignTables],
|
|
987
|
+
["Views", views],
|
|
988
|
+
["Views", materializedViews],
|
|
989
|
+
] as const;
|
|
990
|
+
const findRelation = (
|
|
991
|
+
matches: (relation: { name: string; schema: string }) => boolean,
|
|
992
|
+
) => {
|
|
993
|
+
for (const [kind, relations] of relationKinds) {
|
|
994
|
+
const relation = relations.find(matches);
|
|
995
|
+
if (relation) return { kind, relation };
|
|
996
|
+
}
|
|
997
|
+
return undefined;
|
|
998
|
+
};
|
|
999
|
+
const match =
|
|
1000
|
+
findRelation(
|
|
1001
|
+
({ name, schema: relationSchema }) =>
|
|
1002
|
+
name === pgType && relationSchema === preferredSchema,
|
|
1003
|
+
) ?? findRelation(({ name }) => name === pgType);
|
|
1004
|
+
if (!match || !schemas.some(({ name }) => name === match.relation.schema)) {
|
|
1005
|
+
return undefined;
|
|
1268
1006
|
}
|
|
1007
|
+
const { kind, relation } = match;
|
|
1008
|
+
return `Database[${JSON.stringify(relation.schema)}]['${kind}'][${JSON.stringify(
|
|
1009
|
+
relation.name,
|
|
1010
|
+
)}]`;
|
|
1269
1011
|
};
|