@rebasepro/server-postgres 0.17.3 → 0.18.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 (204) hide show
  1. package/LICENSE +0 -1
  2. package/README.md +31 -0
  3. package/dist/BranchService-ByLfM_Tt.js +360 -0
  4. package/dist/BranchService-ByLfM_Tt.js.map +1 -0
  5. package/dist/__vite-browser-external-BnuHet1e.js +46 -0
  6. package/dist/__vite-browser-external-BnuHet1e.js.map +1 -0
  7. package/dist/arg-Dni7MzLB.js +87 -0
  8. package/dist/arg-Dni7MzLB.js.map +1 -0
  9. package/dist/auth-users-columns-D2LBFrMH.js +116 -0
  10. package/dist/auth-users-columns-D2LBFrMH.js.map +1 -0
  11. package/dist/backup-argv.d.ts +19 -0
  12. package/dist/backup-cli-oM9WQVlV.js +408 -0
  13. package/dist/backup-cli-oM9WQVlV.js.map +1 -0
  14. package/dist/backup-service-Br3tqwsy.js +715 -0
  15. package/dist/backup-service-Br3tqwsy.js.map +1 -0
  16. package/dist/branch-argv.d.ts +28 -0
  17. package/dist/branch-prune.d.ts +77 -0
  18. package/dist/cli-collections-path.d.ts +31 -0
  19. package/dist/cli-errors-DxN4yzdH.js +657 -0
  20. package/dist/cli-errors-DxN4yzdH.js.map +1 -0
  21. package/dist/cli-errors.d.ts +123 -0
  22. package/dist/cli-flags.d.ts +101 -0
  23. package/dist/cli-helpers.d.ts +94 -10
  24. package/dist/cli-output-CNdMql-L.js +47 -0
  25. package/dist/cli-output-CNdMql-L.js.map +1 -0
  26. package/dist/cli.js +2020 -0
  27. package/dist/cli.js.map +1 -0
  28. package/dist/collection-index-BHgT_TKd.js +276 -0
  29. package/dist/collection-index-BHgT_TKd.js.map +1 -0
  30. package/dist/{collections-schema-version-BMeu3cgv.js → collections-schema-version-T6cGvrDi.js} +1 -2
  31. package/dist/{collections-schema-version-BMeu3cgv.js.map → collections-schema-version-T6cGvrDi.js.map} +1 -1
  32. package/dist/{connection-GOKU3Hu5.js → databasePoolManager-Bj5FbeAs.js} +112 -40
  33. package/dist/databasePoolManager-Bj5FbeAs.js.map +1 -0
  34. package/dist/databasePoolManager.d.ts +27 -0
  35. package/dist/doctor-D7HWmGzs.js +974 -0
  36. package/dist/doctor-D7HWmGzs.js.map +1 -0
  37. package/dist/{ensure-collection-policies-UCqgv_8c.js → ensure-collection-policies-2c2NV8zX.js} +7 -27
  38. package/dist/ensure-collection-policies-2c2NV8zX.js.map +1 -0
  39. package/dist/ensure-collection-tables-D4wJzM4y.js +820 -0
  40. package/dist/ensure-collection-tables-D4wJzM4y.js.map +1 -0
  41. package/dist/ensure-tables-CIojK-Rx.js +650 -0
  42. package/dist/ensure-tables-CIojK-Rx.js.map +1 -0
  43. package/dist/generate-drizzle-schema-CwEgVyjO.js +123 -0
  44. package/dist/generate-drizzle-schema-CwEgVyjO.js.map +1 -0
  45. package/dist/generate-drizzle-schema-logic-so2Z_lyC.js +528 -0
  46. package/dist/generate-drizzle-schema-logic-so2Z_lyC.js.map +1 -0
  47. package/dist/{ensure-collection-tables-DgVixhX3.js → generate-postgres-ddl-logic-CtnpIBVv.js} +126 -984
  48. package/dist/generate-postgres-ddl-logic-CtnpIBVv.js.map +1 -0
  49. package/dist/generated-schema-staleness-DQdfJl2A.js +157 -0
  50. package/dist/generated-schema-staleness-DQdfJl2A.js.map +1 -0
  51. package/dist/index.d.ts +12 -0
  52. package/dist/index.es.js +7423 -1133
  53. package/dist/index.es.js.map +1 -1
  54. package/dist/introspect-db-logic-C6LQdTxj.js +1378 -0
  55. package/dist/introspect-db-logic-C6LQdTxj.js.map +1 -0
  56. package/dist/policy-drift-kNJ6X2Uj.js +313 -0
  57. package/dist/policy-drift-kNJ6X2Uj.js.map +1 -0
  58. package/dist/{backup-service-DCk7KhhL.js → rls-bootstrap-sql-DLDnUjtx.js} +737 -2689
  59. package/dist/rls-bootstrap-sql-DLDnUjtx.js.map +1 -0
  60. package/dist/{rls-enforcement-DvAbL9YJ.js → rls-enforcement-DeYEjYLt.js} +65 -190
  61. package/dist/rls-enforcement-DeYEjYLt.js.map +1 -0
  62. package/dist/schema/bin/generate-drizzle-schema.d.ts +1 -0
  63. package/dist/schema/bin/generate-postgres-ddl.d.ts +1 -0
  64. package/dist/schema/column-type-drift.d.ts +79 -0
  65. package/dist/schema/doctor-cli.js +129 -0
  66. package/dist/schema/doctor-cli.js.map +1 -0
  67. package/dist/schema/doctor.d.ts +42 -0
  68. package/dist/schema/ensure-collection-tables.d.ts +90 -0
  69. package/dist/schema/generate-drizzle-schema.d.ts +1 -1
  70. package/dist/schema/generate-drizzle-schema.js +24 -0
  71. package/dist/schema/generate-drizzle-schema.js.map +1 -0
  72. package/dist/schema/generate-next-step.d.ts +30 -0
  73. package/dist/schema/generate-postgres-ddl-logic.d.ts +28 -0
  74. package/dist/schema/generate-postgres-ddl.d.ts +1 -1
  75. package/dist/schema/generate-postgres-ddl.js +138 -0
  76. package/dist/schema/generate-postgres-ddl.js.map +1 -0
  77. package/dist/schema/generated-schema-staleness.d.ts +37 -0
  78. package/dist/schema/introspect-db-queries.d.ts +34 -0
  79. package/dist/schema/introspect-db.js +866 -0
  80. package/dist/schema/introspect-db.js.map +1 -0
  81. package/dist/security/rls-enforcement.d.ts +63 -2
  82. package/dist/services/BranchService.d.ts +86 -1
  83. package/dist/services/collection-helpers.d.ts +1 -1
  84. package/dist/services/realtimeService.d.ts +30 -2
  85. package/dist/services/row-pipeline.d.ts +12 -1
  86. package/dist/source-Br7L7GOI.js +410 -0
  87. package/dist/source-Br7L7GOI.js.map +1 -0
  88. package/dist/utils/pg-error-utils.d.ts +3 -1
  89. package/dist/utils/pg-numeric-number-patch.d.ts +31 -0
  90. package/dist/vector-index-DkWxm2de.js +309 -0
  91. package/dist/vector-index-DkWxm2de.js.map +1 -0
  92. package/package.json +32 -27
  93. package/dist/backup-service-DCk7KhhL.js.map +0 -1
  94. package/dist/collection-index-BRUg10H5.js +0 -5690
  95. package/dist/collection-index-BRUg10H5.js.map +0 -1
  96. package/dist/connection-GOKU3Hu5.js.map +0 -1
  97. package/dist/ensure-collection-policies-UCqgv_8c.js.map +0 -1
  98. package/dist/ensure-collection-tables-DgVixhX3.js.map +0 -1
  99. package/dist/rls-bootstrap-sql-B5C9LoJ6.js +0 -244
  100. package/dist/rls-bootstrap-sql-B5C9LoJ6.js.map +0 -1
  101. package/dist/rls-enforcement-DvAbL9YJ.js.map +0 -1
  102. package/dist/src-DiB5RP2Z.js +0 -541
  103. package/dist/src-DiB5RP2Z.js.map +0 -1
  104. package/dist/websocket-BZ4H5wUz.js +0 -8198
  105. package/dist/websocket-BZ4H5wUz.js.map +0 -1
  106. package/src/PostgresAdapter.ts +0 -91
  107. package/src/PostgresBackendDriver.ts +0 -1857
  108. package/src/PostgresBootstrapper.ts +0 -1157
  109. package/src/auth/ensure-tables.ts +0 -896
  110. package/src/auth/schema-version.ts +0 -260
  111. package/src/auth/services.ts +0 -1610
  112. package/src/backup/backup-cli.ts +0 -520
  113. package/src/backup/backup-cron.ts +0 -212
  114. package/src/backup/backup-logic.ts +0 -93
  115. package/src/backup/backup-service.ts +0 -485
  116. package/src/backup/index.ts +0 -12
  117. package/src/backup/pg-tools.ts +0 -403
  118. package/src/backup/retention.ts +0 -86
  119. package/src/cli-errors.ts +0 -265
  120. package/src/cli-helpers.ts +0 -569
  121. package/src/cli-output.ts +0 -43
  122. package/src/cli.ts +0 -1339
  123. package/src/collections/PostgresCollectionRegistry.ts +0 -108
  124. package/src/collections/buildRegistry.ts +0 -68
  125. package/src/collections/validate-relations.ts +0 -402
  126. package/src/connection.ts +0 -333
  127. package/src/data-transformer.ts +0 -845
  128. package/src/databasePoolManager.ts +0 -95
  129. package/src/history/HistoryService.ts +0 -254
  130. package/src/history/ensure-history-table.ts +0 -53
  131. package/src/index.ts +0 -19
  132. package/src/interfaces.ts +0 -60
  133. package/src/module-dir.ts +0 -7
  134. package/src/schema/atlas-argv.ts +0 -94
  135. package/src/schema/auth-schema.ts +0 -298
  136. package/src/schema/auth-users-columns.ts +0 -131
  137. package/src/schema/carved-out-migration.ts +0 -404
  138. package/src/schema/classify-change.ts +0 -436
  139. package/src/schema/collection-index.ts +0 -427
  140. package/src/schema/collections-schema-version.ts +0 -103
  141. package/src/schema/destructive-sql.ts +0 -94
  142. package/src/schema/doctor-cli.ts +0 -66
  143. package/src/schema/doctor-policy-checks.ts +0 -105
  144. package/src/schema/doctor.ts +0 -837
  145. package/src/schema/drizzle-ddl.ts +0 -33
  146. package/src/schema/dynamic-tables.test.ts +0 -302
  147. package/src/schema/dynamic-tables.ts +0 -293
  148. package/src/schema/ensure-collection-policies.ts +0 -198
  149. package/src/schema/ensure-collection-tables.test.ts +0 -518
  150. package/src/schema/ensure-collection-tables.ts +0 -1267
  151. package/src/schema/generate-drizzle-schema-logic.ts +0 -957
  152. package/src/schema/generate-drizzle-schema.ts +0 -143
  153. package/src/schema/generate-postgres-ddl-logic.ts +0 -1339
  154. package/src/schema/generate-postgres-ddl.ts +0 -180
  155. package/src/schema/generate-schema-commit.ts +0 -267
  156. package/src/schema/generated-schema-staleness.ts +0 -284
  157. package/src/schema/introspect-db-constraints.ts +0 -385
  158. package/src/schema/introspect-db-inference.ts +0 -255
  159. package/src/schema/introspect-db-logic.ts +0 -1387
  160. package/src/schema/introspect-db-naming.ts +0 -15
  161. package/src/schema/introspect-db-project.ts +0 -78
  162. package/src/schema/introspect-db-queries.ts +0 -326
  163. package/src/schema/introspect-db-structure.ts +0 -670
  164. package/src/schema/introspect-db-types.ts +0 -56
  165. package/src/schema/introspect-db.ts +0 -257
  166. package/src/schema/introspect-runtime.test.ts +0 -260
  167. package/src/schema/introspect-runtime.ts +0 -327
  168. package/src/schema/non-sql-collections.test.ts +0 -131
  169. package/src/schema/rls-bootstrap-sql.ts +0 -288
  170. package/src/schema/search-column.ts +0 -643
  171. package/src/schema/test-schema.ts +0 -11
  172. package/src/schema/vector-index.ts +0 -491
  173. package/src/security/anonymous-grants.test.ts +0 -73
  174. package/src/security/policy-drift.test.ts +0 -435
  175. package/src/security/policy-drift.ts +0 -473
  176. package/src/security/rls-enforcement.ts +0 -505
  177. package/src/services/BranchService.ts +0 -326
  178. package/src/services/FetchService.ts +0 -2115
  179. package/src/services/PersistService.ts +0 -557
  180. package/src/services/RelationService.ts +0 -978
  181. package/src/services/RelationWriteService.ts +0 -653
  182. package/src/services/cdc/CdcListener.ts +0 -103
  183. package/src/services/cdc/junction-tables.ts +0 -91
  184. package/src/services/cdc/trigger-cdc.ts +0 -173
  185. package/src/services/channel-bus/ChannelBus.ts +0 -44
  186. package/src/services/channel-bus/PostgresChannelBus.ts +0 -299
  187. package/src/services/channel-bus/index.ts +0 -114
  188. package/src/services/channel-history.ts +0 -424
  189. package/src/services/channel-presence.ts +0 -185
  190. package/src/services/collection-helpers.ts +0 -420
  191. package/src/services/dataService.ts +0 -256
  192. package/src/services/index.ts +0 -14
  193. package/src/services/junction-writes.ts +0 -295
  194. package/src/services/nested-path.ts +0 -145
  195. package/src/services/pg-notify-listener.ts +0 -151
  196. package/src/services/realtimeService.ts +0 -2545
  197. package/src/services/row-pipeline.ts +0 -240
  198. package/src/services/write-denial.ts +0 -55
  199. package/src/types.ts +0 -4
  200. package/src/utils/connection-string.ts +0 -58
  201. package/src/utils/drizzle-conditions.ts +0 -2743
  202. package/src/utils/pg-array-null-patch.ts +0 -42
  203. package/src/utils/pg-error-utils.ts +0 -391
  204. package/src/websocket.ts +0 -855
