@rebasepro/server-postgresql 0.0.1-canary.4d4fb3e → 0.0.1-canary.ca2cb6e
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/dist/common/src/collections/CollectionRegistry.d.ts +8 -0
- package/dist/common/src/util/entities.d.ts +22 -0
- package/dist/common/src/util/relations.d.ts +14 -4
- package/dist/common/src/util/resolutions.d.ts +1 -1
- package/dist/index.es.js +1254 -591
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1254 -591
- package/dist/index.umd.js.map +1 -1
- package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +17 -29
- package/dist/server-postgresql/src/auth/services.d.ts +7 -3
- package/dist/server-postgresql/src/collections/PostgresCollectionRegistry.d.ts +1 -1
- package/dist/server-postgresql/src/connection.d.ts +34 -1
- package/dist/server-postgresql/src/data-transformer.d.ts +26 -4
- package/dist/server-postgresql/src/databasePoolManager.d.ts +2 -2
- package/dist/server-postgresql/src/schema/auth-schema.d.ts +139 -38
- package/dist/server-postgresql/src/schema/doctor-cli.d.ts +2 -0
- package/dist/server-postgresql/src/schema/doctor.d.ts +43 -0
- package/dist/server-postgresql/src/schema/generate-drizzle-schema-logic.d.ts +1 -1
- package/dist/server-postgresql/src/schema/test-schema.d.ts +24 -0
- package/dist/server-postgresql/src/services/EntityFetchService.d.ts +22 -8
- package/dist/server-postgresql/src/services/EntityPersistService.d.ts +1 -1
- package/dist/server-postgresql/src/services/RelationService.d.ts +11 -5
- package/dist/server-postgresql/src/services/entity-helpers.d.ts +16 -2
- package/dist/server-postgresql/src/services/entityService.d.ts +8 -6
- package/dist/server-postgresql/src/services/realtimeService.d.ts +2 -0
- package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +2 -2
- package/dist/types/src/controllers/auth.d.ts +2 -0
- package/dist/types/src/controllers/client.d.ts +119 -7
- package/dist/types/src/controllers/collection_registry.d.ts +4 -3
- package/dist/types/src/controllers/customization_controller.d.ts +7 -1
- package/dist/types/src/controllers/data.d.ts +34 -7
- package/dist/types/src/controllers/data_driver.d.ts +20 -28
- package/dist/types/src/controllers/database_admin.d.ts +2 -2
- package/dist/types/src/controllers/email.d.ts +34 -0
- package/dist/types/src/controllers/index.d.ts +1 -0
- package/dist/types/src/controllers/local_config_persistence.d.ts +4 -4
- package/dist/types/src/controllers/navigation.d.ts +5 -5
- package/dist/types/src/controllers/registry.d.ts +6 -3
- package/dist/types/src/controllers/side_entity_controller.d.ts +7 -6
- package/dist/types/src/controllers/storage.d.ts +24 -26
- package/dist/types/src/rebase_context.d.ts +8 -4
- package/dist/types/src/types/backend.d.ts +4 -1
- package/dist/types/src/types/builders.d.ts +5 -4
- package/dist/types/src/types/chips.d.ts +1 -1
- package/dist/types/src/types/collections.d.ts +169 -125
- package/dist/types/src/types/cron.d.ts +102 -0
- package/dist/types/src/types/data_source.d.ts +1 -1
- package/dist/types/src/types/entity_actions.d.ts +8 -8
- package/dist/types/src/types/entity_callbacks.d.ts +15 -15
- package/dist/types/src/types/entity_link_builder.d.ts +1 -1
- package/dist/types/src/types/entity_overrides.d.ts +2 -1
- package/dist/types/src/types/entity_views.d.ts +8 -8
- package/dist/types/src/types/export_import.d.ts +3 -3
- package/dist/types/src/types/index.d.ts +1 -0
- package/dist/types/src/types/plugins.d.ts +72 -18
- package/dist/types/src/types/properties.d.ts +118 -33
- package/dist/types/src/types/relations.d.ts +1 -1
- package/dist/types/src/types/slots.d.ts +30 -6
- package/dist/types/src/types/translations.d.ts +44 -0
- package/dist/types/src/types/user_management_delegate.d.ts +1 -0
- package/drizzle-test/0000_woozy_junta.sql +6 -0
- package/drizzle-test/0001_youthful_arachne.sql +1 -0
- package/drizzle-test/0002_lively_dragon_lord.sql +2 -0
- package/drizzle-test/0003_mean_king_cobra.sql +2 -0
- package/drizzle-test/meta/0000_snapshot.json +47 -0
- package/drizzle-test/meta/0001_snapshot.json +48 -0
- package/drizzle-test/meta/0002_snapshot.json +38 -0
- package/drizzle-test/meta/0003_snapshot.json +48 -0
- package/drizzle-test/meta/_journal.json +34 -0
- package/drizzle-test-out/0000_tan_trauma.sql +6 -0
- package/drizzle-test-out/0001_rapid_drax.sql +1 -0
- package/drizzle-test-out/meta/0000_snapshot.json +44 -0
- package/drizzle-test-out/meta/0001_snapshot.json +54 -0
- package/drizzle-test-out/meta/_journal.json +20 -0
- package/drizzle.test.config.ts +10 -0
- package/package.json +88 -89
- package/scratch.ts +41 -0
- package/src/PostgresBackendDriver.ts +63 -79
- package/src/PostgresBootstrapper.ts +7 -8
- package/src/auth/ensure-tables.ts +158 -86
- package/src/auth/services.ts +109 -50
- package/src/cli.ts +259 -16
- package/src/collections/PostgresCollectionRegistry.ts +6 -6
- package/src/connection.ts +70 -48
- package/src/data-transformer.ts +155 -116
- package/src/databasePoolManager.ts +6 -5
- package/src/history/HistoryService.ts +3 -12
- package/src/interfaces.ts +3 -3
- package/src/schema/auth-schema.ts +26 -3
- package/src/schema/doctor-cli.ts +47 -0
- package/src/schema/doctor.ts +595 -0
- package/src/schema/generate-drizzle-schema-logic.ts +204 -57
- package/src/schema/generate-drizzle-schema.ts +6 -6
- package/src/schema/test-schema.ts +11 -0
- package/src/services/BranchService.ts +5 -5
- package/src/services/EntityFetchService.ts +317 -188
- package/src/services/EntityPersistService.ts +15 -17
- package/src/services/RelationService.ts +299 -37
- package/src/services/entity-helpers.ts +39 -13
- package/src/services/entityService.ts +11 -9
- package/src/services/realtimeService.ts +58 -29
- package/src/utils/drizzle-conditions.ts +25 -24
- package/src/websocket.ts +52 -21
- package/test/auth-services.test.ts +131 -39
- package/test/batch-many-to-many-regression.test.ts +573 -0
- package/test/branchService.test.ts +22 -12
- package/test/data-transformer-hardening.test.ts +417 -0
- package/test/data-transformer.test.ts +175 -0
- package/test/doctor.test.ts +182 -0
- package/test/entityService.errors.test.ts +31 -16
- package/test/entityService.relations.test.ts +155 -59
- package/test/entityService.subcollection-search.test.ts +107 -57
- package/test/entityService.test.ts +105 -47
- package/test/generate-drizzle-schema.test.ts +262 -69
- package/test/historyService.test.ts +31 -16
- package/test/n-plus-one-regression.test.ts +314 -0
- package/test/postgresDataDriver.test.ts +260 -168
- package/test/realtimeService.test.ts +70 -39
- package/test/relation-pipeline-gaps.test.ts +637 -0
- package/test/relations.test.ts +492 -39
- package/test-drizzle-bug.ts +18 -0
- package/test-drizzle-out/0000_cultured_freak.sql +7 -0
- package/test-drizzle-out/0001_tiresome_professor_monster.sql +1 -0
- package/test-drizzle-out/meta/0000_snapshot.json +55 -0
- package/test-drizzle-out/meta/0001_snapshot.json +63 -0
- package/test-drizzle-out/meta/_journal.json +20 -0
- package/test-drizzle-prompt.sh +2 -0
- package/test-policy-prompt.sh +3 -0
- package/test-programmatic.ts +30 -0
- package/test-programmatic2.ts +59 -0
- package/test-schema-no-policies.ts +12 -0
- package/test_drizzle_mock.js +2 -2
- package/test_find_changed.mjs +3 -1
- package/test_hash.js +14 -0
- package/tsconfig.json +1 -1
- package/vite.config.ts +5 -5
|
@@ -20,9 +20,10 @@ describe("generateDrizzleSchema", () => {
|
|
|
20
20
|
table: "products",
|
|
21
21
|
name: "Products",
|
|
22
22
|
properties: {
|
|
23
|
-
name: { type: "string",
|
|
23
|
+
name: { type: "string",
|
|
24
|
+
validation: { required: true } },
|
|
24
25
|
price: { type: "number" },
|
|
25
|
-
available: { type: "boolean" }
|
|
26
|
+
available: { type: "boolean" }
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
];
|
|
@@ -52,7 +53,8 @@ describe("generateDrizzleSchema", () => {
|
|
|
52
53
|
name: "Posts",
|
|
53
54
|
properties: {
|
|
54
55
|
title: { type: "string" },
|
|
55
|
-
author: { type: "relation",
|
|
56
|
+
author: { type: "relation",
|
|
57
|
+
relationName: "author" }
|
|
56
58
|
},
|
|
57
59
|
relations: [
|
|
58
60
|
{
|
|
@@ -69,7 +71,7 @@ describe("generateDrizzleSchema", () => {
|
|
|
69
71
|
const cleanResult = cleanSchema(result);
|
|
70
72
|
|
|
71
73
|
expect(cleanResult).toContain("author_id: varchar(\"author_id\").references(() => users.id, { onDelete: \"set null\" })");
|
|
72
|
-
const expectedRelation =
|
|
74
|
+
const expectedRelation = "export const postsRelations = drizzleRelations(posts, ({ one, many }) => ({ \"author\": one(users, { fields: [posts.author_id], references: [users.id], relationName: \"posts_author_id\" }) }));";
|
|
73
75
|
expect(cleanResult).toContain(cleanSchema(expectedRelation));
|
|
74
76
|
});
|
|
75
77
|
|
|
@@ -80,7 +82,8 @@ describe("generateDrizzleSchema", () => {
|
|
|
80
82
|
name: "Posts",
|
|
81
83
|
properties: {
|
|
82
84
|
title: { type: "string" },
|
|
83
|
-
tags: { type: "relation",
|
|
85
|
+
tags: { type: "relation",
|
|
86
|
+
relationName: "tags" }
|
|
84
87
|
},
|
|
85
88
|
relations: [
|
|
86
89
|
{
|
|
@@ -112,15 +115,18 @@ describe("generateDrizzleSchema", () => {
|
|
|
112
115
|
expect(cleanResult).toContain("post_id: varchar(\"post_id\").notNull().references(() => posts.id, { onDelete: \"cascade\" })");
|
|
113
116
|
expect(cleanResult).toContain("tag_id: varchar(\"tag_id\").notNull().references(() => tags.id, { onDelete: \"cascade\" })");
|
|
114
117
|
expect(cleanResult).toContain("(table) => ({ pk: primaryKey({ columns: [table.post_id, table.tag_id] }) })");
|
|
115
|
-
expect(cleanResult).toContain("export const postsRelations = drizzleRelations(posts, ({ one, many }) => ({ tags: many(postsToTags, { relationName: \"tags\" }) }));");
|
|
118
|
+
expect(cleanResult).toContain("export const postsRelations = drizzleRelations(posts, ({ one, many }) => ({ \"tags\": many(postsToTags, { relationName: \"tags\" }) }));");
|
|
116
119
|
});
|
|
117
120
|
|
|
118
121
|
describe("generateDrizzleSchema Column Types", () => {
|
|
119
|
-
|
|
122
|
+
|
|
120
123
|
describe("String Property", () => {
|
|
121
124
|
it("should default to varchar if no columnType or isId is specified", async () => {
|
|
122
125
|
const collections: EntityCollection[] = [{
|
|
123
|
-
slug: "texts",
|
|
126
|
+
slug: "texts",
|
|
127
|
+
table: "texts",
|
|
128
|
+
name: "Texts",
|
|
129
|
+
properties: { t_default: { type: "string" } }
|
|
124
130
|
}];
|
|
125
131
|
const result = await generateSchema(collections);
|
|
126
132
|
expect(cleanSchema(result)).toContain("t_default: varchar(\"t_default\")");
|
|
@@ -132,9 +138,12 @@ describe("generateDrizzleSchema", () => {
|
|
|
132
138
|
table: "texts",
|
|
133
139
|
name: "Texts",
|
|
134
140
|
properties: {
|
|
135
|
-
t_text: { type: "string",
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
t_text: { type: "string",
|
|
142
|
+
columnType: "text" },
|
|
143
|
+
t_char: { type: "string",
|
|
144
|
+
columnType: "char" },
|
|
145
|
+
t_varchar: { type: "string",
|
|
146
|
+
columnType: "varchar" }
|
|
138
147
|
}
|
|
139
148
|
}];
|
|
140
149
|
const result = await generateSchema(collections);
|
|
@@ -147,23 +156,37 @@ describe("generateDrizzleSchema", () => {
|
|
|
147
156
|
|
|
148
157
|
it("should prioritize isId='uuid' over default varchar", async () => {
|
|
149
158
|
const collections: EntityCollection[] = [{
|
|
150
|
-
slug: "texts",
|
|
159
|
+
slug: "texts",
|
|
160
|
+
table: "texts",
|
|
161
|
+
name: "Texts",
|
|
162
|
+
properties: { t_uuid: { type: "string",
|
|
163
|
+
isId: "uuid" } }
|
|
151
164
|
}];
|
|
152
165
|
const result = await generateSchema(collections);
|
|
153
166
|
expect(cleanSchema(result)).toContain("t_uuid: uuid(\"t_uuid\").primaryKey().defaultRandom()");
|
|
154
167
|
});
|
|
155
|
-
|
|
168
|
+
|
|
156
169
|
it("should combine isId=true with columnType overrides", async () => {
|
|
157
170
|
const collections: EntityCollection[] = [{
|
|
158
|
-
slug: "texts",
|
|
171
|
+
slug: "texts",
|
|
172
|
+
table: "texts",
|
|
173
|
+
name: "Texts",
|
|
174
|
+
properties: { t_id: { type: "string",
|
|
175
|
+
isId: true,
|
|
176
|
+
columnType: "text" } }
|
|
159
177
|
}];
|
|
160
178
|
const result = await generateSchema(collections);
|
|
161
179
|
expect(cleanSchema(result)).toContain("t_id: text(\"t_id\").primaryKey()");
|
|
162
180
|
});
|
|
163
|
-
|
|
181
|
+
|
|
164
182
|
it("should respect validation.unique along with columnType", async () => {
|
|
165
183
|
const collections: EntityCollection[] = [{
|
|
166
|
-
slug: "texts",
|
|
184
|
+
slug: "texts",
|
|
185
|
+
table: "texts",
|
|
186
|
+
name: "Texts",
|
|
187
|
+
properties: { t_unique: { type: "string",
|
|
188
|
+
columnType: "char",
|
|
189
|
+
validation: { unique: true } } }
|
|
167
190
|
}];
|
|
168
191
|
const result = await generateSchema(collections);
|
|
169
192
|
expect(cleanSchema(result)).toContain("t_unique: char(\"t_unique\").unique()");
|
|
@@ -173,7 +196,10 @@ describe("generateDrizzleSchema", () => {
|
|
|
173
196
|
describe("Number Property", () => {
|
|
174
197
|
it("should default to numeric for normal numbers", async () => {
|
|
175
198
|
const collections: EntityCollection[] = [{
|
|
176
|
-
slug: "nums",
|
|
199
|
+
slug: "nums",
|
|
200
|
+
table: "nums",
|
|
201
|
+
name: "Nums",
|
|
202
|
+
properties: { n_def: { type: "number" } }
|
|
177
203
|
}];
|
|
178
204
|
const result = await generateSchema(collections);
|
|
179
205
|
expect(cleanSchema(result)).toContain("n_def: numeric(\"n_def\")");
|
|
@@ -181,7 +207,11 @@ describe("generateDrizzleSchema", () => {
|
|
|
181
207
|
|
|
182
208
|
it("should default to integer if validation.integer is true", async () => {
|
|
183
209
|
const collections: EntityCollection[] = [{
|
|
184
|
-
slug: "nums",
|
|
210
|
+
slug: "nums",
|
|
211
|
+
table: "nums",
|
|
212
|
+
name: "Nums",
|
|
213
|
+
properties: { n_int: { type: "number",
|
|
214
|
+
validation: { integer: true } } }
|
|
185
215
|
}];
|
|
186
216
|
const result = await generateSchema(collections);
|
|
187
217
|
expect(cleanSchema(result)).toContain("n_int: integer(\"n_int\")");
|
|
@@ -189,7 +219,11 @@ describe("generateDrizzleSchema", () => {
|
|
|
189
219
|
|
|
190
220
|
it("should default to integer if isId is true", async () => {
|
|
191
221
|
const collections: EntityCollection[] = [{
|
|
192
|
-
slug: "nums",
|
|
222
|
+
slug: "nums",
|
|
223
|
+
table: "nums",
|
|
224
|
+
name: "Nums",
|
|
225
|
+
properties: { n_id: { type: "number",
|
|
226
|
+
isId: true } }
|
|
193
227
|
}];
|
|
194
228
|
const result = await generateSchema(collections);
|
|
195
229
|
expect(cleanSchema(result)).toContain("n_id: integer(\"n_id\").primaryKey()");
|
|
@@ -201,13 +235,20 @@ describe("generateDrizzleSchema", () => {
|
|
|
201
235
|
table: "numbers",
|
|
202
236
|
name: "Numbers",
|
|
203
237
|
properties: {
|
|
204
|
-
n_int: { type: "number",
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
238
|
+
n_int: { type: "number",
|
|
239
|
+
columnType: "integer" },
|
|
240
|
+
n_real: { type: "number",
|
|
241
|
+
columnType: "real" },
|
|
242
|
+
n_dp: { type: "number",
|
|
243
|
+
columnType: "double precision" },
|
|
244
|
+
n_num: { type: "number",
|
|
245
|
+
columnType: "numeric" },
|
|
246
|
+
n_bigint: { type: "number",
|
|
247
|
+
columnType: "bigint" },
|
|
248
|
+
n_serial: { type: "number",
|
|
249
|
+
columnType: "serial" },
|
|
250
|
+
n_bigserial: { type: "number",
|
|
251
|
+
columnType: "bigserial" }
|
|
211
252
|
}
|
|
212
253
|
}];
|
|
213
254
|
const result = await generateSchema(collections);
|
|
@@ -224,15 +265,25 @@ describe("generateDrizzleSchema", () => {
|
|
|
224
265
|
|
|
225
266
|
it("should combine isId='increment' with columnType overrides safely", async () => {
|
|
226
267
|
const collections: EntityCollection[] = [{
|
|
227
|
-
slug: "nums",
|
|
268
|
+
slug: "nums",
|
|
269
|
+
table: "nums",
|
|
270
|
+
name: "Nums",
|
|
271
|
+
properties: { n_inc: { type: "number",
|
|
272
|
+
isId: "increment",
|
|
273
|
+
columnType: "bigint" } }
|
|
228
274
|
}];
|
|
229
275
|
const result = await generateSchema(collections);
|
|
230
276
|
expect(cleanSchema(result)).toContain("n_inc: bigint(\"n_inc\").generatedByDefaultAsIdentity().primaryKey()");
|
|
231
277
|
});
|
|
232
|
-
|
|
278
|
+
|
|
233
279
|
it("should combine validation.unique with columnType override", async () => {
|
|
234
280
|
const collections: EntityCollection[] = [{
|
|
235
|
-
slug: "nums",
|
|
281
|
+
slug: "nums",
|
|
282
|
+
table: "nums",
|
|
283
|
+
name: "Nums",
|
|
284
|
+
properties: { n_uniq: { type: "number",
|
|
285
|
+
columnType: "real",
|
|
286
|
+
validation: { unique: true } } }
|
|
236
287
|
}];
|
|
237
288
|
const result = await generateSchema(collections);
|
|
238
289
|
expect(cleanSchema(result)).toContain("n_uniq: real(\"n_uniq\").unique()");
|
|
@@ -242,7 +293,10 @@ describe("generateDrizzleSchema", () => {
|
|
|
242
293
|
describe("Date Property", () => {
|
|
243
294
|
it("should default to timestamp with timezone", async () => {
|
|
244
295
|
const collections: EntityCollection[] = [{
|
|
245
|
-
slug: "dates",
|
|
296
|
+
slug: "dates",
|
|
297
|
+
table: "dates",
|
|
298
|
+
name: "Dates",
|
|
299
|
+
properties: { d_def: { type: "date" } }
|
|
246
300
|
}];
|
|
247
301
|
const result = await generateSchema(collections);
|
|
248
302
|
expect(cleanSchema(result)).toContain("d_def: timestamp(\"d_def\", { withTimezone: true, mode: 'string' })");
|
|
@@ -254,9 +308,12 @@ describe("generateDrizzleSchema", () => {
|
|
|
254
308
|
table: "dates",
|
|
255
309
|
name: "Dates",
|
|
256
310
|
properties: {
|
|
257
|
-
d_date: { type: "date",
|
|
258
|
-
|
|
259
|
-
|
|
311
|
+
d_date: { type: "date",
|
|
312
|
+
columnType: "date" },
|
|
313
|
+
d_time: { type: "date",
|
|
314
|
+
columnType: "time" },
|
|
315
|
+
d_ts: { type: "date",
|
|
316
|
+
columnType: "timestamp" }
|
|
260
317
|
}
|
|
261
318
|
}];
|
|
262
319
|
const result = await generateSchema(collections);
|
|
@@ -267,11 +324,14 @@ describe("generateDrizzleSchema", () => {
|
|
|
267
324
|
expect(cleanResult).toContain("d_ts: timestamp(\"d_ts\", { withTimezone: true, mode: 'string' }),");
|
|
268
325
|
});
|
|
269
326
|
});
|
|
270
|
-
|
|
327
|
+
|
|
271
328
|
describe("Map & Array Properties", () => {
|
|
272
329
|
it("should default to jsonb", async () => {
|
|
273
330
|
const collections: EntityCollection[] = [{
|
|
274
|
-
slug: "json_data",
|
|
331
|
+
slug: "json_data",
|
|
332
|
+
table: "json_data",
|
|
333
|
+
name: "JSON Data",
|
|
334
|
+
properties: {
|
|
275
335
|
arr_def: { type: "array" },
|
|
276
336
|
map_def: { type: "map" }
|
|
277
337
|
}
|
|
@@ -287,10 +347,14 @@ describe("generateDrizzleSchema", () => {
|
|
|
287
347
|
table: "json_data",
|
|
288
348
|
name: "JSON Data",
|
|
289
349
|
properties: {
|
|
290
|
-
a_json: { type: "array",
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
350
|
+
a_json: { type: "array",
|
|
351
|
+
columnType: "json" },
|
|
352
|
+
a_jsonb: { type: "array",
|
|
353
|
+
columnType: "jsonb" },
|
|
354
|
+
m_json: { type: "map",
|
|
355
|
+
columnType: "json" },
|
|
356
|
+
m_jsonb: { type: "map",
|
|
357
|
+
columnType: "jsonb" }
|
|
294
358
|
}
|
|
295
359
|
}];
|
|
296
360
|
const result = await generateSchema(collections);
|
|
@@ -314,10 +378,12 @@ describe("generateDrizzleSchema", () => {
|
|
|
314
378
|
name: "Notes",
|
|
315
379
|
properties: {
|
|
316
380
|
title: { type: "string" },
|
|
317
|
-
user_id: { type: "string",
|
|
381
|
+
user_id: { type: "string",
|
|
382
|
+
validation: { required: true } }
|
|
318
383
|
},
|
|
319
384
|
securityRules: [
|
|
320
|
-
{ operation: "all",
|
|
385
|
+
{ operation: "all",
|
|
386
|
+
ownerField: "user_id" }
|
|
321
387
|
]
|
|
322
388
|
}];
|
|
323
389
|
|
|
@@ -341,7 +407,8 @@ describe("generateDrizzleSchema", () => {
|
|
|
341
407
|
user_id: { type: "string" }
|
|
342
408
|
},
|
|
343
409
|
securityRules: [
|
|
344
|
-
{ operation: "select",
|
|
410
|
+
{ operation: "select",
|
|
411
|
+
ownerField: "user_id" }
|
|
345
412
|
]
|
|
346
413
|
}];
|
|
347
414
|
|
|
@@ -361,7 +428,8 @@ describe("generateDrizzleSchema", () => {
|
|
|
361
428
|
user_id: { type: "string" }
|
|
362
429
|
},
|
|
363
430
|
securityRules: [
|
|
364
|
-
{ operation: "insert",
|
|
431
|
+
{ operation: "insert",
|
|
432
|
+
ownerField: "user_id" }
|
|
365
433
|
]
|
|
366
434
|
}];
|
|
367
435
|
|
|
@@ -378,7 +446,8 @@ describe("generateDrizzleSchema", () => {
|
|
|
378
446
|
name: "Articles",
|
|
379
447
|
properties: { title: { type: "string" } },
|
|
380
448
|
securityRules: [
|
|
381
|
-
{ operation: "select",
|
|
449
|
+
{ operation: "select",
|
|
450
|
+
access: "public" }
|
|
382
451
|
]
|
|
383
452
|
}];
|
|
384
453
|
|
|
@@ -394,7 +463,8 @@ describe("generateDrizzleSchema", () => {
|
|
|
394
463
|
name: "Admin Data",
|
|
395
464
|
properties: { data: { type: "string" } },
|
|
396
465
|
securityRules: [
|
|
397
|
-
{ operation: "select",
|
|
466
|
+
{ operation: "select",
|
|
467
|
+
roles: ["admin"] }
|
|
398
468
|
]
|
|
399
469
|
}];
|
|
400
470
|
|
|
@@ -409,7 +479,8 @@ describe("generateDrizzleSchema", () => {
|
|
|
409
479
|
name: "Finance Data",
|
|
410
480
|
properties: { amount: { type: "number" } },
|
|
411
481
|
securityRules: [
|
|
412
|
-
{ operation: "select",
|
|
482
|
+
{ operation: "select",
|
|
483
|
+
roles: ["view"] } // Should not match 'viewer'
|
|
413
484
|
]
|
|
414
485
|
}];
|
|
415
486
|
|
|
@@ -426,7 +497,8 @@ describe("generateDrizzleSchema", () => {
|
|
|
426
497
|
name: "Multi Role",
|
|
427
498
|
properties: { data: { type: "string" } },
|
|
428
499
|
securityRules: [
|
|
429
|
-
{ operation: "select",
|
|
500
|
+
{ operation: "select",
|
|
501
|
+
roles: ["admin", "editor", "super-admin"] }
|
|
430
502
|
]
|
|
431
503
|
}];
|
|
432
504
|
|
|
@@ -441,7 +513,9 @@ describe("generateDrizzleSchema", () => {
|
|
|
441
513
|
name: "Reports",
|
|
442
514
|
properties: { title: { type: "string" } },
|
|
443
515
|
securityRules: [
|
|
444
|
-
{ operation: "select",
|
|
516
|
+
{ operation: "select",
|
|
517
|
+
roles: ["admin", "manager"],
|
|
518
|
+
access: "public" }
|
|
445
519
|
]
|
|
446
520
|
}];
|
|
447
521
|
|
|
@@ -460,7 +534,9 @@ describe("generateDrizzleSchema", () => {
|
|
|
460
534
|
user_id: { type: "string" }
|
|
461
535
|
},
|
|
462
536
|
securityRules: [
|
|
463
|
-
{ operation: "select",
|
|
537
|
+
{ operation: "select",
|
|
538
|
+
roles: ["editor"],
|
|
539
|
+
ownerField: "user_id" }
|
|
464
540
|
]
|
|
465
541
|
}];
|
|
466
542
|
|
|
@@ -481,7 +557,9 @@ describe("generateDrizzleSchema", () => {
|
|
|
481
557
|
is_locked: { type: "boolean" }
|
|
482
558
|
},
|
|
483
559
|
securityRules: [
|
|
484
|
-
{ operation: "update",
|
|
560
|
+
{ operation: "update",
|
|
561
|
+
mode: "restrictive",
|
|
562
|
+
using: "{is_locked} = false" }
|
|
485
563
|
]
|
|
486
564
|
}];
|
|
487
565
|
|
|
@@ -500,7 +578,8 @@ describe("generateDrizzleSchema", () => {
|
|
|
500
578
|
published_at: { type: "date" }
|
|
501
579
|
},
|
|
502
580
|
securityRules: [
|
|
503
|
-
{ operation: "select",
|
|
581
|
+
{ operation: "select",
|
|
582
|
+
using: "{published_at} > now() - interval '30 days'" }
|
|
504
583
|
]
|
|
505
584
|
}];
|
|
506
585
|
|
|
@@ -541,7 +620,9 @@ describe("generateDrizzleSchema", () => {
|
|
|
541
620
|
name: "Data",
|
|
542
621
|
properties: { value: { type: "string" } },
|
|
543
622
|
securityRules: [
|
|
544
|
-
{ name: "my_custom_policy",
|
|
623
|
+
{ name: "my_custom_policy",
|
|
624
|
+
operation: "select",
|
|
625
|
+
access: "public" }
|
|
545
626
|
]
|
|
546
627
|
}];
|
|
547
628
|
|
|
@@ -560,10 +641,20 @@ describe("generateDrizzleSchema", () => {
|
|
|
560
641
|
is_locked: { type: "boolean" }
|
|
561
642
|
},
|
|
562
643
|
securityRules: [
|
|
563
|
-
{ name: "admin_read",
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
644
|
+
{ name: "admin_read",
|
|
645
|
+
operation: "select",
|
|
646
|
+
roles: ["admin"],
|
|
647
|
+
access: "public" },
|
|
648
|
+
{ name: "owner_read",
|
|
649
|
+
operation: "select",
|
|
650
|
+
ownerField: "user_id" },
|
|
651
|
+
{ name: "owner_write",
|
|
652
|
+
operation: "insert",
|
|
653
|
+
ownerField: "user_id" },
|
|
654
|
+
{ name: "no_locked_update",
|
|
655
|
+
operation: "update",
|
|
656
|
+
mode: "restrictive",
|
|
657
|
+
using: "{is_locked} = false" }
|
|
567
658
|
]
|
|
568
659
|
}];
|
|
569
660
|
|
|
@@ -586,7 +677,8 @@ describe("generateDrizzleSchema V2 improvements", () => {
|
|
|
586
677
|
name: "Admin Data",
|
|
587
678
|
properties: { data: { type: "string" } },
|
|
588
679
|
securityRules: [
|
|
589
|
-
{ operation: "select",
|
|
680
|
+
{ operation: "select",
|
|
681
|
+
roles: ["admin"] }
|
|
590
682
|
]
|
|
591
683
|
}];
|
|
592
684
|
const result = await generateSchema(collections);
|
|
@@ -602,7 +694,9 @@ describe("generateDrizzleSchema V2 improvements", () => {
|
|
|
602
694
|
user_id: { type: "string" }
|
|
603
695
|
},
|
|
604
696
|
securityRules: [
|
|
605
|
-
{ name: "owner_rw",
|
|
697
|
+
{ name: "owner_rw",
|
|
698
|
+
operations: ["select", "update"],
|
|
699
|
+
ownerField: "user_id" }
|
|
606
700
|
]
|
|
607
701
|
}];
|
|
608
702
|
const result = await generateSchema(collections);
|
|
@@ -619,7 +713,8 @@ describe("generateDrizzleSchema V2 improvements", () => {
|
|
|
619
713
|
name: "Items",
|
|
620
714
|
properties: { data: { type: "string" } },
|
|
621
715
|
securityRules: [
|
|
622
|
-
{ operations: ["select", "delete"],
|
|
716
|
+
{ operations: ["select", "delete"],
|
|
717
|
+
access: "public" }
|
|
623
718
|
]
|
|
624
719
|
}];
|
|
625
720
|
const result = await generateSchema(collections);
|
|
@@ -633,7 +728,9 @@ describe("generateDrizzleSchema V2 improvements", () => {
|
|
|
633
728
|
name: "Items",
|
|
634
729
|
properties: { data: { type: "string" } },
|
|
635
730
|
securityRules: [
|
|
636
|
-
{ name: "my_policy",
|
|
731
|
+
{ name: "my_policy",
|
|
732
|
+
operations: ["select"],
|
|
733
|
+
access: "public" }
|
|
637
734
|
]
|
|
638
735
|
}];
|
|
639
736
|
const result = await generateSchema(collections);
|
|
@@ -651,7 +748,9 @@ describe("generateDrizzleSchema V2 improvements", () => {
|
|
|
651
748
|
user_id: { type: "string" }
|
|
652
749
|
},
|
|
653
750
|
securityRules: [
|
|
654
|
-
{ name: "owner",
|
|
751
|
+
{ name: "owner",
|
|
752
|
+
operations: ["select", "insert"],
|
|
753
|
+
ownerField: "user_id" }
|
|
655
754
|
]
|
|
656
755
|
}];
|
|
657
756
|
const result = await generateSchema(collections);
|
|
@@ -671,7 +770,10 @@ describe("generateDrizzleSchema V2 improvements", () => {
|
|
|
671
770
|
name: "Items",
|
|
672
771
|
properties: { data: { type: "string" } },
|
|
673
772
|
securityRules: [
|
|
674
|
-
{ name: "test",
|
|
773
|
+
{ name: "test",
|
|
774
|
+
operation: "delete",
|
|
775
|
+
operations: ["select", "insert"],
|
|
776
|
+
access: "public" }
|
|
675
777
|
]
|
|
676
778
|
}];
|
|
677
779
|
const result = await generateSchema(collections);
|
|
@@ -687,7 +789,9 @@ describe("generateDrizzleSchema V2 improvements", () => {
|
|
|
687
789
|
name: "Reports",
|
|
688
790
|
properties: { title: { type: "string" } },
|
|
689
791
|
securityRules: [
|
|
690
|
-
{ operation: "select",
|
|
792
|
+
{ operation: "select",
|
|
793
|
+
roles: ["admin"],
|
|
794
|
+
using: "true" }
|
|
691
795
|
]
|
|
692
796
|
}];
|
|
693
797
|
const result = await generateSchema(collections);
|
|
@@ -701,7 +805,9 @@ describe("generateDrizzleSchema V2 improvements", () => {
|
|
|
701
805
|
name: "Tenant Data",
|
|
702
806
|
properties: { data: { type: "string" } },
|
|
703
807
|
securityRules: [
|
|
704
|
-
{ operation: "select",
|
|
808
|
+
{ operation: "select",
|
|
809
|
+
access: "public",
|
|
810
|
+
pgRoles: ["app_role", "service_role"] }
|
|
705
811
|
]
|
|
706
812
|
}];
|
|
707
813
|
const result = await generateSchema(collections);
|
|
@@ -715,7 +821,8 @@ describe("generateDrizzleSchema V2 improvements", () => {
|
|
|
715
821
|
name: "Default Data",
|
|
716
822
|
properties: { data: { type: "string" } },
|
|
717
823
|
securityRules: [
|
|
718
|
-
{ operation: "select",
|
|
824
|
+
{ operation: "select",
|
|
825
|
+
access: "public" }
|
|
719
826
|
]
|
|
720
827
|
}];
|
|
721
828
|
const result = await generateSchema(collections);
|
|
@@ -723,6 +830,88 @@ describe("generateDrizzleSchema V2 improvements", () => {
|
|
|
723
830
|
});
|
|
724
831
|
});
|
|
725
832
|
|
|
833
|
+
describe("generateDrizzleSchema Deterministic Policies", () => {
|
|
834
|
+
it("should generate the same policy name hash for identically configured rules", async () => {
|
|
835
|
+
const collections1: EntityCollection[] = [{
|
|
836
|
+
slug: "test1",
|
|
837
|
+
table: "test_hash",
|
|
838
|
+
name: "Test",
|
|
839
|
+
properties: { data: { type: "string" } },
|
|
840
|
+
securityRules: [
|
|
841
|
+
{ operation: "select",
|
|
842
|
+
roles: ["admin", "user"] }
|
|
843
|
+
]
|
|
844
|
+
}];
|
|
845
|
+
|
|
846
|
+
const collections2: EntityCollection[] = [{
|
|
847
|
+
slug: "test2",
|
|
848
|
+
table: "test_hash", // Same table name
|
|
849
|
+
name: "Test",
|
|
850
|
+
properties: { data: { type: "string" } },
|
|
851
|
+
securityRules: [
|
|
852
|
+
{ operation: "select",
|
|
853
|
+
roles: ["admin", "user"] }
|
|
854
|
+
]
|
|
855
|
+
}];
|
|
856
|
+
|
|
857
|
+
const result1 = await generateSchema(collections1);
|
|
858
|
+
const result2 = await generateSchema(collections2);
|
|
859
|
+
|
|
860
|
+
// Extract the generated policy name, which should look like pgPolicy("test_hash_select_<hash>"
|
|
861
|
+
const match1 = result1.match(/pgPolicy\("test_hash_select_([a-f0-9]{7})"/);
|
|
862
|
+
const match2 = result2.match(/pgPolicy\("test_hash_select_([a-f0-9]{7})"/);
|
|
863
|
+
|
|
864
|
+
expect(match1).not.toBeNull();
|
|
865
|
+
expect(match2).not.toBeNull();
|
|
866
|
+
expect(match1![1]).toEqual(match2![1]);
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
it("should generate the exact same SQL output regardless of array order in configuration", async () => {
|
|
870
|
+
const collectionsUnsorted: EntityCollection[] = [{
|
|
871
|
+
slug: "test_order",
|
|
872
|
+
table: "test_order",
|
|
873
|
+
name: "Test",
|
|
874
|
+
properties: { data: { type: "string" } },
|
|
875
|
+
securityRules: [
|
|
876
|
+
{
|
|
877
|
+
operation: "select",
|
|
878
|
+
roles: ["user", "admin", "manager"], // Unsorted roles
|
|
879
|
+
pgRoles: ["service_role", "app_role"] // Unsorted pgRoles
|
|
880
|
+
}
|
|
881
|
+
]
|
|
882
|
+
}];
|
|
883
|
+
|
|
884
|
+
const collectionsSorted: EntityCollection[] = [{
|
|
885
|
+
slug: "test_order",
|
|
886
|
+
table: "test_order",
|
|
887
|
+
name: "Test",
|
|
888
|
+
properties: { data: { type: "string" } },
|
|
889
|
+
securityRules: [
|
|
890
|
+
{
|
|
891
|
+
operation: "select",
|
|
892
|
+
roles: ["admin", "manager", "user"], // Sorted roles
|
|
893
|
+
pgRoles: ["app_role", "service_role"] // Sorted pgRoles
|
|
894
|
+
}
|
|
895
|
+
]
|
|
896
|
+
}];
|
|
897
|
+
|
|
898
|
+
const resultUnsorted = await generateSchema(collectionsUnsorted);
|
|
899
|
+
const resultSorted = await generateSchema(collectionsSorted);
|
|
900
|
+
|
|
901
|
+
// The policy names should match because the configuration arrays should be sorted before hashing
|
|
902
|
+
const matchUnsorted = resultUnsorted.match(/pgPolicy\("test_order_select_([a-f0-9]{7})"/);
|
|
903
|
+
const matchSorted = resultSorted.match(/pgPolicy\("test_order_select_([a-f0-9]{7})"/);
|
|
904
|
+
|
|
905
|
+
expect(matchUnsorted).not.toBeNull();
|
|
906
|
+
expect(matchSorted).not.toBeNull();
|
|
907
|
+
expect(matchUnsorted![1]).toEqual(matchSorted![1]);
|
|
908
|
+
|
|
909
|
+
// The actual generated sql bodies should also be identical due to sorting
|
|
910
|
+
expect(resultUnsorted).toContain("string_to_array(auth.roles(), ',') @> ARRAY['admin','manager','user']");
|
|
911
|
+
expect(resultUnsorted).toContain('to: ["app_role", "service_role"]');
|
|
912
|
+
});
|
|
913
|
+
});
|
|
914
|
+
|
|
726
915
|
describe("generateDrizzleSchema ID Generation", () => {
|
|
727
916
|
const cleanSchema = (schema: string) => {
|
|
728
917
|
return schema
|
|
@@ -739,7 +928,8 @@ describe("generateDrizzleSchema ID Generation", () => {
|
|
|
739
928
|
table: "items",
|
|
740
929
|
name: "Items",
|
|
741
930
|
properties: {
|
|
742
|
-
custom_id: { type: "string",
|
|
931
|
+
custom_id: { type: "string",
|
|
932
|
+
isId: "uuid" }
|
|
743
933
|
}
|
|
744
934
|
}];
|
|
745
935
|
const result = await generateSchema(collections);
|
|
@@ -754,7 +944,8 @@ describe("generateDrizzleSchema ID Generation", () => {
|
|
|
754
944
|
table: "tickets",
|
|
755
945
|
name: "Tickets",
|
|
756
946
|
properties: {
|
|
757
|
-
ticket_id: { type: "number",
|
|
947
|
+
ticket_id: { type: "number",
|
|
948
|
+
isId: "increment" }
|
|
758
949
|
}
|
|
759
950
|
}];
|
|
760
951
|
const result = await generateSchema(collections);
|
|
@@ -769,7 +960,8 @@ describe("generateDrizzleSchema ID Generation", () => {
|
|
|
769
960
|
table: "events",
|
|
770
961
|
name: "Events",
|
|
771
962
|
properties: {
|
|
772
|
-
event_id: { type: "string",
|
|
963
|
+
event_id: { type: "string",
|
|
964
|
+
isId: "sql`gen_random_uuid()`" }
|
|
773
965
|
}
|
|
774
966
|
}];
|
|
775
967
|
const result = await generateSchema(collections);
|
|
@@ -784,7 +976,8 @@ describe("generateDrizzleSchema ID Generation", () => {
|
|
|
784
976
|
table: "users",
|
|
785
977
|
name: "Users",
|
|
786
978
|
properties: {
|
|
787
|
-
user_name: { type: "string",
|
|
979
|
+
user_name: { type: "string",
|
|
980
|
+
isId: true }
|
|
788
981
|
}
|
|
789
982
|
}];
|
|
790
983
|
const result = await generateSchema(collections);
|