@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,6 +1,6 @@
1
1
  import type { ComponentRef } from "./component_ref";
2
- import type { EntityReference, EntityRelation, EntityValues, GeoPoint, Entity, Vector } from "./entities";
3
- import type { Relation, JoinStep, OnAction } from "./relations";
2
+ import type { Entity, EntityReference, EntityRelation, EntityValues, GeoPoint, Vector } from "./entities";
3
+ import type { JoinStep, OnAction, Relation } from "./relations";
4
4
  import type { EntityCollection, FilterValues } from "./collections";
5
5
  import type { ColorKey, ColorScheme } from "./chips";
6
6
  import type { AuthController } from "../controllers/auth";
@@ -104,8 +104,8 @@ export interface BaseUIConfig<CustomProps = unknown> {
104
104
  disabled?: boolean | PropertyDisabledConfig;
105
105
  widthPercentage?: number;
106
106
  customProps?: CustomProps;
107
- Field?: ComponentRef<any>;
108
- Preview?: ComponentRef<any>;
107
+ Field?: ComponentRef;
108
+ Preview?: ComponentRef;
109
109
  }
110
110
  export interface BaseProperty<CustomProps = unknown> {
111
111
  ui?: BaseUIConfig<CustomProps>;
@@ -185,7 +185,7 @@ export interface StringProperty extends BaseProperty {
185
185
  * Optional database column type. If not set, it defaults to `varchar` or `uuid` depending on `isId` configuration.
186
186
  * Use `text` for strings with unbound length, `char` for fixed-length strings, or `varchar` for variable-length strings with a limit.
187
187
  */
188
- columnType?: "varchar" | "text" | "char";
188
+ columnType?: "varchar" | "text" | "char" | "uuid";
189
189
  /**
190
190
  * Rules for validating this property
191
191
  */
@@ -541,9 +541,11 @@ export interface ArrayProperty extends BaseProperty {
541
541
  ui?: ArrayUIConfig;
542
542
  type: "array";
543
543
  /**
544
- * Optional database column type. Defaults to `jsonb`.
544
+ * Optional database column type. By default, maps to a native Postgres array
545
+ * (e.g. `text[]`, `integer[]`/`numeric[]`, `boolean[]`) if the element type
546
+ * is a primitive, otherwise defaults to `jsonb`.
545
547
  */
546
- columnType?: "json" | "jsonb";
548
+ columnType?: "json" | "jsonb" | "text[]" | "integer[]" | "boolean[]" | "numeric[]";
547
549
  /**
548
550
  * The property of this array.
549
551
  * You can specify any property (except another Array property)
@@ -105,6 +105,12 @@ export interface RebaseTranslations {
105
105
  navigation_drawer: string;
106
106
  collapse: string;
107
107
  expand: string;
108
+ /** Tooltip for the language switcher in the drawer footer */
109
+ change_language?: string;
110
+ /** Tooltip for the theme toggle in the drawer footer */
111
+ toggle_theme?: string;
112
+ /** Aria label for the user menu trigger in the drawer footer */
113
+ user_menu?: string;
108
114
  error: string;
109
115
  error_uploading_file: string;
110
116
  error_deleting: string;
@@ -426,18 +432,28 @@ export interface RebaseTranslations {
426
432
  deleted: string;
427
433
  select_reference: string;
428
434
  select_references: string;
429
- account_settings: string;
430
- profile: string;
431
- sessions: string;
432
- display_name: string;
433
- photo_url: string;
434
- save_profile: string;
435
- saving: string;
436
- no_active_sessions: string;
437
- revoking: string;
438
- revoke_all_sessions: string;
439
- unknown_device: string;
440
- current: string;
435
+ account_settings?: string;
436
+ profile?: string;
437
+ sessions?: string;
438
+ security?: string;
439
+ change_password?: string;
440
+ current_password?: string;
441
+ new_password?: string;
442
+ confirm_password?: string;
443
+ password_changed?: string;
444
+ passwords_dont_match?: string;
445
+ password_too_short?: string;
446
+ password_change_not_available?: string;
447
+ changing_password?: string;
448
+ display_name?: string;
449
+ photo_url?: string;
450
+ save_profile?: string;
451
+ saving?: string;
452
+ no_active_sessions?: string;
453
+ revoking?: string;
454
+ revoke_all_sessions?: string;
455
+ unknown_device?: string;
456
+ current?: string;
441
457
  role_id: string;
442
458
  role_name: string;
443
459
  add_reference: string;
@@ -1,4 +1,4 @@
1
- import { Role, User } from "../users";
1
+ import type { User } from "../users";
2
2
  /**
3
3
  * Result of creating a new user via admin flow.
4
4
  * Contains the created user plus information about how credentials were delivered.
@@ -15,56 +15,46 @@ export interface UserCreationResult<USER extends User = User> {
15
15
  temporaryPassword?: string;
16
16
  }
17
17
  /**
18
- * Delegate to manage users, roles, and their permissions.
19
- * This interface allows the CMS to be completely agnostic of the underlying
20
- * authentication provider or backend.
18
+ * Delegate to manage auth-specific user operations.
19
+ *
20
+ * This interface allows the CMS to be agnostic of the underlying
21
+ * authentication provider or backend. User/role CRUD is now handled
22
+ * by the collection system; this delegate only exposes auth-specific
23
+ * operations (password hashing, invitations, bootstrap).
21
24
  *
22
25
  * @group Models
23
26
  */
24
27
  export interface UserManagementDelegate<USER extends User = User> {
25
28
  /**
26
- * Are the users and roles currently being fetched?
29
+ * Are auth-related operations currently loading?
27
30
  */
28
31
  loading: boolean;
29
32
  /**
30
- * List of users managed by the CMS.
33
+ * In-memory list of users (used for client-side filtering fallback).
31
34
  */
32
- users: USER[];
35
+ users?: USER[];
33
36
  /**
34
- * Optional error if users failed to load.
37
+ * Error from fetching the users list, if any.
35
38
  */
36
39
  usersError?: Error;
37
40
  /**
38
- * Function to get a user by its uid. This is used to show
39
- * user information when assigning ownership of an entity.
40
- * @param uid
41
+ * Look up a single user by UID from the in-memory cache.
41
42
  */
42
- getUser: (uid: string) => USER | null;
43
+ getUser?: (uid: string) => USER | null;
43
44
  /**
44
- * Search users with server-side pagination.
45
- * When provided, the CMS will use this for the users table
46
- * instead of loading all users into memory.
45
+ * Server-side user search with pagination.
47
46
  */
48
- searchUsers?: (options: {
47
+ searchUsers?: (params: {
49
48
  search?: string;
50
49
  limit?: number;
51
50
  offset?: number;
52
- orderBy?: string;
53
- orderDir?: "asc" | "desc";
54
- roleId?: string;
55
51
  }) => Promise<{
56
52
  users: USER[];
57
53
  total: number;
58
54
  }>;
59
- /**
60
- * Save a user (create or update)
61
- * @param user
62
- */
63
- saveUser?: (user: USER) => Promise<USER>;
64
55
  /**
65
56
  * Create a new user with invitation/password generation support.
66
57
  * Returns additional info about how the credentials were delivered.
67
- * Falls back to saveUser if not provided.
68
58
  */
69
59
  createUser?: (user: USER) => Promise<UserCreationResult<USER>>;
70
60
  /**
@@ -73,46 +63,21 @@ export interface UserManagementDelegate<USER extends User = User> {
73
63
  * or a flag indicating an email invitation was sent.
74
64
  */
75
65
  resetPassword?: (user: USER) => Promise<UserCreationResult<USER>>;
76
- /**
77
- * Delete a user
78
- * @param user
79
- */
80
- deleteUser?: (user: USER) => Promise<void>;
81
- /**
82
- * List of roles defined in the CMS.
83
- */
84
- roles?: Role[];
85
- /**
86
- * Optional error if roles failed to load.
87
- */
88
- rolesError?: Error;
89
- /**
90
- * Save a role (create or update)
91
- * @param role
92
- */
93
- saveRole?: (role: Role) => Promise<void>;
94
- /**
95
- * Delete a role
96
- * @param role
97
- */
98
- deleteRole?: (role: Role) => Promise<void>;
99
66
  /**
100
67
  * Is the currently logged in user an admin?
101
68
  */
102
69
  isAdmin?: boolean;
103
- /**
104
- * If true, the UI will allow the user to create the default roles (admin, editor, viewer).
105
- */
106
- allowDefaultRolesCreation?: boolean;
107
- /**
108
- * Should collection config permissions be included?
109
- */
110
- includeCollectionConfigPermissions?: boolean;
111
70
  /**
112
71
  * Optionally define roles for a given user. This is useful when the roles
113
72
  * are coming from a separate provider than the one issuing the tokens.
114
73
  */
115
- defineRolesFor?: (user: USER) => Promise<Role[] | undefined> | Role[] | undefined;
74
+ defineRolesFor?: (user: USER) => Promise<string[] | undefined> | string[] | undefined;
75
+ /**
76
+ * Whether any admin users exist. Used by the bootstrap banner to decide
77
+ * whether to prompt. Populated via a lightweight check (e.g. `limit=1`
78
+ * query) instead of loading all users.
79
+ */
80
+ hasAdminUsers?: boolean;
116
81
  /**
117
82
  * Optional function to bootstrap an admin user.
118
83
  * Often used when the database is empty.
@@ -1,2 +1 @@
1
1
  export * from "./user";
2
- export * from "./roles";
@@ -35,7 +35,6 @@ export type User = {
35
35
  readonly isAnonymous: boolean;
36
36
  /**
37
37
  * Role IDs assigned to this user (e.g. ["admin", "editor"]).
38
- * These are plain string IDs — use the UserManagementDelegate to look up full Role objects.
39
38
  */
40
39
  roles?: string[];
41
40
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/server-postgresql",
3
3
  "type": "module",
4
- "version": "0.2.3",
4
+ "version": "0.2.5",
5
5
  "description": "PostgreSQL data source backend implementation for Rebase with Drizzle ORM",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -68,11 +68,11 @@
68
68
  "hono": "^4.12.21",
69
69
  "pg": "^8.21.0",
70
70
  "ws": "^8.20.1",
71
- "@rebasepro/common": "0.2.3",
72
- "@rebasepro/server-core": "0.2.3",
73
- "@rebasepro/types": "0.2.3",
74
- "@rebasepro/utils": "0.2.3",
75
- "@rebasepro/sdk-generator": "0.2.3"
71
+ "@rebasepro/common": "0.2.5",
72
+ "@rebasepro/utils": "0.2.5",
73
+ "@rebasepro/server-core": "0.2.5",
74
+ "@rebasepro/sdk-generator": "0.2.5",
75
+ "@rebasepro/types": "0.2.5"
76
76
  },
77
77
  "devDependencies": {
78
78
  "@types/jest": "^29.5.14",
@@ -145,7 +145,8 @@ propertyCallbacks: undefined };
145
145
  startAfter,
146
146
  orderBy,
147
147
  searchString,
148
- order
148
+ order,
149
+ vectorSearch
149
150
  }: FetchCollectionProps<M>): Promise<Entity<M>[]> {
150
151
 
151
152
  const entities = await this.entityService.fetchCollection<M>(path, {
@@ -156,7 +157,8 @@ propertyCallbacks: undefined };
156
157
  offset,
157
158
  startAfter: startAfter as Record<string, unknown> | undefined,
158
159
  databaseId: collection?.databaseId,
159
- searchString
160
+ searchString,
161
+ vectorSearch
160
162
  });
161
163
 
162
164
  const { collection: resolvedCollection, callbacks, propertyCallbacks } = this.resolveCollectionCallbacks(collection, path);
@@ -629,6 +631,12 @@ propertyCallbacks: undefined };
629
631
 
630
632
  }
631
633
 
634
+ async deleteAll(path: string): Promise<void> {
635
+ await this.entityService.deleteAll(path);
636
+ // Notify real-time subscribers of bulk change
637
+ await this.realtimeService.notifyEntityUpdate(path, "*", null);
638
+ }
639
+
632
640
  async checkUniqueField(
633
641
  path: string,
634
642
  name: string,
@@ -1070,6 +1078,10 @@ roles: this.user?.roles ?? [] };
1070
1078
  return this.withTransaction((delegate) => delegate.deleteEntity(props));
1071
1079
  }
1072
1080
 
1081
+ async deleteAll(path: string): Promise<void> {
1082
+ return this.delegate.deleteAll(path);
1083
+ }
1084
+
1073
1085
  async checkUniqueField(
1074
1086
  path: string,
1075
1087
  name: string,
@@ -25,11 +25,12 @@ import {
25
25
  createAuthRoutes,
26
26
  createAdminRoutes,
27
27
  requireAuth,
28
- requireAdmin
28
+ requireAdmin,
29
+ logger
29
30
  // @ts-ignore
30
31
  } from "@rebasepro/server-core";
31
32
  import { ensureAuthTablesExist } from "./auth/ensure-tables";
32
- import { RoleService, UserService, PostgresAuthRepository, AuthSchemaTables } from "./auth/services";
33
+ import { UserService, PostgresAuthRepository, AuthSchemaTables } from "./auth/services";
33
34
  import { createAuthSchema } from "./schema/auth-schema";
34
35
 
35
36
  // @ts-ignore
@@ -50,6 +51,7 @@ import type { HonoEnv } from "@rebasepro/server-core";
50
51
  */
51
52
  export interface PostgresDriverInternals {
52
53
  db: NodePgDatabase<any>;
54
+ readDb?: NodePgDatabase<any>;
53
55
  registry: PostgresCollectionRegistry;
54
56
  realtimeService: RealtimeService;
55
57
  driver: PostgresBackendDriver;
@@ -83,7 +85,7 @@ export function createPostgresBootstrapper(pgConfig: PostgresDriverConfig): Back
83
85
  const registry = new PostgresCollectionRegistry();
84
86
  if (collections) {
85
87
  registry.registerMultiple(collections);
86
- console.log(`📋 [PostgresRegistry] Registered ${registry.getCollections().length} collections: [${registry.getCollections().map(c => c.slug).join(", ")}]`);
88
+ logger.info(`📋 [PostgresRegistry] Registered ${registry.getCollections().length} collections: [${registry.getCollections().map(c => c.slug).join(", ")}]`);
87
89
  }
88
90
 
89
91
  // Register tables
@@ -114,12 +116,26 @@ export function createPostgresBootstrapper(pgConfig: PostgresDriverConfig): Back
114
116
  try {
115
117
  await schemaAwareDb.execute(sql`SELECT 1`);
116
118
  } catch (err) {
117
- console.error("❌ Failed to connect to PostgreSQL:", err);
118
- console.warn("⚠️ Continuing without initial database verification. Drizzle/PG will attempt to connect on subsequent queries.");
119
+ logger.error("❌ Failed to connect to PostgreSQL", { error: err });
120
+ logger.warn("⚠️ Continuing without initial database verification. Drizzle/PG will attempt to connect on subsequent queries.");
119
121
  }
120
122
 
121
123
  // Create services
122
124
  const realtimeService = new RealtimeService(schemaAwareDb, registry);
125
+
126
+ // Initialize read replica connection if configured
127
+ let readDb: import("drizzle-orm/node-postgres").NodePgDatabase<any> | undefined;
128
+ const readUrl = process.env.DATABASE_READ_URL;
129
+ if (readUrl && readUrl !== pgConfig.connectionString) {
130
+ try {
131
+ const { createReadReplicaConnection } = await import("./connection");
132
+ const readResources = createReadReplicaConnection(readUrl, mergedSchema);
133
+ readDb = readResources.db;
134
+ logger.info("📖 [PostgresBootstrapper] Read replica connection established");
135
+ } catch (err) {
136
+ logger.warn("⚠️ Could not connect to read replica, falling back to primary for all queries", { error: err });
137
+ }
138
+ }
123
139
  const poolManager = pgConfig.adminConnectionString
124
140
  ? new DatabasePoolManager(pgConfig.adminConnectionString)
125
141
  : undefined;
@@ -131,21 +147,24 @@ export function createPostgresBootstrapper(pgConfig: PostgresDriverConfig): Back
131
147
  try {
132
148
  await driver.branchService.ensureBranchMetadataTable();
133
149
  } catch (err) {
134
- console.warn("⚠️ Could not initialize branch metadata table:", err);
150
+ logger.warn("⚠️ Could not initialize branch metadata table", { error: err });
135
151
  }
136
152
  }
137
153
 
138
154
  // Enable cross-instance realtime (opt-in)
139
- if (pgConfig.connectionString) {
155
+ // Prefer DATABASE_DIRECT_URL to bypass PgBouncer for LISTEN/NOTIFY
156
+ const directUrl = process.env.DATABASE_DIRECT_URL || pgConfig.connectionString;
157
+ if (directUrl) {
140
158
  try {
141
- await realtimeService.startListening(pgConfig.connectionString);
159
+ await realtimeService.startListening(directUrl);
142
160
  } catch (err) {
143
- console.warn("⚠️ Cross-instance realtime could not be started:", err);
161
+ logger.warn("⚠️ Cross-instance realtime could not be started", { error: err });
144
162
  }
145
163
  }
146
164
 
147
165
  const internals: PostgresDriverInternals = {
148
166
  db: schemaAwareDb,
167
+ readDb,
149
168
  registry,
150
169
  realtimeService,
151
170
  driver,
@@ -176,32 +195,23 @@ export function createPostgresBootstrapper(pgConfig: PostgresDriverConfig): Back
176
195
  }
177
196
 
178
197
  const customUsersTable = registry?.getTable("users");
179
- const customRolesTable = registry?.getTable("roles");
180
198
 
181
199
  let usersSchemaName = "rebase";
182
- let rolesSchemaName = "rebase";
183
200
 
184
201
  if (customUsersTable) {
185
202
  usersSchemaName = getTableConfig(customUsersTable).schema || "public";
186
203
  }
187
- if (customRolesTable) {
188
- rolesSchemaName = getTableConfig(customRolesTable).schema || "public";
189
- }
190
204
 
191
- const authTables = createAuthSchema(rolesSchemaName, usersSchemaName) as unknown as AuthSchemaTables;
205
+ const authTables = createAuthSchema(usersSchemaName) as unknown as AuthSchemaTables;
192
206
  if (customUsersTable) {
193
207
  authTables.users = customUsersTable as unknown as PgTable & Record<string, AnyPgColumn>;
194
208
  }
195
- if (customRolesTable) {
196
- authTables.roles = customRolesTable as unknown as PgTable & Record<string, AnyPgColumn>;
197
- }
198
209
 
199
210
  const userService = new UserService(db, authTables);
200
- const roleService = new RoleService(db, authTables);
201
211
  const authRepository = new PostgresAuthRepository(db, authTables);
202
212
 
203
213
  return { userService,
204
- roleService,
214
+ roleService: userService,
205
215
  emailService,
206
216
  authRepository };
207
217
  },