@supabase/postgrest-typegen 0.4.0 → 0.6.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 +154 -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 +72 -288
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/introspection/index.js +8 -3
- package/dist/introspection/node-tree.d.ts +18 -0
- package/dist/introspection/node-tree.js +100 -0
- package/dist/introspection/sql/view_definitions.sql.d.ts +8 -0
- package/dist/introspection/sql/view_definitions.sql.js +43 -0
- package/dist/introspection/view-nullability.d.ts +14 -0
- package/dist/introspection/view-nullability.js +167 -0
- package/dist/rpc.d.ts +2 -0
- package/dist/rpc.js +232 -0
- package/dist/sort.js +23 -0
- package/dist/types.d.ts +151 -0
- package/dist/types.js +92 -0
- package/package.json +5 -4
- package/src/generation/index.ts +0 -5
- package/src/generation/typescript.ts +102 -383
- package/src/index.ts +1 -0
- package/src/introspection/index.ts +8 -2
- package/src/introspection/node-tree.ts +129 -0
- package/src/introspection/sql/view_definitions.sql.ts +45 -0
- package/src/introspection/view-nullability.ts +243 -0
- package/src/rpc.ts +307 -0
- package/src/sort.ts +31 -0
- package/src/types.ts +102 -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,12 @@ 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
|
+
const rpcFunctions = metadata.rpcFunctions ?? resolveRpcFunctions(metadata);
|
|
32
33
|
const { detectOneToOneRelationships = false, postgrestVersion, defaultSchema = "public", format = defaultFormat, } = opts;
|
|
34
|
+
const resolvedDefaultSchema = schemas.some(({ name }) => name === defaultSchema) || !schemas[0]
|
|
35
|
+
? defaultSchema
|
|
36
|
+
: schemas[0].name;
|
|
33
37
|
// Every relation-typed value resolves against the same collections, so build
|
|
34
38
|
// the lookup context once rather than reassembling it at each call site.
|
|
35
39
|
const typeContext = {
|
|
@@ -47,8 +51,7 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
47
51
|
{ tables: [], views: [], functions: [], enums: [], compositeTypes: [] },
|
|
48
52
|
]));
|
|
49
53
|
const columnsByTableId = {};
|
|
50
|
-
const
|
|
51
|
-
const relationTypeByIds = new Map();
|
|
54
|
+
const relationsById = new Map();
|
|
52
55
|
// group types by id for quicker lookup
|
|
53
56
|
const typesById = new Map();
|
|
54
57
|
const tablesLike = [
|
|
@@ -59,7 +62,7 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
59
62
|
];
|
|
60
63
|
for (const tableLike of tablesLike) {
|
|
61
64
|
columnsByTableId[tableLike.id] = [];
|
|
62
|
-
|
|
65
|
+
relationsById.set(tableLike.id, tableLike);
|
|
63
66
|
}
|
|
64
67
|
for (const column of columns) {
|
|
65
68
|
columnsByTableId[column.table_id]?.push(column);
|
|
@@ -80,10 +83,6 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
80
83
|
};
|
|
81
84
|
for (const type of types) {
|
|
82
85
|
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
86
|
if (type.enums.length > 0) {
|
|
88
87
|
introspectionBySchema[type.schema]?.enums.push(type);
|
|
89
88
|
}
|
|
@@ -146,51 +145,8 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
146
145
|
relationships: getRelationships(materializedView),
|
|
147
146
|
});
|
|
148
147
|
}
|
|
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
|
-
}
|
|
148
|
+
for (const fn of rpcFunctions) {
|
|
149
|
+
introspectionBySchema[fn.schema]?.functions.push(fn);
|
|
194
150
|
}
|
|
195
151
|
// These per-schema groups MERGE multiple metadata collections — `.tables`
|
|
196
152
|
// combines tables + foreign tables, `.views` combines views + materialized
|
|
@@ -199,83 +155,45 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
199
155
|
for (const schemaIntrospection of Object.values(introspectionBySchema)) {
|
|
200
156
|
schemaIntrospection.tables.sort((a, b) => compareStrings(a.table.name, b.table.name));
|
|
201
157
|
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
158
|
schemaIntrospection.enums.sort((a, b) => compareStrings(a.name, b.name));
|
|
204
159
|
schemaIntrospection.compositeTypes.sort((a, b) => compareStrings(a.name, b.name));
|
|
205
160
|
}
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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}` : ""}`;
|
|
161
|
+
const referencedTsType = (schema, { format, type_schema }) => format === null || type_schema === null
|
|
162
|
+
? "unknown"
|
|
163
|
+
: pgTypeToTsType(schema, format, typeContext, type_schema);
|
|
164
|
+
const getFunctionTsReturnType = (overload, returnType) => {
|
|
165
|
+
const { setof_options } = overload;
|
|
166
|
+
const setofOptionsInfo = setof_options
|
|
167
|
+
? `SetofOptions: {
|
|
168
|
+
from: ${JSON.stringify(setof_options.from)}
|
|
169
|
+
to: ${JSON.stringify(setof_options.to)}
|
|
170
|
+
isOneToOne: ${setof_options.is_one_to_one}
|
|
171
|
+
isSetofReturn: ${setof_options.is_setof_return}
|
|
172
|
+
}`
|
|
173
|
+
: "";
|
|
174
|
+
// A ROWS 1 estimate may only collapse the array type when SetofOptions is also emitted:
|
|
175
|
+
// postgrest-js relies on isOneToOne to restore the array shape for direct RPC calls.
|
|
176
|
+
// Without that metadata PostgREST still returns an array, so the collapse would make
|
|
177
|
+
// the type diverge from the actual response.
|
|
178
|
+
const collapsibleSingleRow = setof_options?.is_one_to_one === true;
|
|
179
|
+
const isArray = overload.error === null &&
|
|
180
|
+
overload.is_set_returning &&
|
|
181
|
+
!collapsibleSingleRow;
|
|
182
|
+
return `${returnType}${isArray ? "[]" : ""}
|
|
183
|
+
${setofOptionsInfo}`;
|
|
258
184
|
};
|
|
259
|
-
const getFunctionReturnType = (schema,
|
|
185
|
+
const getFunctionReturnType = (schema, { returns }) => {
|
|
260
186
|
// 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
|
-
});
|
|
187
|
+
if (returns.columns) {
|
|
271
188
|
return `{
|
|
272
|
-
${
|
|
189
|
+
${returns.columns.map((column) => `${JSON.stringify(column.name)}: ${referencedTsType(schema, column)}`)}
|
|
273
190
|
}`;
|
|
274
191
|
}
|
|
275
|
-
// Case 2: returns a relation
|
|
276
|
-
const relation =
|
|
277
|
-
|
|
278
|
-
|
|
192
|
+
// Case 2: returns a row type of a relation in this schema.
|
|
193
|
+
const relation = returns.relation_id === null
|
|
194
|
+
? undefined
|
|
195
|
+
: relationsById.get(returns.relation_id);
|
|
196
|
+
if (relation && relation.schema === schema.name) {
|
|
279
197
|
return `{
|
|
280
198
|
${getColumns(relation.id)
|
|
281
199
|
.map((column) => generateColumnTsDefinition(schema, {
|
|
@@ -289,120 +207,41 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
289
207
|
}`;
|
|
290
208
|
}
|
|
291
209
|
// 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";
|
|
210
|
+
return referencedTsType(schema, returns);
|
|
297
211
|
};
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const soleInArg = inArgs.length === 1 ? inArgs[0] : undefined;
|
|
301
|
-
if (soleInArg &&
|
|
302
|
-
soleInArg.name === "" &&
|
|
303
|
-
relationTypeByIds.get(soleInArg.type_id) &&
|
|
304
|
-
!getTableNameFromRelationId(fn.return_type_relation_id, fn.return_type_id)) {
|
|
305
|
-
return true;
|
|
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
|
-
}
|
|
331
|
-
}
|
|
332
|
-
// Pattern 2: Same parameter name but different types (unresolvable overloads)
|
|
333
|
-
if (soleInArg && soleInArg.name !== "") {
|
|
334
|
-
const conflictingFns = fns.filter(({ fn: otherFn, inArgs: otherInArgs }) => {
|
|
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
|
-
}
|
|
359
|
-
}
|
|
360
|
-
return null;
|
|
361
|
-
};
|
|
362
|
-
const getArgTsType = (schema, { name, type_id }) => {
|
|
363
|
-
const type = typesById.get(type_id);
|
|
364
|
-
if (!type) {
|
|
365
|
-
return "unknown";
|
|
366
|
-
}
|
|
367
|
-
const tsType = pgTypeToTsType(schema, type.name, typeContext, type.schema);
|
|
212
|
+
const getArgTsType = (schema, arg) => {
|
|
213
|
+
const tsType = referencedTsType(schema, arg);
|
|
368
214
|
// A row-typed parameter takes the relation's columns: PostgREST fills the
|
|
369
215
|
// keys it does not receive with NULL, and the computed fields are never
|
|
370
216
|
// input. A named parameter is typed without them. An unnamed one keeps the
|
|
371
217
|
// plain `Row` reference, which is how postgrest-js versions predating
|
|
372
218
|
// `ComputedFields` recognise the function as a computed field.
|
|
373
|
-
if (name === "") {
|
|
219
|
+
if (arg.name === "" || arg.format === null || arg.type_schema === null) {
|
|
374
220
|
return tsType;
|
|
375
221
|
}
|
|
376
|
-
const relationReference = findRelationReference(
|
|
222
|
+
const relationReference = findRelationReference(arg.format, typeContext, arg.type_schema);
|
|
377
223
|
if (relationReference && tsType === `${relationReference}['Row']`) {
|
|
378
224
|
return `Omit<${tsType}, ${relationReference}['ComputedFields']>`;
|
|
379
225
|
}
|
|
380
226
|
return tsType;
|
|
381
227
|
};
|
|
382
|
-
const getArgsTsType = (schema,
|
|
383
|
-
const getFunctionSignatures = (schema,
|
|
384
|
-
return
|
|
385
|
-
.map((
|
|
228
|
+
const getArgsTsType = (schema, args) => `{ ${args.map((arg) => `${JSON.stringify(arg.name)}${arg.has_default ? "?" : ""}: ${getArgTsType(schema, arg)}`)} }`;
|
|
229
|
+
const getFunctionSignatures = (schema, fn) => {
|
|
230
|
+
return fn.overloads
|
|
231
|
+
.map((overload) => {
|
|
386
232
|
// `never` would be wrong here: postgrest-js treats `Args extends { '': Row }`
|
|
387
233
|
// as a computed field (`never extends T` is always true, so same-named table
|
|
388
234
|
// columns get omitted from select results), and an object type with a required
|
|
389
235
|
// `never` property is uninhabited for any tool doing sound type math on the
|
|
390
236
|
// generated types. `Record<PropertyKey, never>` is the accurate type for
|
|
391
237
|
// "callable with no arguments".
|
|
392
|
-
const argsType =
|
|
393
|
-
? getArgsTsType(schema,
|
|
238
|
+
const argsType = overload.args.length > 0
|
|
239
|
+
? getArgsTsType(schema, overload.args)
|
|
394
240
|
: "Record<PropertyKey, never>";
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
returnType = `{ error: true } & ${JSON.stringify(conflictError)}`;
|
|
400
|
-
}
|
|
401
|
-
else if (hasTableRowError(fn, inArgs)) {
|
|
402
|
-
// Special case for computed fields returning scalars functions
|
|
403
|
-
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`)}`;
|
|
404
|
-
}
|
|
405
|
-
return `{ Args: ${argsType}; Returns: ${getFunctionTsReturnType(fn, returnType)} }`;
|
|
241
|
+
const returnType = overload.error === null
|
|
242
|
+
? getFunctionReturnType(schema, overload)
|
|
243
|
+
: `{ error: true } & ${JSON.stringify(overload.error)}`;
|
|
244
|
+
return `{ Args: ${argsType}; Returns: ${getFunctionTsReturnType(overload, returnType)} }`;
|
|
406
245
|
})
|
|
407
246
|
.join(" |\n");
|
|
408
247
|
};
|
|
@@ -429,62 +268,21 @@ export const generateTypescript = async (metadata, opts = {}) => {
|
|
|
429
268
|
return `${JSON.stringify(column.name)}${column.is_optional ? "?" : ""}: ${generateNullableUnionTsType(pgTypeToTsType(schema, column.format, context, column.type_schema), column.is_nullable)}`;
|
|
430
269
|
}
|
|
431
270
|
/**
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
*
|
|
435
|
-
*
|
|
436
|
-
* in `Row`.
|
|
437
|
-
*
|
|
438
|
-
* Matching on the argument's type OID rather than on `argument_types` is what
|
|
439
|
-
* makes named parameters work. `argument_types` is
|
|
440
|
-
* `pg_get_function_arguments()`, which renders the parameter *name* when the
|
|
441
|
-
* parameter has one — `category category` rather than `category` — and
|
|
442
|
-
* schema-qualifies the type when it is not visible in `search_path`. Both
|
|
443
|
-
* forms failed a bare string comparison against the relation name, so
|
|
444
|
-
* `create function name_translated(category category)` silently lost its
|
|
445
|
-
* computed field while the unnamed `name_translated(category)` kept it.
|
|
446
|
-
* See supabase/postgres-meta#1034.
|
|
271
|
+
* The computed fields of a relation, in function name order. A computed
|
|
272
|
+
* field is a function taking a single argument of the relation's own
|
|
273
|
+
* composite type, so it is selectable as if it were a column and belongs in
|
|
274
|
+
* `Row`; `resolveRpcFunctions` marks them with `computed_field_of`.
|
|
447
275
|
*/
|
|
448
|
-
const
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
// A `variadic` argument is an array type and never appears here.
|
|
454
|
-
const argType = relationTypeByIds.get(soleInArg.type_id);
|
|
455
|
-
if (argType) {
|
|
456
|
-
return argType.type_relation_id === relation.id;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
// Fall back to the historical comparison when the argument's type is not in
|
|
460
|
-
// the metadata (callers may pass a reduced `types` list), so a computed
|
|
461
|
-
// field that resolved before keeps resolving.
|
|
462
|
-
return fn.argument_types === relation.name;
|
|
463
|
-
};
|
|
464
|
-
/**
|
|
465
|
-
* The computed fields of a relation, in `schemaFunctions` order. A function
|
|
466
|
-
* named like one of the relation's columns is left out: PostgREST resolves
|
|
467
|
-
* the name to the column, so it is neither selectable nor part of `Row`.
|
|
468
|
-
*/
|
|
469
|
-
const computedFieldsOf = (schemaFunctions, relation) => {
|
|
470
|
-
const columnNames = new Set(getColumns(relation.id).map((column) => column.name));
|
|
471
|
-
const seen = new Set();
|
|
472
|
-
return schemaFunctions.filter(({ fn, inArgs }) => {
|
|
473
|
-
if (!isComputedFieldOf(fn, inArgs, relation) ||
|
|
474
|
-
columnNames.has(fn.name) ||
|
|
475
|
-
seen.has(fn.name)) {
|
|
476
|
-
return false;
|
|
477
|
-
}
|
|
478
|
-
seen.add(fn.name);
|
|
479
|
-
return true;
|
|
480
|
-
});
|
|
481
|
-
};
|
|
482
|
-
const computedFieldTsDefinition = (schema, { fn }) => `${JSON.stringify(fn.name)}: ${generateNullableUnionTsType(getFunctionReturnType(schema, fn), true)}`;
|
|
276
|
+
const computedFieldsOf = (schemaFunctions, relation) => schemaFunctions.flatMap((fn) => {
|
|
277
|
+
const overload = fn.overloads.find(({ computed_field_of }) => computed_field_of === relation.id);
|
|
278
|
+
return overload ? [{ name: fn.name, overload }] : [];
|
|
279
|
+
});
|
|
280
|
+
const computedFieldTsDefinition = (schema, { name, overload }) => `${JSON.stringify(name)}: ${generateNullableUnionTsType(getFunctionReturnType(schema, overload), true)}`;
|
|
483
281
|
// `ComputedFields` names the `Row` keys PostgREST leaves out of `select('*')`,
|
|
484
282
|
// so postgrest-js does not have to infer them from `Functions`.
|
|
485
283
|
const computedFieldsTsType = (computedFields) => computedFields.length === 0
|
|
486
284
|
? "never"
|
|
487
|
-
: computedFields.map(({
|
|
285
|
+
: computedFields.map(({ name }) => JSON.stringify(name)).join(" | ");
|
|
488
286
|
let output = `
|
|
489
287
|
export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[]
|
|
490
288
|
|
|
@@ -611,25 +409,11 @@ export type Database = {
|
|
|
611
409
|
})}
|
|
612
410
|
}
|
|
613
411
|
Functions: {
|
|
614
|
-
${
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
(acc[curr.fn.name] ??= []).push(curr);
|
|
620
|
-
return acc;
|
|
621
|
-
}, {});
|
|
622
|
-
for (const fns of Object.values(schemaFunctionsGroupedByName)) {
|
|
623
|
-
fns.sort((a, b) => compareStrings(a.fn.argument_types, b.fn.argument_types) ||
|
|
624
|
-
compareStrings(a.fn.return_type, b.fn.return_type));
|
|
625
|
-
}
|
|
626
|
-
return Object.entries(schemaFunctionsGroupedByName)
|
|
627
|
-
.map(([fnName, fns]) => {
|
|
628
|
-
const functionSignatures = getFunctionSignatures(schema, fns);
|
|
629
|
-
return `${JSON.stringify(fnName)}:\n${functionSignatures}`;
|
|
630
|
-
})
|
|
631
|
-
.join(",\n");
|
|
632
|
-
})()}
|
|
412
|
+
${schemaFunctions.length === 0
|
|
413
|
+
? "[_ in never]: never"
|
|
414
|
+
: schemaFunctions
|
|
415
|
+
.map((fn) => `${JSON.stringify(fn.name)}:\n${getFunctionSignatures(schema, fn)}`)
|
|
416
|
+
.join(",\n")}
|
|
633
417
|
}
|
|
634
418
|
Enums: {
|
|
635
419
|
${schemaEnums.length === 0
|
|
@@ -658,7 +442,7 @@ export type Database = {
|
|
|
658
442
|
|
|
659
443
|
type DatabaseWithoutInternals = Omit<Database, '__InternalSupabase'>
|
|
660
444
|
|
|
661
|
-
type DefaultSchema = DatabaseWithoutInternals[Extract<keyof Database, ${JSON.stringify(
|
|
445
|
+
type DefaultSchema = DatabaseWithoutInternals[Extract<keyof Database, ${JSON.stringify(resolvedDefaultSchema)}>]
|
|
662
446
|
|
|
663
447
|
export type Tables<
|
|
664
448
|
DefaultSchemaTableNameOrOptions extends
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { GENERATOR_METADATA_VERSION, } from "../types.js";
|
|
2
|
+
import { resolveRpcFunctions } from "../rpc.js";
|
|
2
3
|
import { normalizeRows } from "./normalize.js";
|
|
3
4
|
import { listRelationships } from "./relationships.js";
|
|
4
5
|
import { COLUMNS_SQL } from "./sql/columns.sql.js";
|
|
@@ -10,7 +11,9 @@ import { PRIMARY_KEYS_SQL } from "./sql/primary_keys.sql.js";
|
|
|
10
11
|
import { SCHEMAS_SQL } from "./sql/schemas.sql.js";
|
|
11
12
|
import { TABLES_SQL } from "./sql/table.sql.js";
|
|
12
13
|
import { TYPES_SQL } from "./sql/types.sql.js";
|
|
14
|
+
import { VIEW_DEFINITIONS_SQL } from "./sql/view_definitions.sql.js";
|
|
13
15
|
import { VIEWS_SQL } from "./sql/views.sql.js";
|
|
16
|
+
import { applyViewColumnNullability } from "./view-nullability.js";
|
|
14
17
|
/**
|
|
15
18
|
* Introspect a database into the `GeneratorMetadata` contract.
|
|
16
19
|
*
|
|
@@ -31,7 +34,7 @@ export async function introspect(db, opts = {}) {
|
|
|
31
34
|
// introspection queries under Promise.all. A pooled Queryable runs them in
|
|
32
35
|
// parallel; a single-connection one pipelines them, which is no worse than
|
|
33
36
|
// awaiting each in turn.
|
|
34
|
-
const [schemas, tables, foreignTables, views, materializedViews, columns, primaryKeys, relationships, functions, types,] = await Promise.all([
|
|
37
|
+
const [schemas, tables, foreignTables, views, materializedViews, columns, primaryKeys, relationships, functions, types, viewDefinitions,] = await Promise.all([
|
|
35
38
|
queryRows(SCHEMAS_SQL({ schemaFilter: systemExcludingFilter })),
|
|
36
39
|
queryRows(TABLES_SQL({ schemaFilter: systemExcludingFilter })),
|
|
37
40
|
queryRows(FOREIGN_TABLES_SQL({ schemaFilter: plainFilter })),
|
|
@@ -42,18 +45,20 @@ export async function introspect(db, opts = {}) {
|
|
|
42
45
|
listRelationships(db, systemExcludingFilter),
|
|
43
46
|
queryRows(FUNCTIONS_SQL({ schemaFilter: systemExcludingFilter })),
|
|
44
47
|
queryRows(TYPES_SQL),
|
|
48
|
+
queryRows(VIEW_DEFINITIONS_SQL({ schemaFilter: systemExcludingFilter })),
|
|
45
49
|
]);
|
|
46
|
-
|
|
50
|
+
const metadata = {
|
|
47
51
|
version: GENERATOR_METADATA_VERSION,
|
|
48
52
|
schemas,
|
|
49
53
|
tables,
|
|
50
54
|
foreignTables,
|
|
51
55
|
views,
|
|
52
56
|
materializedViews,
|
|
53
|
-
columns,
|
|
57
|
+
columns: applyViewColumnNullability(columns, viewDefinitions),
|
|
54
58
|
primaryKeys,
|
|
55
59
|
relationships,
|
|
56
60
|
functions: functions.filter(({ return_type }) => !["trigger", "event_trigger"].includes(return_type)),
|
|
57
61
|
types,
|
|
58
62
|
};
|
|
63
|
+
return { ...metadata, rpcFunctions: resolveRpcFunctions(metadata) };
|
|
59
64
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A value in the text form of a `pg_node_tree`: a scalar token, `null` for
|
|
3
|
+
* `<>`, a list, or a node. Integer, OID and bitmapset lists (`(i 1 2)`,
|
|
4
|
+
* `(o 23)`, `(b 3)`) lose their marker and become lists of scalar tokens.
|
|
5
|
+
*/
|
|
6
|
+
export type NodeTreeValue = string | null | NodeTreeValue[] | NodeTreeNode;
|
|
7
|
+
export interface NodeTreeNode {
|
|
8
|
+
type: string;
|
|
9
|
+
fields: Record<string, NodeTreeValue>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Parse the text form of a `pg_node_tree` (for example
|
|
13
|
+
* `pg_rewrite.ev_action::text`), following the tokenizer of PostgreSQL's
|
|
14
|
+
* `read.c`: tokens are separated by whitespace, `(`, `)`, `{` and `}` are
|
|
15
|
+
* tokens of their own, and a backslash escapes the next character.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseNodeTree(text: string): NodeTreeValue;
|
|
18
|
+
export declare function isNode(value: NodeTreeValue | undefined, type?: string): value is NodeTreeNode;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const LIST_MARKERS = new Set(["i", "o", "b", "x"]);
|
|
2
|
+
const WHITESPACE = " \t\n";
|
|
3
|
+
const PUNCTUATION = "(){}";
|
|
4
|
+
/**
|
|
5
|
+
* Parse the text form of a `pg_node_tree` (for example
|
|
6
|
+
* `pg_rewrite.ev_action::text`), following the tokenizer of PostgreSQL's
|
|
7
|
+
* `read.c`: tokens are separated by whitespace, `(`, `)`, `{` and `}` are
|
|
8
|
+
* tokens of their own, and a backslash escapes the next character.
|
|
9
|
+
*/
|
|
10
|
+
export function parseNodeTree(text) {
|
|
11
|
+
const tokens = tokenize(text);
|
|
12
|
+
let position = 0;
|
|
13
|
+
const peek = () => tokens[position];
|
|
14
|
+
const next = () => {
|
|
15
|
+
const token = tokens[position++];
|
|
16
|
+
if (token === undefined) {
|
|
17
|
+
throw new Error("Unexpected end of pg_node_tree");
|
|
18
|
+
}
|
|
19
|
+
return token;
|
|
20
|
+
};
|
|
21
|
+
const isFieldEnd = (token) => token === undefined || token.raw === "}" || token.raw.startsWith(":");
|
|
22
|
+
const parseValue = () => {
|
|
23
|
+
const token = next();
|
|
24
|
+
if (token.raw === "{") {
|
|
25
|
+
const type = next().text;
|
|
26
|
+
const fields = {};
|
|
27
|
+
while (peek()?.raw !== "}") {
|
|
28
|
+
const name = next().raw.slice(1);
|
|
29
|
+
fields[name] = isFieldEnd(peek()) ? null : parseValue();
|
|
30
|
+
// Some fields span several tokens, such as a constant's
|
|
31
|
+
// `:constvalue 4 [ 1 0 0 0 ]`; only the first one is kept.
|
|
32
|
+
while (!isFieldEnd(peek())) {
|
|
33
|
+
parseValue();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
next();
|
|
37
|
+
return { type, fields };
|
|
38
|
+
}
|
|
39
|
+
if (token.raw === "(") {
|
|
40
|
+
const items = [];
|
|
41
|
+
const first = peek();
|
|
42
|
+
if (first !== undefined && LIST_MARKERS.has(first.raw)) {
|
|
43
|
+
next();
|
|
44
|
+
}
|
|
45
|
+
while (peek()?.raw !== ")") {
|
|
46
|
+
items.push(parseValue());
|
|
47
|
+
}
|
|
48
|
+
next();
|
|
49
|
+
return items;
|
|
50
|
+
}
|
|
51
|
+
if (token.raw === "<>") {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return token.text;
|
|
55
|
+
};
|
|
56
|
+
const value = parseValue();
|
|
57
|
+
if (position !== tokens.length) {
|
|
58
|
+
throw new Error("Trailing tokens after pg_node_tree");
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
function tokenize(text) {
|
|
63
|
+
const tokens = [];
|
|
64
|
+
let index = 0;
|
|
65
|
+
while (index < text.length) {
|
|
66
|
+
const character = text.charAt(index);
|
|
67
|
+
if (WHITESPACE.includes(character)) {
|
|
68
|
+
index++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (PUNCTUATION.includes(character)) {
|
|
72
|
+
tokens.push({ raw: character, text: character });
|
|
73
|
+
index++;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const start = index;
|
|
77
|
+
let unescaped = "";
|
|
78
|
+
while (index < text.length) {
|
|
79
|
+
const current = text.charAt(index);
|
|
80
|
+
if (current === "\\" && index + 1 < text.length) {
|
|
81
|
+
unescaped += text.charAt(index + 1);
|
|
82
|
+
index += 2;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (WHITESPACE.includes(current) || PUNCTUATION.includes(current)) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
unescaped += current;
|
|
89
|
+
index++;
|
|
90
|
+
}
|
|
91
|
+
tokens.push({ raw: text.slice(start, index), text: unescaped });
|
|
92
|
+
}
|
|
93
|
+
return tokens;
|
|
94
|
+
}
|
|
95
|
+
export function isNode(value, type) {
|
|
96
|
+
return (typeof value === "object" &&
|
|
97
|
+
value !== null &&
|
|
98
|
+
!Array.isArray(value) &&
|
|
99
|
+
(type === undefined || value.type === type));
|
|
100
|
+
}
|