@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.
Files changed (66) hide show
  1. package/README.md +12 -0
  2. package/dist/collation.d.ts +2 -0
  3. package/dist/collation.js +15 -0
  4. package/dist/generation/go.js +30 -2
  5. package/dist/generation/index.d.ts +1 -1
  6. package/dist/generation/python.js +24 -10
  7. package/dist/generation/swift.js +52 -3
  8. package/dist/generation/typescript.d.ts +18 -7
  9. package/dist/generation/typescript.js +122 -82
  10. package/dist/introspection/index.js +6 -23
  11. package/dist/introspection/relationships.d.ts +4 -4
  12. package/dist/introspection/relationships.js +4 -11
  13. package/dist/introspection/sql/columns.sql.d.ts +2 -7
  14. package/dist/introspection/sql/columns.sql.js +32 -9
  15. package/dist/introspection/sql/common.d.ts +7 -15
  16. package/dist/introspection/sql/foreign_tables.sql.d.ts +2 -6
  17. package/dist/introspection/sql/foreign_tables.sql.js +0 -5
  18. package/dist/introspection/sql/functions.sql.d.ts +2 -5
  19. package/dist/introspection/sql/functions.sql.js +0 -33
  20. package/dist/introspection/sql/helpers.d.ts +1 -1
  21. package/dist/introspection/sql/helpers.js +2 -2
  22. package/dist/introspection/sql/materialized_views.sql.d.ts +2 -4
  23. package/dist/introspection/sql/materialized_views.sql.js +0 -5
  24. package/dist/introspection/sql/pg-format.d.ts +21 -0
  25. package/dist/introspection/sql/pg-format.js +92 -0
  26. package/dist/introspection/sql/primary_keys.sql.d.ts +2 -4
  27. package/dist/introspection/sql/primary_keys.sql.js +0 -4
  28. package/dist/introspection/sql/schemas.sql.d.ts +2 -6
  29. package/dist/introspection/sql/schemas.sql.js +2 -6
  30. package/dist/introspection/sql/table.sql.d.ts +2 -4
  31. package/dist/introspection/sql/table.sql.js +0 -5
  32. package/dist/introspection/sql/table_relationships.sql.d.ts +2 -2
  33. package/dist/introspection/sql/types.sql.d.ts +5 -5
  34. package/dist/introspection/sql/types.sql.js +6 -17
  35. package/dist/introspection/sql/views.sql.d.ts +2 -4
  36. package/dist/introspection/sql/views.sql.js +6 -5
  37. package/dist/introspection/sql/views_key_dependencies.sql.d.ts +2 -2
  38. package/dist/introspection/sql/views_key_dependencies.sql.js +64 -75
  39. package/dist/sort.js +16 -15
  40. package/dist/types.d.ts +4 -0
  41. package/dist/types.js +11 -0
  42. package/package.json +3 -5
  43. package/src/collation.ts +16 -0
  44. package/src/generation/go.ts +31 -2
  45. package/src/generation/index.ts +1 -0
  46. package/src/generation/python.ts +25 -10
  47. package/src/generation/swift.ts +53 -3
  48. package/src/generation/typescript.ts +187 -107
  49. package/src/introspection/index.ts +7 -32
  50. package/src/introspection/relationships.ts +10 -19
  51. package/src/introspection/sql/columns.sql.ts +34 -18
  52. package/src/introspection/sql/common.ts +7 -17
  53. package/src/introspection/sql/foreign_tables.sql.ts +2 -13
  54. package/src/introspection/sql/functions.sql.ts +2 -44
  55. package/src/introspection/sql/helpers.ts +2 -2
  56. package/src/introspection/sql/materialized_views.sql.ts +2 -11
  57. package/src/introspection/sql/pg-format.ts +101 -0
  58. package/src/introspection/sql/primary_keys.sql.ts +2 -10
  59. package/src/introspection/sql/schemas.sql.ts +4 -14
  60. package/src/introspection/sql/table.sql.ts +2 -11
  61. package/src/introspection/sql/table_relationships.sql.ts +2 -4
  62. package/src/introspection/sql/types.sql.ts +6 -26
  63. package/src/introspection/sql/views.sql.ts +8 -11
  64. package/src/introspection/sql/views_key_dependencies.sql.ts +83 -77
  65. package/src/sort.ts +20 -15
  66. package/src/types.ts +11 -0
