@supabase/postgrest-typegen 0.2.1 → 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.
Files changed (58) hide show
  1. package/README.md +13 -5
  2. package/dist/collation.d.ts +2 -0
  3. package/dist/collation.js +15 -0
  4. package/dist/generation/index.d.ts +1 -1
  5. package/dist/generation/python.js +21 -15
  6. package/dist/generation/swift.js +9 -2
  7. package/dist/generation/typescript.d.ts +18 -7
  8. package/dist/generation/typescript.js +102 -79
  9. package/dist/introspection/index.js +6 -23
  10. package/dist/introspection/relationships.d.ts +4 -4
  11. package/dist/introspection/relationships.js +4 -11
  12. package/dist/introspection/sql/columns.sql.d.ts +2 -7
  13. package/dist/introspection/sql/columns.sql.js +23 -17
  14. package/dist/introspection/sql/common.d.ts +7 -15
  15. package/dist/introspection/sql/foreign_tables.sql.d.ts +2 -6
  16. package/dist/introspection/sql/foreign_tables.sql.js +0 -5
  17. package/dist/introspection/sql/functions.sql.d.ts +2 -5
  18. package/dist/introspection/sql/functions.sql.js +0 -33
  19. package/dist/introspection/sql/materialized_views.sql.d.ts +2 -4
  20. package/dist/introspection/sql/materialized_views.sql.js +0 -5
  21. package/dist/introspection/sql/primary_keys.sql.d.ts +2 -4
  22. package/dist/introspection/sql/primary_keys.sql.js +0 -4
  23. package/dist/introspection/sql/schemas.sql.d.ts +2 -6
  24. package/dist/introspection/sql/schemas.sql.js +2 -6
  25. package/dist/introspection/sql/table.sql.d.ts +2 -4
  26. package/dist/introspection/sql/table.sql.js +0 -5
  27. package/dist/introspection/sql/table_relationships.sql.d.ts +2 -2
  28. package/dist/introspection/sql/types.sql.d.ts +5 -5
  29. package/dist/introspection/sql/types.sql.js +6 -17
  30. package/dist/introspection/sql/views.sql.d.ts +2 -4
  31. package/dist/introspection/sql/views.sql.js +0 -5
  32. package/dist/introspection/sql/views_key_dependencies.sql.d.ts +2 -2
  33. package/dist/introspection/sql/views_key_dependencies.sql.js +64 -75
  34. package/dist/sort.js +32 -16
  35. package/dist/types.d.ts +4 -4
  36. package/dist/types.js +6 -4
  37. package/package.json +4 -4
  38. package/src/collation.ts +16 -0
  39. package/src/generation/index.ts +1 -0
  40. package/src/generation/python.ts +28 -15
  41. package/src/generation/swift.ts +9 -2
  42. package/src/generation/typescript.ts +161 -104
  43. package/src/introspection/index.ts +7 -32
  44. package/src/introspection/relationships.ts +10 -19
  45. package/src/introspection/sql/columns.sql.ts +25 -26
  46. package/src/introspection/sql/common.ts +7 -17
  47. package/src/introspection/sql/foreign_tables.sql.ts +2 -13
  48. package/src/introspection/sql/functions.sql.ts +2 -44
  49. package/src/introspection/sql/materialized_views.sql.ts +2 -11
  50. package/src/introspection/sql/primary_keys.sql.ts +2 -10
  51. package/src/introspection/sql/schemas.sql.ts +4 -14
  52. package/src/introspection/sql/table.sql.ts +2 -11
  53. package/src/introspection/sql/table_relationships.sql.ts +2 -4
  54. package/src/introspection/sql/types.sql.ts +6 -26
  55. package/src/introspection/sql/views.sql.ts +2 -11
  56. package/src/introspection/sql/views_key_dependencies.sql.ts +83 -77
  57. package/src/sort.ts +37 -18
  58. package/src/types.ts +6 -4
package/dist/sort.js CHANGED
@@ -1,12 +1,28 @@
1
+ import { compareStrings } from "./collation.js";
1
2
  // Relations (tables/views/materialized views/foreign tables) and types share a
