@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.
Files changed (136) hide show
  1. package/dist/common/src/collections/CollectionRegistry.d.ts +8 -0
  2. package/dist/common/src/util/entities.d.ts +22 -0
  3. package/dist/common/src/util/relations.d.ts +14 -4
  4. package/dist/common/src/util/resolutions.d.ts +1 -1
  5. package/dist/index.es.js +1254 -591
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +1254 -591
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +17 -29
  10. package/dist/server-postgresql/src/auth/services.d.ts +7 -3
  11. package/dist/server-postgresql/src/collections/PostgresCollectionRegistry.d.ts +1 -1
  12. package/dist/server-postgresql/src/connection.d.ts +34 -1
  13. package/dist/server-postgresql/src/data-transformer.d.ts +26 -4
  14. package/dist/server-postgresql/src/databasePoolManager.d.ts +2 -2
  15. package/dist/server-postgresql/src/schema/auth-schema.d.ts +139 -38
  16. package/dist/server-postgresql/src/schema/doctor-cli.d.ts +2 -0
  17. package/dist/server-postgresql/src/schema/doctor.d.ts +43 -0
  18. package/dist/server-postgresql/src/schema/generate-drizzle-schema-logic.d.ts +1 -1
  19. package/dist/server-postgresql/src/schema/test-schema.d.ts +24 -0
  20. package/dist/server-postgresql/src/services/EntityFetchService.d.ts +22 -8
  21. package/dist/server-postgresql/src/services/EntityPersistService.d.ts +1 -1
  22. package/dist/server-postgresql/src/services/RelationService.d.ts +11 -5
  23. package/dist/server-postgresql/src/services/entity-helpers.d.ts +16 -2
  24. package/dist/server-postgresql/src/services/entityService.d.ts +8 -6
  25. package/dist/server-postgresql/src/services/realtimeService.d.ts +2 -0
  26. package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +2 -2
  27. package/dist/types/src/controllers/auth.d.ts +2 -0
  28. package/dist/types/src/controllers/client.d.ts +119 -7
  29. package/dist/types/src/controllers/collection_registry.d.ts +4 -3
  30. package/dist/types/src/controllers/customization_controller.d.ts +7 -1
  31. package/dist/types/src/controllers/data.d.ts +34 -7
  32. package/dist/types/src/controllers/data_driver.d.ts +20 -28
  33. package/dist/types/src/controllers/database_admin.d.ts +2 -2
  34. package/dist/types/src/controllers/email.d.ts +34 -0
  35. package/dist/types/src/controllers/index.d.ts +1 -0
  36. package/dist/types/src/controllers/local_config_persistence.d.ts +4 -4
  37. package/dist/types/src/controllers/navigation.d.ts +5 -5
  38. package/dist/types/src/controllers/registry.d.ts +6 -3
  39. package/dist/types/src/controllers/side_entity_controller.d.ts +7 -6
  40. package/dist/types/src/controllers/storage.d.ts +24 -26
  41. package/dist/types/src/rebase_context.d.ts +8 -4
  42. package/dist/types/src/types/backend.d.ts +4 -1
  43. package/dist/types/src/types/builders.d.ts +5 -4
  44. package/dist/types/src/types/chips.d.ts +1 -1
  45. package/dist/types/src/types/collections.d.ts +169 -125
  46. package/dist/types/src/types/cron.d.ts +102 -0
  47. package/dist/types/src/types/data_source.d.ts +1 -1
  48. package/dist/types/src/types/entity_actions.d.ts +8 -8
  49. package/dist/types/src/types/entity_callbacks.d.ts +15 -15
  50. package/dist/types/src/types/entity_link_builder.d.ts +1 -1
  51. package/dist/types/src/types/entity_overrides.d.ts +2 -1
  52. package/dist/types/src/types/entity_views.d.ts +8 -8
  53. package/dist/types/src/types/export_import.d.ts +3 -3
  54. package/dist/types/src/types/index.d.ts +1 -0
  55. package/dist/types/src/types/plugins.d.ts +72 -18
  56. package/dist/types/src/types/properties.d.ts +118 -33
  57. package/dist/types/src/types/relations.d.ts +1 -1
  58. package/dist/types/src/types/slots.d.ts +30 -6
  59. package/dist/types/src/types/translations.d.ts +44 -0
  60. package/dist/types/src/types/user_management_delegate.d.ts +1 -0
  61. package/drizzle-test/0000_woozy_junta.sql +6 -0
  62. package/drizzle-test/0001_youthful_arachne.sql +1 -0
  63. package/drizzle-test/0002_lively_dragon_lord.sql +2 -0
  64. package/drizzle-test/0003_mean_king_cobra.sql +2 -0
  65. package/drizzle-test/meta/0000_snapshot.json +47 -0
  66. package/drizzle-test/meta/0001_snapshot.json +48 -0
  67. package/drizzle-test/meta/0002_snapshot.json +38 -0
  68. package/drizzle-test/meta/0003_snapshot.json +48 -0
  69. package/drizzle-test/meta/_journal.json +34 -0
  70. package/drizzle-test-out/0000_tan_trauma.sql +6 -0
  71. package/drizzle-test-out/0001_rapid_drax.sql +1 -0
  72. package/drizzle-test-out/meta/0000_snapshot.json +44 -0
  73. package/drizzle-test-out/meta/0001_snapshot.json +54 -0
  74. package/drizzle-test-out/meta/_journal.json +20 -0
  75. package/drizzle.test.config.ts +10 -0
  76. package/package.json +88 -89
  77. package/scratch.ts +41 -0
  78. package/src/PostgresBackendDriver.ts +63 -79
  79. package/src/PostgresBootstrapper.ts +7 -8
  80. package/src/auth/ensure-tables.ts +158 -86
  81. package/src/auth/services.ts +109 -50
  82. package/src/cli.ts +259 -16
  83. package/src/collections/PostgresCollectionRegistry.ts +6 -6
  84. package/src/connection.ts +70 -48
  85. package/src/data-transformer.ts +155 -116
  86. package/src/databasePoolManager.ts +6 -5
  87. package/src/history/HistoryService.ts +3 -12
  88. package/src/interfaces.ts +3 -3
  89. package/src/schema/auth-schema.ts +26 -3
  90. package/src/schema/doctor-cli.ts +47 -0
  91. package/src/schema/doctor.ts +595 -0
  92. package/src/schema/generate-drizzle-schema-logic.ts +204 -57
  93. package/src/schema/generate-drizzle-schema.ts +6 -6
  94. package/src/schema/test-schema.ts +11 -0
  95. package/src/services/BranchService.ts +5 -5
  96. package/src/services/EntityFetchService.ts +317 -188
  97. package/src/services/EntityPersistService.ts +15 -17
  98. package/src/services/RelationService.ts +299 -37
  99. package/src/services/entity-helpers.ts +39 -13
  100. package/src/services/entityService.ts +11 -9
  101. package/src/services/realtimeService.ts +58 -29
  102. package/src/utils/drizzle-conditions.ts +25 -24
  103. package/src/websocket.ts +52 -21
  104. package/test/auth-services.test.ts +131 -39
  105. package/test/batch-many-to-many-regression.test.ts +573 -0
  106. package/test/branchService.test.ts +22 -12
  107. package/test/data-transformer-hardening.test.ts +417 -0
  108. package/test/data-transformer.test.ts +175 -0
  109. package/test/doctor.test.ts +182 -0
  110. package/test/entityService.errors.test.ts +31 -16
  111. package/test/entityService.relations.test.ts +155 -59
  112. package/test/entityService.subcollection-search.test.ts +107 -57
  113. package/test/entityService.test.ts +105 -47
  114. package/test/generate-drizzle-schema.test.ts +262 -69
  115. package/test/historyService.test.ts +31 -16
  116. package/test/n-plus-one-regression.test.ts +314 -0
  117. package/test/postgresDataDriver.test.ts +260 -168
  118. package/test/realtimeService.test.ts +70 -39
  119. package/test/relation-pipeline-gaps.test.ts +637 -0
  120. package/test/relations.test.ts +492 -39
  121. package/test-drizzle-bug.ts +18 -0
  122. package/test-drizzle-out/0000_cultured_freak.sql +7 -0
  123. package/test-drizzle-out/0001_tiresome_professor_monster.sql +1 -0
  124. package/test-drizzle-out/meta/0000_snapshot.json +55 -0
  125. package/test-drizzle-out/meta/0001_snapshot.json +63 -0
  126. package/test-drizzle-out/meta/_journal.json +20 -0
  127. package/test-drizzle-prompt.sh +2 -0
  128. package/test-policy-prompt.sh +3 -0
  129. package/test-programmatic.ts +30 -0
  130. package/test-programmatic2.ts +59 -0
  131. package/test-schema-no-policies.ts +12 -0
  132. package/test_drizzle_mock.js +2 -2
  133. package/test_find_changed.mjs +3 -1
  134. package/test_hash.js +14 -0
  135. package/tsconfig.json +1 -1
  136. 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, _rebase_invalidated: false }]),
