@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.
- package/README.md +69 -89
- package/dist/common/src/util/permissions.d.ts +14 -6
- package/dist/index.es.js +79 -112
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +79 -112
- package/dist/index.umd.js.map +1 -1
- package/dist/server-postgresql/src/auth/services.d.ts +11 -11
- package/dist/server-postgresql/src/data-transformer.d.ts +0 -3
- package/dist/server-postgresql/src/databasePoolManager.d.ts +1 -1
- package/dist/server-postgresql/src/types.d.ts +3 -0
- package/dist/server-postgresql/src/websocket.d.ts +8 -3
- package/dist/types/src/types/backend.d.ts +36 -1
- package/dist/types/src/types/collections.d.ts +21 -1
- package/dist/types/src/types/properties.d.ts +0 -8
- package/package.json +6 -6
- package/src/PostgresBackendDriver.ts +1 -1
- package/src/PostgresBootstrapper.ts +4 -3
- package/src/auth/services.ts +28 -27
- package/src/cli.ts +50 -23
- package/src/data-transformer.ts +57 -95
- package/src/databasePoolManager.ts +2 -1
- package/src/services/EntityPersistService.ts +29 -12
- package/src/types.ts +4 -0
- package/src/websocket.ts +37 -22
- package/drizzle.test.config.ts +0 -10
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
2
|
-
import {
|
|
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:
|
|
7
|
-
refreshTokens:
|
|
8
|
-
passwordResetTokens:
|
|
9
|
-
appConfig:
|
|
10
|
-
userIdentities:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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<
|
|
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.
|
|
@@ -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
|
-
|
|
6
|
-
|
|
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
|
-
/**
|
|
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
|
+
"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.
|
|
72
|
-
"@rebasepro/server-core": "0.
|
|
73
|
-
"@rebasepro/types": "0.
|
|
74
|
-
"@rebasepro/
|
|
75
|
-
"@rebasepro/
|
|
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
|
|
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
|
|
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
|
|
221
|
+
authTables.users = usersTable as RebasePgTable;
|
|
221
222
|
}
|
|
222
223
|
|
|
223
224
|
const userService = new UserService(db, authTables);
|
package/src/auth/services.ts
CHANGED
|
@@ -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
|
|
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:
|
|
31
|
-
refreshTokens:
|
|
32
|
-
passwordResetTokens:
|
|
33
|
-
appConfig:
|
|
34
|
-
userIdentities:
|
|
31
|
+
users: RebasePgTable;
|
|
32
|
+
refreshTokens: RebasePgTable;
|
|
33
|
+
passwordResetTokens: RebasePgTable;
|
|
34
|
+
appConfig: RebasePgTable;
|
|
35
|
+
userIdentities: RebasePgTable;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
function getColumnKey(table:
|
|
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:
|
|
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:
|
|
61
|
-
private userIdentitiesTable:
|
|
61
|
+
private usersTable: RebasePgTable;
|
|
62
|
+
private userIdentitiesTable: RebasePgTable;
|
|
62
63
|
|
|
63
64
|
constructor(
|
|
64
65
|
private db: NodePgDatabase,
|
|
65
|
-
tableOrTables?:
|
|
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
|
|
70
|
-
this.userIdentitiesTable = (tables.userIdentities || userIdentities) as
|
|
70
|
+
this.usersTable = (tables.users || users) as RebasePgTable;
|
|
71
|
+
this.userIdentitiesTable = (tables.userIdentities || userIdentities) as RebasePgTable;
|
|
71
72
|
} else {
|
|
72
|
-
const table = tableOrTables as
|
|
73
|
-
this.usersTable = table || (users as unknown as
|
|
74
|
-
this.userIdentitiesTable = userIdentities as unknown as
|
|
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
|
|
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:
|
|
501
|
+
private refreshTokensTable: RebasePgTable;
|
|
501
502
|
|
|
502
503
|
constructor(
|
|
503
504
|
private db: NodePgDatabase,
|
|
504
|
-
tableOrTables?:
|
|
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
|
|
508
|
+
this.refreshTokensTable = ((tableOrTables as Partial<AuthSchemaTables>).refreshTokens || refreshTokens) as RebasePgTable;
|
|
508
509
|
} else {
|
|
509
|
-
this.refreshTokensTable = (tableOrTables as
|
|
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:
|
|
599
|
+
private passwordResetTokensTable: RebasePgTable;
|
|
599
600
|
|
|
600
601
|
constructor(
|
|
601
602
|
private db: NodePgDatabase,
|
|
602
|
-
tableOrTables?:
|
|
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
|
|
606
|
+
this.passwordResetTokensTable = ((tableOrTables as Partial<AuthSchemaTables>).passwordResetTokens || passwordResetTokens) as RebasePgTable;
|
|
606
607
|
} else {
|
|
607
|
-
this.passwordResetTokensTable = (tableOrTables as
|
|
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?:
|
|
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?:
|
|
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
|
-
|
|
447
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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);
|