@rebasepro/server-postgresql 0.0.1-canary.4d4fb3e
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/LICENSE +6 -0
- package/README.md +106 -0
- package/build-errors.txt +37 -0
- package/dist/common/src/collections/CollectionRegistry.d.ts +48 -0
- package/dist/common/src/collections/index.d.ts +1 -0
- package/dist/common/src/data/buildRebaseData.d.ts +14 -0
- package/dist/common/src/index.d.ts +3 -0
- package/dist/common/src/util/builders.d.ts +57 -0
- package/dist/common/src/util/callbacks.d.ts +6 -0
- package/dist/common/src/util/collections.d.ts +11 -0
- package/dist/common/src/util/common.d.ts +2 -0
- package/dist/common/src/util/conditions.d.ts +26 -0
- package/dist/common/src/util/entities.d.ts +36 -0
- package/dist/common/src/util/enums.d.ts +3 -0
- package/dist/common/src/util/index.d.ts +16 -0
- package/dist/common/src/util/navigation_from_path.d.ts +34 -0
- package/dist/common/src/util/navigation_utils.d.ts +20 -0
- package/dist/common/src/util/parent_references_from_path.d.ts +6 -0
- package/dist/common/src/util/paths.d.ts +14 -0
- package/dist/common/src/util/permissions.d.ts +5 -0
- package/dist/common/src/util/references.d.ts +2 -0
- package/dist/common/src/util/relations.d.ts +12 -0
- package/dist/common/src/util/resolutions.d.ts +72 -0
- package/dist/common/src/util/storage.d.ts +24 -0
- package/dist/index.es.js +10635 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +10643 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +112 -0
- package/dist/server-postgresql/src/PostgresBootstrapper.d.ts +40 -0
- package/dist/server-postgresql/src/auth/ensure-tables.d.ts +6 -0
- package/dist/server-postgresql/src/auth/services.d.ts +188 -0
- package/dist/server-postgresql/src/cli.d.ts +1 -0
- package/dist/server-postgresql/src/collections/PostgresCollectionRegistry.d.ts +43 -0
- package/dist/server-postgresql/src/connection.d.ts +7 -0
- package/dist/server-postgresql/src/data-transformer.d.ts +36 -0
- package/dist/server-postgresql/src/databasePoolManager.d.ts +20 -0
- package/dist/server-postgresql/src/history/HistoryService.d.ts +71 -0
- package/dist/server-postgresql/src/history/ensure-history-table.d.ts +7 -0
- package/dist/server-postgresql/src/index.d.ts +13 -0
- package/dist/server-postgresql/src/interfaces.d.ts +18 -0
- package/dist/server-postgresql/src/schema/auth-schema.d.ts +767 -0
- package/dist/server-postgresql/src/schema/generate-drizzle-schema-logic.d.ts +2 -0
- package/dist/server-postgresql/src/schema/generate-drizzle-schema.d.ts +1 -0
- package/dist/server-postgresql/src/services/BranchService.d.ts +47 -0
- package/dist/server-postgresql/src/services/EntityFetchService.d.ts +195 -0
- package/dist/server-postgresql/src/services/EntityPersistService.d.ts +41 -0
- package/dist/server-postgresql/src/services/RelationService.d.ts +92 -0
- package/dist/server-postgresql/src/services/entity-helpers.d.ts +24 -0
- package/dist/server-postgresql/src/services/entityService.d.ts +102 -0
- package/dist/server-postgresql/src/services/index.d.ts +4 -0
- package/dist/server-postgresql/src/services/realtimeService.d.ts +186 -0
- package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +116 -0
- package/dist/server-postgresql/src/websocket.d.ts +5 -0
- package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
- package/dist/types/src/controllers/auth.d.ts +117 -0
- package/dist/types/src/controllers/client.d.ts +58 -0
- package/dist/types/src/controllers/collection_registry.d.ts +44 -0
- package/dist/types/src/controllers/customization_controller.d.ts +54 -0
- package/dist/types/src/controllers/data.d.ts +141 -0
- package/dist/types/src/controllers/data_driver.d.ts +168 -0
- package/dist/types/src/controllers/database_admin.d.ts +11 -0
- package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
- package/dist/types/src/controllers/effective_role.d.ts +4 -0
- package/dist/types/src/controllers/index.d.ts +17 -0
- package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
- package/dist/types/src/controllers/navigation.d.ts +213 -0
- package/dist/types/src/controllers/registry.d.ts +51 -0
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
- package/dist/types/src/controllers/side_entity_controller.d.ts +89 -0
- package/dist/types/src/controllers/snackbar.d.ts +24 -0
- package/dist/types/src/controllers/storage.d.ts +173 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/rebase_context.d.ts +101 -0
- package/dist/types/src/types/backend.d.ts +533 -0
- package/dist/types/src/types/builders.d.ts +14 -0
- package/dist/types/src/types/chips.d.ts +5 -0
- package/dist/types/src/types/collections.d.ts +812 -0
- package/dist/types/src/types/data_source.d.ts +64 -0
- package/dist/types/src/types/entities.d.ts +145 -0
- package/dist/types/src/types/entity_actions.d.ts +98 -0
- package/dist/types/src/types/entity_callbacks.d.ts +173 -0
- package/dist/types/src/types/entity_link_builder.d.ts +7 -0
- package/dist/types/src/types/entity_overrides.d.ts +9 -0
- package/dist/types/src/types/entity_views.d.ts +61 -0
- package/dist/types/src/types/export_import.d.ts +21 -0
- package/dist/types/src/types/index.d.ts +22 -0
- package/dist/types/src/types/locales.d.ts +4 -0
- package/dist/types/src/types/modify_collections.d.ts +5 -0
- package/dist/types/src/types/plugins.d.ts +225 -0
- package/dist/types/src/types/properties.d.ts +1091 -0
- package/dist/types/src/types/property_config.d.ts +70 -0
- package/dist/types/src/types/relations.d.ts +336 -0
- package/dist/types/src/types/slots.d.ts +228 -0
- package/dist/types/src/types/translations.d.ts +826 -0
- package/dist/types/src/types/user_management_delegate.d.ts +120 -0
- package/dist/types/src/types/websockets.d.ts +78 -0
- package/dist/types/src/users/index.d.ts +2 -0
- package/dist/types/src/users/roles.d.ts +22 -0
- package/dist/types/src/users/user.d.ts +46 -0
- package/jest-all.log +3128 -0
- package/jest.log +49 -0
- package/package.json +93 -0
- package/src/PostgresBackendDriver.ts +1024 -0
- package/src/PostgresBootstrapper.ts +232 -0
- package/src/auth/ensure-tables.ts +309 -0
- package/src/auth/services.ts +740 -0
- package/src/cli.ts +347 -0
- package/src/collections/PostgresCollectionRegistry.ts +96 -0
- package/src/connection.ts +62 -0
- package/src/data-transformer.ts +569 -0
- package/src/databasePoolManager.ts +84 -0
- package/src/history/HistoryService.ts +257 -0
- package/src/history/ensure-history-table.ts +45 -0
- package/src/index.ts +13 -0
- package/src/interfaces.ts +60 -0
- package/src/schema/auth-schema.ts +146 -0
- package/src/schema/generate-drizzle-schema-logic.ts +618 -0
- package/src/schema/generate-drizzle-schema.ts +151 -0
- package/src/services/BranchService.ts +237 -0
- package/src/services/EntityFetchService.ts +1447 -0
- package/src/services/EntityPersistService.ts +351 -0
- package/src/services/RelationService.ts +1012 -0
- package/src/services/entity-helpers.ts +121 -0
- package/src/services/entityService.ts +209 -0
- package/src/services/index.ts +13 -0
- package/src/services/realtimeService.ts +1005 -0
- package/src/utils/drizzle-conditions.ts +999 -0
- package/src/websocket.ts +487 -0
- package/test/auth-services.test.ts +569 -0
- package/test/branchService.test.ts +357 -0
- package/test/drizzle-conditions.test.ts +895 -0
- package/test/entityService.errors.test.ts +352 -0
- package/test/entityService.relations.test.ts +912 -0
- package/test/entityService.subcollection-search.test.ts +516 -0
- package/test/entityService.test.ts +977 -0
- package/test/generate-drizzle-schema.test.ts +795 -0
- package/test/historyService.test.ts +126 -0
- package/test/postgresDataDriver.test.ts +556 -0
- package/test/realtimeService.test.ts +276 -0
- package/test/relations.test.ts +662 -0
- package/test_drizzle_mock.js +3 -0
- package/test_find_changed.mjs +30 -0
- package/test_output.txt +3145 -0
- package/tsconfig.json +49 -0
- package/tsconfig.prod.json +20 -0
- package/vite.config.ts +82 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import arg from "arg";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import execa from "execa";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
import { execSync } from "child_process";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
|
|
11
|
+
function resolveLocalBin(binName: string): string | null {
|
|
12
|
+
let cwd = process.cwd();
|
|
13
|
+
// Try to find node_modules/.bin upwards
|
|
14
|
+
while (cwd !== "/") {
|
|
15
|
+
const candidate = path.join(cwd, "node_modules", ".bin", binName);
|
|
16
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
17
|
+
cwd = path.dirname(cwd);
|
|
18
|
+
}
|
|
19
|
+
// Fall back to globally installed binary via which
|
|
20
|
+
try {
|
|
21
|
+
const globalPath = execSync(`which ${binName}`, { encoding: "utf-8" }).trim();
|
|
22
|
+
if (globalPath && fs.existsSync(globalPath)) return globalPath;
|
|
23
|
+
} catch {
|
|
24
|
+
// not found globally
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function runPluginCommand(args: string[]) {
|
|
30
|
+
const domain = args[0]; // "db" or "schema"
|
|
31
|
+
const subcommand = args[1];
|
|
32
|
+
|
|
33
|
+
if (domain === "db") {
|
|
34
|
+
await dbCommand(subcommand, args);
|
|
35
|
+
} else if (domain === "schema") {
|
|
36
|
+
await schemaCommand(subcommand, args);
|
|
37
|
+
} else {
|
|
38
|
+
console.error(chalk.red(`Unknown domain command: ${domain}`));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function dbCommand(subcommand: string, rawArgs: string[]): Promise<void> {
|
|
44
|
+
const VALID_ACTIONS = ["push", "pull", "generate", "migrate", "studio", "branch"];
|
|
45
|
+
if (!subcommand || !VALID_ACTIONS.includes(subcommand)) {
|
|
46
|
+
console.error(chalk.red(`Unknown db command. Valid: ${VALID_ACTIONS.join(", ")}`));
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (subcommand === "branch") {
|
|
51
|
+
await branchCommand(rawArgs);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (subcommand === "generate") {
|
|
56
|
+
console.log("");
|
|
57
|
+
console.log(chalk.bold(" 📦 Rebase DB Generate"));
|
|
58
|
+
console.log(chalk.gray(" Step 1/2: Generating Drizzle schema from collections..."));
|
|
59
|
+
console.log("");
|
|
60
|
+
await schemaCommand("generate", rawArgs);
|
|
61
|
+
console.log("");
|
|
62
|
+
console.log(chalk.gray(" Step 2/2: Generating SQL migration files..."));
|
|
63
|
+
console.log("");
|
|
64
|
+
await runDrizzleKit("generate", rawArgs);
|
|
65
|
+
console.log("");
|
|
66
|
+
console.log(` You can now run ${chalk.bold.green("rebase db migrate")} to apply the migrations to your database.`);
|
|
67
|
+
console.log("");
|
|
68
|
+
} else {
|
|
69
|
+
await runDrizzleKit(subcommand, rawArgs);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function branchCommand(rawArgs: string[]): Promise<void> {
|
|
74
|
+
const branchAction = rawArgs[2]; // create, list, delete, info
|
|
75
|
+
|
|
76
|
+
if (!branchAction || branchAction === "--help") {
|
|
77
|
+
printBranchHelp();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Load .env for DATABASE_URL
|
|
82
|
+
try {
|
|
83
|
+
const dotenv = await import("dotenv");
|
|
84
|
+
const envPath = process.env.DOTENV_CONFIG_PATH;
|
|
85
|
+
if (envPath) {
|
|
86
|
+
dotenv.config({ path: envPath });
|
|
87
|
+
} else {
|
|
88
|
+
dotenv.config();
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
// dotenv may not be installed
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const databaseUrl = process.env.DATABASE_URL || process.env.ADMIN_CONNECTION_STRING;
|
|
95
|
+
if (!databaseUrl) {
|
|
96
|
+
console.error(chalk.red("✗ DATABASE_URL is not set. Make sure your .env file is configured."));
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Dynamic imports to avoid loading heavy deps when not needed
|
|
101
|
+
const { DatabasePoolManager } = await import("./databasePoolManager");
|
|
102
|
+
const { BranchService } = await import("./services/BranchService");
|
|
103
|
+
const { drizzle } = await import("drizzle-orm/node-postgres");
|
|
104
|
+
const { Pool } = await import("pg");
|
|
105
|
+
|
|
106
|
+
const pool = new Pool({ connectionString: databaseUrl, max: 3 });
|
|
107
|
+
const db = drizzle(pool);
|
|
108
|
+
const poolManager = new DatabasePoolManager(databaseUrl);
|
|
109
|
+
const branchService = new BranchService(db, poolManager);
|
|
110
|
+
|
|
111
|
+
// Ensure metadata table exists
|
|
112
|
+
await branchService.ensureBranchMetadataTable();
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
switch (branchAction) {
|
|
116
|
+
case "create": {
|
|
117
|
+
const name = rawArgs[3];
|
|
118
|
+
if (!name) {
|
|
119
|
+
console.error(chalk.red("✗ Branch name is required."));
|
|
120
|
+
console.log(chalk.gray(" Usage: rebase db branch create <name> [--from <source>]"));
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
let source: string | undefined;
|
|
124
|
+
const fromIdx = rawArgs.indexOf("--from");
|
|
125
|
+
if (fromIdx !== -1 && rawArgs[fromIdx + 1]) {
|
|
126
|
+
source = rawArgs[fromIdx + 1];
|
|
127
|
+
}
|
|
128
|
+
console.log("");
|
|
129
|
+
console.log(chalk.bold(" 🌿 Creating database branch..."));
|
|
130
|
+
console.log(chalk.gray(` Name: ${name}`));
|
|
131
|
+
if (source) console.log(chalk.gray(` Source: ${source}`));
|
|
132
|
+
console.log("");
|
|
133
|
+
const branch = await branchService.createBranch(name, source ? { source } : undefined);
|
|
134
|
+
console.log(chalk.green(` ✓ Branch "${branch.name}" created successfully.`));
|
|
135
|
+
console.log(chalk.gray(` Database: rb_${branch.name}`));
|
|
136
|
+
console.log(chalk.gray(` Parent: ${branch.parentDatabase}`));
|
|
137
|
+
console.log("");
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
case "list": {
|
|
142
|
+
const branches = await branchService.listBranches();
|
|
143
|
+
console.log("");
|
|
144
|
+
if (branches.length === 0) {
|
|
145
|
+
console.log(chalk.gray(" No branches found. Create one with: rebase db branch create <name>"));
|
|
146
|
+
} else {
|
|
147
|
+
console.log(chalk.bold(` 🌿 ${branches.length} branch(es):`));
|
|
148
|
+
console.log("");
|
|
149
|
+
for (const b of branches) {
|
|
150
|
+
const size = b.sizeBytes != null
|
|
151
|
+
? chalk.gray(` (${formatBytes(b.sizeBytes)})`)
|
|
152
|
+
: "";
|
|
153
|
+
const age = chalk.gray(` — created ${timeAgo(b.createdAt)}`);
|
|
154
|
+
console.log(` ${chalk.green("●")} ${chalk.bold(b.name)}${size}${age}`);
|
|
155
|
+
console.log(chalk.gray(` from ${b.parentDatabase}`));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
console.log("");
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case "delete": {
|
|
163
|
+
const name = rawArgs[3];
|
|
164
|
+
if (!name) {
|
|
165
|
+
console.error(chalk.red("✗ Branch name is required."));
|
|
166
|
+
console.log(chalk.gray(" Usage: rebase db branch delete <name>"));
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
console.log("");
|
|
170
|
+
console.log(chalk.bold(` 🗑️ Deleting branch "${name}"...`));
|
|
171
|
+
await branchService.deleteBranch(name);
|
|
172
|
+
console.log(chalk.green(` ✓ Branch "${name}" deleted.`));
|
|
173
|
+
console.log("");
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
case "info": {
|
|
178
|
+
const name = rawArgs[3];
|
|
179
|
+
if (!name) {
|
|
180
|
+
console.error(chalk.red("✗ Branch name is required."));
|
|
181
|
+
console.log(chalk.gray(" Usage: rebase db branch info <name>"));
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
const info = await branchService.getBranchInfo(name);
|
|
185
|
+
console.log("");
|
|
186
|
+
if (!info) {
|
|
187
|
+
console.error(chalk.red(` ✗ Branch "${name}" not found.`));
|
|
188
|
+
} else {
|
|
189
|
+
console.log(chalk.bold(` 🌿 Branch: ${info.name}`));
|
|
190
|
+
console.log(chalk.gray(` Database: rb_${info.name}`));
|
|
191
|
+
console.log(chalk.gray(` Parent: ${info.parentDatabase}`));
|
|
192
|
+
console.log(chalk.gray(` Created: ${info.createdAt.toISOString()}`));
|
|
193
|
+
if (info.sizeBytes != null) {
|
|
194
|
+
console.log(chalk.gray(` Size: ${formatBytes(info.sizeBytes)}`));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
console.log("");
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
default:
|
|
202
|
+
console.error(chalk.red(`Unknown branch action: "${branchAction}".`));
|
|
203
|
+
printBranchHelp();
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
} finally {
|
|
207
|
+
await poolManager.shutdown();
|
|
208
|
+
await pool.end();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function printBranchHelp() {
|
|
213
|
+
console.log(`
|
|
214
|
+
${chalk.bold("rebase db branch")} — Database branching commands
|
|
215
|
+
|
|
216
|
+
${chalk.green.bold("Usage")}
|
|
217
|
+
rebase db branch ${chalk.blue("<command>")} [options]
|
|
218
|
+
|
|
219
|
+
${chalk.green.bold("Commands")}
|
|
220
|
+
${chalk.blue.bold("create")} <name> [--from <source>] Create a new branch
|
|
221
|
+
${chalk.blue.bold("list")} List all branches
|
|
222
|
+
${chalk.blue.bold("delete")} <name> Delete a branch
|
|
223
|
+
${chalk.blue.bold("info")} <name> Show branch details
|
|
224
|
+
|
|
225
|
+
${chalk.green.bold("Examples")}
|
|
226
|
+
${chalk.gray("# Create a branch from the current database")}
|
|
227
|
+
rebase db branch create feature_auth
|
|
228
|
+
|
|
229
|
+
${chalk.gray("# Create a branch from a specific source")}
|
|
230
|
+
rebase db branch create staging --from production
|
|
231
|
+
|
|
232
|
+
${chalk.gray("# List all branches")}
|
|
233
|
+
rebase db branch list
|
|
234
|
+
|
|
235
|
+
${chalk.gray("# Delete a branch")}
|
|
236
|
+
rebase db branch delete feature_auth
|
|
237
|
+
`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function formatBytes(bytes: number): string {
|
|
241
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
242
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
243
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
244
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function timeAgo(date: Date): string {
|
|
248
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
249
|
+
if (seconds < 60) return "just now";
|
|
250
|
+
const minutes = Math.floor(seconds / 60);
|
|
251
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
252
|
+
const hours = Math.floor(minutes / 60);
|
|
253
|
+
if (hours < 24) return `${hours}h ago`;
|
|
254
|
+
const days = Math.floor(hours / 24);
|
|
255
|
+
return `${days}d ago`;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function runDrizzleKit(action: string, _rawArgs: string[]): Promise<void> {
|
|
259
|
+
const drizzleKitBin = resolveLocalBin("drizzle-kit");
|
|
260
|
+
if (!drizzleKitBin) {
|
|
261
|
+
console.error(chalk.red("✗ Could not find drizzle-kit binary."));
|
|
262
|
+
console.error(chalk.gray(" Install it with: pnpm add -D drizzle-kit"));
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
await execa(drizzleKitBin, [action], {
|
|
268
|
+
cwd: process.cwd(),
|
|
269
|
+
stdio: "inherit",
|
|
270
|
+
env: { ...process.env as Record<string, string> },
|
|
271
|
+
});
|
|
272
|
+
} catch (err: unknown) {
|
|
273
|
+
console.error(chalk.red(`✗ Failed to run drizzle-kit ${action}: ${err instanceof Error ? err.message : String(err)}`));
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function schemaCommand(subcommand: string, rawArgs: string[]): Promise<void> {
|
|
279
|
+
if (subcommand === "generate") {
|
|
280
|
+
const argsList = arg(
|
|
281
|
+
{
|
|
282
|
+
"--collections": String,
|
|
283
|
+
"--output": String,
|
|
284
|
+
"--watch": Boolean,
|
|
285
|
+
"-c": "--collections",
|
|
286
|
+
"-o": "--output",
|
|
287
|
+
"-w": "--watch",
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
argv: rawArgs.slice(2), // db generate ... or schema generate ...
|
|
291
|
+
permissive: true,
|
|
292
|
+
}
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
// Here we just invoke the local generate-drizzle-schema.ts since we are inside the postgresql-backend
|
|
296
|
+
// If installed in node_modules, __dirname is node_modules/@rebasepro/server-postgresql/dist or src.
|
|
297
|
+
const generatorScript = path.join(__dirname, "schema", "generate-drizzle-schema.ts");
|
|
298
|
+
if (!fs.existsSync(generatorScript)) {
|
|
299
|
+
console.error(chalk.red(`✗ Could not find generate-drizzle-schema.ts at ${generatorScript}`));
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const tsxBin = resolveLocalBin("tsx");
|
|
304
|
+
if (!tsxBin) {
|
|
305
|
+
console.error(chalk.red("✗ Could not find tsx binary."));
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const collectionsPath = argsList["--collections"] || path.join("..", "shared", "collections");
|
|
310
|
+
const outputPath = argsList["--output"] || path.join("src", "schema.generated.ts");
|
|
311
|
+
const watch = argsList["--watch"] || false;
|
|
312
|
+
|
|
313
|
+
console.log("");
|
|
314
|
+
console.log(chalk.bold(" 🔧 Rebase Schema Generator"));
|
|
315
|
+
console.log("");
|
|
316
|
+
|
|
317
|
+
const cmdParts = [
|
|
318
|
+
tsxBin,
|
|
319
|
+
generatorScript,
|
|
320
|
+
`--collections=${collectionsPath}`,
|
|
321
|
+
`--output=${outputPath}`,
|
|
322
|
+
];
|
|
323
|
+
if (watch) {
|
|
324
|
+
cmdParts.push("--watch");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
await execa(cmdParts[0], cmdParts.slice(1), {
|
|
329
|
+
cwd: process.cwd(),
|
|
330
|
+
stdio: "inherit",
|
|
331
|
+
env: { ...process.env as Record<string, string> },
|
|
332
|
+
});
|
|
333
|
+
} catch (err: unknown) {
|
|
334
|
+
console.error(chalk.red(`✗ Failed to run schema generator: ${err instanceof Error ? err.message : String(err)}`));
|
|
335
|
+
process.exit(1);
|
|
336
|
+
}
|
|
337
|
+
} else {
|
|
338
|
+
console.error(chalk.red(`Unknown schema command.`));
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Entry point when called directly
|
|
344
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
345
|
+
// Drop node and script path
|
|
346
|
+
runPluginCommand(process.argv.slice(2)).catch(() => process.exit(1));
|
|
347
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { CollectionRegistry } from "@rebasepro/common";
|
|
2
|
+
import type { EntityCollection } from "@rebasepro/types";
|
|
3
|
+
import { PgEnum, PgTable } from "drizzle-orm/pg-core";
|
|
4
|
+
import { Relations } from "drizzle-orm";
|
|
5
|
+
import { CollectionRegistryInterface } from "../interfaces";
|
|
6
|
+
import { getTableName } from "@rebasepro/common";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* PostgreSQL-specific collection registry.
|
|
10
|
+
* Extends the base CollectionRegistry with support for Drizzle ORM tables, enums, and relations.
|
|
11
|
+
*
|
|
12
|
+
* Satisfies CollectionRegistryInterface through inheritance from CollectionRegistry.
|
|
13
|
+
*/
|
|
14
|
+
export class PostgresCollectionRegistry extends CollectionRegistry implements CollectionRegistryInterface {
|
|
15
|
+
|
|
16
|
+
private tables = new Map<string, PgTable>();
|
|
17
|
+
private enums = new Map<string, PgEnum<[string, ...string[]]>>();
|
|
18
|
+
private relations = new Map<string, Relations>();
|
|
19
|
+
|
|
20
|
+
registerTable(table: PgTable, tableName: string) {
|
|
21
|
+
this.tables.set(tableName, table);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
getTable(tableName: string): PgTable | undefined {
|
|
25
|
+
return this.tables.get(tableName);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Checks if a specific collection has a registered table
|
|
30
|
+
*/
|
|
31
|
+
hasTableForCollection(tableName: string): boolean {
|
|
32
|
+
return this.tables.has(tableName);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Finds collections assigned to a specific driver that do not have a registered table.
|
|
37
|
+
*/
|
|
38
|
+
getCollectionsWithoutTables(driverId: string = "(default)"): EntityCollection[] {
|
|
39
|
+
const collections = this.getCollections().filter(
|
|
40
|
+
c => c.driver === driverId || (!c.driver && driverId === "(default)")
|
|
41
|
+
);
|
|
42
|
+
return collections.filter(c => !this.tables.has(getTableName(c)));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
registerEnums(enums: Record<string, PgEnum<[string, ...string[]]>>) {
|
|
46
|
+
Object.entries(enums).forEach(([name, value]) => this.enums.set(name, value));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
registerRelations(relations: Record<string, Relations>) {
|
|
50
|
+
Object.entries(relations).forEach(([name, value]) => this.relations.set(name, value));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getEnum(name: string): PgEnum<[string, ...string[]]> | undefined {
|
|
54
|
+
return this.enums.get(name);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getRelation(name: string): Relations | undefined {
|
|
58
|
+
return this.relations.get(name);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getAllEnums(): Record<string, PgEnum<[string, ...string[]]>> {
|
|
62
|
+
return Object.fromEntries(this.enums.entries());
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getAllRelations(): Record<string, Relations> {
|
|
66
|
+
return Object.fromEntries(this.relations.entries());
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get the merged schema object (tables + relations) for use with Drizzle's
|
|
71
|
+
* relational query API (`db.query`).
|
|
72
|
+
*/
|
|
73
|
+
getMergedSchema(): Record<string, unknown> {
|
|
74
|
+
const result: Record<string, unknown> = {};
|
|
75
|
+
for (const [name, table] of this.tables.entries()) {
|
|
76
|
+
result[name] = table;
|
|
77
|
+
}
|
|
78
|
+
for (const [name, relation] of this.relations.entries()) {
|
|
79
|
+
result[name] = relation;
|
|
80
|
+
}
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get the available Drizzle relation keys for a given collection path.
|
|
86
|
+
* Maps from the collection's relation property names to the Drizzle relation names
|
|
87
|
+
* defined in the schema.
|
|
88
|
+
*/
|
|
89
|
+
getRelationKeysForCollection(collectionPath: string): string[] {
|
|
90
|
+
const collection = this.getCollectionByPath(collectionPath) as import("@rebasepro/types").PostgresCollection<Record<string, unknown>, import("@rebasepro/types").User>;
|
|
91
|
+
if (!collection?.relations) return [];
|
|
92
|
+
return collection.relations.map(r => r.relationName || r.localKey || "").filter(Boolean);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
}
|
|
96
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Pool } from "pg";
|
|
2
|
+
import { drizzle } from "drizzle-orm/node-postgres";
|
|
3
|
+
|
|
4
|
+
export function createPostgresDatabaseConnection(connectionString: string, schema?: Record<string, unknown>) {
|
|
5
|
+
const pool = new Pool({
|
|
6
|
+
connectionString,
|
|
7
|
+
// Connection pool settings for resilience
|
|
8
|
+
max: 20, // Maximum number of connections in the pool
|
|
9
|
+
idleTimeoutMillis: 30000, // Close idle connections after 30 seconds
|
|
10
|
+
connectionTimeoutMillis: 10000, // Timeout for new connections
|
|
11
|
+
// Retry configuration
|
|
12
|
+
query_timeout: 30000, // Query timeout
|
|
13
|
+
statement_timeout: 30000, // Statement timeout
|
|
14
|
+
// Keep connections alive
|
|
15
|
+
keepAlive: true,
|
|
16
|
+
keepAliveInitialDelayMillis: 0
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Handle connection errors and implement reconnection logic
|
|
20
|
+
pool.on("error", (err) => {
|
|
21
|
+
console.error("Database connection error:", err);
|
|
22
|
+
|
|
23
|
+
// Handle specific timeout errors
|
|
24
|
+
if (err.message.includes("ETIMEDOUT")) {
|
|
25
|
+
console.warn("Connection timeout detected, pool will automatically retry...");
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Handle successful connections
|
|
30
|
+
pool.on("connect", (client) => {
|
|
31
|
+
console.debug("Database client connected");
|
|
32
|
+
|
|
33
|
+
// Set up client-level error handling
|
|
34
|
+
client.on("error", (err) => {
|
|
35
|
+
console.error("Database client error:", err);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Handle client removal from pool
|
|
40
|
+
pool.on("remove", (client) => {
|
|
41
|
+
console.debug("Database client removed from pool");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Create drizzle instance — pass schema when available to enable db.query relational API
|
|
45
|
+
const db = schema ? drizzle(pool, { schema }) : drizzle(pool);
|
|
46
|
+
|
|
47
|
+
// Graceful shutdown handler
|
|
48
|
+
process.on("SIGINT", async () => {
|
|
49
|
+
console.log("SIGINT: Closing database pool...");
|
|
50
|
+
await pool.end();
|
|
51
|
+
process.exit(0);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
process.on("SIGTERM", async () => {
|
|
55
|
+
console.log("SIGTERM: Closing database pool...");
|
|
56
|
+
await pool.end();
|
|
57
|
+
process.exit(0);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return { db, connectionString };
|
|
61
|
+
}
|
|
62
|
+
|