@rebasepro/server-postgresql 0.1.0 → 0.2.1

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 (75) hide show
  1. package/LICENSE +22 -6
  2. package/dist/common/src/util/entities.d.ts +2 -2
  3. package/dist/common/src/util/relations.d.ts +1 -1
  4. package/dist/index.es.js +1250 -1665
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/index.umd.js +1196 -1611
  7. package/dist/index.umd.js.map +1 -1
  8. package/dist/server-postgresql/src/PostgresAdapter.d.ts +6 -0
  9. package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +2 -1
  10. package/dist/server-postgresql/src/PostgresBootstrapper.d.ts +0 -5
  11. package/dist/server-postgresql/src/auth/ensure-tables.d.ts +2 -1
  12. package/dist/server-postgresql/src/auth/services.d.ts +37 -15
  13. package/dist/server-postgresql/src/index.d.ts +1 -0
  14. package/dist/server-postgresql/src/schema/auth-schema.d.ts +43 -856
  15. package/dist/server-postgresql/src/schema/default-collections.d.ts +2 -0
  16. package/dist/server-postgresql/src/schema/doctor.d.ts +10 -1
  17. package/dist/server-postgresql/src/schema/introspect-db-logic.d.ts +1 -0
  18. package/dist/server-postgresql/src/services/entity-helpers.d.ts +1 -1
  19. package/dist/server-postgresql/src/services/realtimeService.d.ts +12 -0
  20. package/dist/server-postgresql/src/websocket.d.ts +2 -1
  21. package/dist/types/src/controllers/auth.d.ts +9 -8
  22. package/dist/types/src/controllers/client.d.ts +3 -0
  23. package/dist/types/src/types/auth_adapter.d.ts +356 -0
  24. package/dist/types/src/types/collections.d.ts +67 -2
  25. package/dist/types/src/types/database_adapter.d.ts +94 -0
  26. package/dist/types/src/types/entity_actions.d.ts +7 -1
  27. package/dist/types/src/types/entity_callbacks.d.ts +1 -1
  28. package/dist/types/src/types/entity_views.d.ts +36 -1
  29. package/dist/types/src/types/index.d.ts +2 -0
  30. package/dist/types/src/types/plugins.d.ts +1 -1
  31. package/dist/types/src/types/properties.d.ts +24 -5
  32. package/dist/types/src/types/property_config.d.ts +6 -2
  33. package/dist/types/src/types/relations.d.ts +1 -1
  34. package/dist/types/src/types/translations.d.ts +8 -0
  35. package/dist/types/src/users/user.d.ts +5 -0
  36. package/package.json +21 -15
  37. package/src/PostgresAdapter.ts +59 -0
  38. package/src/PostgresBackendDriver.ts +57 -8
  39. package/src/PostgresBootstrapper.ts +35 -15
  40. package/src/auth/ensure-tables.ts +82 -189
  41. package/src/auth/services.ts +421 -170
  42. package/src/cli.ts +44 -13
  43. package/src/data-transformer.ts +78 -8
  44. package/src/history/HistoryService.ts +25 -2
  45. package/src/index.ts +1 -0
  46. package/src/schema/auth-schema.ts +130 -98
  47. package/src/schema/default-collections.ts +68 -0
  48. package/src/schema/doctor-cli.ts +5 -1
  49. package/src/schema/doctor.ts +85 -8
  50. package/src/schema/generate-drizzle-schema-logic.ts +74 -27
  51. package/src/schema/generate-drizzle-schema.ts +13 -3
  52. package/src/schema/introspect-db-inference.ts +5 -5
  53. package/src/schema/introspect-db-logic.ts +9 -2
  54. package/src/schema/introspect-db.ts +14 -3
  55. package/src/services/EntityFetchService.ts +5 -5
  56. package/src/services/RelationService.ts +2 -2
  57. package/src/services/entity-helpers.ts +1 -1
  58. package/src/services/realtimeService.ts +145 -136
  59. package/src/utils/drizzle-conditions.ts +16 -2
  60. package/src/websocket.ts +113 -37
  61. package/test/auth-services.test.ts +163 -74
  62. package/test/data-transformer-hardening.test.ts +57 -0
  63. package/test/data-transformer.test.ts +43 -0
  64. package/test/generate-drizzle-schema.test.ts +7 -5
  65. package/test/introspect-db-utils.test.ts +4 -1
  66. package/test/postgresDataDriver.test.ts +17 -0
  67. package/test/realtimeService.test.ts +7 -7
  68. package/test/websocket.test.ts +139 -0
  69. package/examples/sdk-demo/node_modules/esbuild/LICENSE.md +0 -21
  70. package/examples/sdk-demo/node_modules/esbuild/README.md +0 -3
  71. package/examples/sdk-demo/node_modules/esbuild/bin/esbuild +0 -223
  72. package/examples/sdk-demo/node_modules/esbuild/install.js +0 -289
  73. package/examples/sdk-demo/node_modules/esbuild/lib/main.d.ts +0 -716
  74. package/examples/sdk-demo/node_modules/esbuild/lib/main.js +0 -2242
  75. package/examples/sdk-demo/node_modules/esbuild/package.json +0 -49
