@supabase/postgrest-typegen 0.2.0 → 0.2.2

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.
Files changed (66) hide show
  1. package/README.md +12 -0
  2. package/dist/collation.d.ts +2 -0
  3. package/dist/collation.js +15 -0
  4. package/dist/generation/go.js +30 -2
  5. package/dist/generation/index.d.ts +1 -1
  6. package/dist/generation/python.js +24 -10
  7. package/dist/generation/swift.js +52 -3
  8. package/dist/generation/typescript.d.ts +18 -7
  9. package/dist/generation/typescript.js +122 -82
  10. package/dist/introspection/index.js +6 -23
  11. package/dist/introspection/relationships.d.ts +4 -4
  12. package/dist/introspection/relationships.js +4 -11
  13. package/dist/introspection/sql/columns.sql.d.ts +2 -7
  14. package/dist/introspection/sql/columns.sql.js +32 -9
  15. package/dist/introspection/sql/common.d.ts +7 -15
  16. package/dist/introspection/sql/foreign_tables.sql.d.ts +2 -6
  17. package/dist/introspection/sql/foreign_tables.sql.js +0 -5
  18. package/dist/introspection/sql/functions.sql.d.ts +2 -5
  19. package/dist/introspection/sql/functions.sql.js +0 -33
  20. package/dist/introspection/sql/helpers.d.ts +1 -1
  21. package/dist/introspection/sql/helpers.js +2 -2
  22. package/dist/introspection/sql/materialized_views.sql.d.ts +2 -4
  23. package/dist/introspection/sql/materialized_views.sql.js +0 -5
  24. package/dist/introspection/sql/pg-format.d.ts +21 -0
  25. package/dist/introspection/sql/pg-format.js +92 -0
  26. package/dist/introspection/sql/primary_keys.sql.d.ts +2 -4
  27. package/dist/introspection/sql/primary_keys.sql.js +0 -4
  28. package/dist/introspection/sql/schemas.sql.d.ts +2 -6
  29. package/dist/introspection/sql/schemas.sql.js +2 -6
  30. package/dist/introspection/sql/table.sql.d.ts +2 -4
  31. package/dist/introspection/sql/table.sql.js +0 -5
  32. package/dist/introspection/sql/table_relationships.sql.d.ts +2 -2
  33. package/dist/introspection/sql/types.sql.d.ts +5 -5
  34. package/dist/introspection/sql/types.sql.js +6 -17
  35. package/dist/introspection/sql/views.sql.d.ts +2 -4
  36. package/dist/introspection/sql/views.sql.js +6 -5
  37. package/dist/introspection/sql/views_key_dependencies.sql.d.ts +2 -2
  38. package/dist/introspection/sql/views_key_dependencies.sql.js +64 -75
  39. package/dist/sort.js +16 -15
  40. package/dist/types.d.ts +4 -0
  41. package/dist/types.js +11 -0
  42. package/package.json +3 -5
  43. package/src/collation.ts +16 -0
  44. package/src/generation/go.ts +31 -2
  45. package/src/generation/index.ts +1 -0
  46. package/src/generation/python.ts +25 -10
  47. package/src/generation/swift.ts +53 -3
  48. package/src/generation/typescript.ts +187 -107
  49. package/src/introspection/index.ts +7 -32
  50. package/src/introspection/relationships.ts +10 -19
  51. package/src/introspection/sql/columns.sql.ts +34 -18
  52. package/src/introspection/sql/common.ts +7 -17
  53. package/src/introspection/sql/foreign_tables.sql.ts +2 -13
  54. package/src/introspection/sql/functions.sql.ts +2 -44
  55. package/src/introspection/sql/helpers.ts +2 -2
  56. package/src/introspection/sql/materialized_views.sql.ts +2 -11
  57. package/src/introspection/sql/pg-format.ts +101 -0
  58. package/src/introspection/sql/primary_keys.sql.ts +2 -10
  59. package/src/introspection/sql/schemas.sql.ts +4 -14
  60. package/src/introspection/sql/table.sql.ts +2 -11
  61. package/src/introspection/sql/table_relationships.sql.ts +2 -4
  62. package/src/introspection/sql/types.sql.ts +6 -26
  63. package/src/introspection/sql/views.sql.ts +8 -11
  64. package/src/introspection/sql/views_key_dependencies.sql.ts +83 -77
  65. package/src/sort.ts +20 -15
  66. package/src/types.ts +11 -0
