@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.
- package/package.json +24 -18
- package/src/PostgresBackendDriver.ts +65 -44
- package/src/PostgresBootstrapper.ts +34 -2
- package/src/auth/ensure-tables.ts +56 -1
- package/src/auth/services.ts +94 -1
- package/src/cli-errors.ts +162 -0
- package/src/cli-helpers.ts +183 -0
- package/src/cli.ts +198 -251
- package/src/schema/auth-default-policies.ts +90 -0
- package/src/schema/auth-schema.ts +25 -2
- package/src/schema/doctor.ts +2 -4
- package/src/schema/generate-drizzle-schema-logic.ts +4 -3
- package/src/schema/generate-drizzle-schema.ts +3 -5
- package/src/schema/generate-postgres-ddl-logic.ts +524 -0
- package/src/schema/generate-postgres-ddl.ts +116 -0
- package/src/services/EntityPersistService.ts +10 -8
- package/src/services/entityService.ts +28 -3
- package/src/utils/pg-error-utils.ts +16 -0
- package/src/utils/table-classification.ts +16 -0
- package/src/websocket.ts +9 -0
- package/test/auth-default-policies.test.ts +89 -0
- package/test/cli-helpers-extended.test.ts +324 -0
- package/test/cli-helpers.test.ts +59 -0
- package/test/connection.test.ts +292 -0
- package/test/databasePoolManager.test.ts +289 -0
- package/test/doctor-extended.test.ts +443 -0
- package/test/e2e/db-e2e.test.ts +293 -0
- package/test/e2e/pg-setup.ts +79 -0
- package/test/entity-persist-composite-keys.test.ts +451 -0
- package/test/generate-postgres-ddl-edge-cases.test.ts +716 -0
- package/test/generate-postgres-ddl.test.ts +300 -0
- package/test/mfa-service.test.ts +544 -0
- package/test/pg-error-utils.test.ts +50 -1
- package/test/realtimeService-channels.test.ts +696 -0
- package/test/unmapped-tables-safety.test.ts +55 -342
- package/vitest.e2e.config.ts +10 -0
- package/build-errors.txt +0 -37
- package/dist/PostgresAdapter.d.ts +0 -6
- package/dist/PostgresBackendDriver.d.ts +0 -110
- package/dist/PostgresBootstrapper.d.ts +0 -46
- package/dist/auth/ensure-tables.d.ts +0 -10
- package/dist/auth/services.d.ts +0 -231
- package/dist/cli.d.ts +0 -1
- package/dist/collections/PostgresCollectionRegistry.d.ts +0 -47
- package/dist/connection.d.ts +0 -65
- package/dist/data-transformer.d.ts +0 -55
- package/dist/databasePoolManager.d.ts +0 -20
- package/dist/history/HistoryService.d.ts +0 -71
- package/dist/history/ensure-history-table.d.ts +0 -7
- package/dist/index.d.ts +0 -14
- package/dist/index.es.js +0 -10803
- package/dist/index.es.js.map +0 -1
- package/dist/interfaces.d.ts +0 -18
- package/dist/schema/auth-schema.d.ts +0 -2149
- package/dist/schema/doctor-cli.d.ts +0 -2
- package/dist/schema/doctor.d.ts +0 -52
- package/dist/schema/generate-drizzle-schema-logic.d.ts +0 -2
- package/dist/schema/generate-drizzle-schema.d.ts +0 -1
- package/dist/schema/introspect-db-inference.d.ts +0 -5
- package/dist/schema/introspect-db-logic.d.ts +0 -118
- package/dist/schema/introspect-db.d.ts +0 -1
- package/dist/schema/test-schema.d.ts +0 -24
- package/dist/services/BranchService.d.ts +0 -47
- package/dist/services/EntityFetchService.d.ts +0 -214
- package/dist/services/EntityPersistService.d.ts +0 -40
- package/dist/services/RelationService.d.ts +0 -98
- package/dist/services/entity-helpers.d.ts +0 -38
- package/dist/services/entityService.d.ts +0 -110
- package/dist/services/index.d.ts +0 -4
- package/dist/services/realtimeService.d.ts +0 -220
- package/dist/types.d.ts +0 -3
- package/dist/utils/drizzle-conditions.d.ts +0 -138
- package/dist/utils/pg-array-null-patch.d.ts +0 -16
- package/dist/utils/pg-error-utils.d.ts +0 -55
- 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
|
+
});
|