@rebasepro/server-postgresql 0.4.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.
@@ -1,13 +1,13 @@
1
1
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
2
- import { PgTable, AnyPgColumn } from "drizzle-orm/pg-core";
2
+ import type { RebasePgTable } from "../types";
3
3
  import { UserRepository, TokenRepository, MfaRepository, AuthRepository, UserData, CreateUserData, RoleData, CreateRoleData, RefreshTokenInfo, PasswordResetTokenInfo, UserIdentityData, ListUsersOptions, PaginatedUsersResult, MfaFactor, MfaChallengeInfo, RoleData as Role } from "@rebasepro/server-core";
4
4
  export type { Role };
5
5
  export interface AuthSchemaTables {
6
- users: PgTable & Record<string, AnyPgColumn>;
7
- refreshTokens: PgTable & Record<string, AnyPgColumn>;
8
- passwordResetTokens: PgTable & Record<string, AnyPgColumn>;
9
- appConfig: PgTable & Record<string, AnyPgColumn>;
10
- userIdentities: PgTable & Record<string, AnyPgColumn>;
6
+ users: RebasePgTable;
7
+ refreshTokens: RebasePgTable;
8
+ passwordResetTokens: RebasePgTable;
9
+ appConfig: RebasePgTable;
10
+ userIdentities: RebasePgTable;
11
11
  }
12
12
  /**
13
13
  * PostgreSQL implementation of UserRepository.
@@ -17,7 +17,7 @@ export declare class UserService implements UserRepository {
17
17
  private db;
18
18
  private usersTable;
19
19
  private userIdentitiesTable;
20
- constructor(db: NodePgDatabase, tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>);
20
+ constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
21
21
  private getQualifiedUsersTableName;
22
22
  private mapRowToUser;
23
23
  private mapPayload;
@@ -74,7 +74,7 @@ export declare class UserService implements UserRepository {
74
74
  export declare class RefreshTokenService {
75
75
  private db;
76
76
  private refreshTokensTable;
77
- constructor(db: NodePgDatabase, tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>);
77
+ constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
78
78
  private getQualifiedRefreshTokensTableName;
79
79
  createToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
80
80
  findByHash(tokenHash: string): Promise<RefreshTokenInfo | null>;
@@ -89,7 +89,7 @@ export declare class RefreshTokenService {
89
89
  export declare class PasswordResetTokenService {
90
90
  private db;
91
91
  private passwordResetTokensTable;
92
- constructor(db: NodePgDatabase, tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>);
92
+ constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
93
93
  private getQualifiedPasswordResetTokensTableName;
94
94
  /**
95
95
  * Create a password reset token
@@ -123,7 +123,7 @@ export declare class PostgresTokenRepository implements TokenRepository {
123
123
  private db;
124
124
  private refreshTokenService;
125
125
  private passwordResetTokenService;
126
- constructor(db: NodePgDatabase, tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>);
126
+ constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
127
127
  createRefreshToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
128
128
  findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null>;
129
129
  deleteRefreshToken(tokenHash: string): Promise<void>;
@@ -145,7 +145,7 @@ export declare class PostgresAuthRepository implements AuthRepository {
145
145
  private db;
146
146
  private userService;
147
147
  private tokenRepository;
148
- constructor(db: NodePgDatabase, tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>);
148
+ constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
149
149
  createUser(data: CreateUserData): Promise<UserData>;
150
150
  getUserById(id: string): Promise<UserData | null>;
151
151
  getUserByEmail(email: string): Promise<UserData | null>;
@@ -42,9 +42,6 @@ export declare function serializePropertyToServer(value: unknown, property: Prop
42
42
  * Transform IDs back to relation objects for frontend
43
43
  */
44
44
  export declare function parseDataFromServer<M extends Record<string, unknown>>(data: M, collection: EntityCollection, db?: NodePgDatabase<Record<string, unknown>>, registry?: PostgresCollectionRegistry): Promise<M>;
45
- /**
46
- * Parse a single property value from database format to frontend format
47
- */
48
45
  export declare function parsePropertyFromServer(value: unknown, property: Property, collection: EntityCollection, propertyKey?: string): unknown;