2
3
  // (schema, name) identity within a database; `id` breaks any residual tie.
3
- const bySchemaName = (a, b) => a.schema.localeCompare(b.schema) ||
4
- a.name.localeCompare(b.name) ||
4
+ const bySchemaName = (a, b) => compareStrings(a.schema, b.schema) ||
5
+ compareStrings(a.name, b.name) ||
5
6
  a.id - b.id;
6
- // Mirrors the TypeScript generator's historical `relationships.sort`.
7
- const byRelationship = (a, b) => a.foreign_key_name.localeCompare(b.foreign_key_name) ||
8
- a.referenced_relation.localeCompare(b.referenced_relation) ||
9
- JSON.stringify(a.referenced_columns).localeCompare(JSON.stringify(b.referenced_columns));
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.
19
+ const byRelationship = (a, b) => compareStrings(a.foreign_key_name, b.foreign_key_name) ||
20
+ compareStrings(a.referenced_relation, b.referenced_relation) ||
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);
10
26
  /**
11
27
  * Return a new {@link GeneratorMetadata} with every collection ordered by a
12
28
  * stable, total, semantic key (matching the TypeScript generator's ordering).
@@ -16,32 +32,32 @@ const byRelationship = (a, b) => a.foreign_key_name.localeCompare(b.foreign_key_
16
32
  export function sortGeneratorMetadata(metadata) {
17
33
  return {
18
34
  version: metadata.version,
19
- schemas: [...metadata.schemas].sort((a, b) => a.name.localeCompare(b.name) || a.id - b.id),
35
+ schemas: [...metadata.schemas].sort((a, b) => compareStrings(a.name, b.name) || a.id - b.id),
20
36
  tables: [...metadata.tables].sort(bySchemaName),
21
37
  foreignTables: [...metadata.foreignTables].sort(bySchemaName),
22
38
  views: [...metadata.views].sort(bySchemaName),
23
39
  materializedViews: [...metadata.materializedViews].sort(bySchemaName),
24
40
  // Group columns by their table's (schema, name); name-order within a table
25
41
  // (matches the TypeScript generator's per-table `columns.sort(by name)`).
26
- columns: [...metadata.columns].sort((a, b) => a.schema.localeCompare(b.schema) ||
27
- a.table.localeCompare(b.table) ||
28
- a.name.localeCompare(b.name)),
42
+ columns: [...metadata.columns].sort((a, b) => compareStrings(a.schema, b.schema) ||
43
+ compareStrings(a.table, b.table) ||
44
+ compareStrings(a.name, b.name)),
29
45
  // Grouped by table like `columns`; within a table the SQL's declared
30
46
  // column order (`array_position(indkey, attnum)`) is preserved by the
31
47
  // stable sort, since composite key order is meaningful.
32
- primaryKeys: [...metadata.primaryKeys].sort((a, b) => a.schema.localeCompare(b.schema) ||
33
- a.table_name.localeCompare(b.table_name)),
48
+ primaryKeys: [...metadata.primaryKeys].sort((a, b) => compareStrings(a.schema, b.schema) ||
49
+ compareStrings(a.table_name, b.table_name)),
34
50
  relationships: [...metadata.relationships].sort(byRelationship),
35
51
  // Functions can overload, so the signature is part of the identity. Args
36
52
  // are addressed by name in generated RPC types, so sort them by name too.
37
53
  functions: [...metadata.functions]
38
- .sort((a, b) => a.schema.localeCompare(b.schema) ||
39
- a.name.localeCompare(b.name) ||
40
- a.identity_argument_types.localeCompare(b.identity_argument_types) ||
54
+ .sort((a, b) => compareStrings(a.schema, b.schema) ||
55
+ compareStrings(a.name, b.name) ||
56
+ compareStrings(a.identity_argument_types, b.identity_argument_types) ||
41
57
  a.id - b.id)
42
58
  .map((fn) => ({
43
59
  ...fn,
44
- args: [...fn.args].sort((a, b) => a.name.localeCompare(b.name)),
60
+ args: [...fn.args].sort((a, b) => compareStrings(a.name, b.name)),
45
61
  })),
46
62
  types: [...metadata.types].sort(bySchemaName),
47
63
  };
package/dist/types.d.ts CHANGED
@@ -182,8 +182,8 @@ declare const postgresViewSchema: import("arktype/internal/variants/object.ts").
182
182
  schema: string;
183
183
  name: string;
184
184
  is_updatable: boolean;
185
- is_insert_enabled: boolean;
186
- is_update_enabled: boolean;
185
+ is_insert_enabled?: boolean | undefined;
186
+ is_update_enabled?: boolean | undefined;
187
187
  comment: string | null;
188
188
  columns?: {
189
189
  table_id: number;
@@ -252,9 +252,9 @@ export declare const generatorMetadataSchema: import("arktype/internal/variants/
252
252
  views: {
253
253
  comment: string | null;
254
254
  id: number;
255
- is_insert_enabled: boolean;
255
+ is_insert_enabled?: boolean | undefined;
256
256
  is_updatable: boolean;
257
- is_update_enabled: boolean;
257
+ is_update_enabled?: boolean | undefined;
258
258
  name: string;
259
259
  schema: string;
260
260
  }[];
package/dist/types.js CHANGED
@@ -139,12 +139,14 @@ const postgresViewSchema = type({
139
139
  /**
140
140
  * Whether INSERT works through this view: auto-updatable per
141
141
  * `pg_relation_is_updatable`, or made insertable by an INSTEAD OF INSERT
142
- * trigger. Tracked separately from `is_updatable` (which mirrors
143
- * `information_schema.views` and ignores triggers).
142
+ * trigger or an unconditional INSTEAD rule. Tracked separately from
143
+ * `is_updatable` (which mirrors `information_schema.views` and ignores
144
+ * triggers). Optional because version 1 metadata predating this field is
145
+ * still valid; consumers fall back to `is_updatable` when absent.
144
146
  */
