@rebasepro/server-postgresql 0.3.0 → 0.5.0

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 (60) hide show
  1. package/README.md +69 -89
  2. package/dist/common/src/collections/default-collections.d.ts +5 -8
  3. package/dist/common/src/data/query_builder.d.ts +6 -2
  4. package/dist/common/src/util/permissions.d.ts +14 -6
  5. package/dist/index.es.js +379 -611
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +375 -607
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +2 -0
  10. package/dist/server-postgresql/src/auth/ensure-tables.d.ts +7 -4
  11. package/dist/server-postgresql/src/auth/services.d.ts +17 -42
  12. package/dist/server-postgresql/src/data-transformer.d.ts +0 -3
  13. package/dist/server-postgresql/src/databasePoolManager.d.ts +1 -1
  14. package/dist/server-postgresql/src/schema/auth-schema.d.ts +87 -340
  15. package/dist/server-postgresql/src/services/EntityFetchService.d.ts +2 -1
  16. package/dist/server-postgresql/src/services/EntityPersistService.d.ts +4 -0
  17. package/dist/server-postgresql/src/services/entityService.d.ts +4 -0
  18. package/dist/server-postgresql/src/types.d.ts +3 -0
  19. package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +5 -1
  20. package/dist/server-postgresql/src/websocket.d.ts +8 -3
  21. package/dist/types/src/controllers/auth.d.ts +2 -2
  22. package/dist/types/src/controllers/client.d.ts +25 -40
  23. package/dist/types/src/controllers/data.d.ts +21 -3
  24. package/dist/types/src/controllers/data_driver.d.ts +5 -0
  25. package/dist/types/src/controllers/email.d.ts +2 -0
  26. package/dist/types/src/types/auth_adapter.d.ts +3 -56
  27. package/dist/types/src/types/backend.d.ts +38 -3
  28. package/dist/types/src/types/backend_hooks.d.ts +2 -17
  29. package/dist/types/src/types/collections.d.ts +30 -6
  30. package/dist/types/src/types/entity_views.d.ts +19 -28
  31. package/dist/types/src/types/properties.d.ts +9 -15
  32. package/dist/types/src/types/user_management_delegate.d.ts +16 -53
  33. package/dist/types/src/users/index.d.ts +0 -1
  34. package/dist/types/src/users/user.d.ts +0 -1
  35. package/package.json +6 -6
  36. package/src/PostgresBackendDriver.ts +10 -0
  37. package/src/PostgresBootstrapper.ts +27 -22
  38. package/src/auth/ensure-tables.ts +82 -129
  39. package/src/auth/services.ts +99 -197
  40. package/src/cli.ts +50 -23
  41. package/src/data-transformer.ts +57 -95
  42. package/src/databasePoolManager.ts +2 -1
  43. package/src/schema/auth-schema.ts +13 -69
  44. package/src/schema/doctor.ts +44 -3
  45. package/src/schema/generate-drizzle-schema-logic.ts +33 -3
  46. package/src/schema/generate-drizzle-schema.ts +2 -6
  47. package/src/schema/introspect-db-logic.ts +7 -0
  48. package/src/services/EntityFetchService.ts +13 -1
  49. package/src/services/EntityPersistService.ts +38 -12
  50. package/src/services/entityService.ts +7 -0
  51. package/src/types.ts +4 -0
  52. package/src/utils/drizzle-conditions.ts +40 -5
  53. package/src/websocket.ts +38 -25
  54. package/test/auth-services.test.ts +7 -150
  55. package/test/doctor.test.ts +6 -2
  56. package/test/relation-pipeline-gaps.test.ts +315 -0
  57. package/dist/server-postgresql/src/schema/default-collections.d.ts +0 -2
  58. package/dist/types/src/users/roles.d.ts +0 -14
  59. package/drizzle.test.config.ts +0 -10
  60. package/src/schema/default-collections.ts +0 -69
@@ -1,7 +1,8 @@
1
1
  import { eq, getTableName, sql } from "drizzle-orm";