49
46
  /**
50
47
  * Lightweight value normalization for db.query results.
@@ -6,7 +6,7 @@ export declare class DatabasePoolManager {
6
6
  readonly defaultDatabaseName: string;
7
7
  private readonly rootConnectionString;
8
8
  constructor(adminConnectionString: string);
9
- getDrizzle(databaseName: string): NodePgDatabase<any>;
9
+ getDrizzle(databaseName: string): NodePgDatabase<Record<string, never>>;
10
10
  getPool(databaseName: string): Pool;
11
11
  /**
12
12
  * Disconnect and remove the pool for a specific database.
@@ -0,0 +1,3 @@
1
+ import type { PgTable, AnyPgColumn } from "drizzle-orm/pg-core";
2
+ /** Drizzle PgTable with column access by name. Runtime Drizzle tables satisfy this shape. */
3
+ export type RebasePgTable = PgTable & Record<string, AnyPgColumn>;
@@ -1,6 +1,11 @@
1
1
  import { RealtimeService } from "./services/realtimeService";
2
2
  import { PostgresBackendDriver } from "./PostgresBackendDriver";
3
- import { AuthAdapter } from "@rebasepro/types";
3
+ import type { AuthAdapter } from "@rebasepro/types";
4
4
  import { Server } from "http";
5
- import { AuthConfig } from "@rebasepro/server-core";
6
- export declare function createPostgresWebSocket(server: Server, realtimeService: RealtimeService, driver: PostgresBackendDriver, authConfig?: AuthConfig, authAdapter?: AuthAdapter): void;
5
+ /** Minimal subset of RebaseAuthConfig used by the WebSocket layer. */
6
+ interface WsAuthConfig {
7
+ requireAuth?: boolean;
8
+ jwtSecret?: string;
9
+ }
10
+ export declare function createPostgresWebSocket(server: Server, realtimeService: RealtimeService, driver: PostgresBackendDriver, authConfig?: WsAuthConfig, authAdapter?: AuthAdapter): void;
11
+ export {};
@@ -1,5 +1,6 @@
1
1
  import type { Entity } from "./entities";
2
2
  import type { EntityCollection, FilterValues, WhereFilterOp } from "./collections";
3
+ import type { AuthAdapter } from "./auth_adapter";
3
4
  /**
4
5
  * Abstract database connection interface.
5
6
  * Represents a connection to any database system.
@@ -182,6 +183,24 @@ export interface RealtimeProvider {
182
183
  * Notify all relevant subscribers of an entity update
183
184
  */
184
185
  notifyEntityUpdate(path: string, entityId: string, entity: Entity | null, databaseId?: string): Promise<void>;
186
+ /**
187
+ * Called when the HTTP server is ready and listening.
188
+ * Useful for providers that need the server address for callbacks.
189
+ */
190
+ onServerReady?(serverInfo: {
191
+ port: number;
192
+ hostname?: string;
193
+ }): void;
194
+ /**
195
+ * Gracefully shut down the realtime provider.
196
+ * Called during server shutdown to clean up resources.
197
+ */
198
+ destroy?(): Promise<void>;
199
+ /**
200
+ * Stop the internal LISTEN client (e.g., PostgreSQL LISTEN/NOTIFY).
201
+ * Called during graceful shutdown before closing database connections.
202
+ */
203
+ stopListening?(): Promise<void>;
185
204
  }
186
205
  /**
187
206
  * Abstract collection registry interface.
@@ -464,6 +483,22 @@ export interface BackendBootstrapper {
464
483
  * (e.g., `"postgres"`, `"mongodb"`, `"mysql"`).
465
484
  */
466
485
  type: string;
486
+ /**
487
+ * Unique identifier for this bootstrapper instance.
488
+ * Used to register the driver in the driver registry.
489
+ * Defaults to `type` if not set.
490
+ */
491
+ id?: string;
492
+ /**
493
+ * Whether this bootstrapper provides the default driver.
494
+ * When true, the coordinator uses this driver as the primary one.
495
+ */
496
+ isDefault?: boolean;
497
+ /**
498
+ * Run database migrations for this driver.
499
+ * Called by the coordinator after all drivers are initialized.
500
+ */
501
+ runMigrations?(config: unknown, driverResult: InitializedDriver): Promise<void>;
467
502
  /**
468
503
  * Create a DataDriver from the given config.
469
504
  * This is the only **required** method.
@@ -498,7 +533,7 @@ export interface BackendBootstrapper {
498
533
  /**
499
534
  * Initialize WebSocket server for realtime operations.
500
535
  */