9
- fetchEntity: jest.fn().mockResolvedValue({ id: 1, _rebase_invalidated: false }),
10
- searchEntities: jest.fn().mockResolvedValue([]),
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, path: "posts", values: { title: "Refetched Title" } }]),
57
- fetchEntity: jest.fn().mockResolvedValue({ id: 1, path: "posts", values: { title: "Refetched Entity Title" } }),
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", subscriptionId: "sub-1" },
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", subscriptionId: "sub-1" },
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", path: "posts", values: { _rebase_invalidated: true } } as any;
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", subscriptionId: "sub-1" },
155
+ payload: { path: "posts",
156
+ subscriptionId: "sub-1" }
146
157
  });
147
158
 
148
159
  // Simulated normal Local update
149
- const freshEntity = { id: "1", path: "posts", values: { title: "Immediate Patch" } } as any;
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", entityId: "1", subscriptionId: "sub-2" },
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", path: "posts", values: { _rebase_invalidated: true } } as any;
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", entityId: "1" }));
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", entityId: "1", subscriptionId: "sub-2" },
229
+ payload: { path: "posts",
230
+ entityId: "1",
231
+ subscriptionId: "sub-2" }
212
232
  });
213
233
 
214
- const freshEntity = { id: "1", path: "posts", values: { title: "Pure Patch" } } as any;
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", subscriptionId: "sub-rls" },
234
- }, { userId: "user123", roles: ["admin", "editor"] });
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", path: "posts", values: { _rebase_invalidated: true } } as any;
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", entityId: "1", subscriptionId: "sub-rls-ent" },
258
- }, { userId: "user456", roles: ["viewer"] });
259
-
260
- const dummyEntity = { id: "1", path: "posts", values: { _rebase_invalidated: true } } as any;
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
  });