@rebasepro/server-postgresql 0.1.2 → 0.2.3

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 (71) hide show
  1. package/LICENSE +22 -6
  2. package/dist/common/src/data/query_builder.d.ts +51 -0
  3. package/dist/common/src/index.d.ts +1 -0
  4. package/dist/common/src/util/entities.d.ts +2 -2
  5. package/dist/common/src/util/relations.d.ts +1 -1
  6. package/dist/index.es.js +1435 -738
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/index.umd.js +1433 -736
  9. package/dist/index.umd.js.map +1 -1
  10. package/dist/server-postgresql/src/PostgresAdapter.d.ts +6 -0
  11. package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +2 -1
  12. package/dist/server-postgresql/src/PostgresBootstrapper.d.ts +0 -5
  13. package/dist/server-postgresql/src/auth/ensure-tables.d.ts +2 -1
  14. package/dist/server-postgresql/src/auth/services.d.ts +37 -15
  15. package/dist/server-postgresql/src/index.d.ts +1 -0
  16. package/dist/server-postgresql/src/schema/auth-schema.d.ts +43 -856
  17. package/dist/server-postgresql/src/schema/default-collections.d.ts +2 -0
  18. package/dist/server-postgresql/src/schema/doctor.d.ts +10 -1
  19. package/dist/server-postgresql/src/schema/introspect-db-logic.d.ts +1 -0
  20. package/dist/server-postgresql/src/services/entity-helpers.d.ts +1 -1
  21. package/dist/server-postgresql/src/services/realtimeService.d.ts +12 -0
  22. package/dist/server-postgresql/src/websocket.d.ts +2 -1
  23. package/dist/types/src/controllers/auth.d.ts +9 -8
  24. package/dist/types/src/controllers/client.d.ts +3 -0
  25. package/dist/types/src/controllers/data.d.ts +21 -0
  26. package/dist/types/src/types/auth_adapter.d.ts +356 -0
  27. package/dist/types/src/types/collections.d.ts +67 -2
  28. package/dist/types/src/types/database_adapter.d.ts +94 -0
  29. package/dist/types/src/types/entity_actions.d.ts +7 -1
  30. package/dist/types/src/types/entity_callbacks.d.ts +1 -1
  31. package/dist/types/src/types/entity_views.d.ts +36 -1
  32. package/dist/types/src/types/index.d.ts +2 -0
  33. package/dist/types/src/types/plugins.d.ts +1 -1
  34. package/dist/types/src/types/properties.d.ts +24 -5
  35. package/dist/types/src/types/property_config.d.ts +6 -2
  36. package/dist/types/src/types/relations.d.ts +1 -1
  37. package/dist/types/src/types/translations.d.ts +8 -0
  38. package/dist/types/src/users/user.d.ts +5 -0
  39. package/package.json +22 -15
  40. package/src/PostgresAdapter.ts +59 -0
  41. package/src/PostgresBackendDriver.ts +66 -13
  42. package/src/PostgresBootstrapper.ts +35 -15
  43. package/src/auth/ensure-tables.ts +82 -189
  44. package/src/auth/services.ts +421 -170
  45. package/src/cli.ts +49 -13
  46. package/src/data-transformer.ts +78 -8
  47. package/src/history/HistoryService.ts +25 -2
  48. package/src/index.ts +1 -0
  49. package/src/schema/auth-schema.ts +130 -98
  50. package/src/schema/default-collections.ts +69 -0
  51. package/src/schema/doctor-cli.ts +5 -1
  52. package/src/schema/doctor.ts +166 -48
  53. package/src/schema/generate-drizzle-schema-logic.ts +74 -27
  54. package/src/schema/generate-drizzle-schema.ts +13 -3
  55. package/src/schema/introspect-db-inference.ts +5 -5
  56. package/src/schema/introspect-db-logic.ts +9 -2
  57. package/src/schema/introspect-db.ts +14 -3
  58. package/src/services/EntityFetchService.ts +5 -5
  59. package/src/services/RelationService.ts +2 -2
  60. package/src/services/entity-helpers.ts +1 -1
  61. package/src/services/realtimeService.ts +145 -136
  62. package/src/utils/drizzle-conditions.ts +16 -2
  63. package/src/websocket.ts +113 -37
  64. package/test/auth-services.test.ts +163 -74
  65. package/test/data-transformer-hardening.test.ts +57 -0
  66. package/test/data-transformer.test.ts +43 -0
  67. package/test/generate-drizzle-schema.test.ts +7 -5
  68. package/test/introspect-db-utils.test.ts +4 -1
  69. package/test/postgresDataDriver.test.ts +147 -1
  70. package/test/realtimeService.test.ts +7 -7
  71. package/test/websocket.test.ts +139 -0