501
- initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: import("../controllers/data_driver").DataDriver, config?: unknown): Promise<void> | void;
536
+ initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: import("../controllers/data_driver").DataDriver, config?: unknown, authAdapter?: AuthAdapter): Promise<void> | void;
502
537
  }
503
538
  /**
504
539
  * Result of `BackendBootstrapper.initializeDriver()`.
@@ -343,6 +343,23 @@ export interface BaseEntityCollection<M extends Record<string, unknown> = Record
343
343
  * Builder for the collection actions rendered in the toolbar
344
344
  */
345
345
  Actions?: ComponentRef<CollectionActionsProps>[];
346
+ /**
347
+ * The database table name for this collection.
348
+ * Automatically set for PostgreSQL collections.
349
+ * For non-SQL backends, this may be undefined.
350
+ */
351
+ table?: string;
352
+ /**
353
+ * Relations defined for this collection.
354
+ * Populated at normalization time from inline relation properties
355
+ * or explicit relation definitions.
356
+ */
357
+ relations?: Relation[];
358
+ /**
359
+ * Security rules for this collection (Row Level Security).
360
+ * When defined, the backend enforces access control policies.
361
+ */
362
+ securityRules?: SecurityRule[];
346
363
  }
347
364
  /**
348
365
  * A collection backed by PostgreSQL (or any SQL database).
@@ -436,7 +453,10 @@ export interface MongoDBCollection<M extends Record<string, unknown> = Record<st
436
453
  * @group Models
437
454
  */
438
455
  export type EntityCollection<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User> = PostgresCollection<M, USER> | FirebaseCollection<M, USER> | MongoDBCollection<M, USER>;
