@rebasepro/server-postgresql 0.0.1-canary.09e5ec5

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 (196) hide show
  1. package/LICENSE +6 -0
  2. package/README.md +106 -0
  3. package/build-errors.txt +37 -0
  4. package/dist/common/src/collections/CollectionRegistry.d.ts +56 -0
  5. package/dist/common/src/collections/index.d.ts +1 -0
  6. package/dist/common/src/data/buildRebaseData.d.ts +14 -0
  7. package/dist/common/src/index.d.ts +3 -0
  8. package/dist/common/src/util/builders.d.ts +57 -0
  9. package/dist/common/src/util/callbacks.d.ts +6 -0
  10. package/dist/common/src/util/collections.d.ts +11 -0
  11. package/dist/common/src/util/common.d.ts +2 -0
  12. package/dist/common/src/util/conditions.d.ts +26 -0
  13. package/dist/common/src/util/entities.d.ts +58 -0
  14. package/dist/common/src/util/enums.d.ts +3 -0
  15. package/dist/common/src/util/index.d.ts +16 -0
  16. package/dist/common/src/util/navigation_from_path.d.ts +34 -0
  17. package/dist/common/src/util/navigation_utils.d.ts +20 -0
  18. package/dist/common/src/util/parent_references_from_path.d.ts +6 -0
  19. package/dist/common/src/util/paths.d.ts +14 -0
  20. package/dist/common/src/util/permissions.d.ts +5 -0
  21. package/dist/common/src/util/references.d.ts +2 -0
  22. package/dist/common/src/util/relations.d.ts +22 -0
  23. package/dist/common/src/util/resolutions.d.ts +72 -0
  24. package/dist/common/src/util/storage.d.ts +24 -0
  25. package/dist/index.es.js +11298 -0
  26. package/dist/index.es.js.map +1 -0
  27. package/dist/index.umd.js +11306 -0
  28. package/dist/index.umd.js.map +1 -0
  29. package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +100 -0
  30. package/dist/server-postgresql/src/PostgresBootstrapper.d.ts +40 -0
  31. package/dist/server-postgresql/src/auth/ensure-tables.d.ts +6 -0
  32. package/dist/server-postgresql/src/auth/services.d.ts +192 -0
  33. package/dist/server-postgresql/src/cli.d.ts +1 -0
  34. package/dist/server-postgresql/src/collections/PostgresCollectionRegistry.d.ts +43 -0
  35. package/dist/server-postgresql/src/connection.d.ts +40 -0
  36. package/dist/server-postgresql/src/data-transformer.d.ts +58 -0
  37. package/dist/server-postgresql/src/databasePoolManager.d.ts +20 -0
  38. package/dist/server-postgresql/src/history/HistoryService.d.ts +71 -0
  39. package/dist/server-postgresql/src/history/ensure-history-table.d.ts +7 -0
  40. package/dist/server-postgresql/src/index.d.ts +13 -0
  41. package/dist/server-postgresql/src/interfaces.d.ts +18 -0
  42. package/dist/server-postgresql/src/schema/auth-schema.d.ts +868 -0
  43. package/dist/server-postgresql/src/schema/doctor-cli.d.ts +2 -0
  44. package/dist/server-postgresql/src/schema/doctor.d.ts +43 -0
  45. package/dist/server-postgresql/src/schema/generate-drizzle-schema-logic.d.ts +2 -0
  46. package/dist/server-postgresql/src/schema/generate-drizzle-schema.d.ts +1 -0
  47. package/dist/server-postgresql/src/schema/introspect-db-logic.d.ts +82 -0
  48. package/dist/server-postgresql/src/schema/introspect-db.d.ts +1 -0
  49. package/dist/server-postgresql/src/schema/test-schema.d.ts +24 -0
  50. package/dist/server-postgresql/src/services/BranchService.d.ts +47 -0
  51. package/dist/server-postgresql/src/services/EntityFetchService.d.ts +209 -0
  52. package/dist/server-postgresql/src/services/EntityPersistService.d.ts +41 -0
  53. package/dist/server-postgresql/src/services/RelationService.d.ts +98 -0
  54. package/dist/server-postgresql/src/services/entity-helpers.d.ts +38 -0
  55. package/dist/server-postgresql/src/services/entityService.d.ts +104 -0
  56. package/dist/server-postgresql/src/services/index.d.ts +4 -0
  57. package/dist/server-postgresql/src/services/realtimeService.d.ts +188 -0
  58. package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +116 -0
  59. package/dist/server-postgresql/src/websocket.d.ts +5 -0
  60. package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
  61. package/dist/types/src/controllers/auth.d.ts +119 -0
  62. package/dist/types/src/controllers/client.d.ts +170 -0
  63. package/dist/types/src/controllers/collection_registry.d.ts +45 -0
  64. package/dist/types/src/controllers/customization_controller.d.ts +60 -0
  65. package/dist/types/src/controllers/data.d.ts +168 -0
  66. package/dist/types/src/controllers/data_driver.d.ts +160 -0
  67. package/dist/types/src/controllers/database_admin.d.ts +11 -0
  68. package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
  69. package/dist/types/src/controllers/effective_role.d.ts +4 -0
  70. package/dist/types/src/controllers/email.d.ts +34 -0
  71. package/dist/types/src/controllers/index.d.ts +18 -0
  72. package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
  73. package/dist/types/src/controllers/navigation.d.ts +213 -0
  74. package/dist/types/src/controllers/registry.d.ts +54 -0
  75. package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
  76. package/dist/types/src/controllers/side_entity_controller.d.ts +90 -0
  77. package/dist/types/src/controllers/snackbar.d.ts +24 -0
  78. package/dist/types/src/controllers/storage.d.ts +171 -0
  79. package/dist/types/src/index.d.ts +4 -0
  80. package/dist/types/src/rebase_context.d.ts +105 -0
  81. package/dist/types/src/types/backend.d.ts +536 -0
  82. package/dist/types/src/types/builders.d.ts +15 -0
  83. package/dist/types/src/types/chips.d.ts +5 -0
  84. package/dist/types/src/types/collections.d.ts +856 -0
  85. package/dist/types/src/types/cron.d.ts +102 -0
  86. package/dist/types/src/types/data_source.d.ts +64 -0
  87. package/dist/types/src/types/entities.d.ts +145 -0
  88. package/dist/types/src/types/entity_actions.d.ts +98 -0
  89. package/dist/types/src/types/entity_callbacks.d.ts +173 -0
  90. package/dist/types/src/types/entity_link_builder.d.ts +7 -0
  91. package/dist/types/src/types/entity_overrides.d.ts +10 -0
  92. package/dist/types/src/types/entity_views.d.ts +61 -0
  93. package/dist/types/src/types/export_import.d.ts +21 -0
  94. package/dist/types/src/types/index.d.ts +23 -0
  95. package/dist/types/src/types/locales.d.ts +4 -0
  96. package/dist/types/src/types/modify_collections.d.ts +5 -0
  97. package/dist/types/src/types/plugins.d.ts +279 -0
  98. package/dist/types/src/types/properties.d.ts +1176 -0
  99. package/dist/types/src/types/property_config.d.ts +70 -0
  100. package/dist/types/src/types/relations.d.ts +336 -0
  101. package/dist/types/src/types/slots.d.ts +252 -0
  102. package/dist/types/src/types/translations.d.ts +870 -0
  103. package/dist/types/src/types/user_management_delegate.d.ts +121 -0
  104. package/dist/types/src/types/websockets.d.ts +78 -0
  105. package/dist/types/src/users/index.d.ts +2 -0
  106. package/dist/types/src/users/roles.d.ts +22 -0
  107. package/dist/types/src/users/user.d.ts +46 -0
  108. package/drizzle-test/0000_woozy_junta.sql +6 -0
  109. package/drizzle-test/0001_youthful_arachne.sql +1 -0
  110. package/drizzle-test/0002_lively_dragon_lord.sql +2 -0
  111. package/drizzle-test/0003_mean_king_cobra.sql +2 -0
  112. package/drizzle-test/meta/0000_snapshot.json +47 -0
  113. package/drizzle-test/meta/0001_snapshot.json +48 -0
  114. package/drizzle-test/meta/0002_snapshot.json +38 -0
  115. package/drizzle-test/meta/0003_snapshot.json +48 -0
  116. package/drizzle-test/meta/_journal.json +34 -0
  117. package/drizzle-test-out/0000_tan_trauma.sql +6 -0
  118. package/drizzle-test-out/0001_rapid_drax.sql +1 -0
  119. package/drizzle-test-out/meta/0000_snapshot.json +44 -0
  120. package/drizzle-test-out/meta/0001_snapshot.json +54 -0
  121. package/drizzle-test-out/meta/_journal.json +20 -0
  122. package/drizzle.test.config.ts +10 -0
  123. package/jest-all.log +3128 -0
  124. package/jest.log +49 -0
  125. package/package.json +92 -0
  126. package/scratch.ts +41 -0
  127. package/src/PostgresBackendDriver.ts +1008 -0
  128. package/src/PostgresBootstrapper.ts +231 -0
  129. package/src/auth/ensure-tables.ts +381 -0
  130. package/src/auth/services.ts +799 -0
  131. package/src/cli.ts +648 -0
  132. package/src/collections/PostgresCollectionRegistry.ts +96 -0
  133. package/src/connection.ts +84 -0
  134. package/src/data-transformer.ts +608 -0
  135. package/src/databasePoolManager.ts +85 -0
  136. package/src/history/HistoryService.ts +248 -0
  137. package/src/history/ensure-history-table.ts +45 -0
  138. package/src/index.ts +13 -0
  139. package/src/interfaces.ts +60 -0
  140. package/src/schema/auth-schema.ts +169 -0
  141. package/src/schema/doctor-cli.ts +47 -0
  142. package/src/schema/doctor.ts +595 -0
  143. package/src/schema/generate-drizzle-schema-logic.ts +765 -0
  144. package/src/schema/generate-drizzle-schema.ts +151 -0
  145. package/src/schema/introspect-db-logic.ts +542 -0
  146. package/src/schema/introspect-db.ts +211 -0
  147. package/src/schema/test-schema.ts +11 -0
  148. package/src/services/BranchService.ts +237 -0
  149. package/src/services/EntityFetchService.ts +1576 -0
  150. package/src/services/EntityPersistService.ts +349 -0
  151. package/src/services/RelationService.ts +1274 -0
  152. package/src/services/entity-helpers.ts +147 -0
  153. package/src/services/entityService.ts +211 -0
  154. package/src/services/index.ts +13 -0
  155. package/src/services/realtimeService.ts +1034 -0
  156. package/src/utils/drizzle-conditions.ts +1000 -0
  157. package/src/websocket.ts +518 -0
  158. package/test/auth-services.test.ts +661 -0
  159. package/test/batch-many-to-many-regression.test.ts +573 -0
  160. package/test/branchService.test.ts +367 -0
  161. package/test/data-transformer-hardening.test.ts +417 -0
  162. package/test/data-transformer.test.ts +175 -0
  163. package/test/doctor.test.ts +182 -0
  164. package/test/drizzle-conditions.test.ts +895 -0
  165. package/test/entityService.errors.test.ts +367 -0
  166. package/test/entityService.relations.test.ts +1008 -0
  167. package/test/entityService.subcollection-search.test.ts +566 -0
  168. package/test/entityService.test.ts +1035 -0
  169. package/test/generate-drizzle-schema.test.ts +988 -0
  170. package/test/historyService.test.ts +141 -0
  171. package/test/introspect-db-generation.test.ts +436 -0
  172. package/test/introspect-db-utils.test.ts +389 -0
  173. package/test/n-plus-one-regression.test.ts +314 -0
  174. package/test/postgresDataDriver.test.ts +648 -0
  175. package/test/realtimeService.test.ts +307 -0
  176. package/test/relation-pipeline-gaps.test.ts +637 -0
  177. package/test/relations.test.ts +1115 -0
  178. package/test/unmapped-tables-safety.test.ts +345 -0
  179. package/test-drizzle-bug.ts +18 -0
  180. package/test-drizzle-out/0000_cultured_freak.sql +7 -0
  181. package/test-drizzle-out/0001_tiresome_professor_monster.sql +1 -0
  182. package/test-drizzle-out/meta/0000_snapshot.json +55 -0
  183. package/test-drizzle-out/meta/0001_snapshot.json +63 -0
  184. package/test-drizzle-out/meta/_journal.json +20 -0
  185. package/test-drizzle-prompt.sh +2 -0
  186. package/test-policy-prompt.sh +3 -0
  187. package/test-programmatic.ts +30 -0
  188. package/test-programmatic2.ts +59 -0
  189. package/test-schema-no-policies.ts +12 -0
  190. package/test_drizzle_mock.js +3 -0
  191. package/test_find_changed.mjs +32 -0
  192. package/test_hash.js +14 -0
  193. package/test_output.txt +3145 -0
  194. package/tsconfig.json +49 -0
  195. package/tsconfig.prod.json +20 -0
  196. package/vite.config.ts +82 -0
