@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
@@ -1,11 +1,11 @@
1
1
 
2
- import { describe, it, expect, beforeEach } from '@jest/globals';
3
- import { PostgresBackendDriver } from '../src/PostgresBackendDriver';
4
- import { RealtimeService } from '../src/services/realtimeService';
5
- import { EntityService } from '../src/services/entityService';
6
- import { NodePgDatabase } from 'drizzle-orm/node-postgres';
7
- import { sql } from 'drizzle-orm';
8
- import type { EntityCollection, Entity } from '@rebasepro/types';
2
+ import { describe, it, expect, beforeEach } from "@jest/globals";
3
+ import { PostgresBackendDriver } from "../src/PostgresBackendDriver";
4
+ import { RealtimeService } from "../src/services/realtimeService";
5
+ import { EntityService } from "../src/services/entityService";
6
+ import { NodePgDatabase } from "drizzle-orm/node-postgres";
7
+ import { sql } from "drizzle-orm";
8
+ import type { EntityCollection, Entity } from "@rebasepro/types";
9
9
 
10
10
  type MockTx = { execute: jest.Mock };
11
11
  type MockUser = { uid: string; email?: string; roles?: unknown[] };
@@ -18,7 +18,7 @@ const mockDb = {
18
18
  from: jest.fn().mockReturnThis(),
19
19
  limit: jest.fn().mockReturnThis(),
20
20
  where: jest.fn().mockReturnThis(),
21
- orderBy: jest.fn().mockReturnThis(),
21
+ orderBy: jest.fn().mockReturnThis()
22
22
  } as unknown as NodePgDatabase;
23
23
 