439
- /** An EntityCollection that supports SQL-style relations (e.g. Postgres). */
456
+ /**
457
+ * An EntityCollection that supports SQL-style relations (e.g. Postgres).
458
+ * @deprecated Use `EntityCollection` directly — `table`, `relations`, and `securityRules` are now on `BaseEntityCollection`.
459
+ */
440
460
  export type CollectionWithRelations<M extends Record<string, unknown> = Record<string, unknown>> = EntityCollection<M> & {
441
461
  table?: string;
442
462
  relations?: Relation[];
@@ -641,14 +641,6 @@ export interface MapProperty extends BaseProperty {
641
641
  * Properties that are displayed when rendered as a preview
642
642
  */
643
643
  previewProperties?: string[];
644
- /**
645
- * Allow the user to add only some keys in this map.
646
- * By default, all properties of the map have the corresponding field in
647
- * the form view. Setting this flag to true allows to pick only some.
648
- * Useful for map that can have a lot of sub-properties that may not be
649
- * needed
650
- */
651
- pickOnlySomeKeys?: boolean;
652
644
  /**
653
645
  * Render this map as a key-value table that allows to use
654
646
  * arbitrary keys. You don't need to define the properties in this case.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/server-postgresql",
3
3
  "type": "module",
4
- "version": "0.4.0",
4
+ "version": "0.5.0",
5
5
  "description": "PostgreSQL data source backend implementation for Rebase with Drizzle ORM",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -68,11 +68,11 @@
68
68
  "hono": "^4.12.21",
69
69
  "pg": "^8.21.0",
70
70
  "ws": "^8.20.1",
71
- "@rebasepro/sdk-generator": "0.4.0",
72
- "@rebasepro/server-core": "0.4.0",
73
- "@rebasepro/types": "0.4.0",
74
- "@rebasepro/common": "0.4.0",
75
- "@rebasepro/utils": "0.4.0"
71
+ "@rebasepro/sdk-generator": "0.5.0",
72
+ "@rebasepro/server-core": "0.5.0",
73
+ "@rebasepro/types": "0.5.0",
74
+ "@rebasepro/utils": "0.5.0",
75
+ "@rebasepro/common": "0.5.0"
76
76
  },
77
77
  "devDependencies": {
78
78
  "@types/jest": "^29.5.14",
@@ -1079,7 +1079,7 @@ roles: this.user?.roles ?? [] };
1079
1079
  }
1080
1080
 
1081
1081
  async deleteAll(path: string): Promise<void> {
1082
- return this.delegate.deleteAll(path);
1082
+ return this.withTransaction((delegate) => delegate.deleteAll(path));
1083
1083
  }
1084
1084
 
1085
1085
  async checkUniqueField(
@@ -6,7 +6,8 @@
6
6
 
7
7
  import { getTableName, isTable, Relations, sql, Table } from "drizzle-orm";
8
8
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
9
- import { PgEnum, PgTable, getTableConfig, AnyPgColumn } from "drizzle-orm/pg-core";
9
+ import { PgEnum, PgTable, getTableConfig } from "drizzle-orm/pg-core";
10
+ import type { RebasePgTable } from "./types";
10
11
  import {
11
12
  BackendBootstrapper,
12
13
  InitializedDriver,
@@ -207,7 +208,7 @@ export function createPostgresBootstrapper(pgConfig: PostgresDriverConfig): Back
207
208
  : authCollection.slug)
208
209
  : undefined;
209
210
  const usersTable = tableName
210
- ? registry.getTable(tableName) as (PgTable & Record<string, AnyPgColumn>) | undefined
211
+ ? registry.getTable(tableName) as RebasePgTable | undefined
211
212
  : undefined;
212
213
 
213
214
  let usersSchemaName = "rebase";
@@ -217,7 +218,7 @@ export function createPostgresBootstrapper(pgConfig: PostgresDriverConfig): Back
217
218
 
218
219
  const authTables = createAuthSchema(usersSchemaName) as unknown as AuthSchemaTables;
219
220
  if (usersTable) {
220
- authTables.users = usersTable as unknown as PgTable & Record<string, AnyPgColumn>;
221
+ authTables.users = usersTable as RebasePgTable;
221
222
  }
222
223
 
223
224
  const userService = new UserService(db, authTables);
@@ -1,6 +1,7 @@
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";
3
+ import { getTableConfig } from "drizzle-orm/pg-core";
4
+ import type { RebasePgTable } from "../types";
4
5
  import { users, refreshTokens, passwordResetTokens, userIdentities } from "../schema/auth-schema";
5
6
  import {
6
7
  UserRepository,
@@ -27,14 +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
- refreshTokens: PgTable & Record<string, AnyPgColumn>;
32
- passwordResetTokens: PgTable & Record<string, AnyPgColumn>;
33
- appConfig: PgTable & Record<string, AnyPgColumn>;
34
- userIdentities: PgTable & Record<string, AnyPgColumn>;
31
+ users: RebasePgTable;
32
+ refreshTokens: RebasePgTable;
33
+ passwordResetTokens: RebasePgTable;
34
+ appConfig: RebasePgTable;
35
+ userIdentities: RebasePgTable;
35
36
  }
36
37
 
37
- function getColumnKey(table: (PgTable & Record<string, AnyPgColumn>) | undefined, ...keys: string[]): string | undefined {
38
+ function getColumnKey(table: RebasePgTable | undefined, ...keys: string[]): string | undefined {
38
39
  if (!table) return undefined;
39
40
  for (const key of keys) {
40
41
  if (key in table) return key;
@@ -46,7 +47,7 @@ function getColumnKey(table: (PgTable & Record<string, AnyPgColumn>) | undefined
46
47
  return undefined;
47
48
  }
48
49
 
49
- function getColumn(table: (PgTable & Record<string, AnyPgColumn>) | undefined, ...keys: string[]): AnyPgColumn | undefined {
50
+ function getColumn(table: RebasePgTable | undefined, ...keys: string[]): RebasePgTable[string] | undefined {
50
51
  if (!table) return undefined;
51
52
  const key = getColumnKey(table, ...keys);
52
53
  return key ? table[key] : undefined;
@@ -57,21 +58,21 @@ function getColumn(table: (PgTable & Record<string, AnyPgColumn>) | undefined, .
57
58
  * Handles all user-related database operations using Drizzle ORM.
58
59
  */
59
60
  export class UserService implements UserRepository {
60
- private usersTable: PgTable & Record<string, AnyPgColumn>;
61
- private userIdentitiesTable: PgTable & Record<string, AnyPgColumn>;
61
+ private usersTable: RebasePgTable;
62
+ private userIdentitiesTable: RebasePgTable;
62
63
 
63
64
  constructor(
64
65
  private db: NodePgDatabase,
65
- tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>
66
+ tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>
66
67
  ) {
67
68
  if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).users)) {
68
69
  const tables = tableOrTables as Partial<AuthSchemaTables>;
69
- this.usersTable = (tables.users || users) as unknown as PgTable & Record<string, AnyPgColumn>;
70
- this.userIdentitiesTable = (tables.userIdentities || userIdentities) as unknown as PgTable & Record<string, AnyPgColumn>;
70
+ this.usersTable = (tables.users || users) as RebasePgTable;
71
+ this.userIdentitiesTable = (tables.userIdentities || userIdentities) as RebasePgTable;
71
72
  } else {
72
- const table = tableOrTables as (PgTable & Record<string, AnyPgColumn>) | undefined;
73
- this.usersTable = table || (users as unknown as PgTable & Record<string, AnyPgColumn>);
74
- this.userIdentitiesTable = userIdentities 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;
75
76
  }
76
77
  }