@@ -1,33 +0,0 @@
1
- /**
2
- * The server's DDL bootstrapper, over a Drizzle handle.
3
- *
4
- * `createDdlBootstrapper` in `@rebasepro/server` wants a plain
5
- * `(sql: string) => Promise<rows>`; the driver's internal stores hold a Drizzle
6
- * database. This is the adapter between them, and it exists so the retry policy
7
- * has exactly one definition. A second copy of the SQLSTATE list living in the
8
- * driver is how the two drift apart, and the drift is invisible: both versions
9
- * work perfectly on every single-instance deployment.
10
- */
11
- import { sql } from "drizzle-orm";
12
- import type { NodePgDatabase } from "drizzle-orm/node-postgres";
13
- import { createDdlBootstrapper, type DdlBootstrapper } from "@rebasepro/server";
14
-
15
- /**
16
- * A {@link DdlBootstrapper} that runs its statements through `db.execute`.
17
- *
18
- * @param db the Drizzle handle the calling store already holds
19
- * @param scope log prefix identifying the caller, e.g. `"channel-presence"`
20
- */
21
- export function drizzleDdlBootstrapper(
22
- db: NodePgDatabase<Record<string, unknown>>,
23
- scope: string
24
- ): DdlBootstrapper {
25
- return createDdlBootstrapper(async (statement: string) => {
26
- // `sql.raw`, because everything reaching this path is DDL assembled from
27
- // identifiers that were validated before they got here — there is no
28
- // parameter to bind, and Drizzle's tagged template would treat the whole
29
- // statement as one.
30
- const result = await db.execute(sql.raw(statement));
31
- return (result as unknown as { rows?: Record<string, unknown>[] }).rows ?? [];
32
- }, scope);
33
- }
@@ -1,302 +0,0 @@
1
- import { describe, expect, it } from "@jest/globals";
2
- import { getTableName, sql } from "drizzle-orm";
3
- import { getTableConfig } from "drizzle-orm/pg-core";
4
- import { drizzle } from "drizzle-orm/node-postgres";
5
-
6
- import { buildDrizzleTablesFromSchema, buildDrizzleRelationsFromSchema } from "./dynamic-tables";
7
- import { buildTablesMap, TableColumn, PrimaryKeyRow, ForeignKeyRow } from "./introspect-db-logic";
8
-
9
- function column(overrides: Partial<TableColumn> & { table_name: string; column_name: string }): TableColumn {
10
- return {
11
- data_type: "text",
12
- udt_name: "text",
13
- is_nullable: "YES",
14
- column_default: null,
15
- atttypmod: null,
16
- ...overrides
17
- };
18
- }
19
-
20
- function tablesMapOf(columns: TableColumn[], pks: PrimaryKeyRow[], fks: ForeignKeyRow[] = []) {
21
- const tableNames = [...new Set(columns.map((c) => c.table_name))].map((table_name) => ({ table_name }));
22
- return buildTablesMap(tableNames, columns, pks, fks);
23
- }
24
-
25
- describe("buildDrizzleTablesFromSchema", () => {
26
- it("builds a table per introspected table, named after the pg table", () => {
27
- const tables = buildDrizzleTablesFromSchema(
28
- tablesMapOf(
29
- [column({ table_name: "posts", column_name: "id", data_type: "uuid", udt_name: "uuid", is_nullable: "NO" })],
30
- [{ table_name: "posts", column_name: "id" }]
31
- )
32
- );
33
-
34
- expect(Object.keys(tables)).toEqual(["posts"]);
35
- expect(getTableName(tables.posts)).toBe("posts");
36
- });
37
-
38
- it("maps pg types onto the matching drizzle column types", () => {
39
- const tables = buildDrizzleTablesFromSchema(
40
- tablesMapOf(
41
- [
42
- column({ table_name: "t", column_name: "id", data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
43
- column({ table_name: "t", column_name: "count", data_type: "integer", udt_name: "int4" }),
44
- column({ table_name: "t", column_name: "big", data_type: "bigint", udt_name: "int8" }),
45
- column({ table_name: "t", column_name: "price", data_type: "numeric", udt_name: "numeric" }),
46
- column({ table_name: "t", column_name: "active", data_type: "boolean", udt_name: "bool" }),
47
- column({ table_name: "t", column_name: "meta", data_type: "jsonb", udt_name: "jsonb" }),
48
- column({ table_name: "t", column_name: "created_at", data_type: "timestamp with time zone", udt_name: "timestamptz" }),
49
- column({ table_name: "t", column_name: "born_on", data_type: "date", udt_name: "date" })
50
- ],
51
- [{ table_name: "t", column_name: "id" }]
52
- )
53
- );
54
-
55
- const byName = Object.fromEntries(
56
- getTableConfig(tables.t).columns.map((c) => [c.name, c.getSQLType()])
57
- );
58
-
59
- expect(byName.id).toBe("uuid");
60
- expect(byName.count).toBe("integer");
61
- expect(byName.big).toBe("bigint");
62
- expect(byName.price).toBe("numeric");
63
- expect(byName.active).toBe("boolean");
64
- expect(byName.meta).toBe("jsonb");
65
- expect(byName.created_at).toBe("timestamp with time zone");
66
- expect(byName.born_on).toBe("date");
67
- });
68
-
69
- it("carries NOT NULL and single-column primary keys onto the columns", () => {
70
- const tables = buildDrizzleTablesFromSchema(
71
- tablesMapOf(
72
- [
73
- column({ table_name: "t", column_name: "id", data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
74
- column({ table_name: "t", column_name: "title", is_nullable: "NO" }),
75
- column({ table_name: "t", column_name: "subtitle", is_nullable: "YES" })
76
- ],
77
- [{ table_name: "t", column_name: "id" }]
78
- )
79
- );
80
-
81
- const columns = getTableConfig(tables.t).columns;
82
- const id = columns.find((c) => c.name === "id")!;
83
- const title = columns.find((c) => c.name === "title")!;
84
- const subtitle = columns.find((c) => c.name === "subtitle")!;
85
-
86
- expect(id.primary).toBe(true);
87
- expect(title.notNull).toBe(true);
88
- expect(title.primary).toBe(false);
89
- expect(subtitle.notNull).toBe(false);
90
- });
91
-
92
- it("declares composite primary keys as a table constraint", () => {
93
- const tables = buildDrizzleTablesFromSchema(
94
- tablesMapOf(
95
- [
96
- column({ table_name: "posts_tags", column_name: "post_id", data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
97
- column({ table_name: "posts_tags", column_name: "tag_id", data_type: "uuid", udt_name: "uuid", is_nullable: "NO" })
98
- ],
99
- [
100
- { table_name: "posts_tags", column_name: "post_id" },
101
- { table_name: "posts_tags", column_name: "tag_id" }
102
- ]
103
- )
104
- );
105
-
106
- const config = getTableConfig(tables.posts_tags);
107
- expect(config.primaryKeys).toHaveLength(1);
108
- expect(config.primaryKeys[0].columns.map((c) => c.name).sort()).toEqual(["post_id", "tag_id"]);
109
- // No column should claim the primary key on its own.
110
- expect(config.columns.every((c) => !c.primary)).toBe(true);
111
- });
112
-
113
- it("binds tables to a non-public schema", () => {
114
- const tables = buildDrizzleTablesFromSchema(
115
- tablesMapOf(
116
- [column({ table_name: "users", column_name: "id", data_type: "uuid", udt_name: "uuid", is_nullable: "NO" })],
117
- [{ table_name: "users", column_name: "id" }]
118
- ),
119
- "rebase"
120
- );
121
-
122
- expect(getTableConfig(tables.users).schema).toBe("rebase");
123
- });
124
-
125
- it("falls back to text for unknown types rather than dropping the column", () => {
126
- const tables = buildDrizzleTablesFromSchema(
127
- tablesMapOf(
128
- [
129
- column({ table_name: "t", column_name: "id", data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
130
- column({ table_name: "t", column_name: "location", data_type: "USER-DEFINED", udt_name: "geography" }),
131
- column({ table_name: "t", column_name: "weird", data_type: "USER-DEFINED", udt_name: "some_future_type" })
132
- ],
133
- [{ table_name: "t", column_name: "id" }]
134
- )
135
- );
136
-
137
- const byName = Object.fromEntries(getTableConfig(tables.t).columns.map((c) => [c.name, c.getSQLType()]));
138
- expect(byName.location).toBe("text");
139
- expect(byName.weird).toBe("text");
140
- });
141
-
142
- it("maps the types a plain type switch tends to miss", () => {
143
- const tables = buildDrizzleTablesFromSchema(
144
- tablesMapOf(
145
- [
146
- column({ table_name: "t", column_name: "id", data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
147
- column({ table_name: "t", column_name: "blob", data_type: "bytea", udt_name: "bytea" }),
148
- column({ table_name: "t", column_name: "span", data_type: "interval", udt_name: "interval" }),
149
- column({ table_name: "t", column_name: "ip", data_type: "inet", udt_name: "inet" }),
150
- column({ table_name: "t", column_name: "mac", data_type: "macaddr", udt_name: "macaddr" }),
151
- column({ table_name: "t", column_name: "code", data_type: "character", udt_name: "bpchar", atttypmod: 14 }),
152
- column({ table_name: "t", column_name: "name", data_type: "character varying", udt_name: "varchar", atttypmod: 104 }),
153
- column({ table_name: "t", column_name: "clock", data_type: "time with time zone", udt_name: "timetz" }),
154
- column({ table_name: "t", column_name: "embedding", data_type: "USER-DEFINED", udt_name: "vector", atttypmod: 3 })
155
- ],
156
- [{ table_name: "t", column_name: "id" }]
157
- )
158
- );
159
-
160
- const byName = Object.fromEntries(getTableConfig(tables.t).columns.map((c) => [c.name, c.getSQLType()]));
161
-
162
- // bytea has no drizzle builder — a text fallback would corrupt binary.
163
- expect(byName.blob).toBe("bytea");
164
- expect(byName.span).toBe("interval");
165
- expect(byName.ip).toBe("inet");
166
- expect(byName.mac).toBe("macaddr");
167
- expect(byName.code).toBe("char(10)");
168
- expect(byName.name).toBe("varchar(100)");
169
- expect(byName.clock).toBe("time with time zone");
170
- expect(byName.embedding).toBe("vector(3)");
171
- });
172
-
173
- it("types arrays by their element rather than flattening them to text[]", () => {
174
- const tables = buildDrizzleTablesFromSchema(
175
- tablesMapOf(
176
- [
177
- column({ table_name: "t", column_name: "id", data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
178
- column({ table_name: "t", column_name: "tags", data_type: "ARRAY", udt_name: "_text" }),
179
- column({ table_name: "t", column_name: "scores", data_type: "ARRAY", udt_name: "_int4" }),
180
- column({ table_name: "t", column_name: "ids", data_type: "ARRAY", udt_name: "_uuid" })
181
- ],
182
- [{ table_name: "t", column_name: "id" }]
183
- )
184
- );
185
-
186
- const byName = Object.fromEntries(getTableConfig(tables.t).columns.map((c) => [c.name, c.getSQLType()]));
187
-
188
- expect(byName.tags).toBe("text[]");
189
- expect(byName.scores).toBe("integer[]");
190
- expect(byName.ids).toBe("uuid[]");
191
- });
192
-
193
- it("falls back to text for a vector of undeclared width", () => {
194
- const tables = buildDrizzleTablesFromSchema(
195
- tablesMapOf(
196
- [
197
- column({ table_name: "t", column_name: "id", data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
198
- column({ table_name: "t", column_name: "embedding", data_type: "USER-DEFINED", udt_name: "vector", atttypmod: -1 })
199
- ],
200
- [{ table_name: "t", column_name: "id" }]
201
- )
202
- );
203
-
204
- const byName = Object.fromEntries(getTableConfig(tables.t).columns.map((c) => [c.name, c.getSQLType()]));
205
- expect(byName.embedding).toBe("text");
206
- });
207
-
208
- it("produces tables drizzle can compile into SQL", () => {
209
- // The real proof: a table object is only useful if the query builder
210
- // accepts it. Compiling a select exercises the whole construction.
211
- const tables = buildDrizzleTablesFromSchema(
212
- tablesMapOf(
213
- [
214
- column({ table_name: "posts", column_name: "id", data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
215
- column({ table_name: "posts", column_name: "title", is_nullable: "NO" })
216
- ],
217
- [{ table_name: "posts", column_name: "id" }]
218
- )
219
- );
220
-
221
- const db = drizzle({ schema: tables, client: {} as never });
222
- const query = db.select().from(tables.posts as never).toSQL();
223
-
224
- expect(query.sql).toContain('from "posts"');
225
- expect(query.sql).toContain('"id"');
226
- expect(query.sql).toContain('"title"');
227
- });
228
- });
229
-
230
- describe("buildDrizzleRelationsFromSchema", () => {
231
- const schema = () =>
232
- tablesMapOf(
233
- [
234
- column({ table_name: "authors", column_name: "id", data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
235
- column({ table_name: "posts", column_name: "id", data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
236
- column({ table_name: "posts", column_name: "author_id", data_type: "uuid", udt_name: "uuid" })
237
- ],
238
- [
239
- { table_name: "authors", column_name: "id" },
240
- { table_name: "posts", column_name: "id" }
241
- ],
242
- [{ table_name: "posts", column_name: "author_id", foreign_table_name: "authors", foreign_column_name: "id" }]
243
- );
244
-
245
- it("pairs each owning one() with the inverse many() drizzle requires", () => {
246
- // Drizzle's normalizeRelation throws on a named one() with no matching
247
- // many(), which silently drops the query to the N+1 fallback path.
248
- const map = schema();
249
- const tables = buildDrizzleTablesFromSchema(map);
250
- const rels = buildDrizzleRelationsFromSchema(map, tables);
251
-
252
- expect(Object.keys(rels).sort()).toEqual(["authorsRelations", "postsRelations"]);
253
-
254
- const db = drizzle({ schema: { ...tables, ...rels }, client: {} as never });
255
- const config = (db as unknown as { _: { schema: Record<string, { relations: Record<string, unknown> }> } })._.schema;
256
-
257
- expect(Object.keys(config.posts.relations)).toEqual(["author"]);
258
- expect(Object.keys(config.authors.relations)).toEqual(["posts"]);
259
- });
260
-
261
- it("compiles a relational query that joins the related table", () => {
262
- const map = schema();
263
- const tables = buildDrizzleTablesFromSchema(map);
264
- const rels = buildDrizzleRelationsFromSchema(map, tables);
265
- const db = drizzle({ schema: { ...tables, ...rels }, client: {} as never });
266
-
267
- const query = (db as never as { query: Record<string, { findMany(a: unknown): { toSQL(): { sql: string } } }> })
268
- .query.posts.findMany({ with: { author: true } }).toSQL();
269
-
270
- expect(query.sql).toContain('"authors"');
271
- expect(query.sql).toContain("author_id");
272
- });
273
-
274
- it("skips a relation whose key would shadow a real column", () => {
275
- const map = tablesMapOf(
276
- [
277
- column({ table_name: "authors", column_name: "id", data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
278
- column({ table_name: "posts", column_name: "id", data_type: "uuid", udt_name: "uuid", is_nullable: "NO" }),
279
- // A literal "author" column alongside the author_id foreign key.
280
- column({ table_name: "posts", column_name: "author", data_type: "text", udt_name: "text" }),
281
- column({ table_name: "posts", column_name: "author_id", data_type: "uuid", udt_name: "uuid" })
282
- ],
283
- [
284
- { table_name: "authors", column_name: "id" },
285
- { table_name: "posts", column_name: "id" }
286
- ],
287
- [{ table_name: "posts", column_name: "author_id", foreign_table_name: "authors", foreign_column_name: "id" }]
288
- );
289
- const tables = buildDrizzleTablesFromSchema(map);
290
- const rels = buildDrizzleRelationsFromSchema(map, tables);
291
-
292
- expect(rels.postsRelations).toBeUndefined();
293
- });
294
-
295
- it("returns nothing for a schema with no foreign keys", () => {
296
- const map = tablesMapOf(
297
- [column({ table_name: "posts", column_name: "id", data_type: "uuid", udt_name: "uuid", is_nullable: "NO" })],
298
- [{ table_name: "posts", column_name: "id" }]
299
- );
300
- expect(buildDrizzleRelationsFromSchema(map, buildDrizzleTablesFromSchema(map))).toEqual({});
301
- });
302
- });
@@ -1,293 +0,0 @@
1
- /**
2
- * Build drizzle tables at runtime from an introspected schema.
3
- *
4
- * A project with declared collections gets its drizzle tables from a generated `schema.generated.ts` that
5
- * the developer commits. BaaS mode has no such file — it points at a database
6
- * and serves it — so the equivalent table objects are constructed here from
7
- * `information_schema` metadata.
8
- *
9
- * These are handed to drizzle as its schema, which keeps the relational query
10
- * path (`db.query.*`) working; without them FetchService would fall back to
11
- * plain selects and lose relation loading.
12
- */
13
- import {
14
- bigint,
15
- boolean,
16
- char,
17
- cidr,
18
- customType,
19
- date,
20
- doublePrecision,
21
- geometry,
22
- inet,
23
- integer,
24
- interval,
25
- json,
26
- jsonb,
27
- line,
28
- macaddr,
29
- macaddr8,
30
- numeric,
31
- pgSchema,
32
- pgTable,
33
- point,
34
- primaryKey,
35
- real,
36
- smallint,
37
- text,
38
- time,
39
- timestamp,
40
- uuid,
41
- varchar,
42
- vector,
43
- type PgColumnBuilderBase,
44
- type PgTable
45
- } from "drizzle-orm/pg-core";
46
-
47
- import { relations, type Relations } from "drizzle-orm";
48
-
49
- import type { TableColumn, TableMeta } from "./introspect-db-logic";
50
-
51
- /** drizzle ships no bytea builder; binary must round-trip as a Buffer. */
52
- const bytea = customType<{ data: Buffer; driverData: Buffer }>({
53
- dataType: () => "bytea"
54
- });
55
-
56
- /**
57
- * Postgres stores a column's type modifier (varchar length, vector dimensions)
58
- * in `atttypmod`. For length-carrying string types it is length + VARHDRSZ(4);
59
- * for pgvector it is the dimension count as-is. -1 means unspecified.
60
- */
61
- function varlenLength(col: TableColumn): number | undefined {
62
- return col.atttypmod && col.atttypmod > 4 ? col.atttypmod - 4 : undefined;
63
- }
64
-
65
- /**
66
- * Map a Postgres type to a drizzle column builder, keyed on `udt_name` — the
67
- * concrete underlying type, which is exact where `data_type` reports umbrella
68
- * values like "ARRAY" or "USER-DEFINED".
69
- *
70
- * Unknown types fall back to `text`: the driver still reads and writes them,
71
- * with the value passing through as a string, which beats dropping the column.
72
- */
73
- function scalarBuilder(udtName: string, name: string, col: TableColumn): PgColumnBuilderBase {
74
- switch (udtName) {
75
- case "uuid":
76
- return uuid(name);
77
- case "bool":
78
- return boolean(name);
79
- case "int2":
80
- return smallint(name);
81
- case "int4":
82
- return integer(name);
83
- case "int8":
84
- return bigint(name, { mode: "number" });
85
- case "float4":
86
- return real(name);
87
- case "float8":
88
- return doublePrecision(name);
89
- case "numeric":
90
- case "money":
91
- return numeric(name);
92
- case "json":
93
- return json(name);
94
- case "jsonb":
95
- return jsonb(name);
96
- case "date":
97
- return date(name);
98
- case "time":
99
- return time(name);
100
- case "timetz":
101
- return time(name, { withTimezone: true });
102
- case "timestamp":
103
- return timestamp(name);
104
- case "timestamptz":
105
- return timestamp(name, { withTimezone: true });
106
- case "interval":
107
- return interval(name);
108
- case "bytea":
109
- return bytea(name);
110
- case "inet":
111
- return inet(name);
112
- case "cidr":
113
- return cidr(name);
114
- case "macaddr":
115
- return macaddr(name);
116
- case "macaddr8":
117
- return macaddr8(name);
118
- case "point":
119
- return point(name);
120
- case "line":
121
- return line(name);
122
- case "geometry":
123
- return geometry(name);
124
- case "vector": {
125
- // drizzle requires the dimension count; without it fall back to text
126
- // rather than declaring a vector of unknown width.
127
- const dimensions = col.atttypmod && col.atttypmod > 0 ? col.atttypmod : undefined;
128
- return dimensions ? vector(name, { dimensions }) : text(name);
129
- }
130
- case "bpchar": {
131
- const length = varlenLength(col);
132
- return length ? char(name, { length }) : char(name);
133
- }
134
- case "varchar": {
135
- const length = varlenLength(col);
136
- return length ? varchar(name, { length }) : text(name);
137
- }
138
- default:
139
- // Includes text, enums (pg enums are strings on the wire), citext,
140
- // geography, and anything else this driver hasn't met yet.
141
- return text(name);
142
- }
143
- }
144
-
145
- function columnBuilderFor(col: TableColumn): PgColumnBuilderBase {
146
- // Array types are named after their element with a leading underscore
147
- // (_int4 = int4[]), so the element mapping is reused verbatim.
148
- if (col.udt_name.startsWith("_")) {
149
- const element = scalarBuilder(col.udt_name.slice(1), col.column_name, col);
150
- return (element as unknown as { array(): PgColumnBuilderBase }).array();
151
- }
152
- return scalarBuilder(col.udt_name, col.column_name, col);
153
- }
154
-
155
- /**
156
- * Build one drizzle table per introspected table, keyed by table name.
157
- */
158
- export function buildDrizzleTablesFromSchema(
159
- tablesMap: Map<string, TableMeta>,
160
- pgSchemaName = "public"
161
- ): Record<string, PgTable> {
162
- const schema = pgSchemaName === "public" ? null : pgSchema(pgSchemaName);
163
- // The column set is only known at runtime, so drizzle's generic table
164
- // signature can't be satisfied statically; call it through a loose type.
165
- const createTable = (schema ? schema.table.bind(schema) : pgTable) as unknown as (
166
- name: string,
167
- columns: Record<string, PgColumnBuilderBase>,
168
- extras?: (self: Record<string, unknown>) => unknown[]
169
- ) => PgTable;
170
-
171
- const tables: Record<string, PgTable> = {};
172
-
173
- for (const [tableName, meta] of tablesMap) {
174
- const columns: Record<string, PgColumnBuilderBase> = {};
175
-
176
- for (const col of meta.columns) {
177
- let builder = columnBuilderFor(col);
178
-
179
- if (col.is_nullable === "NO") {
180
- builder = (builder as unknown as { notNull(): PgColumnBuilderBase }).notNull();
181
- }
182
- // Single-column primary keys are marked inline; composite keys are
183
- // declared in the table extras below.
184
- if (meta.pks.length === 1 && meta.pks[0] === col.column_name) {
185
- builder = (builder as unknown as { primaryKey(): PgColumnBuilderBase }).primaryKey();
186
- }
187
-
188
- columns[col.column_name] = builder;
189
- }
190
-
191
- const isComposite = meta.pks.length > 1;
192
- tables[tableName] = createTable(
193
- tableName,
194
- columns,
195
- isComposite
196
- ? (t) => [primaryKey({ columns: meta.pks.map((pk) => t[pk]) as never })]
197
- : undefined
198
- );
199
- }
200
-
201
- return tables;
202
- }
203
-
204
- /**
205
- * Build drizzle `relations()` for the foreign keys, so the relational query
206
- * path can actually load them.
207
- *
208
- * FetchService asks drizzle for `with: { <key>: true }`, keyed by the relation
209
- * property on the collection. Tables alone don't satisfy that — without these,
210
- * `?include=author` silently returns the raw `author_id` and no author.
211
- *
212
- * The keys here must match `buildRelations` in introspect-runtime.ts, which is
213
- * what names the collection's relation properties.
214
- */
215
- export function buildDrizzleRelationsFromSchema(
216
- tablesMap: Map<string, TableMeta>,
217
- tables: Record<string, PgTable>
218
- ): Record<string, Relations> {
219
- /** Owning side, per table: the `one()` relations from its foreign keys. */
220
- const owning = new Map<string, { key: string; targetTable: string; fkColumn: string; targetColumn: string; relationName: string }[]>();
221
- /** Inverse side, per referenced table: the matching `many()` back-references. */
222
- const inverse = new Map<string, { key: string; sourceTable: string; relationName: string }[]>();
223
-
224
- for (const [tableName, meta] of tablesMap) {
225
- if (!tables[tableName]) continue;
226
- const columnNames = new Set(meta.columns.map((c) => c.column_name));
227
-
228
- for (const fk of meta.fks) {
229
- if (!tables[fk.foreign_table_name]) continue;
230
-
231
- // Mirrors buildRelations in introspect-runtime: author_id -> author,
232
- // falling back to the target table when the column name carries no hint.
233
- let key = fk.column_name.replace(/_id$/, "");
234
- if (meta.pks.includes(fk.column_name) && key === fk.column_name) {
235
- key = fk.foreign_table_name;
236
- }
237
- // A relation key must not shadow a real column.
238
- if (columnNames.has(key)) continue;
239
-
240
- // Pairs the two sides. Drizzle matches a named one() to the many()
241
- // carrying the same name, and disambiguates multiple foreign keys
242
- // into the same table.
243
- const relationName = `${tableName}_${fk.column_name}`;
244
-
245
- owning.set(tableName, [
246
- ...(owning.get(tableName) ?? []),
247
- { key, targetTable: fk.foreign_table_name, fkColumn: fk.column_name, targetColumn: fk.foreign_column_name, relationName }
248
- ]);
249
-
250
- const backKey = tableName;
251
- const targetColumns = new Set((tablesMap.get(fk.foreign_table_name)?.columns ?? []).map((c) => c.column_name));
252
- if (targetColumns.has(backKey)) continue;
253
-
254
- inverse.set(fk.foreign_table_name, [
255
- ...(inverse.get(fk.foreign_table_name) ?? []),
256
- { key: backKey, sourceTable: tableName, relationName }
257
- ]);
258
- }
259
- }
260
-
261
- const built: Record<string, Relations> = {};
262
-
263
- for (const tableName of tablesMap.keys()) {
264
- const table = tables[tableName];
265
- const ones = owning.get(tableName) ?? [];
266
- const manys = inverse.get(tableName) ?? [];
267
- if (!table || (ones.length === 0 && manys.length === 0)) continue;
268
-
269
- built[`${tableName}Relations`] = relations(table, ({ one, many }) => {
270
- const map: Record<string, unknown> = {};
271
-
272
- for (const rel of ones) {
273
- map[rel.key] = one(tables[rel.targetTable], {
274
- fields: [(table as unknown as Record<string, never>)[rel.fkColumn]],
275
- references: [(tables[rel.targetTable] as unknown as Record<string, never>)[rel.targetColumn]],
276
- relationName: rel.relationName
277
- });
278
- }
279
-
280
- // Drizzle needs the inverse of every named one(); without it,
281
- // normalizeRelation throws and the relational path is dead.
282
- for (const rel of manys) {
283
- // An inverse must not collide with an owning key on this table.
284
- if (map[rel.key]) continue;
285
- map[rel.key] = many(tables[rel.sourceTable], { relationName: rel.relationName });
286
- }
287
-
288
- return map as never;
289
- });
290
- }
291
-
292
- return built;
293
- }