@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.
- package/README.md +12 -0
- package/dist/collation.d.ts +2 -0
- package/dist/collation.js +15 -0
- package/dist/generation/go.js +30 -2
- package/dist/generation/index.d.ts +1 -1
- package/dist/generation/python.js +24 -10
- package/dist/generation/swift.js +52 -3
- package/dist/generation/typescript.d.ts +18 -7
- package/dist/generation/typescript.js +122 -82
- 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 +32 -9
- 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/helpers.d.ts +1 -1
- package/dist/introspection/sql/helpers.js +2 -2
- 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/pg-format.d.ts +21 -0
- package/dist/introspection/sql/pg-format.js +92 -0
- 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 +6 -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 -0
- package/dist/types.js +11 -0
- package/package.json +3 -5
- package/src/collation.ts +16 -0
- package/src/generation/go.ts +31 -2
- package/src/generation/index.ts +1 -0
- package/src/generation/python.ts +25 -10
- package/src/generation/swift.ts +53 -3
- package/src/generation/typescript.ts +187 -107
- package/src/introspection/index.ts +7 -32
- package/src/introspection/relationships.ts +10 -19
- package/src/introspection/sql/columns.sql.ts +34 -18
- 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/helpers.ts +2 -2
- package/src/introspection/sql/materialized_views.sql.ts +2 -11
- package/src/introspection/sql/pg-format.ts +101 -0
- 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 +8 -11
- package/src/introspection/sql/views_key_dependencies.sql.ts +83 -77
- package/src/sort.ts +20 -15
- 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
|
|
337
|
+
compareStrings(a.table.name, b.table.name),
|
|
306
338
|
);
|
|
307
339
|
introspectionBySchema[schema].views.sort((a, b) =>
|
|
308
|
-
a.view.name
|
|
340
|
+
compareStrings(a.view.name, b.view.name),
|
|
309
341
|
);
|
|
310
342
|
introspectionBySchema[schema].functions.sort((a, b) =>
|
|
311
|
-
a.fn.name
|
|
343
|
+
compareStrings(a.fn.name, b.fn.name),
|
|
312
344
|
);
|
|
313
345
|
introspectionBySchema[schema].enums.sort((a, b) =>
|
|
314
|
-
a.name
|
|
346
|
+
compareStrings(a.name, b.name),
|
|
315
347
|
);
|
|
316
348
|
introspectionBySchema[schema].compositeTypes.sort((a, b) =>
|
|
317
|
-
a.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
|
-
|
|
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(
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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(
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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(
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
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
|
-
|
|
764
|
+
typeContext,
|
|
705
765
|
),
|
|
706
766
|
),
|
|
707
767
|
...schemaFunctions
|
|
708
|
-
.filter(({ fn }) =>
|
|
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 (
|
|
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
|
-
|
|
799
|
+
typeContext,
|
|
735
800
|
);
|
|
736
801
|
})}
|
|
737
802
|
}
|
|
738
803
|
Update: {
|
|
739
804
|
${columnsByTableId[table.id].map((column) => {
|
|
740
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
853
|
+
typeContext,
|
|
786
854
|
),
|
|
787
855
|
),
|
|
788
856
|
...schemaFunctions
|
|
789
|
-
.filter(({ fn }) =>
|
|
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
|
-
|
|
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
|
-
|
|
887
|
+
typeContext,
|
|
816
888
|
);
|
|
817
889
|
})}
|
|
818
890
|
}
|
|
819
|
-
|
|
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
|
-
|
|
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
|
|
863
|
-
a.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(
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
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
|
-
|
|
1057
|
-
|
|
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
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
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
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
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
|
-
|
|
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
|
);
|