@rebasepro/server-postgresql 0.6.1 → 0.7.0

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 (75) hide show
  1. package/package.json +24 -18
  2. package/src/PostgresBackendDriver.ts +65 -44
  3. package/src/PostgresBootstrapper.ts +34 -2
  4. package/src/auth/ensure-tables.ts +56 -1
  5. package/src/auth/services.ts +94 -1
  6. package/src/cli-errors.ts +162 -0
  7. package/src/cli-helpers.ts +183 -0
  8. package/src/cli.ts +198 -251
  9. package/src/schema/auth-default-policies.ts +90 -0
  10. package/src/schema/auth-schema.ts +25 -2
  11. package/src/schema/doctor.ts +2 -4
  12. package/src/schema/generate-drizzle-schema-logic.ts +4 -3
  13. package/src/schema/generate-drizzle-schema.ts +3 -5
  14. package/src/schema/generate-postgres-ddl-logic.ts +524 -0
  15. package/src/schema/generate-postgres-ddl.ts +116 -0
  16. package/src/services/EntityPersistService.ts +10 -8
  17. package/src/services/entityService.ts +28 -3
  18. package/src/utils/pg-error-utils.ts +16 -0
  19. package/src/utils/table-classification.ts +16 -0
  20. package/src/websocket.ts +9 -0
  21. package/test/auth-default-policies.test.ts +89 -0
  22. package/test/cli-helpers-extended.test.ts +324 -0
  23. package/test/cli-helpers.test.ts +59 -0
  24. package/test/connection.test.ts +292 -0
  25. package/test/databasePoolManager.test.ts +289 -0
  26. package/test/doctor-extended.test.ts +443 -0
  27. package/test/e2e/db-e2e.test.ts +293 -0
  28. package/test/e2e/pg-setup.ts +79 -0
  29. package/test/entity-persist-composite-keys.test.ts +451 -0
  30. package/test/generate-postgres-ddl-edge-cases.test.ts +716 -0
  31. package/test/generate-postgres-ddl.test.ts +300 -0
  32. package/test/mfa-service.test.ts +544 -0
  33. package/test/pg-error-utils.test.ts +50 -1
  34. package/test/realtimeService-channels.test.ts +696 -0
  35. package/test/unmapped-tables-safety.test.ts +55 -342
  36. package/vitest.e2e.config.ts +10 -0
  37. package/build-errors.txt +0 -37
  38. package/dist/PostgresAdapter.d.ts +0 -6
  39. package/dist/PostgresBackendDriver.d.ts +0 -110
  40. package/dist/PostgresBootstrapper.d.ts +0 -46
  41. package/dist/auth/ensure-tables.d.ts +0 -10
  42. package/dist/auth/services.d.ts +0 -231
  43. package/dist/cli.d.ts +0 -1
  44. package/dist/collections/PostgresCollectionRegistry.d.ts +0 -47
  45. package/dist/connection.d.ts +0 -65
  46. package/dist/data-transformer.d.ts +0 -55
  47. package/dist/databasePoolManager.d.ts +0 -20
  48. package/dist/history/HistoryService.d.ts +0 -71
  49. package/dist/history/ensure-history-table.d.ts +0 -7
  50. package/dist/index.d.ts +0 -14
  51. package/dist/index.es.js +0 -10803
  52. package/dist/index.es.js.map +0 -1
  53. package/dist/interfaces.d.ts +0 -18
  54. package/dist/schema/auth-schema.d.ts +0 -2149
  55. package/dist/schema/doctor-cli.d.ts +0 -2
  56. package/dist/schema/doctor.d.ts +0 -52
  57. package/dist/schema/generate-drizzle-schema-logic.d.ts +0 -2
  58. package/dist/schema/generate-drizzle-schema.d.ts +0 -1
  59. package/dist/schema/introspect-db-inference.d.ts +0 -5
  60. package/dist/schema/introspect-db-logic.d.ts +0 -118
  61. package/dist/schema/introspect-db.d.ts +0 -1
  62. package/dist/schema/test-schema.d.ts +0 -24
  63. package/dist/services/BranchService.d.ts +0 -47
  64. package/dist/services/EntityFetchService.d.ts +0 -214
  65. package/dist/services/EntityPersistService.d.ts +0 -40
  66. package/dist/services/RelationService.d.ts +0 -98
  67. package/dist/services/entity-helpers.d.ts +0 -38
  68. package/dist/services/entityService.d.ts +0 -110
  69. package/dist/services/index.d.ts +0 -4
  70. package/dist/services/realtimeService.d.ts +0 -220
  71. package/dist/types.d.ts +0 -3
  72. package/dist/utils/drizzle-conditions.d.ts +0 -138
  73. package/dist/utils/pg-array-null-patch.d.ts +0 -16
  74. package/dist/utils/pg-error-utils.d.ts +0 -55
  75. package/dist/websocket.d.ts +0 -11
