@supabase/postgrest-typegen 0.2.2 → 0.2.3
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 -8
- package/dist/generation/python.js +21 -15
- package/dist/sort.js +17 -2
- package/package.json +3 -3
- package/src/generation/python.ts +28 -15
- package/src/sort.ts +19 -5
package/README.md
CHANGED
|
@@ -17,19 +17,19 @@ JSON output, not inside `postgrest-typegen`.
|
|
|
17
17
|
|
|
18
18
|
The TypeScript, Go, Python, and Swift generators bundled here are a
|
|
19
19
|
**deliberate transition**, not the target architecture for new languages.
|
|
20
|
-
They were ported byte-
|
|
21
|
-
`supabase gen types`
|
|
22
|
-
|
|
23
|
-
move out to their own SDK repos too (tracked in SDK-1641), not yet
|
|
24
|
-
— sequenced after postgres-meta's cutover to this package settles.
|
|
20
|
+
They were ported byte-for-byte from postgres-meta's own templates so
|
|
21
|
+
`supabase gen types` kept working unchanged; postgres-meta has since deleted
|
|
22
|
+
its copies and consumes this package instead. It's decided that all four
|
|
23
|
+
eventually move out to their own SDK repos too (tracked in SDK-1641), not yet
|
|
24
|
+
scheduled — sequenced after postgres-meta's cutover to this package settles.
|
|
25
25
|
|
|
26
26
|
> [!IMPORTANT]
|
|
27
27
|
> **We are no longer accepting contributions that fix the bundled generators.**
|
|
28
28
|
> Because each of the four is moving out to its own language's SDK repository,
|
|
29
29
|
> a fix landed here would have to be made a second time in the destination
|
|
30
|
-
> repository, and
|
|
31
|
-
>
|
|
32
|
-
> tracked and carried across the move.
|
|
30
|
+
> repository, and postgres-meta and the CLI only pick it up once they bump
|
|
31
|
+
> their pinned version of this package. Please open an issue describing the
|
|
32
|
+
> bug instead, so it can be tracked and carried across the move.
|
|
33
33
|
|
|
34
34
|
## Design
|
|
35
35
|
|
|
@@ -53,7 +53,7 @@ class PythonContext {
|
|
|
53
53
|
schemas;
|
|
54
54
|
constructor(types, columns, schemas) {
|
|
55
55
|
this.schemas = Object.fromEntries(schemas.map((schema) => [schema.name, schema]));
|
|
56
|
-
this.types = Object.fromEntries(types.map((type) => [type.name, type]));
|
|
56
|
+
this.types = Object.fromEntries(types.map((type) => [qualifiedTypeName(type.schema, type.name), type]));
|
|
57
57
|
this.columns = columns.reduce((acc, curr) => {
|
|
58
58
|
acc[curr.table_id] ??= [];
|
|
59
59
|
acc[curr.table_id].push(curr);
|
|
@@ -61,30 +61,35 @@ class PythonContext {
|
|
|
61
61
|
}, {});
|
|
62
62
|
this.user_enums = Object.fromEntries(types
|
|
63
63
|
.filter((type) => type.enums.length > 0)
|
|
64
|
-
.map((type) => [
|
|
64
|
+
.map((type) => [
|
|
65
|
+
qualifiedTypeName(type.schema, type.name),
|
|
66
|
+
new PythonEnum(type),
|
|
67
|
+
]));
|
|
65
68
|
}
|
|
66
|
-
resolveTypeName(name) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
resolveTypeName(name, typeSchema) {
|
|
70
|
+
const qualified = qualifiedTypeName(typeSchema, name);
|
|
71
|
+
if (qualified in this.user_enums) {
|
|
72
|
+
return this.user_enums[qualified].name;
|
|
69
73
|
}
|
|
70
74
|
if (name in PY_TYPE_MAP) {
|
|
71
75
|
return PY_TYPE_MAP[name];
|
|
72
76
|
}
|
|
73
|
-
if (
|
|
74
|
-
const type = this.types[
|
|
75
|
-
|
|
76
|
-
return `${formatForPyClassName(schema)}${formatForPyClassName(type.name)}`;
|
|
77
|
+
if (qualified in this.types) {
|
|
78
|
+
const type = this.types[qualified];
|
|
79
|
+
return `${formatForPyClassName(type.schema)}${formatForPyClassName(type.name)}`;
|
|
77
80
|
}
|
|
78
81
|
return "Any";
|
|
79
82
|
}
|
|
80
|
-
|
|
83
|
+
// Array types live in the schema of their element type, so `typeSchema`
|
|
84
|
+
// carries over to the element.
|
|
85
|
+
parsePgType(pg_type, typeSchema) {
|
|
81
86
|
if (pg_type.startsWith("_")) {
|
|
82
87
|
const inner_str = pg_type.slice(1);
|
|
83
|
-
const inner = this.parsePgType(inner_str);
|
|
88
|
+
const inner = this.parsePgType(inner_str, typeSchema);
|
|
84
89
|
return new PythonListType(inner);
|
|
85
90
|
}
|
|
86
91
|
else {
|
|
87
|
-
const type_name = this.resolveTypeName(pg_type);
|
|
92
|
+
const type_name = this.resolveTypeName(pg_type, typeSchema);
|
|
88
93
|
return new PythonSimpleType(type_name);
|
|
89
94
|
}
|
|
90
95
|
}
|
|
@@ -98,7 +103,7 @@ class PythonContext {
|
|
|
98
103
|
};
|
|
99
104
|
});
|
|
100
105
|
const attributeEntries = attributes.map((attribute) => {
|
|
101
|
-
const type = this.parsePgType(attribute.type.name);
|
|
106
|
+
const type = this.parsePgType(attribute.type.name, attribute.type.schema);
|
|
102
107
|
// Composite type attributes cannot carry NOT NULL constraints in
|
|
103
108
|
// Postgres, so every field is inherently nullable.
|
|
104
109
|
return new PythonBaseModelAttr(attribute.name, type, true);
|
|
@@ -109,14 +114,14 @@ class PythonContext {
|
|
|
109
114
|
columnsToClassAttrs(table_id) {
|
|
110
115
|
const attrs = this.columns[table_id] ?? [];
|
|
111
116
|
return attrs.map((col) => {
|
|
112
|
-
const type = this.parsePgType(col.format);
|
|
117
|
+
const type = this.parsePgType(col.format, col.type_schema);
|
|
113
118
|
return new PythonBaseModelAttr(col.name, type, col.is_nullable);
|
|
114
119
|
});
|
|
115
120
|
}
|
|
116
121
|
columnsToDictAttrs(table_id, not_required) {
|
|
117
122
|
const attrs = this.columns[table_id] ?? [];
|
|
118
123
|
return attrs.map((col) => {
|
|
119
|
-
const type = this.parsePgType(col.format);
|
|
124
|
+
const type = this.parsePgType(col.format, col.type_schema);
|
|
120
125
|
return new PythonTypedDictAttr(col.name, type, col.is_nullable, not_required ||
|
|
121
126
|
col.is_nullable ||
|
|
122
127
|
col.is_identity ||
|
|
@@ -139,6 +144,7 @@ class PythonContext {
|
|
|
139
144
|
return new PythonBaseModel(matview.name, this.schemas[matview.schema], attributes);
|
|
140
145
|
}
|
|
141
146
|
}
|
|
147
|
+
const qualifiedTypeName = (schema, name) => `${schema}.${name}`;
|
|
142
148
|
class PythonEnum {
|
|
143
149
|
name;
|
|
144
150
|
variants;
|
package/dist/sort.js
CHANGED
|
@@ -4,10 +4,25 @@ import { compareStrings } from "./collation.js";
|
|
|
4
4
|
const bySchemaName = (a, b) => compareStrings(a.schema, b.schema) ||
|
|
5
5
|
compareStrings(a.name, b.name) ||
|
|
6
6
|
a.id - b.id;
|
|
7
|
-
//
|
|
7
|
+
// Column lists compare as their JSON text, matching the TypeScript generator's
|
|
8
|
+
// historical `relationships.sort`.
|
|
9
|
+
const compareColumns = (a, b) => compareStrings(JSON.stringify(a), JSON.stringify(b));
|
|
10
|
+
// The first three keys mirror the TypeScript generator's historical
|
|
11
|
+
// `relationships.sort`. They are not total: `expandViewRelationships` in
|
|
12
|
+
// `introspection/relationships.ts` copies one foreign key onto every view
|
|
13
|
+
// exposing it, on either side, and onto every combination of view columns
|
|
14
|
+
// carrying it. View-to-table copies share the referenced side and differ in
|
|
15
|
+
// `schema`, `relation` and `columns`; table-to-view copies onto same-named
|
|
16
|
+
// views in different schemas differ only in `referenced_schema`. The remaining
|
|
17
|
+
// keys order those copies, since the view key dependency query does not order
|
|
18
|
+
// the view columns it aggregates.
|
|
8
19
|
const byRelationship = (a, b) => compareStrings(a.foreign_key_name, b.foreign_key_name) ||
|
|
9
20
|
compareStrings(a.referenced_relation, b.referenced_relation) ||
|
|
10
|
-
|
|
21
|
+
compareColumns(a.referenced_columns, b.referenced_columns) ||
|
|
22
|
+
compareStrings(a.referenced_schema, b.referenced_schema) ||
|
|
23
|
+
compareStrings(a.schema, b.schema) ||
|
|
24
|
+
compareStrings(a.relation, b.relation) ||
|
|
25
|
+
compareColumns(a.columns, b.columns);
|
|
11
26
|
/**
|
|
12
27
|
* Return a new {@link GeneratorMetadata} with every collection ordered by a
|
|
13
28
|
* stable, total, semantic key (matching the TypeScript generator's ordering).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supabase/postgrest-typegen",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Type generation for PostgREST from PostgreSQL schemas",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codegen",
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
"@types/node": "^26.3.0",
|
|
73
73
|
"@types/pg": "^8.23.1",
|
|
74
74
|
"knip": "^6.32.3",
|
|
75
|
-
"oxlint": "1.
|
|
76
|
-
"oxlint-tsgolint": "7.0.
|
|
75
|
+
"oxlint": "1.83.0",
|
|
76
|
+
"oxlint-tsgolint": "7.0.2002",
|
|
77
77
|
"pg": "^8.23.0",
|
|
78
78
|
"testcontainers": "^12.1.0",
|
|
79
79
|
"typescript": "7.0.2"
|
package/src/generation/python.ts
CHANGED
|
@@ -88,7 +88,9 @@ class PythonContext {
|
|
|
88
88
|
this.schemas = Object.fromEntries(
|
|
89
89
|
schemas.map((schema) => [schema.name, schema]),
|
|
90
90
|
);
|
|
91
|
-
this.types = Object.fromEntries(
|
|
91
|
+
this.types = Object.fromEntries(
|
|
92
|
+
types.map((type) => [qualifiedTypeName(type.schema, type.name), type]),
|
|
93
|
+
);
|
|
92
94
|
this.columns = columns.reduce(
|
|
93
95
|
(acc, curr) => {
|
|
94
96
|
acc[curr.table_id] ??= [];
|
|
@@ -100,32 +102,37 @@ class PythonContext {
|
|
|
100
102
|
this.user_enums = Object.fromEntries(
|
|
101
103
|
types
|
|
102
104
|
.filter((type) => type.enums.length > 0)
|
|
103
|
-
.map((type) => [
|
|
105
|
+
.map((type) => [
|
|
106
|
+
qualifiedTypeName(type.schema, type.name),
|
|
107
|
+
new PythonEnum(type),
|
|
108
|
+
]),
|
|
104
109
|
);
|
|
105
110
|
}
|
|
106
111
|
|
|
107
|
-
resolveTypeName(name: string): string {
|
|
108
|
-
|
|
109
|
-
|
|
112
|
+
resolveTypeName(name: string, typeSchema: string): string {
|
|
113
|
+
const qualified = qualifiedTypeName(typeSchema, name);
|
|
114
|
+
if (qualified in this.user_enums) {
|
|
115
|
+
return this.user_enums[qualified].name;
|
|
110
116
|
}
|
|
111
117
|
if (name in PY_TYPE_MAP) {
|
|
112
118
|
return PY_TYPE_MAP[name];
|
|
113
119
|
}
|
|
114
|
-
if (
|
|
115
|
-
const type = this.types[
|
|
116
|
-
|
|
117
|
-
return `${formatForPyClassName(schema)}${formatForPyClassName(type.name)}`;
|
|
120
|
+
if (qualified in this.types) {
|
|
121
|
+
const type = this.types[qualified];
|
|
122
|
+
return `${formatForPyClassName(type.schema)}${formatForPyClassName(type.name)}`;
|
|
118
123
|
}
|
|
119
124
|
return "Any";
|
|
120
125
|
}
|
|
121
126
|
|
|
122
|
-
|
|
127
|
+
// Array types live in the schema of their element type, so `typeSchema`
|
|
128
|
+
// carries over to the element.
|
|
129
|
+
parsePgType(pg_type: string, typeSchema: string): PythonType {
|
|
123
130
|
if (pg_type.startsWith("_")) {
|
|
124
131
|
const inner_str = pg_type.slice(1);
|
|
125
|
-
const inner = this.parsePgType(inner_str);
|
|
132
|
+
const inner = this.parsePgType(inner_str, typeSchema);
|
|
126
133
|
return new PythonListType(inner);
|
|
127
134
|
} else {
|
|
128
|
-
const type_name = this.resolveTypeName(pg_type);
|
|
135
|
+
const type_name = this.resolveTypeName(pg_type, typeSchema);
|
|
129
136
|
return new PythonSimpleType(type_name);
|
|
130
137
|
}
|
|
131
138
|
}
|
|
@@ -141,7 +148,10 @@ class PythonContext {
|
|
|
141
148
|
});
|
|
142
149
|
const attributeEntries: PythonBaseModelAttr[] = attributes.map(
|
|
143
150
|
(attribute) => {
|
|
144
|
-
const type = this.parsePgType(
|
|
151
|
+
const type = this.parsePgType(
|
|
152
|
+
attribute.type!.name,
|
|
153
|
+
attribute.type!.schema,
|
|
154
|
+
);
|
|
145
155
|
// Composite type attributes cannot carry NOT NULL constraints in
|
|
146
156
|
// Postgres, so every field is inherently nullable.
|
|
147
157
|
return new PythonBaseModelAttr(attribute.name, type, true);
|
|
@@ -155,7 +165,7 @@ class PythonContext {
|
|
|
155
165
|
columnsToClassAttrs(table_id: number): PythonBaseModelAttr[] {
|
|
156
166
|
const attrs = this.columns[table_id] ?? [];
|
|
157
167
|
return attrs.map((col) => {
|
|
158
|
-
const type = this.parsePgType(col.format);
|
|
168
|
+
const type = this.parsePgType(col.format, col.type_schema);
|
|
159
169
|
return new PythonBaseModelAttr(col.name, type, col.is_nullable);
|
|
160
170
|
});
|
|
161
171
|
}
|
|
@@ -166,7 +176,7 @@ class PythonContext {
|
|
|
166
176
|
): PythonTypedDictAttr[] {
|
|
167
177
|
const attrs = this.columns[table_id] ?? [];
|
|
168
178
|
return attrs.map((col) => {
|
|
169
|
-
const type = this.parsePgType(col.format);
|
|
179
|
+
const type = this.parsePgType(col.format, col.type_schema);
|
|
170
180
|
return new PythonTypedDictAttr(
|
|
171
181
|
col.name,
|
|
172
182
|
type,
|
|
@@ -222,6 +232,9 @@ class PythonContext {
|
|
|
222
232
|
}
|
|
223
233
|
}
|
|
224
234
|
|
|
235
|
+
const qualifiedTypeName = (schema: string, name: string): string =>
|
|
236
|
+
`${schema}.${name}`;
|
|
237
|
+
|
|
225
238
|
class PythonEnum implements Serializable {
|
|
226
239
|
name: string;
|
|
227
240
|
variants: string[];
|
package/src/sort.ts
CHANGED
|
@@ -38,17 +38,31 @@ const bySchemaName = (
|
|
|
38
38
|
compareStrings(a.name, b.name) ||
|
|
39
39
|
a.id - b.id;
|
|
40
40
|
|
|
41
|
-
//
|
|
41
|
+
// Column lists compare as their JSON text, matching the TypeScript generator's
|
|
42
|
+
// historical `relationships.sort`.
|
|
43
|
+
const compareColumns = (a: string[], b: string[]): number =>
|
|
44
|
+
compareStrings(JSON.stringify(a), JSON.stringify(b));
|
|
45
|
+
|
|
46
|
+
// The first three keys mirror the TypeScript generator's historical
|
|
47
|
+
// `relationships.sort`. They are not total: `expandViewRelationships` in
|
|
48
|
+
// `introspection/relationships.ts` copies one foreign key onto every view
|
|
49
|
+
// exposing it, on either side, and onto every combination of view columns
|
|
50
|
+
// carrying it. View-to-table copies share the referenced side and differ in
|
|
51
|
+
// `schema`, `relation` and `columns`; table-to-view copies onto same-named
|
|
52
|
+
// views in different schemas differ only in `referenced_schema`. The remaining
|
|
53
|
+
// keys order those copies, since the view key dependency query does not order
|
|
54
|
+
// the view columns it aggregates.
|
|
42
55
|
const byRelationship = (
|
|
43
56
|
a: PostgresRelationship,
|
|
44
57
|
b: PostgresRelationship,
|
|
45
58
|
): number =>
|
|
46
59
|
compareStrings(a.foreign_key_name, b.foreign_key_name) ||
|
|
47
60
|
compareStrings(a.referenced_relation, b.referenced_relation) ||
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
)
|
|
61
|
+
compareColumns(a.referenced_columns, b.referenced_columns) ||
|
|
62
|
+
compareStrings(a.referenced_schema, b.referenced_schema) ||
|
|
63
|
+
compareStrings(a.schema, b.schema) ||
|
|
64
|
+
compareStrings(a.relation, b.relation) ||
|
|
65
|
+
compareColumns(a.columns, b.columns);
|
|
52
66
|
|
|
53
67
|
/**
|
|
54
68
|
* Return a new {@link GeneratorMetadata} with every collection ordered by a
|