@@ -1,19 +1,45 @@
1
1
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
2
2
  import { UserService, RoleService, RefreshTokenService, PasswordResetTokenService, Role } from "../src/auth/services";
3
- import { users, refreshTokens, passwordResetTokens, User } from "../src/schema/auth-schema";
3
+ import { users, refreshTokens, passwordResetTokens } from "../src/schema/auth-schema";
4
+ import { UserData } from "@rebasepro/server-core";
4
5
 
5
6
  // Mock the drizzle-orm functions
6
- jest.mock("drizzle-orm", () => ({
7
- eq: jest.fn((field, value) => ({ field,
8
- value,
9
- type: "eq" })),
10
- sql: jest.fn((strings: TemplateStringsArray, ...values: unknown[]) => ({
11
- strings,
12
- values,
13
- type: "sql"
14
- })),
15
- relations: jest.fn(() => ({}))
16
- }));
7
+ jest.mock("drizzle-orm", () => {
8
+ const actual = jest.requireActual("drizzle-orm");
9
+ return {
10
+ ...actual,
11
+ eq: jest.fn((field, value) => ({ field, value, type: "eq" })),
12
+ sql: Object.assign(
13
+ jest.fn((strings: TemplateStringsArray, ...values: unknown[]) => ({
14
+ strings,
15
+ values,
16
+ type: "sql"
17
+ })),
18
+ {
19
+ raw: jest.fn((val: string) => ({ val, type: "sql-raw" })),
20
+ join: jest.fn((parts: unknown[], separator: unknown) => ({ parts, separator, type: "sql-join" }))
21
+ }
22
+ ),
23
+ relations: jest.fn(() => ({}))
24
+ };
25
+ });
26
+
27
+ function mockUserData(overrides: Partial<UserData>): UserData {
28
+ return {
29
+ id: "user-123",
30
+ email: "test@example.com",
31
+ passwordHash: null,
32
+ displayName: null,
33
+ photoUrl: null,
34
+ emailVerified: false,
35
+ emailVerificationToken: null,
36
+ emailVerificationSentAt: null,
37
+ createdAt: expect.any(Date),
38
+ updatedAt: expect.any(Date),
39
+ metadata: {},
40
+ ...overrides
41
+ };
42
+ }
17
43
 