77
78
 
@@ -82,7 +83,7 @@ export class UserService implements UserRepository {
82
83
  }
83
84
 
84
85
  private mapRowToUser(row: Record<string, unknown>): UserData {
85
- if (!row) return row as unknown as UserData;
86
+ if (!row) return row as UserData;
86
87
 
87
88
  const id = (row.id ?? row.uid) as string;
88
89
  const email = row.email as string;
@@ -497,16 +498,16 @@ export class UserService implements UserRepository {
497
498
 
498
499
 
499
500
  export class RefreshTokenService {
500
- private refreshTokensTable: PgTable & Record<string, AnyPgColumn>;
501
+ private refreshTokensTable: RebasePgTable;
501
502
 
502
503
  constructor(
503
504
  private db: NodePgDatabase,
504
- tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>
505
+ tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>
505
506
  ) {
506
507
  if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).refreshTokens || (tableOrTables as Partial<AuthSchemaTables>).users)) {
507
- 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;
508
509
  } else {
509
- 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);
510
511
  }
511
512
  }
512
513
 
@@ -595,16 +596,16 @@ export class RefreshTokenService {
595
596
  * Password reset token service
596
597
  */
597
598
  export class PasswordResetTokenService {
598
- private passwordResetTokensTable: PgTable & Record<string, AnyPgColumn>;
599
+ private passwordResetTokensTable: RebasePgTable;
599
600
 
600
601
  constructor(
601
602
  private db: NodePgDatabase,
602
- tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>
603
+ tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>
603
604
  ) {
604
605
  if (tableOrTables && ((tableOrTables as Partial<AuthSchemaTables>).passwordResetTokens || (tableOrTables as Partial<AuthSchemaTables>).users)) {
605
- 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;
606
607
  } else {
607
- 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);
608
609
  }
609
610
  }
610
611
 
@@ -704,7 +705,7 @@ export class PostgresTokenRepository implements TokenRepository {
704
705
 
705
706
  constructor(
706
707
  private db: NodePgDatabase,
707
- tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>
708
+ tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>
708
709
  ) {
709
710
  this.refreshTokenService = new RefreshTokenService(db, tableOrTables);
710
711
  this.passwordResetTokenService = new PasswordResetTokenService(db, tableOrTables);
@@ -770,7 +771,7 @@ export class PostgresAuthRepository implements AuthRepository {
770
771
 
771
772
  constructor(
772
773
  private db: NodePgDatabase,
773
- tableOrTables?: (PgTable & Record<string, AnyPgColumn>) | Partial<AuthSchemaTables>
774
+ tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>
774
775
  ) {
775
776
  this.userService = new UserService(db, tableOrTables);
776
777
  this.tokenRepository = new PostgresTokenRepository(db, tableOrTables);
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);