2
2
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
3
- import { getTableConfig, PgTable, AnyPgColumn } from "drizzle-orm/pg-core";
4
- import { users, roles, userRoles, refreshTokens, passwordResetTokens, userIdentities } from "../schema/auth-schema";
3
+ import { getTableConfig } from "drizzle-orm/pg-core";
4
+ import type { RebasePgTable } from "../types";
5
+ import { users, refreshTokens, passwordResetTokens, userIdentities } from "../schema/auth-schema";
5
6
  import {
6
7
  UserRepository,
7
8
  RoleRepository,
@@ -27,16 +28,14 @@ import { toSnakeCase, camelCase } from "@rebasepro/utils";
27
28
  export type { Role };
28
29
 
29
30
  export interface AuthSchemaTables {
30
- users: PgTable & Record<string, AnyPgColumn>;
31
- roles: PgTable & Record<string, AnyPgColumn>;
32
- userRoles: PgTable & Record<string, AnyPgColumn>;
33
- refreshTokens: PgTable & Record<string, AnyPgColumn>;
34
- passwordResetTokens: PgTable & Record<string, AnyPgColumn>;
35
- appConfig: PgTable & Record<string, AnyPgColumn>;
36
- userIdentities: PgTable & Record<string, AnyPgColumn>;
31
+ users: RebasePgTable;
32
+ refreshTokens: RebasePgTable;
33
+ passwordResetTokens: RebasePgTable;
34
+ appConfig: RebasePgTable;
35
+ userIdentities: RebasePgTable;
37
36
  }
38
37
 
39
- function getColumnKey(table: (PgTable & Record<string, AnyPgColumn>) | undefined, ...keys: string[]): string | undefined {
38
+ function getColumnKey(table: RebasePgTable | undefined, ...keys: string[]): string | undefined {
40
39
  if (!table) return undefined;
41
40
  for (const key of keys) {
42
41
  if (key in table) return key;
@@ -48,7 +47,7 @@ function getColumnKey(table: (PgTable & Record<string, AnyPgColumn>) | undefined
48
47
  return undefined;
49
48
  }
50
49
 
51
- function getColumn(table: (PgTable & Record<string, AnyPgColumn>) | undefined, ...keys: string[]): AnyPgColumn | undefined {
50
+ function getColumn(table: RebasePgTable | undefined, ...keys: string[]): RebasePgTable[string] | undefined {
52
51
  if (!table) return undefined;
53
52
  const key = getColumnKey(table, ...keys);
54
53
  return key ? table[key] : undefined;
@@ -59,27 +58,21 @@ function getColumn(table: (PgTable & Record<string, AnyPgColumn>) | undefined, .
59
58
  * Handles all user-related database operations using Drizzle ORM.
60
59
  */
61
60
  export class UserService implements UserRepository {
62
- private usersTable: PgTable & Record<string, AnyPgColumn>;
63
- private userIdentitiesTable: PgTable & Record<string, AnyPgColumn>;
64
- private userRolesTable: PgTable & Record<string, AnyPgColumn>;
65
- private rolesTable: PgTable & Record<string, AnyPgColumn>;
61
+ private usersTable: RebasePgTable;
62
+ private userIdentitiesTable: RebasePgTable;
66
63
 
67
64
  constructor(
68
65
  private db: NodePgDatabase,
69
- tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>
66
+ tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>
70
67
  ) {
71
- if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).users || (tableOrTables as Partial<AuthSchemaTables>).roles)) {
68
+ if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).users)) {
72
69
  const tables = tableOrTables as Partial<AuthSchemaTables>;
73
- this.usersTable = (tables.users || users) as unknown as PgTable & Record<string, AnyPgColumn>;
74
- this.userIdentitiesTable = (tables.userIdentities || userIdentities) as unknown as PgTable & Record<string, AnyPgColumn>;
75
- this.userRolesTable = (tables.userRoles || userRoles) as unknown as PgTable & Record<string, AnyPgColumn>;
76
- this.rolesTable = (tables.roles || roles) as unknown as PgTable & Record<string, AnyPgColumn>;
70
+ this.usersTable = (tables.users || users) as RebasePgTable;
71
+ this.userIdentitiesTable = (tables.userIdentities || userIdentities) as RebasePgTable;
77
72
  } else {
78
- const table = tableOrTables as (PgTable & Record<string, AnyPgColumn>) | undefined;
79
- this.usersTable = table || (users as unknown as PgTable & Record<string, AnyPgColumn>);
80
- this.userIdentitiesTable = userIdentities as unknown as PgTable & Record<string, AnyPgColumn>;
81
- this.userRolesTable = userRoles as unknown as PgTable & Record<string, AnyPgColumn>;
82
- this.rolesTable = roles as unknown as PgTable & Record<string, AnyPgColumn>;
73
+ const table = tableOrTables as RebasePgTable | undefined;
74
+ this.usersTable = table || (users as unknown as RebasePgTable);
75
+ this.userIdentitiesTable = userIdentities as unknown as RebasePgTable;
83
76
  }
84
77
  }
85
78
 
@@ -90,7 +83,7 @@ export class UserService implements UserRepository {
90
83
  }
91
84
 
92
85
  private mapRowToUser(row: Record<string, unknown>): UserData {
93
- if (!row) return row as unknown as UserData;
86
+ if (!row) return row as UserData;
94
87
 
95
88
  const id = (row.id ?? row.uid) as string;
96
89
  const email = row.email as string;
@@ -115,6 +108,7 @@ export class UserService implements UserRepository {
115
108
  "email_verification_token", "emailVerificationToken",
116
109
  "email_verification_sent_at", "emailVerificationSentAt",
117
110
  "is_anonymous", "isAnonymous",
111
+ "roles",
118
112
  "created_at", "createdAt",
119
113
  "updated_at", "updatedAt",
120
114
  "metadata"
@@ -315,11 +309,9 @@ export class UserService implements UserRepository {
315
309
  const idColumn = idCol ? idCol.name : "id";
316
310
 
317
311
  const usersTableName = this.getQualifiedUsersTableName();
318
- const rolesSchema = getTableConfig(this.userRolesTable).schema || "public";
319
-
320
312
  const conditions = [];
321
313
  if (roleId) {
322
- 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})`);
314
+ conditions.push(sql`${roleId} = ANY(${sql.raw(usersTableName)}.roles)`);
323
315
  }
324
316
  if (search) {
325
317
  const pattern = `%${search}%`;
@@ -331,7 +323,7 @@ export class UserService implements UserRepository {
331
323
  // Sorting: users with roles first if no role filter, then by requested order
332
324
  const orderByClause = roleId
333
325
  ? sql`ORDER BY ${sql.raw(usersTableName)}.${sql.raw(orderColumn)} ${direction}`
334
- : 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}`;
326
+ : sql`ORDER BY array_length(${sql.raw(usersTableName)}.roles, 1) DESC NULLS LAST, ${sql.raw(usersTableName)}.${sql.raw(orderColumn)} ${direction}`;
335
327
 
336
328
  const countResult = await this.db.execute(sql`
337
329
  SELECT count(*)::int as total FROM ${sql.raw(usersTableName)}
@@ -428,23 +420,25 @@ export class UserService implements UserRepository {
428
420
  }
429
421
 
430
422
  /**
431
- * Get roles for a user from database
423
+ * Get roles for a user from database (inline TEXT[] column)
432
424
  */
433
425
  async getUserRoles(userId: string): Promise<Role[]> {
434
- const rolesSchema = getTableConfig(this.rolesTable).schema || "public";
426
+ const usersTableName = this.getQualifiedUsersTableName();
435
427
  const result = await this.db.execute(sql`
436
- SELECT r.id, r.name, r.is_admin, r.default_permissions, r.collection_permissions
437
- FROM ${sql.raw(`"${rolesSchema}"."roles"`)} r
438
- INNER JOIN ${sql.raw(`"${rolesSchema}"."user_roles"`)} ur ON r.id = ur.role_id
439
- WHERE ur.user_id = ${userId}
428
+ SELECT roles FROM ${sql.raw(usersTableName)} WHERE id = ${userId}
440
429
  `);
441
430
 
442
- return (result.rows as Array<{ id: string; name: string; is_admin: boolean; default_permissions: Record<string, boolean> | null; collection_permissions: Record<string, Record<string, boolean>> | null }>).map(row => ({
443
- id: row.id,
444
- name: row.name,
445
- isAdmin: row.is_admin,
446
- defaultPermissions: row.default_permissions,
447
- collectionPermissions: row.collection_permissions
431
+ if (result.rows.length === 0) return [];
432
+
433
+ const row = result.rows[0] as { roles: string[] | null };
434
+ const roleIds = row.roles ?? [];
435
+
436
+ return roleIds.map(id => ({
437
+ id,
438
+ name: id,
439
+ isAdmin: id === "admin",
440
+ defaultPermissions: null,
441
+ collectionPermissions: null
448
442
  }));
449
443
  }
450
444
 
@@ -452,37 +446,39 @@ export class UserService implements UserRepository {
452
446
  * Get role IDs for a user
453
447
  */
454
448
  async getUserRoleIds(userId: string): Promise<string[]> {
455
- const roles = await this.getUserRoles(userId);
456
- return roles.map(r => r.id);
449
+ const usersTableName = this.getQualifiedUsersTableName();
450
+ const result = await this.db.execute(sql`
451
+ SELECT roles FROM ${sql.raw(usersTableName)} WHERE id = ${userId}
452
+ `);
453
+
454
+ if (result.rows.length === 0) return [];
455
+
456
+ const row = result.rows[0] as { roles: string[] | null };
457
+ return row.roles ?? [];
457
458
  }
458
459
 
459
460
  /**
460
- * Set roles for a user
461
+ * Set roles for a user (replaces existing roles)
461
462
  */
462
463
  async setUserRoles(userId: string, roleIds: string[]): Promise<void> {
463
- const rolesSchema = getTableConfig(this.userRolesTable).schema || "public";
464
- // Delete existing roles
465
- await this.db.execute(sql`DELETE FROM ${sql.raw(`"${rolesSchema}"."user_roles"`)} WHERE user_id = ${userId}`);
466
-
467
- // Insert new roles
468
- for (const roleId of roleIds) {
469
- await this.db.execute(sql`
470
- INSERT INTO ${sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
471
- VALUES (${userId}, ${roleId})
472
- ON CONFLICT DO NOTHING
473
- `);
474
- }
464
+ const usersTableName = this.getQualifiedUsersTableName();
465
+ const rolesArray = `{${roleIds.join(",")}}`;
466
+ await this.db.execute(sql`
467
+ UPDATE ${sql.raw(usersTableName)}
468
+ SET roles = ${rolesArray}::text[], updated_at = NOW()
469
+ WHERE id = ${userId}
470
+ `);
475
471
  }
476
472
 
477
473
  /**
478
- * Assign a specific role to new user
474
+ * Assign a specific role to new user (appends if not present)
479
475
  */
480
476
  async assignDefaultRole(userId: string, roleId: string): Promise<void> {
481
- const rolesSchema = getTableConfig(this.userRolesTable).schema || "public";
477
+ const usersTableName = this.getQualifiedUsersTableName();
482
478
  await this.db.execute(sql`
483
- INSERT INTO ${sql.raw(`"${rolesSchema}"."user_roles"`)} (user_id, role_id)
484
- VALUES (${userId}, ${roleId})
485
- ON CONFLICT DO NOTHING
479
+ UPDATE ${sql.raw(usersTableName)}
480
+ SET roles = array_append(roles, ${roleId}), updated_at = NOW()
481
+ WHERE id = ${userId} AND NOT (${roleId} = ANY(roles))
486
482
  `);
487
483
  }
488
484
 
@@ -499,126 +495,19 @@ export class UserService implements UserRepository {
499
495
  }
500
496
  }
501
497
 
502
- /**
503
- * PostgreSQL implementation of RoleRepository.
504
- * Handles all role-related database operations using Drizzle ORM.
505
- */
506
- export class RoleService implements RoleRepository {
507
- private rolesTable: PgTable & Record<string, AnyPgColumn>;
508
-
509
- constructor(
510
- private db: NodePgDatabase,
511
- tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>
512
- ) {
513
- if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).roles || (tableOrTables as Partial<AuthSchemaTables>).users)) {
514
- this.rolesTable = ((tableOrTables as Partial<AuthSchemaTables>).roles || roles) as unknown as PgTable & Record<string, AnyPgColumn>;
515
- } else {
516
- this.rolesTable = (tableOrTables as unknown as PgTable & Record<string, AnyPgColumn>) || (roles as unknown as PgTable & Record<string, AnyPgColumn>);
517
- }
518
- }
519
-
520
- private getQualifiedRolesTableName(): string {
521
- const name = getTableName(this.rolesTable);
522
- const schema = getTableConfig(this.rolesTable).schema || "public";
523
- return `"${schema}"."${name}"`;
524
- }
525
-
526
- async getRoleById(id: string): Promise<Role | null> {
527
- const tableName = this.getQualifiedRolesTableName();
528
- const result = await this.db.execute(sql`
529
- SELECT id, name, is_admin, default_permissions, collection_permissions
530
- FROM ${sql.raw(tableName)}
531
- WHERE id = ${id}
532
- `);
533
-
534
- if (result.rows.length === 0) return null;
535
498
 
536
- const row = result.rows[0] as { id: string; name: string; is_admin: boolean; default_permissions: Record<string, boolean> | null; collection_permissions: Record<string, Record<string, boolean>> | null };
537
- return {
538
- id: row.id,
539
- name: row.name,
540
- isAdmin: row.is_admin,
541
- defaultPermissions: row.default_permissions,
542
- collectionPermissions: row.collection_permissions
543
- };
544
- }
545
-
546
- async listRoles(): Promise<Role[]> {
547
- const tableName = this.getQualifiedRolesTableName();
548
- const result = await this.db.execute(sql`
549
- SELECT id, name, is_admin, default_permissions, collection_permissions
550
- FROM ${sql.raw(tableName)}
551
- ORDER BY name
552
- `);
553
-
554
- return (result.rows as Array<{ id: string; name: string; is_admin: boolean; default_permissions: Record<string, boolean> | null; collection_permissions: Record<string, Record<string, boolean>> | null }>).map(row => ({
555
- id: row.id,
556
- name: row.name,
557
- isAdmin: row.is_admin,
558
- defaultPermissions: row.default_permissions,
559
- collectionPermissions: row.collection_permissions
560
- }));
561
- }
562
-
563
- async createRole(data: Omit<Role, "isAdmin" | "collectionPermissions"> & { isAdmin?: boolean; collectionPermissions?: Role["collectionPermissions"] }): Promise<Role> {
564
- const tableName = this.getQualifiedRolesTableName();
565
- const result = await this.db.execute(sql`
566
- INSERT INTO ${sql.raw(tableName)} (id, name, is_admin, default_permissions, collection_permissions)
567
- VALUES (
568
- ${data.id},
569
- ${data.name},
570
- ${data.isAdmin ?? false},
571
- ${data.defaultPermissions ? JSON.stringify(data.defaultPermissions) : null}::jsonb,
572
- ${data.collectionPermissions ? JSON.stringify(data.collectionPermissions) : null}::jsonb
573
- )
574
- RETURNING id, name, is_admin, default_permissions, collection_permissions
575
- `);
576
-
577
- const row = result.rows[0] as { id: string; name: string; is_admin: boolean; default_permissions: Record<string, boolean> | null; collection_permissions: Record<string, Record<string, boolean>> | null };
578
- return {
579
- id: row.id,
580
- name: row.name,
581
- isAdmin: row.is_admin,
582
- defaultPermissions: row.default_permissions,
583
- collectionPermissions: row.collection_permissions
584
- };
585
- }
586
-
587
- async updateRole(id: string, data: Partial<Omit<Role, "id">>): Promise<Role | null> {
588
- const existing = await this.getRoleById(id);
589
- if (!existing) return null;
590
-
591
- const tableName = this.getQualifiedRolesTableName();
592
- await this.db.execute(sql`
593
- UPDATE ${sql.raw(tableName)}
594
- SET
595
- name = ${data.name ?? existing.name},
596
- is_admin = ${data.isAdmin ?? existing.isAdmin},
597
- default_permissions = ${data.defaultPermissions ? JSON.stringify(data.defaultPermissions) : JSON.stringify(existing.defaultPermissions)}::jsonb,
598
- collection_permissions = ${data.collectionPermissions !== undefined ? (data.collectionPermissions ? JSON.stringify(data.collectionPermissions) : null) : (existing.collectionPermissions ? JSON.stringify(existing.collectionPermissions) : null)}::jsonb
599
- WHERE id = ${id}
600
- `);
601
-
602
- return this.getRoleById(id);
603
- }
604
-
605
- async deleteRole(id: string): Promise<void> {
606
- const tableName = this.getQualifiedRolesTableName();
607
- await this.db.execute(sql`DELETE FROM ${sql.raw(tableName)} WHERE id = ${id}`);
608
- }
609
- }
610
499
 
611
500
  export class RefreshTokenService {
612
- private refreshTokensTable: PgTable & Record<string, AnyPgColumn>;
501
+ private refreshTokensTable: RebasePgTable;
613
502
 
614
503
  constructor(
615
504
  private db: NodePgDatabase,
616
- tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>
505
+ tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>
617
506
  ) {
618
507
  if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).refreshTokens || (tableOrTables as Partial<AuthSchemaTables>).users)) {
619
- this.refreshTokensTable = ((tableOrTables as Partial<AuthSchemaTables>).refreshTokens || refreshTokens) as unknown as PgTable & Record<string, AnyPgColumn>;
508
+ this.refreshTokensTable = ((tableOrTables as Partial<AuthSchemaTables>).refreshTokens || refreshTokens) as RebasePgTable;
620
509
  } else {
621
- this.refreshTokensTable = (tableOrTables as unknown as PgTable & Record<string, AnyPgColumn>) || (refreshTokens as unknown as PgTable & Record<string, AnyPgColumn>);
510
+ this.refreshTokensTable = (tableOrTables as RebasePgTable) || (refreshTokens as unknown as RebasePgTable);
622
511
  }
623
512
  }
624
513
 
@@ -707,16 +596,16 @@ export class RefreshTokenService {
707
596
  * Password reset token service
708
597
  */
709
598
  export class PasswordResetTokenService {
710
- private passwordResetTokensTable: PgTable & Record<string, AnyPgColumn>;
599
+ private passwordResetTokensTable: RebasePgTable;
711
600
 
712
601
  constructor(
713
602
  private db: NodePgDatabase,
714
- tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>
603
+ tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>
715
604
  ) {
716
605
  if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).passwordResetTokens || (tableOrTables as Partial<AuthSchemaTables>).users)) {
717
- this.passwordResetTokensTable = ((tableOrTables as Partial<AuthSchemaTables>).passwordResetTokens || passwordResetTokens) as unknown as PgTable & Record<string, AnyPgColumn>;
606
+ this.passwordResetTokensTable = ((tableOrTables as Partial<AuthSchemaTables>).passwordResetTokens || passwordResetTokens) as RebasePgTable;
718
607
  } else {
719
- this.passwordResetTokensTable = (tableOrTables as unknown as PgTable & Record<string, AnyPgColumn>) || (passwordResetTokens as unknown as PgTable & Record<string, AnyPgColumn>);
608
+ this.passwordResetTokensTable = (tableOrTables as RebasePgTable) || (passwordResetTokens as unknown as RebasePgTable);
720
609
  }
721
610
  }
722
611
 
@@ -816,7 +705,7 @@ export class PostgresTokenRepository implements TokenRepository {
816
705
 
817
706
  constructor(
818
707
  private db: NodePgDatabase,
819
- tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>
708
+ tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>
820
709
  ) {
821
710
  this.refreshTokenService = new RefreshTokenService(db, tableOrTables);
822
711
  this.passwordResetTokenService = new PasswordResetTokenService(db, tableOrTables);
@@ -878,15 +767,13 @@ export class PostgresTokenRepository implements TokenRepository {
878
767
  */
879
768
  export class PostgresAuthRepository implements AuthRepository {
880
769
  private userService: UserService;
881
- private roleService: RoleService;
882
770
  private tokenRepository: PostgresTokenRepository;
883
771
 
884
772
  constructor(
885
773
  private db: NodePgDatabase,
886
- tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>
774
+ tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>
887
775
  ) {
888
776
  this.userService = new UserService(db, tableOrTables);
889
- this.roleService = new RoleService(db, tableOrTables);
890
777
  this.tokenRepository = new PostgresTokenRepository(db, tableOrTables);
891
778
  }
892
779
 
@@ -968,30 +855,48 @@ export class PostgresAuthRepository implements AuthRepository {
968
855
  return this.userService.getUserWithRoles(userId);
969
856
  }
970
857
 
971
- // Role operations (delegate to RoleService)
858
+ // Role operations (roles are inline on users, synthesized from string IDs)
972
859
 
973
860
  async getRoleById(id: string): Promise<RoleData | null> {
974
- return this.roleService.getRoleById(id);
861
+ return {
862
+ id,
863
+ name: id,
864
+ isAdmin: id === "admin",
865
+ defaultPermissions: null,
866
+ collectionPermissions: null
867
+ };
975
868
  }
976
869
 
977
870
  async listRoles(): Promise<RoleData[]> {
978
- return this.roleService.listRoles();
871
+ return [
872
+ { id: "admin", name: "Admin", isAdmin: true, defaultPermissions: null, collectionPermissions: null },
873
+ { id: "editor", name: "Editor", isAdmin: false, defaultPermissions: null, collectionPermissions: null },
874
+ { id: "viewer", name: "Viewer", isAdmin: false, defaultPermissions: null, collectionPermissions: null }
875
+ ];
979
876
  }
980
877
 
981
- async createRole(data: CreateRoleData): Promise<RoleData> {
982
- return this.roleService.createRole({
983
- ...data,
984
- defaultPermissions: data.defaultPermissions ?? null,
985
- collectionPermissions: data.collectionPermissions ?? null
986
- });
878
+ async createRole(_data: CreateRoleData): Promise<RoleData> {
879
+ return {
880
+ id: _data.id,
881
+ name: _data.name,
882
+ isAdmin: _data.isAdmin ?? false,
883
+ defaultPermissions: _data.defaultPermissions ?? null,
884
+ collectionPermissions: _data.collectionPermissions ?? null
885
+ };
987
886
  }
988
887
 
989
888
  async updateRole(id: string, data: Partial<Omit<RoleData, "id">>): Promise<RoleData | null> {
990
- return this.roleService.updateRole(id, data);
889
+ return {
890
+ id,
891
+ name: data.name ?? id,
892
+ isAdmin: data.isAdmin ?? (id === "admin"),
893
+ defaultPermissions: data.defaultPermissions ?? null,
894
+ collectionPermissions: data.collectionPermissions ?? null
895
+ };
991
896
  }
992
897
 
993
- async deleteRole(id: string): Promise<void> {
994
- await this.roleService.deleteRole(id);
898
+ async deleteRole(_id: string): Promise<void> {
899
+ // No-op: roles are inline strings on users
995
900
  }
996
901
 
997
902
  // Token operations (delegate to PostgresTokenRepository)
@@ -1314,6 +1219,3 @@ export class MfaService implements MfaRepository {
1314
1219
 
1315
1220
  /** PostgreSQL user repository implementation */
1316
1221
  export type PostgresUserRepository = UserService;
1317
-
1318
- /** PostgreSQL role repository implementation */
1319
- export type PostgresRoleRepository = RoleService;
package/src/cli.ts CHANGED
@@ -436,15 +436,28 @@ async function runDrizzleKit(action: string, _rawArgs: string[]): Promise<void>
436
436
  // dotenv may not be available — fall through
437
437
  }
438
438
 
439
- const interactive = ["generate", "push"].includes(action);
439
+ const interactive = ["generate", "push"].includes(action) && Boolean(process.stdout.isTTY);
440
440
 
441
441
  // For push: always use --strict (prompts before destructive ops) and --verbose
442
442
  // (shows all SQL). This ensures unmapped tables are never silently dropped.
443
443
  const drizzleKitArgs = [action];
444
444
  if (action === "push") {
445
445
  drizzleKitArgs.push("--strict", "--verbose");
446
- if (_rawArgs.includes("--force")) {
447
- drizzleKitArgs.push("--force");
446
+ }
447
+
448
+ // Forward any additional arguments, excluding schema-generator-specific options
449
+ const excludedFlags = ["--collections", "-c", "--output", "-o", "--watch", "-w"];
450
+ for (let i = 2; i < _rawArgs.length; i++) {
451
+ const arg = _rawArgs[i];
452
+ if (excludedFlags.includes(arg)) {
453
+ // Skip this flag and its value if it takes a parameter
454
+ if (["--collections", "-c", "--output", "-o"].includes(arg)) {
455
+ i++; // Skip the next arg (the value)
456
+ }
457
+ continue;
458
+ }
459
+ if (!drizzleKitArgs.includes(arg)) {
460
+ drizzleKitArgs.push(arg);
448
461
  }
449
462
  }
450
463
 
@@ -456,7 +469,7 @@ async function runDrizzleKit(action: string, _rawArgs: string[]): Promise<void>
456
469
  env
457
470
  });
458
471
  } else {
459
- const child = execa(drizzleKitBin, [action], {
472
+ const child = execa(drizzleKitBin, drizzleKitArgs, {
460
473
  cwd: process.cwd(),
461
474
  env,
462
475
  reject: false
@@ -473,20 +486,28 @@ async function runDrizzleKit(action: string, _rawArgs: string[]): Promise<void>
473
486
  const stdout = stripAnsi(result.stdout || "").trim();
474
487
  const stderr = stripAnsi(result.stderr || "").trim();
475
488
 
476
- if (result.exitCode !== 0) {
489
+ const hasTtyError = stdout.includes("Interactive prompts require a TTY terminal") ||
490
+ stderr.includes("Interactive prompts require a TTY terminal");
491
+
492
+ if (result.exitCode !== 0 || hasTtyError) {
477
493
  console.error(chalk.red(`\n✗ drizzle-kit ${action} failed.\n`));
478
- const errorOutput = stderr || stdout;
479
- if (errorOutput) {
480
- const lines = errorOutput.split("\n").filter((l: string) => l.trim());
481
- let printedCount = 0;
482
- for (const line of lines) {
483
- if (line.toLowerCase().includes("error") || line.includes("cannot") || line.includes("already exists") || line.includes("does not exist") || line.includes("violates") || line.includes("permission denied")) {
484
- console.error(chalk.red(` ${line.trim()}`));
485
- printedCount++;
494
+ if (hasTtyError) {
495
+ console.error(chalk.red(" Error: Interactive prompts require a TTY terminal."));
496
+ console.error(chalk.gray(" Please run with --force to skip interactive prompts or run in an interactive terminal."));
497
+ } else {
498
+ const errorOutput = stderr || stdout;
499
+ if (errorOutput) {
500
+ const lines = errorOutput.split("\n").filter((l: string) => l.trim());
501
+ let printedCount = 0;
502
+ for (const line of lines) {
503
+ if (line.toLowerCase().includes("error") || line.includes("cannot") || line.includes("already exists") || line.includes("does not exist") || line.includes("violates") || line.includes("permission denied")) {
504
+ console.error(chalk.red(` ${line.trim()}`));
505
+ printedCount++;
506
+ }
507
+ }
508
+ if (printedCount === 0) {
509
+ lines.slice(0, 10).forEach(line => console.error(chalk.red(` ${line.trim()}`)));
486
510
  }
487
- }
488
- if (printedCount === 0) {
489
- lines.slice(0, 10).forEach(line => console.error(chalk.red(` ${line.trim()}`)));
490
511
  }
491
512
  }
492
513
  console.error("");
@@ -498,15 +519,21 @@ async function runDrizzleKit(action: string, _rawArgs: string[]): Promise<void>
498
519
  // eslint-disable-next-line no-control-regex
499
520
  const stripAnsi = (s: string) => s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\[?[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⣷⣯⣟⡿⢿⣻⣽]+\]\s*/g, "");
500
521
  const cleaned = stripAnsi(msg).trim();
522
+ const hasTtyError = cleaned.includes("Interactive prompts require a TTY terminal");
501
523
  console.error(chalk.red(`\n✗ drizzle-kit ${action} failed.\n`));
502
- const lines = cleaned.split("\n").filter((l: string) => l.trim());
503
- for (const line of lines) {
504
- if (line.toLowerCase().includes("error") || line.includes("cannot") || line.includes("already exists") || line.includes("does not exist") || line.includes("violates")) {
505
- console.error(chalk.red(` ${line.trim()}`));
524
+ if (hasTtyError) {
525
+ console.error(chalk.red(" Error: Interactive prompts require a TTY terminal."));
526
+ console.error(chalk.gray(" Please run with --force to skip interactive prompts or run in an interactive terminal."));
527
+ } else {
528
+ const lines = cleaned.split("\n").filter((l: string) => l.trim());
529
+ for (const line of lines) {
530
+ if (line.toLowerCase().includes("error") || line.includes("cannot") || line.includes("already exists") || line.includes("does not exist") || line.includes("violates")) {
531
+ console.error(chalk.red(` ${line.trim()}`));
532
+ }
533
+ }
534
+ if (lines.length === 0) {
535
+ console.error(chalk.gray(` ${cleaned}`));
506
536
  }
507
- }
508
- if (lines.length === 0) {
509
- console.error(chalk.gray(` ${cleaned}`));
510
537
  }
511
538
  console.error("");
512
539
  process.exit(1);