@supabase/postgrest-typegen 0.2.1 → 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.
- package/README.md +8 -0
- package/dist/collation.d.ts +2 -0
- package/dist/collation.js +15 -0
- package/dist/generation/index.d.ts +1 -1
- package/dist/generation/swift.js +9 -2
- package/dist/generation/typescript.d.ts +18 -7
- package/dist/generation/typescript.js +102 -79
- package/dist/introspection/index.js +6 -23
- package/dist/introspection/relationships.d.ts +4 -4
- package/dist/introspection/relationships.js +4 -11
- package/dist/introspection/sql/columns.sql.d.ts +2 -7
- package/dist/introspection/sql/columns.sql.js +23 -17
- package/dist/introspection/sql/common.d.ts +7 -15
- package/dist/introspection/sql/foreign_tables.sql.d.ts +2 -6
- package/dist/introspection/sql/foreign_tables.sql.js +0 -5
- package/dist/introspection/sql/functions.sql.d.ts +2 -5
- package/dist/introspection/sql/functions.sql.js +0 -33
- package/dist/introspection/sql/materialized_views.sql.d.ts +2 -4
- package/dist/introspection/sql/materialized_views.sql.js +0 -5
- package/dist/introspection/sql/primary_keys.sql.d.ts +2 -4
- package/dist/introspection/sql/primary_keys.sql.js +0 -4
- package/dist/introspection/sql/schemas.sql.d.ts +2 -6
- package/dist/introspection/sql/schemas.sql.js +2 -6
- package/dist/introspection/sql/table.sql.d.ts +2 -4
- package/dist/introspection/sql/table.sql.js +0 -5
- package/dist/introspection/sql/table_relationships.sql.d.ts +2 -2
- package/dist/introspection/sql/types.sql.d.ts +5 -5
- package/dist/introspection/sql/types.sql.js +6 -17
- package/dist/introspection/sql/views.sql.d.ts +2 -4
- package/dist/introspection/sql/views.sql.js +0 -5
- package/dist/introspection/sql/views_key_dependencies.sql.d.ts +2 -2
- package/dist/introspection/sql/views_key_dependencies.sql.js +64 -75
- package/dist/sort.js +16 -15
- package/dist/types.d.ts +4 -4
- package/dist/types.js +6 -4
- package/package.json +3 -3
- package/src/collation.ts +16 -0
- package/src/generation/index.ts +1 -0
- package/src/generation/swift.ts +9 -2
- package/src/generation/typescript.ts +161 -104
- package/src/introspection/index.ts +7 -32
- package/src/introspection/relationships.ts +10 -19
- package/src/introspection/sql/columns.sql.ts +25 -26
- package/src/introspection/sql/common.ts +7 -17
- package/src/introspection/sql/foreign_tables.sql.ts +2 -13
- package/src/introspection/sql/functions.sql.ts +2 -44
- package/src/introspection/sql/materialized_views.sql.ts +2 -11
- package/src/introspection/sql/primary_keys.sql.ts +2 -10
- package/src/introspection/sql/schemas.sql.ts +4 -14
- package/src/introspection/sql/table.sql.ts +2 -11
- package/src/introspection/sql/table_relationships.sql.ts +2 -4
- package/src/introspection/sql/types.sql.ts +6 -26
- package/src/introspection/sql/views.sql.ts +2 -11
- package/src/introspection/sql/views_key_dependencies.sql.ts +83 -77
- package/src/sort.ts +20 -15
- package/src/types.ts +6 -4
package/src/generation/swift.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
PostgresType,
|
|
8
8
|
PostgresView,
|
|
9
9
|
} from "../types.ts";
|
|
10
|
+
import { compareStrings } from "../collation.ts";
|
|
10
11
|
|
|
11
12
|
type Operation = "Select" | "Insert" | "Update";
|
|
12
13
|
export type AccessControl = "internal" | "public" | "private" | "package";
|
|
@@ -313,11 +314,11 @@ export const generateSwift = (
|
|
|
313
314
|
...schemas.flatMap((schema) => {
|
|
314
315
|
const schemaTables = [...tables, ...foreignTables]
|
|
315
316
|
.filter((table) => table.schema === schema.name)
|
|
316
|
-
.sort(({ name: a }, { name: b }) => a
|
|
317
|
+
.sort(({ name: a }, { name: b }) => compareStrings(a, b));
|
|
317
318
|
|
|
318
319
|
const schemaViews = [...views, ...materializedViews]
|
|
319
320
|
.filter((table) => table.schema === schema.name)
|
|
320
|
-
.sort(({ name: a }, { name: b }) => a
|
|
321
|
+
.sort(({ name: a }, { name: b }) => compareStrings(a, b));
|
|
321
322
|
|
|
322
323
|
const schemaEnums = types.filter(
|
|
323
324
|
(type) => type.schema === schema.name && type.enums.length > 0,
|
|
@@ -424,6 +425,12 @@ const pgTypeToSwiftType = (
|
|
|
424
425
|
swiftType = "String";
|
|
425
426
|
} else if (["json", "jsonb"].includes(pgType)) {
|
|
426
427
|
swiftType = "AnyJSON";
|
|
428
|
+
// `void` and `record` are pseudo-types: Postgres rejects them as a column or
|
|
429
|
+
// composite attribute type ("column has pseudo-type void"), and this
|
|
430
|
+
// generator only renders columns and attributes, never functions. So these
|
|
431
|
+
// two arms are unreachable today and deliberately untested. They are kept
|
|
432
|
+
// because they are the mappings to use if Swift ever emits functions, which
|
|
433
|
+
// is where these types do appear.
|
|
427
434
|
} else if (pgType === "void") {
|
|
428
435
|
swiftType = "Void";
|
|
429
436
|
} else if (pgType === "record") {
|
|
@@ -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<{
|
|
@@ -304,19 +334,19 @@ export const generateTypescript = async (
|
|
|
304
334
|
// single-collection `sortGeneratorMetadata` pass and stays here.
|
|
305
335
|
for (const schema in introspectionBySchema) {
|
|
306
336
|
introspectionBySchema[schema].tables.sort((a, b) =>
|
|
307
|
-
a.table.name
|
|
337
|
+
compareStrings(a.table.name, b.table.name),
|
|
308
338
|
);
|
|
309
339
|
introspectionBySchema[schema].views.sort((a, b) =>
|
|
310
|
-
a.view.name
|
|
340
|
+
compareStrings(a.view.name, b.view.name),
|
|
311
341
|
);
|
|
312
342
|
introspectionBySchema[schema].functions.sort((a, b) =>
|
|
313
|
-
a.fn.name
|
|
343
|
+
compareStrings(a.fn.name, b.fn.name),
|
|
314
344
|
);
|
|
315
345
|
introspectionBySchema[schema].enums.sort((a, b) =>
|
|
316
|
-
a.name
|
|
346
|
+
compareStrings(a.name, b.name),
|
|
317
347
|
);
|
|
318
348
|
introspectionBySchema[schema].compositeTypes.sort((a, b) =>
|
|
319
|
-
a.name
|
|
349
|
+
compareStrings(a.name, b.name),
|
|
320
350
|
);
|
|
321
351
|
}
|
|
322
352
|
|
|
@@ -395,12 +425,7 @@ export const generateTypescript = async (
|
|
|
395
425
|
const type = typesById.get(type_id);
|
|
396
426
|
let tsType = "unknown";
|
|
397
427
|
if (type) {
|
|
398
|
-
tsType = pgTypeToTsType(schema, type.name,
|
|
399
|
-
types,
|
|
400
|
-
schemas,
|
|
401
|
-
tables,
|
|
402
|
-
views,
|
|
403
|
-
});
|
|
428
|
+
tsType = pgTypeToTsType(schema, type.name, typeContext, type.schema);
|
|
404
429
|
}
|
|
405
430
|
return { name, type: tsType };
|
|
406
431
|
});
|
|
@@ -431,12 +456,7 @@ export const generateTypescript = async (
|
|
|
431
456
|
is_nullable: column.is_nullable,
|
|
432
457
|
is_optional: false,
|
|
433
458
|
},
|
|
434
|
-
|
|
435
|
-
types,
|
|
436
|
-
schemas,
|
|
437
|
-
tables,
|
|
438
|
-
views,
|
|
439
|
-
},
|
|
459
|
+
typeContext,
|
|
440
460
|
),
|
|
441
461
|
)
|
|
442
462
|
.join(",\n")}
|
|
@@ -446,12 +466,7 @@ export const generateTypescript = async (
|
|
|
446
466
|
// Case 3: returns base/array/composite/enum type.
|
|
447
467
|
const type = typesById.get(fn.return_type_id);
|
|
448
468
|
if (type) {
|
|
449
|
-
return pgTypeToTsType(schema, type.name,
|
|
450
|
-
types,
|
|
451
|
-
schemas,
|
|
452
|
-
tables,
|
|
453
|
-
views,
|
|
454
|
-
});
|
|
469
|
+
return pgTypeToTsType(schema, type.name, typeContext, type.schema);
|
|
455
470
|
}
|
|
456
471
|
|
|
457
472
|
return "unknown";
|
|
@@ -567,12 +582,12 @@ export const generateTypescript = async (
|
|
|
567
582
|
const type = typesById.get(type_id);
|
|
568
583
|
let tsType = "unknown";
|
|
569
584
|
if (type) {
|
|
570
|
-
tsType = pgTypeToTsType(
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
585
|
+
tsType = pgTypeToTsType(
|
|
586
|
+
schema,
|
|
587
|
+
type.name,
|
|
588
|
+
typeContext,
|
|
589
|
+
type.schema,
|
|
590
|
+
);
|
|
576
591
|
}
|
|
577
592
|
return { name, type: tsType, has_default };
|
|
578
593
|
},
|
|
@@ -591,12 +606,12 @@ export const generateTypescript = async (
|
|
|
591
606
|
const type = typesById.get(type_id);
|
|
592
607
|
let tsType = "unknown";
|
|
593
608
|
if (type) {
|
|
594
|
-
tsType = pgTypeToTsType(
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
609
|
+
tsType = pgTypeToTsType(
|
|
610
|
+
schema,
|
|
611
|
+
type.name,
|
|
612
|
+
typeContext,
|
|
613
|
+
type.schema,
|
|
614
|
+
);
|
|
600
615
|
}
|
|
601
616
|
return { name, type: tsType, has_default };
|
|
602
617
|
},
|
|
@@ -615,12 +630,12 @@ export const generateTypescript = async (
|
|
|
615
630
|
const type = typesById.get(type_id);
|
|
616
631
|
let tsType = "unknown";
|
|
617
632
|
if (type) {
|
|
618
|
-
tsType = pgTypeToTsType(
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
633
|
+
tsType = pgTypeToTsType(
|
|
634
|
+
schema,
|
|
635
|
+
type.name,
|
|
636
|
+
typeContext,
|
|
637
|
+
type.schema,
|
|
638
|
+
);
|
|
624
639
|
}
|
|
625
640
|
return { name, type: tsType, has_default };
|
|
626
641
|
},
|
|
@@ -666,12 +681,7 @@ export const generateTypescript = async (
|
|
|
666
681
|
is_nullable: boolean;
|
|
667
682
|
is_optional: boolean;
|
|
668
683
|
},
|
|
669
|
-
context:
|
|
670
|
-
types: PostgresType[];
|
|
671
|
-
schemas: PostgresSchema[];
|
|
672
|
-
tables: PostgresTable[];
|
|
673
|
-
views: PostgresView[];
|
|
674
|
-
},
|
|
684
|
+
context: TypeResolutionContext,
|
|
675
685
|
) {
|
|
676
686
|
return `${JSON.stringify(column.name)}${column.is_optional ? "?" : ""}: ${generateNullableUnionTsType(
|
|
677
687
|
pgTypeToTsType(schema, column.format, context, column.type_schema),
|
|
@@ -679,6 +689,43 @@ export const generateTypescript = async (
|
|
|
679
689
|
)}`;
|
|
680
690
|
}
|
|
681
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
|
+
|
|
682
729
|
let output = `
|
|
683
730
|
export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[]
|
|
684
731
|
|
|
@@ -714,11 +761,13 @@ export type Database = {
|
|
|
714
761
|
is_nullable: column.is_nullable,
|
|
715
762
|
is_optional: false,
|
|
716
763
|
},
|
|
717
|
-
|
|
764
|
+
typeContext,
|
|
718
765
|
),
|
|
719
766
|
),
|
|
720
767
|
...schemaFunctions
|
|
721
|
-
.filter(({ fn }) =>
|
|
768
|
+
.filter(({ fn, inArgs }) =>
|
|
769
|
+
isComputedFieldOf(fn, inArgs, table),
|
|
770
|
+
)
|
|
722
771
|
.map(({ fn }) => {
|
|
723
772
|
return `${JSON.stringify(fn.name)}: ${generateNullableUnionTsType(
|
|
724
773
|
getFunctionReturnType(schema, fn),
|
|
@@ -747,7 +796,7 @@ export type Database = {
|
|
|
747
796
|
column.is_identity ||
|
|
748
797
|
column.default_value !== null,
|
|
749
798
|
},
|
|
750
|
-
|
|
799
|
+
typeContext,
|
|
751
800
|
);
|
|
752
801
|
})}
|
|
753
802
|
}
|
|
@@ -769,7 +818,7 @@ export type Database = {
|
|
|
769
818
|
is_nullable: column.is_nullable,
|
|
770
819
|
is_optional: true,
|
|
771
820
|
},
|
|
772
|
-
|
|
821
|
+
typeContext,
|
|
773
822
|
);
|
|
774
823
|
})}
|
|
775
824
|
}
|
|
@@ -801,11 +850,13 @@ export type Database = {
|
|
|
801
850
|
is_nullable: column.is_nullable,
|
|
802
851
|
is_optional: false,
|
|
803
852
|
},
|
|
804
|
-
|
|
853
|
+
typeContext,
|
|
805
854
|
),
|
|
806
855
|
),
|
|
807
856
|
...schemaFunctions
|
|
808
|
-
.filter(({ fn }) =>
|
|
857
|
+
.filter(({ fn, inArgs }) =>
|
|
858
|
+
isComputedFieldOf(fn, inArgs, view),
|
|
859
|
+
)
|
|
809
860
|
.map(
|
|
810
861
|
({ fn }) =>
|
|
811
862
|
`${JSON.stringify(fn.name)}: ${generateNullableUnionTsType(
|
|
@@ -816,7 +867,9 @@ export type Database = {
|
|
|
816
867
|
]}
|
|
817
868
|
}
|
|
818
869
|
${
|
|
819
|
-
|
|
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)
|
|
820
873
|
? `Insert: {
|
|
821
874
|
${columnsByTableId[view.id].map((column) => {
|
|
822
875
|
if (!column.is_updatable) {
|
|
@@ -831,14 +884,14 @@ export type Database = {
|
|
|
831
884
|
is_nullable: true,
|
|
832
885
|
is_optional: true,
|
|
833
886
|
},
|
|
834
|
-
|
|
887
|
+
typeContext,
|
|
835
888
|
);
|
|
836
889
|
})}
|
|
837
890
|
}
|
|
838
891
|
`
|
|
839
892
|
: ""
|
|
840
893
|
}${
|
|
841
|
-
view.is_update_enabled
|
|
894
|
+
(view.is_update_enabled ?? view.is_updatable)
|
|
842
895
|
? `Update: {
|
|
843
896
|
${columnsByTableId[view.id].map((column) => {
|
|
844
897
|
if (!column.is_updatable) {
|
|
@@ -853,7 +906,7 @@ export type Database = {
|
|
|
853
906
|
is_nullable: true,
|
|
854
907
|
is_optional: true,
|
|
855
908
|
},
|
|
856
|
-
|
|
909
|
+
typeContext,
|
|
857
910
|
);
|
|
858
911
|
})}
|
|
859
912
|
}
|
|
@@ -882,8 +935,8 @@ export type Database = {
|
|
|
882
935
|
for (const fnName in schemaFunctionsGroupedByName) {
|
|
883
936
|
schemaFunctionsGroupedByName[fnName].sort(
|
|
884
937
|
(a, b) =>
|
|
885
|
-
a.fn.argument_types
|
|
886
|
-
a.fn.return_type
|
|
938
|
+
compareStrings(a.fn.argument_types, b.fn.argument_types) ||
|
|
939
|
+
compareStrings(a.fn.return_type, b.fn.return_type),
|
|
887
940
|
);
|
|
888
941
|
}
|
|
889
942
|
|
|
@@ -919,12 +972,12 @@ export type Database = {
|
|
|
919
972
|
let tsType = "unknown";
|
|
920
973
|
if (type) {
|
|
921
974
|
tsType = `${generateNullableUnionTsType(
|
|
922
|
-
pgTypeToTsType(
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
975
|
+
pgTypeToTsType(
|
|
976
|
+
schema,
|
|
977
|
+
type.name,
|
|
978
|
+
typeContext,
|
|
979
|
+
type.schema,
|
|
980
|
+
),
|
|
928
981
|
true,
|
|
929
982
|
)}`;
|
|
930
983
|
}
|
|
@@ -1070,23 +1123,22 @@ export const Constants = {
|
|
|
1070
1123
|
export const pgTypeToTsType = (
|
|
1071
1124
|
schema: PostgresSchema,
|
|
1072
1125
|
pgType: string,
|
|
1073
|
-
|
|
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 {
|
|
1074
1135
|
types,
|
|
1075
1136
|
schemas,
|
|
1076
1137
|
tables,
|
|
1077
1138
|
views,
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
tables: PostgresTable[];
|
|
1082
|
-
views: PostgresView[];
|
|
1083
|
-
},
|
|
1084
|
-
// The schema that actually owns `pgType` (a column's `type_schema`), when
|
|
1085
|
-
// known. Disambiguates same-named enums/composites across schemas; falls
|
|
1086
|
-
// back to `schema.name` (the table's own schema) when not provided, e.g.
|
|
1087
|
-
// for function args/return types where no equivalent field exists yet.
|
|
1088
|
-
typeSchema?: string,
|
|
1089
|
-
): string => {
|
|
1139
|
+
foreignTables = [],
|
|
1140
|
+
materializedViews = [],
|
|
1141
|
+
} = context;
|
|
1090
1142
|
if (pgType === "bool") {
|
|
1091
1143
|
return "boolean";
|
|
1092
1144
|
} else if (
|
|
@@ -1118,12 +1170,7 @@ export const pgTypeToTsType = (
|
|
|
1118
1170
|
} else if (pgType === "record") {
|
|
1119
1171
|
return "Record<string, unknown>";
|
|
1120
1172
|
} else if (pgType.startsWith("_")) {
|
|
1121
|
-
return `(${pgTypeToTsType(
|
|
1122
|
-
schema,
|
|
1123
|
-
pgType.substring(1),
|
|
1124
|
-
{ types, schemas, tables, views },
|
|
1125
|
-
typeSchema,
|
|
1126
|
-
)})[]`;
|
|
1173
|
+
return `(${pgTypeToTsType(schema, pgType.substring(1), context, typeSchema)})[]`;
|
|
1127
1174
|
} else {
|
|
1128
1175
|
const preferredSchema = typeSchema ?? schema.name;
|
|
1129
1176
|
const enumTypes = types.filter(
|
|
@@ -1156,27 +1203,37 @@ export const pgTypeToTsType = (
|
|
|
1156
1203
|
return "unknown";
|
|
1157
1204
|
}
|
|
1158
1205
|
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
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 };
|
|
1168
1224
|
}
|
|
1169
|
-
return
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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,
|
|
1180
1237
|
)}]['Row']`;
|
|
1181
1238
|
}
|
|
1182
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
|
-
|
|
57
|
-
|
|
50
|
+
opts.includedSchemas,
|
|
51
|
+
opts.excludedSchemas,
|
|
58
52
|
DEFAULT_SYSTEM_SCHEMAS,
|
|
59
53
|
);
|
|
60
|
-
const plainFilter = filterByList(
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
*
|
|
145
|
-
*
|
|
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
|
-
|
|
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
|
);
|