@supabase/postgrest-typegen 0.0.1

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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +133 -0
  3. package/dist/generation/constants.d.ts +9 -0
  4. package/dist/generation/constants.js +10 -0
  5. package/dist/generation/go.d.ts +6 -0
  6. package/dist/generation/go.js +233 -0
  7. package/dist/generation/index.d.ts +5 -0
  8. package/dist/generation/index.js +5 -0
  9. package/dist/generation/python.d.ts +6 -0
  10. package/dist/generation/python.js +339 -0
  11. package/dist/generation/swift.d.ts +11 -0
  12. package/dist/generation/swift.js +309 -0
  13. package/dist/generation/typescript.d.ts +32 -0
  14. package/dist/generation/typescript.js +796 -0
  15. package/dist/index.d.ts +4 -0
  16. package/dist/index.js +4 -0
  17. package/dist/introspection/index.d.ts +27 -0
  18. package/dist/introspection/index.js +70 -0
  19. package/dist/introspection/normalize.d.ts +31 -0
  20. package/dist/introspection/normalize.js +65 -0
  21. package/dist/introspection/relationships.d.ts +47 -0
  22. package/dist/introspection/relationships.js +85 -0
  23. package/dist/introspection/sql/columns.sql.d.ts +7 -0
  24. package/dist/introspection/sql/columns.sql.js +122 -0
  25. package/dist/introspection/sql/common.d.ts +16 -0
  26. package/dist/introspection/sql/common.js +1 -0
  27. package/dist/introspection/sql/foreign_tables.sql.d.ts +6 -0
  28. package/dist/introspection/sql/foreign_tables.sql.js +18 -0
  29. package/dist/introspection/sql/functions.sql.d.ts +5 -0
  30. package/dist/introspection/sql/functions.sql.js +148 -0
  31. package/dist/introspection/sql/helpers.d.ts +12 -0
  32. package/dist/introspection/sql/helpers.js +28 -0
  33. package/dist/introspection/sql/materialized_views.sql.d.ts +4 -0
  34. package/dist/introspection/sql/materialized_views.sql.js +19 -0
  35. package/dist/introspection/sql/primary_keys.sql.d.ts +4 -0
  36. package/dist/introspection/sql/primary_keys.sql.js +32 -0
  37. package/dist/introspection/sql/schemas.sql.d.ts +6 -0
  38. package/dist/introspection/sql/schemas.sql.js +24 -0
  39. package/dist/introspection/sql/table.sql.d.ts +4 -0
  40. package/dist/introspection/sql/table.sql.js +41 -0
  41. package/dist/introspection/sql/table_relationships.sql.d.ts +2 -0
  42. package/dist/introspection/sql/table_relationships.sql.js +49 -0
  43. package/dist/introspection/sql/types.sql.d.ts +5 -0
  44. package/dist/introspection/sql/types.sql.js +65 -0
  45. package/dist/introspection/sql/views.sql.d.ts +4 -0
  46. package/dist/introspection/sql/views.sql.js +20 -0
  47. package/dist/introspection/sql/views_key_dependencies.sql.d.ts +2 -0
  48. package/dist/introspection/sql/views_key_dependencies.sql.js +195 -0
  49. package/dist/sort.d.ts +35 -0
  50. package/dist/sort.js +48 -0
  51. package/dist/types.d.ts +372 -0
  52. package/dist/types.js +207 -0
  53. package/package.json +87 -0
  54. package/src/generation/constants.ts +11 -0
  55. package/src/generation/go.ts +356 -0
  56. package/src/generation/index.ts +13 -0
  57. package/src/generation/python.ts +461 -0
  58. package/src/generation/swift.ts +481 -0
  59. package/src/generation/typescript.ts +1144 -0
  60. package/src/index.ts +4 -0
  61. package/src/introspection/index.ts +124 -0
  62. package/src/introspection/normalize.ts +71 -0
  63. package/src/introspection/relationships.ts +173 -0
  64. package/src/introspection/sql/columns.sql.ts +131 -0
  65. package/src/introspection/sql/common.ts +18 -0
  66. package/src/introspection/sql/foreign_tables.sql.ts +26 -0
  67. package/src/introspection/sql/functions.sql.ts +159 -0
  68. package/src/introspection/sql/helpers.ts +33 -0
  69. package/src/introspection/sql/materialized_views.sql.ts +25 -0
  70. package/src/introspection/sql/primary_keys.sql.ts +38 -0
  71. package/src/introspection/sql/schemas.sql.ts +32 -0
  72. package/src/introspection/sql/table.sql.ts +47 -0
  73. package/src/introspection/sql/table_relationships.sql.ts +53 -0
  74. package/src/introspection/sql/types.sql.ts +74 -0
  75. package/src/introspection/sql/views.sql.ts +26 -0
  76. package/src/introspection/sql/views_key_dependencies.sql.ts +199 -0
  77. package/src/sort.ts +102 -0
  78. package/src/types.ts +237 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Supabase
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # @supabase/postgrest-typegen
2
+
3
+ Type generation for [PostgREST](https://postgrest.org) from a PostgreSQL
4
+ schema. This is the type-generation engine behind `supabase gen types`,
5
+ extracted from [postgres-meta](https://github.com/supabase/postgres-meta) into a
6
+ small, driver-agnostic library.
7
+
8
+ > **Status:** alpha. The public API is settling as generators and introspection
9
+ > are ported.
10
+
11
+ ## Scope: introspection is permanent, four generators are transitional
12
+
13
+ This package's permanent job is introspection and the `GeneratorMetadata`
14
+ contract (see "Out-of-process generators" below): any *new* language's
15
+ generator lives in that language's own SDK repo, consuming this package's
16
+ JSON output, not inside `postgrest-typegen`.
17
+
18
+ The TypeScript, Go, Python, and Swift generators bundled here are a
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.
25
+
26
+ ## Design
27
+
28
+ There is a hard split between **introspection** (database → metadata) and
29
+ **generation** (metadata → string):
30
+
31
+ ```ts
32
+ import { introspect } from "@supabase/postgrest-typegen/introspection";
33
+ import {
34
+ generateTypescript,
35
+ sortGeneratorMetadata,
36
+ } from "@supabase/postgrest-typegen/generation";
37
+
38
+ // Any `pg.Pool` / `pg.Client` (or compatible driver) works here.
39
+ const metadata = await introspect(pool, { includedSchemas: ["public"] });
40
+ // Canonically sort before generating (see "Stable ordering" below).
41
+ const types = await generateTypescript(sortGeneratorMetadata(metadata), {
42
+ postgrestVersion: "12",
43
+ });
44
+ ```
45
+
46
+ `GeneratorMetadata` is the pluggable contract: the SQL introspector is the
47
+ default producer, but any source that can produce that shape can feed the
48
+ generators.
49
+
50
+ ### Stable ordering (`sortGeneratorMetadata`)
51
+
52
+ The Go/Python/Swift generators emit tables, views, and materialized views in
53
+ `GeneratorMetadata` order, so their output depends on how the producer ordered
54
+ its collections (a SQL introspector returns rows in environment-dependent heap
55
+ order). `sortGeneratorMetadata` is a pure pass that canonically sorts every
56
+ collection; **apply it after introspection and before any `generate*` call** so
57
+ output is deterministic regardless of the producer. Generators expect
58
+ pre-sorted input and do not re-sort it themselves.
59
+
60
+ ### Runtime validation (opt-in)
61
+
62
+ `GeneratorMetadata` is backed by an [ArkType](https://arktype.io) schema, so a
63
+ result coming from a custom/injected producer can be validated at runtime
64
+ rather than blindly cast. `introspect()` does **not** validate — wrap its
65
+ result yourself when you want the guarantee:
66
+
67
+ ```ts
68
+ import { parseGeneratorMetadata, generatorMetadataSchema } from "@supabase/postgrest-typegen";
69
+
70
+ // Throws a TypeError with a readable summary if the shape is wrong.
71
+ const metadata = parseGeneratorMetadata(await someCustomIntrospector(db));
72
+
73
+ // Or use the raw schema directly for custom flows.
74
+ const out = generatorMetadataSchema(unknownInput);
75
+ ```
76
+
77
+ ### Out-of-process generators (JSON Schema + serialization)
78
+
79
+ Generators that live outside this package's process, in another language's
80
+ own SDK repo, receive `GeneratorMetadata` as JSON rather than a live object.
81
+ This package owns that boundary, so no consumer serializes the contract a
82
+ different way:
83
+
84
+ ```ts
85
+ import {
86
+ serializeGeneratorMetadata,
87
+ generatorMetadataJsonSchema,
88
+ } from "@supabase/postgrest-typegen";
89
+
90
+ // The exact JSON document an out-of-process generator (e.g. `dart run
91
+ // supabase_typegen`) receives over stdin/stdout.
92
+ const json = serializeGeneratorMetadata(sortGeneratorMetadata(metadata));
93
+
94
+ // The JSON Schema for that document, for consumers that want to validate or
95
+ // codegen against the contract without depending on ArkType or TypeScript.
96
+ generatorMetadataJsonSchema;
97
+ ```
98
+
99
+ `GeneratorMetadata.version` is bumped whenever the shape changes in a way a
100
+ consumer should branch on, since an out-of-process consumer only sees the
101
+ serialized document and can't otherwise detect a shape change until something
102
+ breaks at read time.
103
+
104
+ ### Generators
105
+
106
+ ```ts
107
+ import {
108
+ generateTypescript, // async (uses prettier)
109
+ generateGo,
110
+ generatePython,
111
+ generateSwift,
112
+ } from "@supabase/postgrest-typegen/generation";
113
+ ```
114
+
115
+ | Function | Options |
116
+ | -------------------- | ----------------------------------------------------------------------- |
117
+ | `generateTypescript` | `{ detectOneToOneRelationships?, postgrestVersion?, defaultSchema? }` |
118
+ | `generateGo` | — |
119
+ | `generatePython` | — |
120
+ | `generateSwift` | `{ accessControl?: 'internal' \| 'public' \| 'private' \| 'package' }` |
121
+
122
+ ## Installation
123
+
124
+ Not yet published; consumed in-repo for now (`packages/postgrest-typegen`).
125
+
126
+ ```bash
127
+ # pg is a peer of your application, not bundled here
128
+ npm install pg
129
+ ```
130
+
131
+ ## License
132
+
133
+ MIT
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Type-generation constants ported verbatim from
3
+ * `postgres-meta/src/server/constants.ts`. Only the typegen-relevant values
4
+ * live here; server/runtime constants stay in postgres-meta.
5
+ *
6
+ * Consumed by the TypeScript generator's function-argument handling.
7
+ */
8
+ export declare const VALID_UNNAMED_FUNCTION_ARG_TYPES: Set<number>;
9
+ export declare const VALID_FUNCTION_ARGS_MODE: Set<string>;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Type-generation constants ported verbatim from
3
+ * `postgres-meta/src/server/constants.ts`. Only the typegen-relevant values
4
+ * live here; server/runtime constants stay in postgres-meta.
5
+ *
6
+ * Consumed by the TypeScript generator's function-argument handling.
7
+ */
8
+ // json/jsonb/text types
9
+ export const VALID_UNNAMED_FUNCTION_ARG_TYPES = new Set([114, 3802, 25]);
10
+ export const VALID_FUNCTION_ARGS_MODE = new Set(["in", "inout", "variadic"]);
@@ -0,0 +1,6 @@
1
+ import type { GeneratorMetadata } from "../types.ts";
2
+ /**
3
+ * Emits tables/views/materialized views in `GeneratorMetadata` order. Pass
4
+ * input pre-sorted with `sortGeneratorMetadata` for deterministic output.
5
+ */
6
+ export declare const generateGo: ({ schemas, tables, views, materializedViews, columns, types, }: GeneratorMetadata) => string;
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Emits tables/views/materialized views in `GeneratorMetadata` order. Pass
3
+ * input pre-sorted with `sortGeneratorMetadata` for deterministic output.
4
+ */
5
+ export const generateGo = ({ schemas, tables, views, materializedViews, columns, types, }) => {
6
+ const columnsByTableId = columns.reduce((acc, curr) => {
7
+ acc[curr.table_id] ??= [];
8
+ acc[curr.table_id].push(curr);
9
+ return acc;
10
+ }, {});
11
+ const compositeTypes = types.filter((type) => type.attributes.length > 0);
12
+ let output = `
13
+ package database
14
+
15
+ ${tables
16
+ .filter((table) => schemas.some((schema) => schema.name === table.schema))
17
+ .flatMap((table) => generateTableStructsForOperations(schemas.find((schema) => schema.name === table.schema), table, columnsByTableId[table.id], types, ["Select", "Insert", "Update"]))
18
+ .join("\n\n")}
19
+
20
+ ${views
21
+ .filter((view) => schemas.some((schema) => schema.name === view.schema))
22
+ .flatMap((view) => generateTableStructsForOperations(schemas.find((schema) => schema.name === view.schema), view, columnsByTableId[view.id], types, ["Select"]))
23
+ .join("\n\n")}
24
+
25
+ ${materializedViews
26
+ .filter((materializedView) => schemas.some((schema) => schema.name === materializedView.schema))
27
+ .flatMap((materializedView) => generateTableStructsForOperations(schemas.find((schema) => schema.name === materializedView.schema), materializedView, columnsByTableId[materializedView.id], types, ["Select"]))
28
+ .join("\n\n")}
29
+
30
+ ${compositeTypes
31
+ .filter((compositeType) => schemas.some((schema) => schema.name === compositeType.schema))
32
+ .map((compositeType) => generateCompositeTypeStruct(schemas.find((schema) => schema.name === compositeType.schema), compositeType, types))
33
+ .join("\n\n")}
34
+ `.trim();
35
+ return output;
36
+ };
37
+ /**
38
+ * Converts a Postgres name to PascalCase.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * formatForGoTypeName('pokedex') // Pokedex
43
+ * formatForGoTypeName('pokemon_center') // PokemonCenter
44
+ * formatForGoTypeName('victory-road') // VictoryRoad
45
+ * formatForGoTypeName('pokemon league') // PokemonLeague
46
+ * ```
47
+ */
48
+ function formatForGoTypeName(name) {
49
+ return name
50
+ .split(/[^a-zA-Z0-9]/)
51
+ .map((word) => {
52
+ if (word) {
53
+ return `${word[0].toUpperCase()}${word.slice(1)}`;
54
+ }
55
+ else {
56
+ return "";
57
+ }
58
+ })
59
+ .join("");
60
+ }
61
+ function generateTableStruct(schema, table, columns, types, operation) {
62
+ // Storing columns as a tuple of [formattedName, type, name] rather than creating the string
63
+ // representation of the line allows us to pre-format the entries. Go formats
64
+ // struct fields to be aligned, e.g.:
65
+ // ```go
66
+ // type Pokemon struct {
67
+ // id int `json:"id"`
68
+ // name string `json:"name"`
69
+ // }
70
+ const columnEntries = columns?.map((column) => {
71
+ let nullable;
72
+ if (operation === "Insert") {
73
+ nullable =
74
+ column.is_nullable ||
75
+ column.is_identity ||
76
+ column.is_generated ||
77
+ !!column.default_value;
78
+ }
79
+ else if (operation === "Update") {
80
+ nullable = true;
81
+ }
82
+ else {
83
+ nullable = column.is_nullable;
84
+ }
85
+ return [
86
+ formatForGoTypeName(column.name),
87
+ pgTypeToGoType(column.format, nullable, types),
88
+ column.name,
89
+ ];
90
+ }) ?? [];
91
+ const [maxFormattedNameLength, maxTypeLength] = columnEntries.reduce(([maxFormattedName, maxType], [formattedName, type]) => {
92
+ return [
93
+ Math.max(maxFormattedName, formattedName.length),
94
+ Math.max(maxType, type.length),
95
+ ];
96
+ }, [0, 0]);
97
+ // Pad the formatted name and type to align the struct fields, then join
98
+ // create the final string representation of the struct fields.
99
+ const formattedColumnEntries = columnEntries.map(([formattedName, type, name]) => {
100
+ return ` ${formattedName.padEnd(maxFormattedNameLength)} ${type.padEnd(maxTypeLength)} \`json:"${name}"\``;
101
+ });
102
+ return `
103
+ type ${formatForGoTypeName(schema.name)}${formatForGoTypeName(table.name)}${operation} struct {
104
+ ${formattedColumnEntries.join("\n")}
105
+ }
106
+ `.trim();
107
+ }
108
+ function generateTableStructsForOperations(schema, table, columns, types, operations) {
109
+ return operations.map((operation) => generateTableStruct(schema, table, columns, types, operation));
110
+ }
111
+ function generateCompositeTypeStruct(schema, type, types) {
112
+ // Use the type_id of the attributes to find the types of the attributes
113
+ const typeWithRetrievedAttributes = {
114
+ ...type,
115
+ attributes: type.attributes.map((attribute) => {
116
+ const type = types.find((type) => type.id === attribute.type_id);
117
+ return {
118
+ ...attribute,
119
+ type,
120
+ };
121
+ }),
122
+ };
123
+ const attributeEntries = typeWithRetrievedAttributes.attributes.map((attribute) => [
124
+ formatForGoTypeName(attribute.name),
125
+ pgTypeToGoType(attribute.type.format, false),
126
+ attribute.name,
127
+ ]);
128
+ const [maxFormattedNameLength, maxTypeLength] = attributeEntries.reduce(([maxFormattedName, maxType], [formattedName, type]) => {
129
+ return [
130
+ Math.max(maxFormattedName, formattedName.length),
131
+ Math.max(maxType, type.length),
132
+ ];
133
+ }, [0, 0]);
134
+ // Pad the formatted name and type to align the struct fields, then join
135
+ // create the final string representation of the struct fields.
136
+ const formattedAttributeEntries = attributeEntries.map(([formattedName, type, name]) => {
137
+ return ` ${formattedName.padEnd(maxFormattedNameLength)} ${type.padEnd(maxTypeLength)} \`json:"${name}"\``;
138
+ });
139
+ return `
140
+ type ${formatForGoTypeName(schema.name)}${formatForGoTypeName(type.name)} struct {
141
+ ${formattedAttributeEntries.join("\n")}
142
+ }
143
+ `.trim();
144
+ }
145
+ // Note: the type map uses `interface{ } `, not `any`, to remain compatible with
146
+ // older versions of Go.
147
+ const GO_TYPE_MAP = {
148
+ // Bool
149
+ bool: "bool",
150
+ // Numbers
151
+ int2: "int16",
152
+ int4: "int32",
153
+ int8: "int64",
154
+ float4: "float32",
155
+ float8: "float64",
156
+ numeric: "float64",
157
+ // Strings
158
+ bytea: "[]byte",
159
+ bpchar: "string",
160
+ varchar: "string",
161
+ date: "string",
162
+ text: "string",
163
+ citext: "string",
164
+ time: "string",
165
+ timetz: "string",
166
+ timestamp: "string",
167
+ timestamptz: "string",
168
+ interval: "string",
169
+ uuid: "string",
170
+ vector: "string",
171
+ // JSON
172
+ json: "interface{}",
173
+ jsonb: "interface{}",
174
+ // Range
175
+ int4range: "string",
176
+ int4multirange: "string",
177
+ int8range: "string",
178
+ int8multirange: "string",
179
+ numrange: "string",
180
+ nummultirange: "string",
181
+ tsrange: "string",
182
+ tsmultirange: "string",
183
+ tstzrange: "string",
184
+ tstzmultirange: "string",
185
+ daterange: "string",
186
+ datemultirange: "string",
187
+ // Misc
188
+ void: "interface{}",
189
+ record: "map[string]interface{}",
190
+ };
191
+ const GO_NULLABLE_TYPE_MAP = {
192
+ string: "*string",
193
+ bool: "*bool",
194
+ int16: "*int16",
195
+ int32: "*int32",
196
+ int64: "*int64",
197
+ float32: "*float32",
198
+ float64: "*float64",
199
+ "[]byte": "[]byte",
200
+ "interface{}": "interface{}",
201
+ "map[string]interface{}": "map[string]interface{}",
202
+ };
203
+ function pgTypeToGoType(pgType, nullable, types = []) {
204
+ let goType = undefined;
205
+ if (pgType in GO_TYPE_MAP) {
206
+ goType = GO_TYPE_MAP[pgType];
207
+ }
208
+ // Enums
209
+ const enumType = types.find((type) => type.name === pgType && type.enums.length > 0);
210
+ if (enumType) {
211
+ goType = "string";
212
+ }
213
+ if (goType) {
214
+ if (nullable) {
215
+ return GO_NULLABLE_TYPE_MAP[goType];
216
+ }
217
+ return goType;
218
+ }
219
+ // Composite types
220
+ const compositeType = types.find((type) => type.name === pgType && type.attributes.length > 0);
221
+ if (compositeType) {
222
+ // TODO: generate composite types
223
+ // return formatForGoTypeName(pgType)
224
+ return "map[string]interface{}";
225
+ }
226
+ // Arrays
227
+ if (pgType.startsWith("_")) {
228
+ const innerType = pgTypeToGoType(pgType.slice(1), nullable, types);
229
+ return `[]${innerType} `;
230
+ }
231
+ // Fallback
232
+ return "interface{}";
233
+ }
@@ -0,0 +1,5 @@
1
+ export { sortGeneratorMetadata } from "../sort.ts";
2
+ export { generateGo } from "./go.ts";
3
+ export { generatePython } from "./python.ts";
4
+ export { generateTypescript, type GenerateTypescriptOptions, pgTypeToTsType, } from "./typescript.ts";
5
+ export { type AccessControl, generateSwift, type GenerateSwiftOptions, } from "./swift.ts";
@@ -0,0 +1,5 @@
1
+ export { sortGeneratorMetadata } from "../sort.js";
2
+ export { generateGo } from "./go.js";
3
+ export { generatePython } from "./python.js";
4
+ export { generateTypescript, pgTypeToTsType, } from "./typescript.js";
5
+ export { generateSwift, } from "./swift.js";
@@ -0,0 +1,6 @@
1
+ import type { GeneratorMetadata } from "../types.ts";
2
+ /**
3
+ * Emits tables/views/materialized views in `GeneratorMetadata` order. Pass
4
+ * input pre-sorted with `sortGeneratorMetadata` for deterministic output.
5
+ */
6
+ export declare const generatePython: ({ schemas, tables, views, materializedViews, columns, types, }: GeneratorMetadata) => string;