@@ -1,7 +1,8 @@
1
- import type { SQLQueryPropsWithSchemaFilter } from "./common.ts";
1
+ import type { SchemaFilterProps } from "./common.ts";
2
+ import { literal } from "./pg-format.ts";
2
3
 
3
4
  export const VIEWS_KEY_DEPENDENCIES_SQL = (
4
- props: SQLQueryPropsWithSchemaFilter,
5
+ props: SchemaFilterProps,
5
6
  ) => /* SQL */ `
6
7
  -- Adapted from
7
8
  -- https://github.com/PostgREST/postgrest/blob/f9f0f79fa914ac00c11fbf7f4c558e14821e67e2/src/PostgREST/SchemaCache.hs#L820
@@ -47,81 +48,7 @@ views as (
47
48
  transform_json as (
48
49
  select
49
50
  view_id, view_schema, view_name,
50
- -- the following formatting is without indentation on purpose
51
- -- to allow simple diffs, with less whitespace noise
52
- replace(
53
- replace(
54
- replace(
55
- replace(
56
- replace(
57
- replace(
58
- replace(
59
- regexp_replace(
60
- replace(
61
- replace(
62
- replace(
63
- replace(
64
- replace(
65
- replace(
66
- replace(
67
- replace(
68
- replace(
69
- replace(
70
- replace(
71
- view_definition::text,
72
- -- This conversion to json is heavily optimized for performance.
73
- -- The general idea is to use as few regexp_replace() calls as possible.
74
- -- Simple replace() is a lot faster, so we jump through some hoops
75
- -- to be able to use regexp_replace() only once.
76
- -- This has been tested against a huge schema with 250+ different views.
77
- -- The unit tests do NOT reflect all possible inputs. Be careful when changing this!
78
- -- -----------------------------------------------
79
- -- pattern | replacement | flags
80
- -- -----------------------------------------------
81
- -- <> in pg_node_tree is the same as null in JSON, but due to very poor performance of json_typeof
82
- -- we need to make this an empty array here to prevent json_array_elements from throwing an error
83
- -- when the targetList is null.
84
- -- We'll need to put it first, to make the node protection below work for node lists that start with
85
- -- null: (<> ..., too. This is the case for coldefexprs, when the first column does not have a default value.
86
- '<>' , '()'
87
- -- , is not part of the pg_node_tree format, but used in the regex.
88
- -- This removes all , that might be part of column names.
89
- ), ',' , ''
90
- -- The same applies for { and }, although those are used a lot in pg_node_tree.
91
- -- We remove the escaped ones, which might be part of column names again.
92
- ), E'\\\\{' , ''
93
- ), E'\\\\}' , ''
94
- -- The fields we need are formatted as json manually to protect them from the regex.
95
- ), ' :targetList ' , ',"targetList":'
96
- ), ' :resno ' , ',"resno":'
97
- ), ' :resorigtbl ' , ',"resorigtbl":'
98
- ), ' :resorigcol ' , ',"resorigcol":'
99
- -- Make the regex also match the node type, e.g. \`{QUERY ...\`, to remove it in one pass.
100
- ), '{' , '{ :'
101
- -- Protect node lists, which start with \`({\` or \`((\` from the greedy regex.
102
- -- The extra \`{\` is removed again later.
103
- ), '((' , '{(('
104
- ), '({' , '{({'
105
- -- This regex removes all unused fields to avoid the need to format all of them correctly.
106
- -- This leads to a smaller json result as well.
107
- -- Removal stops at \`,\` for used fields (see above) and \`}\` for the end of the current node.
108
- -- Nesting can't be parsed correctly with a regex, so we stop at \`{\` as well and
109
- -- add an empty key for the followig node.
110
- ), ' :[^}{,]+' , ',"":' , 'g'
111
- -- For performance, the regex also added those empty keys when hitting a \`,\` or \`}\`.
112
- -- Those are removed next.
113
- ), ',"":}' , '}'
114
- ), ',"":,' , ','
115
- -- This reverses the "node list protection" from above.
116
- ), '{(' , '('
117
- -- Every key above has been added with a \`,\` so far. The first key in an object doesn't need it.
118
- ), '{,' , '{'
119
- -- pg_node_tree has \`()\` around lists, but JSON uses \`[]\`
120
- ), '(' , '['
121
- ), ')' , ']'
122
- -- pg_node_tree has \` \` between list items, but JSON uses \`,\`
123
- ), ' ' , ','
124
- )::json as view_definition
51
+ ${nodeTreeToJson("view_definition::text")}::json as view_definition
125
52
  from views
126
53
  ),
127
54
  target_entries as(
@@ -197,3 +124,82 @@ group by sch.nspname, tbl.relname, rep.view_schema, rep.view_name, pks_fks.conn
197
124
  -- make sure we only return key for which all columns are referenced in the view - no partial PKs or FKs
198
125
  having ncol = array_length(array_agg(row(col.attname, view_columns) order by pks_fks.ord), 1)
199
126
  `;
