@rebasepro/server-postgresql 0.2.3 → 0.2.5

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 (63) hide show
  1. package/dist/common/src/collections/default-collections.d.ts +9 -0
  2. package/dist/common/src/collections/index.d.ts +1 -0
  3. package/dist/common/src/util/permissions.d.ts +1 -0
  4. package/dist/index.es.js +1075 -470
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/index.umd.js +1071 -466
  7. package/dist/index.umd.js.map +1 -1
  8. package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +3 -1
  9. package/dist/server-postgresql/src/PostgresBootstrapper.d.ts +1 -0
  10. package/dist/server-postgresql/src/auth/services.d.ts +48 -31
  11. package/dist/server-postgresql/src/connection.d.ts +25 -0
  12. package/dist/server-postgresql/src/schema/auth-schema.d.ts +2135 -41
  13. package/dist/server-postgresql/src/services/EntityFetchService.d.ts +4 -0
  14. package/dist/server-postgresql/src/services/EntityPersistService.d.ts +4 -0
  15. package/dist/server-postgresql/src/services/entityService.d.ts +6 -0
  16. package/dist/server-postgresql/src/services/realtimeService.d.ts +20 -0
  17. package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +18 -0
  18. package/dist/types/src/controllers/auth.d.ts +4 -26
  19. package/dist/types/src/controllers/client.d.ts +25 -43
  20. package/dist/types/src/controllers/collection_registry.d.ts +1 -1
  21. package/dist/types/src/controllers/data.d.ts +4 -0
  22. package/dist/types/src/controllers/data_driver.d.ts +23 -0
  23. package/dist/types/src/controllers/registry.d.ts +5 -4
  24. package/dist/types/src/rebase_context.d.ts +1 -1
  25. package/dist/types/src/types/auth_adapter.d.ts +5 -60
  26. package/dist/types/src/types/backend.d.ts +2 -2
  27. package/dist/types/src/types/backend_hooks.d.ts +2 -17
  28. package/dist/types/src/types/collections.d.ts +0 -4
  29. package/dist/types/src/types/component_ref.d.ts +1 -1
  30. package/dist/types/src/types/cron.d.ts +1 -1
  31. package/dist/types/src/types/entity_views.d.ts +1 -0
  32. package/dist/types/src/types/export_import.d.ts +1 -1
  33. package/dist/types/src/types/formex.d.ts +2 -2
  34. package/dist/types/src/types/properties.d.ts +9 -7
  35. package/dist/types/src/types/translations.d.ts +28 -12
  36. package/dist/types/src/types/user_management_delegate.d.ts +22 -57
  37. package/dist/types/src/users/index.d.ts +0 -1
  38. package/dist/types/src/users/user.d.ts +0 -1
  39. package/package.json +6 -6
  40. package/src/PostgresBackendDriver.ts +14 -2
  41. package/src/PostgresBootstrapper.ts +30 -20
  42. package/src/auth/ensure-tables.ts +116 -103
  43. package/src/auth/services.ts +347 -177
  44. package/src/connection.ts +77 -0
  45. package/src/data-transformer.ts +2 -2
  46. package/src/schema/auth-schema.ts +85 -75
  47. package/src/schema/doctor.ts +44 -3
  48. package/src/schema/generate-drizzle-schema-logic.ts +33 -3
  49. package/src/schema/generate-drizzle-schema.ts +6 -6
  50. package/src/schema/introspect-db-logic.ts +7 -0
  51. package/src/services/EntityFetchService.ts +69 -10
  52. package/src/services/EntityPersistService.ts +9 -0
  53. package/src/services/entityService.ts +9 -0
  54. package/src/services/realtimeService.ts +214 -2
  55. package/src/utils/drizzle-conditions.ts +74 -2
  56. package/src/websocket.ts +10 -2
  57. package/test/auth-services.test.ts +10 -166
  58. package/test/doctor.test.ts +6 -2
  59. package/test/drizzle-conditions.test.ts +168 -0
  60. package/vite.config.ts +1 -1
  61. package/dist/server-postgresql/src/schema/default-collections.d.ts +0 -2
  62. package/dist/types/src/users/roles.d.ts +0 -22
  63. package/src/schema/default-collections.ts +0 -69
