@supabase/postgrest-typegen 0.2.0 → 0.2.2
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 +12 -0
- package/dist/collation.d.ts +2 -0
- package/dist/collation.js +15 -0
- package/dist/generation/go.js +30 -2
- package/dist/generation/index.d.ts +1 -1
- package/dist/generation/python.js +24 -10
- package/dist/generation/swift.js +52 -3
- package/dist/generation/typescript.d.ts +18 -7
- package/dist/generation/typescript.js +122 -82
- package/dist/introspection/index.js +6 -23
- package/dist/introspection/relationships.d.ts +4 -4
- package/dist/introspection/relationships.js +4 -11
- package/dist/introspection/sql/columns.sql.d.ts +2 -7
- package/dist/introspection/sql/columns.sql.js +32 -9
- package/dist/introspection/sql/common.d.ts +7 -15
- package/dist/introspection/sql/foreign_tables.sql.d.ts +2 -6
- package/dist/introspection/sql/foreign_tables.sql.js +0 -5
- package/dist/introspection/sql/functions.sql.d.ts +2 -5
- package/dist/introspection/sql/functions.sql.js +0 -33
- package/dist/introspection/sql/helpers.d.ts +1 -1
- package/dist/introspection/sql/helpers.js +2 -2
- package/dist/introspection/sql/materialized_views.sql.d.ts +2 -4
- package/dist/introspection/sql/materialized_views.sql.js +0 -5
- package/dist/introspection/sql/pg-format.d.ts +21 -0
- package/dist/introspection/sql/pg-format.js +92 -0
- package/dist/introspection/sql/primary_keys.sql.d.ts +2 -4
- package/dist/introspection/sql/primary_keys.sql.js +0 -4
- package/dist/introspection/sql/schemas.sql.d.ts +2 -6
- package/dist/introspection/sql/schemas.sql.js +2 -6
- package/dist/introspection/sql/table.sql.d.ts +2 -4
- package/dist/introspection/sql/table.sql.js +0 -5
- package/dist/introspection/sql/table_relationships.sql.d.ts +2 -2
- package/dist/introspection/sql/types.sql.d.ts +5 -5
- package/dist/introspection/sql/types.sql.js +6 -17
- package/dist/introspection/sql/views.sql.d.ts +2 -4
- package/dist/introspection/sql/views.sql.js +6 -5
- package/dist/introspection/sql/views_key_dependencies.sql.d.ts +2 -2
- package/dist/introspection/sql/views_key_dependencies.sql.js +64 -75
- package/dist/sort.js +16 -15
- package/dist/types.d.ts +4 -0
- package/dist/types.js +11 -0
- package/package.json +3 -5
- package/src/collation.ts +16 -0
- package/src/generation/go.ts +31 -2
- package/src/generation/index.ts +1 -0
- package/src/generation/python.ts +25 -10
- package/src/generation/swift.ts +53 -3
- package/src/generation/typescript.ts +187 -107
- package/src/introspection/index.ts +7 -32
- package/src/introspection/relationships.ts +10 -19
- package/src/introspection/sql/columns.sql.ts +34 -18
- package/src/introspection/sql/common.ts +7 -17
- package/src/introspection/sql/foreign_tables.sql.ts +2 -13
- package/src/introspection/sql/functions.sql.ts +2 -44
- package/src/introspection/sql/helpers.ts +2 -2
- package/src/introspection/sql/materialized_views.sql.ts +2 -11
- package/src/introspection/sql/pg-format.ts +101 -0
- package/src/introspection/sql/primary_keys.sql.ts +2 -10
- package/src/introspection/sql/schemas.sql.ts +4 -14
- package/src/introspection/sql/table.sql.ts +2 -11
- package/src/introspection/sql/table_relationships.sql.ts +2 -4
- package/src/introspection/sql/types.sql.ts +6 -26
- package/src/introspection/sql/views.sql.ts +8 -11
- package/src/introspection/sql/views_key_dependencies.sql.ts +83 -77
- package/src/sort.ts +20 -15
- package/src/types.ts +11 -0
package/dist/types.js
CHANGED
|
@@ -136,6 +136,17 @@ const postgresViewSchema = type({
|
|
|
136
136
|
schema: "string",
|
|
137
137
|
name: "string",
|
|
138
138
|
is_updatable: "boolean",
|
|
139
|
+
/**
|
|
140
|
+
* Whether INSERT works through this view: auto-updatable per
|
|
141
|
+
* `pg_relation_is_updatable`, or made insertable by an INSTEAD OF INSERT
|
|
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.
|
|
146
|
+
*/
|
|
147
|
+
"is_insert_enabled?": "boolean",
|
|
148
|
+
/** Same as `is_insert_enabled`, for UPDATE. */
|
|
149
|
+
"is_update_enabled?": "boolean",
|
|
139
150
|
comment: "string | null",
|
|
140
151
|
"columns?": postgresColumnSchema.array(),
|
|
141
152
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supabase/postgrest-typegen",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Type generation for PostgREST from PostgreSQL schemas",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codegen",
|
|
@@ -62,8 +62,7 @@
|
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"arktype": "2.2.3",
|
|
65
|
-
"oxfmt": "0.
|
|
66
|
-
"pg-format": "1.0.4"
|
|
65
|
+
"oxfmt": "0.66.0"
|
|
67
66
|
},
|
|
68
67
|
"devDependencies": {
|
|
69
68
|
"@testcontainers/postgresql": "^12.1.0",
|
|
@@ -72,9 +71,8 @@
|
|
|
72
71
|
"@types/bun": "^1.4.0",
|
|
73
72
|
"@types/node": "^26.3.0",
|
|
74
73
|
"@types/pg": "^8.23.1",
|
|
75
|
-
"@types/pg-format": "^1.0.5",
|
|
76
74
|
"knip": "^6.32.3",
|
|
77
|
-
"oxlint": "1.
|
|
75
|
+
"oxlint": "1.81.0",
|
|
78
76
|
"oxlint-tsgolint": "7.0.2001",
|
|
79
77
|
"pg": "^8.23.0",
|
|
80
78
|
"testcontainers": "^12.1.0",
|
package/src/collation.ts
ADDED
|
@@ -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
|
+
}
|
package/src/generation/go.ts
CHANGED
|
@@ -118,6 +118,35 @@ function formatForGoTypeName(name: string): string {
|
|
|
118
118
|
.join("");
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Renders the `json` struct tag for a column or composite attribute name.
|
|
123
|
+
*
|
|
124
|
+
* The name is quoted with `JSON.stringify`, whose escape sequences are a
|
|
125
|
+
* subset of Go's, so `reflect.StructTag.Get("json")` recovers the exact name
|
|
126
|
+
* via `strconv.Unquote` even when it contains quotes, backslashes or control
|
|
127
|
+
* characters. The tag is emitted as a raw string literal like before, except
|
|
128
|
+
* when the name contains a backtick: Go raw literals cannot contain one, so
|
|
129
|
+
* such tags fall back to an interpreted literal with a second round of
|
|
130
|
+
* escaping. Names the marshaler cannot represent still compile and
|
|
131
|
+
* round-trip through `reflect.StructTag`, but `encoding/json` applies its
|
|
132
|
+
* own tag rules at runtime: a comma splits the name from tag options (so
|
|
133
|
+
* only the part before it is used as the JSON name), and a name it rejects
|
|
134
|
+
* outright (for example one containing a quote) falls back to the Go field
|
|
135
|
+
* name. That is a limitation of the struct tag convention itself, not of
|
|
136
|
+
* the generated source.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* formatForGoStructTag('name') // `json:"name"`
|
|
141
|
+
* formatForGoStructTag('a"b') // `json:"a\"b"`
|
|
142
|
+
* formatForGoStructTag('back`tick') // "json:\"back`tick\""
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
function formatForGoStructTag(name: string): string {
|
|
146
|
+
const tag = `json:${JSON.stringify(name)}`;
|
|
147
|
+
return tag.includes("`") ? JSON.stringify(tag) : `\`${tag}\``;
|
|
148
|
+
}
|
|
149
|
+
|
|
121
150
|
function generateTableStruct(
|
|
122
151
|
schema: PostgresSchema,
|
|
123
152
|
table: PostgresTable | PostgresView | PostgresMaterializedView,
|
|
@@ -170,7 +199,7 @@ function generateTableStruct(
|
|
|
170
199
|
([formattedName, type, name]) => {
|
|
171
200
|
return ` ${formattedName.padEnd(maxFormattedNameLength)} ${type.padEnd(
|
|
172
201
|
maxTypeLength,
|
|
173
|
-
)}
|
|
202
|
+
)} ${formatForGoStructTag(name)}`;
|
|
174
203
|
},
|
|
175
204
|
);
|
|
176
205
|
|
|
@@ -232,7 +261,7 @@ function generateCompositeTypeStruct(
|
|
|
232
261
|
([formattedName, type, name]) => {
|
|
233
262
|
return ` ${formattedName.padEnd(maxFormattedNameLength)} ${type.padEnd(
|
|
234
263
|
maxTypeLength,
|
|
235
|
-
)}
|
|
264
|
+
)} ${formatForGoStructTag(name)}`;
|
|
236
265
|
},
|
|
237
266
|
);
|
|
238
267
|
|
package/src/generation/index.ts
CHANGED
package/src/generation/python.ts
CHANGED
|
@@ -48,13 +48,12 @@ from typing import (
|
|
|
48
48
|
Any,
|
|
49
49
|
List,
|
|
50
50
|
Literal,
|
|
51
|
-
NotRequired,
|
|
52
51
|
Optional,
|
|
53
|
-
TypeAlias,
|
|
54
52
|
TypedDict,
|
|
55
53
|
)
|
|
54
|
+
from typing_extensions import NotRequired, TypeAlias
|
|
56
55
|
|
|
57
|
-
from pydantic import BaseModel, Field,
|
|
56
|
+
from pydantic import BaseModel, Field, JsonValue
|
|
58
57
|
|
|
59
58
|
${concatLines(Object.values(ctx.user_enums))}
|
|
60
59
|
|
|
@@ -143,7 +142,9 @@ class PythonContext {
|
|
|
143
142
|
const attributeEntries: PythonBaseModelAttr[] = attributes.map(
|
|
144
143
|
(attribute) => {
|
|
145
144
|
const type = this.parsePgType(attribute.type!.name);
|
|
146
|
-
|
|
145
|
+
// Composite type attributes cannot carry NOT NULL constraints in
|
|
146
|
+
// Postgres, so every field is inherently nullable.
|
|
147
|
+
return new PythonBaseModelAttr(attribute.name, type, true);
|
|
147
148
|
},
|
|
148
149
|
);
|
|
149
150
|
|
|
@@ -229,7 +230,9 @@ class PythonEnum implements Serializable {
|
|
|
229
230
|
this.variants = type.enums;
|
|
230
231
|
}
|
|
231
232
|
serialize(): string {
|
|
232
|
-
const variants = this.variants
|
|
233
|
+
const variants = this.variants
|
|
234
|
+
.map((item) => escapePythonString(item))
|
|
235
|
+
.join(", ");
|
|
233
236
|
return `${this.name}: TypeAlias = Literal[${variants}]`;
|
|
234
237
|
}
|
|
235
238
|
}
|
|
@@ -273,7 +276,7 @@ class PythonBaseModelAttr implements Serializable {
|
|
|
273
276
|
const py_type = this.nullable
|
|
274
277
|
? `Optional[${this.py_type.serialize()}]`
|
|
275
278
|
: this.py_type.serialize();
|
|
276
|
-
return ` ${this.name}: ${py_type} = Field(alias
|
|
279
|
+
return ` ${this.name}: ${py_type} = Field(alias=${escapePythonString(this.pg_name)})`;
|
|
277
280
|
}
|
|
278
281
|
}
|
|
279
282
|
|
|
@@ -326,7 +329,7 @@ class PythonTypedDictAttr implements Serializable {
|
|
|
326
329
|
const py_type = this.nullable
|
|
327
330
|
? `Optional[${this.py_type.serialize()}]`
|
|
328
331
|
: this.py_type.serialize();
|
|
329
|
-
const annotation = `Annotated[${py_type}, Field(alias
|
|
332
|
+
const annotation = `Annotated[${py_type}, Field(alias=${escapePythonString(this.pg_name)})]`;
|
|
330
333
|
const rhs = this.not_required ? `NotRequired[${annotation}]` : annotation;
|
|
331
334
|
return ` ${this.name}: ${rhs}`;
|
|
332
335
|
}
|
|
@@ -367,6 +370,16 @@ function concatLines(items: Serializable[]): string {
|
|
|
367
370
|
return items.map((item) => item.serialize()).join("\n\n");
|
|
368
371
|
}
|
|
369
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Emits a Postgres name as a double-quoted Python string literal. JSON string
|
|
375
|
+
* escaping is a subset of Python's (`\"`, `\\`, `\n`, `\uXXXX`), so
|
|
376
|
+
* `JSON.stringify` yields a literal Python parses to the exact original name
|
|
377
|
+
* and keeps quotes, backslashes and newlines from breaking out of it.
|
|
378
|
+
*/
|
|
379
|
+
function escapePythonString(value: string): string {
|
|
380
|
+
return JSON.stringify(value);
|
|
381
|
+
}
|
|
382
|
+
|
|
370
383
|
const PY_TYPE_MAP: Record<string, string> = {
|
|
371
384
|
// Bool
|
|
372
385
|
bool: "bool",
|
|
@@ -395,9 +408,11 @@ const PY_TYPE_MAP: Record<string, string> = {
|
|
|
395
408
|
vector: "list[Any]",
|
|
396
409
|
interval: "str",
|
|
397
410
|
|
|
398
|
-
// JSON
|
|
399
|
-
|
|
400
|
-
|
|
411
|
+
// JSON. PostgREST returns these columns already deserialized, so the
|
|
412
|
+
// generated models must accept parsed values (JsonValue), not JSON strings
|
|
413
|
+
// (which is what pydantic's Json[...] validates and parses).
|
|
414
|
+
json: "JsonValue",
|
|
415
|
+
jsonb: "JsonValue",
|
|
401
416
|
|
|
402
417
|
// Range types (can be adjusted to more complex types if needed)
|
|
403
418
|
int4range: "str",
|
package/src/generation/swift.ts
CHANGED
|
@@ -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";
|
|
@@ -174,6 +175,49 @@ function generateProtocolConformances(protocols: string[]): string {
|
|
|
174
175
|
return protocols.length === 0 ? "" : `: ${protocols.join(", ")}`;
|
|
175
176
|
}
|
|
176
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Serializes a database-provided value as a Swift string literal. Backslashes
|
|
180
|
+
* would otherwise start escape or interpolation sequences (`\(`), double
|
|
181
|
+
* quotes would terminate the literal early, and line breaks or control
|
|
182
|
+
* characters are not allowed in single-line literals, so all of them are
|
|
183
|
+
* escaped per Swift's string literal grammar. U+2028 and U+2029 are legal in
|
|
184
|
+
* Swift literals but are escaped anyway because other tooling mishandles
|
|
185
|
+
* them. Swift unescapes at compile time, so raw values round-trip unchanged.
|
|
186
|
+
*/
|
|
187
|
+
function swiftStringLiteral(value: string): string {
|
|
188
|
+
let escaped = "";
|
|
189
|
+
for (const character of value) {
|
|
190
|
+
switch (character) {
|
|
191
|
+
case "\\":
|
|
192
|
+
escaped += "\\\\";
|
|
193
|
+
break;
|
|
194
|
+
case '"':
|
|
195
|
+
escaped += '\\"';
|
|
196
|
+
break;
|
|
197
|
+
case "\t":
|
|
198
|
+
escaped += "\\t";
|
|
199
|
+
break;
|
|
200
|
+
case "\n":
|
|
201
|
+
escaped += "\\n";
|
|
202
|
+
break;
|
|
203
|
+
case "\r":
|
|
204
|
+
escaped += "\\r";
|
|
205
|
+
break;
|
|
206
|
+
default: {
|
|
207
|
+
const codePoint = character.codePointAt(0)!;
|
|
208
|
+
escaped +=
|
|
209
|
+
codePoint < 0x20 ||
|
|
210
|
+
codePoint === 0x7f ||
|
|
211
|
+
codePoint === 0x2028 ||
|
|
212
|
+
codePoint === 0x2029
|
|
213
|
+
? `\\u{${codePoint.toString(16)}}`
|
|
214
|
+
: character;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return `"${escaped}"`;
|
|
219
|
+
}
|
|
220
|
+
|
|
177
221
|
function generateEnum(
|
|
178
222
|
enum_: SwiftEnum,
|
|
179
223
|
{ accessControl, level }: SwiftGeneratorOptions & { level: number },
|
|
@@ -184,7 +228,7 @@ function generateEnum(
|
|
|
184
228
|
)} {`,
|
|
185
229
|
...enum_.cases.map(
|
|
186
230
|
(case_) =>
|
|
187
|
-
`${ident(level + 1)}case ${case_.formattedName} =
|
|
231
|
+
`${ident(level + 1)}case ${case_.formattedName} = ${swiftStringLiteral(case_.rawValue)}`,
|
|
188
232
|
),
|
|
189
233
|
`${ident(level)}}`,
|
|
190
234
|
];
|
|
@@ -270,11 +314,11 @@ export const generateSwift = (
|
|
|
270
314
|
...schemas.flatMap((schema) => {
|
|
271
315
|
const schemaTables = [...tables, ...foreignTables]
|
|
272
316
|
.filter((table) => table.schema === schema.name)
|
|
273
|
-
.sort(({ name: a }, { name: b }) => a
|
|
317
|
+
.sort(({ name: a }, { name: b }) => compareStrings(a, b));
|
|
274
318
|
|
|
275
319
|
const schemaViews = [...views, ...materializedViews]
|
|
276
320
|
.filter((table) => table.schema === schema.name)
|
|
277
|
-
.sort(({ name: a }, { name: b }) => a
|
|
321
|
+
.sort(({ name: a }, { name: b }) => compareStrings(a, b));
|
|
278
322
|
|
|
279
323
|
const schemaEnums = types.filter(
|
|
280
324
|
(type) => type.schema === schema.name && type.enums.length > 0,
|
|
@@ -381,6 +425,12 @@ const pgTypeToSwiftType = (
|
|
|
381
425
|
swiftType = "String";
|
|
382
426
|
} else if (["json", "jsonb"].includes(pgType)) {
|
|
383
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.
|
|
384
434
|
} else if (pgType === "void") {
|
|
385
435
|
swiftType = "Void";
|
|
386
436
|
} else if (pgType === "record") {
|