@rebasepro/server-postgres 0.9.1-canary.1d2d8b5 → 0.9.1-canary.26fe4b2

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 (62) hide show
  1. package/README.md +21 -0
  2. package/dist/PostgresBackendDriver.d.ts +43 -2
  3. package/dist/PostgresBootstrapper.d.ts +17 -1
  4. package/dist/auth/services.d.ts +68 -52
  5. package/dist/collections/buildRegistry.d.ts +27 -0
  6. package/dist/connection.d.ts +21 -0
  7. package/dist/data-transformer.d.ts +9 -2
  8. package/dist/index.es.js +2060 -762
  9. package/dist/index.es.js.map +1 -1
  10. package/dist/schema/auth-bootstrap-sql.d.ts +1 -1
  11. package/dist/schema/auth-schema.d.ts +24 -24
  12. package/dist/schema/doctor.d.ts +1 -1
  13. package/dist/schema/introspect-db-logic.d.ts +0 -5
  14. package/dist/schema/introspect-db-naming.d.ts +10 -0
  15. package/dist/security/policy-drift.d.ts +30 -0
  16. package/dist/security/rls-enforcement.d.ts +2 -2
  17. package/dist/services/FetchService.d.ts +4 -24
  18. package/dist/services/PersistService.d.ts +9 -1
  19. package/dist/services/RelationService.d.ts +34 -1
  20. package/dist/services/channel-history.d.ts +118 -0
  21. package/dist/services/collection-helpers.d.ts +79 -14
  22. package/dist/services/dataService.d.ts +3 -1
  23. package/dist/services/index.d.ts +1 -1
  24. package/dist/services/realtimeService.d.ts +76 -2
  25. package/dist/services/row-pipeline.d.ts +63 -0
  26. package/package.json +12 -33
  27. package/src/PostgresBackendDriver.ts +183 -18
  28. package/src/PostgresBootstrapper.ts +80 -26
  29. package/src/auth/ensure-tables.ts +170 -28
  30. package/src/auth/services.ts +181 -150
  31. package/src/cli.ts +60 -0
  32. package/src/collections/buildRegistry.ts +59 -0
  33. package/src/connection.ts +61 -1
  34. package/src/data-transformer.ts +11 -9
  35. package/src/databasePoolManager.ts +2 -0
  36. package/src/schema/auth-bootstrap-sql.ts +7 -1
  37. package/src/schema/auth-schema.ts +13 -13
  38. package/src/schema/doctor.ts +45 -20
  39. package/src/schema/generate-drizzle-schema-logic.ts +24 -29
  40. package/src/schema/generate-postgres-ddl-logic.ts +76 -28
  41. package/src/schema/introspect-db-inference.ts +1 -1
  42. package/src/schema/introspect-db-logic.ts +1 -10
  43. package/src/schema/introspect-db-naming.ts +15 -0
  44. package/src/schema/introspect-db.ts +19 -2
  45. package/src/schema/introspect-runtime.ts +1 -1
  46. package/src/security/policy-drift.test.ts +106 -1
  47. package/src/security/policy-drift.ts +56 -0
  48. package/src/security/rls-enforcement.ts +11 -5
  49. package/src/services/BranchService.ts +42 -10
  50. package/src/services/FetchService.ts +65 -270
  51. package/src/services/PersistService.ts +62 -9
  52. package/src/services/RelationService.ts +153 -94
  53. package/src/services/channel-history.ts +343 -0
  54. package/src/services/collection-helpers.ts +164 -47
  55. package/src/services/dataService.ts +3 -2
  56. package/src/services/index.ts +1 -0
  57. package/src/services/realtimeService.ts +238 -29
  58. package/src/services/row-pipeline.ts +239 -0
  59. package/src/utils/drizzle-conditions.ts +13 -0
  60. package/src/websocket.ts +34 -12
  61. package/dist/schema/auth-default-policies.d.ts +0 -10
  62. package/src/schema/auth-default-policies.ts +0 -132
@@ -82,6 +82,33 @@ export class UserService implements UserRepository {
82
82
  return `"${schema}"."${name}"`;
83
83
  }
84
84
 