145
- is_insert_enabled: "boolean",
147
+ "is_insert_enabled?": "boolean",
146
148
  /** Same as `is_insert_enabled`, for UPDATE. */
147
- is_update_enabled: "boolean",
149
+ "is_update_enabled?": "boolean",
148
150
  comment: "string | null",
149
151
  "columns?": postgresColumnSchema.array(),
150
152
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supabase/postgrest-typegen",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Type generation for PostgREST from PostgreSQL schemas",
5
5
  "keywords": [
6
6
  "codegen",
@@ -62,7 +62,7 @@
62
62
  },
63
63
  "dependencies": {
64
64
  "arktype": "2.2.3",
65
- "oxfmt": "0.65.0"
65
+ "oxfmt": "0.66.0"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@testcontainers/postgresql": "^12.1.0",
@@ -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.80.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"
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The one string comparison every ordering in this package uses.
3
+ *
4
+ * Generated output is ordered by name, and `String.prototype.localeCompare`
5
+ * without a locale follows the host's default locale, so the same database
6
+ * produced differently ordered code on differently configured machines (a
7
+ * Swedish host sorts `ä` after `z`, an English one next to `a`). Pinning the
8
+ * collator to English keeps the order postgres-meta's output has on the
9
+ * English CI runners and makes it the same everywhere else.
10
+ */
11
+ const collator = new Intl.Collator("en");
12
+
13
+ /** Compare two strings with the pinned English collation. */
14
+ export function compareStrings(a: string, b: string): number {
15
+ return collator.compare(a, b);
16
+ }
@@ -5,6 +5,7 @@ export {
5
5
  generateTypescript,
6
6
  type GenerateTypescriptOptions,
7
7
  pgTypeToTsType,
8
+ type TypeResolutionContext,
8
9
  } from "./typescript.ts";
9
10
  export {
10
11
  type AccessControl,
@@ -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[];
@@ -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.localeCompare(b));
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.localeCompare(b));
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") {