18
44
  describe("Auth Services", () => {
19
45
  let db: jest.Mocked<NodePgDatabase<Record<string, unknown>>>;
@@ -37,9 +63,7 @@ describe("Auth Services", () => {
37
63
  });
38
64
 
39
65
  mockSelectWhere = jest.fn().mockResolvedValue([]);
40
- mockSelectFrom = jest.fn().mockReturnValue({
41
- where: mockSelectWhere
42
- });
66
+ mockSelectFrom = jest.fn();
43
67
 
44
68
  mockUpdateReturning = jest.fn().mockResolvedValue([]);
45
69
  mockUpdateWhere = jest.fn().mockReturnValue({ returning: mockUpdateReturning });
@@ -49,13 +73,50 @@ describe("Auth Services", () => {
49
73
 
50
74
  mockExecute = jest.fn().mockResolvedValue({ rows: [] });
51
75
 
76
+ // Set up chainable mock for db.select()
77
+ const mockChain: any = {};
78
+ mockChain.from = jest.fn().mockImplementation((...args) => {
79
+ const result = mockSelectFrom(...args);
80
+ if (result && typeof result.then === "function") {
81
+ return result; // If listUsers mocks selectFrom to return a promise, return it directly
82
+ }
83
+ return mockChain;
84
+ });
85
+ mockChain.innerJoin = jest.fn().mockReturnValue(mockChain);
86
+ mockChain.where = jest.fn().mockImplementation((...args) => {
87
+ mockChain.wherePromise = mockSelectWhere(...args);
88
+ return mockChain;
89
+ });
90
+ mockChain.limit = jest.fn().mockReturnValue(mockChain);
91
+ mockChain.offset = jest.fn().mockReturnValue(mockChain);
92
+ mockChain.orderBy = jest.fn().mockReturnValue(mockChain);
93
+ mockChain.then = jest.fn().mockImplementation(async (onFulfilled) => {
94
+ let val;
95
+ if (mockChain.wherePromise) {
96
+ val = await mockChain.wherePromise;
97
+ mockChain.wherePromise = null;
98
+ } else if (mockSelectWhere.mock.calls.length > 0) {
99
+ const result = mockSelectWhere.mock.results[mockSelectWhere.mock.results.length - 1];
100
+ val = result.type === "return" ? result.value : undefined;
101
+ if (val && typeof val.then === "function") {
102
+ val = await val;
103
+ }
104
+ } else {
105
+ val = [];
106
+ }
107
+ return onFulfilled(val || []);
108
+ });
109
+
52
110
  db = {
53
111
  insert: jest.fn().mockReturnValue({ values: mockInsertValues }),
54
- select: jest.fn().mockReturnValue({ from: mockSelectFrom }),
112
+ select: jest.fn().mockReturnValue(mockChain),
55
113
  update: jest.fn().mockReturnValue({ set: mockUpdateSet }),
56
114
  delete: jest.fn().mockReturnValue({ where: mockDeleteWhere }),
57
115
  execute: mockExecute
58
116
  } as unknown as jest.Mocked<NodePgDatabase<Record<string, unknown>>>;
117
+
118
+ // Set default return value for mockSelectFrom to return mockChain (chainable)
119
+ mockSelectFrom.mockReturnValue(mockChain);
59
120
  });
60
121
 
61
122
  describe("UserService", () => {
@@ -71,30 +132,34 @@ describe("Auth Services", () => {
71
132
  email: "test@example.com",
72
133
  displayName: "Test User"
73
134
  };
74
- const createdUser = { id: "user-123",
75
- ...newUser,
76
- createdAt: new Date(),
77
- updatedAt: new Date() };
78
- mockInsertReturning.mockResolvedValueOnce([createdUser]);
135
+ const dbReturnedUser = {
136
+ id: "user-123",
137
+ ...newUser,
138
+ createdAt: new Date(),
139
+ updatedAt: new Date()
140
+ };
141
+ mockInsertReturning.mockResolvedValueOnce([dbReturnedUser]);
79
142
 
80
143
  const result = await userService.createUser(newUser);
81
144
 
82
145
  expect(db.insert).toHaveBeenCalledWith(users);
83
- expect(mockInsertValues).toHaveBeenCalledWith(newUser);
84
- expect(result).toEqual(createdUser);
146
+ expect(mockInsertValues).toHaveBeenCalledWith({
147
+ ...newUser,
148
+ metadata: {}
149
+ });
150
+ expect(result).toEqual(mockUserData({ displayName: "Test User" }));
85
151
  });
86
152
  });
87
153
 
88
154
  describe("getUserById", () => {
89
155
  it("should return user when found", async () => {
90
- const mockUser = { id: "user-123",
91
- email: "test@example.com" };
156
+ const mockUser = { id: "user-123", email: "test@example.com" };
92
157
  mockSelectWhere.mockResolvedValueOnce([mockUser]);
93
158
 
94
159
  const result = await userService.getUserById("user-123");
95
160
 
96
161
  expect(db.select).toHaveBeenCalled();
97
- expect(result).toEqual(mockUser);
162
+ expect(result).toEqual(mockUserData({}));
98
163
  });
99
164
 
100
165
  it("should return null when user not found", async () => {
@@ -108,13 +173,12 @@ email: "test@example.com" };
108
173
 
109
174
  describe("getUserByEmail", () => {
110
175
  it("should return user when found by email", async () => {
111
- const mockUser = { id: "user-123",
112
- email: "test@example.com" };
176
+ const mockUser = { id: "user-123", email: "test@example.com" };
113
177
  mockSelectWhere.mockResolvedValueOnce([mockUser]);
114
178
 
115
179
  const result = await userService.getUserByEmail("test@example.com");
116
180
 
117
- expect(result).toEqual(mockUser);
181
+ expect(result).toEqual(mockUserData({}));
118
182
  });
119
183
 
120
184
  it("should lowercase email for lookup", async () => {
@@ -128,14 +192,14 @@ email: "test@example.com" };
128
192
  });
129
193
 
130
194
  describe("getUserByIdentity", () => {
131
- it("should execute sql for identity lookup", async () => {
132
- const mockUser = { id: "user-123" };
133
- // execute mock instead of select
134
- mockExecute.mockResolvedValueOnce({ rows: [mockUser] });
195
+ it("should fetch user by identity", async () => {
196
+ const mockUser = { id: "user-123", email: "test@example.com" };
197
+ mockSelectWhere.mockResolvedValueOnce([{ user: mockUser }]);
135
198
 
136
199
  const result = await userService.getUserByIdentity("google", "google-abc");
137
200
 
138
- expect(mockExecute).toHaveBeenCalled();
201
+ expect(db.select).toHaveBeenCalled();
202
+ expect(result).toEqual(expect.objectContaining({ id: "user-123", email: "test@example.com" }));
139
203
  });
140
204
  });
141
205
 
@@ -158,9 +222,11 @@ email: "test@example.com" };
158
222
 
159
223
  describe("updateUser", () => {
160
224
  it("should update user and return updated record", async () => {
161
- const updatedUser = { id: "user-123",
162
- email: "test@example.com",
163
- displayName: "Updated Name" };
225
+ const updatedUser = {
226
+ id: "user-123",
227
+ email: "test@example.com",
228
+ displayName: "Updated Name"
229
+ };
164
230
  mockUpdateReturning.mockResolvedValueOnce([updatedUser]);
165
231
 
166
232
  const result = await userService.updateUser("user-123", { displayName: "Updated Name" });
@@ -170,7 +236,7 @@ displayName: "Updated Name" };
170
236
  displayName: "Updated Name",
171
237
  updatedAt: expect.any(Date)
172
238
  }));
173
- expect(result).toEqual(updatedUser);
239
+ expect(result).toEqual(mockUserData({ displayName: "Updated Name" }));
174
240
  });
175
241
 
176
242
  it("should return null when user not found", async () => {
@@ -194,17 +260,18 @@ displayName: "Updated Name" };
194
260
  describe("listUsers", () => {
195
261
  it("should return all users", async () => {
196
262
  const mockUsers = [
197
- { id: "user-1",
198
- email: "user1@example.com" },
199
- { id: "user-2",
200
- email: "user2@example.com" }
263
+ { id: "user-1", email: "user1@example.com" },
264
+ { id: "user-2", email: "user2@example.com" }
201
265
  ];
202
266
  mockSelectFrom.mockReturnValueOnce(Promise.resolve(mockUsers));
203
267
 
204
268
  const result = await userService.listUsers();
205
269
 
206
270
  expect(db.select).toHaveBeenCalled();
207
- expect(result).toEqual(mockUsers);
271
+ expect(result).toEqual([
272
+ mockUserData({ id: "user-1", email: "user1@example.com" }),
273
+ mockUserData({ id: "user-2", email: "user2@example.com" })
274
+ ]);
208
275
  });
209
276
  });
210
277
 
@@ -264,13 +331,12 @@ email: "user2@example.com" }
264
331
 
265
332
  describe("getUserByVerificationToken", () => {
266
333
  it("should find user by verification token", async () => {
267
- const mockUser = { id: "user-123",
268
- email: "test@example.com" };
334
+ const mockUser = { id: "user-123", email: "test@example.com" };
269
335
  mockSelectWhere.mockResolvedValueOnce([mockUser]);
270
336
 
271
337
  const result = await userService.getUserByVerificationToken("token-abc");
272
338
 
273
- expect(result).toEqual(mockUser);
339
+ expect(result).toEqual(mockUserData({}));
274
340
  });
275
341
  });
276
342
 
@@ -279,17 +345,17 @@ email: "test@example.com" };
279
345
  mockExecute.mockResolvedValueOnce({
280
346
  rows: [
281
347
  { id: "admin",
282
- name: "Admin",
283
- is_admin: true,
284
- default_permissions: null,
285
- collection_permissions: null,
286
- config: null },
348
+ name: "Admin",
349
+ is_admin: true,
350
+ default_permissions: null,
351
+ collection_permissions: null,
352
+ config: null },
287
353
  { id: "editor",
288
- name: "Editor",
289
- is_admin: false,
290
- default_permissions: { edit: true },
291
- collection_permissions: null,
292
- config: null }
354
+ name: "Editor",
355
+ is_admin: false,
356
+ default_permissions: { edit: true },
357
+ collection_permissions: null,
358
+ config: null }
293
359
  ]
294
360
  });
295
361
 
@@ -312,11 +378,11 @@ config: null }
312
378
  mockExecute.mockResolvedValueOnce({
313
379
  rows: [
314
380
  { id: "admin",
315
- name: "Admin",
316
- is_admin: true,
317
- default_permissions: null,
318
- collection_permissions: null,
319
- config: null }
381
+ name: "Admin",
382
+ is_admin: true,
383
+ default_permissions: null,
384
+ collection_permissions: null,
385
+ config: null }
320
386
  ]
321
387
  });