127
+
128
+ /**
129
+ * One text rewrite of the `pg_node_tree` to JSON conversion: a `replace`, or
130
+ * a global `regexp_replace` when `isRegex` is set.
131
+ */
132
+ type NodeTreeRewrite = {
133
+ pattern: string;
134
+ replacement: string;
135
+ isRegex?: boolean;
136
+ };
137
+
138
+ /**
139
+ * The rewrites, innermost first, that turn the `pg_node_tree` text of a view
140
+ * definition into JSON exposing only `targetList`, `resno`, `resorigtbl` and
141
+ * `resorigcol`. Ported from PostgREST's schema cache query, which keeps them
142
+ * as plain `replace` calls with a single `regexp_replace` for speed; the
143
+ * order matters, and the comments carry PostgREST's reasoning.
144
+ */
145
+ const NODE_TREE_TO_JSON_REWRITES: readonly NodeTreeRewrite[] = [
146
+ // `<>` is pg_node_tree's null. json_typeof is too slow to special-case it,
147
+ // so it becomes an empty list, which also keeps the node protection below
148
+ // working for lists that start with null, such as coldefexprs whose first
149
+ // column has no default.
150
+ { pattern: "<>", replacement: "()" },
151
+ // `,`, `{` and `}` are used by the JSON conversion; drop the ones that may
152
+ // be part of column names (`{` and `}` are escaped there).
153
+ { pattern: ",", replacement: "" },
154
+ { pattern: "\\{", replacement: "" },
155
+ { pattern: "\\}", replacement: "" },
156
+ // Format the fields we need as JSON keys so the regex below leaves them.
157
+ { pattern: " :targetList ", replacement: ',"targetList":' },
158
+ { pattern: " :resno ", replacement: ',"resno":' },
159
+ { pattern: " :resorigtbl ", replacement: ',"resorigtbl":' },
160
+ { pattern: " :resorigcol ", replacement: ',"resorigcol":' },
161
+ // Make the regex also match the node type, e.g. `{QUERY ...`.
162
+ { pattern: "{", replacement: "{ :" },
163
+ // Protect node lists, which start with `({` or `((`, from the greedy regex.
164
+ // The extra `{` is removed again below.
165
+ { pattern: "((", replacement: "{((" },
166
+ { pattern: "({", replacement: "{({" },
167
+ // Remove every unused field. Removal stops at `,` for the kept fields, at
168
+ // `}` for the end of the node and at `{` for a nested node, leaving an empty
169
+ // key for the following node.
170
+ { pattern: " :[^}{,]+", replacement: ',"":', isRegex: true },
171
+ // The regex also added those empty keys before `}` and `,`.
172
+ { pattern: ',"":}', replacement: "}" },
173
+ { pattern: ',"":,', replacement: "," },
174
+ // Undo the node list protection.
175
+ { pattern: "{(", replacement: "(" },
176
+ // Every key was added with a leading `,`; the first key of an object does
177
+ // not need it.
178
+ { pattern: "{,", replacement: "{" },
179
+ // pg_node_tree lists use `( )` with space separated items; JSON uses `[ ]`
180
+ // with commas.
181
+ { pattern: "(", replacement: "[" },
182
+ { pattern: ")", replacement: "]" },
183
+ { pattern: " ", replacement: "," },
184
+ ];
185
+
186
+ /**
187
+ * Render {@link NODE_TREE_TO_JSON_REWRITES} applied to `expression` as nested
188
+ * SQL calls: the opening calls stacked, then each rewrite's arguments on the
189
+ * line that closes it.
190
+ */
191
+ function nodeTreeToJson(expression: string): string {
192
+ const lines = NODE_TREE_TO_JSON_REWRITES.toReversed().map(
193
+ (rewrite) => ` ${rewrite.isRegex ? "regexp_replace" : "replace"}(`,
194
+ );
195
+ lines.push(` ${expression},`);
196
+ NODE_TREE_TO_JSON_REWRITES.forEach((rewrite, index) => {
197
+ const flags = rewrite.isRegex ? ", 'g'" : "";
198
+ const closing =
199
+ index === NODE_TREE_TO_JSON_REWRITES.length - 1 ? ")" : "),";
200
+ lines.push(
201
+ ` ${literal(rewrite.pattern)}, ${literal(rewrite.replacement)}${flags}${closing}`,
202
+ );
203
+ });
204
+ return lines.join("\n");
205
+ }
package/src/sort.ts CHANGED
@@ -26,6 +26,7 @@
26
26
  * addressed by name (the generated type is an object), matching TypeScript.
