@rebasepro/server-postgresql 0.6.1 → 0.7.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/package.json +24 -18
- package/src/PostgresBackendDriver.ts +65 -44
- package/src/PostgresBootstrapper.ts +34 -2
- package/src/auth/ensure-tables.ts +56 -1
- package/src/auth/services.ts +94 -1
- package/src/cli-errors.ts +162 -0
- package/src/cli-helpers.ts +183 -0
- package/src/cli.ts +198 -251
- package/src/schema/auth-default-policies.ts +90 -0
- package/src/schema/auth-schema.ts +25 -2
- package/src/schema/doctor.ts +2 -4
- package/src/schema/generate-drizzle-schema-logic.ts +4 -3
- package/src/schema/generate-drizzle-schema.ts +3 -5
- package/src/schema/generate-postgres-ddl-logic.ts +524 -0
- package/src/schema/generate-postgres-ddl.ts +116 -0
- package/src/services/EntityPersistService.ts +10 -8
- package/src/services/entityService.ts +28 -3
- package/src/utils/pg-error-utils.ts +16 -0
- package/src/utils/table-classification.ts +16 -0
- package/src/websocket.ts +9 -0
- package/test/auth-default-policies.test.ts +89 -0
- package/test/cli-helpers-extended.test.ts +324 -0
- package/test/cli-helpers.test.ts +59 -0
- package/test/connection.test.ts +292 -0
- package/test/databasePoolManager.test.ts +289 -0
- package/test/doctor-extended.test.ts +443 -0
- package/test/e2e/db-e2e.test.ts +293 -0
- package/test/e2e/pg-setup.ts +79 -0
- package/test/entity-persist-composite-keys.test.ts +451 -0
- package/test/generate-postgres-ddl-edge-cases.test.ts +716 -0
- package/test/generate-postgres-ddl.test.ts +300 -0
- package/test/mfa-service.test.ts +544 -0
- package/test/pg-error-utils.test.ts +50 -1
- package/test/realtimeService-channels.test.ts +696 -0
- package/test/unmapped-tables-safety.test.ts +55 -342
- package/vitest.e2e.config.ts +10 -0
- package/build-errors.txt +0 -37
- package/dist/PostgresAdapter.d.ts +0 -6
- package/dist/PostgresBackendDriver.d.ts +0 -110
- package/dist/PostgresBootstrapper.d.ts +0 -46
- package/dist/auth/ensure-tables.d.ts +0 -10
- package/dist/auth/services.d.ts +0 -231
- package/dist/cli.d.ts +0 -1
- package/dist/collections/PostgresCollectionRegistry.d.ts +0 -47
- package/dist/connection.d.ts +0 -65
- package/dist/data-transformer.d.ts +0 -55
- package/dist/databasePoolManager.d.ts +0 -20
- package/dist/history/HistoryService.d.ts +0 -71
- package/dist/history/ensure-history-table.d.ts +0 -7
- package/dist/index.d.ts +0 -14
- package/dist/index.es.js +0 -10803
- package/dist/index.es.js.map +0 -1
- package/dist/interfaces.d.ts +0 -18
- package/dist/schema/auth-schema.d.ts +0 -2149
- package/dist/schema/doctor-cli.d.ts +0 -2
- package/dist/schema/doctor.d.ts +0 -52
- package/dist/schema/generate-drizzle-schema-logic.d.ts +0 -2
- package/dist/schema/generate-drizzle-schema.d.ts +0 -1
- package/dist/schema/introspect-db-inference.d.ts +0 -5
- package/dist/schema/introspect-db-logic.d.ts +0 -118
- package/dist/schema/introspect-db.d.ts +0 -1
- package/dist/schema/test-schema.d.ts +0 -24
- package/dist/services/BranchService.d.ts +0 -47
- package/dist/services/EntityFetchService.d.ts +0 -214
- package/dist/services/EntityPersistService.d.ts +0 -40
- package/dist/services/RelationService.d.ts +0 -98
- package/dist/services/entity-helpers.d.ts +0 -38
- package/dist/services/entityService.d.ts +0 -110
- package/dist/services/index.d.ts +0 -4
- package/dist/services/realtimeService.d.ts +0 -220
- package/dist/types.d.ts +0 -3
- package/dist/utils/drizzle-conditions.d.ts +0 -138
- package/dist/utils/pg-array-null-patch.d.ts +0 -16
- package/dist/utils/pg-error-utils.d.ts +0 -55
- package/dist/websocket.d.ts +0 -11
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import { logger } from "@rebasepro/server-core";
|
|
7
|
+
import type { EntityCollection, Relation } from "@rebasepro/types";
|
|
8
|
+
|
|
9
|
+
const getHelpersDirname = () => {
|
|
10
|
+
try {
|
|
11
|
+
return eval("__dirname");
|
|
12
|
+
} catch {
|
|
13
|
+
const err = new Error();
|
|
14
|
+
const stackLine = err.stack?.split("\n")[1] || "";
|
|
15
|
+
const match = stackLine.match(/\((.*?):\d+:\d+\)/) || stackLine.match(/at (.*?):\d+:\d+/);
|
|
16
|
+
if (match && match[1]) {
|
|
17
|
+
let cleanPath = match[1];
|
|
18
|
+
if (cleanPath.startsWith("file://")) {
|
|
19
|
+
cleanPath = fileURLToPath(cleanPath);
|
|
20
|
+
}
|
|
21
|
+
return path.dirname(cleanPath);
|
|
22
|
+
}
|
|
23
|
+
return process.cwd();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const __helpersDirname = getHelpersDirname();
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
export function resolveLocalBin(binName: string): string | null {
|
|
31
|
+
// Try to find node_modules/.bin upwards from __helpersDirname first (package-relative)
|
|
32
|
+
let dir = __helpersDirname;
|
|
33
|
+
while (true) {
|
|
34
|
+
const candidate = path.join(dir, "node_modules", ".bin", binName);
|
|
35
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
36
|
+
const parent = path.dirname(dir);
|
|
37
|
+
if (parent === dir) break;
|
|
38
|
+
dir = parent;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let cwd = process.cwd();
|
|
42
|
+
// Try to find node_modules/.bin upwards from process.cwd()
|
|
43
|
+
while (true) {
|
|
44
|
+
const candidate = path.join(cwd, "node_modules", ".bin", binName);
|
|
45
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
46
|
+
const parent = path.dirname(cwd);
|
|
47
|
+
if (parent === cwd) break;
|
|
48
|
+
cwd = parent;
|
|
49
|
+
}
|
|
50
|
+
// Fall back to globally installed binary via which/where
|
|
51
|
+
try {
|
|
52
|
+
const cmd = process.platform === "win32" ? `where ${binName}` : `which ${binName}`;
|
|
53
|
+
const globalPath = execSync(cmd, { encoding: "utf-8" }).trim().split("\n")[0].trim();
|
|
54
|
+
if (globalPath && fs.existsSync(globalPath)) return globalPath;
|
|
55
|
+
} catch {
|
|
56
|
+
// not found globally
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function getTableIncludesFromCollections(collections: EntityCollection[]): Promise<string[]> {
|
|
62
|
+
const { getTableName, resolveCollectionRelations } = await import("@rebasepro/common");
|
|
63
|
+
const { isPostgresCollection } = await import("@rebasepro/types");
|
|
64
|
+
|
|
65
|
+
const includes: string[] = [];
|
|
66
|
+
for (const col of collections) {
|
|
67
|
+
const tableName = getTableName(col);
|
|
68
|
+
const schema = isPostgresCollection(col) && col.schema ? col.schema : "public";
|
|
69
|
+
if (tableName) {
|
|
70
|
+
includes.push(`${schema}.${tableName}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const resolvedRelations = resolveCollectionRelations(col);
|
|
74
|
+
for (const relation of Object.values(resolvedRelations) as Relation[]) {
|
|
75
|
+
if (relation.through) {
|
|
76
|
+
const junctionTableName = relation.through.table;
|
|
77
|
+
const targetCollection = relation.target();
|
|
78
|
+
const targetSchema = isPostgresCollection(targetCollection) && targetCollection.schema ? targetCollection.schema : "public";
|
|
79
|
+
includes.push(`${targetSchema}.${junctionTableName}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return Array.from(new Set(includes));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function getTableIncludes(collectionsPath: string): Promise<string[]> {
|
|
88
|
+
const resolvedPath = path.resolve(collectionsPath);
|
|
89
|
+
const collections: EntityCollection[] = [];
|
|
90
|
+
if (fs.existsSync(resolvedPath)) {
|
|
91
|
+
const stats = fs.statSync(resolvedPath);
|
|
92
|
+
if (stats.isDirectory()) {
|
|
93
|
+
const files = fs.readdirSync(resolvedPath);
|
|
94
|
+
for (const file of files) {
|
|
95
|
+
if ((file.endsWith(".ts") || file.endsWith(".js")) &&
|
|
96
|
+
!file.includes(".test.") &&
|
|
97
|
+
!file.endsWith(".d.ts") &&
|
|
98
|
+
file !== "index.ts" && file !== "index.js") {
|
|
99
|
+
try {
|
|
100
|
+
const filePath = path.join(resolvedPath, file);
|
|
101
|
+
const fileUrl = pathToFileURL(filePath).href;
|
|
102
|
+
const module = await import(fileUrl);
|
|
103
|
+
if (module && module.default) {
|
|
104
|
+
collections.push(module.default);
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
// ignore
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return getTableIncludesFromCollections(collections);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function getDevDatabaseUrl(databaseUrl: string): string {
|
|
118
|
+
try {
|
|
119
|
+
const parsed = new URL(databaseUrl);
|
|
120
|
+
const dbName = parsed.pathname.slice(1);
|
|
121
|
+
parsed.pathname = `/${dbName}_dev_diff`;
|
|
122
|
+
return parsed.toString();
|
|
123
|
+
} catch {
|
|
124
|
+
return databaseUrl + "_dev_diff";
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function ensureDevDatabaseExists(databaseUrl: string, devDatabaseUrl: string) {
|
|
129
|
+
try {
|
|
130
|
+
const { Client } = await import("pg");
|
|
131
|
+
const parsed = new URL(databaseUrl);
|
|
132
|
+
const devDbName = new URL(devDatabaseUrl).pathname.slice(1);
|
|
133
|
+
|
|
134
|
+
parsed.pathname = "/postgres";
|
|
135
|
+
const client = new Client({ connectionString: parsed.toString() });
|
|
136
|
+
await client.connect();
|
|
137
|
+
try {
|
|
138
|
+
const res = await client.query("SELECT 1 FROM pg_database WHERE datname = $1", [devDbName]);
|
|
139
|
+
if (res.rowCount === 0) {
|
|
140
|
+
await client.query(`CREATE DATABASE "${devDbName}"`);
|
|
141
|
+
logger.info(chalk.gray(` ✓ Created validation database "${devDbName}"`));
|
|
142
|
+
}
|
|
143
|
+
} finally {
|
|
144
|
+
await client.end();
|
|
145
|
+
}
|
|
146
|
+
} catch {
|
|
147
|
+
// Ignore, let Atlas handle connection failures
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function getTableExcludes(databaseUrl: string, collectionsPath: string): Promise<string[]> {
|
|
152
|
+
const includes = await getTableIncludes(collectionsPath);
|
|
153
|
+
const excludes: string[] = ["atlas_schema_revisions.*", "auth.*"];
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const { Client } = await import("pg");
|
|
157
|
+
const client = new Client({ connectionString: databaseUrl });
|
|
158
|
+
await client.connect();
|
|
159
|
+
try {
|
|
160
|
+
const res = await client.query(`
|
|
161
|
+
SELECT table_schema || '.' || table_name AS full_name
|
|
162
|
+
FROM information_schema.tables
|
|
163
|
+
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
164
|
+
AND table_type IN ('BASE TABLE', 'VIEW');
|
|
165
|
+
`);
|
|
166
|
+
|
|
167
|
+
const existingTables = res.rows.map((row: { full_name: string }) => row.full_name);
|
|
168
|
+
|
|
169
|
+
for (const table of existingTables) {
|
|
170
|
+
if (!includes.includes(table)) {
|
|
171
|
+
excludes.push(table);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
} finally {
|
|
175
|
+
await client.end();
|
|
176
|
+
}
|
|
177
|
+
} catch (err) {
|
|
178
|
+
logger.warn(chalk.yellow(` ⚠️ Failed to query database for unmapped tables: ${err instanceof Error ? err.message : String(err)}`));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return excludes;
|
|
182
|
+
}
|
|
183
|
+
|