@@ -1,6 +1,7 @@
1
- import { eq, sql } from "drizzle-orm";
1
+ import { eq, getTableName, sql } from "drizzle-orm";
2
2
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
3
- import { users, userIdentities, refreshTokens, passwordResetTokens, User, NewUser } from "../schema/auth-schema";
3
+ import { getTableConfig, PgTable, AnyPgColumn } from "drizzle-orm/pg-core";
4
+ import { users, roles, userRoles, refreshTokens, passwordResetTokens, userIdentities } from "../schema/auth-schema";
4
5
  import {
5
6
  UserRepository,
6
7
  RoleRepository,
@@ -18,61 +19,224 @@ import {
18
19
  RoleData as Role
19
20
  // @ts-ignore
20
21
  } from "@rebasepro/server-core";
22
+ import { toSnakeCase, camelCase } from "@rebasepro/utils";
21
23
 
22
24
  export type { Role };
23
25
 
26
+ export interface AuthSchemaTables {
27
+ users: PgTable & Record<string, AnyPgColumn>;
28
+ roles: PgTable & Record<string, AnyPgColumn>;
29
+ userRoles: PgTable & Record<string, AnyPgColumn>;
30
+ refreshTokens: PgTable & Record<string, AnyPgColumn>;
31
+ passwordResetTokens: PgTable & Record<string, AnyPgColumn>;
32
+ appConfig: PgTable & Record<string, AnyPgColumn>;
33
+ userIdentities: PgTable & Record<string, AnyPgColumn>;
34
+ }
35
+
36
+ function getColumnKey(table: (PgTable & Record<string, AnyPgColumn>) | undefined, ...keys: string[]): string | undefined {
37
+ if (!table) return undefined;
38
+ for (const key of keys) {
39
+ if (key in table) return key;
40
+ const snake = toSnakeCase(key);
41
+ if (snake in table) return snake;
42
+ const camel = camelCase(key);
43
+ if (camel in table) return camel;
44
+ }
45
+ return undefined;
46
+ }
47
+
48
+ function getColumn(table: (PgTable & Record<string, AnyPgColumn>) | undefined, ...keys: string[]): AnyPgColumn | undefined {
49
+ if (!table) return undefined;
50
+ const key = getColumnKey(table, ...keys);
51
+ return key ? table[key] : undefined;
52
+ }
53
+
24
54
  /**
25
55
  * PostgreSQL implementation of UserRepository.
26
56
  * Handles all user-related database operations using Drizzle ORM.
27
57
  */
28
58
  export class UserService implements UserRepository {
29
- constructor(private db: NodePgDatabase) { }
59
+ private usersTable: PgTable & Record<string, AnyPgColumn>;
60
+ private userIdentitiesTable: PgTable & Record<string, AnyPgColumn>;
61
+ private userRolesTable: PgTable & Record<string, AnyPgColumn>;
62
+ private rolesTable: PgTable & Record<string, AnyPgColumn>;
63
+
64
+ constructor(
65
+ private db: NodePgDatabase,
66
+ tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>
67
+ ) {
68
+ if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).users || (tableOrTables as Partial<AuthSchemaTables>).roles)) {
69
+ const tables = tableOrTables as Partial<AuthSchemaTables>;
70
+ this.usersTable = (tables.users || users) as unknown as PgTable & Record<string, AnyPgColumn>;
71
+ this.userIdentitiesTable = (tables.userIdentities || userIdentities) as unknown as PgTable & Record<string, AnyPgColumn>;
72
+ this.userRolesTable = (tables.userRoles || userRoles) as unknown as PgTable & Record<string, AnyPgColumn>;
73
+ this.rolesTable = (tables.roles || roles) as unknown as PgTable & Record<string, AnyPgColumn>;
74
+ } else {
75
+ const table = tableOrTables as (PgTable & Record<string, AnyPgColumn>) | undefined;
76
+ this.usersTable = table || (users as unknown as PgTable & Record<string, AnyPgColumn>);
77
+ this.userIdentitiesTable = userIdentities as unknown as PgTable & Record<string, AnyPgColumn>;
78
+ this.userRolesTable = userRoles as unknown as PgTable & Record<string, AnyPgColumn>;
79
+ this.rolesTable = roles as unknown as PgTable & Record<string, AnyPgColumn>;
80
+ }
81
+ }
82
+
83
+ private getQualifiedUsersTableName(): string {
84
+ const name = getTableName(this.usersTable);
85
+ const schema = getTableConfig(this.usersTable).schema || "public";
86
+ return `"${schema}"."${name}"`;
87
+ }
88
+
89
+ private mapRowToUser(row: Record<string, unknown>): UserData {
90
+ if (!row) return row as unknown as UserData;
91
+
92
+ const id = (row.id ?? row.uid) as string;
93
+ const email = row.email as string;
94
+ const passwordHash = (row.password_hash ?? row.passwordHash ?? null) as string | null | undefined;
95
+ const displayName = (row.display_name ?? row.displayName ?? null) as string | null | undefined;
96
+ const photoUrl = (row.photo_url ?? row.photoUrl ?? row.photoURL ?? null) as string | null | undefined;
97
+ const emailVerified = (row.email_verified ?? row.emailVerified ?? false) as boolean;
98
+ const emailVerificationToken = (row.email_verification_token ?? row.emailVerificationToken ?? null) as string | null | undefined;
99
+ const emailVerificationSentAt = (row.email_verification_sent_at ?? row.emailVerificationSentAt ?? null) as string | number | Date | null;
100
+ const createdAt = (row.created_at ?? row.createdAt) as string | number | Date | undefined;
101
+ const updatedAt = (row.updated_at ?? row.updatedAt) as string | number | Date | undefined;
102
+
103
+ const metadata: Record<string, any> = { ...((row.metadata as Record<string, any> | undefined) || {}) };
104
+
105
+ const knownKeys = new Set([
106
+ "id", "uid", "email",
107
+ "password_hash", "passwordHash",
108
+ "display_name", "displayName",
109
+ "photo_url", "photoUrl", "photoURL",
110
+ "email_verified", "emailVerified",
111
+ "email_verification_token", "emailVerificationToken",
112
+ "email_verification_sent_at", "emailVerificationSentAt",
113
+ "created_at", "createdAt",
114
+ "updated_at", "updatedAt",
115
+ "metadata"
116
+ ]);
117
+
118
+ for (const [key, val] of Object.entries(row)) {
119
+ if (!knownKeys.has(key)) {
120
+ const camelKey = camelCase(key);
121
+ metadata[camelKey] = val;
122
+ }
123
+ }
30
124
 
31
- async createUser(data: NewUser): Promise<User> {
32
- const [user] = await this.db.insert(users).values(data).returning();
33
- return user;
125
+ return {
126
+ id,
127
+ email,
128
+ passwordHash,
129
+ displayName,
130
+ photoUrl,
131
+ emailVerified,
132
+ emailVerificationToken,
133
+ emailVerificationSentAt: emailVerificationSentAt ? new Date(emailVerificationSentAt) : null,
134
+ createdAt: createdAt ? new Date(createdAt) : new Date(),
135
+ updatedAt: updatedAt ? new Date(updatedAt) : new Date(),
136
+ metadata
137
+ };
34
138
  }
35
139
 
36
- async getUserById(id: string): Promise<User | null> {
37
- const [user] = await this.db.select().from(users).where(eq(users.id, id));
38
- return user || null;
140
+ private mapPayload(data: Partial<CreateUserData>): Record<string, unknown> {
141
+ if (!data) return {};
142
+
143
+ const payload: Record<string, unknown> = {};
144
+
145
+ const idKey = getColumnKey(this.usersTable, "id") || "id";
146
+ const emailKey = getColumnKey(this.usersTable, "email") || "email";
147
+ const passwordHashKey = getColumnKey(this.usersTable, "passwordHash", "password_hash") || "passwordHash";
148
+ const displayNameKey = getColumnKey(this.usersTable, "displayName", "display_name") || "displayName";
149
+ const photoUrlKey = getColumnKey(this.usersTable, "photoUrl", "photo_url") || "photoUrl";
150
+ const emailVerifiedKey = getColumnKey(this.usersTable, "emailVerified", "email_verified") || "emailVerified";
151
+ const emailVerificationTokenKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
152
+ const emailVerificationSentAtKey = getColumnKey(this.usersTable, "emailVerificationSentAt", "email_verification_sent_at") || "emailVerificationSentAt";
153
+ const createdAtKey = getColumnKey(this.usersTable, "createdAt", "created_at") || "createdAt";
154
+ const updatedAtKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
155
+ const metadataKey = getColumnKey(this.usersTable, "metadata") || "metadata";
156
+
157
+ if ("id" in data) payload[idKey] = data.id;
158
+ if ("email" in data) payload[emailKey] = data.email;
159
+ if ("passwordHash" in data) payload[passwordHashKey] = data.passwordHash;
160
+ if ("displayName" in data) payload[displayNameKey] = data.displayName;
161
+ if ("photoUrl" in data) payload[photoUrlKey] = data.photoUrl;
162
+ if ("emailVerified" in data) payload[emailVerifiedKey] = data.emailVerified;
163
+ if ("emailVerificationToken" in data) payload[emailVerificationTokenKey] = data.emailVerificationToken;
164
+ if ("emailVerificationSentAt" in data) payload[emailVerificationSentAtKey] = data.emailVerificationSentAt;
165
+ if ("createdAt" in data) payload[createdAtKey] = data.createdAt;
166
+ if ("updatedAt" in data) payload[updatedAtKey] = data.updatedAt;
167
+
168
+ const metadata: Record<string, any> = { ...(data.metadata || {}) };
169
+ const remainingMetadata: Record<string, any> = {};
170
+
171
+ for (const [key, val] of Object.entries(metadata)) {
172
+ const tableColKey = getColumnKey(this.usersTable, key);
173
+ if (tableColKey &&
174
+ tableColKey !== idKey &&
175
+ tableColKey !== emailKey &&
176
+ tableColKey !== passwordHashKey &&
177
+ tableColKey !== displayNameKey &&
178
+ tableColKey !== photoUrlKey &&
179
+ tableColKey !== emailVerifiedKey &&
180
+ tableColKey !== emailVerificationTokenKey &&
181
+ tableColKey !== emailVerificationSentAtKey &&
182
+ tableColKey !== createdAtKey &&
183
+ tableColKey !== updatedAtKey &&
184
+ tableColKey !== metadataKey) {
185
+ payload[tableColKey] = val;
186
+ } else {
187
+ remainingMetadata[key] = val;
188
+ }
189
+ }
190
+
191
+ if (metadataKey in this.usersTable) {
192
+ payload[metadataKey] = remainingMetadata;
193
+ }
194
+
195
+ return payload;
39
196
  }
40
197
 
41
- async getUserByEmail(email: string): Promise<User | null> {
42
- const [user] = await this.db.select().from(users).where(eq(users.email, email.toLowerCase()));
43
- return user || null;
198
+ async createUser(data: CreateUserData): Promise<UserData> {
199
+ const payload = this.mapPayload(data);
200
+ const [row] = (await this.db.insert(this.usersTable).values(payload).returning()) as Record<string, unknown>[];
201
+ return this.mapRowToUser(row);
44
202
  }
45
203
 
46
- async getUserByIdentity(provider: string, providerId: string): Promise<User | null> {
47
- const result = await this.db.execute(sql`
48
- SELECT u.*
49
- FROM rebase.users u
50
- INNER JOIN rebase.user_identities ui ON u.id = ui.user_id
51
- WHERE ui.provider = ${provider} AND ui.provider_id = ${providerId}
52
- LIMIT 1
53
- `);
204
+ async getUserById(id: string): Promise<UserData | null> {
205
+ const idCol = getColumn(this.usersTable, "id");
206
+ if (!idCol) return null;
207
+ const [row] = await this.db.select().from(this.usersTable).where(eq(idCol, id));
208
+ return row ? this.mapRowToUser(row as Record<string, unknown>) : null;
209
+ }
54
210
 
55
- if (result.rows.length === 0) return null;
211
+ async getUserByEmail(email: string): Promise<UserData | null> {
212
+ const emailCol = getColumn(this.usersTable, "email");
213
+ if (!emailCol) return null;
214
+ const [row] = await this.db.select().from(this.usersTable).where(eq(emailCol, email.toLowerCase()));
215
+ return row ? this.mapRowToUser(row as Record<string, unknown>) : null;
216
+ }
56
217
 
57
- const row = result.rows[0] as Record<string, unknown>;
58
- return {
59
- id: row.id as string,
60
- email: row.email as string,
61
- passwordHash: (row.password_hash as string | null) ?? null,
62
- displayName: (row.display_name as string | null) ?? null,
63
- photoUrl: (row.photo_url as string | null) ?? null,
64
- emailVerified: (row.email_verified as boolean | undefined) ?? false,
65
- emailVerificationToken: (row.email_verification_token as string | null) ?? null,
66
- emailVerificationSentAt: (row.email_verification_sent_at as Date | null) ?? null,
67
- createdAt: row.created_at as Date,
68
- updatedAt: row.updated_at as Date
69
- } as User;
218
+ async getUserByIdentity(provider: string, providerId: string): Promise<UserData | null> {
219
+ const userIdCol = getColumn(this.usersTable, "id");
220
+ if (!userIdCol) return null;
221
+
222
+ const result = await this.db
223
+ .select({ user: this.usersTable })
224
+ .from(this.usersTable)
225
+ .innerJoin(this.userIdentitiesTable, eq(userIdCol, this.userIdentitiesTable.userId))
226
+ .where(
227
+ sql`${this.userIdentitiesTable.provider} = ${provider} AND ${this.userIdentitiesTable.providerId} = ${providerId}`
228
+ )
229
+ .limit(1);
230
+
231
+ if (result.length === 0) return null;
232
+ return this.mapRowToUser(result[0].user as Record<string, unknown>);
70
233
  }
71
234
 
72
235
  async getUserIdentities(userId: string): Promise<UserIdentityData[]> {
236
+ const schema = getTableConfig(this.userIdentitiesTable).schema || "public";
73
237
  const result = await this.db.execute(sql`
74
238
  SELECT id, user_id, provider, provider_id, profile_data, created_at, updated_at
75
- FROM rebase.user_identities
239
+ FROM ${sql.raw(`"${schema}"."user_identities"`)}
76
240
  WHERE user_id = ${userId}
77
241
  `);
78
242
 
@@ -88,30 +252,38 @@ export class UserService implements UserRepository {
88
252
  }
89
253
 
90
254
  async linkUserIdentity(userId: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {
91
- await this.db.insert(userIdentities).values({
255
+ await this.db.insert(this.userIdentitiesTable).values({
92
256
  userId,
93
257
  provider,
94
258
  providerId,
95
259
  profileData: profileData || null
96
- }).onConflictDoNothing({ target: [userIdentities.provider, userIdentities.providerId] });
260
+ }).onConflictDoNothing({ target: [this.userIdentitiesTable.provider, this.userIdentitiesTable.providerId] });
97
261
  }
98
262
 
99
- async updateUser(id: string, data: Partial<Omit<NewUser, "id">>): Promise<User | null> {
100
- const [user] = await this.db
101
- .update(users)
102
- .set({ ...data,
103
- updatedAt: new Date() })
104
- .where(eq(users.id, id))
105
- .returning();
106
- return user || null;
263
+ async updateUser(id: string, data: Partial<Omit<CreateUserData, "id">>): Promise<UserData | null> {
264
+ const idCol = getColumn(this.usersTable, "id");
265
+ if (!idCol) return null;
266
+ const payload = this.mapPayload(data);
267
+ const updatedAtKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
268
+ payload[updatedAtKey] = new Date();
269
+
270
+ const [row] = (await this.db
271
+ .update(this.usersTable)
272
+ .set(payload)
273
+ .where(eq(idCol, id))
274
+ .returning()) as Record<string, unknown>[];
275
+ return row ? this.mapRowToUser(row) : null;
107
276
  }
108
277
 
109
278
  async deleteUser(id: string): Promise<void> {
110
- await this.db.delete(users).where(eq(users.id, id));
279
+ const idCol = getColumn(this.usersTable, "id");
280
+ if (!idCol) return;
281
+ await this.db.delete(this.usersTable).where(eq(idCol, id));
111
282
  }
112
283
 
113
- async listUsers(): Promise<User[]> {
114
- return this.db.select().from(users);
284
+ async listUsers(): Promise<UserData[]> {
285
+ const rows = await this.db.select().from(this.usersTable);
286
+ return (rows as Record<string, unknown>[]).map(row => this.mapRowToUser(row));
115
287
  }
116
288
 
117
289
  async listUsersPaginated(options?: ListUsersOptions): Promise<PaginatedUsersResult> {
@@ -122,125 +294,139 @@ updatedAt: new Date() })
122
294
  const orderDir = options?.orderDir || "desc";
123
295
  const roleId = options?.roleId;
124
296
 
125
- // Map camelCase field names to snake_case column names
126
- const columnMap: Record<string, string> = {
127
- email: "email",
128
- displayName: "display_name",
129
- createdAt: "created_at",
130
- updatedAt: "updated_at",
131
- provider: "provider"
132
- };
133
- const orderColumn = columnMap[orderBy] || "created_at";
297
+ const orderCol = getColumn(this.usersTable, orderBy);
298
+ const orderColumn = orderCol ? orderCol.name : "created_at";
134
299
  const direction = orderDir === "asc" ? sql`ASC` : sql`DESC`;
135
300
 
301
+ const emailCol = getColumn(this.usersTable, "email");
302
+ const emailColumn = emailCol ? emailCol.name : "email";
303
+ const displayNameCol = getColumn(this.usersTable, "displayName", "display_name");
304
+ const displayNameColumn = displayNameCol ? displayNameCol.name : "display_name";
305
+ const idCol = getColumn(this.usersTable, "id");
306
+ const idColumn = idCol ? idCol.name : "id";
307
+
308
+ const usersTableName = this.getQualifiedUsersTableName();
309
+ const rolesSchema = getTableConfig(this.userRolesTable).schema || "public";
310
+
136
311
  const conditions = [];
137
312
  if (roleId) {
138
- conditions.push(sql`EXISTS (SELECT 1 FROM rebase.user_roles ur WHERE ur.user_id = users.id AND ur.role_id = ${roleId})`);
313
+ conditions.push(sql`EXISTS (SELECT 1 FROM ${sql.raw(`"${rolesSchema}"."user_roles"`)} ur WHERE ur.user_id = ${sql.raw(usersTableName)}.${sql.raw(idColumn)} AND ur.role_id = ${roleId})`);
139
314
  }
140
315
  if (search) {
141
316
  const pattern = `%${search}%`;
142
- conditions.push(sql`(email ILIKE ${pattern} OR display_name ILIKE ${pattern})`);
317
+ conditions.push(sql`(${sql.raw(usersTableName)}.${sql.raw(emailColumn)} ILIKE ${pattern} OR ${sql.raw(usersTableName)}.${sql.raw(displayNameColumn)} ILIKE ${pattern})`);
143
318
  }
144
319
 
145
320
  const whereClause = conditions.length > 0 ? sql`WHERE ${sql.join(conditions, sql` AND `)}` : sql``;
146
321
 
147
322
  // Sorting: users with roles first if no role filter, then by requested order
148
323
  const orderByClause = roleId
149
- ? sql`ORDER BY ${sql.raw(orderColumn)} ${direction}`
150
- : sql`ORDER BY (SELECT count(*) FROM rebase.user_roles ur WHERE ur.user_id = users.id) DESC, ${sql.raw(orderColumn)} ${direction}`;
324
+ ? sql`ORDER BY ${sql.raw(usersTableName)}.${sql.raw(orderColumn)} ${direction}`
325
+ : sql`ORDER BY (SELECT count(*) FROM ${sql.raw(`"${rolesSchema}"."user_roles"`)} ur WHERE ur.user_id = ${sql.raw(usersTableName)}.${sql.raw(idColumn)}) DESC, ${sql.raw(usersTableName)}.${sql.raw(orderColumn)} ${direction}`;
151
326
 
152
327
  const countResult = await this.db.execute(sql`
153
- SELECT count(*)::int as total FROM rebase.users
328
+ SELECT count(*)::int as total FROM ${sql.raw(usersTableName)}
154
329
  ${whereClause}
155
330
  `);
156
331
  const total = (countResult.rows[0] as { total: number }).total;
157
332
 
158
333
  const dataResult = await this.db.execute(sql`
159
- SELECT * FROM rebase.users
334
+ SELECT * FROM ${sql.raw(usersTableName)}
160
335
  ${whereClause}
161
336
  ${orderByClause}
162
337
  LIMIT ${limit} OFFSET ${offset}
163
338
  `);
164
- const rows = dataResult.rows as User[];
339
+ const rows = dataResult.rows;
165
340
 
166
- // Map snake_case rows to camelCase UserData
167
- const mappedUsers: User[] = rows.map((row: Record<string, unknown>) => ({
168
- id: row.id as string,
169
- email: row.email as string,
170
- passwordHash: ((row.password_hash ?? row.passwordHash) as string | null) ?? null,
171
- displayName: ((row.display_name ?? row.displayName) as string | null) ?? null,
172
- photoUrl: ((row.photo_url ?? row.photoUrl) as string | null) ?? null,
173
- emailVerified: ((row.email_verified ?? row.emailVerified) as boolean | undefined) ?? false,
174
- emailVerificationToken: ((row.email_verification_token ?? row.emailVerificationToken) as string | null) ?? null,
175
- emailVerificationSentAt: ((row.email_verification_sent_at ?? row.emailVerificationSentAt) as Date | null) ?? null,
176
- createdAt: (row.created_at ?? row.createdAt) as Date,
177
- updatedAt: (row.updated_at ?? row.updatedAt) as Date
178
- })) as User[];
341
+ // Map rows to camelCase UserData
342
+ const mappedUsers: UserData[] = (rows as Record<string, unknown>[]).map((row) => this.mapRowToUser(row));
179
343
 
180
344
  return { users: mappedUsers,
181
- total,
182
- limit,
183
- offset };
345
+ total,
346
+ limit,
347
+ offset };
184
348
  }
185
349
 
186
350
  /**
187
351
  * Update user's password hash
188
352
  */
189
353
  async updatePassword(id: string, passwordHash: string): Promise<void> {
354
+ const idCol = getColumn(this.usersTable, "id");
355
+ if (!idCol) return;
356
+ const passwordHashColKey = getColumnKey(this.usersTable, "passwordHash", "password_hash") || "passwordHash";
357
+ const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
358
+
190
359
  await this.db
191
- .update(users)
192
- .set({ passwordHash,
193
- updatedAt: new Date() })
194
- .where(eq(users.id, id));
360
+ .update(this.usersTable)
361
+ .set({
362
+ [passwordHashColKey]: passwordHash,
363
+ [updatedAtColKey]: new Date()
364
+ })
365
+ .where(eq(idCol, id));
195
366
  }
196
367
 
197
368
  /**
198
369
  * Set email verification status
199
370
  */
200
371
  async setEmailVerified(id: string, verified: boolean): Promise<void> {
372
+ const idCol = getColumn(this.usersTable, "id");
373
+ if (!idCol) return;
374
+ const emailVerifiedColKey = getColumnKey(this.usersTable, "emailVerified", "email_verified") || "emailVerified";
375
+ const emailVerificationTokenColKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
376
+ const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
377
+
201
378
  await this.db
202
- .update(users)
379
+ .update(this.usersTable)
203
380
  .set({
204
- emailVerified: verified,
205
- emailVerificationToken: null,
206
- updatedAt: new Date()
381
+ [emailVerifiedColKey]: verified,
382
+ [emailVerificationTokenColKey]: null,
383
+ [updatedAtColKey]: new Date()
207
384
  })
208
- .where(eq(users.id, id));
385
+ .where(eq(idCol, id));
209
386
  }
210
387
 
211
388
  /**
212
389
  * Set email verification token
213
390
  */
214
391
  async setVerificationToken(id: string, token: string | null): Promise<void> {
392
+ const idCol = getColumn(this.usersTable, "id");
393
+ if (!idCol) return;
394
+ const emailVerificationTokenColKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
395
+ const emailVerificationSentAtColKey = getColumnKey(this.usersTable, "emailVerificationSentAt", "email_verification_sent_at") || "emailVerificationSentAt";
396
+ const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
397
+
215
398
  await this.db
216
- .update(users)
399
+ .update(this.usersTable)
217
400
  .set({
218
- emailVerificationToken: token,
219
- emailVerificationSentAt: token ? new Date() : null,
220
- updatedAt: new Date()
401
+ [emailVerificationTokenColKey]: token,
402
+ [emailVerificationSentAtColKey]: token ? new Date() : null,
403
+ [updatedAtColKey]: new Date()
221
404
  })
222
- .where(eq(users.id, id));
405
+ .where(eq(idCol, id));
223
406
  }
224
407
 
225
408
  /**
226
409
  * Find user by email verification token
227
410
  */
228
- async getUserByVerificationToken(token: string): Promise<User | null> {
229
- const [user] = await this.db
411
+ async getUserByVerificationToken(token: string): Promise<UserData | null> {
412
+ const tokenCol = getColumn(this.usersTable, "emailVerificationToken", "email_verification_token");
413
+ if (!tokenCol) return null;
414
+ const [row] = await this.db
230
415
  .select()
231
- .from(users)
232
- .where(eq(users.emailVerificationToken, token));
233
- return user || null;
416
+ .from(this.usersTable)
417
+ .where(eq(tokenCol, token));
418
+ return row ? this.mapRowToUser(row as Record<string, unknown>) : null;
234
419
  }
235
420
 
236
421
  /**
237
422
  * Get roles for a user from database
238
423
  */
239
424
  async getUserRoles(userId: string): Promise<Role[]> {
425
+ const rolesSchema = getTableConfig(this.rolesTable).schema || "public";
240
426
  const result = await this.db.execute(sql`
241
427
  SELECT r.id, r.name, r.is_admin, r.default_permissions, r.collection_permissions, r.config
242
- FROM rebase.roles r
243
- INNER JOIN rebase.user_roles ur ON r.id = ur.role_id
428
+ FROM ${sql.raw(`"${rolesSchema}"."roles"`)} r
429
+ INNER JOIN ${sql.raw(`"${rolesSchema}"."user_roles"`)} ur ON r.id = ur.role_id
244
430
  WHERE ur.user_id = ${userId}
245
431
  `);
246
432
 
@@ -266,13 +452,14 @@ updatedAt: new Date() })
266
452
  * Set roles for a user
267
453
  */
268
454
  async setUserRoles(userId: string, roleIds: string[]): Promise<void> {
455
+ const rolesSchema = getTableConfig(this.userRolesTable).schema || "public";
269
456
  // Delete existing roles
270
- await this.db.execute(sql`DELETE FROM rebase.user_roles WHERE user_id = ${userId}`);
457
+ await this.db.execute(sql`DELETE FROM ${sql.raw(`"${rolesSchema}"."user_roles"`)} WHERE user_id = ${userId}`);
271
458
 
272
459
  // Insert new roles
273
460
  for (const roleId of roleIds) {
274
461
  await this.db.execute(sql`
275
- INSERT INTO rebase.user_roles (user_id, role_id)
462
+ INSERT INTO ${sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
276
463
  VALUES (${userId}, ${roleId})
277
464
  ON CONFLICT DO NOTHING
278
465
  `);
@@ -283,8 +470,9 @@ updatedAt: new Date() })
283
470
  * Assign a specific role to new user
284
471
  */
285
472
  async assignDefaultRole(userId: string, roleId: string): Promise<void> {
473
+ const rolesSchema = getTableConfig(this.userRolesTable).schema || "public";
286
474
  await this.db.execute(sql`
287
- INSERT INTO rebase.user_roles (user_id, role_id)
475
+ INSERT INTO ${sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
288
476
  VALUES (${userId}, ${roleId})
289
477
  ON CONFLICT DO NOTHING
290
478
  `);
@@ -293,13 +481,13 @@ updatedAt: new Date() })
293
481
  /**
294
482
  * Get user with their roles
295
483
  */
296
- async getUserWithRoles(userId: string): Promise<{ user: User; roles: Role[] } | null> {
484
+ async getUserWithRoles(userId: string): Promise<{ user: UserData; roles: Role[] } | null> {
297
485
  const user = await this.getUserById(userId);
298
486
  if (!user) return null;
299
487
 
300
488
  const roles = await this.getUserRoles(userId);
301
489
  return { user,
302
- roles };
490
+ roles };
303
491
  }
304
492
  }
305
493
 
@@ -308,12 +496,30 @@ roles };
308
496
  * Handles all role-related database operations using Drizzle ORM.
309
497
  */
310
498
  export class RoleService implements RoleRepository {
311
- constructor(private db: NodePgDatabase) { }
499
+ private rolesTable: PgTable & Record<string, AnyPgColumn>;
500
+
501
+ constructor(
502
+ private db: NodePgDatabase,
503
+ tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>
504
+ ) {
505
+ if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).roles || (tableOrTables as Partial<AuthSchemaTables>).users)) {
506
+ this.rolesTable = ((tableOrTables as Partial<AuthSchemaTables>).roles || roles) as unknown as PgTable & Record<string, AnyPgColumn>;
507
+ } else {
508
+ this.rolesTable = (tableOrTables as unknown as PgTable & Record<string, AnyPgColumn>) || (roles as unknown as PgTable & Record<string, AnyPgColumn>);
509
+ }
510
+ }
511
+
512
+ private getQualifiedRolesTableName(): string {
513
+ const name = getTableName(this.rolesTable);
514
+ const schema = getTableConfig(this.rolesTable).schema || "public";
515
+ return `"${schema}"."${name}"`;
516
+ }
312
517
 
