Skip to content

Commit 1a52837

Browse files
committed
feat: generate types for foreign tables
1 parent dd47c6d commit 1a52837

File tree

7 files changed

+141
-70
lines changed

7 files changed

+141
-70
lines changed

src/lib/PostgresMetaForeignTables.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { literal } from 'pg-format'
2-
import { coalesceRowsToArray } from './helpers.js'
2+
import { coalesceRowsToArray, filterByList } from './helpers.js'
33
import { columnsSql, foreignTablesSql } from './sql/index.js'
44
import { PostgresMetaResult, PostgresForeignTable } from './types.js'
55

@@ -11,25 +11,37 @@ export default class PostgresMetaForeignTables {
1111
}
1212

1313
async list(options: {
14+
includedSchemas?: string[]
15+
excludedSchemas?: string[]
1416
limit?: number
1517
offset?: number
1618
includeColumns: false
1719
}): Promise<PostgresMetaResult<(PostgresForeignTable & { columns: never })[]>>
1820
async list(options?: {
21+
includedSchemas?: string[]
22+
excludedSchemas?: string[]
1923
limit?: number
2024
offset?: number
2125
includeColumns?: boolean
2226
}): Promise<PostgresMetaResult<(PostgresForeignTable & { columns: unknown[] })[]>>
2327
async list({
28+
includedSchemas,
29+
excludedSchemas,
2430
limit,
2531
offset,
2632
includeColumns = true,
2733
}: {
34+
includedSchemas?: string[]
35+
excludedSchemas?: string[]
2836
limit?: number
2937
offset?: number
3038
includeColumns?: boolean
3139
} = {}): Promise<PostgresMetaResult<PostgresForeignTable[]>> {
3240
let sql = generateEnrichedForeignTablesSql({ includeColumns })
41+
const filter = filterByList(includedSchemas, excludedSchemas)
42+
if (filter) {
43+
sql += ` where schema ${filter}`
44+
}
3345
if (limit) {
3446
sql += ` limit ${limit}`
3547
}

src/lib/generators.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import PostgresMeta from './PostgresMeta.js'
22
import {
33
PostgresColumn,
4+
PostgresForeignTable,
45
PostgresFunction,
56
PostgresMaterializedView,
67
PostgresRelationship,
@@ -14,6 +15,7 @@ import { PostgresMetaResult } from './types.js'
1415
export type GeneratorMetadata = {
1516
schemas: PostgresSchema[]
1617
tables: Omit<PostgresTable, 'columns'>[]
18+
foreignTables: Omit<PostgresForeignTable, 'columns'>[]
1719
views: Omit<PostgresView, 'columns'>[]
1820
materializedViews: Omit<PostgresMaterializedView, 'columns'>[]
1921
columns: PostgresColumn[]
@@ -46,6 +48,15 @@ export async function getGeneratorMetadata(
4648
return { data: null, error: tablesError }
4749
}
4850

51+
const { data: foreignTables, error: foreignTablesError } = await pgMeta.foreignTables.list({
52+
includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined,
53+
excludedSchemas,
54+
includeColumns: false,
55+
})
56+
if (foreignTablesError) {
57+
return { data: null, error: foreignTablesError }
58+
}
59+
4960
const { data: views, error: viewsError } = await pgMeta.views.list({
5061
includedSchemas: includedSchemas.length > 0 ? includedSchemas : undefined,
5162
excludedSchemas,
@@ -104,6 +115,7 @@ export async function getGeneratorMetadata(
104115
(includedSchemas.length === 0 || includedSchemas.includes(name))
105116
),
106117
tables,
118+
foreignTables,
107119
views,
108120
materializedViews,
109121
columns,

src/server/server.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ if (EXPORT_DOCS) {
4141
const [
4242
{ data: schemas, error: schemasError },
4343
{ data: tables, error: tablesError },
44+
{ data: foreignTables, error: foreignTablesError },
4445
{ data: views, error: viewsError },
4546
{ data: materializedViews, error: materializedViewsError },
4647
{ data: columns, error: columnsError },
@@ -54,6 +55,11 @@ if (EXPORT_DOCS) {
5455
GENERATE_TYPES_INCLUDED_SCHEMAS.length > 0 ? GENERATE_TYPES_INCLUDED_SCHEMAS : undefined,
5556
includeColumns: false,
5657
}),
58+
pgMeta.foreignTables.list({
59+
includedSchemas:
60+
GENERATE_TYPES_INCLUDED_SCHEMAS.length > 0 ? GENERATE_TYPES_INCLUDED_SCHEMAS : undefined,
61+
includeColumns: false,
62+
}),
5763
pgMeta.views.list({
5864
includedSchemas:
5965
GENERATE_TYPES_INCLUDED_SCHEMAS.length > 0 ? GENERATE_TYPES_INCLUDED_SCHEMAS : undefined,
@@ -86,6 +92,9 @@ if (EXPORT_DOCS) {
8692
if (tablesError) {
8793
throw new Error(tablesError.message)
8894
}
95+
if (foreignTablesError) {
96+
throw new Error(foreignTablesError.message)
97+
}
8998
if (viewsError) {
9099
throw new Error(viewsError.message)
91100
}
@@ -113,6 +122,7 @@ if (EXPORT_DOCS) {
113122
GENERATE_TYPES_INCLUDED_SCHEMAS.includes(name)
114123
),
115124
tables: tables!,
125+
foreignTables: foreignTables!,
116126
views: views!,
117127
materializedViews: materializedViews!,
118128
columns: columns!,

src/server/templates/typescript.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type { GeneratorMetadata } from '../../lib/generators.js'
1212
export const apply = async ({
1313
schemas,
1414
tables,
15+
foreignTables,
1516
views,
1617
materializedViews,
1718
columns,
@@ -23,7 +24,7 @@ export const apply = async ({
2324
detectOneToOneRelationships: boolean
2425
}): Promise<string> => {
2526
const columnsByTableId = Object.fromEntries<PostgresColumn[]>(
26-
[...tables, ...views, ...materializedViews].map((t) => [t.id, []])
27+
[...tables, ...foreignTables, ...views, ...materializedViews].map((t) => [t.id, []])
2728
)
2829
columns
2930
.filter((c) => c.table_id in columnsByTableId)
@@ -37,7 +38,7 @@ export type Database = {
3738
${schemas
3839
.sort(({ name: a }, { name: b }) => a.localeCompare(b))
3940
.map((schema) => {
40-
const schemaTables = tables
41+
const schemaTables = [...tables, ...foreignTables]
4142
.filter((table) => table.schema === schema.name)
4243
.sort(({ name: a }, { name: b }) => a.localeCompare(b))
4344
const schemaViews = [...views, ...materializedViews]

test/db/00-init.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ create extension postgres_fdw;
8787
create server foreign_server foreign data wrapper postgres_fdw options (host 'localhost', port '5432', dbname 'postgres');
8888
create user mapping for postgres server foreign_server options (user 'postgres', password 'postgres');
8989
create foreign table foreign_table (
90-
id int8,
90+
id int8 not null,
9191
name text,
9292
status user_status
9393
) server foreign_server options (schema_name 'public', table_name 'users');

test/lib/foreign-tables.ts

Lines changed: 66 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -22,71 +22,71 @@ test('list', async () => {
2222
const res = await pgMeta.foreignTables.list()
2323
expect(cleanNondetFromResponse(res).data?.find(({ name }) => name === 'foreign_table'))
2424
.toMatchInlineSnapshot(`
25-
{
26-
"columns": [
27-
{
28-
"check": null,
29-
"comment": null,
30-
"data_type": "bigint",
31-
"default_value": null,
32-
"enums": [],
33-
"format": "int8",
34-
"identity_generation": null,
35-
"is_generated": false,
36-
"is_identity": false,
37-
"is_nullable": true,
38-
"is_unique": false,
39-
"is_updatable": true,
40-
"name": "id",
41-
"ordinal_position": 1,
42-
"schema": "public",
43-
"table": "foreign_table",
44-
},
45-
{
46-
"check": null,
47-
"comment": null,
48-
"data_type": "text",
49-
"default_value": null,
50-
"enums": [],
51-
"format": "text",
52-
"identity_generation": null,
53-
"is_generated": false,
54-
"is_identity": false,
55-
"is_nullable": true,
56-
"is_unique": false,
57-
"is_updatable": true,
58-
"name": "name",
59-
"ordinal_position": 2,
60-
"schema": "public",
61-
"table": "foreign_table",
62-
},
63-
{
64-
"check": null,
65-
"comment": null,
66-
"data_type": "USER-DEFINED",
67-
"default_value": null,
68-
"enums": [
69-
"ACTIVE",
70-
"INACTIVE",
71-
],
72-
"format": "user_status",
73-
"identity_generation": null,
74-
"is_generated": false,
75-
"is_identity": false,
76-
"is_nullable": true,
77-
"is_unique": false,
78-
"is_updatable": true,
79-
"name": "status",
80-
"ordinal_position": 3,
81-
"schema": "public",
82-
"table": "foreign_table",
83-
},
84-
],
85-
"comment": null,
86-
"name": "foreign_table",
87-
"schema": "public",
88-
}
89-
`)
25+
{
26+
"columns": [
27+
{
28+
"check": null,
29+
"comment": null,
30+
"data_type": "bigint",
31+
"default_value": null,
32+
"enums": [],
33+
"format": "int8",
34+
"identity_generation": null,
35+
"is_generated": false,
36+
"is_identity": false,
37+
"is_nullable": false,
38+
"is_unique": false,
39+
"is_updatable": true,
40+
"name": "id",
41+
"ordinal_position": 1,
42+
"schema": "public",
43+
"table": "foreign_table",
44+
},
45+
{
46+
"check": null,
47+
"comment": null,
48+
"data_type": "text",
49+
"default_value": null,
50+
"enums": [],
51+
"format": "text",
52+
"identity_generation": null,
53+
"is_generated": false,
54+
"is_identity": false,
55+
"is_nullable": true,
56+
"is_unique": false,
57+
"is_updatable": true,
58+
"name": "name",
59+
"ordinal_position": 2,
60+
"schema": "public",
61+
"table": "foreign_table",
62+
},
63+
{
64+
"check": null,
65+
"comment": null,
66+
"data_type": "USER-DEFINED",
67+
"default_value": null,
68+
"enums": [
69+
"ACTIVE",
70+
"INACTIVE",
71+
],
72+
"format": "user_status",
73+
"identity_generation": null,
74+
"is_generated": false,
75+
"is_identity": false,
76+
"is_nullable": true,
77+
"is_unique": false,
78+
"is_updatable": true,
79+
"name": "status",
80+
"ordinal_position": 3,
81+
"schema": "public",
82+
"table": "foreign_table",
83+
},
84+
],
85+
"comment": null,
86+
"name": "foreign_table",
87+
"schema": "public",
88+
}
89+
`)
9090
})
9191

9292
test('list without columns', async () => {
@@ -117,7 +117,7 @@ test('retrieve', async () => {
117117
"identity_generation": null,
118118
"is_generated": false,
119119
"is_identity": false,
120-
"is_nullable": true,
120+
"is_nullable": false,
121121
"is_unique": false,
122122
"is_updatable": true,
123123
"name": "id",

test/server/typegen.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,24 @@ test('typegen', async () => {
3636
Update: {}
3737
Relationships: []
3838
}
39+
foreign_table: {
40+
Row: {
41+
id: number
42+
name: string | null
43+
status: Database["public"]["Enums"]["user_status"] | null
44+
}
45+
Insert: {
46+
id: number
47+
name?: string | null
48+
status?: Database["public"]["Enums"]["user_status"] | null
49+
}
50+
Update: {
51+
id?: number
52+
name?: string | null
53+
status?: Database["public"]["Enums"]["user_status"] | null
54+
}
55+
Relationships: []
56+
}
3957
memes: {
4058
Row: {
4159
category: number | null
@@ -519,6 +537,24 @@ test('typegen w/ one-to-one relationships', async () => {
519537
Update: {}
520538
Relationships: []
521539
}
540+
foreign_table: {
541+
Row: {
542+
id: number
543+
name: string | null
544+
status: Database["public"]["Enums"]["user_status"] | null
545+
}
546+
Insert: {
547+
id: number
548+
name?: string | null
549+
status?: Database["public"]["Enums"]["user_status"] | null
550+
}
551+
Update: {
552+
id?: number
553+
name?: string | null
554+
status?: Database["public"]["Enums"]["user_status"] | null
555+
}
556+
Relationships: []
557+
}
522558
memes: {
523559
Row: {
524560
category: number | null

0 commit comments

Comments
 (0)