@@ -7,7 +7,9 @@ import { format as oxfmtFormat } from "oxfmt";
7
7
  import type {
8
8
  GeneratorMetadata,
9
9
  PostgresColumn,
10
+ PostgresForeignTable,
10
11
  PostgresFunction,
12
+ PostgresMaterializedView,
11
13
  PostgresSchema,
12
14
  PostgresTable,
13
15
  PostgresType,
@@ -17,6 +19,7 @@ import {
17
19
  VALID_FUNCTION_ARGS_MODE,
18
20
  VALID_UNNAMED_FUNCTION_ARG_TYPES,
19
21
  } from "./constants.ts";
22
+ import { compareStrings } from "../collation.ts";
20
23
 
21
24
  export interface GenerateTypescriptOptions {
22
25
  /**
@@ -59,6 +62,23 @@ const defaultFormat = async (code: string): Promise<string> => {
59
62
  return formatted;
60
63
  };
61
64
 
65
+ /**
66
+ * Collections `pgTypeToTsType` resolves a relation-typed value against.
67
+ *
68
+ * Foreign tables are generated under `Tables` and materialized views under
69
+ * `Views`, exactly like their plain counterparts, so a value typed as one has
70
+ * a `Row` to point at and belongs here. Both are optional so the exported
71
+ * `pgTypeToTsType` signature stays backwards compatible.
72
+ */
73
+ export type TypeResolutionContext = {
74
+ types: PostgresType[];
75
+ schemas: PostgresSchema[];
76
+ tables: PostgresTable[];
77
+ views: PostgresView[];
78
+ foreignTables?: PostgresForeignTable[];
79
+ materializedViews?: PostgresMaterializedView[];
80
+ };
81
+
62
82
  type TsRelationship = Pick<
63
83
  GeneratorMetadata["relationships"][number],
64
84
  | "foreign_key_name"
@@ -95,6 +115,16 @@ export const generateTypescript = async (
95
115
  defaultSchema = "public",
96
116
  format = defaultFormat,
97
117
  } = opts;
118
+ // Every relation-typed value resolves against the same collections, so build
119
+ // the lookup context once rather than reassembling it at each call site.
120
+ const typeContext: TypeResolutionContext = {
121
+ types,
122
+ schemas,
123
+ tables,
124
+ views,
125
+ foreignTables,
126
+ materializedViews,
127
+ };
98
128
  // Ordering of all collections (incl. relationships, columns, args below) is
99
129
  // provided by `sortGeneratorMetadata`; this generator no longer re-sorts.
100
130
  const introspectionBySchema = Object.fromEntries<{
@@ -232,6 +262,8 @@ export const generateTypescript = async (
232
262
  view: {
233
263
  ...materializedView,
234
264
  is_updatable: false,
265
+ is_insert_enabled: false,
266
+ is_update_enabled: false,
235
267
  },
236
268
  relationships: getRelationships(materializedView),
237
269
  });
@@ -302,19 +334,19 @@ export const generateTypescript = async (
302
334
  // single-collection `sortGeneratorMetadata` pass and stays here.
303
335
  for (const schema in introspectionBySchema) {
304
336
  introspectionBySchema[schema].tables.sort((a, b) =>
305
- a.table.name.localeCompare(b.table.name),
337
+ compareStrings(a.table.name, b.table.name),
306
338
  );
307
339
  introspectionBySchema[schema].views.sort((a, b) =>
308
- a.view.name.localeCompare(b.view.name),
340
+ compareStrings(a.view.name, b.view.name),
309
341
  );
310
342
  introspectionBySchema[schema].functions.sort((a, b) =>
311
- a.fn.name.localeCompare(b.fn.name),
343
+ compareStrings(a.fn.name, b.fn.name),
312
344
  );
313
345
  introspectionBySchema[schema].enums.sort((a, b) =>
314
- a.name.localeCompare(b.name),
346
+ compareStrings(a.name, b.name),
315
347
  );
316
348
  introspectionBySchema[schema].compositeTypes.sort((a, b) =>
317
- a.name.localeCompare(b.name),
349
+ compareStrings(a.name, b.name),
318
350
  );
319
351
  }
320
352
 
@@ -393,12 +425,7 @@ export const generateTypescript = async (
393
425
  const type = typesById.get(type_id);
394
426
  let tsType = "unknown";
395
427
  if (type) {
396
- tsType = pgTypeToTsType(schema, type.name, {
397
- types,
398
- schemas,
399
- tables,
400
- views,
401
- });
428
+ tsType = pgTypeToTsType(schema, type.name, typeContext, type.schema);
402
429
  }
403
430
  return { name, type: tsType };
404
431
  });
@@ -429,12 +456,7 @@ export const generateTypescript = async (
429
456
  is_nullable: column.is_nullable,
430
457
  is_optional: false,
431
458
  },
432
- {
433
- types,
434
- schemas,
435
- tables,
436
- views,
437
- },
459
+ typeContext,
438
460
  ),
439
461
  )