313
518
  async getRoleById(id: string): Promise<Role | null> {
519
+ const tableName = this.getQualifiedRolesTableName();
314
520
  const result = await this.db.execute(sql`
315
521
  SELECT id, name, is_admin, default_permissions, collection_permissions, config
316
- FROM rebase.roles
522
+ FROM ${sql.raw(tableName)}
317
523
  WHERE id = ${id}
318
524
  `);
319
525
 
@@ -331,9 +537,10 @@ export class RoleService implements RoleRepository {
331
537
  }
332
538
 
333
539
  async listRoles(): Promise<Role[]> {
540
+ const tableName = this.getQualifiedRolesTableName();
334
541
  const result = await this.db.execute(sql`
335
542
  SELECT id, name, is_admin, default_permissions, collection_permissions, config
336
- FROM rebase.roles
543
+ FROM ${sql.raw(tableName)}
337
544
  ORDER BY name
338
545
  `);
339
546
 
@@ -348,8 +555,9 @@ export class RoleService implements RoleRepository {
348
555
  }
349
556
 
350
557
  async createRole(data: Omit<Role, "isAdmin" | "collectionPermissions"> & { isAdmin?: boolean; collectionPermissions?: Role["collectionPermissions"] }): Promise<Role> {
558
+ const tableName = this.getQualifiedRolesTableName();
351
559
  const result = await this.db.execute(sql`
352
- INSERT INTO rebase.roles (id, name, is_admin, default_permissions, collection_permissions, config)
560
+ INSERT INTO ${sql.raw(tableName)} (id, name, is_admin, default_permissions, collection_permissions, config)
353
561
  VALUES (
354
562
  ${data.id},
355
563
  ${data.name},
@@ -373,12 +581,12 @@ export class RoleService implements RoleRepository {
373
581
  }
374
582
 
375
583
  async updateRole(id: string, data: Partial<Omit<Role, "id">>): Promise<Role | null> {
376
- // For now, use simpler approach
377
584
  const existing = await this.getRoleById(id);
378
585
  if (!existing) return null;
379
586
 
587
+ const tableName = this.getQualifiedRolesTableName();
380
588
  await this.db.execute(sql`
381
- UPDATE rebase.roles
589
+ UPDATE ${sql.raw(tableName)}
382
590
  SET
383
591
  name = ${data.name ?? existing.name},
384
592
  is_admin = ${data.isAdmin ?? existing.isAdmin},
@@ -392,12 +600,30 @@ export class RoleService implements RoleRepository {
392
600
  }
393
601
 
394
602
  async deleteRole(id: string): Promise<void> {
395
- await this.db.execute(sql`DELETE FROM rebase.roles WHERE id = ${id}`);
603
+ const tableName = this.getQualifiedRolesTableName();
604
+ await this.db.execute(sql`DELETE FROM ${sql.raw(tableName)} WHERE id = ${id}`);
396
605
  }
397
606
  }
398
607
 
399
608
  export class RefreshTokenService {
400
- constructor(private db: NodePgDatabase) { }
609
+ private refreshTokensTable: PgTable & Record<string, AnyPgColumn>;
610
+
611
+ constructor(
612
+ private db: NodePgDatabase,
613
+ tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>
614
+ ) {
615
+ if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).refreshTokens || (tableOrTables as Partial<AuthSchemaTables>).users)) {
616
+ this.refreshTokensTable = ((tableOrTables as Partial<AuthSchemaTables>).refreshTokens || refreshTokens) as unknown as PgTable & Record<string, AnyPgColumn>;
617
+ } else {
618
+ this.refreshTokensTable = (tableOrTables as unknown as PgTable & Record<string, AnyPgColumn>) || (refreshTokens as unknown as PgTable & Record<string, AnyPgColumn>);
619
+ }
620
+ }
621
+
622
+ private getQualifiedRefreshTokensTableName(): string {
623
+ const name = getTableName(this.refreshTokensTable);
624
+ const schema = getTableConfig(this.refreshTokensTable).schema || "public";
625
+ return `"${schema}"."${name}"`;
626
+ }
401
627
 
402
628
  async createToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
403
629
  // Fallback to empty string because UNIQUE constraints treat NULLs as strictly distinct in standard Postgres.
@@ -407,14 +633,15 @@ export class RefreshTokenService {
407
633
 
408
634
  // Delete any existing session for this user/device combo, then insert.
409
635
  // This approach doesn't require the unique_device_session constraint to exist.
636
+ const tableName = this.getQualifiedRefreshTokensTableName();
410
637
  await this.db.execute(sql`
411
- DELETE FROM rebase.refresh_tokens
638
+ DELETE FROM ${sql.raw(tableName)}
412
639
  WHERE user_id = ${userId}
413
640
  AND user_agent = ${safeUserAgent}
414
641
  AND ip_address = ${safeIpAddress}
415
642
  `);
416
643
 
417
- await this.db.insert(refreshTokens)
644
+ await this.db.insert(this.refreshTokensTable)
418
645
  .values({
419
646
  userId,
420
647
  tokenHash,
@@ -427,49 +654,49 @@ export class RefreshTokenService {
427
654
  async findByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {
428
655
  const [token] = await this.db
429
656
  .select({
430
- id: refreshTokens.id,
431
- userId: refreshTokens.userId,
432
- tokenHash: refreshTokens.tokenHash,
433
- expiresAt: refreshTokens.expiresAt,
434
- createdAt: refreshTokens.createdAt,
435
- userAgent: refreshTokens.userAgent,
436
- ipAddress: refreshTokens.ipAddress
657
+ id: this.refreshTokensTable.id,
658
+ userId: this.refreshTokensTable.userId,
659
+ tokenHash: this.refreshTokensTable.tokenHash,
660
+ expiresAt: this.refreshTokensTable.expiresAt,
661
+ createdAt: this.refreshTokensTable.createdAt,
662
+ userAgent: this.refreshTokensTable.userAgent,
663
+ ipAddress: this.refreshTokensTable.ipAddress
437
664
  })
438
- .from(refreshTokens)
439
- .where(eq(refreshTokens.tokenHash, tokenHash));
665
+ .from(this.refreshTokensTable)
666
+ .where(eq(this.refreshTokensTable.tokenHash, tokenHash));
440
667
 
441
- return token || null;
668
+ return (token as RefreshTokenInfo) || null;
442
669
  }
443
670
 
444
671
  async deleteByHash(tokenHash: string): Promise<void> {
445
- await this.db.delete(refreshTokens).where(eq(refreshTokens.tokenHash, tokenHash));
672
+ await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.tokenHash, tokenHash));
446
673
  }
447
674
 
448
675
  async deleteAllForUser(userId: string): Promise<void> {
449
- await this.db.delete(refreshTokens).where(eq(refreshTokens.userId, userId));
676
+ await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.userId, userId));
450
677
  }
451
678
 
452
679
  async listForUser(userId: string): Promise<RefreshTokenInfo[]> {
453
680
  const tokens = await this.db
454
681
  .select({
455
- id: refreshTokens.id,
456
- userId: refreshTokens.userId,
457
- tokenHash: refreshTokens.tokenHash,
458
- expiresAt: refreshTokens.expiresAt,
459
- createdAt: refreshTokens.createdAt,
460
- userAgent: refreshTokens.userAgent,
461
- ipAddress: refreshTokens.ipAddress
682
+ id: this.refreshTokensTable.id,
683
+ userId: this.refreshTokensTable.userId,
684
+ tokenHash: this.refreshTokensTable.tokenHash,
685
+ expiresAt: this.refreshTokensTable.expiresAt,
686
+ createdAt: this.refreshTokensTable.createdAt,
687
+ userAgent: this.refreshTokensTable.userAgent,
688
+ ipAddress: this.refreshTokensTable.ipAddress
462
689
  })
463
- .from(refreshTokens)
464
- .where(eq(refreshTokens.userId, userId))
465
- .orderBy(refreshTokens.createdAt);
690
+ .from(this.refreshTokensTable)
691
+ .where(eq(this.refreshTokensTable.userId, userId))
692
+ .orderBy(this.refreshTokensTable.createdAt);
466
693
 
467
- return tokens;
694
+ return tokens as RefreshTokenInfo[];
468
695
  }
469
696
 
470
697
  async deleteById(id: string, userId: string): Promise<void> {
471
- await this.db.delete(refreshTokens)
472
- .where(sql`${refreshTokens.id} = ${id} AND ${refreshTokens.userId} = ${userId}`);
698
+ await this.db.delete(this.refreshTokensTable)
699
+ .where(sql`${this.refreshTokensTable.id} = ${id} AND ${this.refreshTokensTable.userId} = ${userId}`);
473
700
  }
474
701
  }