322
388
 
@@ -353,28 +419,27 @@ config: null }
353
419
 
354
420
  describe("getUserWithRoles", () => {
355
421
  it("should return user with roles", async () => {
356
- const mockUser = { id: "user-123",
357
- email: "test@example.com" };
422
+ const mockUser = { id: "user-123", email: "test@example.com" };
358
423
  mockSelectWhere.mockResolvedValueOnce([mockUser]);
359
424
  mockExecute.mockResolvedValueOnce({
360
425
  rows: [{ id: "admin",
361
- name: "Admin",
362
- is_admin: true,
363
- default_permissions: null,
364
- collection_permissions: null,
365
- config: null }]
426
+ name: "Admin",
427
+ is_admin: true,
428
+ default_permissions: null,
429
+ collection_permissions: null,
430
+ config: null }]
366
431
  });
367
432
 
368
433
  const result = await userService.getUserWithRoles("user-123");
369
434
 
370
435
  expect(result).toEqual({
371
- user: mockUser,
436
+ user: mockUserData({}),
372
437
  roles: [{ id: "admin",
373
- name: "Admin",
374
- isAdmin: true,
375
- defaultPermissions: null,
376
- collectionPermissions: null,
377
- config: null }]
438
+ name: "Admin",
439
+ isAdmin: true,
440
+ defaultPermissions: null,
441
+ collectionPermissions: null,
442
+ config: null }]
378
443
  });