440
462
  .join(",\n")}
@@ -444,12 +466,7 @@ export const generateTypescript = async (
444
466
  // Case 3: returns base/array/composite/enum type.
445
467
  const type = typesById.get(fn.return_type_id);
446
468
  if (type) {
447
- return pgTypeToTsType(schema, type.name, {
448
- types,
449
- schemas,
450
- tables,
451
- views,
452
- });
469
+ return pgTypeToTsType(schema, type.name, typeContext, type.schema);
453
470
  }
454
471
 
455
472
  return "unknown";
@@ -547,7 +564,13 @@ export const generateTypescript = async (
547
564
  ) => {
548
565
  return fns
549
566
  .map(({ fn, inArgs }) => {
550
- let argsType = "never";
567
+ // `never` would be wrong here: postgrest-js treats `Args extends { '': Row }`
568
+ // as a computed field (`never extends T` is always true, so same-named table
569
+ // columns get omitted from select results), and an object type with a required
570
+ // `never` property is uninhabited for any tool doing sound type math on the
571
+ // generated types. `Record<PropertyKey, never>` is the accurate type for
572
+ // "callable with no arguments".
573
+ let argsType = "Record<PropertyKey, never>";
551
574
  let returnType = getFunctionReturnType(schema, fn);
552
575
 
553
576
  // Check for specific error cases
@@ -559,12 +582,12 @@ export const generateTypescript = async (
559
582
  const type = typesById.get(type_id);
560
583
  let tsType = "unknown";
561
584
  if (type) {
562
- tsType = pgTypeToTsType(schema, type.name, {
563
- types,
564
- schemas,
565
- tables,
566
- views,
567
- });
585
+ tsType = pgTypeToTsType(
586
+ schema,
587
+ type.name,
588
+ typeContext,
589
+ type.schema,
590
+ );
568
591
  }
569
592
  return { name, type: tsType, has_default };
570
593
  },
@@ -583,12 +606,12 @@ export const generateTypescript = async (
583
606
  const type = typesById.get(type_id);
584
607
  let tsType = "unknown";
585
608
  if (type) {
586
- tsType = pgTypeToTsType(schema, type.name, {
587
- types,
588
- schemas,
589
- tables,
590
- views,
591
- });
609
+ tsType = pgTypeToTsType(
610
+ schema,
611
+ type.name,
612
+ typeContext,
613
+ type.schema,
614
+ );
592
615
  }
593
616
  return { name, type: tsType, has_default };
594
617
  },
@@ -607,12 +630,12 @@ export const generateTypescript = async (
607
630
  const type = typesById.get(type_id);
608
631
  let tsType = "unknown";
609
632
  if (type) {
610
- tsType = pgTypeToTsType(schema, type.name, {
611
- types,
612
- schemas,
613
- tables,
614
- views,
615
- });
633
+ tsType = pgTypeToTsType(
634
+ schema,
635
+ type.name,
636
+ typeContext,
637
+ type.schema,
638
+ );
616
639
  }
617
640
  return { name, type: tsType, has_default };
618
641
  },
@@ -637,6 +660,11 @@ export const generateTypescript = async (
637
660
  : "";
638
661
 
639
662
  function generateNullableUnionTsType(tsType: string, isNullable: boolean) {
663
+ // The generated Json type includes null, so a NOT NULL json/jsonb column
664
+ // has to be narrowed with NonNullable to reflect the database constraint.
665
+ if (tsType === "Json" && !isNullable) {
666
+ return `NonNullable<${tsType}>`;
667
+ }
640
668
  // Only add the null union if the type is not unknown as unknown already includes null
641
669
  if (tsType === "unknown" || tsType === "any" || !isNullable) {
642
670
  return tsType;
@@ -653,12 +681,7 @@ export const generateTypescript = async (
653
681
  is_nullable: boolean;
654
682
  is_optional: boolean;
655
683
  },
656
- context: {
657
- types: PostgresType[];
658
- schemas: PostgresSchema[];
659
- tables: PostgresTable[];
660
- views: PostgresView[];
661
- },
684
+ context: TypeResolutionContext,
662
685
  ) {
663
686
  return `${JSON.stringify(column.name)}${column.is_optional ? "?" : ""}: ${generateNullableUnionTsType(
664
687
  pgTypeToTsType(schema, column.format, context, column.type_schema),
@@ -666,6 +689,43 @@ export const generateTypescript = async (
666
689
  )}`;
667
690
  }
668
691
 
692
+ /**
693
+ * Is `fn` a PostgREST computed field on `relation` (a table or view)?
694
+ *
695
+ * A computed field is a function taking a single argument of the relation's
696
+ * own composite type, so it is selectable as if it were a column and belongs
697
+ * in `Row`.
698
+ *
699
+ * Matching on the argument's type OID rather than on `argument_types` is what
700
+ * makes named parameters work. `argument_types` is
701
+ * `pg_get_function_arguments()`, which renders the parameter *name* when the
702
+ * parameter has one — `category category` rather than `category` — and
703
+ * schema-qualifies the type when it is not visible in `search_path`. Both
704
+ * forms failed a bare string comparison against the relation name, so
705
+ * `create function name_translated(category category)` silently lost its
706
+ * computed field while the unnamed `name_translated(category)` kept it.
707
+ * See supabase/postgres-meta#1034.
708
+ */
709
+ const isComputedFieldOf = (
710
+ fn: PostgresFunction,
711
+ inArgs: PostgresFunction["args"],
712
+ relation: { id: number; name: string },
713
+ ) => {
714
+ if (inArgs.length === 1) {
715
+ // `relationTypeByIds` is already "types backed by a relation", so a hit
716
+ // identifies the relation regardless of how the argument was spelled.
717
+ // A `variadic` argument is an array type and never appears here.
718
+ const argType = relationTypeByIds.get(inArgs[0]!.type_id);
719
+ if (argType) {
720
+ return argType.type_relation_id === relation.id;
721
+ }
722
+ }
723
+ // Fall back to the historical comparison when the argument's type is not in
724
+ // the metadata (callers may pass a reduced `types` list), so a computed
725
+ // field that resolved before keeps resolving.
726
+ return fn.argument_types === relation.name;
727
+ };
728
+
669
729
  let output = `
670
730
  export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[]
671
731
 
@@ -701,11 +761,13 @@ export type Database = {
701
761
  is_nullable: column.is_nullable,
702
762
  is_optional: false,
703
763
  },
704
- { types, schemas, tables, views },
764
+ typeContext,
705
765
  ),
706
766
  ),
707
767
  ...schemaFunctions
708
- .filter(({ fn }) => fn.argument_types === table.name)
768
+ .filter(({ fn, inArgs }) =>
769
+ isComputedFieldOf(fn, inArgs, table),
770
+ )
709
771
  .map(({ fn }) => {
710
772
  return `${JSON.stringify(fn.name)}: ${generateNullableUnionTsType(
711
773
  getFunctionReturnType(schema, fn),
@@ -716,7 +778,10 @@ export type Database = {
716
778
  }
717
779
  Insert: {
718
780
  ${columnsByTableId[table.id].map((column) => {
719
- if (column.identity_generation === "ALWAYS") {
781
+ if (
782
+ column.identity_generation === "ALWAYS" ||
783
+ column.is_generated
784
+ ) {
720
785
  return `${JSON.stringify(column.name)}?: never`;
721
786
  }
722
787
  return generateColumnTsDefinition(
@@ -731,13 +796,16 @@ export type Database = {
731
796
  column.is_identity ||
732
797
  column.default_value !== null,
733
798
  },
734
- { types, schemas, tables, views },
799
+ typeContext,
735
800
  );
736
801
  })}
737
802
  }
738
803
  Update: {
739
804
  ${columnsByTableId[table.id].map((column) => {
740
- if (column.identity_generation === "ALWAYS") {
805
+ if (
806
+ column.identity_generation === "ALWAYS" ||
807
+ column.is_generated
808
+ ) {
741
809
  return `${JSON.stringify(column.name)}?: never`;
742
810
  }
743
811
 
@@ -750,7 +818,7 @@ export type Database = {
750
818
  is_nullable: column.is_nullable,
751
819
  is_optional: true,
752
820
  },
753
- { types, schemas, tables, views },
821
+ typeContext,
754
822
  );
755
823
  })}
756
824
  }
@@ -782,11 +850,13 @@ export type Database = {
782
850
  is_nullable: column.is_nullable,
783
851
  is_optional: false,
784
852
  },
785
- { types, schemas, tables, views },
853
+ typeContext,
786
854
  ),
787
855
  ),
788
856
  ...schemaFunctions
789
- .filter(({ fn }) => fn.argument_types === view.name)
857
+ .filter(({ fn, inArgs }) =>
858
+ isComputedFieldOf(fn, inArgs, view),
859
+ )
790
860
  .map(
791
861
  ({ fn }) =>
792
862
  `${JSON.stringify(fn.name)}: ${generateNullableUnionTsType(
@@ -797,7 +867,9 @@ export type Database = {
797
867
  ]}
798
868
  }
799
869
  ${
800
- view.is_updatable
870
+ // Metadata predating the trigger-aware flags (still valid
871
+ // version 1 documents) falls back to the old gate.
872
+ (view.is_insert_enabled ?? view.is_updatable)
801
873
  ? `Insert: {
802
874
  ${columnsByTableId[view.id].map((column) => {
803
875
  if (!column.is_updatable) {
@@ -812,11 +884,15 @@ export type Database = {
812
884
  is_nullable: true,
813
885
  is_optional: true,
814
886
  },
815
- { types, schemas, tables, views },
887
+ typeContext,
816
888
  );
817
889
  })}
818
890
  }
819
- Update: {
891
+ `
892
+ : ""
893
+ }${
894
+ (view.is_update_enabled ?? view.is_updatable)
895
+ ? `Update: {
820
896
  ${columnsByTableId[view.id].map((column) => {
821
897
  if (!column.is_updatable) {
822
898
  return `${JSON.stringify(column.name)}?: never`;
@@ -830,7 +906,7 @@ export type Database = {
830
906
  is_nullable: true,
831
907
  is_optional: true,
832
908
  },
833
- { types, schemas, tables, views },
909
+ typeContext,
834
910
  );
835
911
  })}
836
912
  }
@@ -859,8 +935,8 @@ export type Database = {
859
935
  for (const fnName in schemaFunctionsGroupedByName) {
860
936
  schemaFunctionsGroupedByName[fnName].sort(
861
937
  (a, b) =>
862
- a.fn.argument_types.localeCompare(b.fn.argument_types) ||
863
- a.fn.return_type.localeCompare(b.fn.return_type),
938
+ compareStrings(a.fn.argument_types, b.fn.argument_types) ||
939
+ compareStrings(a.fn.return_type, b.fn.return_type),
864
940
  );
865
941
  }
866
942
 
@@ -896,12 +972,12 @@ export type Database = {
896
972
  let tsType = "unknown";
897
973
  if (type) {
898
974
  tsType = `${generateNullableUnionTsType(
899
- pgTypeToTsType(schema, type.name, {
900
- types,
901
- schemas,
902
- tables,
903
- views,
904
- }),
975
+ pgTypeToTsType(
976
+ schema,
977
+ type.name,
978
+ typeContext,
979
+ type.schema,
980
+ ),
905
981
  true,
906
982
  )}`;
907
983
  }
@@ -1047,23 +1123,22 @@ export const Constants = {
1047
1123
  export const pgTypeToTsType = (
1048
1124
  schema: PostgresSchema,
1049
1125
  pgType: string,
1050
- {
1126
+ context: TypeResolutionContext,
1127
+ // The schema that actually owns `pgType`, from the column's `type_schema` or
1128
+ // the resolved `PostgresType.schema`. Disambiguates same-named enums,
1129
+ // composites and relations across schemas. Falls back to `schema.name` (the
1130
+ // schema being generated) when not provided, which is only a guess, so pass
1131
+ // it whenever the owning schema is known.
1132
+ typeSchema?: string,
1133
+ ): string => {
1134
+ const {
1051
1135
  types,
1052
1136
  schemas,
1053
1137
  tables,
1054
1138
  views,
1055
- }: {
1056
- types: PostgresType[];
1057
- schemas: PostgresSchema[];
1058
- tables: PostgresTable[];
1059
- views: PostgresView[];
1060
- },
1061
- // The schema that actually owns `pgType` (a column's `type_schema`), when
1062
- // known. Disambiguates same-named enums/composites across schemas; falls
1063
- // back to `schema.name` (the table's own schema) when not provided, e.g.
1064
- // for function args/return types where no equivalent field exists yet.
1065
- typeSchema?: string,
1066
- ): string => {
1139
+ foreignTables = [],
1140
+ materializedViews = [],
1141
+ } = context;
1067
1142
  if (pgType === "bool") {
1068
1143
  return "boolean";
1069
1144
  } else if (
@@ -1095,12 +1170,7 @@ export const pgTypeToTsType = (
1095
1170
  } else if (pgType === "record") {
1096
1171
  return "Record<string, unknown>";
1097
1172
  } else if (pgType.startsWith("_")) {
1098
- return `(${pgTypeToTsType(
1099
- schema,
1100
- pgType.substring(1),
1101
- { types, schemas, tables, views },
1102
- typeSchema,
1103
- )})[]`;
1173
+ return `(${pgTypeToTsType(schema, pgType.substring(1), context, typeSchema)})[]`;
1104
1174
  } else {
1105
1175
  const preferredSchema = typeSchema ?? schema.name;
1106
1176
  const enumTypes = types.filter(
@@ -1133,27 +1203,37 @@ export const pgTypeToTsType = (
1133
1203
  return "unknown";
1134
1204
  }
1135
1205
 
1136
- const tableRowTypes = tables.filter((table) => table.name === pgType);
1137
- if (tableRowTypes.length > 0) {
1138
- const tableRowType =
1139
- tableRowTypes.find((type) => type.schema === preferredSchema) ||
1140
- tableRowTypes[0];
1141
- if (schemas.some(({ name }) => name === tableRowType.schema)) {
1142
- return `Database[${JSON.stringify(tableRowType.schema)}]['Tables'][${JSON.stringify(
1143
- tableRowType.name,
1144
- )}]['Row']`;
1206
+ // A relation-typed value carries only a bare type name, so several relations
1207
+ // can share it and `preferredSchema` is what disambiguates them. Match on
1208
+ // the schema across every relation kind before falling back to kind order,
1209
+ // otherwise a relation in an unrelated schema outranks the one the caller
1210
+ // meant. Foreign tables are generated under `Tables` and materialized views
1211
+ // under `Views`, alongside their plain counterparts.
1212
+ const relationKinds = [
1213
+ ["Tables", tables],
1214
+ ["Tables", foreignTables],
1215
+ ["Views", views],
1216
+ ["Views", materializedViews],
1217
+ ] as const;
1218
+ const findRelation = (
1219
+ matches: (relation: { name: string; schema: string }) => boolean,
1220
+ ) => {
1221
+ for (const [kind, relations] of relationKinds) {
1222
+ const relation = relations.find(matches);
1223
+ if (relation) return { kind, relation };
1145
1224
  }
1146
- return "unknown";
1147
- }
1148
-
1149
- const viewRowTypes = views.filter((view) => view.name === pgType);
1150
- if (viewRowTypes.length > 0) {
1151
- const viewRowType =
1152
- viewRowTypes.find((type) => type.schema === preferredSchema) ||
1153
- viewRowTypes[0];
1154
- if (schemas.some(({ name }) => name === viewRowType.schema)) {
1155
- return `Database[${JSON.stringify(viewRowType.schema)}]['Views'][${JSON.stringify(
1156
- viewRowType.name,
1225
+ return undefined;
1226
+ };
1227
+ const match =
1228
+ findRelation(
1229
+ ({ name, schema: relationSchema }) =>
1230
+ name === pgType && relationSchema === preferredSchema,
1231
+ ) ?? findRelation(({ name }) => name === pgType);
1232
+ if (match) {
1233
+ const { kind, relation } = match;
1234
+ if (schemas.some(({ name }) => name === relation.schema)) {
1235
+ return `Database[${JSON.stringify(relation.schema)}]['${kind}'][${JSON.stringify(
1236
+ relation.name,
1157
1237
  )}]['Row']`;
1158
1238
  }
1159
1239
  return "unknown";
@@ -44,20 +44,14 @@ export async function introspect(
44
44
  db: Queryable,
45
45
  opts: IntrospectOptions = {},
46
46
  ): Promise<GeneratorMetadata> {
47
- const includedSchemas = opts.includedSchemas ?? [];
48
- const excludedSchemas = opts.excludedSchemas ?? [];
49
- // Managers receive `undefined` (not an empty array) when no filter is set.
50
- const included = includedSchemas.length > 0 ? includedSchemas : undefined;
51
- const excluded = excludedSchemas.length > 0 ? excludedSchemas : undefined;
52
-
53
47
  // Most queries exclude the system schemas by default; foreign tables and
54
48
  // materialized views do not (matching the respective manager `list()` calls).
55
49
  const systemExcludingFilter = filterByList(
56
- included,
57
- excluded,
50
+ opts.includedSchemas,
51
+ opts.excludedSchemas,
58
52
  DEFAULT_SYSTEM_SCHEMAS,
59
53
  );
60
- const plainFilter = filterByList(included, excluded);
54
+ const plainFilter = filterByList(opts.includedSchemas, opts.excludedSchemas);
61
55
 
62
56
  const queryRows = async (sql: string) =>
63
57
  normalizeRows((await db.query(sql)).rows);
@@ -78,40 +72,21 @@ export async function introspect(
78
72
  functions,
79
73
  types,
80
74
  ] = await Promise.all([
81
- queryRows(
82
- SCHEMAS_SQL({
83
- includeSystemSchemas: false,
84
- nameFilter: systemExcludingFilter,
85
- }),
86
- ),
75
+ queryRows(SCHEMAS_SQL({ schemaFilter: systemExcludingFilter })),
87
76
  queryRows(TABLES_SQL({ schemaFilter: systemExcludingFilter })),
88
77
  queryRows(FOREIGN_TABLES_SQL({ schemaFilter: plainFilter })),
89
78
  queryRows(VIEWS_SQL({ schemaFilter: systemExcludingFilter })),
90
79
  queryRows(MATERIALIZED_VIEWS_SQL({ schemaFilter: plainFilter })),
91
80
  queryRows(COLUMNS_SQL({ schemaFilter: systemExcludingFilter })),
92
81
  queryRows(PRIMARY_KEYS_SQL({ schemaFilter: systemExcludingFilter })),
93
- listRelationships(db, {
94
- includedSchemas: included,
95
- excludedSchemas: excluded,
96
- }),
82
+ listRelationships(db, systemExcludingFilter),
97
83
  queryRows(FUNCTIONS_SQL({ schemaFilter: systemExcludingFilter })),
98
- // types: includeSystemSchemas true (no schema filter), table + array types included.
99
- queryRows(
100
- TYPES_SQL({
101
- schemaFilter: "",
102
- includeTableTypes: true,
103
- includeArrayTypes: true,
104
- }),
105
- ),
84
+ queryRows(TYPES_SQL),
106
85
  ]);
107
86
 
108
87
  return {
109
88
  version: GENERATOR_METADATA_VERSION,
110
- schemas: schemas.filter(
111
- ({ name }) =>
112
- !excludedSchemas.includes(name) &&
113
- (includedSchemas.length === 0 || includedSchemas.includes(name)),
114
- ),
89
+ schemas,
115
90
  tables,
116
91
  foreignTables,
117
92
  views,
@@ -1,6 +1,5 @@
1
1
  import type { PostgresRelationship } from "../types.ts";
2
- import type { IntrospectOptions, Queryable } from "./index.ts";
3
- import { DEFAULT_SYSTEM_SCHEMAS, filterByList } from "./sql/helpers.ts";
2
+ import type { Queryable } from "./index.ts";
4
3
  import { TABLE_RELATIONSHIPS_SQL } from "./sql/table_relationships.sql.ts";
5
4
  import { VIEWS_KEY_DEPENDENCIES_SQL } from "./sql/views_key_dependencies.sql.ts";
6
5
 
@@ -52,16 +51,14 @@ export function expandViewRelationships(
52
51
  colDeps: ColDep[],
53
52
  ): { tableColumns: string[]; viewColumns: string[] }[] => {
54
53
  const tableColumns = colDeps.map(({ table_column }) => table_column);
55
- // https://gist.github.com/ssippe/1f92625532eef28be6974f898efb23ef?permalink_comment_id=3474581#gistcomment-3474581
56
- const cartesianProduct = <T>(allEntries: T[][]): T[][] => {
57
- return allEntries.reduce<T[][]>(
58
- (results, entries) =>
59
- results
60
- .map((result) => entries.map((entry) => result.concat(entry)))
61
- .reduce((subResults, result) => subResults.concat(result), []),
54
+ const cartesianProduct = <T>(lists: T[][]): T[][] =>
55
+ lists.reduce<T[][]>(
56
+ (combinations, entries) =>
57
+ combinations.flatMap((combination) =>
58
+ entries.map((entry) => [...combination, entry]),
59
+ ),
62
60
  [[]],
63
61
  );
64
- };
65
62
  const viewColumnsPermutations = cartesianProduct(
66
63
  colDeps.map((cd) => cd.view_columns),
67
64
  );
@@ -141,20 +138,14 @@ export function expandViewRelationships(
141
138
 
142
139
  /**
143
140
  * List all relationships (table↔table plus the view-derived ones) for the
144
- * given schema filter. Mirrors `PostgresMetaRelationships.list()` with the
145
- * generator path's defaults (`includeSystemSchemas: false`). Errors from the
141
+ * schemas selected by `schemaFilter`, the `IN (…)`/`NOT IN (…)` fragment
142
+ * `introspect()` builds with the system schemas excluded. Errors from the
146
143
  * injected `Queryable` propagate by throwing — callers adapt.
147
144
  */
148
145
  export async function listRelationships(
149
146
  db: Queryable,
150
- { includedSchemas, excludedSchemas }: IntrospectOptions = {},
147
+ schemaFilter: string,
151
148
  ): Promise<PostgresRelationship[]> {
152
- const schemaFilter = filterByList(
153
- includedSchemas,
154
- excludedSchemas,
155
- DEFAULT_SYSTEM_SCHEMAS,
156
- );
157
-
158
149
  const { rows: allTableM2oAndO2oRelationships } = await db.query(
159
150
  TABLE_RELATIONSHIPS_SQL({ schemaFilter }),
160
151
  );