@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
@@ -0,0 +1,461 @@
1
+ import type {
2
+ GeneratorMetadata,
3
+ PostgresColumn,
4
+ PostgresMaterializedView,
5
+ PostgresSchema,
6
+ PostgresTable,
7
+ PostgresType,
8
+ PostgresView,
9
+ } from "../types.ts";
10
+
11
+ /**
12
+ * Emits tables/views/materialized views in `GeneratorMetadata` order. Pass
13
+ * input pre-sorted with `sortGeneratorMetadata` for deterministic output.
14
+ */
15
+ export const generatePython = ({
16
+ schemas,
17
+ tables,
18
+ views,
19
+ materializedViews,
20
+ columns,
21
+ types,
22
+ }: GeneratorMetadata): string => {
23
+ const ctx = new PythonContext(types, columns, schemas);
24
+ // Used for efficient lookup of types by schema name
25
+ const schemasNames = new Set(schemas.map((schema) => schema.name));
26
+ const py_tables = tables.flatMap((table) => {
27
+ const py_class_and_methods = ctx.tableToClass(table);
28
+ return py_class_and_methods;
29
+ });
30
+ const composite_types = types
31
+ // We always include system schemas, so we need to filter out types that are not in the included schemas
32
+ .filter(
33
+ (type) => type.attributes.length > 0 && schemasNames.has(type.schema),
34
+ )
35
+ .map((type) => ctx.typeToClass(type));
36
+ const py_views = views.map((view) => ctx.viewToClass(view));
37
+ const py_matviews = materializedViews.map((matview) =>
38
+ ctx.matViewToClass(matview),
39
+ );
40
+
41
+ let output = `
42
+ from __future__ import annotations
43
+
44
+ import datetime
45
+ import uuid
46
+ from typing import (
47
+ Annotated,
48
+ Any,
49
+ List,
50
+ Literal,
51
+ NotRequired,
52
+ Optional,
53
+ TypeAlias,
54
+ TypedDict,
55
+ )
56
+
57
+ from pydantic import BaseModel, Field, Json
58
+
59
+ ${concatLines(Object.values(ctx.user_enums))}
60
+
61
+ ${concatLines(py_tables)}
62
+
63
+ ${concatLines(py_views)}
64
+
65
+ ${concatLines(py_matviews)}
66
+
67
+ ${concatLines(composite_types)}
68
+
69
+ `.trim();
70
+
71
+ return output;
72
+ };
73
+
74
+ interface Serializable {
75
+ serialize(): string;
76
+ }
77
+
78
+ class PythonContext {
79
+ types: { [k: string]: PostgresType };
80
+ user_enums: { [k: string]: PythonEnum };
81
+ columns: Record<number, PostgresColumn[]>;
82
+ schemas: { [k: string]: PostgresSchema };
83
+
84
+ constructor(
85
+ types: PostgresType[],
86
+ columns: PostgresColumn[],
87
+ schemas: PostgresSchema[],
88
+ ) {
89
+ this.schemas = Object.fromEntries(
90
+ schemas.map((schema) => [schema.name, schema]),
91
+ );
92
+ this.types = Object.fromEntries(types.map((type) => [type.name, type]));
93
+ this.columns = columns.reduce(
94
+ (acc, curr) => {
95
+ acc[curr.table_id] ??= [];
96
+ acc[curr.table_id].push(curr);
97
+ return acc;
98
+ },
99
+ {} as Record<number, PostgresColumn[]>,
100
+ );
101
+ this.user_enums = Object.fromEntries(
102
+ types
103
+ .filter((type) => type.enums.length > 0)
104
+ .map((type) => [type.name, new PythonEnum(type)]),
105
+ );
106
+ }
107
+
108
+ resolveTypeName(name: string): string {
109
+ if (name in this.user_enums) {
110
+ return this.user_enums[name].name;
111
+ }
112
+ if (name in PY_TYPE_MAP) {
113
+ return PY_TYPE_MAP[name];
114
+ }
115
+ if (name in this.types) {
116
+ const type = this.types[name];
117
+ const schema = type!.schema;
118
+ return `${formatForPyClassName(schema)}${formatForPyClassName(type.name)}`;
119
+ }
120
+ return "Any";
121
+ }
122
+
123
+ parsePgType(pg_type: string): PythonType {
124
+ if (pg_type.startsWith("_")) {
125
+ const inner_str = pg_type.slice(1);
126
+ const inner = this.parsePgType(inner_str);
127
+ return new PythonListType(inner);
128
+ } else {
129
+ const type_name = this.resolveTypeName(pg_type);
130
+ return new PythonSimpleType(type_name);
131
+ }
132
+ }
133
+
134
+ typeToClass(type: PostgresType): PythonBaseModel {
135
+ const types = Object.values(this.types);
136
+ const attributes = type.attributes.map((attribute) => {
137
+ const type = types.find((type) => type.id === attribute.type_id);
138
+ return {
139
+ ...attribute,
140
+ type,
141
+ };
142
+ });
143
+ const attributeEntries: PythonBaseModelAttr[] = attributes.map(
144
+ (attribute) => {
145
+ const type = this.parsePgType(attribute.type!.name);
146
+ return new PythonBaseModelAttr(attribute.name, type, false);
147
+ },
148
+ );
149
+
150
+ const schema = this.schemas[type.schema];
151
+ return new PythonBaseModel(type.name, schema, attributeEntries);
152
+ }
153
+
154
+ columnsToClassAttrs(table_id: number): PythonBaseModelAttr[] {
155
+ const attrs = this.columns[table_id] ?? [];
156
+ return attrs.map((col) => {
157
+ const type = this.parsePgType(col.format);
158
+ return new PythonBaseModelAttr(col.name, type, col.is_nullable);
159
+ });
160
+ }
161
+
162
+ columnsToDictAttrs(
163
+ table_id: number,
164
+ not_required: boolean,
165
+ ): PythonTypedDictAttr[] {
166
+ const attrs = this.columns[table_id] ?? [];
167
+ return attrs.map((col) => {
168
+ const type = this.parsePgType(col.format);
169
+ return new PythonTypedDictAttr(
170
+ col.name,
171
+ type,
172
+ col.is_nullable,
173
+ not_required ||
174
+ col.is_nullable ||
175
+ col.is_identity ||
176
+ col.default_value !== null,
177
+ );
178
+ });
179
+ }
180
+
181
+ tableToClass(
182
+ table: PostgresTable,
183
+ ): [PythonBaseModel, PythonTypedDict, PythonTypedDict] {
184
+ const schema = this.schemas[table.schema];
185
+ const select = new PythonBaseModel(
186
+ table.name,
187
+ schema,
188
+ this.columnsToClassAttrs(table.id),
189
+ );
190
+ const insert = new PythonTypedDict(
191
+ table.name,
192
+ "Insert",
193
+ schema,
194
+ this.columnsToDictAttrs(table.id, false),
195
+ );
196
+ const update = new PythonTypedDict(
197
+ table.name,
198
+ "Update",
199
+ schema,
200
+ this.columnsToDictAttrs(table.id, true),
201
+ );
202
+ return [select, insert, update];
203
+ }
204
+
205
+ viewToClass(view: PostgresView): PythonBaseModel {
206
+ const attributes = this.columnsToClassAttrs(view.id);
207
+ return new PythonBaseModel(
208
+ view.name,
209
+ this.schemas[view.schema],
210
+ attributes,
211
+ );
212
+ }
213
+
214
+ matViewToClass(matview: PostgresMaterializedView): PythonBaseModel {
215
+ const attributes = this.columnsToClassAttrs(matview.id);
216
+ return new PythonBaseModel(
217
+ matview.name,
218
+ this.schemas[matview.schema],
219
+ attributes,
220
+ );
221
+ }
222
+ }
223
+
224
+ class PythonEnum implements Serializable {
225
+ name: string;
226
+ variants: string[];
227
+ constructor(type: PostgresType) {
228
+ this.name = `${formatForPyClassName(type.schema)}${formatForPyClassName(type.name)}`;
229
+ this.variants = type.enums;
230
+ }
231
+ serialize(): string {
232
+ const variants = this.variants.map((item) => `"${item}"`).join(", ");
233
+ return `${this.name}: TypeAlias = Literal[${variants}]`;
234
+ }
235
+ }
236
+
237
+ type PythonType = PythonListType | PythonSimpleType;
238
+
239
+ class PythonSimpleType implements Serializable {
240
+ name: string;
241
+ constructor(name: string) {
242
+ this.name = name;
243
+ }
244
+ serialize(): string {
245
+ return this.name;
246
+ }
247
+ }
248
+
249
+ class PythonListType implements Serializable {
250
+ inner: PythonType;
251
+ constructor(inner: PythonType) {
252
+ this.inner = inner;
253
+ }
254
+ serialize(): string {
255
+ return `List[${this.inner.serialize()}]`;
256
+ }
257
+ }
258
+
259
+ class PythonBaseModelAttr implements Serializable {
260
+ name: string;
261
+ pg_name: string;
262
+ py_type: PythonType;
263
+ nullable: boolean;
264
+
265
+ constructor(name: string, py_type: PythonType, nullable: boolean) {
266
+ this.name = formatForPyAttributeName(name);
267
+ this.pg_name = name;
268
+ this.py_type = py_type;
269
+ this.nullable = nullable;
270
+ }
271
+
272
+ serialize(): string {
273
+ const py_type = this.nullable
274
+ ? `Optional[${this.py_type.serialize()}]`
275
+ : this.py_type.serialize();
276
+ return ` ${this.name}: ${py_type} = Field(alias="${this.pg_name}")`;
277
+ }
278
+ }
279
+
280
+ class PythonBaseModel implements Serializable {
281
+ name: string;
282
+ table_name: string;
283
+ schema: PostgresSchema;
284
+ class_attributes: PythonBaseModelAttr[];
285
+
286
+ constructor(
287
+ name: string,
288
+ schema: PostgresSchema,
289
+ class_attributes: PythonBaseModelAttr[],
290
+ ) {
291
+ this.schema = schema;
292
+ this.class_attributes = class_attributes;
293
+ this.table_name = name;
294
+ this.name = `${formatForPyClassName(schema.name)}${formatForPyClassName(name)}`;
295
+ }
296
+ serialize(): string {
297
+ const attributes =
298
+ this.class_attributes.length > 0
299
+ ? this.class_attributes.map((attr) => attr.serialize()).join("\n")
300
+ : " pass";
301
+ return `class ${this.name}(BaseModel):\n${attributes}`;
302
+ }
303
+ }
304
+
305
+ class PythonTypedDictAttr implements Serializable {
306
+ name: string;
307
+ pg_name: string;
308
+ py_type: PythonType;
309
+ nullable: boolean;
310
+ not_required: boolean;
311
+
312
+ constructor(
313
+ name: string,
314
+ py_type: PythonType,
315
+ nullable: boolean,
316
+ required: boolean,
317
+ ) {
318
+ this.name = formatForPyAttributeName(name);
319
+ this.pg_name = name;
320
+ this.py_type = py_type;
321
+ this.nullable = nullable;
322
+ this.not_required = required;
323
+ }
324
+
325
+ serialize(): string {
326
+ const py_type = this.nullable
327
+ ? `Optional[${this.py_type.serialize()}]`
328
+ : this.py_type.serialize();
329
+ const annotation = `Annotated[${py_type}, Field(alias="${this.pg_name}")]`;
330
+ const rhs = this.not_required ? `NotRequired[${annotation}]` : annotation;
331
+ return ` ${this.name}: ${rhs}`;
332
+ }
333
+ }
334
+
335
+ class PythonTypedDict implements Serializable {
336
+ name: string;
337
+ table_name: string;
338
+ parent_class: string;
339
+ schema: PostgresSchema;
340
+ dict_attributes: PythonTypedDictAttr[];
341
+ operation: "Insert" | "Update";
342
+
343
+ constructor(
344
+ name: string,
345
+ operation: "Insert" | "Update",
346
+ schema: PostgresSchema,
347
+ dict_attributes: PythonTypedDictAttr[],
348
+ parent_class: string = "BaseModel",
349
+ ) {
350
+ this.schema = schema;
351
+ this.dict_attributes = dict_attributes;
352
+ this.table_name = name;
353
+ this.name = `${formatForPyClassName(schema.name)}${formatForPyClassName(name)}`;
354
+ this.parent_class = parent_class;
355
+ this.operation = operation;
356
+ }
357
+ serialize(): string {
358
+ const attributes =
359
+ this.dict_attributes.length > 0
360
+ ? this.dict_attributes.map((attr) => attr.serialize()).join("\n")
361
+ : " pass";
362
+ return `class ${this.name}${this.operation}(TypedDict):\n${attributes}`;
363
+ }
364
+ }
365
+
366
+ function concatLines(items: Serializable[]): string {
367
+ return items.map((item) => item.serialize()).join("\n\n");
368
+ }
369
+
370
+ const PY_TYPE_MAP: Record<string, string> = {
371
+ // Bool
372
+ bool: "bool",
373
+
374
+ // Numbers
375
+ int2: "int",
376
+ int4: "int",
377
+ int8: "int",
378
+ float4: "float",
379
+ float8: "float",
380
+ numeric: "float",
381
+
382
+ // Strings
383
+ bytea: "bytes",
384
+ bpchar: "str",
385
+ varchar: "str",
386
+ string: "str",
387
+ date: "datetime.date",
388
+ text: "str",
389
+ citext: "str",
390
+ time: "datetime.time",
391
+ timetz: "datetime.time",
392
+ timestamp: "datetime.datetime",
393
+ timestamptz: "datetime.datetime",
394
+ uuid: "uuid.UUID",
395
+ vector: "list[Any]",
396
+ interval: "str",
397
+
398
+ // JSON
399
+ json: "Json[Any]",
400
+ jsonb: "Json[Any]",
401
+
402
+ // Range types (can be adjusted to more complex types if needed)
403
+ int4range: "str",
404
+ int4multirange: "str",
405
+ int8range: "str",
406
+ int8multirange: "str",
407
+ numrange: "str",
408
+ nummultirange: "str",
409
+ tsrange: "str",
410
+ tsmultirange: "str",
411
+ tstzrange: "str",
412
+ tstzmultirange: "str",
413
+ daterange: "str",
414
+ datemultirange: "str",
415
+
416
+ // Miscellaneous types
417
+ void: "None",
418
+ record: "dict[str, Any]",
419
+ } as const;
420
+
421
+ /**
422
+ * Converts a Postgres name to PascalCase.
423
+ *
424
+ * @example
425
+ * ```ts
426
+ * formatForPyTypeName('pokedex') // Pokedex
427
+ * formatForPyTypeName('pokemon_center') // PokemonCenter
428
+ * formatForPyTypeName('victory-road') // VictoryRoad
429
+ * formatForPyTypeName('pokemon league') // PokemonLeague
430
+ * ```
431
+ */
432
+
433
+ function formatForPyClassName(name: string): string {
434
+ return name
435
+ .split(/[^a-zA-Z0-9]/)
436
+ .map((word) => {
437
+ if (word) {
438
+ return `${word[0].toUpperCase()}${word.slice(1)}`;
439
+ } else {
440
+ return "";
441
+ }
442
+ })
443
+ .join("");
444
+ }
445
+ /**
446
+ * Converts a Postgres name to snake_case.
447
+ *
448
+ * @example
449
+ * ```ts
450
+ * formatForPyTypeName('Pokedex') // pokedex
451
+ * formatForPyTypeName('PokemonCenter') // pokemon_enter
452
+ * formatForPyTypeName('victory-road') // victory_road
453
+ * formatForPyTypeName('pokemon league') // pokemon_league
454
+ * ```
455
+ */
456
+ function formatForPyAttributeName(name: string): string {
457
+ return name
458
+ .split(/[^a-zA-Z0-9]+/) // Split on non-alphanumeric characters (like spaces, dashes, etc.)
459
+ .map((word) => word.toLowerCase()) // Convert each word to lowercase
460
+ .join("_"); // Join with underscores
461
+ }