@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,12 @@
1
+ /**
2
+ * Schemas that postgres-meta excludes by default when `includeSystemSchemas`
3
+ * is false. Ported from `postgres-meta/src/lib/constants.ts`.
4
+ */
5
+ export declare const DEFAULT_SYSTEM_SCHEMAS: string[];
6
+ /**
7
+ * Build a SQL `IN (...)` / `NOT IN (...)` fragment for an include/exclude list,
8
+ * ported verbatim from `postgres-meta/src/lib/helpers.ts`. Values are escaped
9
+ * with pg-format's `literal`. Returns an empty string when there is nothing to
10
+ * filter, so callers interpolate it conditionally.
11
+ */
12
+ export declare const filterByList: (include?: (string | number)[], exclude?: (string | number)[], defaultExclude?: (string | number)[]) => string;
@@ -0,0 +1,28 @@
1
+ import { literal } from "pg-format";
2
+ /**
3
+ * Schemas that postgres-meta excludes by default when `includeSystemSchemas`
4
+ * is false. Ported from `postgres-meta/src/lib/constants.ts`.
5
+ */
6
+ export const DEFAULT_SYSTEM_SCHEMAS = [
7
+ "information_schema",
8
+ "pg_catalog",
9
+ "pg_toast",
10
+ ];
11
+ /**
12
+ * Build a SQL `IN (...)` / `NOT IN (...)` fragment for an include/exclude list,
13
+ * ported verbatim from `postgres-meta/src/lib/helpers.ts`. Values are escaped
14
+ * with pg-format's `literal`. Returns an empty string when there is nothing to
15
+ * filter, so callers interpolate it conditionally.
16
+ */
17
+ export const filterByList = (include, exclude, defaultExclude) => {
18
+ if (defaultExclude) {
19
+ exclude = defaultExclude.concat(exclude ?? []);
20
+ }
21
+ if (include?.length) {
22
+ return `IN (${include.map(literal).join(",")})`;
23
+ }
24
+ else if (exclude?.length) {
25
+ return `NOT IN (${exclude.map(literal).join(",")})`;
26
+ }
27
+ return "";
28
+ };
@@ -0,0 +1,4 @@
1
+ import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from "./common.ts";
2
+ export declare const MATERIALIZED_VIEWS_SQL: (props: SQLQueryPropsWithSchemaFilterAndIdsFilter & {
3
+ materializedViewIdentifierFilter?: string;
4
+ }) => string;
@@ -0,0 +1,19 @@
1
+ import { literal } from "pg-format";
2
+ export const MATERIALIZED_VIEWS_SQL = (props) => /* SQL */ `
3
+ select
4
+ c.oid::int8 as id,
5
+ n.nspname as schema,
6
+ c.relname as name,
7
+ c.relispopulated as is_populated,
8
+ obj_description(c.oid) as comment
9
+ from
10
+ pg_class c
11
+ join pg_namespace n on n.oid = c.relnamespace
12
+ where
13
+ ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ""}
14
+ ${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ""}
15
+ ${props.materializedViewIdentifierFilter ? `(n.nspname || '.' || c.relname) ${props.materializedViewIdentifierFilter} AND` : ""}
16
+ c.relkind = 'm'
17
+ ${props.limit ? `limit ${literal(props.limit)}` : ""}
18
+ ${props.offset ? `offset ${literal(props.offset)}` : ""}
19
+ `;
@@ -0,0 +1,4 @@
1
+ import type { SQLQueryPropsWithSchemaFilter } from "./common.ts";
2
+ export declare const PRIMARY_KEYS_SQL: (props: SQLQueryPropsWithSchemaFilter & {
3
+ tableIdentifierFilter?: string;
4
+ }) => string;
@@ -0,0 +1,32 @@
1
+ import { literal } from "pg-format";
2
+ export const PRIMARY_KEYS_SQL = (props) => /* SQL */ `
3
+ SELECT
4
+ c.oid :: int8 AS table_id,
5
+ n.nspname AS schema,
6
+ c.relname AS table_name,
7
+ a.attname AS name
8
+ FROM
9
+ pg_index i
10
+ JOIN pg_class c ON i.indrelid = c.oid
11
+ JOIN pg_namespace n ON c.relnamespace = n.oid
12
+ JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(i.indkey)
13
+ WHERE
14
+ ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ""}
15
+ ${props.tableIdentifierFilter ? `n.nspname || '.' || c.relname ${props.tableIdentifierFilter} AND` : ""}
16
+ i.indisprimary
17
+ AND c.relkind IN ('r', 'p')
18
+ AND NOT pg_is_other_temp_schema(n.oid)
19
+ AND (
20
+ pg_has_role(c.relowner, 'USAGE')
21
+ OR has_table_privilege(
22
+ c.oid,
23
+ 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'
24
+ )
25
+ OR has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')
26
+ )
27
+ ORDER BY
28
+ c.oid,
29
+ array_position(i.indkey, a.attnum)
30
+ ${props.limit ? `limit ${literal(props.limit)}` : ""}
31
+ ${props.offset ? `offset ${literal(props.offset)}` : ""}
32
+ `;
@@ -0,0 +1,6 @@
1
+ import type { SQLQueryProps } from "./common.ts";
2
+ export declare const SCHEMAS_SQL: (props: SQLQueryProps & {
3
+ nameFilter?: string;
4
+ idsFilter?: string;
5
+ includeSystemSchemas?: boolean;
6
+ }) => string;
@@ -0,0 +1,24 @@
1
+ import { literal } from "pg-format";
2
+ export const SCHEMAS_SQL = (props) => /* SQL */ `
3
+ -- Adapted from information_schema.schemata
4
+ select
5
+ n.oid::int8 as id,
6
+ n.nspname as name,
7
+ u.rolname as owner
8
+ from
9
+ pg_namespace n,
10
+ pg_roles u
11
+ where
12
+ n.nspowner = u.oid
13
+ ${props.idsFilter ? `and n.oid ${props.idsFilter}` : ""}
14
+ ${props.nameFilter ? `and n.nspname ${props.nameFilter}` : ""}
15
+ ${!props.includeSystemSchemas ? `and not pg_catalog.starts_with(n.nspname, 'pg_')` : ""}
16
+ and (
17
+ pg_has_role(n.nspowner, 'USAGE')
18
+ or has_schema_privilege(n.oid, 'CREATE, USAGE')
19
+ )
20
+ and not pg_catalog.starts_with(n.nspname, 'pg_temp_')
21
+ and not pg_catalog.starts_with(n.nspname, 'pg_toast_temp_')
22
+ ${props.limit ? `limit ${literal(props.limit)}` : ""}
23
+ ${props.offset ? `offset ${literal(props.offset)}` : ""}
24
+ `;
@@ -0,0 +1,4 @@
1
+ import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from "./common.ts";
2
+ export declare const TABLES_SQL: (props: SQLQueryPropsWithSchemaFilterAndIdsFilter & {
3
+ tableIdentifierFilter?: string;
4
+ }) => string;
@@ -0,0 +1,41 @@
1
+ import { literal } from "pg-format";
2
+ export const TABLES_SQL = (props) => /* SQL */ `
3
+ SELECT
4
+ c.oid :: int8 AS id,
5
+ nc.nspname AS schema,
6
+ c.relname AS name,
7
+ c.relrowsecurity AS rls_enabled,
8
+ c.relforcerowsecurity AS rls_forced,
9
+ CASE
10
+ WHEN c.relreplident = 'd' THEN 'DEFAULT'
11
+ WHEN c.relreplident = 'i' THEN 'INDEX'
12
+ WHEN c.relreplident = 'f' THEN 'FULL'
13
+ ELSE 'NOTHING'
14
+ END AS replica_identity,
15
+ pg_total_relation_size(format('%I.%I', nc.nspname, c.relname)) :: int8 AS bytes,
16
+ pg_size_pretty(
17
+ pg_total_relation_size(format('%I.%I', nc.nspname, c.relname))
18
+ ) AS size,
19
+ pg_stat_get_live_tuples(c.oid) AS live_rows_estimate,
20
+ pg_stat_get_dead_tuples(c.oid) AS dead_rows_estimate,
21
+ obj_description(c.oid) AS comment
22
+ FROM
23
+ pg_namespace nc
24
+ JOIN pg_class c ON nc.oid = c.relnamespace
25
+ WHERE
26
+ ${props.schemaFilter ? `nc.nspname ${props.schemaFilter} AND` : ""}
27
+ ${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ""}
28
+ ${props.tableIdentifierFilter ? `nc.nspname || '.' || c.relname ${props.tableIdentifierFilter} AND` : ""}
29
+ c.relkind IN ('r', 'p')
30
+ AND NOT pg_is_other_temp_schema(nc.oid)
31
+ AND (
32
+ pg_has_role(c.relowner, 'USAGE')
33
+ OR has_table_privilege(
34
+ c.oid,
35
+ 'SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER'
36
+ )
37
+ OR has_any_column_privilege(c.oid, 'SELECT, INSERT, UPDATE, REFERENCES')
38
+ )
39
+ ${props.limit ? `limit ${literal(props.limit)}` : ""}
40
+ ${props.offset ? `offset ${literal(props.offset)}` : ""}
41
+ `;
@@ -0,0 +1,2 @@
1
+ import type { SQLQueryPropsWithSchemaFilter } from "./common.ts";
2
+ export declare const TABLE_RELATIONSHIPS_SQL: (props: SQLQueryPropsWithSchemaFilter) => string;
@@ -0,0 +1,49 @@
1
+ export const TABLE_RELATIONSHIPS_SQL = (props) => /* SQL */ `
2
+ -- Adapted from
3
+ -- https://github.com/PostgREST/postgrest/blob/f9f0f79fa914ac00c11fbf7f4c558e14821e67e2/src/PostgREST/SchemaCache.hs#L722
4
+ WITH
5
+ pks_uniques_cols AS (
6
+ SELECT
7
+ connamespace,
8
+ conrelid,
9
+ jsonb_agg(column_info.cols) as cols
10
+ FROM pg_constraint
11
+ JOIN lateral (
12
+ SELECT array_agg(cols.attname order by cols.attnum) as cols
13
+ FROM ( select unnest(conkey) as col) _
14
+ JOIN pg_attribute cols on cols.attrelid = conrelid and cols.attnum = col
15
+ ) column_info ON TRUE
16
+ WHERE
17
+ contype IN ('p', 'u') and
18
+ connamespace::regnamespace::text <> 'pg_catalog'
19
+ ${props.schemaFilter ? `and connamespace::regnamespace::text ${props.schemaFilter}` : ""}
20
+ GROUP BY connamespace, conrelid
21
+ )
22
+ SELECT
23
+ traint.conname AS foreign_key_name,
24
+ ns1.nspname AS schema,
25
+ tab.relname AS relation,
26
+ column_info.cols AS columns,
27
+ ns2.nspname AS referenced_schema,
28
+ other.relname AS referenced_relation,
29
+ column_info.refs AS referenced_columns,
30
+ (column_info.cols IN (SELECT * FROM jsonb_array_elements(pks_uqs.cols))) AS is_one_to_one
31
+ FROM pg_constraint traint
32
+ JOIN LATERAL (
33
+ SELECT
34
+ jsonb_agg(cols.attname order by ord) AS cols,
35
+ jsonb_agg(refs.attname order by ord) AS refs
36
+ FROM unnest(traint.conkey, traint.confkey) WITH ORDINALITY AS _(col, ref, ord)
37
+ JOIN pg_attribute cols ON cols.attrelid = traint.conrelid AND cols.attnum = col
38
+ JOIN pg_attribute refs ON refs.attrelid = traint.confrelid AND refs.attnum = ref
39
+ WHERE ${props.schemaFilter ? `traint.connamespace::regnamespace::text ${props.schemaFilter}` : "true"}
40
+ ) AS column_info ON TRUE
41
+ JOIN pg_namespace ns1 ON ns1.oid = traint.connamespace
42
+ JOIN pg_class tab ON tab.oid = traint.conrelid
43
+ JOIN pg_class other ON other.oid = traint.confrelid
44
+ JOIN pg_namespace ns2 ON ns2.oid = other.relnamespace
45
+ LEFT JOIN pks_uniques_cols pks_uqs ON pks_uqs.connamespace = traint.connamespace AND pks_uqs.conrelid = traint.conrelid
46
+ WHERE traint.contype = 'f'
47
+ AND traint.conparentid = 0
48
+ ${props.schemaFilter ? `and ns1.nspname ${props.schemaFilter}` : ""}
49
+ `;
@@ -0,0 +1,5 @@
1
+ import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from "./common.ts";
2
+ export declare const TYPES_SQL: (props: SQLQueryPropsWithSchemaFilterAndIdsFilter & {
3
+ includeTableTypes?: boolean;
4
+ includeArrayTypes?: boolean;
5
+ }) => string;
@@ -0,0 +1,65 @@
1
+ import { literal } from "pg-format";
2
+ export const TYPES_SQL = (props) => /* SQL */ `
3
+ select
4
+ t.oid::int8 as id,
5
+ t.typname as name,
6
+ n.nspname as schema,
7
+ format_type (t.oid, null) as format,
8
+ coalesce(t_enums.enums, '[]') as enums,
9
+ coalesce(t_attributes.attributes, '[]') as attributes,
10
+ obj_description (t.oid, 'pg_type') as comment,
11
+ nullif(t.typrelid::int8, 0) as type_relation_id
12
+ from
13
+ pg_type t
14
+ left join pg_namespace n on n.oid = t.typnamespace
15
+ left join (
16
+ select
17
+ enumtypid,
18
+ jsonb_agg(enumlabel order by enumsortorder) as enums
19
+ from
20
+ pg_enum
21
+ group by
22
+ enumtypid
23
+ ) as t_enums on t_enums.enumtypid = t.oid
24
+ left join (
25
+ select
26
+ oid,
27
+ jsonb_agg(
28
+ jsonb_build_object('name', a.attname, 'type_id', a.atttypid::int8)
29
+ order by a.attnum asc
30
+ ) as attributes
31
+ from
32
+ pg_class c
33
+ join pg_attribute a on a.attrelid = c.oid
34
+ where
35
+ c.relkind = 'c' and not a.attisdropped
36
+ group by
37
+ c.oid
38
+ ) as t_attributes on t_attributes.oid = t.typrelid
39
+ where
40
+ (
41
+ t.typrelid = 0
42
+ or (
43
+ select
44
+ c.relkind ${props.includeTableTypes ? `in ('c', 'r', 'v', 'm', 'p')` : `= 'c'`}
45
+ from
46
+ pg_class c
47
+ where
48
+ c.oid = t.typrelid
49
+ )
50
+ )
51
+ ${!props.includeArrayTypes
52
+ ? `and not exists (
53
+ select
54
+ from
55
+ pg_type el
56
+ where
57
+ el.oid = t.typelem
58
+ and el.typarray = t.oid
59
+ )`
60
+ : ""}
61
+ ${props.schemaFilter ? `and n.nspname ${props.schemaFilter}` : ""}
62
+ ${props.idsFilter ? `and t.oid ${props.idsFilter}` : ""}
63
+ ${props.limit ? `limit ${literal(props.limit)}` : ""}
64
+ ${props.offset ? `offset ${literal(props.offset)}` : ""}
65
+ `;
@@ -0,0 +1,4 @@
1
+ import type { SQLQueryPropsWithSchemaFilterAndIdsFilter } from "./common.ts";
2
+ export declare const VIEWS_SQL: (props: SQLQueryPropsWithSchemaFilterAndIdsFilter & {
3
+ viewIdentifierFilter?: string;
4
+ }) => string;
@@ -0,0 +1,20 @@
1
+ import { literal } from "pg-format";
2
+ export const VIEWS_SQL = (props) => /* SQL */ `
3
+ SELECT
4
+ c.oid :: int8 AS id,
5
+ n.nspname AS schema,
6
+ c.relname AS name,
7
+ -- See definition of information_schema.views
8
+ (pg_relation_is_updatable(c.oid, false) & 20) = 20 AS is_updatable,
9
+ obj_description(c.oid) AS comment
10
+ FROM
11
+ pg_class c
12
+ JOIN pg_namespace n ON n.oid = c.relnamespace
13
+ WHERE
14
+ ${props.schemaFilter ? `n.nspname ${props.schemaFilter} AND` : ""}
15
+ ${props.idsFilter ? `c.oid ${props.idsFilter} AND` : ""}
16
+ ${props.viewIdentifierFilter ? `(n.nspname || '.' || c.relname) ${props.viewIdentifierFilter} AND` : ""}
17
+ c.relkind = 'v'
18
+ ${props.limit ? `limit ${literal(props.limit)}` : ""}
19
+ ${props.offset ? `offset ${literal(props.offset)}` : ""}
20
+ `;
@@ -0,0 +1,2 @@
1
+ import type { SQLQueryPropsWithSchemaFilter } from "./common.ts";
2
+ export declare const VIEWS_KEY_DEPENDENCIES_SQL: (props: SQLQueryPropsWithSchemaFilter) => string;
@@ -0,0 +1,195 @@
1
+ export const VIEWS_KEY_DEPENDENCIES_SQL = (props) => /* SQL */ `
2
+ -- Adapted from
3
+ -- https://github.com/PostgREST/postgrest/blob/f9f0f79fa914ac00c11fbf7f4c558e14821e67e2/src/PostgREST/SchemaCache.hs#L820
4
+ with recursive
5
+ pks_fks as (
6
+ -- pk + fk referencing col
7
+ select
8
+ contype::text as contype,
9
+ conname,
10
+ array_length(conkey, 1) as ncol,
11
+ conrelid as resorigtbl,
12
+ col as resorigcol,
13
+ ord
14
+ from pg_constraint
15
+ left join lateral unnest(conkey) with ordinality as _(col, ord) on true
16
+ where contype IN ('p', 'f')
17
+ union
18
+ -- fk referenced col
19
+ select
20
+ concat(contype, '_ref') as contype,
21
+ conname,
22
+ array_length(confkey, 1) as ncol,
23
+ confrelid,
24
+ col,
25
+ ord
26
+ from pg_constraint
27
+ left join lateral unnest(confkey) with ordinality as _(col, ord) on true
28
+ where contype='f'
29
+ ${props.schemaFilter ? `and connamespace::regnamespace::text ${props.schemaFilter}` : ""}
30
+ ),
31
+ views as (
32
+ select
33
+ c.oid as view_id,
34
+ n.nspname as view_schema,
35
+ c.relname as view_name,
36
+ r.ev_action as view_definition
37
+ from pg_class c
38
+ join pg_namespace n on n.oid = c.relnamespace
39
+ join pg_rewrite r on r.ev_class = c.oid
40
+ where c.relkind in ('v', 'm')
41
+ ${props.schemaFilter ? `and n.nspname ${props.schemaFilter}` : ""}
42
+ ),
43
+ transform_json as (
44
+ select
45
+ view_id, view_schema, view_name,
46
+ -- the following formatting is without indentation on purpose
47
+ -- to allow simple diffs, with less whitespace noise
48
+ replace(
49
+ replace(
50
+ replace(
51
+ replace(
52
+ replace(
53
+ replace(
54
+ replace(
55
+ regexp_replace(
56
+ replace(
57
+ replace(
58
+ replace(
59
+ replace(
60
+ replace(
61
+ replace(
62
+ replace(
63
+ replace(
64
+ replace(
65
+ replace(
66
+ replace(
67
+ view_definition::text,
68
+ -- This conversion to json is heavily optimized for performance.
69
+ -- The general idea is to use as few regexp_replace() calls as possible.
70
+ -- Simple replace() is a lot faster, so we jump through some hoops
71
+ -- to be able to use regexp_replace() only once.
72
+ -- This has been tested against a huge schema with 250+ different views.
73
+ -- The unit tests do NOT reflect all possible inputs. Be careful when changing this!
74
+ -- -----------------------------------------------
75
+ -- pattern | replacement | flags
76
+ -- -----------------------------------------------
77
+ -- <> in pg_node_tree is the same as null in JSON, but due to very poor performance of json_typeof
78
+ -- we need to make this an empty array here to prevent json_array_elements from throwing an error
79
+ -- when the targetList is null.
80
+ -- We'll need to put it first, to make the node protection below work for node lists that start with
81
+ -- null: (<> ..., too. This is the case for coldefexprs, when the first column does not have a default value.
82
+ '<>' , '()'
83
+ -- , is not part of the pg_node_tree format, but used in the regex.
84
+ -- This removes all , that might be part of column names.
85
+ ), ',' , ''
86
+ -- The same applies for { and }, although those are used a lot in pg_node_tree.
87
+ -- We remove the escaped ones, which might be part of column names again.
88
+ ), E'\\\\{' , ''
89
+ ), E'\\\\}' , ''
90
+ -- The fields we need are formatted as json manually to protect them from the regex.
91
+ ), ' :targetList ' , ',"targetList":'
92
+ ), ' :resno ' , ',"resno":'
93
+ ), ' :resorigtbl ' , ',"resorigtbl":'
94
+ ), ' :resorigcol ' , ',"resorigcol":'
95
+ -- Make the regex also match the node type, e.g. \`{QUERY ...\`, to remove it in one pass.
96
+ ), '{' , '{ :'
97
+ -- Protect node lists, which start with \`({\` or \`((\` from the greedy regex.
98
+ -- The extra \`{\` is removed again later.
99
+ ), '((' , '{(('
100
+ ), '({' , '{({'
101
+ -- This regex removes all unused fields to avoid the need to format all of them correctly.
102
+ -- This leads to a smaller json result as well.
103
+ -- Removal stops at \`,\` for used fields (see above) and \`}\` for the end of the current node.
104
+ -- Nesting can't be parsed correctly with a regex, so we stop at \`{\` as well and
105
+ -- add an empty key for the followig node.
106
+ ), ' :[^}{,]+' , ',"":' , 'g'
107
+ -- For performance, the regex also added those empty keys when hitting a \`,\` or \`}\`.
108
+ -- Those are removed next.
109
+ ), ',"":}' , '}'
110
+ ), ',"":,' , ','
111
+ -- This reverses the "node list protection" from above.
112
+ ), '{(' , '('
113
+ -- Every key above has been added with a \`,\` so far. The first key in an object doesn't need it.
114
+ ), '{,' , '{'
115
+ -- pg_node_tree has \`()\` around lists, but JSON uses \`[]\`
116
+ ), '(' , '['
117
+ ), ')' , ']'
118
+ -- pg_node_tree has \` \` between list items, but JSON uses \`,\`
119
+ ), ' ' , ','
120
+ )::json as view_definition
121
+ from views
122
+ ),
123
+ target_entries as(
124
+ select
125
+ view_id, view_schema, view_name,
126
+ json_array_elements(view_definition->0->'targetList') as entry
127
+ from transform_json
128
+ ),
129
+ results as(
130
+ select
131
+ view_id, view_schema, view_name,
132
+ (entry->>'resno')::int as view_column,
133
+ (entry->>'resorigtbl')::oid as resorigtbl,
134
+ (entry->>'resorigcol')::int as resorigcol
135
+ from target_entries
136
+ ),
137
+ -- CYCLE detection according to PG docs: https://www.postgresql.org/docs/current/queries-with.html#QUERIES-WITH-CYCLE
138
+ -- Can be replaced with CYCLE clause once PG v13 is EOL.
139
+ recursion(view_id, view_schema, view_name, view_column, resorigtbl, resorigcol, is_cycle, path) as(
140
+ select
141
+ r.*,
142
+ false,
143
+ ARRAY[resorigtbl]
144
+ from results r
145
+ where ${props.schemaFilter ? `view_schema ${props.schemaFilter}` : "true"}
146
+ union all
147
+ select
148
+ view.view_id,
149
+ view.view_schema,
150
+ view.view_name,
151
+ view.view_column,
152
+ tab.resorigtbl,
153
+ tab.resorigcol,
154
+ tab.resorigtbl = ANY(path),
155
+ path || tab.resorigtbl
156
+ from recursion view
157
+ join results tab on view.resorigtbl=tab.view_id and view.resorigcol=tab.view_column
158
+ where not is_cycle
159
+ ),
160
+ repeated_references as(
161
+ select
162
+ view_id,
163
+ view_schema,
164
+ view_name,
165
+ resorigtbl,
166
+ resorigcol,
167
+ array_agg(attname) as view_columns
168
+ from recursion
169
+ join pg_attribute vcol on vcol.attrelid = view_id and vcol.attnum = view_column
170
+ group by
171
+ view_id,
172
+ view_schema,
173
+ view_name,
174
+ resorigtbl,
175
+ resorigcol
176
+ )
177
+ select
178
+ sch.nspname as table_schema,
179
+ tbl.relname as table_name,
180
+ rep.view_schema,
181
+ rep.view_name,
182
+ pks_fks.conname as constraint_name,
183
+ pks_fks.contype as constraint_type,
184
+ jsonb_agg(
185
+ jsonb_build_object('table_column', col.attname, 'view_columns', view_columns) order by pks_fks.ord
186
+ ) as column_dependencies
187
+ from repeated_references rep
188
+ join pks_fks using (resorigtbl, resorigcol)
189
+ join pg_class tbl on tbl.oid = rep.resorigtbl
190
+ join pg_attribute col on col.attrelid = tbl.oid and col.attnum = rep.resorigcol
191
+ join pg_namespace sch on sch.oid = tbl.relnamespace
192
+ group by sch.nspname, tbl.relname, rep.view_schema, rep.view_name, pks_fks.conname, pks_fks.contype, pks_fks.ncol
193
+ -- make sure we only return key for which all columns are referenced in the view - no partial PKs or FKs
194
+ having ncol = array_length(array_agg(row(col.attname, view_columns) order by pks_fks.ord), 1)
195
+ `;
package/dist/sort.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Canonical ordering pass for {@link GeneratorMetadata}.
3
+ *
4
+ * The language generators consume the metadata collections in array order.
5
+ * This pass is the single, generator-agnostic stabilizer: it returns a new
6
+ * `GeneratorMetadata` whose every collection is ordered so that any producer
7
+ * (the bundled SQL `introspect()` or a custom injected adapter) yields
8
+ * byte-stable codegen. Generators document that they expect input pre-sorted
9
+ * with this function; ordering is intentionally NOT enforced inside
10
+ * `introspect()` so the concern lives in one place.
11
+ *
12
+ * **The TypeScript generator is the source of truth for ordering.** The keys
13
+ * below replicate the sorts that `generateTypescript` historically applied
14
+ * internally (schemas/tables/views/functions/types/columns/relationships by
15
+ * name; function args by name), so that generator can drop its own sorting and
16
+ * stay byte-identical. The other generators then inherit the same canonical
17
+ * order for free.
18
+ *
19
+ * Keys are **semantic** (schema + name + signature), never oid: equivalent
20
+ * databases assign different oids, so an oid sort would still churn output
21
+ * across environments. `id` is only a final tie-breaker for a total order.
22
+ *
23
+ * Composite type `attributes` and enum values are left untouched — their order
24
+ * is semantically meaningful (struct field / enum-label order) and must be
25
+ * preserved. Function `args` are sorted by name because PostgREST RPC args are
26
+ * addressed by name (the generated type is an object), matching TypeScript.
27
+ */
28
+ import type { GeneratorMetadata } from "./types.ts";
29
+ /**
30
+ * Return a new {@link GeneratorMetadata} with every collection ordered by a
31
+ * stable, total, semantic key (matching the TypeScript generator's ordering).
32
+ * Pure (does not mutate the input) and idempotent. Apply this after
33
+ * introspection and before any `generate*` call.
34
+ */
35
+ export declare function sortGeneratorMetadata(metadata: GeneratorMetadata): GeneratorMetadata;
package/dist/sort.js ADDED
@@ -0,0 +1,48 @@
1
+ // Relations (tables/views/materialized views/foreign tables) and types share a
2
+ // (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) ||
5
+ 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));
10
+ /**
11
+ * Return a new {@link GeneratorMetadata} with every collection ordered by a
12
+ * stable, total, semantic key (matching the TypeScript generator's ordering).
13
+ * Pure (does not mutate the input) and idempotent. Apply this after
14
+ * introspection and before any `generate*` call.
15
+ */
16
+ export function sortGeneratorMetadata(metadata) {
17
+ return {
18
+ version: metadata.version,
19
+ schemas: [...metadata.schemas].sort((a, b) => a.name.localeCompare(b.name) || a.id - b.id),
20
+ tables: [...metadata.tables].sort(bySchemaName),
21
+ foreignTables: [...metadata.foreignTables].sort(bySchemaName),
22
+ views: [...metadata.views].sort(bySchemaName),
23
+ materializedViews: [...metadata.materializedViews].sort(bySchemaName),
24
+ // Group columns by their table's (schema, name); name-order within a table
25
+ // (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)),
29
+ // Grouped by table like `columns`; within a table the SQL's declared
30
+ // column order (`array_position(indkey, attnum)`) is preserved by the
31
+ // 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)),
34
+ relationships: [...metadata.relationships].sort(byRelationship),
35
+ // Functions can overload, so the signature is part of the identity. Args
36
+ // are addressed by name in generated RPC types, so sort them by name too.
37
+ 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) ||
41
+ a.id - b.id)
42
+ .map((fn) => ({
43
+ ...fn,
44
+ args: [...fn.args].sort((a, b) => a.name.localeCompare(b.name)),
45
+ })),
46
+ types: [...metadata.types].sort(bySchemaName),
47
+ };
48
+ }