379
444
  });
380
445
 
@@ -386,6 +451,30 @@ config: null }]
386
451
  expect(result).toBeNull();
387
452
  });
388
453
  });
454
+
455
+ describe("listUsersPaginated", () => {
456
+ it("should return paginated and filtered users list", async () => {
457
+ mockExecute
458
+ .mockResolvedValueOnce({ rows: [{ total: 1 }] })
459
+ .mockResolvedValueOnce({ rows: [{ id: "user-123", email: "test@example.com" }] });
460
+
461
+ const result = await userService.listUsersPaginated({
462
+ limit: 10,
463
+ offset: 0,
464
+ search: "test",
465
+ orderBy: "email",
466
+ orderDir: "asc"
467
+ });
468
+
469
+ expect(mockExecute).toHaveBeenCalledTimes(2);
470
+ expect(result).toEqual({
471
+ users: [mockUserData({ id: "user-123", email: "test@example.com" })],
472
+ total: 1,
473
+ limit: 10,
474
+ offset: 0
475
+ });
476
+ });
477
+ });
389
478
  });
390
479
 
391
480
  describe("RoleService", () => {
@@ -415,3 +415,60 @@ describe("structural dunder guard", () => {
415
415
  expect(dunderKeys).toEqual([]);
416
416
  });
417
417
  });