475
702
 
@@ -477,19 +704,37 @@ export class RefreshTokenService {
477
704
  * Password reset token service
478
705
  */
479
706
  export class PasswordResetTokenService {
480
- constructor(private db: NodePgDatabase) { }
707
+ private passwordResetTokensTable: PgTable & Record<string, AnyPgColumn>;
708
+
709
+ constructor(
710
+ private db: NodePgDatabase,
711
+ tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>
712
+ ) {
713
+ if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).passwordResetTokens || (tableOrTables as Partial<AuthSchemaTables>).users)) {
714
+ this.passwordResetTokensTable = ((tableOrTables as Partial<AuthSchemaTables>).passwordResetTokens || passwordResetTokens) as unknown as PgTable & Record<string, AnyPgColumn>;
715
+ } else {
716
+ this.passwordResetTokensTable = (tableOrTables as unknown as PgTable & Record<string, AnyPgColumn>) || (passwordResetTokens as unknown as PgTable & Record<string, AnyPgColumn>);
717
+ }
718
+ }
719
+
720
+ private getQualifiedPasswordResetTokensTableName(): string {
721
+ const name = getTableName(this.passwordResetTokensTable);
722
+ const schema = getTableConfig(this.passwordResetTokensTable).schema || "public";
723
+ return `"${schema}"."${name}"`;
724
+ }
481
725
 
