@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 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-parity from postgres-meta's own templates so
21
- `supabase gen types` keeps working unchanged while postgres-meta's copies get
22
- deprecated in favor of this package. It's decided that all four eventually
23
- move out to their own SDK repos too (tracked in SDK-1641), not yet scheduled
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 it also breaks byte parity with postgres-meta until a release
31
- > propagates. Please open an issue describing the bug instead, so it can be
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) => [type.name, new PythonEnum(type)]));
64
+ .map((type) => [
65
+ qualifiedTypeName(type.schema, type.name),
66
+ new PythonEnum(type),
67
+ ]));
65
68
  }
66
- resolveTypeName(name) {
67
- if (name in this.user_enums) {
68
- return this.user_enums[name].name;
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 (name in this.types) {
74
- const type = this.types[name];
75
- const schema = type.schema;
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
- parsePgType(pg_type) {
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
- // Mirrors the TypeScript generator's historical `relationships.sort`.
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
- compareStrings(JSON.stringify(a.referenced_columns), JSON.stringify(b.referenced_columns));
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.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.81.0",
76
- "oxlint-tsgolint": "7.0.2001",
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"
@@ -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(types.map((type) => [type.name, type]));
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) => [type.name, new PythonEnum(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
- if (name in this.user_enums) {
109
- return this.user_enums[name].name;
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 (name in this.types) {
115
- const type = this.types[name];
116
- const schema = type!.schema;
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
- parsePgType(pg_type: string): PythonType {
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(attribute.type!.name);
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
- // Mirrors the TypeScript generator's historical `relationships.sort`.
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
- compareStrings(
49
- JSON.stringify(a.referenced_columns),
50
- JSON.stringify(b.referenced_columns),
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