@@ -0,0 +1,300 @@
1
+ import { EntityCollection, PostgresCollection } from "@rebasepro/types";
2
+ import { generatePostgresDdl } from "../src/schema/generate-postgres-ddl-logic";
3
+
4
+ describe("generatePostgresDdl", () => {
5
+ const cleanDdl = (ddl: string) => {
6
+ return ddl
7
+ .replace(/--.*$/gm, "") // Remove single-line SQL comments
8
+ .replace(/\n{2,}/g, "\n") // Collapse multiple newlines
9
+ .replace(/\s+/g, " ") // Collapse whitespace
10
+ .trim();
11
+ };
12
+
13
+ it("should generate a simple table with basic types", async () => {
14
+ const collections: EntityCollection[] = [
15
+ {
16
+ slug: "products",
17
+ table: "products",
18
+ name: "Products",
19
+ properties: {
20
+ name: { type: "string", validation: { required: true } },
21
+ price: { type: "number" },
22
+ available: { type: "boolean" }
23
+ }
24
+ }
25
+ ];
26
+
27
+ const result = await generatePostgresDdl(collections);
28
+ const cleanResult = cleanDdl(result);
29
+
30
+ expect(cleanResult).toContain("CREATE TABLE \"public\".\"products\" (");
31
+ expect(cleanResult).toContain("\"id\" VARCHAR(255) PRIMARY KEY");
32
+ expect(cleanResult).toContain("\"name\" VARCHAR(255) NOT NULL");
33
+ expect(cleanResult).toContain("\"price\" NUMERIC");
34
+ expect(cleanResult).toContain("\"available\" BOOLEAN");
35
+ expect(cleanResult).toContain("ALTER TABLE \"public\".\"products\" ENABLE ROW LEVEL SECURITY");
36
+ });
37
+
38
+ it("should generate custom schemas", async () => {
39
+ const collections: PostgresCollection[] = [
40
+ {
41
+ slug: "products",
42
+ table: "products",
43
+ name: "Products",
44
+ schema: "inventory",
45
+ properties: {
46
+ name: { type: "string" }
47
+ }
48
+ }
49
+ ];
50
+
51
+ const result = await generatePostgresDdl(collections);
52
+ expect(result).toContain("CREATE SCHEMA IF NOT EXISTS \"inventory\";");
53
+ expect(result).toContain("CREATE TABLE \"inventory\".\"products\"");
54
+ });
55
+
56
+ it("should generate enum types", async () => {
57
+ const collections: EntityCollection[] = [
58
+ {
59
+ slug: "orders",
60
+ table: "orders",
61
+ name: "Orders",
62
+ properties: {
63
+ status: {
64
+ type: "string",
65
+ enum: ["pending", "shipped", "delivered"]
66
+ }
67
+ }
68
+ }
69
+ ];
70
+
71
+ const result = await generatePostgresDdl(collections);
72
+ expect(result).toContain("CREATE TYPE \"public\".\"orders_status\" AS ENUM ('pending', 'shipped', 'delivered');");
73
+ expect(result).toContain("\"status\" \"public\".\"orders_status\"");
74
+ });
75
+
76
+ it("should generate one-to-many relation foreign keys", async () => {
77
+ const usersCollection: EntityCollection = {
78
+ slug: "users",
79
+ table: "users",
80
+ name: "Users",
81
+ properties: {
82
+ name: { type: "string" }
83
+ }
84
+ };
85
+ const postsCollection: EntityCollection = {
86
+ slug: "posts",
87
+ table: "posts",
88
+ name: "Posts",
89
+ properties: {
90
+ title: { type: "string" },
91
+ author: { type: "relation", relationName: "author" }
92
+ },
93
+ relations: [
94
+ {
95
+ relationName: "author",
96
+ target: () => usersCollection,
97
+ cardinality: "one",
98
+ localKey: "author_id",
99
+ onDelete: "set null"
100
+ }
101
+ ]
102
+ };
103
+
104
+ const result = await generatePostgresDdl([usersCollection, postsCollection]);
105
+ const cleanResult = cleanDdl(result);
106
+
107
+ expect(cleanResult).toContain("\"author_id\" VARCHAR(255)");
108
+ expect(cleanResult).toContain("ALTER TABLE \"public\".\"posts\" ADD CONSTRAINT \"posts_author_id_fkey\" FOREIGN KEY (\"author_id\") REFERENCES \"public\".\"users\" (\"id\") ON DELETE SET NULL;");
109
+ });
110
+
111
+ it("should generate many-to-many junction tables", async () => {
112
+ const postsCollection: EntityCollection = {
113
+ slug: "posts",
114
+ table: "posts",
115
+ name: "Posts",
116
+ properties: {
117
+ title: { type: "string" },
118
+ tags: { type: "relation", relationName: "tags" }
119
+ },
120
+ relations: [
121
+ {
122
+ relationName: "tags",
123
+ target: () => tagsCollection,
124
+ cardinality: "many",
125
+ direction: "owning",
126
+ through: {
127
+ table: "posts_to_tags",
128
+ sourceColumn: "post_id",
129
+ targetColumn: "tag_id"
130
+ }
131
+ }
132
+ ]
133
+ };
134
+ const tagsCollection: EntityCollection = {
135
+ slug: "tags",
136
+ table: "tags",
137
+ name: "Tags",
138
+ properties: {
139
+ name: { type: "string" }
140
+ }
141
+ };
142
+
143
+ const result = await generatePostgresDdl([postsCollection, tagsCollection]);
144
+ const cleanResult = cleanDdl(result);
145
+
146
+ expect(cleanResult).toContain("CREATE TABLE \"public\".\"posts_to_tags\" (");
147
+ expect(cleanResult).toContain("\"post_id\" VARCHAR(255) NOT NULL");
148
+ expect(cleanResult).toContain("\"tag_id\" VARCHAR(255) NOT NULL");
149
+ expect(cleanResult).toContain("ALTER TABLE \"public\".\"posts_to_tags\" ADD CONSTRAINT \"posts_to_tags_post_id_fkey\" FOREIGN KEY (\"post_id\") REFERENCES \"public\".\"posts\" (\"id\") ON DELETE CASCADE;");
150
+ expect(cleanResult).toContain("ALTER TABLE \"public\".\"posts_to_tags\" ADD CONSTRAINT \"posts_to_tags_tag_id_fkey\" FOREIGN KEY (\"tag_id\") REFERENCES \"public\".\"tags\" (\"id\") ON DELETE CASCADE;");
151
+ expect(cleanResult).toContain("PRIMARY KEY (\"post_id\", \"tag_id\")");
152
+ });
153
+
154
+ it("should generate column default values", async () => {
155
+ const collections: EntityCollection[] = [
156
+ {
157
+ slug: "users_uuid",
158
+ table: "users_uuid",
159
+ name: "Users UUID",
160
+ properties: {
161
+ uuid_id: { type: "string", isId: "uuid" },
162
+ created: { type: "date", autoValue: "on_create" }
163
+ }
164
+ },
165
+ {
166
+ slug: "users_cuid",
167
+ table: "users_cuid",
168
+ name: "Users CUID",
169
+ properties: {
170
+ cuid_id: { type: "string", isId: "cuid" }
171
+ }
172
+ }
173
+ ];
174
+
175
+ const result = await generatePostgresDdl(collections);
176
+ const cleanResult = cleanDdl(result);
177
+
178
+ expect(cleanResult).toContain("\"uuid_id\" UUID PRIMARY KEY DEFAULT gen_random_uuid()");
179
+ expect(cleanResult).toContain("\"cuid_id\" VARCHAR(255) PRIMARY KEY DEFAULT cuid()");
180
+ expect(cleanResult).toContain("\"created\" TIMESTAMP WITH TIME ZONE DEFAULT now()");
181
+ });
182
+
183
+ it("should generate RLS policies with ownerField and roles", async () => {
184
+ const collections: PostgresCollection[] = [
185
+ {
186
+ slug: "posts",
187
+ table: "posts",
188
+ name: "Posts",
189
+ properties: {
190
+ title: { type: "string" },
191
+ user_id: { type: "string" }
192
+ },
193
+ securityRules: [
194
+ {
195
+ operation: "select",
196
+ access: "public"
197
+ },
198
+ {
199
+ operation: "update",
200
+ ownerField: "user_id",
201
+ roles: ["admin", "editor"]
202
+ }
203
+ ]
204
+ }
205
+ ];
206
+
207
+ const result = await generatePostgresDdl(collections);
208
+ const cleanResult = cleanDdl(result);
209
+
210
+ expect(cleanResult).toContain("CREATE POLICY \"posts_select_");
211
+ expect(cleanResult).toContain("USING (true)");
212
+ expect(cleanResult).toContain("CREATE POLICY \"posts_update_");
213
+ expect(cleanResult).toContain("USING ((user_id = auth.uid()) AND (string_to_array(auth.roles(), ',') && ARRAY['admin','editor']))");
214
+ });
215
+
216
+ it("should generate advanced column types, constraints, and default values", async () => {
217
+ const collections: EntityCollection[] = [
218
+ {
219
+ slug: "items",
220
+ table: "items",
221
+ name: "Items",
222
+ properties: {
223
+ price_int: { type: "number", validation: { integer: true } },
224
+ price_num: { type: "number" },
225
+ big_val: { type: "number", columnType: "bigint" },
226
+ raw_date: { type: "date", columnType: "date" },
227
+ raw_time: { type: "date", columnType: "time" },
228
+ raw_map: { type: "map", columnType: "json" },
229
+ raw_vector: { type: "vector", dimensions: 1536 },
230
+ raw_binary: { type: "binary" },
231
+ text_arr: { type: "array", of: { type: "string" } },
232
+ int_arr: { type: "array", of: { type: "number", validation: { integer: true } } },
233
+ bool_arr: { type: "array", of: { type: "boolean" } },
234
+ num_arr: { type: "array", of: { type: "number" } },
235
+ ref_val: { type: "reference", path: "other_collection" },
236
+ req_val: { type: "string", validation: { required: true } },
237
+ uniq_val: { type: "string", validation: { unique: true } }
238
+ }
239
+ },
240
+ {
241
+ slug: "other_collection",
242
+ table: "other_collection",
243
+ name: "Other Collection",
244
+ properties: {
245
+ id: { type: "string", isId: "uuid" }
246
+ }
247
+ }
248
+ ];
249
+
250
+ const result = await generatePostgresDdl(collections);
251
+ const cleanResult = cleanDdl(result);
252
+
253
+ expect(cleanResult).toContain("\"price_int\" INTEGER");
254
+ expect(cleanResult).toContain("\"price_num\" NUMERIC");
255
+ expect(cleanResult).toContain("\"big_val\" BIGINT");
256
+ expect(cleanResult).toContain("\"raw_date\" DATE");
257
+ expect(cleanResult).toContain("\"raw_time\" TIME");
258
+ expect(cleanResult).toContain("\"raw_map\" JSON");
259
+ expect(cleanResult).toContain("\"raw_vector\" VECTOR(1536)");
260
+ expect(cleanResult).toContain("\"raw_binary\" BYTEA");
261
+ expect(cleanResult).toContain("\"text_arr\" TEXT[]");
262
+ expect(cleanResult).toContain("\"int_arr\" INTEGER[]");
263
+ expect(cleanResult).toContain("\"bool_arr\" BOOLEAN[]");
264
+ expect(cleanResult).toContain("\"num_arr\" NUMERIC[]");
265
+ expect(cleanResult).toContain("\"ref_val\" UUID");
266
+ expect(cleanResult).toContain("ALTER TABLE \"public\".\"items\" ADD CONSTRAINT \"items_ref_val_fkey\" FOREIGN KEY (\"ref_val\") REFERENCES \"public\".\"other_collection\" (\"id\") ON DELETE SET NULL;");
267
+ expect(cleanResult).toContain("\"req_val\" VARCHAR(255) NOT NULL");
268
+ expect(cleanResult).toContain("\"uniq_val\" VARCHAR(255) UNIQUE");
269
+ });
270
+
271
+ it("should generate RLS policies with restrictive mode, multiple operations, and custom using/check clauses", async () => {
272
+ const collections: PostgresCollection[] = [
273
+ {
274
+ slug: "documents",
275
+ table: "documents",
276
+ name: "Documents",
277
+ properties: {
278
+ title: { type: "string" },
279
+ owner: { type: "string" }
280
+ },
281
+ securityRules: [
282
+ {
283
+ name: "restrict_access",
284
+ mode: "restrictive",
285
+ operations: ["select", "update"],
286
+ using: "owner = auth.uid()",
287
+ withCheck: "owner = auth.uid()",
288
+ pgRoles: ["authenticated"]
289
+ }
290
+ ]
291
+ }
292
+ ];
293
+
294
+ const result = await generatePostgresDdl(collections);
295
+ const cleanResult = cleanDdl(result);
296
+
297
+ expect(cleanResult).toContain("CREATE POLICY \"restrict_access_select\" ON \"public\".\"documents\" AS RESTRICTIVE FOR SELECT TO \"authenticated\" USING (owner = auth.uid())");
298
+ expect(cleanResult).toContain("CREATE POLICY \"restrict_access_update\" ON \"public\".\"documents\" AS RESTRICTIVE FOR UPDATE TO \"authenticated\" USING (owner = auth.uid()) WITH CHECK (owner = auth.uid())");
299
+ });
300
+ });