@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
|
@@ -5,9 +5,11 @@ import { EntityCollection } from "@rebasepro/types";
|
|
|
5
5
|
|
|
6
6
|
jest.mock("../src/services/entityService", () => ({
|
|
7
7
|
EntityService: jest.fn().mockImplementation(() => ({
|
|
8
|
-
fetchCollection: jest.fn().mockResolvedValue([{ id: 1,
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
fetchCollection: jest.fn().mockResolvedValue([{ id: 1,
|
|
9
|
+
_rebase_invalidated: false }]),
|
|
10
|
+
fetchEntity: jest.fn().mockResolvedValue({ id: 1,
|
|
11
|
+
_rebase_invalidated: false }),
|
|
12
|
+
searchEntities: jest.fn().mockResolvedValue([])
|
|
11
13
|
}))
|
|
12
14
|
}));
|
|
13
15
|
|
|
@@ -25,9 +27,9 @@ const mockPostsCollection: EntityCollection = {
|
|
|
25
27
|
table: "posts",
|
|
26
28
|
properties: {
|
|
27
29
|
id: { type: "number" },
|
|
28
|
-
title: { type: "string" }
|
|
30
|
+
title: { type: "string" }
|
|
29
31
|
},
|
|
30
|
-
idField: "id"
|
|
32
|
+
idField: "id"
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
describe("RealtimeService", () => {
|
|
@@ -45,7 +47,7 @@ describe("RealtimeService", () => {
|
|
|
45
47
|
select: jest.fn().mockReturnThis(),
|
|
46
48
|
from: jest.fn().mockReturnThis(),
|
|
47
49
|
where: jest.fn().mockReturnThis(),
|
|
48
|
-
limit: jest.fn().mockReturnThis()
|
|
50
|
+
limit: jest.fn().mockReturnThis()
|
|
49
51
|
} as unknown as jest.Mocked<NodePgDatabase<any>>;
|
|
50
52
|
(db as any).then = jest.fn((resolve) => resolve([]));
|
|
51
53
|
|
|
@@ -53,14 +55,18 @@ describe("RealtimeService", () => {
|
|
|
53
55
|
jest.spyOn(registry, "getCollectionByPath").mockReturnValue(mockPostsCollection);
|
|
54
56
|
|
|
55
57
|
mockDriver = {
|
|
56
|
-
fetchCollection: jest.fn().mockResolvedValue([{ id: 1,
|
|
57
|
-
|
|
58
|
+
fetchCollection: jest.fn().mockResolvedValue([{ id: 1,
|
|
59
|
+
path: "posts",
|
|
60
|
+
values: { title: "Refetched Title" } }]),
|
|
61
|
+
fetchEntity: jest.fn().mockResolvedValue({ id: 1,
|
|
62
|
+
path: "posts",
|
|
63
|
+
values: { title: "Refetched Entity Title" } })
|
|
58
64
|
};
|
|
59
65
|
|
|
60
66
|
const mockPoolManager = {
|
|
61
67
|
defaultDatabaseName: "main"
|
|
62
68
|
};
|
|
63
|
-
|
|
69
|
+
|
|
64
70
|
const mockAuthSettings = {
|
|
65
71
|
accessTokenSecret: "secret"
|
|
66
72
|
};
|
|
@@ -90,9 +96,10 @@ describe("RealtimeService", () => {
|
|
|
90
96
|
realtimeService.addClient("client-1", ws);
|
|
91
97
|
await realtimeService.handleClientMessage("client-1", {
|
|
92
98
|
type: "subscribe_collection",
|
|
93
|
-
payload: { path: "posts",
|
|
99
|
+
payload: { path: "posts",
|
|
100
|
+
subscriptionId: "sub-1" }
|
|
94
101
|
});
|
|
95
|
-
|
|
102
|
+
|
|
96
103
|
expect(realtimeService.subscriptions.has("sub-1")).toBe(true);
|
|
97
104
|
realtimeService.removeClient("client-1");
|
|
98
105
|
expect(realtimeService.subscriptions.has("sub-1")).toBe(false);
|
|
@@ -103,14 +110,17 @@ describe("RealtimeService", () => {
|
|
|
103
110
|
it("triggers debounced refetch and omits dummy entities on PG_NOTIFY invalidation", async () => {
|
|
104
111
|
const ws = new MockWebSocket() as any;
|
|
105
112
|
realtimeService.addClient("client-1", ws);
|
|
106
|
-
|
|
113
|
+
|
|
107
114
|
await realtimeService.handleClientMessage("client-1", {
|
|
108
115
|
type: "subscribe_collection",
|
|
109
|
-
payload: { path: "posts",
|
|
116
|
+
payload: { path: "posts",
|
|
117
|
+
subscriptionId: "sub-1" }
|
|
110
118
|
});
|
|
111
119
|
|
|
112
120
|
// Simulate PG_NOTIFY listener receiving cross-instance payload
|
|
113
|
-
const dummyEntity = { id: "1",
|
|
121
|
+
const dummyEntity = { id: "1",
|
|
122
|
+
path: "posts",
|
|
123
|
+
values: { _rebase_invalidated: true } } as any;
|
|
114
124
|
await realtimeService.notifyEntityUpdate("posts", "1", dummyEntity, undefined, false);
|
|
115
125
|
|
|
116
126
|
// Phase 1: sendCollectionEntityPatch SHOULD NOT SEND for dummy
|
|
@@ -118,7 +128,7 @@ describe("RealtimeService", () => {
|
|
|
118
128
|
|
|
119
129
|
// Phase 2: Debounced refetch should kick in after 300ms
|
|
120
130
|
jest.advanceTimersByTime(350);
|
|
121
|
-
|
|
131
|
+
|
|
122
132
|
// Wait for async promises to drain
|
|
123
133
|
await Promise.resolve();
|
|
124
134
|
await Promise.resolve();
|
|
@@ -130,7 +140,7 @@ describe("RealtimeService", () => {
|
|
|
130
140
|
expect(ws.send).toHaveBeenCalled();
|
|
131
141
|
const lastCall = ws.send.mock.calls[ws.send.mock.calls.length - 1][0];
|
|
132
142
|
const parsed = JSON.parse(lastCall);
|
|
133
|
-
|
|
143
|
+
|
|
134
144
|
expect(parsed.type).toBe("collection_update");
|
|
135
145
|
expect(parsed.subscriptionId).toBe("sub-1");
|
|
136
146
|
expect(parsed.entities[0].values.title).toBe("Refetched Title");
|
|
@@ -139,14 +149,17 @@ describe("RealtimeService", () => {
|
|
|
139
149
|
it("sends instant entity patch for valid entity updates without _rebase_invalidated", async () => {
|
|
140
150
|
const ws = new MockWebSocket() as any;
|
|
141
151
|
realtimeService.addClient("client-1", ws);
|
|
142
|
-
|
|
152
|
+
|
|
143
153
|
await realtimeService.handleClientMessage("client-1", {
|
|
144
154
|
type: "subscribe_collection",
|
|
145
|
-
payload: { path: "posts",
|
|
155
|
+
payload: { path: "posts",
|
|
156
|
+
subscriptionId: "sub-1" }
|
|
146
157
|
});
|
|
147
158
|
|
|
148
159
|
// Simulated normal Local update
|
|
149
|
-
const freshEntity = { id: "1",
|
|
160
|
+
const freshEntity = { id: "1",
|
|
161
|
+
path: "posts",
|
|
162
|
+
values: { title: "Immediate Patch" } } as any;
|
|
150
163
|
await realtimeService.notifyEntityUpdate("posts", "1", freshEntity, undefined, false);
|
|
151
164
|
|
|
152
165
|
// Phase 1: It SHOULD send immediate patch
|
|
@@ -171,14 +184,18 @@ describe("RealtimeService", () => {
|
|
|
171
184
|
it("triggers debounced refetch and omits dummy entity update for single entity subscriptions", async () => {
|
|
172
185
|
const ws = new MockWebSocket() as any;
|
|
173
186
|
realtimeService.addClient("client-2", ws);
|
|
174
|
-
|
|
187
|
+
|
|
175
188
|
await realtimeService.handleClientMessage("client-2", {
|
|
176
189
|
type: "subscribe_entity",
|
|
177
|
-
payload: { path: "posts",
|
|
190
|
+
payload: { path: "posts",
|
|
191
|
+
entityId: "1",
|
|
192
|
+
subscriptionId: "sub-2" }
|
|
178
193
|
});
|
|
179
194
|
|
|
180
195
|
// Need to mock sendEntityUpdate
|
|
181
|
-
const dummyEntity = { id: "1",
|
|
196
|
+
const dummyEntity = { id: "1",
|
|
197
|
+
path: "posts",
|
|
198
|
+
values: { _rebase_invalidated: true } } as any;
|
|
182
199
|
await realtimeService.notifyEntityUpdate("posts", "1", dummyEntity, undefined, false);
|
|
183
200
|
|
|
184
201
|
// Important: we patched notifyPathUpdate to NOT send entity_update directly if invalidated
|
|
@@ -190,13 +207,14 @@ describe("RealtimeService", () => {
|
|
|
190
207
|
await Promise.resolve();
|
|
191
208
|
|
|
192
209
|
// It should fetch the single entity
|
|
193
|
-
expect(mockDriver.fetchEntity).toHaveBeenCalledWith(expect.objectContaining({ path: "posts",
|
|
210
|
+
expect(mockDriver.fetchEntity).toHaveBeenCalledWith(expect.objectContaining({ path: "posts",
|
|
211
|
+
entityId: "1" }));
|
|
194
212
|
|
|
195
213
|
// It should send entity update
|
|
196
214
|
expect(ws.send).toHaveBeenCalled();
|
|
197
215
|
const lastCall = ws.send.mock.calls[ws.send.mock.calls.length - 1][0];
|
|
198
216
|
const parsed = JSON.parse(lastCall);
|
|
199
|
-
|
|
217
|
+
|
|
200
218
|
expect(parsed.type).toBe("entity_update");
|
|
201
219
|
expect(parsed.subscriptionId).toBe("sub-2");
|
|
202
220
|
expect(parsed.entity.values.title).toBe("Refetched Entity Title");
|
|
@@ -205,19 +223,23 @@ describe("RealtimeService", () => {
|
|
|
205
223
|
it("sends instant entity update if valid payload (local mutation)", async () => {
|
|
206
224
|
const ws = new MockWebSocket() as any;
|
|
207
225
|
realtimeService.addClient("client-2", ws);
|
|
208
|
-
|
|
226
|
+
|
|
209
227
|
await realtimeService.handleClientMessage("client-2", {
|
|
210
228
|
type: "subscribe_entity",
|
|
211
|
-
payload: { path: "posts",
|
|
229
|
+
payload: { path: "posts",
|
|
230
|
+
entityId: "1",
|
|
231
|
+
subscriptionId: "sub-2" }
|
|
212
232
|
});
|
|
213
233
|
|
|
214
|
-
const freshEntity = { id: "1",
|
|
234
|
+
const freshEntity = { id: "1",
|
|
235
|
+
path: "posts",
|
|
236
|
+
values: { title: "Pure Patch" } } as any;
|
|
215
237
|
await realtimeService.notifyEntityUpdate("posts", "1", freshEntity, undefined, false);
|
|
216
238
|
|
|
217
239
|
expect(ws.send).toHaveBeenCalled();
|
|
218
240
|
const lastCall = ws.send.mock.calls[ws.send.mock.calls.length - 1][0];
|
|
219
241
|
const parsed = JSON.parse(lastCall);
|
|
220
|
-
|
|
242
|
+
|
|
221
243
|
expect(parsed.type).toBe("entity_update");
|
|
222
244
|
expect(parsed.entity.values.title).toBe("Pure Patch");
|
|
223
245
|
});
|
|
@@ -227,14 +249,18 @@ describe("RealtimeService", () => {
|
|
|
227
249
|
it("applies auth context correctly on debounced collection refetches", async () => {
|
|
228
250
|
const ws = new MockWebSocket() as any;
|
|
229
251
|
realtimeService.addClient("client-rls", ws);
|
|
230
|
-
|
|
252
|
+
|
|
231
253
|
await realtimeService.handleClientMessage("client-rls", {
|
|
232
254
|
type: "subscribe_collection",
|
|
233
|
-
payload: { path: "posts",
|
|
234
|
-
|
|
255
|
+
payload: { path: "posts",
|
|
256
|
+
subscriptionId: "sub-rls" }
|
|
257
|
+
}, { userId: "user123",
|
|
258
|
+
roles: ["admin", "editor"] });
|
|
235
259
|
|
|
236
260
|
// Simulate PG_NOTIFY invalidation
|
|
237
|
-
const dummyEntity = { id: "1",
|
|
261
|
+
const dummyEntity = { id: "1",
|
|
262
|
+
path: "posts",
|
|
263
|
+
values: { _rebase_invalidated: true } } as any;
|
|
238
264
|
await realtimeService.notifyEntityUpdate("posts", "1", dummyEntity, undefined, false);
|
|
239
265
|
|
|
240
266
|
jest.advanceTimersByTime(350);
|
|
@@ -243,7 +269,7 @@ describe("RealtimeService", () => {
|
|
|
243
269
|
|
|
244
270
|
expect(db.execute).toHaveBeenCalled();
|
|
245
271
|
const executeCalls = db.execute.mock.calls.map(c => JSON.stringify(c[0]));
|
|
246
|
-
|
|
272
|
+
|
|
247
273
|
expect(executeCalls.some(sql => sql.includes("set_config('app.user_id'"))).toBe(true);
|
|
248
274
|
expect(executeCalls.some(sql => sql.includes("set_config('app.user_roles'"))).toBe(true);
|
|
249
275
|
});
|
|
@@ -251,13 +277,18 @@ describe("RealtimeService", () => {
|
|
|
251
277
|
it("applies auth context correctly on debounced entity refetches", async () => {
|
|
252
278
|
const ws = new MockWebSocket() as any;
|
|
253
279
|
realtimeService.addClient("client-rls-ent", ws);
|
|
254
|
-
|
|
280
|
+
|
|
255
281
|
await realtimeService.handleClientMessage("client-rls-ent", {
|
|
256
282
|
type: "subscribe_entity",
|
|
257
|
-
payload: { path: "posts",
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
283
|
+
payload: { path: "posts",
|
|
284
|
+
entityId: "1",
|
|
285
|
+
subscriptionId: "sub-rls-ent" }
|
|
286
|
+
}, { userId: "user456",
|
|
287
|
+
roles: ["viewer"] });
|
|
288
|
+
|
|
289
|
+
const dummyEntity = { id: "1",
|
|
290
|
+
path: "posts",
|
|
291
|
+
values: { _rebase_invalidated: true } } as any;
|
|
261
292
|
await realtimeService.notifyEntityUpdate("posts", "1", dummyEntity, undefined, false);
|
|
262
293
|
|
|
263
294
|
jest.advanceTimersByTime(350);
|
|
@@ -266,7 +297,7 @@ describe("RealtimeService", () => {
|
|
|
266
297
|
|
|
267
298
|
expect(db.execute).toHaveBeenCalled();
|
|
268
299
|
const executeCalls = db.execute.mock.calls.map(c => JSON.stringify(c[0]));
|
|
269
|
-
|
|
300
|
+
|
|
270
301
|
expect(executeCalls.some(sql => sql.includes("set_config('app.user_id'"))).toBe(true);
|
|
271
302
|
expect(executeCalls.some(sql => sql.includes("set_config('app.user_roles'"))).toBe(true);
|
|
272
303
|
});
|