85
+ /**
86
+ * Run a privileged auth write with an explicitly cleared RLS context.
87
+ *
88
+ * The auth services run on the base/owner connection, which by design
89
+ * carries a NULL `app.uid` so the `auth.uid() IS NULL` server-escape
90
+ * in the default policies applies. That NULL is normally guaranteed by
91
+ * `set_config(..., is_local = true)` resetting at transaction end — but a
92
+ * GUC that survives on a pooled connection (or a connection role that
93
+ * doesn't bypass RLS: FORCE ROW LEVEL SECURITY, or a non-owner role)
94
+ * turns the trusted write into an RLS-scoped one and denies it with
95
+ * SQLSTATE 42501. Clearing the GUCs here, transaction-locally at the
96
+ * single chokepoint, makes the server context deterministic instead of
97
+ * trusting whatever state the pool hands us. `auth.uid()` reads '' as
98
+ * NULL via NULLIF, so '' is the server context.
99
+ */
100
+ private async withServerContext<T>(fn: (db: NodePgDatabase) => Promise<T>): Promise<T> {
101
+ return await this.db.transaction(async (tx) => {
102
+ await tx.execute(sql`
103
+ SELECT set_config('app.uid', '', true),
104
+ set_config('app.user_id', '', true),
105
+ set_config('app.user_roles', '', true),
106
+ set_config('app.jwt', '', true)
107
+ `);
108
+ return await fn(tx as unknown as NodePgDatabase);
109
+ });
110
+ }
111
+
85
112
  private mapRowToUser(row: Record<string, unknown>): UserData {
86
113
  if (!row) return row as UserData;
87
114
 
@@ -200,7 +227,9 @@ export class UserService implements UserRepository {
200
227
 
201
228
  async createUser(data: CreateUserData): Promise<UserData> {
202
229
  const payload = this.mapPayload(data);
203
- const [row] = (await this.db.insert(this.usersTable).values(payload).returning()) as Record<string, unknown>[];
230
+ const [row] = await this.withServerContext(async (db) =>
231
+ (await db.insert(this.usersTable).values(payload).returning()) as Record<string, unknown>[]
232
+ );
204
233
  return this.mapRowToUser(row);
205
234
  }
206
235
 
@@ -225,7 +254,7 @@ export class UserService implements UserRepository {
225
254
  const result = await this.db
226
255
  .select({ user: this.usersTable })
227
256
  .from(this.usersTable)
228
- .innerJoin(this.userIdentitiesTable, eq(userIdCol, this.userIdentitiesTable.userId))
257
+ .innerJoin(this.userIdentitiesTable, eq(userIdCol, this.userIdentitiesTable.uid))
229
258
  .where(
230
259
  sql`${this.userIdentitiesTable.provider} = ${provider} AND ${this.userIdentitiesTable.providerId} = ${providerId}`
231
260
  )
@@ -235,17 +264,17 @@ export class UserService implements UserRepository {
235
264
  return this.mapRowToUser(result[0].user as Record<string, unknown>);
236
265
  }
237
266
 
238
- async getUserIdentities(userId: string): Promise<UserIdentityData[]> {
267
+ async getUserIdentities(uid: string): Promise<UserIdentityData[]> {
239
268
  const schema = getTableConfig(this.userIdentitiesTable).schema || "public";
240
269
  const result = await this.db.execute(sql`
241
- SELECT id, user_id, provider, provider_id, profile_data, created_at, updated_at
270
+ SELECT id, uid, provider, provider_id, profile_data, created_at, updated_at
242
271
  FROM ${sql.raw(`"${schema}"."user_identities"`)}
243
- WHERE user_id = ${userId}
272
+ WHERE uid = ${uid}
244
273
  `);
245
274
 
246
275
  return result.rows.map((row: Record<string, unknown>) => ({
247
276
  id: row.id as string,
248
- userId: row.user_id as string,
277
+ uid: row.uid as string,
249
278
  provider: row.provider as string,
250
279
  providerId: row.provider_id as string,
251
280
  profileData: (row.profile_data as Record<string, unknown> | null) ?? null,
@@ -254,13 +283,13 @@ export class UserService implements UserRepository {
254
283
  }));
255
284
  }
256
285
 
257
- async linkUserIdentity(userId: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {
258
- await this.db.insert(this.userIdentitiesTable).values({
259
- userId,
286
+ async linkUserIdentity(uid: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {
287
+ await this.withServerContext(async (db) => db.insert(this.userIdentitiesTable).values({
288
+ uid,
260
289
  provider,
261
290
  providerId,
262
291
  profileData: profileData || null
263
- }).onConflictDoNothing({ target: [this.userIdentitiesTable.provider, this.userIdentitiesTable.providerId] });
292
+ }).onConflictDoNothing({ target: [this.userIdentitiesTable.provider, this.userIdentitiesTable.providerId] }));
264
293
  }
265
294
 
266
295
  async updateUser(id: string, data: Partial<Omit<CreateUserData, "id">>): Promise<UserData | null> {
@@ -270,18 +299,20 @@ export class UserService implements UserRepository {
270
299
  const updatedAtKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
271
300
  payload[updatedAtKey] = new Date();
272
301
 
273
- const [row] = (await this.db
274
- .update(this.usersTable)
275
- .set(payload)
276
- .where(eq(idCol, id))
277
- .returning()) as Record<string, unknown>[];
302
+ const [row] = await this.withServerContext(async (db) =>
303
+ (await db
304
+ .update(this.usersTable)
305
+ .set(payload)
306
+ .where(eq(idCol, id))
307
+ .returning()) as Record<string, unknown>[]
308
+ );
278
309
  return row ? this.mapRowToUser(row) : null;
279
310
  }
280
311
 
281
312
  async deleteUser(id: string): Promise<void> {
282
313
  const idCol = getColumn(this.usersTable, "id");
283
314
  if (!idCol) return;
284
- await this.db.delete(this.usersTable).where(eq(idCol, id));
315
+ await this.withServerContext(async (db) => db.delete(this.usersTable).where(eq(idCol, id)));
285
316
  }
286
317
 
287
318
  async listUsers(): Promise<UserData[]> {
@@ -357,13 +388,13 @@ export class UserService implements UserRepository {
357
388
  const passwordHashColKey = getColumnKey(this.usersTable, "passwordHash", "password_hash") || "passwordHash";
358
389
  const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
359
390
 
360
- await this.db
391
+ await this.withServerContext(async (db) => db
361
392
  .update(this.usersTable)
362
393
  .set({
363
394
  [passwordHashColKey]: passwordHash,
364
395
  [updatedAtColKey]: new Date()
365
396
  })
366
- .where(eq(idCol, id));
397
+ .where(eq(idCol, id)));
367
398
  }
368
399
 
369
400
  /**
@@ -376,14 +407,14 @@ export class UserService implements UserRepository {
376
407
  const emailVerificationTokenColKey = getColumnKey(this.usersTable, "emailVerificationToken", "email_verification_token") || "emailVerificationToken";
377
408
  const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
378
409
 
379
- await this.db
410
+ await this.withServerContext(async (db) => db
380
411
  .update(this.usersTable)
381
412
  .set({
382
413
  [emailVerifiedColKey]: verified,
383
414
  [emailVerificationTokenColKey]: null,
384
415
  [updatedAtColKey]: new Date()
385
416
  })
386
- .where(eq(idCol, id));
417
+ .where(eq(idCol, id)));
387
418
  }
388
419
 
389
420
  /**
@@ -396,14 +427,14 @@ export class UserService implements UserRepository {
396
427
  const emailVerificationSentAtColKey = getColumnKey(this.usersTable, "emailVerificationSentAt", "email_verification_sent_at") || "emailVerificationSentAt";
397
428
  const updatedAtColKey = getColumnKey(this.usersTable, "updatedAt", "updated_at") || "updatedAt";
398
429
 
399
- await this.db
430
+ await this.withServerContext(async (db) => db
400
431
  .update(this.usersTable)
401
432
  .set({
402
433
  [emailVerificationTokenColKey]: token,
403
434
  [emailVerificationSentAtColKey]: token ? new Date() : null,
404
435
  [updatedAtColKey]: new Date()
405
436
  })
406
- .where(eq(idCol, id));
437
+ .where(eq(idCol, id)));
407
438
  }
408
439
 
409
440
  /**
@@ -422,10 +453,10 @@ export class UserService implements UserRepository {
422
453
  /**
423
454
  * Get roles for a user from database (inline TEXT[] column)
424
455
  */
425
- async getUserRoles(userId: string): Promise<Role[]> {
456
+ async getUserRoles(uid: string): Promise<Role[]> {
426
457
  const usersTableName = this.getQualifiedUsersTableName();
427
458
  const result = await this.db.execute(sql`
428
- SELECT roles FROM ${sql.raw(usersTableName)} WHERE id = ${userId}
459
+ SELECT roles FROM ${sql.raw(usersTableName)} WHERE id = ${uid}
429
460
  `);
430
461
 
431
462
  if (result.rows.length === 0) return [];
@@ -445,10 +476,10 @@ export class UserService implements UserRepository {
445
476
  /**
446
477
  * Get role IDs for a user
447
478
  */
448
- async getUserRoleIds(userId: string): Promise<string[]> {
479
+ async getUserRoleIds(uid: string): Promise<string[]> {
449
480
  const usersTableName = this.getQualifiedUsersTableName();
450
481
  const result = await this.db.execute(sql`
451
- SELECT roles FROM ${sql.raw(usersTableName)} WHERE id = ${userId}
482
+ SELECT roles FROM ${sql.raw(usersTableName)} WHERE id = ${uid}
452
483
  `);
453
484
 
454
485
  if (result.rows.length === 0) return [];
@@ -460,36 +491,36 @@ export class UserService implements UserRepository {
460
491
  /**
461
492
  * Set roles for a user (replaces existing roles)
462
493
  */
463
- async setUserRoles(userId: string, roleIds: string[]): Promise<void> {
494
+ async setUserRoles(uid: string, roleIds: string[]): Promise<void> {
464
495
  const usersTableName = this.getQualifiedUsersTableName();
465
496
  const rolesArray = `{${roleIds.join(",")}}`;
466
- await this.db.execute(sql`
497
+ await this.withServerContext(async (db) => db.execute(sql`
467
498
  UPDATE ${sql.raw(usersTableName)}
468
499
  SET roles = ${rolesArray}::text[], updated_at = NOW()
469
- WHERE id = ${userId}
470
- `);
500
+ WHERE id = ${uid}
501
+ `));
471
502
  }
472
503
 
473
504
  /**
474
505
  * Assign a specific role to new user (appends if not present)
475
506
  */
476
- async assignDefaultRole(userId: string, roleId: string): Promise<void> {
507
+ async assignDefaultRole(uid: string, roleId: string): Promise<void> {
477
508
  const usersTableName = this.getQualifiedUsersTableName();
478
- await this.db.execute(sql`
509
+ await this.withServerContext(async (db) => db.execute(sql`
479
510
  UPDATE ${sql.raw(usersTableName)}
480
511
  SET roles = array_append(roles, ${roleId}), updated_at = NOW()
481
- WHERE id = ${userId} AND NOT (${roleId} = ANY(roles))
482
- `);
512
+ WHERE id = ${uid} AND NOT (${roleId} = ANY(roles))
513
+ `));
483
514
  }
484
515
 
485
516
  /**
486
517
  * Get user with their roles
487
518
  */
488
- async getUserWithRoles(userId: string): Promise<{ user: UserData; roles: Role[] } | null> {
489
- const user = await this.getUserById(userId);
519
+ async getUserWithRoles(uid: string): Promise<{ user: UserData; roles: Role[] } | null> {
520
+ const user = await this.getUserById(uid);
490
521
  if (!user) return null;
491
522
 
492
- const roles = await this.getUserRoles(userId);
523
+ const roles = await this.getUserRoles(uid);
493
524
  return { user,
494
525
  roles };
495
526
  }
@@ -510,20 +541,20 @@ export class RefreshTokenService {
510
541
  }
511
542
  }
512
543
 
513
- async createToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
544
+ async createToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
514
545
  // Fallback to empty string because UNIQUE constraints treat NULLs as strictly distinct in standard Postgres.
515
- // We want (userId, NULL, NULL) to collide and overwrite, so we map undefined/null to empty strings.
546
+ // We want (uid, NULL, NULL) to collide and overwrite, so we map undefined/null to empty strings.
516
547
  const safeUserAgent = userAgent || "";
517
548
  const safeIpAddress = ipAddress || "";
518
549
 
519
- // Atomic upsert keyed on the device session (user_id, user_agent, ip_address).
550
+ // Atomic upsert keyed on the device session (uid, user_agent, ip_address).
520
551
  // A DELETE-then-INSERT races under concurrent refreshes — cookie-mode boot can
521
552
  // fire two /refresh calls at once (both carrying the same refresh cookie): both
522
553
  // DELETE, then the second INSERT violates `unique_device_session` and 500s.
523
554
  // A single INSERT ... ON CONFLICT DO UPDATE rotates the token atomically.
524
555
  await this.db.insert(this.refreshTokensTable)
525
556
  .values({
526
- userId,
557
+ uid,
527
558
  tokenHash,
528
559
  expiresAt,
529
560
  userAgent: safeUserAgent,
@@ -531,7 +562,7 @@ export class RefreshTokenService {
531
562
  })
532
563
  .onConflictDoUpdate({
533
564
  target: [
534
- this.refreshTokensTable.userId,
565
+ this.refreshTokensTable.uid,
535
566
  this.refreshTokensTable.userAgent,
536
567
  this.refreshTokensTable.ipAddress
537
568
  ],
@@ -543,7 +574,7 @@ export class RefreshTokenService {
543
574
  const [token] = await this.db
544
575
  .select({
545
576
  id: this.refreshTokensTable.id,
546
- userId: this.refreshTokensTable.userId,
577
+ uid: this.refreshTokensTable.uid,
547
578
  tokenHash: this.refreshTokensTable.tokenHash,
548
579
  expiresAt: this.refreshTokensTable.expiresAt,
549
580
  createdAt: this.refreshTokensTable.createdAt,
@@ -560,15 +591,15 @@ export class RefreshTokenService {
560
591
  await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.tokenHash, tokenHash));
561
592
  }
562
593
 
563
- async deleteAllForUser(userId: string): Promise<void> {
564
- await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.userId, userId));
594
+ async deleteAllForUser(uid: string): Promise<void> {
595
+ await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.uid, uid));
565
596
  }
566
597
 
567
- async listForUser(userId: string): Promise<RefreshTokenInfo[]> {
598
+ async listForUser(uid: string): Promise<RefreshTokenInfo[]> {
568
599
  const tokens = await this.db
569
600
  .select({
570
601
  id: this.refreshTokensTable.id,
571
- userId: this.refreshTokensTable.userId,
602
+ uid: this.refreshTokensTable.uid,
572
603
  tokenHash: this.refreshTokensTable.tokenHash,
573
604
  expiresAt: this.refreshTokensTable.expiresAt,
574
605
  createdAt: this.refreshTokensTable.createdAt,
@@ -576,15 +607,15 @@ export class RefreshTokenService {
576
607
  ipAddress: this.refreshTokensTable.ipAddress
577
608
  })
578
609
  .from(this.refreshTokensTable)
579
- .where(eq(this.refreshTokensTable.userId, userId))
610
+ .where(eq(this.refreshTokensTable.uid, uid))
580
611
  .orderBy(this.refreshTokensTable.createdAt);
581
612
 
582
613
  return tokens as RefreshTokenInfo[];
583
614
  }
584
615
 
585
- async deleteById(id: string, userId: string): Promise<void> {
616
+ async deleteById(id: string, uid: string): Promise<void> {
586
617
  await this.db.delete(this.refreshTokensTable)
587
- .where(sql`${this.refreshTokensTable.id} = ${id} AND ${this.refreshTokensTable.userId} = ${userId}`);
618
+ .where(sql`${this.refreshTokensTable.id} = ${id} AND ${this.refreshTokensTable.uid} = ${uid}`);
588
619
  }
589
620
  }
590
621
 
@@ -614,16 +645,16 @@ export class PasswordResetTokenService {
614
645
  /**
615
646
  * Create a password reset token
616
647
  */
617
- async createToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
648
+ async createToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {
618
649
  // Delete any existing unused tokens for this user
619
650
  const tableName = this.getQualifiedPasswordResetTokensTableName();
620
651
  await this.db.execute(sql`
621
652
  DELETE FROM ${sql.raw(tableName)}
622
- WHERE user_id = ${userId} AND used_at IS NULL
653
+ WHERE uid = ${uid} AND used_at IS NULL
623
654
  `);
624
655
 
625
656
  await this.db.insert(this.passwordResetTokensTable).values({
626
- userId,
657
+ uid,
627
658
  tokenHash,
628
659
  expiresAt
629
660
  });
@@ -632,21 +663,21 @@ export class PasswordResetTokenService {
632
663
  /**
633
664
  * Find a valid (not expired, not used) token by hash
634
665
  */
635
- async findValidByHash(tokenHash: string): Promise<{ userId: string; expiresAt: Date } | null> {
666
+ async findValidByHash(tokenHash: string): Promise<{ uid: string; expiresAt: Date } | null> {
636
667
  const [token] = await this.db
637
668
  .select({
638
- userId: this.passwordResetTokensTable.userId,
669
+ uid: this.passwordResetTokensTable.uid,
639
670
  expiresAt: this.passwordResetTokensTable.expiresAt
640
671
  })
641
672
  .from(this.passwordResetTokensTable)
642
- .where(eq(this.passwordResetTokensTable.tokenHash, tokenHash)) as unknown as Array<{ userId: string; expiresAt: Date }>;
673
+ .where(eq(this.passwordResetTokensTable.tokenHash, tokenHash)) as unknown as Array<{ uid: string; expiresAt: Date }>;
643
674
 
644
675
  if (!token) return null;
645
676
 
646
677
  // Check if expired or used
647
678
  const tableName = this.getQualifiedPasswordResetTokensTableName();
648
679
  const result = await this.db.execute(sql`
649
- SELECT user_id, expires_at
680
+ SELECT uid, expires_at
650
681
  FROM ${sql.raw(tableName)}
651
682
  WHERE token_hash = ${tokenHash}
652
683
  AND used_at IS NULL
@@ -655,9 +686,9 @@ export class PasswordResetTokenService {
655
686
 
656
687
  if (result.rows.length === 0) return null;
657
688
 
658
- const row = result.rows[0] as { user_id: string; expires_at: string | number | Date };
689
+ const row = result.rows[0] as { uid: string; expires_at: string | number | Date };
659
690
  return {
660
- userId: row.user_id,
691
+ uid: row.uid,
661
692
  expiresAt: new Date(row.expires_at)
662
693
  };
663
694
  }
@@ -675,8 +706,8 @@ export class PasswordResetTokenService {
675
706
  /**
676
707
  * Delete all tokens for a user
677
708
  */
678
- async deleteAllForUser(userId: string): Promise<void> {
679
- await this.db.delete(this.passwordResetTokensTable).where(eq(this.passwordResetTokensTable.userId, userId));
709
+ async deleteAllForUser(uid: string): Promise<void> {
710
+ await this.db.delete(this.passwordResetTokensTable).where(eq(this.passwordResetTokensTable.uid, uid));
680
711
  }
681
712
 
682
713
  /**
@@ -711,16 +742,16 @@ export class MagicLinkTokenService {
711
742
  return `"${schema}"."${name}"`;
712
743
  }
713
744
 
714
- async createToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
745
+ async createToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {
715
746
  // Delete any existing unused tokens for this user
716
747
  const tableName = this.getQualifiedTableName();
717
748
  await this.db.execute(sql`
718
749
  DELETE FROM ${sql.raw(tableName)}
719
- WHERE user_id = ${userId} AND used_at IS NULL
750
+ WHERE uid = ${uid} AND used_at IS NULL
720
751
  `);
721
752
 
722
753
  await this.db.insert(this.magicLinkTokensTable).values({
723
- userId,
754
+ uid,
724
755
  tokenHash,
725
756
  expiresAt
726
757
  });
@@ -729,7 +760,7 @@ export class MagicLinkTokenService {
729
760
  async findValidByHash(tokenHash: string): Promise<MagicLinkTokenInfo | null> {
730
761
  const tableName = this.getQualifiedTableName();
731
762
  const result = await this.db.execute(sql`
732
- SELECT user_id, expires_at
763
+ SELECT uid, expires_at
733
764
  FROM ${sql.raw(tableName)}
734
765
  WHERE token_hash = ${tokenHash}
735
766
  AND used_at IS NULL
@@ -738,9 +769,9 @@ export class MagicLinkTokenService {
738
769
 
739
770
  if (result.rows.length === 0) return null;
740
771
 
741
- const row = result.rows[0] as { user_id: string; expires_at: string | number | Date };
772
+ const row = result.rows[0] as { uid: string; expires_at: string | number | Date };
742
773
  return {
743
- userId: row.user_id,
774
+ uid: row.uid,
744
775
  expiresAt: new Date(row.expires_at)
745
776
  };
746
777
  }
@@ -773,8 +804,8 @@ export class PostgresTokenRepository implements TokenRepository {
773
804
 
774
805
  // Refresh token operations
775
806
 
776
- async createRefreshToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
777
- await this.refreshTokenService.createToken(userId, tokenHash, expiresAt, userAgent, ipAddress);
807
+ async createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
808
+ await this.refreshTokenService.createToken(uid, tokenHash, expiresAt, userAgent, ipAddress);
778
809
  }
779
810
 
780
811
  async findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {
@@ -785,22 +816,22 @@ export class PostgresTokenRepository implements TokenRepository {
785
816
  await this.refreshTokenService.deleteByHash(tokenHash);
786
817
  }
787
818
 
788
- async deleteAllRefreshTokensForUser(userId: string): Promise<void> {
789
- await this.refreshTokenService.deleteAllForUser(userId);
819
+ async deleteAllRefreshTokensForUser(uid: string): Promise<void> {
820
+ await this.refreshTokenService.deleteAllForUser(uid);
790
821
  }
791
822
 
792
- async listRefreshTokensForUser(userId: string): Promise<RefreshTokenInfo[]> {
793
- return this.refreshTokenService.listForUser(userId);
823
+ async listRefreshTokensForUser(uid: string): Promise<RefreshTokenInfo[]> {
824
+ return this.refreshTokenService.listForUser(uid);
794
825
  }
795
826
 
796
- async deleteRefreshTokenById(id: string, userId: string): Promise<void> {
797
- await this.refreshTokenService.deleteById(id, userId);
827
+ async deleteRefreshTokenById(id: string, uid: string): Promise<void> {
828
+ await this.refreshTokenService.deleteById(id, uid);
798
829
  }
799
830
 
800
831
  // Password reset token operations
801
832
 
802
- async createPasswordResetToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
803
- await this.passwordResetTokenService.createToken(userId, tokenHash, expiresAt);
833
+ async createPasswordResetToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {
834
+ await this.passwordResetTokenService.createToken(uid, tokenHash, expiresAt);
804
835
  }
805
836
 
806
837
  async findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null> {
@@ -811,8 +842,8 @@ export class PostgresTokenRepository implements TokenRepository {
811
842
  await this.passwordResetTokenService.markAsUsed(tokenHash);
812
843
  }
813
844
 
814
- async deleteAllPasswordResetTokensForUser(userId: string): Promise<void> {
815
- await this.passwordResetTokenService.deleteAllForUser(userId);
845
+ async deleteAllPasswordResetTokensForUser(uid: string): Promise<void> {
846
+ await this.passwordResetTokenService.deleteAllForUser(uid);
816
847
  }
817
848
 
818
849
  async deleteExpiredTokens(): Promise<void> {
@@ -821,8 +852,8 @@ export class PostgresTokenRepository implements TokenRepository {
821
852
 
822
853
  // Magic link token operations
823
854
 
824
- async createMagicLinkToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
825
- await this.magicLinkTokenService.createToken(userId, tokenHash, expiresAt);
855
+ async createMagicLinkToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {
856
+ await this.magicLinkTokenService.createToken(uid, tokenHash, expiresAt);
826
857
  }
827
858
 
828
859
  async findValidMagicLinkToken(tokenHash: string): Promise<MagicLinkTokenInfo | null> {
@@ -869,12 +900,12 @@ export class PostgresAuthRepository implements AuthRepository {
869
900
  return this.userService.getUserByIdentity(provider, providerId);
870
901
  }
871
902
 
872
- async getUserIdentities(userId: string): Promise<UserIdentityData[]> {
873
- return this.userService.getUserIdentities(userId);
903
+ async getUserIdentities(uid: string): Promise<UserIdentityData[]> {
904
+ return this.userService.getUserIdentities(uid);
874
905
  }
875
906
 
876
- async linkUserIdentity(userId: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {
877
- return this.userService.linkUserIdentity(userId, provider, providerId, profileData);
907
+ async linkUserIdentity(uid: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void> {
908
+ return this.userService.linkUserIdentity(uid, provider, providerId, profileData);
878
909
  }
879
910
 
880
911
  async updateUser(id: string, data: Partial<Omit<CreateUserData, "id">>): Promise<UserData | null> {
@@ -909,24 +940,24 @@ export class PostgresAuthRepository implements AuthRepository {
909
940
  return this.userService.getUserByVerificationToken(token);
910
941
  }
911
942
 
912
- async getUserRoles(userId: string): Promise<RoleData[]> {
913
- return this.userService.getUserRoles(userId);
943
+ async getUserRoles(uid: string): Promise<RoleData[]> {
944
+ return this.userService.getUserRoles(uid);
914
945
  }
915
946
 
916
- async getUserRoleIds(userId: string): Promise<string[]> {
917
- return this.userService.getUserRoleIds(userId);
947
+ async getUserRoleIds(uid: string): Promise<string[]> {
948
+ return this.userService.getUserRoleIds(uid);
918
949
  }
919
950
 
920
- async setUserRoles(userId: string, roleIds: string[]): Promise<void> {
921
- await this.userService.setUserRoles(userId, roleIds);
951
+ async setUserRoles(uid: string, roleIds: string[]): Promise<void> {
952
+ await this.userService.setUserRoles(uid, roleIds);
922
953
  }
923
954
 
924
- async assignDefaultRole(userId: string, roleId: string): Promise<void> {
925
- await this.userService.assignDefaultRole(userId, roleId);
955
+ async assignDefaultRole(uid: string, roleId: string): Promise<void> {
956
+ await this.userService.assignDefaultRole(uid, roleId);
926
957
  }
927
958
 
928
- async getUserWithRoles(userId: string): Promise<{ user: UserData; roles: RoleData[] } | null> {
929
- return this.userService.getUserWithRoles(userId);
959
+ async getUserWithRoles(uid: string): Promise<{ user: UserData; roles: RoleData[] } | null> {
960
+ return this.userService.getUserWithRoles(uid);
930
961
  }
931
962
 
932
963
  // Role operations (roles are inline on users, synthesized from string IDs)
@@ -987,8 +1018,8 @@ collectionPermissions: null }
987
1018
 
988
1019
  // Token operations (delegate to PostgresTokenRepository)
989
1020
 
990
- async createRefreshToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
991
- await this.tokenRepository.createRefreshToken(userId, tokenHash, expiresAt, userAgent, ipAddress);
1021
+ async createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void> {
1022
+ await this.tokenRepository.createRefreshToken(uid, tokenHash, expiresAt, userAgent, ipAddress);
992
1023
  }
993
1024
 
994
1025
  async findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null> {
@@ -999,20 +1030,20 @@ collectionPermissions: null }
999
1030
  await this.tokenRepository.deleteRefreshToken(tokenHash);
1000
1031
  }
1001
1032
 
1002
- async deleteAllRefreshTokensForUser(userId: string): Promise<void> {
1003
- await this.tokenRepository.deleteAllRefreshTokensForUser(userId);
1033
+ async deleteAllRefreshTokensForUser(uid: string): Promise<void> {
1034
+ await this.tokenRepository.deleteAllRefreshTokensForUser(uid);
1004
1035
  }
1005
1036
 
1006
- async listRefreshTokensForUser(userId: string): Promise<RefreshTokenInfo[]> {
1007
- return this.tokenRepository.listRefreshTokensForUser(userId);
1037
+ async listRefreshTokensForUser(uid: string): Promise<RefreshTokenInfo[]> {
1038
+ return this.tokenRepository.listRefreshTokensForUser(uid);
1008
1039
  }
1009
1040
 
1010
- async deleteRefreshTokenById(id: string, userId: string): Promise<void> {
1011
- await this.tokenRepository.deleteRefreshTokenById(id, userId);
1041
+ async deleteRefreshTokenById(id: string, uid: string): Promise<void> {
1042
+ await this.tokenRepository.deleteRefreshTokenById(id, uid);
1012
1043
  }
1013
1044
 
1014
- async createPasswordResetToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
1015
- await this.tokenRepository.createPasswordResetToken(userId, tokenHash, expiresAt);
1045
+ async createPasswordResetToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {
1046
+ await this.tokenRepository.createPasswordResetToken(uid, tokenHash, expiresAt);
1016
1047
  }
1017
1048
 
1018
1049
  async findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null> {
@@ -1023,8 +1054,8 @@ collectionPermissions: null }
1023
1054
  await this.tokenRepository.markPasswordResetTokenUsed(tokenHash);
1024
1055
  }
1025
1056
 
1026
- async deleteAllPasswordResetTokensForUser(userId: string): Promise<void> {
1027
- await this.tokenRepository.deleteAllPasswordResetTokensForUser(userId);
1057
+ async deleteAllPasswordResetTokensForUser(uid: string): Promise<void> {
1058
+ await this.tokenRepository.deleteAllPasswordResetTokensForUser(uid);
1028
1059
  }
1029
1060
 
1030
1061
  async deleteExpiredTokens(): Promise<void> {
@@ -1033,8 +1064,8 @@ collectionPermissions: null }
1033
1064
 
1034
1065
  // Magic link token operations
1035
1066
 
1036
- async createMagicLinkToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void> {
1037
- await this.tokenRepository.createMagicLinkToken(userId, tokenHash, expiresAt);
1067
+ async createMagicLinkToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void> {
1068
+ await this.tokenRepository.createMagicLinkToken(uid, tokenHash, expiresAt);
1038
1069
  }
1039
1070
 
1040
1071
  async findValidMagicLinkToken(tokenHash: string): Promise<MagicLinkTokenInfo | null> {
@@ -1055,12 +1086,12 @@ collectionPermissions: null }
1055
1086
  return this._mfaService;
1056
1087
  }
1057
1088
 
1058
- async createMfaFactor(userId: string, factorType: "totp", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor> {
1059
- return this.getMfaService().createMfaFactor(userId, factorType, secretEncrypted, friendlyName);
1089
+ async createMfaFactor(uid: string, factorType: "totp", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor> {
1090
+ return this.getMfaService().createMfaFactor(uid, factorType, secretEncrypted, friendlyName);
1060
1091
  }
1061
1092
 
1062
- async getMfaFactors(userId: string): Promise<MfaFactor[]> {
1063
- return this.getMfaService().getMfaFactors(userId);
1093
+ async getMfaFactors(uid: string): Promise<MfaFactor[]> {
1094
+ return this.getMfaService().getMfaFactors(uid);
1064
1095
  }
1065
1096
 
1066
1097
  async getMfaFactorById(factorId: string): Promise<(MfaFactor & { secretEncrypted: string }) | null> {
@@ -1071,8 +1102,8 @@ collectionPermissions: null }
1071
1102
  return this.getMfaService().verifyMfaFactor(factorId);
1072
1103
  }
1073
1104
 
1074
- async deleteMfaFactor(factorId: string, userId: string): Promise<void> {
1075
- return this.getMfaService().deleteMfaFactor(factorId, userId);
1105
+ async deleteMfaFactor(factorId: string, uid: string): Promise<void> {
1106
+ return this.getMfaService().deleteMfaFactor(factorId, uid);
1076
1107
  }
1077
1108
 
1078
1109
  async createMfaChallenge(factorId: string, ipAddress?: string): Promise<MfaChallengeInfo> {
@@ -1087,24 +1118,24 @@ collectionPermissions: null }
1087
1118
  return this.getMfaService().verifyMfaChallenge(challengeId);
1088
1119
  }
1089
1120
 
1090
- async createRecoveryCodes(userId: string, codeHashes: string[]): Promise<void> {
1091
- return this.getMfaService().createRecoveryCodes(userId, codeHashes);
1121
+ async createRecoveryCodes(uid: string, codeHashes: string[]): Promise<void> {
1122
+ return this.getMfaService().createRecoveryCodes(uid, codeHashes);
1092
1123
  }
1093
1124
 
1094
- async useRecoveryCode(userId: string, codeHash: string): Promise<boolean> {
1095
- return this.getMfaService().useRecoveryCode(userId, codeHash);
1125
+ async useRecoveryCode(uid: string, codeHash: string): Promise<boolean> {
1126
+ return this.getMfaService().useRecoveryCode(uid, codeHash);
1096
1127
  }
1097
1128
 
1098
- async getUnusedRecoveryCodeCount(userId: string): Promise<number> {
1099
- return this.getMfaService().getUnusedRecoveryCodeCount(userId);
1129
+ async getUnusedRecoveryCodeCount(uid: string): Promise<number> {
1130
+ return this.getMfaService().getUnusedRecoveryCodeCount(uid);
1100
1131
  }
1101
1132
 
1102
- async deleteAllRecoveryCodes(userId: string): Promise<void> {
1103
- return this.getMfaService().deleteAllRecoveryCodes(userId);
1133
+ async deleteAllRecoveryCodes(uid: string): Promise<void> {
1134
+ return this.getMfaService().deleteAllRecoveryCodes(uid);
1104
1135
  }
1105
1136
 
1106
- async hasVerifiedMfaFactors(userId: string): Promise<boolean> {
1107
- return this.getMfaService().hasVerifiedMfaFactors(userId);
1137
+ async hasVerifiedMfaFactors(uid: string): Promise<boolean> {
1138
+ return this.getMfaService().hasVerifiedMfaFactors(uid);
1108
1139
  }
1109
1140
  }
1110
1141
 
@@ -1124,22 +1155,22 @@ export class MfaService implements MfaRepository {
1124
1155
  }
1125
1156
 
1126
1157
  async createMfaFactor(
1127
- userId: string,
1158
+ uid: string,
1128
1159
  factorType: "totp",
1129
1160
  secretEncrypted: string,
1130
1161
  friendlyName?: string
1131
1162
  ): Promise<MfaFactor> {
1132
1163
  const tableName = this.qualify("mfa_factors");
1133
1164
  const result = await this.db.execute(sql`
1134
- INSERT INTO ${sql.raw(tableName)} (user_id, factor_type, secret_encrypted, friendly_name)
1135
- VALUES (${userId}, ${factorType}, ${secretEncrypted}, ${friendlyName ?? null})
1136
- RETURNING id, user_id, factor_type, friendly_name, verified, created_at, updated_at
1165
+ INSERT INTO ${sql.raw(tableName)} (uid, factor_type, secret_encrypted, friendly_name)
1166
+ VALUES (${uid}, ${factorType}, ${secretEncrypted}, ${friendlyName ?? null})
1167
+ RETURNING id, uid, factor_type, friendly_name, verified, created_at, updated_at
1137
1168
  `);
1138
1169
 
1139
1170
  const row = result.rows[0] as Record<string, unknown>;
1140
1171
  return {
1141
1172
  id: row.id as string,
1142
- userId: row.user_id as string,
1173
+ uid: row.uid as string,
1143
1174
  factorType: row.factor_type as "totp",
1144
1175
  friendlyName: (row.friendly_name as string | null) ?? undefined,
1145
1176
  verified: row.verified as boolean,
@@ -1148,18 +1179,18 @@ export class MfaService implements MfaRepository {
1148
1179
  };
1149
1180
  }
1150
1181
 
1151
- async getMfaFactors(userId: string): Promise<MfaFactor[]> {
1182
+ async getMfaFactors(uid: string): Promise<MfaFactor[]> {
1152
1183
  const tableName = this.qualify("mfa_factors");
1153
1184
  const result = await this.db.execute(sql`
1154
- SELECT id, user_id, factor_type, friendly_name, verified, created_at, updated_at
1185
+ SELECT id, uid, factor_type, friendly_name, verified, created_at, updated_at
1155
1186
  FROM ${sql.raw(tableName)}
1156
- WHERE user_id = ${userId}
1187
+ WHERE uid = ${uid}
1157
1188
  ORDER BY created_at
1158
1189
  `);
1159
1190
 
1160
1191
  return (result.rows as Array<Record<string, unknown>>).map(row => ({
1161
1192
  id: row.id as string,
1162
- userId: row.user_id as string,
1193
+ uid: row.uid as string,
1163
1194
  factorType: row.factor_type as "totp",
1164
1195
  friendlyName: (row.friendly_name as string | null) ?? undefined,
1165
1196
  verified: row.verified as boolean,
@@ -1171,7 +1202,7 @@ export class MfaService implements MfaRepository {
1171
1202
  async getMfaFactorById(factorId: string): Promise<(MfaFactor & { secretEncrypted: string }) | null> {
1172
1203
  const tableName = this.qualify("mfa_factors");
1173
1204
  const result = await this.db.execute(sql`
1174
- SELECT id, user_id, factor_type, secret_encrypted, friendly_name, verified, created_at, updated_at
1205
+ SELECT id, uid, factor_type, secret_encrypted, friendly_name, verified, created_at, updated_at
1175
1206
  FROM ${sql.raw(tableName)}
1176
1207
  WHERE id = ${factorId}
1177
1208
  `);
@@ -1181,7 +1212,7 @@ export class MfaService implements MfaRepository {
1181
1212
  const row = result.rows[0] as Record<string, unknown>;
1182
1213
  return {
1183
1214
  id: row.id as string,
1184
- userId: row.user_id as string,
1215
+ uid: row.uid as string,
1185
1216
  factorType: row.factor_type as "totp",
1186
1217
  secretEncrypted: row.secret_encrypted as string,
1187
1218
  friendlyName: (row.friendly_name as string | null) ?? undefined,
@@ -1200,11 +1231,11 @@ export class MfaService implements MfaRepository {
1200
1231
  `);
1201
1232
  }
1202
1233
 
1203
- async deleteMfaFactor(factorId: string, userId: string): Promise<void> {
1234
+ async deleteMfaFactor(factorId: string, uid: string): Promise<void> {
1204
1235
  const tableName = this.qualify("mfa_factors");
1205
1236
  await this.db.execute(sql`
1206
1237
  DELETE FROM ${sql.raw(tableName)}
1207
- WHERE id = ${factorId} AND user_id = ${userId}
1238
+ WHERE id = ${factorId} AND uid = ${uid}
1208
1239
  `);
1209
1240
  }
1210
1241
 
@@ -1257,56 +1288,56 @@ export class MfaService implements MfaRepository {
1257
1288
  `);
1258
1289
  }
1259
1290
 
1260
- async createRecoveryCodes(userId: string, codeHashes: string[]): Promise<void> {
1291
+ async createRecoveryCodes(uid: string, codeHashes: string[]): Promise<void> {
1261
1292
  const tableName = this.qualify("recovery_codes");
1262
1293
  // Delete existing codes first
1263
1294
  await this.db.execute(sql`
1264
- DELETE FROM ${sql.raw(tableName)} WHERE user_id = ${userId}
1295
+ DELETE FROM ${sql.raw(tableName)} WHERE uid = ${uid}
1265
1296
  `);
1266
1297
 
1267
1298
  // Insert new codes
1268
1299
  for (const hash of codeHashes) {
1269
1300
  await this.db.execute(sql`
1270
- INSERT INTO ${sql.raw(tableName)} (user_id, code_hash)
1271
- VALUES (${userId}, ${hash})
1301
+ INSERT INTO ${sql.raw(tableName)} (uid, code_hash)
1302
+ VALUES (${uid}, ${hash})
1272
1303
  `);
1273
1304
  }
1274
1305
  }
1275
1306
 
1276
- async useRecoveryCode(userId: string, codeHash: string): Promise<boolean> {
1307
+ async useRecoveryCode(uid: string, codeHash: string): Promise<boolean> {
1277
1308
  const tableName = this.qualify("recovery_codes");
1278
1309
  const result = await this.db.execute(sql`
1279
1310
  UPDATE ${sql.raw(tableName)}
1280
1311
  SET used_at = NOW()
1281
- WHERE user_id = ${userId} AND code_hash = ${codeHash} AND used_at IS NULL
1312
+ WHERE uid = ${uid} AND code_hash = ${codeHash} AND used_at IS NULL
1282
1313
  RETURNING id
1283
1314
  `);
1284
1315
 
1285
1316
  return result.rows.length > 0;
1286
1317
  }
1287
1318
 
1288
- async getUnusedRecoveryCodeCount(userId: string): Promise<number> {
1319
+ async getUnusedRecoveryCodeCount(uid: string): Promise<number> {
1289
1320
  const tableName = this.qualify("recovery_codes");
1290
1321
  const result = await this.db.execute(sql`
1291
1322
  SELECT COUNT(*)::int as count FROM ${sql.raw(tableName)}
1292
- WHERE user_id = ${userId} AND used_at IS NULL
1323
+ WHERE uid = ${uid} AND used_at IS NULL
1293
1324
  `);
1294
1325
 
1295
1326
  return (result.rows[0] as { count: number }).count;
1296
1327
  }
1297
1328
 
1298
- async deleteAllRecoveryCodes(userId: string): Promise<void> {
1329
+ async deleteAllRecoveryCodes(uid: string): Promise<void> {
1299
1330
  const tableName = this.qualify("recovery_codes");
1300
1331
  await this.db.execute(sql`
1301
- DELETE FROM ${sql.raw(tableName)} WHERE user_id = ${userId}
1332
+ DELETE FROM ${sql.raw(tableName)} WHERE uid = ${uid}
1302
1333
  `);
1303
1334
  }
1304
1335
 
1305
- async hasVerifiedMfaFactors(userId: string): Promise<boolean> {
1336
+ async hasVerifiedMfaFactors(uid: string): Promise<boolean> {
1306
1337
  const tableName = this.qualify("mfa_factors");
1307
1338
  const result = await this.db.execute(sql`
1308
1339
  SELECT COUNT(*)::int as count FROM ${sql.raw(tableName)}
1309
- WHERE user_id = ${userId} AND verified = TRUE
1340
+ WHERE uid = ${uid} AND verified = TRUE
1310
1341
  `);
1311
1342
 
1312
1343
  return (result.rows[0] as { count: number }).count > 0;