27
27
  */
28
28
  import type { GeneratorMetadata, PostgresRelationship } from "./types.ts";
29
+ import { compareStrings } from "./collation.ts";
29
30
 
30
31
  // Relations (tables/views/materialized views/foreign tables) and types share a
31
32
  // (schema, name) identity within a database; `id` breaks any residual tie.
@@ -33,8 +34,8 @@ const bySchemaName = (
33
34
  a: { schema: string; name: string; id: number },
34
35
  b: { schema: string; name: string; id: number },
35
36
  ): number =>
36
- a.schema.localeCompare(b.schema) ||
37
- a.name.localeCompare(b.name) ||
37
+ compareStrings(a.schema, b.schema) ||
38
+ compareStrings(a.name, b.name) ||
38
39
  a.id - b.id;
39
40
 
40
41
  // Mirrors the TypeScript generator's historical `relationships.sort`.
@@ -42,9 +43,10 @@ const byRelationship = (
42
43
  a: PostgresRelationship,
43
44
  b: PostgresRelationship,
44
45
  ): number =>
45
- a.foreign_key_name.localeCompare(b.foreign_key_name) ||
46
- a.referenced_relation.localeCompare(b.referenced_relation) ||
47
- JSON.stringify(a.referenced_columns).localeCompare(
46
+ compareStrings(a.foreign_key_name, b.foreign_key_name) ||
47
+ compareStrings(a.referenced_relation, b.referenced_relation) ||
48
+ compareStrings(
49
+ JSON.stringify(a.referenced_columns),
48
50
  JSON.stringify(b.referenced_columns),
49
51
  );
50
52
 
@@ -60,7 +62,7 @@ export function sortGeneratorMetadata(
60
62
  return {
61
63
  version: metadata.version,
62
64
  schemas: [...metadata.schemas].sort(
63
- (a, b) => a.name.localeCompare(b.name) || a.id - b.id,
65
+ (a, b) => compareStrings(a.name, b.name) || a.id - b.id,
64
66
  ),
65
67
  tables: [...metadata.tables].sort(bySchemaName),
66
68
  foreignTables: [...metadata.foreignTables].sort(bySchemaName),
@@ -70,17 +72,17 @@ export function sortGeneratorMetadata(
70
72
  // (matches the TypeScript generator's per-table `columns.sort(by name)`).
71
73
  columns: [...metadata.columns].sort(
72
74
  (a, b) =>
73
- a.schema.localeCompare(b.schema) ||
74
- a.table.localeCompare(b.table) ||
75
- a.name.localeCompare(b.name),
75
+ compareStrings(a.schema, b.schema) ||
76
+ compareStrings(a.table, b.table) ||
77
+ compareStrings(a.name, b.name),
76
78
  ),
77
79
  // Grouped by table like `columns`; within a table the SQL's declared
78
80
  // column order (`array_position(indkey, attnum)`) is preserved by the
79
81
  // stable sort, since composite key order is meaningful.
80
82
  primaryKeys: [...metadata.primaryKeys].sort(
81
83
  (a, b) =>
82
- a.schema.localeCompare(b.schema) ||
83
- a.table_name.localeCompare(b.table_name),
84
+ compareStrings(a.schema, b.schema) ||
85
+ compareStrings(a.table_name, b.table_name),
84
86
  ),
85
87
  relationships: [...metadata.relationships].sort(byRelationship),
86
88
  // Functions can overload, so the signature is part of the identity. Args
@@ -88,14 +90,17 @@ export function sortGeneratorMetadata(
88
90
  functions: [...metadata.functions]
89
91
  .sort(
90
92
  (a, b) =>
91
- a.schema.localeCompare(b.schema) ||
92
- a.name.localeCompare(b.name) ||
93
- a.identity_argument_types.localeCompare(b.identity_argument_types) ||
93
+ compareStrings(a.schema, b.schema) ||
94
+ compareStrings(a.name, b.name) ||
95
+ compareStrings(
96
+ a.identity_argument_types,
97
+ b.identity_argument_types,
98
+ ) ||
94
99
  a.id - b.id,
95
100
  )
96
101
  .map((fn) => ({
97
102
  ...fn,
98
- args: [...fn.args].sort((a, b) => a.name.localeCompare(b.name)),
103
+ args: [...fn.args].sort((a, b) => compareStrings(a.name, b.name)),
99
104
  })),
100
105
  types: [...metadata.types].sort(bySchemaName),
101
106
  };
package/src/types.ts CHANGED
@@ -156,6 +156,17 @@ const postgresViewSchema = type({
156
156
  schema: "string",
157
157
  name: "string",
158
158
  is_updatable: "boolean",
159
+ /**
160
+ * Whether INSERT works through this view: auto-updatable per
161
+ * `pg_relation_is_updatable`, or made insertable by an INSTEAD OF INSERT
162
+ * trigger or an unconditional INSTEAD rule. Tracked separately from
163
+ * `is_updatable` (which mirrors `information_schema.views` and ignores
164
+ * triggers). Optional because version 1 metadata predating this field is
165
+ * still valid; consumers fall back to `is_updatable` when absent.
166
+ */
167
+ "is_insert_enabled?": "boolean",
168
+ /** Same as `is_insert_enabled`, for UPDATE. */
169
+ "is_update_enabled?": "boolean",
159
170
  comment: "string | null",
160
171
  "columns?": postgresColumnSchema.array(),
161
172
  });