418
+
419
+ // ─────────────────────────────────────────────────────────────
420
+ // parsePropertyFromServer — vector parsing
421
+ // ─────────────────────────────────────────────────────────────
422
+ describe("parsePropertyFromServer vector parsing", () => {
423
+ const targetCollection = makeCollection("items", {
424
+ embedding: {
425
+ type: "vector",
426
+ dimensions: 3,
427
+ name: "Embedding"
428
+ } as unknown as Property
429
+ });
430
+
431
+ it("parses string representation '[0.1,-0.2,3.14]' from postgres into wrapped vector", () => {
432
+ const property = targetCollection.properties.embedding as Property;
433
+ const result = parsePropertyFromServer("[0.1,-0.2,3.14]", property, targetCollection, "embedding");
434
+ expect(result).toEqual({
435
+ __type: "Vector",
436
+ value: [0.1, -0.2, 3.14]
437
+ });
438
+ });
439
+
440
+ it("parses numeric array [0.1,-0.2,3.14] into wrapped vector", () => {
441
+ const property = targetCollection.properties.embedding as Property;
442
+ const result = parsePropertyFromServer([0.1, -0.2, 3.14], property, targetCollection, "embedding");
443
+ expect(result).toEqual({
444
+ __type: "Vector",
445
+ value: [0.1, -0.2, 3.14]
446
+ });
447
+ });
448
+ });
449
+
450
+ // ─────────────────────────────────────────────────────────────
451
+ // parsePropertyFromServer — binary parsing
452
+ // ─────────────────────────────────────────────────────────────
453
+ describe("parsePropertyFromServer binary parsing", () => {
454
+ const targetCollection = makeCollection("items", {
455
+ data: {
456
+ type: "binary",
457
+ name: "Data"
458
+ } as unknown as Property
459
+ });
460
+
461
+ it("parses database Buffer into a base64 data URL string", () => {
462
+ const property = targetCollection.properties.data as Property;
463
+ const buffer = Buffer.from("hello");
464
+ const result = parsePropertyFromServer(buffer, property, targetCollection, "data");
465
+ expect(result).toBe("data:application/octet-stream;base64,aGVsbG8=");
466
+ });
467
+
468
+ it("parses Buffer object (JSON serialization) into a base64 data URL string", () => {
469
+ const property = targetCollection.properties.data as Property;
470
+ const bufferObj = { type: "Buffer", data: Array.from(Buffer.from("hello")) };
471
+ const result = parsePropertyFromServer(bufferObj, property, targetCollection, "data");
472
+ expect(result).toBe("data:application/octet-stream;base64,aGVsbG8=");
473
+ });
474
+ });
@@ -172,4 +172,47 @@ path: "authors" }
172
172
  expect(serializePropertyToServer(false, boolProp)).toBe(false);
173
173
  });
174
174
  });
175
+
176
+ // ── Vector property ──
177
+ describe("vector property", () => {
178
+ const vectorProp: Property = { type: "vector", dimensions: 3 } as Property;
179
+
180
+ it("should serialize a Vector instance to a flat array", () => {
181
+ const vectorVal = { __type: "Vector", value: [1.5, -2.0, 3.14] };
182
+ expect(serializePropertyToServer(vectorVal, vectorProp)).toEqual([1.5, -2.0, 3.14]);
183
+ });
184
+
185
+ it("should serialize a raw number array to a flat array", () => {
186
+ expect(serializePropertyToServer([0.5, 0.6, 0.7], vectorProp)).toEqual([0.5, 0.6, 0.7]);
187
+ });
188
+
189
+ it("should return null for null values", () => {
190
+ expect(serializePropertyToServer(null, vectorProp)).toBeNull();
191
+ });
192
+
193
+ it("should return undefined for undefined values", () => {
194
+ expect(serializePropertyToServer(undefined, vectorProp)).toBeUndefined();
195
+ });
196
+ });
197
+
198
+ // ── Binary property ──
199
+ describe("binary property", () => {
200
+ const binaryProp: Property = { type: "binary" } as Property;
201
+
202
+ it("should serialize a base64 data URL to a Buffer", () => {
203
+ const base64Data = "data:application/octet-stream;base64,aGVsbG8=";
204
+ const result = serializePropertyToServer(base64Data, binaryProp);
205
+ expect(Buffer.isBuffer(result)).toBe(true);
206
+ expect((result as Buffer).toString("utf8")).toBe("hello");
207
+ });
208
+
209
+ it("should pass through a Buffer as-is", () => {
210
+ const buffer = Buffer.from("world");
211
+ expect(serializePropertyToServer(buffer, binaryProp)).toBe(buffer);
212
+ });
213
+
214
+ it("should return null for null values", () => {
215
+ expect(serializePropertyToServer(null, binaryProp)).toBeNull();
216
+ });
217
+ });
175
218
  });