24
24
  const mockRealtimeService = {
@@ -26,11 +26,11 @@ const mockRealtimeService = {
26
26
  addSubscriptionCallback: jest.fn(),
27
27
  removeSubscriptionCallback: jest.fn(),
28
28
  subscriptions: new Map(),
29
- notifyEntityUpdate: jest.fn(),
29
+ notifyEntityUpdate: jest.fn()
30
30
  } as unknown as RealtimeService;
31
31
 
32
32
 
33
- describe('PostgresBackendDriver', () => {
33
+ describe("PostgresBackendDriver", () => {
34
34
  let delegate: PostgresBackendDriver;
35
35
 
36
36
  beforeEach(() => {
@@ -38,22 +38,24 @@ describe('PostgresBackendDriver', () => {
38
38
  delegate = new PostgresBackendDriver(mockDb, mockRealtimeService);
39
39
  });
40
40
 
41
- it('should initialize correctly', () => {
41
+ it("should initialize correctly", () => {
42
42
  expect(delegate).toBeDefined();
43
- expect(delegate.key).toBe('postgres');
43
+ expect(delegate.key).toBe("postgres");
44
44
  });
45
45
 
46
- describe('withAuth', () => {
47
- it('should return a new delegate instance', async () => {
48
- const user = { uid: 'test-user', email: 'test@example.com' };
46
+ describe("withAuth", () => {
47
+ it("should return a new delegate instance", async () => {
48
+ const user = { uid: "test-user",
49
+ email: "test@example.com" };
49
50
  const authDelegate = await delegate.withAuth(user);
50
51
 
51
52
  expect(authDelegate).toBeDefined();
52
53
  expect(authDelegate).not.toBe(delegate);
53
54
  });
54
55
 
55
- it('should wrap methods in a transaction', async () => {
56
- const user = { uid: 'test-user', email: 'test@example.com' };
56
+ it("should wrap methods in a transaction", async () => {
57
+ const user = { uid: "test-user",
58
+ email: "test@example.com" };
57
59
  const authDelegate = await delegate.withAuth(user);
58
60
 
59
61
  const mockTx = { execute: jest.fn() };
@@ -61,15 +63,18 @@ describe('PostgresBackendDriver', () => {
61
63
  return await cb(mockTx);
62
64
  });
63
65
 
64
- jest.spyOn(PostgresBackendDriver.prototype, 'fetchCollection').mockResolvedValueOnce([]);
66
+ jest.spyOn(PostgresBackendDriver.prototype, "fetchCollection").mockResolvedValueOnce([]);
65
67
 
66
- await authDelegate.fetchCollection({ path: 'test_collection', collection: { slug: 'test', properties: {} } as unknown as EntityCollection });
68
+ await authDelegate.fetchCollection({ path: "test_collection",
69
+ collection: { slug: "test",
70
+ properties: {} } as unknown as EntityCollection });
67
71
 
68
72
  expect(mockDb.transaction).toHaveBeenCalled();
69
73
  });
70
74
 
71
- it('should set app.user_id in the transaction for RLS', async () => {
72
- const user = { uid: 'test-user-123', email: 'test@example.com' };
75
+ it("should set app.user_id in the transaction for RLS", async () => {
76
+ const user = { uid: "test-user-123",
77
+ email: "test@example.com" };
73
78
  const authDelegate = await delegate.withAuth(user);
74
79
 
75
80
  const mockTx: MockTx = { execute: jest.fn() };
@@ -77,9 +82,11 @@ describe('PostgresBackendDriver', () => {
77
82
  return await cb(mockTx);
78
83
  });
79
84
 
80
- jest.spyOn(PostgresBackendDriver.prototype, 'fetchCollection').mockResolvedValueOnce([]);
85
+ jest.spyOn(PostgresBackendDriver.prototype, "fetchCollection").mockResolvedValueOnce([]);
81
86
 
82
- await authDelegate.fetchCollection({ path: 'test', collection: { slug: 'test', properties: {} } as unknown as EntityCollection });
87
+ await authDelegate.fetchCollection({ path: "test",
88
+ collection: { slug: "test",
89
+ properties: {} } as unknown as EntityCollection });
83
90
 
84
91
  expect(mockDb.transaction).toHaveBeenCalled();
85
92
  expect(mockTx.execute).toHaveBeenCalled();
@@ -89,8 +96,10 @@ describe('PostgresBackendDriver', () => {
89
96
  expect(callString).toContain("test-user-123");
90
97
  });
91
98
 
92
- it('should set app.user_roles handling array of strings correctly', async () => {
93
- const user: MockUser = { uid: 'test-user-123', email: 'test@example.com', roles: ['admin', 'editor'] };
99
+ it("should set app.user_roles handling array of strings correctly", async () => {
100
+ const user: MockUser = { uid: "test-user-123",
101
+ email: "test@example.com",
102
+ roles: ["admin", "editor"] };
94
103
  const authDelegate = await delegate.withAuth(user);
95
104
 
96
105
  const mockTx: MockTx = { execute: jest.fn() };
@@ -98,9 +107,11 @@ describe('PostgresBackendDriver', () => {
98
107
  return await cb(mockTx);
99
108
  });
100
109
 
101
- jest.spyOn(PostgresBackendDriver.prototype, 'fetchCollection').mockResolvedValueOnce([]);
110
+ jest.spyOn(PostgresBackendDriver.prototype, "fetchCollection").mockResolvedValueOnce([]);
102
111
 
103
- await authDelegate.fetchCollection({ path: 'test', collection: { slug: 'test', properties: {} } as unknown as EntityCollection });
112
+ await authDelegate.fetchCollection({ path: "test",
113
+ collection: { slug: "test",
114
+ properties: {} } as unknown as EntityCollection });
104
115
 
105
116
  expect(mockTx.execute).toHaveBeenCalledTimes(1);
106
117
  const sqlCall = mockTx.execute.mock.calls[0][0];
@@ -109,8 +120,10 @@ describe('PostgresBackendDriver', () => {
109
120
  expect(callString).toContain("admin,editor");
110
121
  });
111
122
 
112
- it('should set app.user_roles handling array of objects correctly', async () => {
113
- const user: MockUser = { uid: 'test-user-123', email: 'test@example.com', roles: [{ id: 'admin' }, { id: 'editor' }] };
123
+ it("should set app.user_roles handling array of objects correctly", async () => {
124
+ const user: MockUser = { uid: "test-user-123",
125
+ email: "test@example.com",
126
+ roles: [{ id: "admin" }, { id: "editor" }] };
114
127
  const authDelegate = await delegate.withAuth(user);
115
128
 
116
129
  const mockTx: MockTx = { execute: jest.fn() };
@@ -118,9 +131,11 @@ describe('PostgresBackendDriver', () => {
118
131
  return await cb(mockTx);
119
132
  });
120
133
 
121
- jest.spyOn(PostgresBackendDriver.prototype, 'fetchCollection').mockResolvedValueOnce([]);
134
+ jest.spyOn(PostgresBackendDriver.prototype, "fetchCollection").mockResolvedValueOnce([]);
122
135
 
123
- await authDelegate.fetchCollection({ path: 'test', collection: { slug: 'test', properties: {} } as unknown as EntityCollection });
136
+ await authDelegate.fetchCollection({ path: "test",
137
+ collection: { slug: "test",
138
+ properties: {} } as unknown as EntityCollection });
124
139
 
125
140
  expect(mockTx.execute).toHaveBeenCalledTimes(1);
126
141
  const sqlCall = mockTx.execute.mock.calls[0][0];
@@ -129,7 +144,7 @@ describe('PostgresBackendDriver', () => {
129
144
  expect(callString).toContain("admin,editor");
130
145
  });
131
146
 
132
- it('should fallback to anonymous and empty roles when missing from user', async () => {
147
+ it("should fallback to anonymous and empty roles when missing from user", async () => {
133
148
  const user = {} as unknown as MockUser; // Empty user object
134
149
  const authDelegate = await delegate.withAuth(user);
135
150
 
@@ -139,23 +154,25 @@ describe('PostgresBackendDriver', () => {
139
154
  });
140
155
 
141
156
  // We mock fetchCollection to just return something and not crash
142
- jest.spyOn(PostgresBackendDriver.prototype, 'fetchCollection').mockResolvedValueOnce([]);
157
+ jest.spyOn(PostgresBackendDriver.prototype, "fetchCollection").mockResolvedValueOnce([]);
143
158
 
144
- await authDelegate.fetchCollection({ path: 'test', collection: { slug: 'test', properties: {} } as unknown as EntityCollection });
159
+ await authDelegate.fetchCollection({ path: "test",
160
+ collection: { slug: "test",
161
+ properties: {} } as unknown as EntityCollection });
145
162
 
146
163
  expect(mockDb.transaction).toHaveBeenCalledTimes(1);
147
-
164
+
148
165
  const transactionCallback = (mockDb.transaction as jest.Mock).mock.calls[0][0];
149
166
  const mockTx: MockTx = { execute: jest.fn() };
150
167
  await transactionCallback(mockTx).catch(() => {});
151
-
168
+
152
169
  const sqlCall = mockTx.execute.mock.calls[0][0];
153
170
  const callString = JSON.stringify(sqlCall);
154
171
  expect(callString).toContain("set_config");
155
172
  expect(callString).toContain("anonymous");
156
173
  });
157
174
 
158
- it('should gracefully handle completely null or undefined user objects', async () => {
175
+ it("should gracefully handle completely null or undefined user objects", async () => {
159
176
  const authDelegate = await delegate.withAuth(null as unknown as MockUser);
160
177
 
161
178
  (mockDb.transaction as jest.Mock).mockImplementation(async (cb: (tx: MockTx) => Promise<unknown>) => {
@@ -163,14 +180,16 @@ describe('PostgresBackendDriver', () => {
163
180
  return cb(mockTx);
164
181
  });
165
182
 
166
- jest.spyOn(PostgresBackendDriver.prototype, 'fetchCollection').mockResolvedValueOnce([]);
183
+ jest.spyOn(PostgresBackendDriver.prototype, "fetchCollection").mockResolvedValueOnce([]);
167
184
 
168
- await authDelegate.fetchCollection({ path: 'test', collection: { slug: 'test', properties: {} } as unknown as EntityCollection });
185
+ await authDelegate.fetchCollection({ path: "test",
186
+ collection: { slug: "test",
187
+ properties: {} } as unknown as EntityCollection });
169
188
 
170
189
  const transactionCallback = (mockDb.transaction as jest.Mock).mock.calls[0][0];
171
190
  const mockTx: MockTx = { execute: jest.fn() };
172
191
  await transactionCallback(mockTx).catch(() => {});
173
-
192
+
174
193
  const sqlCall = mockTx.execute.mock.calls[0][0];
175
194
  const callString = JSON.stringify(sqlCall);
176
195
  expect(callString).toContain("set_config");
@@ -178,14 +197,15 @@ describe('PostgresBackendDriver', () => {
178
197
  });
179
198
  });
180
199
 
181
- describe('AuthenticatedPostgresBackendDriver Transactional Integrity', () => {
200
+ describe("AuthenticatedPostgresBackendDriver Transactional Integrity", () => {
182
201
  let authDelegate: Awaited<ReturnType<typeof delegate.withAuth>>;
183
202
 
184
203
  beforeEach(async () => {
185
- authDelegate = await delegate.withAuth({ uid: 'test-user', email: 'test@example.com' });
204
+ authDelegate = await delegate.withAuth({ uid: "test-user",
205
+ email: "test@example.com" });
186
206
  });
187
207
 
188
- it('should execute operation and flush notifications on success', async () => {
208
+ it("should execute operation and flush notifications on success", async () => {
189
209
  const mockTx = {
190
210
  execute: jest.fn()
191
211
  };
@@ -195,25 +215,29 @@ describe('PostgresBackendDriver', () => {
195
215
  });
196
216
 
197
217
  // Let's pretend the operation queues a notification
198
- jest.spyOn(PostgresBackendDriver.prototype, 'saveEntity').mockImplementationOnce(async function(this: { _pendingNotifications?: Array<Record<string, unknown>> }) {
218
+ jest.spyOn(PostgresBackendDriver.prototype, "saveEntity").mockImplementationOnce(async function(this: { _pendingNotifications?: Array<Record<string, unknown>> }) {
199
219
  this._pendingNotifications?.push({
200
- path: 'test',
201
- entityId: '123',
202
- entity: {} as unknown as Entity,
220
+ path: "test",
221
+ entityId: "123",
222
+ entity: {} as unknown as Entity
203
223
  });
204
224
  return {} as unknown as Entity;
205
225
  });
206
226
 
207
- await authDelegate.saveEntity({ path: 'test', entityId: '123', values: {}, collection: {} as unknown as EntityCollection, status: 'new' });
227
+ await authDelegate.saveEntity({ path: "test",
228
+ entityId: "123",
229
+ values: {},
230
+ collection: {} as unknown as EntityCollection,
231
+ status: "new" });
208
232
 
209
233
  // Ensure transaction was called
210
234
  expect(mockDb.transaction).toHaveBeenCalled();
211
235
 
212
236
  // Ensure notification was flushed after commit
213
- expect(mockRealtimeService.notifyEntityUpdate).toHaveBeenCalledWith('test', '123', {}, undefined);
237
+ expect(mockRealtimeService.notifyEntityUpdate).toHaveBeenCalledWith("test", "123", {}, undefined);
214
238
  });
215
239
 
216
- it('should NOT flush notifications if transaction throws an error', async () => {
240
+ it("should NOT flush notifications if transaction throws an error", async () => {
217
241
  const mockTx = {
218
242
  execute: jest.fn()
219
243
  };
@@ -222,22 +246,26 @@ describe('PostgresBackendDriver', () => {
222
246
  return await cb(mockTx);
223
247
  });
224
248
 
225
- jest.spyOn(PostgresBackendDriver.prototype, 'saveEntity').mockImplementationOnce(async function(this: { _pendingNotifications?: Array<Record<string, unknown>> }) {
249
+ jest.spyOn(PostgresBackendDriver.prototype, "saveEntity").mockImplementationOnce(async function(this: { _pendingNotifications?: Array<Record<string, unknown>> }) {
226
250
  this._pendingNotifications?.push({
227
- path: 'test',
228
- entityId: '123',
229
- entity: {} as unknown as Entity,
251
+ path: "test",
252
+ entityId: "123",
253
+ entity: {} as unknown as Entity
230
254
  });
231
255
  throw new Error("Transaction failed");
232
256
  });
233
257
 
234
- await expect(authDelegate.saveEntity({ path: 'test', entityId: '123', values: {}, collection: {} as unknown as EntityCollection, status: 'new' })).rejects.toThrow("Transaction failed");
258
+ await expect(authDelegate.saveEntity({ path: "test",
259
+ entityId: "123",
260
+ values: {},
261
+ collection: {} as unknown as EntityCollection,
262
+ status: "new" })).rejects.toThrow("Transaction failed");
235
263
 
236
264
  // Ensure notification was NOT flushed
237
265
  expect(mockRealtimeService.notifyEntityUpdate).not.toHaveBeenCalled();
238
266
  });
239
267
 
240
- it('should return successfully even if a deferred notification throw an error (resilience)', async () => {
268
+ it("should return successfully even if a deferred notification throw an error (resilience)", async () => {
241
269
  const mockTx = {
242
270
  execute: jest.fn()
243
271
  };
@@ -246,27 +274,31 @@ describe('PostgresBackendDriver', () => {
246
274
  return await cb(mockTx);
247
275
  });
248
276
 
249
- jest.spyOn(PostgresBackendDriver.prototype, 'saveEntity').mockImplementationOnce(async function(this: { _pendingNotifications?: Array<Record<string, unknown>> }) {
277
+ jest.spyOn(PostgresBackendDriver.prototype, "saveEntity").mockImplementationOnce(async function(this: { _pendingNotifications?: Array<Record<string, unknown>> }) {
250
278
  this._pendingNotifications?.push({
251
- path: 'test',
252
- entityId: 'buggy-123',
253
- entity: {} as unknown as Entity,
279
+ path: "test",
280
+ entityId: "buggy-123",
281
+ entity: {} as unknown as Entity
254
282
  });
255
- return { id: 'success' } as unknown as Entity;
283
+ return { id: "success" } as unknown as Entity;
256
284
  });
257
285
 
258
286
  // Mock the notification service intentionally crashing
259
287
  const notifySpy = mockRealtimeService.notifyEntityUpdate as jest.Mock;
260
288
  notifySpy.mockRejectedValueOnce(new Error("Network Failure on Notification"));
261
-
289
+
262
290
  // Should still return the entity successfully despite the error
263
- const result = await authDelegate.saveEntity({ path: 'test', entityId: 'buggy-123', values: {}, collection: {} as unknown as EntityCollection, status: 'new' });
264
-
265
- expect(result).toEqual({ id: 'success' });
266
- expect(notifySpy).toHaveBeenCalledWith('test', 'buggy-123', {}, undefined);
291
+ const result = await authDelegate.saveEntity({ path: "test",
292
+ entityId: "buggy-123",
293
+ values: {},
294
+ collection: {} as unknown as EntityCollection,
295
+ status: "new" });
296
+
297
+ expect(result).toEqual({ id: "success" });
298
+ expect(notifySpy).toHaveBeenCalledWith("test", "buggy-123", {}, undefined);
267
299
  });
268
300
 
269
- it('should safely isolate completely concurrent transaction notifications from leaking across scopes', async () => {
301
+ it("should safely isolate completely concurrent transaction notifications from leaking across scopes", async () => {
270
302
  const mockTx = {
271
303
  execute: jest.fn()
272
304
  };
@@ -278,143 +310,173 @@ describe('PostgresBackendDriver', () => {
278
310
  });
279
311
 
280
312
  // Operation 1 flags a notification
281
- const save1 = jest.spyOn(PostgresBackendDriver.prototype, 'saveEntity').mockImplementationOnce(async function(this: { _pendingNotifications?: Array<Record<string, unknown>> }) {
282
- this._pendingNotifications?.push({ path: 'scope-1', entityId: '1', entity: {} as unknown as Entity });
313
+ const save1 = jest.spyOn(PostgresBackendDriver.prototype, "saveEntity").mockImplementationOnce(async function(this: { _pendingNotifications?: Array<Record<string, unknown>> }) {
314
+ this._pendingNotifications?.push({ path: "scope-1",
315
+ entityId: "1",
316
+ entity: {} as unknown as Entity });
283
317
  return {} as unknown as Entity;
284
318
  });
285
319
 
286
320
  // Operation 2 flags a different notification
287
- const save2 = jest.spyOn(PostgresBackendDriver.prototype, 'saveEntity').mockImplementationOnce(async function(this: { _pendingNotifications?: Array<Record<string, unknown>> }) {
288
- this._pendingNotifications?.push({ path: 'scope-2', entityId: '2', entity: {} as unknown as Entity });
321
+ const save2 = jest.spyOn(PostgresBackendDriver.prototype, "saveEntity").mockImplementationOnce(async function(this: { _pendingNotifications?: Array<Record<string, unknown>> }) {
322
+ this._pendingNotifications?.push({ path: "scope-2",
323
+ entityId: "2",
324
+ entity: {} as unknown as Entity });
289
325
  return {} as unknown as Entity;
290
326
  });
291
327
 
292
328
  // Fire simultaneously
293
329
  await Promise.all([
294
- authDelegate.saveEntity({ path: 'scope-1', entityId: '1', values: {}, collection: {} as unknown as EntityCollection, status: 'new' }),
295
- authDelegate.saveEntity({ path: 'scope-2', entityId: '2', values: {}, collection: {} as unknown as EntityCollection, status: 'new' })
330
+ authDelegate.saveEntity({ path: "scope-1",
331
+ entityId: "1",
332
+ values: {},
333
+ collection: {} as unknown as EntityCollection,
334
+ status: "new" }),
335
+ authDelegate.saveEntity({ path: "scope-2",
336
+ entityId: "2",
337
+ values: {},
338
+ collection: {} as unknown as EntityCollection,
339
+ status: "new" })
296
340
  ]);
297
341
 
298
342
  // Ensure our notify was called with both exact combinations, but NOT cross-pollinated
299
- expect(mockRealtimeService.notifyEntityUpdate).toHaveBeenCalledWith('scope-1', '1', {}, undefined);
300
- expect(mockRealtimeService.notifyEntityUpdate).toHaveBeenCalledWith('scope-2', '2', {}, undefined);
301
-
343
+ expect(mockRealtimeService.notifyEntityUpdate).toHaveBeenCalledWith("scope-1", "1", {}, undefined);
344
+ expect(mockRealtimeService.notifyEntityUpdate).toHaveBeenCalledWith("scope-2", "2", {}, undefined);
345
+
302
346
  // Check count
303
347
  expect(mockRealtimeService.notifyEntityUpdate).toHaveBeenCalledTimes(2);
304
348
  });
305
349
  });
306
350
 
307
- describe('AuthenticatedPostgresBackendDriver Delegation', () => {
351
+ describe("AuthenticatedPostgresBackendDriver Delegation", () => {
308
352
  let authDelegate: Awaited<ReturnType<typeof delegate.withAuth>>;
309
353
 
310
354
  beforeEach(async () => {
311
- authDelegate = await delegate.withAuth({ uid: 'test-user', email: 'test@example.com' });
355
+ authDelegate = await delegate.withAuth({ uid: "test-user",
356
+ email: "test@example.com" });
312
357
  });
313
358
 
314
- it('should delegate executeSql directly without a transaction', async () => {
315
- jest.spyOn(delegate, 'executeSql').mockResolvedValueOnce([{ id: 1 }] as unknown as Record<string, unknown>[]);
359
+ it("should delegate executeSql via admin without a transaction", async () => {
360
+ jest.spyOn(delegate, "executeSql").mockResolvedValueOnce([{ id: 1 }] as unknown as Record<string, unknown>[]);
316
361
 
317
- const result = await authDelegate.executeSql("SELECT 1");
362
+ const result = await authDelegate.admin.executeSql("SELECT 1");
318
363
 
319
364
  expect(mockDb.transaction).not.toHaveBeenCalled();
320
- expect(delegate.executeSql).toHaveBeenCalledWith("SELECT 1", undefined);
365
+ expect(delegate.executeSql).toHaveBeenCalledWith("SELECT 1");
321
366
  expect(result).toEqual([{ id: 1 }]);
322
367
  });
323
368
 
324
- it('should override listenCollection to inject auth context', () => {
369
+ it("should override listenCollection to inject auth context", () => {
325
370
  const mockUnsubscribe = jest.fn();
326
-
371
+
327
372
  // Clear original map instead of reassigning
328
373
  mockRealtimeService.subscriptions.clear();
329
-
374
+
330
375
  // The act of calling the delegated method should update the last subscription
331
- jest.spyOn(delegate, 'listenCollection').mockImplementationOnce(() => {
332
- mockRealtimeService.subscriptions.set('sub1', { clientId: 'driver', authContext: undefined });
376
+ jest.spyOn(delegate, "listenCollection").mockImplementationOnce(() => {
377
+ mockRealtimeService.subscriptions.set("sub1", { clientId: "driver",
378
+ authContext: undefined });
333
379
  return mockUnsubscribe;
334
380
  });
335
381
 
336
- const unsub = authDelegate.listenCollection({ path: 'test', collection: {} as unknown as EntityCollection, callbacks: {} as unknown as Record<string, unknown> });
382
+ const unsub = authDelegate.listenCollection({ path: "test",
383
+ collection: {} as unknown as EntityCollection,
384
+ callbacks: {} as unknown as Record<string, unknown> });
337
385
 
338
386
  expect(unsub).toBe(mockUnsubscribe);
339
- expect(mockRealtimeService.subscriptions.get('sub1').authContext).toEqual({ userId: 'test-user', roles: [] });
387
+ expect(mockRealtimeService.subscriptions.get("sub1").authContext).toEqual({ userId: "test-user",
388
+ roles: [] });
340
389
  });
341
390
 
342
- it('should override listenEntity to inject auth context', () => {
391
+ it("should override listenEntity to inject auth context", () => {
343
392
  const mockUnsubscribe = jest.fn();
344
-
393
+
345
394
  mockRealtimeService.subscriptions.clear();
346
-
347
- jest.spyOn(delegate, 'listenEntity').mockImplementationOnce(() => {
348
- mockRealtimeService.subscriptions.set('sub2', { clientId: 'driver', authContext: undefined });
395
+
396
+ jest.spyOn(delegate, "listenEntity").mockImplementationOnce(() => {
397
+ mockRealtimeService.subscriptions.set("sub2", { clientId: "driver",
398
+ authContext: undefined });
349
399
  return mockUnsubscribe;
350
400
  });
351
401
 
352
- const unsub = authDelegate.listenEntity({ path: 'test', entityId: '123', collection: {} as unknown as EntityCollection, callbacks: {} as unknown as Record<string, unknown> });
402
+ const unsub = authDelegate.listenEntity({ path: "test",
403
+ entityId: "123",
404
+ collection: {} as unknown as EntityCollection,
405
+ callbacks: {} as unknown as Record<string, unknown> });
353
406
 
354
407
  expect(unsub).toBe(mockUnsubscribe);
355
- expect(mockRealtimeService.subscriptions.get('sub2').authContext).toEqual({ userId: 'test-user', roles: [] });
408
+ expect(mockRealtimeService.subscriptions.get("sub2").authContext).toEqual({ userId: "test-user",
409
+ roles: [] });
356
410
  });
357
411
 
358
- it('should handle listenCollection gracefully if delegate fails to add a subscription', () => {
412
+ it("should handle listenCollection gracefully if delegate fails to add a subscription", () => {
359
413
  const mockUnsubscribe = jest.fn();
360
414
  mockRealtimeService.subscriptions.clear();
361
- jest.spyOn(delegate, 'listenCollection').mockImplementationOnce(() => mockUnsubscribe);
362
- const unsub = authDelegate.listenCollection({ path: 'empty-test', collection: {} as unknown as EntityCollection, callbacks: {} as unknown as Record<string, unknown> });
415
+ jest.spyOn(delegate, "listenCollection").mockImplementationOnce(() => mockUnsubscribe);
416
+ const unsub = authDelegate.listenCollection({ path: "empty-test",
417
+ collection: {} as unknown as EntityCollection,
418
+ callbacks: {} as unknown as Record<string, unknown> });
363
419
  expect(unsub).toBe(mockUnsubscribe);
364
420
  });
365
421
 
366
- it('should NOT skip authContext injection if subscription has a non-driver clientId', () => {
422
+ it("should NOT skip authContext injection if subscription has a non-driver clientId", () => {
367
423
  const mockUnsubscribe = jest.fn();
368
424
  mockRealtimeService.subscriptions.clear();
369
- jest.spyOn(delegate, 'listenCollection').mockImplementationOnce(() => {
370
- mockRealtimeService.subscriptions.set('sub-ext', { clientId: 'external-client', authContext: undefined });
425
+ jest.spyOn(delegate, "listenCollection").mockImplementationOnce(() => {
426
+ mockRealtimeService.subscriptions.set("sub-ext", { clientId: "external-client",
427
+ authContext: undefined });
371
428
  return mockUnsubscribe;
372
429
  });
373
- authDelegate.listenCollection({ path: 'test', collection: {} as unknown as EntityCollection, callbacks: {} as unknown as Record<string, unknown> });
430
+ authDelegate.listenCollection({ path: "test",
431
+ collection: {} as unknown as EntityCollection,
432
+ callbacks: {} as unknown as Record<string, unknown> });
374
433
  // authContext should NOT be injected because clientId !== 'driver'
375
- expect(mockRealtimeService.subscriptions.get('sub-ext').authContext).toBeUndefined();
434
+ expect(mockRealtimeService.subscriptions.get("sub-ext").authContext).toBeUndefined();
376
435
  });
377
436
 
378
- it('should delegate fetchAvailableDatabases without a transaction', async () => {
379
- jest.spyOn(delegate, 'fetchAvailableDatabases').mockResolvedValueOnce(['db1', 'db2']);
380
- const result = await authDelegate.fetchAvailableDatabases();
437
+ it("should delegate fetchAvailableDatabases via admin without a transaction", async () => {
438
+ jest.spyOn(delegate, "fetchAvailableDatabases").mockResolvedValueOnce(["db1", "db2"]);
439
+ const result = await authDelegate.admin.fetchAvailableDatabases!();
381
440
  expect(mockDb.transaction).not.toHaveBeenCalled();
382
- expect(result).toEqual(['db1', 'db2']);
441
+ expect(result).toEqual(["db1", "db2"]);
383
442
  });
384
443
 
385
- it('should delegate fetchAvailableRoles without a transaction', async () => {
386
- jest.spyOn(delegate, 'fetchAvailableRoles').mockResolvedValueOnce(['admin', 'viewer']);
387
- const result = await authDelegate.fetchAvailableRoles();
444
+ it("should delegate fetchAvailableRoles via admin without a transaction", async () => {
445
+ jest.spyOn(delegate, "fetchAvailableRoles").mockResolvedValueOnce(["admin", "viewer"]);
446
+ const result = await authDelegate.admin.fetchAvailableRoles!();
388
447
  expect(mockDb.transaction).not.toHaveBeenCalled();
389
- expect(result).toEqual(['admin', 'viewer']);
448
+ expect(result).toEqual(["admin", "viewer"]);
390
449
  });
391
450
 
392
- it('should delegate fetchCurrentDatabase without a transaction', async () => {
393
- jest.spyOn(delegate, 'fetchCurrentDatabase').mockResolvedValueOnce('my_db');
394
- const result = await authDelegate.fetchCurrentDatabase();
451
+ it("should delegate fetchCurrentDatabase via admin without a transaction", async () => {
452
+ jest.spyOn(delegate, "fetchCurrentDatabase").mockResolvedValueOnce("my_db");
453
+ const result = await authDelegate.admin.fetchCurrentDatabase!();
395
454
  expect(mockDb.transaction).not.toHaveBeenCalled();
396
- expect(result).toBe('my_db');
455
+ expect(result).toBe("my_db");
397
456
  });
398
457
 
399
- it('should delegate fetchUnmappedTables without a transaction', async () => {
400
- jest.spyOn(delegate, 'fetchUnmappedTables').mockResolvedValueOnce(['orphan_table']);
401
- const result = await authDelegate.fetchUnmappedTables(['mapped']);
458
+ it("should delegate fetchUnmappedTables via admin without a transaction", async () => {
459
+ jest.spyOn(delegate, "fetchUnmappedTables").mockResolvedValueOnce(["orphan_table"]);
460
+ const result = await authDelegate.admin.fetchUnmappedTables!(["mapped"]);
402
461
  expect(mockDb.transaction).not.toHaveBeenCalled();
403
- expect(result).toEqual(['orphan_table']);
462
+ expect(result).toEqual(["orphan_table"]);
404
463
  });
405
464
 
406
- it('should delegate fetchTableMetadata without a transaction', async () => {
407
- jest.spyOn(delegate, 'fetchTableMetadata').mockResolvedValueOnce([{ name: 'id', type: 'int4' }] as unknown as Record<string, unknown>[]);
408
- const result = await authDelegate.fetchTableMetadata('users');
465
+ it("should delegate fetchTableMetadata via admin without a transaction", async () => {
466
+ jest.spyOn(delegate, "fetchTableMetadata").mockResolvedValueOnce([{ name: "id",
467
+ type: "int4" }] as unknown as Record<string, unknown>[]);
468
+ const result = await authDelegate.admin.fetchTableMetadata!("users");
409
469
  expect(mockDb.transaction).not.toHaveBeenCalled();
410
- expect(result).toEqual([{ name: 'id', type: 'int4' }]);
470
+ expect(result).toEqual([{ name: "id",
471
+ type: "int4" }]);
411
472
  });
412
473
  });
413
474
 
414
- describe('AuthenticatedPostgresBackendDriver Security & Contract', () => {
415
- it('should use parameterized queries (drizzle sql``) NOT string interpolation for set_config', async () => {
475
+ describe("AuthenticatedPostgresBackendDriver Security & Contract", () => {
476
+ it("should use parameterized queries (drizzle sql``) NOT string interpolation for set_config", async () => {
416
477
  // A malicious uid should be passed as a parameter, not concatenated
417
- const maliciousUser: MockUser = { uid: "admin'; DROP TABLE users; --", email: 'hacker@evil.com' };
478
+ const maliciousUser: MockUser = { uid: "admin'; DROP TABLE users; --",
479
+ email: "hacker@evil.com" };
418
480
  const authDelegate = await delegate.withAuth(maliciousUser);
419
481
 
420
482
  const mockTx: MockTx = { execute: jest.fn() };
@@ -422,9 +484,11 @@ describe('PostgresBackendDriver', () => {
422
484
  return await cb(mockTx);
423
485
  });
424
486
 
425
- jest.spyOn(PostgresBackendDriver.prototype, 'fetchCollection').mockResolvedValueOnce([]);
487
+ jest.spyOn(PostgresBackendDriver.prototype, "fetchCollection").mockResolvedValueOnce([]);
426
488
 
427
- await authDelegate.fetchCollection({ path: 'x', collection: { slug: 'x', properties: {} } as unknown as EntityCollection });
489
+ await authDelegate.fetchCollection({ path: "x",
490
+ collection: { slug: "x",
491
+ properties: {} } as unknown as EntityCollection });
428
492
 
429
493
  // The SQL template tag should have the userId as a parameter value, not embedded in the SQL string
430
494
  const sqlObj = mockTx.execute.mock.calls[0][0];
@@ -433,11 +497,12 @@ describe('PostgresBackendDriver', () => {
433
497
  // The malicious string should appear as a bound parameter, not as raw SQL
434
498
  expect(serialized).toContain("admin'; DROP TABLE users; --");
435
499
  // Verify it's using Drizzle's tagged template, which inherently parameterizes
436
- expect(sqlObj).toHaveProperty('queryChunks');
500
+ expect(sqlObj).toHaveProperty("queryChunks");
437
501
  });
438
502
 
439
- it('should produce a valid JWT payload in set_config even with exotic roles', async () => {
440
- const user: MockUser = { uid: 'u1', roles: ['role"with"quotes', 'role,with,commas', 'rôle-spécial'] };
503
+ it("should produce a valid JWT payload in set_config even with exotic roles", async () => {
504
+ const user: MockUser = { uid: "u1",
505
+ roles: ['role"with"quotes', "role,with,commas", "rôle-spécial"] };
441
506
  const authDelegate = await delegate.withAuth(user);
442
507
 
443
508
  const mockTx: MockTx = { execute: jest.fn() };
@@ -445,18 +510,21 @@ describe('PostgresBackendDriver', () => {
445
510
  return await cb(mockTx);
446
511
  });
447
512
 
448
- jest.spyOn(PostgresBackendDriver.prototype, 'fetchCollection').mockResolvedValueOnce([]);
513
+ jest.spyOn(PostgresBackendDriver.prototype, "fetchCollection").mockResolvedValueOnce([]);
449
514
 
450
- await authDelegate.fetchCollection({ path: 'x', collection: { slug: 'x', properties: {} } as unknown as EntityCollection });
515
+ await authDelegate.fetchCollection({ path: "x",
516
+ collection: { slug: "x",
517
+ properties: {} } as unknown as EntityCollection });
451
518
 
452
519
  const serialized = JSON.stringify(mockTx.execute.mock.calls[0][0]);
453
520
  // The JWT should be valid JSON.stringify output containing the roles
454
521
  expect(serialized).toContain('role\\"with\\"quotes');
455
- expect(serialized).toContain('rôle-spécial');
522
+ expect(serialized).toContain("rôle-spécial");
456
523
  });
457
524
 
458
- it('should handle role objects missing the id field by falling back to String()', async () => {
459
- const user: MockUser = { uid: 'u1', roles: [{ name: 'viewer' }, 42, null] };
525
+ it("should handle role objects missing the id field by falling back to String()", async () => {
526
+ const user: MockUser = { uid: "u1",
527
+ roles: [{ name: "viewer" }, 42, null] };
460
528
  const authDelegate = await delegate.withAuth(user);
461
529
 
462
530
  const mockTx: MockTx = { execute: jest.fn() };
@@ -464,92 +532,116 @@ describe('PostgresBackendDriver', () => {
464
532
  return await cb(mockTx);
465
533
  });
466
534
 
467
- jest.spyOn(PostgresBackendDriver.prototype, 'fetchCollection').mockResolvedValueOnce([]);
535
+ jest.spyOn(PostgresBackendDriver.prototype, "fetchCollection").mockResolvedValueOnce([]);
468
536
 
469
- await authDelegate.fetchCollection({ path: 'x', collection: { slug: 'x', properties: {} } as unknown as EntityCollection });
537
+ await authDelegate.fetchCollection({ path: "x",
538
+ collection: { slug: "x",
539
+ properties: {} } as unknown as EntityCollection });
470
540
 
471
541
  const serialized = JSON.stringify(mockTx.execute.mock.calls[0][0]);
472
542
  // Objects without id → String({name:'viewer'}) = "[object Object]", 42 → "42", null → "null"
473
- expect(serialized).toContain('set_config');
543
+ expect(serialized).toContain("set_config");
474
544
  });
475
545
 
476
- it('should wrap deleteEntity in a transaction with RLS', async () => {
546
+ it("should wrap deleteEntity in a transaction with RLS", async () => {
477
547
  (mockDb.transaction as jest.Mock).mockImplementation(async (cb) => {
478
548
  return await cb({ execute: jest.fn() });
479
549
  });
480
- jest.spyOn(PostgresBackendDriver.prototype, 'deleteEntity').mockResolvedValueOnce(undefined);
550
+ jest.spyOn(PostgresBackendDriver.prototype, "deleteEntity").mockResolvedValueOnce(undefined);
481
551
 
482
- const authDelegate = await delegate.withAuth({ uid: 'deleter', email: 'del@test.com' } as MockUser);
483
- await authDelegate.deleteEntity({ entity: { id: '1', path: 'x', values: {} } as unknown as Entity });
552
+ const authDelegate = await delegate.withAuth({ uid: "deleter",
553
+ email: "del@test.com" } as MockUser);
554
+ await authDelegate.deleteEntity({ entity: { id: "1",
555
+ path: "x",
556
+ values: {} } as unknown as Entity });
484
557
 
485
558
  expect(mockDb.transaction).toHaveBeenCalled();
486
559
  });
487
560
 
488
- it('should wrap checkUniqueField in a transaction with RLS', async () => {
561
+ it("should wrap checkUniqueField in a transaction with RLS", async () => {
489
562
  (mockDb.transaction as jest.Mock).mockImplementation(async (cb) => {
490
563
  return await cb({ execute: jest.fn() });
491
564
  });
492
- jest.spyOn(PostgresBackendDriver.prototype, 'checkUniqueField').mockResolvedValueOnce(true);
565
+ jest.spyOn(PostgresBackendDriver.prototype, "checkUniqueField").mockResolvedValueOnce(true);
493
566
 
494
- const authDelegate = await delegate.withAuth({ uid: 'checker', email: 'c@test.com' } as MockUser);
495
- const result = await authDelegate.checkUniqueField('path', 'email', 'test@x.com', '1');
567
+ const authDelegate = await delegate.withAuth({ uid: "checker",
568
+ email: "c@test.com" } as MockUser);
569
+ const result = await authDelegate.checkUniqueField("path", "email", "test@x.com", "1");
496
570
 
497
571
  expect(mockDb.transaction).toHaveBeenCalled();
498
572
  expect(result).toBe(true);
499
573
  });
500
574
 
501
- it('should wrap countEntities in a transaction with RLS', async () => {
575
+ it("should wrap countEntities in a transaction with RLS", async () => {
502
576
  (mockDb.transaction as jest.Mock).mockImplementation(async (cb) => {
503
577
  return await cb({ execute: jest.fn() });
504
578
  });
505
- jest.spyOn(PostgresBackendDriver.prototype, 'countEntities').mockResolvedValueOnce(42);
579
+ jest.spyOn(PostgresBackendDriver.prototype, "countEntities").mockResolvedValueOnce(42);
506
580
 
507
- const authDelegate = await delegate.withAuth({ uid: 'counter', email: 'c@test.com' } as MockUser);
508
- const result = await authDelegate.countEntities({ path: 'items', collection: {} as unknown as EntityCollection });
581
+ const authDelegate = await delegate.withAuth({ uid: "counter",
582
+ email: "c@test.com" } as MockUser);
583
+ const result = await authDelegate.countEntities({ path: "items",
584
+ collection: {} as unknown as EntityCollection });
509
585
 
510
586
  expect(mockDb.transaction).toHaveBeenCalled();
511
587
  expect(result).toBe(42);
512
588
  });
513
589
 
514
590
  it('should expose key="postgres" and initialised=true on the authenticated wrapper', async () => {
515
- const authDelegate = await delegate.withAuth({ uid: 'u', email: 'u@t.com' } as MockUser);
516
- expect(authDelegate.key).toBe('postgres');
591
+ const authDelegate = await delegate.withAuth({ uid: "u",
592
+ email: "u@t.com" } as MockUser);
593
+ expect(authDelegate.key).toBe("postgres");
517
594
  expect(authDelegate.initialised).toBe(true);
518
595
  });
519
596
 
520
- it('should NOT share user state if withAuth is called with different users', async () => {
521
- const auth1 = await delegate.withAuth({ uid: 'alice', email: 'a@t.com' } as MockUser);
522
- const auth2 = await delegate.withAuth({ uid: 'bob', email: 'b@t.com' } as MockUser);
597
+ it("should NOT share user state if withAuth is called with different users", async () => {
598
+ const auth1 = await delegate.withAuth({ uid: "alice",
599
+ email: "a@t.com" } as MockUser);
600
+ const auth2 = await delegate.withAuth({ uid: "bob",
601
+ email: "b@t.com" } as MockUser);
523
602
 
524
- expect(auth1.user.uid).toBe('alice');
525
- expect(auth2.user.uid).toBe('bob');
603
+ expect(auth1.user.uid).toBe("alice");
604
+ expect(auth2.user.uid).toBe("bob");
526
605
  // They share the same underlying delegate
527
606
  expect(auth1.delegate).toBe(auth2.delegate);
528
607
  });
529
608
 
530
- it('should create a fresh pendingNotifications array per withTransaction call (no accumulation)', async () => {
531
- const authDelegate = await delegate.withAuth({ uid: 'u1', email: 'u@t.com' } as MockUser);
609
+ it("should create a fresh pendingNotifications array per withTransaction call (no accumulation)", async () => {
610
+ const authDelegate = await delegate.withAuth({ uid: "u1",
611
+ email: "u@t.com" } as MockUser);
532
612
  const mockTx = { execute: jest.fn() };
533
613
 
534
614
  (mockDb.transaction as jest.Mock).mockImplementation(async (cb) => await cb(mockTx));
535
615
 
536
- jest.spyOn(PostgresBackendDriver.prototype, 'saveEntity')
616
+ jest.spyOn(PostgresBackendDriver.prototype, "saveEntity")
537
617
  .mockImplementationOnce(async function(this: { _pendingNotifications?: Array<Record<string, unknown>> }) {
538
- this._pendingNotifications?.push({ path: 'call-1', entityId: '1', entity: {} as unknown as Entity });
618
+ this._pendingNotifications?.push({ path: "call-1",
619
+ entityId: "1",
620
+ entity: {} as unknown as Entity });
539
621
  return {} as unknown as Entity;
540
622
  })
541
623
  .mockImplementationOnce(async function(this: { _pendingNotifications?: Array<Record<string, unknown>> }) {
542
- this._pendingNotifications?.push({ path: 'call-2', entityId: '2', entity: {} as unknown as Entity });
624
+ this._pendingNotifications?.push({ path: "call-2",
625
+ entityId: "2",
626
+ entity: {} as unknown as Entity });
543
627
  return {} as unknown as Entity;
544
628
  });
545
629
 
546
- await authDelegate.saveEntity({ path: 'call-1', entityId: '1', values: {}, collection: {} as unknown as EntityCollection, status: 'new' });
547
- await authDelegate.saveEntity({ path: 'call-2', entityId: '2', values: {}, collection: {} as unknown as EntityCollection, status: 'new' });
630
+ await authDelegate.saveEntity({ path: "call-1",
631
+ entityId: "1",
632
+ values: {},
633
+ collection: {} as unknown as EntityCollection,
634
+ status: "new" });
635
+ await authDelegate.saveEntity({ path: "call-2",
636
+ entityId: "2",
637
+ values: {},
638
+ collection: {} as unknown as EntityCollection,
639
+ status: "new" });
548
640
 
549
641
  // Each call should have flushed exactly 1 notification, not accumulated
550
642
  expect(mockRealtimeService.notifyEntityUpdate).toHaveBeenCalledTimes(2);
551
- expect(mockRealtimeService.notifyEntityUpdate).toHaveBeenNthCalledWith(1, 'call-1', '1', {}, undefined);
552
- expect(mockRealtimeService.notifyEntityUpdate).toHaveBeenNthCalledWith(2, 'call-2', '2', {}, undefined);
643
+ expect(mockRealtimeService.notifyEntityUpdate).toHaveBeenNthCalledWith(1, "call-1", "1", {}, undefined);
644
+ expect(mockRealtimeService.notifyEntityUpdate).toHaveBeenNthCalledWith(2, "call-2", "2", {}, undefined);
553
645
  });
554
646
  });
555
647
  });