@supabase/postgrest-typegen 0.2.4 → 0.2.5

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