@@ -1,5 +1,5 @@
1
1
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
2
- import { UserService, RoleService, RefreshTokenService, PasswordResetTokenService, Role } from "../src/auth/services";
2
+ import { UserService, RefreshTokenService, PasswordResetTokenService, Role } from "../src/auth/services";
3
3
  import { users, refreshTokens, passwordResetTokens } from "../src/schema/auth-schema";
4
4
  import { UserData } from "@rebasepro/server-core";
5
5
 
@@ -37,6 +37,7 @@ function mockUserData(overrides: Partial<UserData>): UserData {
37
37
  createdAt: expect.any(Date),
38
38
  updatedAt: expect.any(Date),
39
39
  metadata: {},
40
+ isAnonymous: false,
40
41
  ...overrides
41
42
  };
42
43
  }
@@ -343,20 +344,7 @@ describe("Auth Services", () => {
343
344
  describe("getUserRoles", () => {
344
345
  it("should return roles for user", async () => {
345
346
  mockExecute.mockResolvedValueOnce({
346
- rows: [
347
- { id: "admin",
348
- name: "Admin",
349
- is_admin: true,
350
- default_permissions: null,
351
- collection_permissions: null,
352
- config: null },
353
- { id: "editor",
354
- name: "Editor",
355
- is_admin: false,
356
- default_permissions: { edit: true },
357
- collection_permissions: null,
358
- config: null }
359
- ]
347
+ rows: [{ roles: ["admin", "editor"] }]
360
348
  });
361
349
 
362
350
  const roles = await userService.getUserRoles("user-123");
@@ -364,11 +352,10 @@ describe("Auth Services", () => {
364
352
  expect(roles).toHaveLength(2);
365
353
  expect(roles[0]).toEqual({
366
354
  id: "admin",
367
- name: "Admin",
355
+ name: "admin",
368
356
  isAdmin: true,
369
357
  defaultPermissions: null,
370
- collectionPermissions: null,
371
- config: null
358
+ collectionPermissions: null
372
359
  });
373
360
  });
374
361
  });
@@ -376,14 +363,7 @@ describe("Auth Services", () => {
376
363
  describe("getUserRoleIds", () => {
377
364
  it("should return role IDs for user", async () => {
378
365
  mockExecute.mockResolvedValueOnce({
379
- rows: [
380
- { id: "admin",
381
- name: "Admin",
382
- is_admin: true,
383
- default_permissions: null,
384
- collection_permissions: null,
385
- config: null }
386
- ]
366
+ rows: [{ roles: ["admin"] }]
387
367
  });
388
368
 
389
369
  const roleIds = await userService.getUserRoleIds("user-123");
@@ -396,10 +376,7 @@ describe("Auth Services", () => {
396
376
  it("should delete existing and insert new roles", async () => {
397
377
  await userService.setUserRoles("user-123", ["admin", "editor"]);
398
378
 
399
- // First call deletes existing roles
400
379
  expect(mockExecute).toHaveBeenCalled();
401
- // Subsequent calls insert new roles
402
- expect(mockExecute.mock.calls.length).toBeGreaterThanOrEqual(1);
403
380
  });
404
381
  });
405
382
 
@@ -411,7 +388,7 @@ describe("Auth Services", () => {
411
388
  });
412
389
 
413
390
  it("should use editor as default role", async () => {
414
- await userService.assignDefaultRole("user-123");
391
+ await userService.assignDefaultRole("user-123", "editor");
415
392
 
416
393
  expect(mockExecute).toHaveBeenCalled();
417
394
  });
@@ -422,12 +399,7 @@ describe("Auth Services", () => {
422
399
  const mockUser = { id: "user-123", email: "test@example.com" };
423
400
  mockSelectWhere.mockResolvedValueOnce([mockUser]);
424
401
  mockExecute.mockResolvedValueOnce({
425
- rows: [{ id: "admin",
426
- name: "Admin",
427
- is_admin: true,
428
- default_permissions: null,
429
- collection_permissions: null,
430
- config: null }]
402
+ rows: [{ roles: ["admin"] }]
431
403
  });
432
404
 
433
405
  const result = await userService.getUserWithRoles("user-123");
@@ -435,11 +407,10 @@ describe("Auth Services", () => {
435
407
  expect(result).toEqual({
436
408
  user: mockUserData({}),
437
409
  roles: [{ id: "admin",
438
- name: "Admin",
410
+ name: "admin",
439
411
  isAdmin: true,
440
412
  defaultPermissions: null,
441
- collectionPermissions: null,
442
- config: null }]
413
+ collectionPermissions: null }]
443
414
  });
444
415
  });
445
416
 
@@ -477,133 +448,6 @@ describe("Auth Services", () => {
477
448
  });
478
449
  });
479
450
 
480
- describe("RoleService", () => {
481
- let roleService: RoleService;
482
-
483
- beforeEach(() => {
484
- roleService = new RoleService(db);
485
- });
486
-
487
- describe("getRoleById", () => {
488
- it("should return role when found", async () => {
489
- mockExecute.mockResolvedValueOnce({
490
- rows: [{ id: "admin",
491
- name: "Admin",
492
- is_admin: true,
493
- default_permissions: null,
494
- collection_permissions: null,
495
- config: null }]
496
- });
497
-
498
- const result = await roleService.getRoleById("admin");
499
-
500
- expect(result).toEqual({
501
- id: "admin",
502
- name: "Admin",
503
- isAdmin: true,
504
- defaultPermissions: null,
505
- collectionPermissions: null,
506
- config: null
507
- });
508
- });
509
-
510
- it("should return null when role not found", async () => {
511
- mockExecute.mockResolvedValueOnce({ rows: [] });
512
-
513
- const result = await roleService.getRoleById("nonexistent");
514
-
515
- expect(result).toBeNull();
516
- });
517
- });
518
-
519
- describe("listRoles", () => {
520
- it("should return all roles", async () => {
521
- mockExecute.mockResolvedValueOnce({
522
- rows: [
523
- { id: "admin",
524
- name: "Admin",
525
- is_admin: true,
526
- default_permissions: null,
527
- collection_permissions: null,
528
- config: null },
529
- { id: "editor",
530
- name: "Editor",
531
- is_admin: false,
532
- default_permissions: null,
533
- collection_permissions: null,
534
- config: null }
535
- ]
536
- });
537
-
538
- const roles = await roleService.listRoles();
539
-
540
- expect(roles).toHaveLength(2);
541
- });
542
- });
543
-
544
- describe("createRole", () => {
545
- it("should create a role", async () => {
546
- mockExecute.mockResolvedValueOnce({
547
- rows: [{ id: "custom",
548
- name: "Custom Role",
549
- is_admin: false,
550
- default_permissions: null,
551
- collection_permissions: null,
552
- config: null }]
553
- });
554
-
555
- const role = await roleService.createRole({
556
- id: "custom",
557
- name: "Custom Role",
558
- defaultPermissions: null,
559
- config: null
560
- });
561
-
562
- expect(role.id).toBe("custom");
563
- expect(role.name).toBe("Custom Role");
564
- });
565
- });
566
-
567
- describe("updateRole", () => {
568
- it("should update a role", async () => {
569
- mockExecute
570
- .mockResolvedValueOnce({ rows: [{ id: "admin",
571
- name: "Admin",
572
- is_admin: true,
573
- default_permissions: null,
574
- collection_permissions: null,
575
- config: null }] })
576
- .mockResolvedValueOnce({ rows: [] })
577
- .mockResolvedValueOnce({ rows: [{ id: "admin",
578
- name: "Super Admin",
579
- is_admin: true,
580
- default_permissions: null,
581
- collection_permissions: null,
582
- config: null }] });
583
-
584
- const result = await roleService.updateRole("admin", { name: "Super Admin" });
585
-
586
- expect(result?.name).toBe("Super Admin");
587
- });
588
-
589
- it("should return null when role not found", async () => {
590
- mockExecute.mockResolvedValueOnce({ rows: [] });
591
-
592
- const result = await roleService.updateRole("nonexistent", { name: "Test" });
593
-
594
- expect(result).toBeNull();
595
- });
596
- });
597
-
598
- describe("deleteRole", () => {
599
- it("should delete a role", async () => {
600
- await roleService.deleteRole("custom");
601
-
602
- expect(mockExecute).toHaveBeenCalled();
603
- });
604
- });
605
- });
606
-
607
451
  describe("RefreshTokenService", () => {
608
452
  let refreshTokenService: RefreshTokenService;
609
453
 
@@ -135,8 +135,12 @@ columnType: "time" } as DateProperty)).toBe("time without time zone");
135
135
  it("should map json types correctly", () => {
136
136
  expect(getExpectedColumnType({ type: "map" })).toBe("jsonb");
137
137
  expect(getExpectedColumnType({ type: "array" })).toBe("jsonb");
138
- expect(getExpectedColumnType({ type: "array",
139
- columnType: "json" } as ArrayProperty)).toBe("json");
138
+ expect(getExpectedColumnType({ type: "array", columnType: "json" } as ArrayProperty)).toBe("json");
139
+
140
+ // Native array element type mappings
141
+ expect(getExpectedColumnType({ type: "array", of: { type: "string" } } as ArrayProperty)).toBe("ARRAY");
142
+ expect(getExpectedColumnType({ type: "array", of: { type: "number", validation: { integer: true } } } as ArrayProperty)).toBe("ARRAY");
143
+ expect(getExpectedColumnType({ type: "array", of: { type: "boolean" } } as ArrayProperty)).toBe("ARRAY");
140
144
  });
141
145
 
142
146
  it("should map enum string to USER-DEFINED", () => {
@@ -893,3 +893,171 @@ describe("DrizzleConditionBuilder - Many-to-Many Relations", () => {
893
893
  });
894
894
  });
895
895
  });
896
+
897
+ describe("DrizzleConditionBuilder - Filter Operators", () => {
898
+ // Mock table for filter tests
899
+ const mockUsersTable = pgTable("users", {
900
+ id: serial("id").primaryKey(),
901
+ name: varchar("name").notNull(),
902
+ email: varchar("email").notNull(),
903
+ age: integer("age")
904
+ });
905
+
906
+ describe("buildSingleFilterCondition - array-contains", () => {
907
+ it("should generate a non-null condition for a single value", () => {
908
+ const condition = DrizzleConditionBuilder.buildSingleFilterCondition(
909
+ mockUsersTable.name,
910
+ "array-contains",
911
+ "featured"
912
+ );
913
+ expect(condition).not.toBeNull();
914
+ });
915
+ });
916
+
917
+ describe("buildSingleFilterCondition - array-contains-any", () => {
918
+ it("should generate a non-null condition for an array of values", () => {
919
+ const condition = DrizzleConditionBuilder.buildSingleFilterCondition(
920
+ mockUsersTable.name,
921
+ "array-contains-any",
922
+ ["featured", "popular", "trending"]
923
+ );
924
+ expect(condition).not.toBeNull();
925
+ });
926
+
927
+ it("should fallback to array-contains for a single (non-array) value", () => {
928
+ const condition = DrizzleConditionBuilder.buildSingleFilterCondition(
929
+ mockUsersTable.name,
930
+ "array-contains-any",
931
+ "featured"
932
+ );
933
+ expect(condition).not.toBeNull();
934
+ });
935
+ });
936
+
937
+ describe("buildSingleFilterCondition - not-in", () => {
938
+ it("should generate a non-null condition for an array of values", () => {
939
+ const condition = DrizzleConditionBuilder.buildSingleFilterCondition(
940
+ mockUsersTable.age,
941
+ "not-in",
942
+ [1, 2, 3]
943
+ );
944
+ expect(condition).not.toBeNull();
945
+ });
946
+
947
+ it("should return null for empty array", () => {
948
+ const condition = DrizzleConditionBuilder.buildSingleFilterCondition(
949
+ mockUsersTable.age,
950
+ "not-in",
951
+ []
952
+ );
953
+ expect(condition).toBeNull();
954
+ });
955
+
956
+ it("should return null for non-array value", () => {
957
+ const condition = DrizzleConditionBuilder.buildSingleFilterCondition(
958
+ mockUsersTable.age,
959
+ "not-in",
960
+ 42
961
+ );
962
+ expect(condition).toBeNull();
963
+ });
964
+ });
965
+
966
+ describe("buildSingleFilterCondition - existing operators", () => {
967
+ it("should generate a condition for equality", () => {
968
+ const condition = DrizzleConditionBuilder.buildSingleFilterCondition(
969
+ mockUsersTable.name,
970
+ "==",
971
+ "alice"
972
+ );
973
+ expect(condition).not.toBeNull();
974
+ });
975
+
976
+ it("should generate IS NULL for equality with null", () => {
977
+ const condition = DrizzleConditionBuilder.buildSingleFilterCondition(
978
+ mockUsersTable.name,
979
+ "==",
980
+ null
981
+ );
982
+ expect(condition).not.toBeNull();
983
+ });
984
+
985
+ it("should generate IS NOT NULL for inequality with null", () => {
986
+ const condition = DrizzleConditionBuilder.buildSingleFilterCondition(
987
+ mockUsersTable.name,
988
+ "!=",
989
+ null
990
+ );
991
+ expect(condition).not.toBeNull();
992
+ });
993
+
994
+ it("should handle in operator with array", () => {
995
+ const condition = DrizzleConditionBuilder.buildSingleFilterCondition(
996
+ mockUsersTable.age,
997
+ "in",
998
+ [18, 21, 25]
999
+ );
1000
+ expect(condition).not.toBeNull();
1001
+ });
1002
+
1003
+ it("should return null for in operator with empty array", () => {
1004
+ const condition = DrizzleConditionBuilder.buildSingleFilterCondition(
1005
+ mockUsersTable.age,
1006
+ "in",
1007
+ []
1008
+ );
1009
+ expect(condition).toBeNull();
1010
+ });
1011
+
1012
+ it("should warn and return null for unsupported operators", () => {
1013
+ const warnSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
1014
+ const condition = DrizzleConditionBuilder.buildSingleFilterCondition(
1015
+ mockUsersTable.age,
1016
+ "unknown-op" as any,
1017
+ 42
1018
+ );
1019
+ expect(condition).toBeNull();
1020
+ expect(warnSpy).toHaveBeenCalledWith("Unsupported filter operation: unknown-op");
1021
+ warnSpy.mockRestore();
1022
+ });
1023
+ });
1024
+
1025
+ describe("buildFilterConditions - integration with array operators", () => {
1026
+ it("should build filter with array-contains operator", () => {
1027
+ const conditions = DrizzleConditionBuilder.buildFilterConditions(
1028
+ { name: ["array-contains", "featured"] },
1029
+ mockUsersTable,
1030
+ "users"
1031
+ );
1032
+ expect(conditions).toHaveLength(1);
1033
+ });
1034
+
1035
+ it("should build filter with array-contains-any operator", () => {
1036
+ const conditions = DrizzleConditionBuilder.buildFilterConditions(
1037
+ { name: ["array-contains-any", ["featured", "popular"]] },
1038
+ mockUsersTable,
1039
+ "users"
1040
+ );
1041
+ expect(conditions).toHaveLength(1);
1042
+ });
1043
+
1044
+ it("should build filter with not-in operator", () => {
1045
+ const conditions = DrizzleConditionBuilder.buildFilterConditions(
1046
+ { age: ["not-in", [1, 2, 3]] },
1047
+ mockUsersTable,
1048
+ "users"
1049
+ );
1050
+ expect(conditions).toHaveLength(1);
1051
+ });
1052
+
1053
+ it("should skip not-in filter with empty array", () => {
1054
+ const conditions = DrizzleConditionBuilder.buildFilterConditions(
1055
+ { age: ["not-in", []] },
1056
+ mockUsersTable,
1057
+ "users"
1058
+ );
1059
+ expect(conditions).toHaveLength(0);
1060
+ });
1061
+ });
1062
+ });
1063
+
package/vite.config.ts CHANGED
@@ -33,7 +33,7 @@ const isExternal = (id: string) => {
33
33
  // Externalize only deps the consumer app explicitly installs
34
34
  if (CONSUMER_EXTERNALS.some(ext => id === ext || id.startsWith(ext + "/"))) return true;
35
35
  // Externalize Node built-ins
36
- if (["fs", "path", "url", "util", "crypto", "http", "https", "net", "tls", "stream", "events", "os", "child_process", "buffer", "assert", "node:"].some(b => id === b || id.startsWith("node:") || id.startsWith(b + "/"))) return true;
36
+ if (["fs", "path", "url", "util", "crypto", "http", "https", "net", "tls", "stream", "events", "os", "child_process", "buffer", "assert", "dns", "zlib", "querystring", "node:"].some(b => id === b || id.startsWith("node:") || id.startsWith(b + "/"))) return true;
37
37
  // Inline everything else (jsonwebtoken, ws, zod, etc.)
38
38
  return false;
39
39
  };
@@ -1,2 +0,0 @@
1
- import { PostgresCollection } from "@rebasepro/types";
2
- export declare const defaultUsersCollection: PostgresCollection;
@@ -1,22 +0,0 @@
1
- export type Role = {
2
- /**
3
- * ID of the role
4
- */
5
- id: string;
6
- /**
7
- * Name of the role
8
- */
9
- name: string;
10
- /**
11
- * If this flag is true, the user can perform any action
12
- */
13
- isAdmin?: boolean;
14
- /**
15
- * Permissions related to editing the collections
16
- */
17
- config?: {
18
- createCollections?: boolean;
19
- editCollections?: boolean | "own";
20
- deleteCollections?: boolean | "own";
21
- };
22
- };
@@ -1,69 +0,0 @@
1
- import { PostgresCollection } from "@rebasepro/types";
2
-
3
- export const defaultUsersCollection: PostgresCollection = {
4
- name: "Users",
5
- singularName: "User",
6
- slug: "users",
7
- table: "users",
8
- schema: "rebase",
9
- icon: "Users",
10
- group: "Settings",
11
- properties: {
12
- id: {
13
- name: "ID",
14
- type: "string",
15
- isId: "uuid"
16
- },
17
- email: {
18
- name: "Email",
19
- type: "string",
20
- validation: { required: true, unique: true }
21
- },
22
- password_hash: {
23
- name: "Password Hash",
24
- type: "string",
25
- ui: { hideFromCollection: true }
26
- },
27
- display_name: {
28
- name: "Display Name",
29
- type: "string"
30
- },
31
- photo_url: {
32
- name: "Photo URL",
33
- type: "string"
34
- },
35
- email_verified: {
36
- name: "Email Verified",
37
- type: "boolean",
38
- defaultValue: false
39
- },
40
- email_verification_token: {
41
- name: "Email Verification Token",
42
- type: "string",
43
- ui: { hideFromCollection: true }
44
- },
45
- email_verification_sent_at: {
46
- name: "Email Verification Sent At",
47
- type: "date",
48
- ui: { hideFromCollection: true }
49
- },
50
- metadata: {
51
- name: "Metadata",
52
- type: "map",
53
- defaultValue: {},
54
- ui: { hideFromCollection: true }
55
- },
56
- created_at: {
57
- name: "Created At",
58
- type: "date",
59
- autoValue: "on_create",
60
- ui: { readOnly: true, hideFromCollection: true }
61
- },
62
- updated_at: {
63
- name: "Updated At",
64
- type: "date",
65
- autoValue: "on_update",
66
- ui: { readOnly: true, hideFromCollection: true }
67
- }
68
- }
69
- };