482
726
  /**
483
727
  * Create a password reset token
484
728
  */
485
729
  async createToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
486
730
  // Delete any existing unused tokens for this user
731
+ const tableName = this.getQualifiedPasswordResetTokensTableName();
487
732
  await this.db.execute(sql`
488
- DELETE FROM rebase.password_reset_tokens
733
+ DELETE FROM ${sql.raw(tableName)}
489
734
  WHERE user_id = ${userId} AND used_at IS NULL
490
735
  `);
491
736
 
492
- await this.db.insert(passwordResetTokens).values({
737
+ await this.db.insert(this.passwordResetTokensTable).values({
493
738
  userId,
494
739
  tokenHash,
495
740
  expiresAt
@@ -502,18 +747,19 @@ export class PasswordResetTokenService {
502
747
  async findValidByHash(tokenHash: string): Promise<{ userId: string; expiresAt: Date } | null> {
503
748
  const [token] = await this.db
504
749
  .select({
505
- userId: passwordResetTokens.userId,
506
- expiresAt: passwordResetTokens.expiresAt
750
+ userId: this.passwordResetTokensTable.userId,
751
+ expiresAt: this.passwordResetTokensTable.expiresAt
507
752
  })
508
- .from(passwordResetTokens)
509
- .where(eq(passwordResetTokens.tokenHash, tokenHash));
753
+ .from(this.passwordResetTokensTable)
754
+ .where(eq(this.passwordResetTokensTable.tokenHash, tokenHash)) as unknown as Array<{ userId: string; expiresAt: Date }>;
510
755
 
511
756
  if (!token) return null;
512
757
 
513
758
  // Check if expired or used
759
+ const tableName = this.getQualifiedPasswordResetTokensTableName();
514
760
  const result = await this.db.execute(sql`
515
761
  SELECT user_id, expires_at
516
- FROM rebase.password_reset_tokens
762
+ FROM ${sql.raw(tableName)}
517
763
  WHERE token_hash = ${tokenHash}
518
764
  AND used_at IS NULL
519
765
  AND expires_at > NOW()
@@ -533,24 +779,25 @@ export class PasswordResetTokenService {
533
779
  */
534
780
  async markAsUsed(tokenHash: string): Promise<void> {
535
781
  await this.db
536
- .update(passwordResetTokens)
782
+ .update(this.passwordResetTokensTable)
537
783
  .set({ usedAt: new Date() })
538
- .where(eq(passwordResetTokens.tokenHash, tokenHash));
784
+ .where(eq(this.passwordResetTokensTable.tokenHash, tokenHash));
539
785
  }
540
786
 
541
787
  /**
542
788
  * Delete all tokens for a user
543
789
  */
544
790
  async deleteAllForUser(userId: string): Promise<void> {
545
- await this.db.delete(passwordResetTokens).where(eq(passwordResetTokens.userId, userId));
791
+ await this.db.delete(this.passwordResetTokensTable).where(eq(this.passwordResetTokensTable.userId, userId));
546
792
  }
547
793
 
548
794
  /**
549
795
  * Clean up expired tokens
550
796
  */
551
797
  async deleteExpired(): Promise<void> {
798
+ const tableName = this.getQualifiedPasswordResetTokensTableName();
552
799
  await this.db.execute(sql`
553
- DELETE FROM rebase.password_reset_tokens
800
+ DELETE FROM ${sql.raw(tableName)}
554
801
  WHERE expires_at < NOW()
555
802
  `);
556
803
  }
@@ -564,9 +811,12 @@ export class PostgresTokenRepository implements TokenRepository {
564
811
  private refreshTokenService: RefreshTokenService;
565
812
  private passwordResetTokenService: PasswordResetTokenService;
566
813
 
567
- constructor(private db: NodePgDatabase) {
568
- this.refreshTokenService = new RefreshTokenService(db);
569
- this.passwordResetTokenService = new PasswordResetTokenService(db);
814
+ constructor(
815
+ private db: NodePgDatabase,
816
+ tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>
817
+ ) {
818
+ this.refreshTokenService = new RefreshTokenService(db, tableOrTables);
819
+ this.passwordResetTokenService = new PasswordResetTokenService(db, tableOrTables);
570
820
  }
571
821
 
572
822
  // Refresh token operations
@@ -628,28 +878,31 @@ export class PostgresAuthRepository implements AuthRepository {
628
878
  private roleService: RoleService;
629
879
  private tokenRepository: PostgresTokenRepository;
630
880
 
631
- constructor(private db: NodePgDatabase) {
632
- this.userService = new UserService(db);
633
- this.roleService = new RoleService(db);
634
- this.tokenRepository = new PostgresTokenRepository(db);
881
+ constructor(
882
+ private db: NodePgDatabase,
883
+ tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>
884
+ ) {
885
+ this.userService = new UserService(db, tableOrTables);
886
+ this.roleService = new RoleService(db, tableOrTables);
887
+ this.tokenRepository = new PostgresTokenRepository(db, tableOrTables);
635
888
  }
636
889
 
637
890
  // User operations (delegate to UserService)
638
891
 
639
892
  async createUser(data: CreateUserData): Promise<UserData> {
640
- return this.userService.createUser(data as NewUser) as Promise<UserData>;
893
+ return this.userService.createUser(data);
641
894
  }
642
895
 
643
896
  async getUserById(id: string): Promise<UserData | null> {
644
- return this.userService.getUserById(id) as Promise<UserData | null>;
897
+ return this.userService.getUserById(id);
645
898
  }
646
899
 
647
900
  async getUserByEmail(email: string): Promise<UserData | null> {
648
- return this.userService.getUserByEmail(email) as Promise<UserData | null>;
901
+ return this.userService.getUserByEmail(email);
649
902
  }
650
903
 
651
904
  async getUserByIdentity(provider: string, providerId: string): Promise<UserData | null> {
652
- return this.userService.getUserByIdentity(provider, providerId) as Promise<UserData | null>;
905
+ return this.userService.getUserByIdentity(provider, providerId);
653
906
  }
654
907
 
655
908
  async getUserIdentities(userId: string): Promise<UserIdentityData[]> {
@@ -661,7 +914,7 @@ export class PostgresAuthRepository implements AuthRepository {
661
914
  }
662
915
 
663
916
  async updateUser(id: string, data: Partial<Omit<CreateUserData, "id">>): Promise<UserData | null> {
664
- return this.userService.updateUser(id, data) as Promise<UserData | null>;
917
+ return this.userService.updateUser(id, data);
665
918
  }
666
919
 
667
920
  async deleteUser(id: string): Promise<void> {
@@ -669,7 +922,7 @@ export class PostgresAuthRepository implements AuthRepository {
669
922
  }
670
923
 
671
924
  async listUsers(): Promise<UserData[]> {
672
- return this.userService.listUsers() as Promise<UserData[]>;
925
+ return this.userService.listUsers();
673
926
  }
674
927
 
675
928
  async listUsersPaginated(options?: ListUsersOptions): Promise<PaginatedUsersResult> {
@@ -689,7 +942,7 @@ export class PostgresAuthRepository implements AuthRepository {
689
942
  }
690
943
 
691
944
  async getUserByVerificationToken(token: string): Promise<UserData | null> {
692
- return this.userService.getUserByVerificationToken(token) as Promise<UserData | null>;
945
+ return this.userService.getUserByVerificationToken(token);
693
946
  }
694
947
 
695
948
  async getUserRoles(userId: string): Promise<RoleData[]> {
@@ -709,8 +962,7 @@ export class PostgresAuthRepository implements AuthRepository {
709
962
  }
710
963
 
711
964
  async getUserWithRoles(userId: string): Promise<{ user: UserData; roles: RoleData[] } | null> {
712
- const result = await this.userService.getUserWithRoles(userId);
713
- return result as { user: UserData; roles: RoleData[] } | null;
965
+ return this.userService.getUserWithRoles(userId);
714
966
  }
715
967
 
716
968
  // Role operations (delegate to RoleService)
@@ -796,4 +1048,3 @@ export type PostgresUserRepository = UserService;
796
1048
 
797
1049
  /** PostgreSQL role repository implementation */
798
1050
  export type PostgresRoleRepository = RoleService;
799
-