@rebasepro/server-postgresql 0.5.0 → 0.6.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/dist/{server-postgresql/src/PostgresAdapter.d.ts → PostgresAdapter.d.ts} +1 -1
- package/dist/{server-postgresql/src/PostgresBackendDriver.d.ts → PostgresBackendDriver.d.ts} +2 -2
- package/dist/{server-postgresql/src/PostgresBootstrapper.d.ts → PostgresBootstrapper.d.ts} +11 -1
- package/dist/{server-postgresql/src/collections → collections}/PostgresCollectionRegistry.d.ts +4 -0
- package/dist/index.es.js +10168 -11145
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +10735 -11429
- package/dist/index.umd.js.map +1 -1
- package/dist/{server-postgresql/src/services → services}/EntityPersistService.d.ts +0 -14
- package/dist/utils/pg-error-utils.d.ts +55 -0
- package/package.json +24 -21
- package/src/PostgresAdapter.ts +9 -10
- package/src/PostgresBackendDriver.ts +134 -121
- package/src/PostgresBootstrapper.ts +86 -13
- package/src/auth/ensure-tables.ts +28 -5
- package/src/auth/services.ts +28 -18
- package/src/cli.ts +99 -96
- package/src/collections/PostgresCollectionRegistry.ts +7 -0
- package/src/connection.ts +11 -6
- package/src/data-transformer.ts +16 -14
- package/src/databasePoolManager.ts +3 -2
- package/src/history/HistoryService.ts +3 -2
- package/src/history/ensure-history-table.ts +5 -4
- package/src/schema/auth-schema.ts +1 -2
- package/src/schema/doctor-cli.ts +2 -1
- package/src/schema/doctor.ts +40 -37
- package/src/schema/generate-drizzle-schema-logic.ts +56 -18
- package/src/schema/generate-drizzle-schema.ts +11 -11
- package/src/schema/introspect-db-inference.ts +25 -25
- package/src/schema/introspect-db-logic.ts +38 -38
- package/src/schema/introspect-db.ts +28 -27
- package/src/services/BranchService.ts +14 -0
- package/src/services/EntityFetchService.ts +28 -25
- package/src/services/EntityPersistService.ts +11 -141
- package/src/services/RelationService.ts +57 -37
- package/src/services/entity-helpers.ts +6 -2
- package/src/services/realtimeService.ts +45 -32
- package/src/utils/drizzle-conditions.ts +31 -15
- package/src/utils/pg-error-utils.ts +211 -0
- package/src/websocket.ts +15 -12
- package/test/auth-services.test.ts +36 -19
- package/test/batch-many-to-many-regression.test.ts +119 -39
- package/test/data-transformer-hardening.test.ts +67 -33
- package/test/data-transformer.test.ts +4 -2
- package/test/doctor.test.ts +10 -5
- package/test/drizzle-conditions.test.ts +59 -6
- package/test/generate-drizzle-schema.test.ts +65 -40
- package/test/introspect-db-generation.test.ts +179 -81
- package/test/introspect-db-utils.test.ts +92 -37
- package/test/mocks/chalk.cjs +7 -0
- package/test/pg-error-utils.test.ts +221 -0
- package/test/postgresDataDriver.test.ts +14 -5
- package/test/property-ordering.test.ts +126 -79
- package/test/realtimeService.test.ts +6 -2
- package/test/relation-pipeline-gaps.test.ts +84 -36
- package/test/relations.test.ts +247 -0
- package/test/unmapped-tables-safety.test.ts +14 -6
- package/test/websocket.test.ts +1 -1
- package/tsconfig.json +5 -0
- package/tsconfig.prod.json +3 -0
- package/vite.config.ts +5 -5
- package/dist/common/src/collections/CollectionRegistry.d.ts +0 -56
- package/dist/common/src/collections/default-collections.d.ts +0 -9
- package/dist/common/src/collections/index.d.ts +0 -2
- package/dist/common/src/data/buildRebaseData.d.ts +0 -14
- package/dist/common/src/data/query_builder.d.ts +0 -55
- package/dist/common/src/index.d.ts +0 -4
- package/dist/common/src/util/builders.d.ts +0 -57
- package/dist/common/src/util/callbacks.d.ts +0 -6
- package/dist/common/src/util/collections.d.ts +0 -11
- package/dist/common/src/util/common.d.ts +0 -2
- package/dist/common/src/util/conditions.d.ts +0 -26
- package/dist/common/src/util/entities.d.ts +0 -58
- package/dist/common/src/util/enums.d.ts +0 -3
- package/dist/common/src/util/index.d.ts +0 -16
- package/dist/common/src/util/navigation_from_path.d.ts +0 -34
- package/dist/common/src/util/navigation_utils.d.ts +0 -20
- package/dist/common/src/util/parent_references_from_path.d.ts +0 -6
- package/dist/common/src/util/paths.d.ts +0 -14
- package/dist/common/src/util/permissions.d.ts +0 -14
- package/dist/common/src/util/references.d.ts +0 -2
- package/dist/common/src/util/relations.d.ts +0 -22
- package/dist/common/src/util/resolutions.d.ts +0 -72
- package/dist/common/src/util/storage.d.ts +0 -24
- package/dist/types/src/controllers/analytics_controller.d.ts +0 -7
- package/dist/types/src/controllers/auth.d.ts +0 -104
- package/dist/types/src/controllers/client.d.ts +0 -168
- package/dist/types/src/controllers/collection_registry.d.ts +0 -46
- package/dist/types/src/controllers/customization_controller.d.ts +0 -60
- package/dist/types/src/controllers/data.d.ts +0 -207
- package/dist/types/src/controllers/data_driver.d.ts +0 -218
- package/dist/types/src/controllers/database_admin.d.ts +0 -11
- package/dist/types/src/controllers/dialogs_controller.d.ts +0 -36
- package/dist/types/src/controllers/effective_role.d.ts +0 -4
- package/dist/types/src/controllers/email.d.ts +0 -36
- package/dist/types/src/controllers/index.d.ts +0 -18
- package/dist/types/src/controllers/local_config_persistence.d.ts +0 -20
- package/dist/types/src/controllers/navigation.d.ts +0 -225
- package/dist/types/src/controllers/registry.d.ts +0 -63
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +0 -67
- package/dist/types/src/controllers/side_entity_controller.d.ts +0 -97
- package/dist/types/src/controllers/snackbar.d.ts +0 -24
- package/dist/types/src/controllers/storage.d.ts +0 -171
- package/dist/types/src/index.d.ts +0 -4
- package/dist/types/src/rebase_context.d.ts +0 -122
- package/dist/types/src/types/auth_adapter.d.ts +0 -301
- package/dist/types/src/types/backend.d.ts +0 -571
- package/dist/types/src/types/backend_hooks.d.ts +0 -172
- package/dist/types/src/types/builders.d.ts +0 -15
- package/dist/types/src/types/chips.d.ts +0 -5
- package/dist/types/src/types/collections.d.ts +0 -961
- package/dist/types/src/types/component_ref.d.ts +0 -47
- package/dist/types/src/types/cron.d.ts +0 -102
- package/dist/types/src/types/data_source.d.ts +0 -64
- package/dist/types/src/types/database_adapter.d.ts +0 -94
- package/dist/types/src/types/entities.d.ts +0 -145
- package/dist/types/src/types/entity_actions.d.ts +0 -104
- package/dist/types/src/types/entity_callbacks.d.ts +0 -173
- package/dist/types/src/types/entity_link_builder.d.ts +0 -7
- package/dist/types/src/types/entity_overrides.d.ts +0 -10
- package/dist/types/src/types/entity_views.d.ts +0 -87
- package/dist/types/src/types/export_import.d.ts +0 -21
- package/dist/types/src/types/formex.d.ts +0 -40
- package/dist/types/src/types/index.d.ts +0 -28
- package/dist/types/src/types/locales.d.ts +0 -4
- package/dist/types/src/types/modify_collections.d.ts +0 -5
- package/dist/types/src/types/plugins.d.ts +0 -282
- package/dist/types/src/types/properties.d.ts +0 -1173
- package/dist/types/src/types/property_config.d.ts +0 -74
- package/dist/types/src/types/relations.d.ts +0 -336
- package/dist/types/src/types/slots.d.ts +0 -262
- package/dist/types/src/types/translations.d.ts +0 -900
- package/dist/types/src/types/user_management_delegate.d.ts +0 -86
- package/dist/types/src/types/websockets.d.ts +0 -78
- package/dist/types/src/users/index.d.ts +0 -1
- package/dist/types/src/users/user.d.ts +0 -50
- /package/dist/{server-postgresql/src/auth → auth}/ensure-tables.d.ts +0 -0
- /package/dist/{server-postgresql/src/auth → auth}/services.d.ts +0 -0
- /package/dist/{server-postgresql/src/cli.d.ts → cli.d.ts} +0 -0
- /package/dist/{server-postgresql/src/connection.d.ts → connection.d.ts} +0 -0
- /package/dist/{server-postgresql/src/data-transformer.d.ts → data-transformer.d.ts} +0 -0
- /package/dist/{server-postgresql/src/databasePoolManager.d.ts → databasePoolManager.d.ts} +0 -0
- /package/dist/{server-postgresql/src/history → history}/HistoryService.d.ts +0 -0
- /package/dist/{server-postgresql/src/history → history}/ensure-history-table.d.ts +0 -0
- /package/dist/{server-postgresql/src/index.d.ts → index.d.ts} +0 -0
- /package/dist/{server-postgresql/src/interfaces.d.ts → interfaces.d.ts} +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/auth-schema.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/doctor-cli.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/doctor.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/generate-drizzle-schema-logic.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/generate-drizzle-schema.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/introspect-db-inference.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/introspect-db-logic.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/introspect-db.d.ts +0 -0
- /package/dist/{server-postgresql/src/schema → schema}/test-schema.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/BranchService.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/EntityFetchService.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/RelationService.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/entity-helpers.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/entityService.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/index.d.ts +0 -0
- /package/dist/{server-postgresql/src/services → services}/realtimeService.d.ts +0 -0
- /package/dist/{server-postgresql/src/types.d.ts → types.d.ts} +0 -0
- /package/dist/{server-postgresql/src/utils → utils}/drizzle-conditions.d.ts +0 -0
- /package/dist/{server-postgresql/src/websocket.d.ts → websocket.d.ts} +0 -0
|
@@ -4,7 +4,6 @@ import { logger } from "@rebasepro/server-core";
|
|
|
4
4
|
import type { EntityCollection } from "@rebasepro/types";
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
/**
|
|
9
8
|
* Auto-create auth tables if they don't exist.
|
|
10
9
|
*
|
|
@@ -20,16 +19,17 @@ export async function ensureAuthTablesExist(db: NodePgDatabase, collection?: Ent
|
|
|
20
19
|
let usersTableName = '"rebase"."users"';
|
|
21
20
|
let userIdType = "TEXT";
|
|
22
21
|
let usersSchema = "rebase";
|
|
22
|
+
let resolvedTable = "users";
|
|
23
23
|
if (collection) {
|
|
24
|
-
|
|
24
|
+
resolvedTable = ("table" in collection && typeof collection.table === "string")
|
|
25
25
|
? collection.table
|
|
26
26
|
: collection.slug;
|
|
27
27
|
usersSchema = ("schema" in collection && typeof collection.schema === "string")
|
|
28
28
|
? collection.schema
|
|
29
29
|
: "public";
|
|
30
30
|
usersTableName = usersSchema === "public"
|
|
31
|
-
? `"${
|
|
32
|
-
: `"${usersSchema}"."${
|
|
31
|
+
? `"${resolvedTable}"`
|
|
32
|
+
: `"${usersSchema}"."${resolvedTable}"`;
|
|
33
33
|
|
|
34
34
|
// Derive ID column type from collection properties
|
|
35
35
|
const idProp = collection.properties?.id;
|
|
@@ -44,6 +44,30 @@ export async function ensureAuthTablesExist(db: NodePgDatabase, collection?: Ent
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// Introspect the database to find the actual type of usersTableName's ID column if the table exists
|
|
48
|
+
try {
|
|
49
|
+
const result = await db.execute(sql`
|
|
50
|
+
SELECT data_type
|
|
51
|
+
FROM information_schema.columns
|
|
52
|
+
WHERE table_schema = ${usersSchema}
|
|
53
|
+
AND table_name = ${resolvedTable}
|
|
54
|
+
AND column_name = 'id'
|
|
55
|
+
`);
|
|
56
|
+
if (result && result.rows && result.rows.length > 0) {
|
|
57
|
+
const dbType = String((result.rows[0] as { data_type: string }).data_type).toUpperCase();
|
|
58
|
+
if (dbType === "UUID") {
|
|
59
|
+
userIdType = "UUID";
|
|
60
|
+
} else if (dbType === "INTEGER" || dbType === "SMALLINT" || dbType === "BIGINT") {
|
|
61
|
+
userIdType = "INTEGER";
|
|
62
|
+
} else {
|
|
63
|
+
userIdType = "TEXT";
|
|
64
|
+
}
|
|
65
|
+
logger.info(`✨ Detected ${usersTableName}.id type from database: ${dbType}. Using user_id type: ${userIdType}`);
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
// Ignore introspection errors, fallback to derived/default type
|
|
69
|
+
logger.warn(`⚠️ Failed to introspect ${usersTableName}.id type from database, falling back to config type: ${userIdType}`, { error: err });
|
|
70
|
+
}
|
|
47
71
|
|
|
48
72
|
|
|
49
73
|
// ── Create schemas (idempotent) ──────────────────────────────────
|
|
@@ -81,7 +105,6 @@ export async function ensureAuthTablesExist(db: NodePgDatabase, collection?: Ent
|
|
|
81
105
|
`);
|
|
82
106
|
|
|
83
107
|
|
|
84
|
-
|
|
85
108
|
// Create refresh tokens table (includes user_agent, ip_address, and unique constraint)
|
|
86
109
|
await db.execute(sql`
|
|
87
110
|
CREATE TABLE IF NOT EXISTS ${sql.raw(refreshTokensTableName)} (
|
package/src/auth/services.ts
CHANGED
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
MfaFactor,
|
|
22
22
|
MfaChallengeInfo,
|
|
23
23
|
RoleData as Role
|
|
24
|
-
// @ts-ignore
|
|
25
24
|
} from "@rebasepro/server-core";
|
|
26
25
|
import { toSnakeCase, camelCase } from "@rebasepro/utils";
|
|
27
26
|
|
|
@@ -172,18 +171,18 @@ export class UserService implements UserRepository {
|
|
|
172
171
|
|
|
173
172
|
for (const [key, val] of Object.entries(metadata)) {
|
|
174
173
|
const tableColKey = getColumnKey(this.usersTable, key);
|
|
175
|
-
if (tableColKey &&
|
|
176
|
-
tableColKey !== idKey &&
|
|
177
|
-
tableColKey !== emailKey &&
|
|
178
|
-
tableColKey !== passwordHashKey &&
|
|
179
|
-
tableColKey !== displayNameKey &&
|
|
180
|
-
tableColKey !== photoUrlKey &&
|
|
181
|
-
tableColKey !== emailVerifiedKey &&
|
|
182
|
-
tableColKey !== emailVerificationTokenKey &&
|
|
183
|
-
tableColKey !== emailVerificationSentAtKey &&
|
|
174
|
+
if (tableColKey &&
|
|
175
|
+
tableColKey !== idKey &&
|
|
176
|
+
tableColKey !== emailKey &&
|
|
177
|
+
tableColKey !== passwordHashKey &&
|
|
178
|
+
tableColKey !== displayNameKey &&
|
|
179
|
+
tableColKey !== photoUrlKey &&
|
|
180
|
+
tableColKey !== emailVerifiedKey &&
|
|
181
|
+
tableColKey !== emailVerificationTokenKey &&
|
|
182
|
+
tableColKey !== emailVerificationSentAtKey &&
|
|
184
183
|
tableColKey !== isAnonymousKey &&
|
|
185
|
-
tableColKey !== createdAtKey &&
|
|
186
|
-
tableColKey !== updatedAtKey &&
|
|
184
|
+
tableColKey !== createdAtKey &&
|
|
185
|
+
tableColKey !== updatedAtKey &&
|
|
187
186
|
tableColKey !== metadataKey) {
|
|
188
187
|
payload[tableColKey] = val;
|
|
189
188
|
} else {
|
|
@@ -221,7 +220,7 @@ export class UserService implements UserRepository {
|
|
|
221
220
|
async getUserByIdentity(provider: string, providerId: string): Promise<UserData | null> {
|
|
222
221
|
const userIdCol = getColumn(this.usersTable, "id");
|
|
223
222
|
if (!userIdCol) return null;
|
|
224
|
-
|
|
223
|
+
|
|
225
224
|
const result = await this.db
|
|
226
225
|
.select({ user: this.usersTable })
|
|
227
226
|
.from(this.usersTable)
|
|
@@ -496,7 +495,6 @@ export class UserService implements UserRepository {
|
|
|
496
495
|
}
|
|
497
496
|
|
|
498
497
|
|
|
499
|
-
|
|
500
498
|
export class RefreshTokenService {
|
|
501
499
|
private refreshTokensTable: RebasePgTable;
|
|
502
500
|
|
|
@@ -869,9 +867,21 @@ export class PostgresAuthRepository implements AuthRepository {
|
|
|
869
867
|
|
|
870
868
|
async listRoles(): Promise<RoleData[]> {
|
|
871
869
|
return [
|
|
872
|
-
{ id: "admin",
|
|
873
|
-
|
|
874
|
-
|
|
870
|
+
{ id: "admin",
|
|
871
|
+
name: "Admin",
|
|
872
|
+
isAdmin: true,
|
|
873
|
+
defaultPermissions: null,
|
|
874
|
+
collectionPermissions: null },
|
|
875
|
+
{ id: "editor",
|
|
876
|
+
name: "Editor",
|
|
877
|
+
isAdmin: false,
|
|
878
|
+
defaultPermissions: null,
|
|
879
|
+
collectionPermissions: null },
|
|
880
|
+
{ id: "viewer",
|
|
881
|
+
name: "Viewer",
|
|
882
|
+
isAdmin: false,
|
|
883
|
+
defaultPermissions: null,
|
|
884
|
+
collectionPermissions: null }
|
|
875
885
|
];
|
|
876
886
|
}
|
|
877
887
|
|
|
@@ -1017,7 +1027,7 @@ export class PostgresAuthRepository implements AuthRepository {
|
|
|
1017
1027
|
* Handles all MFA-related database operations.
|
|
1018
1028
|
*/
|
|
1019
1029
|
export class MfaService implements MfaRepository {
|
|
1020
|
-
constructor(private db: NodePgDatabase, private schemaName
|
|
1030
|
+
constructor(private db: NodePgDatabase, private schemaName = "rebase") {}
|
|
1021
1031
|
|
|
1022
1032
|
private qualify(tableName: string): string {
|
|
1023
1033
|
return `"${this.schemaName}"."${tableName}"`;
|
package/src/cli.ts
CHANGED
|
@@ -5,6 +5,7 @@ import path from "path";
|
|
|
5
5
|
import fs from "fs";
|
|
6
6
|
import { execSync } from "child_process";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
|
+
import { logger } from "@rebasepro/server-core";
|
|
8
9
|
|
|
9
10
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
|
|
@@ -40,7 +41,7 @@ export async function runPluginCommand(args: string[]) {
|
|
|
40
41
|
} else if (domain === "doctor") {
|
|
41
42
|
await doctorPluginCommand(args);
|
|
42
43
|
} else {
|
|
43
|
-
|
|
44
|
+
logger.error(chalk.red(`Unknown domain command: ${domain}`));
|
|
44
45
|
process.exit(1);
|
|
45
46
|
}
|
|
46
47
|
}
|
|
@@ -48,7 +49,7 @@ export async function runPluginCommand(args: string[]) {
|
|
|
48
49
|
async function dbCommand(subcommand: string, rawArgs: string[]): Promise<void> {
|
|
49
50
|
const VALID_ACTIONS = ["push", "generate", "migrate", "studio", "branch"];
|
|
50
51
|
if (!subcommand || !VALID_ACTIONS.includes(subcommand)) {
|
|
51
|
-
|
|
52
|
+
logger.error(chalk.red(`Unknown db command. Valid: ${VALID_ACTIONS.join(", ")}`));
|
|
52
53
|
process.exit(1);
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -58,44 +59,44 @@ async function dbCommand(subcommand: string, rawArgs: string[]): Promise<void> {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
if (subcommand === "generate") {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
logger.info("");
|
|
63
|
+
logger.info(chalk.bold(" 📦 Rebase DB Generate"));
|
|
64
|
+
logger.info(chalk.gray(" Step 1/2: Generating Drizzle schema from collections..."));
|
|
65
|
+
logger.info("");
|
|
65
66
|
await schemaCommand("generate", rawArgs);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
logger.info("");
|
|
68
|
+
logger.info(chalk.gray(" Step 2/2: Generating SQL migration files..."));
|
|
69
|
+
logger.info("");
|
|
69
70
|
await runDrizzleKit("generate", rawArgs);
|
|
70
71
|
await fixMigrationStatementOrder();
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
logger.info("");
|
|
73
|
+
logger.info(` You can now run ${chalk.bold.green("rebase db migrate")} to apply the migrations to your database.`);
|
|
74
|
+
logger.info("");
|
|
74
75
|
} else if (subcommand === "pull") {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
logger.info("");
|
|
77
|
+
logger.info(chalk.yellow(" ⚠ \"rebase db pull\" has been removed."));
|
|
78
|
+
logger.info(chalk.gray(" Use \"rebase schema introspect\" instead."));
|
|
79
|
+
logger.info("");
|
|
79
80
|
process.exit(1);
|
|
80
81
|
} else {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
logger.info("");
|
|
83
|
+
logger.info(chalk.bold(` 🗄️ Rebase DB ${subcommand.charAt(0).toUpperCase() + subcommand.slice(1)}`));
|
|
84
|
+
logger.info("");
|
|
84
85
|
|
|
85
86
|
if (subcommand === "push") {
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
logger.info(chalk.gray(" Step 1/2: Generating Drizzle schema from collections..."));
|
|
88
|
+
logger.info("");
|
|
88
89
|
await schemaCommand("generate", rawArgs);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
logger.info("");
|
|
91
|
+
logger.info(chalk.gray(" Step 2/2: Pushing schema to database..."));
|
|
92
|
+
logger.info("");
|
|
92
93
|
await runDrizzleKit("push", rawArgs);
|
|
93
94
|
} else if (subcommand === "migrate") {
|
|
94
95
|
await runDrizzleKit("migrate", rawArgs);
|
|
95
96
|
} else if (subcommand === "studio") {
|
|
96
97
|
const schemaPath = path.join(process.cwd(), "src", "schema.generated.ts");
|
|
97
98
|
if (!fs.existsSync(schemaPath)) {
|
|
98
|
-
|
|
99
|
+
logger.info(chalk.yellow(" ⚠ schema.generated.ts not found. Generating schema first..."));
|
|
99
100
|
await schemaCommand("generate", rawArgs);
|
|
100
101
|
}
|
|
101
102
|
await runDrizzleKit("studio", rawArgs);
|
|
@@ -103,9 +104,9 @@ async function dbCommand(subcommand: string, rawArgs: string[]): Promise<void> {
|
|
|
103
104
|
await runDrizzleKit(subcommand, rawArgs);
|
|
104
105
|
}
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
logger.info("");
|
|
108
|
+
logger.info(chalk.green(` ✓ rebase db ${subcommand} completed successfully.`));
|
|
109
|
+
logger.info("");
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
|
|
@@ -132,7 +133,7 @@ async function branchCommand(rawArgs: string[]): Promise<void> {
|
|
|
132
133
|
|
|
133
134
|
const databaseUrl = process.env.DATABASE_URL || process.env.ADMIN_CONNECTION_STRING;
|
|
134
135
|
if (!databaseUrl) {
|
|
135
|
-
|
|
136
|
+
logger.error(chalk.red("✗ DATABASE_URL is not set. Make sure your .env file is configured."));
|
|
136
137
|
process.exit(1);
|
|
137
138
|
}
|
|
138
139
|
|
|
@@ -156,8 +157,8 @@ max: 3 });
|
|
|
156
157
|
case "create": {
|
|
157
158
|
const name = rawArgs[3];
|
|
158
159
|
if (!name) {
|
|
159
|
-
|
|
160
|
-
|
|
160
|
+
logger.error(chalk.red("✗ Branch name is required."));
|
|
161
|
+
logger.info(chalk.gray(" Usage: rebase db branch create <name> [--from <source>]"));
|
|
161
162
|
process.exit(1);
|
|
162
163
|
}
|
|
163
164
|
let source: string | undefined;
|
|
@@ -165,81 +166,81 @@ max: 3 });
|
|
|
165
166
|
if (fromIdx !== -1 && rawArgs[fromIdx + 1]) {
|
|
166
167
|
source = rawArgs[fromIdx + 1];
|
|
167
168
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (source)
|
|
172
|
-
|
|
169
|
+
logger.info("");
|
|
170
|
+
logger.info(chalk.bold(" 🌿 Creating database branch..."));
|
|
171
|
+
logger.info(chalk.gray(` Name: ${name}`));
|
|
172
|
+
if (source) logger.info(chalk.gray(` Source: ${source}`));
|
|
173
|
+
logger.info("");
|
|
173
174
|
const branch = await branchService.createBranch(name, source ? { source } : undefined);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
logger.info(chalk.green(` ✓ Branch "${branch.name}" created successfully.`));
|
|
176
|
+
logger.info(chalk.gray(` Database: rb_${branch.name}`));
|
|
177
|
+
logger.info(chalk.gray(` Parent: ${branch.parentDatabase}`));
|
|
178
|
+
logger.info("");
|
|
178
179
|
break;
|
|
179
180
|
}
|
|
180
181
|
|
|
181
182
|
case "list": {
|
|
182
183
|
const branches = await branchService.listBranches();
|
|
183
|
-
|
|
184
|
+
logger.info("");
|
|
184
185
|
if (branches.length === 0) {
|
|
185
|
-
|
|
186
|
+
logger.info(chalk.gray(" No branches found. Create one with: rebase db branch create <name>"));
|
|
186
187
|
} else {
|
|
187
|
-
|
|
188
|
-
|
|
188
|
+
logger.info(chalk.bold(` 🌿 ${branches.length} branch(es):`));
|
|
189
|
+
logger.info("");
|
|
189
190
|
for (const b of branches) {
|
|
190
191
|
const size = b.sizeBytes != null
|
|
191
192
|
? chalk.gray(` (${formatBytes(b.sizeBytes)})`)
|
|
192
193
|
: "";
|
|
193
194
|
const age = chalk.gray(` — created ${timeAgo(b.createdAt)}`);
|
|
194
|
-
|
|
195
|
-
|
|
195
|
+
logger.info(` ${chalk.green("●")} ${chalk.bold(b.name)}${size}${age}`);
|
|
196
|
+
logger.info(chalk.gray(` from ${b.parentDatabase}`));
|
|
196
197
|
}
|
|
197
198
|
}
|
|
198
|
-
|
|
199
|
+
logger.info("");
|
|
199
200
|
break;
|
|
200
201
|
}
|
|
201
202
|
|
|
202
203
|
case "delete": {
|
|
203
204
|
const name = rawArgs[3];
|
|
204
205
|
if (!name) {
|
|
205
|
-
|
|
206
|
-
|
|
206
|
+
logger.error(chalk.red("✗ Branch name is required."));
|
|
207
|
+
logger.info(chalk.gray(" Usage: rebase db branch delete <name>"));
|
|
207
208
|
process.exit(1);
|
|
208
209
|
}
|
|
209
|
-
|
|
210
|
-
|
|
210
|
+
logger.info("");
|
|
211
|
+
logger.info(chalk.bold(` 🗑️ Deleting branch "${name}"...`));
|
|
211
212
|
await branchService.deleteBranch(name);
|
|
212
|
-
|
|
213
|
-
|
|
213
|
+
logger.info(chalk.green(` ✓ Branch "${name}" deleted.`));
|
|
214
|
+
logger.info("");
|
|
214
215
|
break;
|
|
215
216
|
}
|
|
216
217
|
|
|
217
218
|
case "info": {
|
|
218
219
|
const name = rawArgs[3];
|
|
219
220
|
if (!name) {
|
|
220
|
-
|
|
221
|
-
|
|
221
|
+
logger.error(chalk.red("✗ Branch name is required."));
|
|
222
|
+
logger.info(chalk.gray(" Usage: rebase db branch info <name>"));
|
|
222
223
|
process.exit(1);
|
|
223
224
|
}
|
|
224
225
|
const info = await branchService.getBranchInfo(name);
|
|
225
|
-
|
|
226
|
+
logger.info("");
|
|
226
227
|
if (!info) {
|
|
227
|
-
|
|
228
|
+
logger.error(chalk.red(` ✗ Branch "${name}" not found.`));
|
|
228
229
|
} else {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
230
|
+
logger.info(chalk.bold(` 🌿 Branch: ${info.name}`));
|
|
231
|
+
logger.info(chalk.gray(` Database: rb_${info.name}`));
|
|
232
|
+
logger.info(chalk.gray(` Parent: ${info.parentDatabase}`));
|
|
233
|
+
logger.info(chalk.gray(` Created: ${info.createdAt.toISOString()}`));
|
|
233
234
|
if (info.sizeBytes != null) {
|
|
234
|
-
|
|
235
|
+
logger.info(chalk.gray(` Size: ${formatBytes(info.sizeBytes)}`));
|
|
235
236
|
}
|
|
236
237
|
}
|
|
237
|
-
|
|
238
|
+
logger.info("");
|
|
238
239
|
break;
|
|
239
240
|
}
|
|
240
241
|
|
|
241
242
|
default:
|
|
242
|
-
|
|
243
|
+
logger.error(chalk.red(`Unknown branch action: "${branchAction}".`));
|
|
243
244
|
printBranchHelp();
|
|
244
245
|
process.exit(1);
|
|
245
246
|
}
|
|
@@ -250,7 +251,7 @@ max: 3 });
|
|
|
250
251
|
}
|
|
251
252
|
|
|
252
253
|
function printBranchHelp() {
|
|
253
|
-
|
|
254
|
+
logger.info(`
|
|
254
255
|
${chalk.bold("rebase db branch")} — Database branching commands
|
|
255
256
|
|
|
256
257
|
${chalk.green.bold("Usage")}
|
|
@@ -398,14 +399,16 @@ idx }));
|
|
|
398
399
|
const reordered = stmtEntries.map(e => e.stmt).join(DELIMITER);
|
|
399
400
|
fs.writeFileSync(latestFile, reordered, "utf-8");
|
|
400
401
|
|
|
401
|
-
|
|
402
|
+
logger.info(chalk.yellow(` \u26A0 Reordered migration statements in ${sqlFiles[0].name} (DROP POLICY before ALTER COLUMN)`));
|
|
402
403
|
}
|
|
403
404
|
|
|
404
405
|
async function runDrizzleKit(action: string, _rawArgs: string[]): Promise<void> {
|
|
405
406
|
const drizzleKitBin = resolveLocalBin("drizzle-kit");
|
|
406
407
|
if (!drizzleKitBin) {
|
|
407
|
-
|
|
408
|
-
|
|
408
|
+
logger.error(chalk.red("✗ Could not find drizzle-kit binary."));
|
|
409
|
+
const isNpm = (process.env.npm_config_user_agent ?? "").startsWith("npm/") || fs.existsSync(path.join(process.cwd(), "package-lock.json"));
|
|
410
|
+
const installCmd = isNpm ? "npm install -D drizzle-kit" : "pnpm add -D drizzle-kit";
|
|
411
|
+
logger.error(chalk.gray(` Install it with: ${installCmd}`));
|
|
409
412
|
process.exit(1);
|
|
410
413
|
}
|
|
411
414
|
|
|
@@ -486,14 +489,14 @@ async function runDrizzleKit(action: string, _rawArgs: string[]): Promise<void>
|
|
|
486
489
|
const stdout = stripAnsi(result.stdout || "").trim();
|
|
487
490
|
const stderr = stripAnsi(result.stderr || "").trim();
|
|
488
491
|
|
|
489
|
-
const hasTtyError = stdout.includes("Interactive prompts require a TTY terminal") ||
|
|
492
|
+
const hasTtyError = stdout.includes("Interactive prompts require a TTY terminal") ||
|
|
490
493
|
stderr.includes("Interactive prompts require a TTY terminal");
|
|
491
494
|
|
|
492
495
|
if (result.exitCode !== 0 || hasTtyError) {
|
|
493
|
-
|
|
496
|
+
logger.error(chalk.red(`\n✗ drizzle-kit ${action} failed.\n`));
|
|
494
497
|
if (hasTtyError) {
|
|
495
|
-
|
|
496
|
-
|
|
498
|
+
logger.error(chalk.red(" Error: Interactive prompts require a TTY terminal."));
|
|
499
|
+
logger.error(chalk.gray(" Please run with --force to skip interactive prompts or run in an interactive terminal."));
|
|
497
500
|
} else {
|
|
498
501
|
const errorOutput = stderr || stdout;
|
|
499
502
|
if (errorOutput) {
|
|
@@ -501,16 +504,16 @@ async function runDrizzleKit(action: string, _rawArgs: string[]): Promise<void>
|
|
|
501
504
|
let printedCount = 0;
|
|
502
505
|
for (const line of lines) {
|
|
503
506
|
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
|
-
|
|
507
|
+
logger.error(chalk.red(` ${line.trim()}`));
|
|
505
508
|
printedCount++;
|
|
506
509
|
}
|
|
507
510
|
}
|
|
508
511
|
if (printedCount === 0) {
|
|
509
|
-
lines.slice(0, 10).forEach(line =>
|
|
512
|
+
lines.slice(0, 10).forEach(line => logger.error(chalk.red(` ${line.trim()}`)));
|
|
510
513
|
}
|
|
511
514
|
}
|
|
512
515
|
}
|
|
513
|
-
|
|
516
|
+
logger.error("");
|
|
514
517
|
process.exit(1);
|
|
515
518
|
}
|
|
516
519
|
}
|
|
@@ -520,22 +523,22 @@ async function runDrizzleKit(action: string, _rawArgs: string[]): Promise<void>
|
|
|
520
523
|
const stripAnsi = (s: string) => s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\[?[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⣷⣯⣟⡿⢿⣻⣽]+\]\s*/g, "");
|
|
521
524
|
const cleaned = stripAnsi(msg).trim();
|
|
522
525
|
const hasTtyError = cleaned.includes("Interactive prompts require a TTY terminal");
|
|
523
|
-
|
|
526
|
+
logger.error(chalk.red(`\n✗ drizzle-kit ${action} failed.\n`));
|
|
524
527
|
if (hasTtyError) {
|
|
525
|
-
|
|
526
|
-
|
|
528
|
+
logger.error(chalk.red(" Error: Interactive prompts require a TTY terminal."));
|
|
529
|
+
logger.error(chalk.gray(" Please run with --force to skip interactive prompts or run in an interactive terminal."));
|
|
527
530
|
} else {
|
|
528
531
|
const lines = cleaned.split("\n").filter((l: string) => l.trim());
|
|
529
532
|
for (const line of lines) {
|
|
530
533
|
if (line.toLowerCase().includes("error") || line.includes("cannot") || line.includes("already exists") || line.includes("does not exist") || line.includes("violates")) {
|
|
531
|
-
|
|
534
|
+
logger.error(chalk.red(` ${line.trim()}`));
|
|
532
535
|
}
|
|
533
536
|
}
|
|
534
537
|
if (lines.length === 0) {
|
|
535
|
-
|
|
538
|
+
logger.error(chalk.gray(` ${cleaned}`));
|
|
536
539
|
}
|
|
537
540
|
}
|
|
538
|
-
|
|
541
|
+
logger.error("");
|
|
539
542
|
process.exit(1);
|
|
540
543
|
}
|
|
541
544
|
}
|
|
@@ -561,13 +564,13 @@ async function schemaCommand(subcommand: string, rawArgs: string[]): Promise<voi
|
|
|
561
564
|
// If installed in node_modules, __dirname is node_modules/@rebasepro/server-postgresql/dist or src.
|
|
562
565
|
const generatorScript = path.join(__dirname, "schema", "generate-drizzle-schema.ts");
|
|
563
566
|
if (!fs.existsSync(generatorScript)) {
|
|
564
|
-
|
|
567
|
+
logger.error(chalk.red(`✗ Could not find generate-drizzle-schema.ts at ${generatorScript}`));
|
|
565
568
|
process.exit(1);
|
|
566
569
|
}
|
|
567
570
|
|
|
568
571
|
const tsxBin = resolveLocalBin("tsx");
|
|
569
572
|
if (!tsxBin) {
|
|
570
|
-
|
|
573
|
+
logger.error(chalk.red("✗ Could not find tsx binary."));
|
|
571
574
|
process.exit(1);
|
|
572
575
|
}
|
|
573
576
|
|
|
@@ -575,9 +578,9 @@ async function schemaCommand(subcommand: string, rawArgs: string[]): Promise<voi
|
|
|
575
578
|
const outputPath = argsList["--output"] || path.join("src", "schema.generated.ts");
|
|
576
579
|
const watch = argsList["--watch"] || false;
|
|
577
580
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
+
logger.info("");
|
|
582
|
+
logger.info(chalk.bold(" 🔧 Rebase Schema Generator"));
|
|
583
|
+
logger.info("");
|
|
581
584
|
|
|
582
585
|
const cmdParts = [
|
|
583
586
|
tsxBin,
|
|
@@ -596,7 +599,7 @@ async function schemaCommand(subcommand: string, rawArgs: string[]): Promise<voi
|
|
|
596
599
|
env: { ...process.env as Record<string, string> }
|
|
597
600
|
});
|
|
598
601
|
} catch (err: unknown) {
|
|
599
|
-
|
|
602
|
+
logger.error(chalk.red(`✗ Failed to run schema generator: ${err instanceof Error ? err.message : String(err)}`));
|
|
600
603
|
process.exit(1);
|
|
601
604
|
}
|
|
602
605
|
} else if (subcommand === "introspect") {
|
|
@@ -618,21 +621,21 @@ async function schemaCommand(subcommand: string, rawArgs: string[]): Promise<voi
|
|
|
618
621
|
|
|
619
622
|
const introspectScript = path.join(__dirname, "schema", "introspect-db.ts");
|
|
620
623
|
if (!fs.existsSync(introspectScript)) {
|
|
621
|
-
|
|
624
|
+
logger.error(chalk.red(`✗ Could not find introspect-db.ts at ${introspectScript}`));
|
|
622
625
|
process.exit(1);
|
|
623
626
|
}
|
|
624
627
|
|
|
625
628
|
const tsxBin = resolveLocalBin("tsx");
|
|
626
629
|
if (!tsxBin) {
|
|
627
|
-
|
|
630
|
+
logger.error(chalk.red("✗ Could not find tsx binary."));
|
|
628
631
|
process.exit(1);
|
|
629
632
|
}
|
|
630
633
|
|
|
631
634
|
const outputPath = argsList["--output"] || argsList["--collections"] || path.join("..", "config", "collections");
|
|
632
635
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
+
logger.info("");
|
|
637
|
+
logger.info(chalk.bold(" 🔍 Rebase Schema Introspector"));
|
|
638
|
+
logger.info("");
|
|
636
639
|
|
|
637
640
|
const cmdParts = [
|
|
638
641
|
tsxBin,
|
|
@@ -649,11 +652,11 @@ async function schemaCommand(subcommand: string, rawArgs: string[]): Promise<voi
|
|
|
649
652
|
env: { ...process.env as Record<string, string> }
|
|
650
653
|
});
|
|
651
654
|
} catch (err: unknown) {
|
|
652
|
-
|
|
655
|
+
logger.error(chalk.red(`✗ Failed to run schema introspector: ${err instanceof Error ? err.message : String(err)}`));
|
|
653
656
|
process.exit(1);
|
|
654
657
|
}
|
|
655
658
|
} else {
|
|
656
|
-
|
|
659
|
+
logger.error(chalk.red("Unknown schema command."));
|
|
657
660
|
process.exit(1);
|
|
658
661
|
}
|
|
659
662
|
}
|
|
@@ -676,13 +679,13 @@ async function doctorPluginCommand(rawArgs: string[]): Promise<void> {
|
|
|
676
679
|
|
|
677
680
|
const doctorScript = path.join(__dirname, "schema", "doctor-cli.ts");
|
|
678
681
|
if (!fs.existsSync(doctorScript)) {
|
|
679
|
-
|
|
682
|
+
logger.error(chalk.red(`✗ Could not find doctor.ts at ${doctorScript}`));
|
|
680
683
|
process.exit(1);
|
|
681
684
|
}
|
|
682
685
|
|
|
683
686
|
const tsxBin = resolveLocalBin("tsx");
|
|
684
687
|
if (!tsxBin) {
|
|
685
|
-
|
|
688
|
+
logger.error(chalk.red("✗ Could not find tsx binary."));
|
|
686
689
|
process.exit(1);
|
|
687
690
|
}
|
|
688
691
|
|
|
@@ -32,6 +32,13 @@ export class PostgresCollectionRegistry extends CollectionRegistry implements Co
|
|
|
32
32
|
return this.tables.has(tableName);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Returns all registered table names.
|
|
37
|
+
*/
|
|
38
|
+
getTableNames(): string[] {
|
|
39
|
+
return Array.from(this.tables.keys());
|
|
40
|
+
}
|
|
41
|
+
|
|
35
42
|
/**
|
|
36
43
|
* Finds collections assigned to a specific driver that do not have a registered table.
|
|
37
44
|
*/
|
package/src/connection.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Pool, PoolConfig } from "pg";
|
|
2
2
|
import { drizzle } from "drizzle-orm/node-postgres";
|
|
3
|
+
import { logger } from "@rebasepro/server-core";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Configuration for the Postgres connection pool.
|
|
@@ -69,9 +70,9 @@ export function createPostgresDatabaseConnection(
|
|
|
69
70
|
// (a separate package). The caller can replace these with the structured
|
|
70
71
|
// logger if desired via pool.on() after creation.
|
|
71
72
|
pool.on("error", (err) => {
|
|
72
|
-
|
|
73
|
+
logger.error("[pg-pool] Unexpected pool error", { detail: err.message });
|
|
73
74
|
if (err.message.includes("ETIMEDOUT")) {
|
|
74
|
-
|
|
75
|
+
logger.warn("[pg-pool] Connection timeout detected — pool will auto-retry");
|
|
75
76
|
}
|
|
76
77
|
});
|
|
77
78
|
|
|
@@ -115,12 +116,14 @@ export function createDirectDatabaseConnection(
|
|
|
115
116
|
const pool = new Pool(pgPoolConfig);
|
|
116
117
|
|
|
117
118
|
pool.on("error", (err) => {
|
|
118
|
-
|
|
119
|
+
logger.error("[pg-direct-pool] Unexpected pool error", { detail: err.message });
|
|
119
120
|
});
|
|
120
121
|
|
|
121
122
|
const db = schema ? drizzle(pool, { schema }) : drizzle(pool);
|
|
122
123
|
|
|
123
|
-
return { db,
|
|
124
|
+
return { db,
|
|
125
|
+
pool,
|
|
126
|
+
connectionString };
|
|
124
127
|
}
|
|
125
128
|
|
|
126
129
|
/**
|
|
@@ -152,10 +155,12 @@ export function createReadReplicaConnection(
|
|
|
152
155
|
const pool = new Pool(pgPoolConfig);
|
|
153
156
|
|
|
154
157
|
pool.on("error", (err) => {
|
|
155
|
-
|
|
158
|
+
logger.error("[pg-replica-pool] Unexpected pool error", { detail: err.message });
|
|
156
159
|
});
|
|
157
160
|
|
|
158
161
|
const db = schema ? drizzle(pool, { schema }) : drizzle(pool);
|
|
159
162
|
|
|
160
|
-
return { db,
|
|
163
|
+
return { db,
|
|
164
|
+
pool,
|
|
165
|
+
connectionString };
|
|
161
166
|
}
|