@@ -116,6 +116,8 @@ relationName: "tags" }
116
116
  expect(cleanResult).toContain("tag_id: varchar(\"tag_id\").notNull().references(() => tags.id, { onDelete: \"cascade\" })");
117
117
  expect(cleanResult).toContain("(table) => ({ pk: primaryKey({ columns: [table.post_id, table.tag_id] }) })");
118
118
  expect(cleanResult).toContain("export const postsRelations = drizzleRelations(posts, ({ one, many }) => ({ \"tags\": many(postsToTags, { relationName: \"tags\" }) }));");
119
+ const expectedJunctionRelations = "export const postsToTagsRelations = drizzleRelations(postsToTags, ({ one, many }) => ({ \"post_id\": one(posts, { fields: [postsToTags.post_id], references: [posts.id], relationName: \"tags\" }), \"tag_id\": one(tags, { fields: [postsToTags.tag_id], references: [tags.id], relationName: \"posts_to_tags_tag_id\" }) }));";
120
+ expect(cleanResult).toContain(cleanSchema(expectedJunctionRelations));
119
121
  });
120
122
 
121
123
  describe("generateDrizzleSchema Column Types", () => {
@@ -391,7 +393,7 @@ ownerField: "user_id" }
391
393
  expect(result).toContain("pgPolicy");
392
394
  expect(result).toContain('as: "permissive"');
393
395
  expect(result).toContain('for: "all"');
394
- expect(result).toContain("${table.user_id} = auth.uid()");
396
+ expect(result).toContain("user_id = auth.uid()");
395
397
  // 'all' needs both using and withCheck
396
398
  expect(result).toContain("using:");
397
399
  expect(result).toContain("withCheck:");
@@ -565,7 +567,7 @@ using: "{is_locked} = false" }
565
567
 
566
568
  const result = await generateSchema(collections);
567
569
  expect(result).toContain('as: "restrictive"');
568
- expect(result).toContain("${table.is_locked} = false");
570
+ expect(result).toContain("is_locked = false");
569
571
  });
570
572
 
571
573
  it("should generate raw SQL using clause with column references", async () => {
@@ -584,7 +586,7 @@ using: "{published_at} > now() - interval '30 days'" }
584
586
  }];
585
587
 
586
588
  const result = await generateSchema(collections);
587
- expect(result).toContain("${table.published_at} > now() - interval '30 days'");
589
+ expect(result).toContain("published_at > now() - interval '30 days'");
588
590
  });
589
591
 
590
592
  it("should generate raw SQL withCheck clause", async () => {
@@ -609,8 +611,8 @@ using: "{published_at} > now() - interval '30 days'" }
609
611
  const result = await generateSchema(collections);
610
612
  expect(result).toContain("using:");
611
613
  expect(result).toContain("withCheck:");
612
- expect(result).toContain("${table.user_id} = auth.uid()");
613
- expect(result).toContain("${table.status} != 'archived'");
614
+ expect(result).toContain("user_id = auth.uid()");
615
+ expect(result).toContain("status != 'archived'");
614
616
  });
615
617
 
616
618
  it("should use custom policy names when provided", async () => {
@@ -200,10 +200,13 @@ describe("mapPgType", () => {
200
200
  expect(mapPgType("_text")).toBe("array");
201
201
  });
202
202
  it("maps string-like types to string", () => {
203
- for (const t of ["text", "varchar", "character varying", "char", "character", "uuid", "bytea", "inet", "cidr", "macaddr", "macaddr8", "interval"]) {
203
+ for (const t of ["text", "varchar", "character varying", "char", "character", "uuid", "inet", "cidr", "macaddr", "macaddr8", "interval"]) {
204
204
  expect(mapPgType(t)).toBe("string");
205
205
  }
206
206
  });
207
+ it("maps bytea to binary", () => {
208
+ expect(mapPgType("bytea")).toBe("binary");
209
+ });
207
210
  it("defaults unknown types to string", () => {
208
211
  expect(mapPgType("tsvector")).toBe("string");
209
212
  expect(mapPgType("xml")).toBe("string");