@@ -0,0 +1,307 @@
1
+ import { RealtimeService } from "../src/services/realtimeService";
2
+ import { NodePgDatabase } from "drizzle-orm/node-postgres";
3
+ import { PostgresCollectionRegistry } from "../src/collections/PostgresCollectionRegistry";
4
+ import { EntityCollection } from "@rebasepro/types";
5
+
6
+ jest.mock("../src/services/entityService", () => ({
7
+ EntityService: jest.fn().mockImplementation(() => ({
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([])
13
+ }))
14
+ }));
15
+
16
+ // --- Mock Classes ---
17
+ class MockWebSocket {
18
+ public readyState = 1;
19
+ public send = jest.fn();
20
+ public on = jest.fn();
21
+ constructor() {}
22
+ }
23
+
24
+ const mockPostsCollection: EntityCollection = {
25
+ slug: "posts",
26
+ name: "Posts",
27
+ table: "posts",
28
+ properties: {
29
+ id: { type: "number" },
30
+ title: { type: "string" }
31
+ },
32
+ idField: "id"
33
+ };
34
+
35
+ describe("RealtimeService", () => {
36
+ let realtimeService: RealtimeService;
37
+ let db: jest.Mocked<NodePgDatabase<any>>;
38
+ let registry: PostgresCollectionRegistry;
39
+ let mockDriver: any;
40
+
41
+ beforeEach(() => {
42
+ jest.useFakeTimers();
43
+
44
+ db = {
45
+ execute: jest.fn().mockResolvedValue({ rows: [] }),
46
+ transaction: jest.fn((callback) => callback(db)),
47
+ select: jest.fn().mockReturnThis(),
48
+ from: jest.fn().mockReturnThis(),
49
+ where: jest.fn().mockReturnThis(),
50
+ limit: jest.fn().mockReturnThis()
51
+ } as unknown as jest.Mocked<NodePgDatabase<any>>;
52
+ (db as any).then = jest.fn((resolve) => resolve([]));
53
+
54
+ registry = new PostgresCollectionRegistry();
55
+ jest.spyOn(registry, "getCollectionByPath").mockReturnValue(mockPostsCollection);
56
+
57
+ mockDriver = {
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" } })
64
+ };
65
+
66
+ const mockPoolManager = {
67
+ defaultDatabaseName: "main"
68
+ };
69
+
70
+ const mockAuthSettings = {
71
+ accessTokenSecret: "secret"
72
+ };
73
+
74
+ realtimeService = new RealtimeService(db, registry, mockPoolManager as any, "test-instance", mockAuthSettings);
75
+ realtimeService.setDataDriver(mockDriver);
76
+ realtimeService.setDataDriver(mockDriver);
77
+ });
78
+
79
+ afterEach(() => {
80
+ jest.clearAllMocks();
81
+ jest.useRealTimers();
82
+ });
83
+
84
+ describe("Client Management", () => {
85
+ it("adds and removes clients safely", () => {
86
+ const ws = new MockWebSocket() as any;
87
+ realtimeService.addClient("client-1", ws);
88
+ expect(realtimeService.clients.has("client-1")).toBe(true);
89
+
90
+ realtimeService.removeClient("client-1");
91
+ expect(realtimeService.clients.has("client-1")).toBe(false);
92
+ });
93
+
94
+ it("removes client subscriptions upon disconnect", async () => {
95
+ const ws = new MockWebSocket() as any;
96
+ realtimeService.addClient("client-1", ws);
97
+ await realtimeService.handleClientMessage("client-1", {
98
+ type: "subscribe_collection",
99
+ payload: { path: "posts",
100
+ subscriptionId: "sub-1" }
101
+ });
102
+
103
+ expect(realtimeService.subscriptions.has("sub-1")).toBe(true);
104
+ realtimeService.removeClient("client-1");
105
+ expect(realtimeService.subscriptions.has("sub-1")).toBe(false);
106
+ });
107
+ });
108
+
109
+ describe("Collection Synchronization", () => {
110
+ it("triggers debounced refetch and omits dummy entities on PG_NOTIFY invalidation", async () => {
111
+ const ws = new MockWebSocket() as any;
112
+ realtimeService.addClient("client-1", ws);
113
+
114
+ await realtimeService.handleClientMessage("client-1", {
115
+ type: "subscribe_collection",
116
+ payload: { path: "posts",
117
+ subscriptionId: "sub-1" }
118
+ });
119
+
120
+ // Simulate PG_NOTIFY listener receiving cross-instance payload
121
+ const dummyEntity = { id: "1",
122
+ path: "posts",
123
+ values: { _rebase_invalidated: true } } as any;
124
+ await realtimeService.notifyEntityUpdate("posts", "1", dummyEntity, undefined, false);
125
+
126
+ // Phase 1: sendCollectionEntityPatch SHOULD NOT SEND for dummy
127
+ expect(ws.send).not.toHaveBeenCalledWith(expect.stringContaining("collection_entity_patch"));
128
+
129
+ // Phase 2: Debounced refetch should kick in after 300ms
130
+ jest.advanceTimersByTime(350);
131
+
132
+ // Wait for async promises to drain
133
+ await Promise.resolve();
134
+ await Promise.resolve();
135
+
136
+ // It should fetch the collection with auth
137
+ expect(mockDriver.fetchCollection).toHaveBeenCalled();
138
+
139
+ // It should send the refetched data
140
+ expect(ws.send).toHaveBeenCalled();
141
+ const lastCall = ws.send.mock.calls[ws.send.mock.calls.length - 1][0];
142
+ const parsed = JSON.parse(lastCall);
143
+
144
+ expect(parsed.type).toBe("collection_update");
145
+ expect(parsed.subscriptionId).toBe("sub-1");
146
+ expect(parsed.entities[0].values.title).toBe("Refetched Title");
147
+ });
148
+
149
+ it("sends instant entity patch for valid entity updates without _rebase_invalidated", async () => {
150
+ const ws = new MockWebSocket() as any;
151
+ realtimeService.addClient("client-1", ws);
152
+
153
+ await realtimeService.handleClientMessage("client-1", {
154
+ type: "subscribe_collection",
155
+ payload: { path: "posts",
156
+ subscriptionId: "sub-1" }
157
+ });
158
+
159
+ // Simulated normal Local update
160
+ const freshEntity = { id: "1",
161
+ path: "posts",
162
+ values: { title: "Immediate Patch" } } as any;
163
+ await realtimeService.notifyEntityUpdate("posts", "1", freshEntity, undefined, false);
164
+
165
+ // Phase 1: It SHOULD send immediate patch
166
+ expect(ws.send).toHaveBeenCalled();
167
+ let lastCall = ws.send.mock.calls[ws.send.mock.calls.length - 1][0];
168
+ let parsed = JSON.parse(lastCall);
169
+ expect(parsed.type).toBe("collection_entity_patch");
170
+ expect(parsed.entity.values.title).toBe("Immediate Patch");
171
+
172
+ // Phase 2: Refetch
173
+ jest.advanceTimersByTime(350);
174
+ for (let i = 0; i < 10; i++) {
175
+ await Promise.resolve();
176
+ }
177
+ lastCall = ws.send.mock.calls[ws.send.mock.calls.length - 1][0];
178
+ parsed = JSON.parse(lastCall);
179
+ expect(parsed.type).toBe("collection_update");
180
+ });
181
+ });
182
+
183
+ describe("Entity Synchronization", () => {
184
+ it("triggers debounced refetch and omits dummy entity update for single entity subscriptions", async () => {
185
+ const ws = new MockWebSocket() as any;
186
+ realtimeService.addClient("client-2", ws);
187
+
188
+ await realtimeService.handleClientMessage("client-2", {
189
+ type: "subscribe_entity",
190
+ payload: { path: "posts",
191
+ entityId: "1",
192
+ subscriptionId: "sub-2" }
193
+ });
194
+
195
+ // Need to mock sendEntityUpdate
196
+ const dummyEntity = { id: "1",
197
+ path: "posts",
198
+ values: { _rebase_invalidated: true } } as any;
199
+ await realtimeService.notifyEntityUpdate("posts", "1", dummyEntity, undefined, false);
200
+
201
+ // Important: we patched notifyPathUpdate to NOT send entity_update directly if invalidated
202
+ expect(ws.send).not.toHaveBeenCalledWith(expect.stringContaining("_rebase_invalidated"));
203
+
204
+ // Fast forward refetch timer
205
+ jest.advanceTimersByTime(350);
206
+ await Promise.resolve();
207
+ await Promise.resolve();
208
+
209
+ // It should fetch the single entity
210
+ expect(mockDriver.fetchEntity).toHaveBeenCalledWith(expect.objectContaining({ path: "posts",
211
+ entityId: "1" }));
212
+
213
+ // It should send entity update
214
+ expect(ws.send).toHaveBeenCalled();
215
+ const lastCall = ws.send.mock.calls[ws.send.mock.calls.length - 1][0];
216
+ const parsed = JSON.parse(lastCall);
217
+
218
+ expect(parsed.type).toBe("entity_update");
219
+ expect(parsed.subscriptionId).toBe("sub-2");
220
+ expect(parsed.entity.values.title).toBe("Refetched Entity Title");
221
+ });
222
+
223
+ it("sends instant entity update if valid payload (local mutation)", async () => {
224
+ const ws = new MockWebSocket() as any;
225
+ realtimeService.addClient("client-2", ws);
226
+
227
+ await realtimeService.handleClientMessage("client-2", {
228
+ type: "subscribe_entity",
229
+ payload: { path: "posts",
230
+ entityId: "1",
231
+ subscriptionId: "sub-2" }
232
+ });
233
+
234
+ const freshEntity = { id: "1",
235
+ path: "posts",
236
+ values: { title: "Pure Patch" } } as any;
237
+ await realtimeService.notifyEntityUpdate("posts", "1", freshEntity, undefined, false);
238
+
239
+ expect(ws.send).toHaveBeenCalled();
240
+ const lastCall = ws.send.mock.calls[ws.send.mock.calls.length - 1][0];
241
+ const parsed = JSON.parse(lastCall);
242
+
243
+ expect(parsed.type).toBe("entity_update");
244
+ expect(parsed.entity.values.title).toBe("Pure Patch");
245
+ });
246
+ });
247
+
248
+ describe("RLS (Row Level Security)", () => {
249
+ it("applies auth context correctly on debounced collection refetches", async () => {
250
+ const ws = new MockWebSocket() as any;
251
+ realtimeService.addClient("client-rls", ws);
252
+
253
+ await realtimeService.handleClientMessage("client-rls", {
254
+ type: "subscribe_collection",
255
+ payload: { path: "posts",
256
+ subscriptionId: "sub-rls" }
257
+ }, { userId: "user123",
258
+ roles: ["admin", "editor"] });
259
+
260
+ // Simulate PG_NOTIFY invalidation
261
+ const dummyEntity = { id: "1",
262
+ path: "posts",
263
+ values: { _rebase_invalidated: true } } as any;
264
+ await realtimeService.notifyEntityUpdate("posts", "1", dummyEntity, undefined, false);
265
+
266
+ jest.advanceTimersByTime(350);
267
+ await Promise.resolve();
268
+ await Promise.resolve();
269
+
270
+ expect(db.execute).toHaveBeenCalled();
271
+ const executeCalls = db.execute.mock.calls.map(c => JSON.stringify(c[0]));
272
+
273
+ expect(executeCalls.some(sql => sql.includes("set_config('app.user_id'"))).toBe(true);
274
+ expect(executeCalls.some(sql => sql.includes("set_config('app.user_roles'"))).toBe(true);
275
+ });
276
+
277
+ it("applies auth context correctly on debounced entity refetches", async () => {
278
+ const ws = new MockWebSocket() as any;
279
+ realtimeService.addClient("client-rls-ent", ws);
280
+
281
+ await realtimeService.handleClientMessage("client-rls-ent", {
282
+ type: "subscribe_entity",
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;
292
+ await realtimeService.notifyEntityUpdate("posts", "1", dummyEntity, undefined, false);
293
+
294
+ jest.advanceTimersByTime(350);
295
+ await Promise.resolve();
296
+ await Promise.resolve();
297
+
298
+ expect(db.execute).toHaveBeenCalled();
299
+ const executeCalls = db.execute.mock.calls.map(c => JSON.stringify(c[0]));
300
+
301
+ expect(executeCalls.some(sql => sql.includes("set_config('app.user_id'"))).toBe(true);
302
+ expect(executeCalls.some(sql => sql.includes("set_config('app.user_roles'"))).toBe(true);
303
+ });
304
+ });
305
+
306
+ });
307
+