fakebase-studio 1.0.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 +85 -0
- package/bin/fakebase.js +92 -0
- package/package.json +44 -0
- package/server/dist/db.d.ts +18 -0
- package/server/dist/db.d.ts.map +1 -0
- package/server/dist/db.js +83 -0
- package/server/dist/db.js.map +1 -0
- package/server/dist/index.d.ts +2 -0
- package/server/dist/index.d.ts.map +1 -0
- package/server/dist/index.js +62 -0
- package/server/dist/index.js.map +1 -0
- package/server/dist/routes/connect.d.ts +6 -0
- package/server/dist/routes/connect.d.ts.map +1 -0
- package/server/dist/routes/connect.js +40 -0
- package/server/dist/routes/connect.js.map +1 -0
- package/server/dist/routes/connections.d.ts +3 -0
- package/server/dist/routes/connections.d.ts.map +1 -0
- package/server/dist/routes/connections.js +106 -0
- package/server/dist/routes/connections.js.map +1 -0
- package/server/dist/routes/index.d.ts +6 -0
- package/server/dist/routes/index.d.ts.map +1 -0
- package/server/dist/routes/index.js +16 -0
- package/server/dist/routes/index.js.map +1 -0
- package/server/dist/routes/indexes.d.ts +6 -0
- package/server/dist/routes/indexes.d.ts.map +1 -0
- package/server/dist/routes/indexes.js +38 -0
- package/server/dist/routes/indexes.js.map +1 -0
- package/server/dist/routes/schema.d.ts +6 -0
- package/server/dist/routes/schema.d.ts.map +1 -0
- package/server/dist/routes/schema.js +41 -0
- package/server/dist/routes/schema.js.map +1 -0
- package/server/dist/routes/tables.d.ts +6 -0
- package/server/dist/routes/tables.d.ts.map +1 -0
- package/server/dist/routes/tables.js +121 -0
- package/server/dist/routes/tables.js.map +1 -0
- package/server/dist/services/connections.service.d.ts +31 -0
- package/server/dist/services/connections.service.d.ts.map +1 -0
- package/server/dist/services/connections.service.js +125 -0
- package/server/dist/services/connections.service.js.map +1 -0
- package/server/dist/services/indexes.service.d.ts +8 -0
- package/server/dist/services/indexes.service.d.ts.map +1 -0
- package/server/dist/services/indexes.service.js +54 -0
- package/server/dist/services/indexes.service.js.map +1 -0
- package/server/dist/services/schema.service.d.ts +7 -0
- package/server/dist/services/schema.service.d.ts.map +1 -0
- package/server/dist/services/schema.service.js +165 -0
- package/server/dist/services/schema.service.js.map +1 -0
- package/server/dist/types/api.d.ts +36 -0
- package/server/dist/types/api.d.ts.map +1 -0
- package/server/dist/types/api.js +5 -0
- package/server/dist/types/api.js.map +1 -0
- package/server/dist/types/database.d.ts +29 -0
- package/server/dist/types/database.d.ts.map +1 -0
- package/server/dist/types/database.js +5 -0
- package/server/dist/types/database.js.map +1 -0
- package/server/dist/utils/errors.d.ts +15 -0
- package/server/dist/utils/errors.d.ts.map +1 -0
- package/server/dist/utils/errors.js +63 -0
- package/server/dist/utils/errors.js.map +1 -0
- package/ui/dist/assets/index-D3BM4q8p.js +70 -0
- package/ui/dist/assets/index-DvydtHdZ.css +1 -0
- package/ui/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/ui/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/ui/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/ui/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/ui/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/ui/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/ui/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/ui/dist/index.html +14 -0
- package/ui/dist/vite.svg +1 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getOrCreatePool } from "../db.js";
|
|
2
|
+
import { sendError, sendSuccess } from "../utils/errors.js";
|
|
3
|
+
import { getIndexesList } from "../services/indexes.service.js";
|
|
4
|
+
/**
|
|
5
|
+
* Register indexes routes.
|
|
6
|
+
*/
|
|
7
|
+
export async function indexesRoutes(app) {
|
|
8
|
+
const bodySchema = {
|
|
9
|
+
type: "object",
|
|
10
|
+
required: ["connectionString"],
|
|
11
|
+
properties: {
|
|
12
|
+
connectionString: { type: "string" },
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* POST /api/indexes/list
|
|
17
|
+
* Returns all indexes with columns, size, and scan statistics
|
|
18
|
+
*/
|
|
19
|
+
app.post("/api/indexes/list", { schema: { body: bodySchema } }, async (request, reply) => {
|
|
20
|
+
const { connectionString } = request.body;
|
|
21
|
+
if (!connectionString) {
|
|
22
|
+
return sendError(reply, 400, new Error("Connection string is required"));
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const pool = getOrCreatePool(connectionString);
|
|
26
|
+
await pool.query("SELECT 1 as test");
|
|
27
|
+
request.log.info("Fetching indexes list...");
|
|
28
|
+
const data = await getIndexesList(pool);
|
|
29
|
+
request.log.info({ indexCount: data.indexes.length, schemaCount: data.schemas.length }, "Indexes list fetched");
|
|
30
|
+
return sendSuccess(reply, data);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
request.log.error(error, "Error fetching indexes list");
|
|
34
|
+
return sendError(reply, 500, error);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=indexes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexes.js","sourceRoot":"","sources":["../../routes/indexes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAMhE;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAoB;IACtD,MAAM,UAAU,GAAG;QACjB,IAAI,EAAE,QAAQ;QACd,QAAQ,EAAE,CAAC,kBAAkB,CAAC;QAC9B,UAAU,EAAE;YACV,gBAAgB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;SACrC;KACF,CAAC;IAEF;;;OAGG;IACH,GAAG,CAAC,IAAI,CACN,mBAAmB,EACnB,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAChC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACvB,MAAM,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;QAE1C,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtB,OAAO,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC,CAAC;QAC3E,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,eAAe,CAAC,gBAAgB,CAAC,CAAC;YAC/C,MAAM,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;YAErC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;YAC7C,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;YACxC,OAAO,CAAC,GAAG,CAAC,IAAI,CACd,EAAE,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EACrE,sBAAsB,CACvB,CAAC;YAEF,OAAO,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,6BAA6B,CAAC,CAAC;YACxD,OAAO,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;QACtC,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../routes/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAM/C;;GAEG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA6CtE"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { getOrCreatePool } from "../db.js";
|
|
2
|
+
import { getSchemaData } from "../services/schema.service.js";
|
|
3
|
+
import { sendError, sendSuccess } from "../utils/errors.js";
|
|
4
|
+
/**
|
|
5
|
+
* Register schema-related routes.
|
|
6
|
+
*/
|
|
7
|
+
export async function schemaRoutes(app) {
|
|
8
|
+
app.post("/api/schema", {
|
|
9
|
+
schema: {
|
|
10
|
+
body: {
|
|
11
|
+
type: "object",
|
|
12
|
+
required: ["connectionString"],
|
|
13
|
+
properties: {
|
|
14
|
+
connectionString: { type: "string" },
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
}, async (request, reply) => {
|
|
19
|
+
const { connectionString } = request.body;
|
|
20
|
+
if (!connectionString) {
|
|
21
|
+
return sendError(reply, 400, new Error("Connection string is required"));
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
request.log.info("Fetching schema data...");
|
|
25
|
+
// Get or create pool for this connection string
|
|
26
|
+
const pool = getOrCreatePool(connectionString);
|
|
27
|
+
// Test the pool connection first
|
|
28
|
+
request.log.debug("Testing pool connection...");
|
|
29
|
+
await pool.query("SELECT 1 as test");
|
|
30
|
+
// Get schema data
|
|
31
|
+
const schemaData = await getSchemaData(pool);
|
|
32
|
+
request.log.info({ tableCount: schemaData.tables.length, fkCount: schemaData.foreignKeys.length }, "Successfully fetched schema data");
|
|
33
|
+
return sendSuccess(reply, schemaData);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
request.log.error(error, "Error fetching schema data");
|
|
37
|
+
return sendError(reply, 500, error);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../routes/schema.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAG5D;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAoB;IACrD,GAAG,CAAC,IAAI,CACN,aAAa,EACb;QACE,MAAM,EAAE;YACN,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,QAAQ,EAAE,CAAC,kBAAkB,CAAC;gBAC9B,UAAU,EAAE;oBACV,gBAAgB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;iBACrC;aACF;SACF;KACF,EACD,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACvB,MAAM,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;QAE1C,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtB,OAAO,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC,CAAC;QAC3E,CAAC;QAED,IAAI,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;YAE5C,gDAAgD;YAChD,MAAM,IAAI,GAAG,eAAe,CAAC,gBAAgB,CAAC,CAAC;YAE/C,iCAAiC;YACjC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;YAChD,MAAM,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;YAErC,kBAAkB;YAClB,MAAM,UAAU,GAAe,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC;YACzD,OAAO,CAAC,GAAG,CAAC,IAAI,CACd,EAAE,UAAU,EAAE,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,WAAW,CAAC,MAAM,EAAE,EAChF,kCAAkC,CACnC,CAAC;YAEF,OAAO,WAAW,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QACxC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,4BAA4B,CAAC,CAAC;YACvD,OAAO,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;QACtC,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../routes/tables.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAuB/C;;GAEG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA+IzE"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { getOrCreatePool } from "../db.js";
|
|
2
|
+
import { sendError, sendSuccess } from "../utils/errors.js";
|
|
3
|
+
/** Allow only safe identifier characters to prevent SQL injection */
|
|
4
|
+
const SAFE_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
5
|
+
const DEFAULT_ROW_LIMIT = 1000;
|
|
6
|
+
const MAX_ROW_LIMIT = 10_000;
|
|
7
|
+
/**
|
|
8
|
+
* Register table data routes.
|
|
9
|
+
*/
|
|
10
|
+
export async function tableDataRoutes(app) {
|
|
11
|
+
app.post("/api/tables/data", {
|
|
12
|
+
schema: {
|
|
13
|
+
body: {
|
|
14
|
+
type: "object",
|
|
15
|
+
required: ["connectionString", "schema", "table"],
|
|
16
|
+
properties: {
|
|
17
|
+
connectionString: { type: "string" },
|
|
18
|
+
schema: { type: "string" },
|
|
19
|
+
table: { type: "string" },
|
|
20
|
+
limit: { type: "number" },
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
}, async (request, reply) => {
|
|
25
|
+
const { connectionString, schema, table, limit: limitParam } = request.body;
|
|
26
|
+
if (!connectionString) {
|
|
27
|
+
return sendError(reply, 400, new Error("Connection string is required"));
|
|
28
|
+
}
|
|
29
|
+
if (!schema || !SAFE_IDENTIFIER.test(schema)) {
|
|
30
|
+
return sendError(reply, 400, new Error("Invalid or missing schema name"));
|
|
31
|
+
}
|
|
32
|
+
if (!table || !SAFE_IDENTIFIER.test(table)) {
|
|
33
|
+
return sendError(reply, 400, new Error("Invalid or missing table name"));
|
|
34
|
+
}
|
|
35
|
+
const limit = Math.min(Math.max(1, limitParam ?? DEFAULT_ROW_LIMIT), MAX_ROW_LIMIT);
|
|
36
|
+
try {
|
|
37
|
+
const pool = getOrCreatePool(connectionString);
|
|
38
|
+
await pool.query("SELECT 1 as test");
|
|
39
|
+
// Identifiers are validated; safe to quote and interpolate
|
|
40
|
+
const quotedSchema = `"${schema.replace(/"/g, '""')}"`;
|
|
41
|
+
const quotedTable = `"${table.replace(/"/g, '""')}"`;
|
|
42
|
+
const sql = `SELECT * FROM ${quotedSchema}.${quotedTable} LIMIT $1`;
|
|
43
|
+
request.log.info({ schema, table, limit }, "Fetching table data");
|
|
44
|
+
const result = await pool.query(sql, [limit]);
|
|
45
|
+
const rows = result.rows;
|
|
46
|
+
request.log.info({ rowCount: rows.length }, "Table data fetched");
|
|
47
|
+
return sendSuccess(reply, rows);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
request.log.error(error, "Error fetching table data");
|
|
51
|
+
return sendError(reply, 500, error);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* Get RLS (Row Level Security) info for a table
|
|
56
|
+
*/
|
|
57
|
+
app.post("/api/tables/rls", {
|
|
58
|
+
schema: {
|
|
59
|
+
body: {
|
|
60
|
+
type: "object",
|
|
61
|
+
required: ["connectionString", "schema", "table"],
|
|
62
|
+
properties: {
|
|
63
|
+
connectionString: { type: "string" },
|
|
64
|
+
schema: { type: "string" },
|
|
65
|
+
table: { type: "string" },
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
}, async (request, reply) => {
|
|
70
|
+
const { connectionString, schema, table } = request.body;
|
|
71
|
+
if (!connectionString) {
|
|
72
|
+
return sendError(reply, 400, new Error("Connection string is required"));
|
|
73
|
+
}
|
|
74
|
+
if (!schema || !SAFE_IDENTIFIER.test(schema)) {
|
|
75
|
+
return sendError(reply, 400, new Error("Invalid or missing schema name"));
|
|
76
|
+
}
|
|
77
|
+
if (!table || !SAFE_IDENTIFIER.test(table)) {
|
|
78
|
+
return sendError(reply, 400, new Error("Invalid or missing table name"));
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const pool = getOrCreatePool(connectionString);
|
|
82
|
+
// Check if RLS is enabled on the table
|
|
83
|
+
const quotedSchema = `"${schema.replace(/"/g, '""')}"`;
|
|
84
|
+
const quotedTable = `"${table.replace(/"/g, '""')}"`;
|
|
85
|
+
const rlsStatusResult = await pool.query(`SELECT relrowsecurity, relforcerowsecurity
|
|
86
|
+
FROM pg_class
|
|
87
|
+
WHERE oid = $1::regclass`, [`${quotedSchema}.${quotedTable}`]);
|
|
88
|
+
const rlsStatus = rlsStatusResult.rows[0] ?? { relrowsecurity: false, relforcerowsecurity: false };
|
|
89
|
+
// Get RLS policies
|
|
90
|
+
const policiesResult = await pool.query(`SELECT
|
|
91
|
+
policyname,
|
|
92
|
+
permissive,
|
|
93
|
+
roles,
|
|
94
|
+
cmd,
|
|
95
|
+
qual,
|
|
96
|
+
with_check
|
|
97
|
+
FROM pg_policies
|
|
98
|
+
WHERE schemaname = $1 AND tablename = $2
|
|
99
|
+
ORDER BY policyname`, [schema, table]);
|
|
100
|
+
const policies = policiesResult.rows.map((row) => ({
|
|
101
|
+
name: row.policyname,
|
|
102
|
+
permissive: row.permissive,
|
|
103
|
+
roles: row.roles,
|
|
104
|
+
command: row.cmd,
|
|
105
|
+
using: row.qual,
|
|
106
|
+
withCheck: row.with_check,
|
|
107
|
+
}));
|
|
108
|
+
request.log.info({ schema, table, policyCount: policies.length }, "Fetched RLS info");
|
|
109
|
+
return sendSuccess(reply, {
|
|
110
|
+
enabled: rlsStatus.relrowsecurity,
|
|
111
|
+
forced: rlsStatus.relforcerowsecurity,
|
|
112
|
+
policies,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
request.log.error(error, "Error fetching RLS info");
|
|
117
|
+
return sendError(reply, 500, error);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=tables.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tables.js","sourceRoot":"","sources":["../../routes/tables.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface ConnectionConfig {
|
|
2
|
+
id: string;
|
|
3
|
+
connectionString: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
lastUsed?: number;
|
|
7
|
+
success?: boolean;
|
|
8
|
+
databaseName?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ConnectionsConfig {
|
|
11
|
+
connections: ConnectionConfig[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Loads connections from the config file.
|
|
15
|
+
* Ensures every connection has an id (adds one for legacy entries).
|
|
16
|
+
*/
|
|
17
|
+
export declare function loadConnections(): Promise<ConnectionsConfig>;
|
|
18
|
+
/**
|
|
19
|
+
* Saves or updates a connection.
|
|
20
|
+
*/
|
|
21
|
+
export declare function saveConnection(connectionString: string, success?: boolean, databaseName?: string, name?: string): Promise<ConnectionConfig>;
|
|
22
|
+
/**
|
|
23
|
+
* Removes a connection by id.
|
|
24
|
+
* Returns true if a connection was removed, false if not found.
|
|
25
|
+
*/
|
|
26
|
+
export declare function removeConnectionById(id: string): Promise<boolean>;
|
|
27
|
+
/**
|
|
28
|
+
* Updates a connection's name.
|
|
29
|
+
*/
|
|
30
|
+
export declare function updateConnectionName(connectionString: string, name: string): Promise<ConnectionConfig | null>;
|
|
31
|
+
//# sourceMappingURL=connections.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connections.service.d.ts","sourceRoot":"","sources":["../../services/connections.service.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,gBAAgB,EAAE,CAAC;CACjC;AAsCD;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAgBlE;AAUD;;GAEG;AACH,wBAAsB,cAAc,CAClC,gBAAgB,EAAE,MAAM,EACxB,OAAO,GAAE,OAAe,EACxB,YAAY,CAAC,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,gBAAgB,CAAC,CAoC3B;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASvE;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,gBAAgB,EAAE,MAAM,EACxB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAalC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import fs from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import crypto from "crypto";
|
|
5
|
+
const CONFIG_DIR = path.join(os.homedir(), ".fakebase-studio");
|
|
6
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "connections.json");
|
|
7
|
+
/**
|
|
8
|
+
* Ensures the config directory exists.
|
|
9
|
+
*/
|
|
10
|
+
async function ensureConfigDir() {
|
|
11
|
+
try {
|
|
12
|
+
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
// Directory might already exist
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** Generate a stable id for a connection string (for legacy entries without id). */
|
|
19
|
+
function stableIdFromConnectionString(connectionString) {
|
|
20
|
+
return "legacy-" + crypto.createHash("sha256").update(connectionString.trim()).digest("hex").slice(0, 12);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Ensures every connection has an id (migrates legacy configs).
|
|
24
|
+
*/
|
|
25
|
+
function ensureConnectionIds(connections) {
|
|
26
|
+
return connections.map((conn) => {
|
|
27
|
+
if (conn.id) {
|
|
28
|
+
return conn;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
...conn,
|
|
32
|
+
id: stableIdFromConnectionString(conn.connectionString),
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Loads connections from the config file.
|
|
38
|
+
* Ensures every connection has an id (adds one for legacy entries).
|
|
39
|
+
*/
|
|
40
|
+
export async function loadConnections() {
|
|
41
|
+
try {
|
|
42
|
+
await ensureConfigDir();
|
|
43
|
+
const data = await fs.readFile(CONFIG_FILE, "utf-8");
|
|
44
|
+
const parsed = JSON.parse(data);
|
|
45
|
+
const raw = parsed.connections || [];
|
|
46
|
+
const hadMissingId = raw.length > 0 && raw.some((c) => !c.id);
|
|
47
|
+
const connections = ensureConnectionIds(raw);
|
|
48
|
+
if (hadMissingId) {
|
|
49
|
+
await saveConnectionsConfig({ connections });
|
|
50
|
+
}
|
|
51
|
+
return { connections };
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
// File doesn't exist or is invalid, return empty config
|
|
55
|
+
return { connections: [] };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Saves the connections config to file.
|
|
60
|
+
*/
|
|
61
|
+
async function saveConnectionsConfig(config) {
|
|
62
|
+
await ensureConfigDir();
|
|
63
|
+
await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Saves or updates a connection.
|
|
67
|
+
*/
|
|
68
|
+
export async function saveConnection(connectionString, success = false, databaseName, name) {
|
|
69
|
+
const config = await loadConnections();
|
|
70
|
+
// Check if connection string already exists
|
|
71
|
+
const existingIndex = config.connections.findIndex((conn) => conn.connectionString === connectionString);
|
|
72
|
+
const existing = existingIndex >= 0 ? config.connections[existingIndex] : null;
|
|
73
|
+
const connectionConfig = {
|
|
74
|
+
id: existing?.id ?? crypto.randomUUID(),
|
|
75
|
+
connectionString,
|
|
76
|
+
name: name || existing?.name,
|
|
77
|
+
timestamp: existing?.timestamp ?? Date.now(),
|
|
78
|
+
lastUsed: Date.now(),
|
|
79
|
+
success,
|
|
80
|
+
databaseName,
|
|
81
|
+
};
|
|
82
|
+
if (existingIndex >= 0) {
|
|
83
|
+
config.connections[existingIndex] = {
|
|
84
|
+
...existing,
|
|
85
|
+
...connectionConfig,
|
|
86
|
+
id: existing.id,
|
|
87
|
+
name: name ?? existing.name,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
config.connections.push(connectionConfig);
|
|
92
|
+
}
|
|
93
|
+
// Sort by last used (most recent first)
|
|
94
|
+
config.connections.sort((a, b) => (b.lastUsed || 0) - (a.lastUsed || 0));
|
|
95
|
+
await saveConnectionsConfig(config);
|
|
96
|
+
return connectionConfig;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Removes a connection by id.
|
|
100
|
+
* Returns true if a connection was removed, false if not found.
|
|
101
|
+
*/
|
|
102
|
+
export async function removeConnectionById(id) {
|
|
103
|
+
const config = await loadConnections();
|
|
104
|
+
const originalLength = config.connections.length;
|
|
105
|
+
config.connections = config.connections.filter((conn) => conn.id !== id);
|
|
106
|
+
const wasRemoved = config.connections.length < originalLength;
|
|
107
|
+
if (wasRemoved) {
|
|
108
|
+
await saveConnectionsConfig(config);
|
|
109
|
+
}
|
|
110
|
+
return wasRemoved;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Updates a connection's name.
|
|
114
|
+
*/
|
|
115
|
+
export async function updateConnectionName(connectionString, name) {
|
|
116
|
+
const config = await loadConnections();
|
|
117
|
+
const connection = config.connections.find((conn) => conn.connectionString === connectionString);
|
|
118
|
+
if (connection) {
|
|
119
|
+
connection.name = name;
|
|
120
|
+
await saveConnectionsConfig(config);
|
|
121
|
+
return connection;
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=connections.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connections.service.js","sourceRoot":"","sources":["../../services/connections.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,MAAM,MAAM,QAAQ,CAAC;AAgB5B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,kBAAkB,CAAC,CAAC;AAC/D,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC;AAE9D;;GAEG;AACH,KAAK,UAAU,eAAe;IAC5B,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,gCAAgC;IAClC,CAAC;AACH,CAAC;AAED,oFAAoF;AACpF,SAAS,4BAA4B,CAAC,gBAAwB;IAC5D,OAAO,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC5G,CAAC;AAED;;GAEG;AACH,SAAS,mBAAmB,CAC1B,WAAkE;IAElE,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QAC9B,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACZ,OAAO,IAAwB,CAAC;QAClC,CAAC;QACD,OAAO;YACL,GAAG,IAAI;YACP,EAAE,EAAE,4BAA4B,CAAC,IAAI,CAAC,gBAAgB,CAAC;SACpC,CAAC;IACxB,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,MAAM,eAAe,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QACrD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAgE,CAAC;QAC/F,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;QACrC,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9D,MAAM,WAAW,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;QAC7C,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,qBAAqB,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,EAAE,WAAW,EAAE,CAAC;IACzB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,wDAAwD;QACxD,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;IAC7B,CAAC;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,qBAAqB,CAAC,MAAyB;IAC5D,MAAM,eAAe,EAAE,CAAC;IACxB,MAAM,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AAC5E,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,gBAAwB,EACxB,UAAmB,KAAK,EACxB,YAAqB,EACrB,IAAa;IAEb,MAAM,MAAM,GAAG,MAAM,eAAe,EAAE,CAAC;IAEvC,4CAA4C;IAC5C,MAAM,aAAa,GAAG,MAAM,CAAC,WAAW,CAAC,SAAS,CAChD,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,KAAK,gBAAgB,CACrD,CAAC;IAEF,MAAM,QAAQ,GAAG,aAAa,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/E,MAAM,gBAAgB,GAAqB;QACzC,EAAE,EAAE,QAAQ,EAAE,EAAE,IAAI,MAAM,CAAC,UAAU,EAAE;QACvC,gBAAgB;QAChB,IAAI,EAAE,IAAI,IAAI,QAAQ,EAAE,IAAI;QAC5B,SAAS,EAAE,QAAQ,EAAE,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE;QAC5C,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE;QACpB,OAAO;QACP,YAAY;KACb,CAAC;IAEF,IAAI,aAAa,IAAI,CAAC,EAAE,CAAC;QACvB,MAAM,CAAC,WAAW,CAAC,aAAa,CAAC,GAAG;YAClC,GAAG,QAAS;YACZ,GAAG,gBAAgB;YACnB,EAAE,EAAE,QAAS,CAAC,EAAE;YAChB,IAAI,EAAE,IAAI,IAAI,QAAS,CAAC,IAAI;SAC7B,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC5C,CAAC;IAED,wCAAwC;IACxC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC;IAEzE,MAAM,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAEpC,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,EAAU;IACnD,MAAM,MAAM,GAAG,MAAM,eAAe,EAAE,CAAC;IACvC,MAAM,cAAc,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC;IACjD,MAAM,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACzE,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,cAAc,CAAC;IAC9D,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,qBAAqB,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,gBAAwB,EACxB,IAAY;IAEZ,MAAM,MAAM,GAAG,MAAM,eAAe,EAAE,CAAC;IACvC,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CACxC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,gBAAgB,KAAK,gBAAgB,CACrD,CAAC;IAEF,IAAI,UAAU,EAAE,CAAC;QACf,UAAU,CAAC,IAAI,GAAG,IAAI,CAAC;QACvB,MAAM,qBAAqB,CAAC,MAAM,CAAC,CAAC;QACpC,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Pool } from "pg";
|
|
2
|
+
import type { IndexesListData } from "../types/api.js";
|
|
3
|
+
/**
|
|
4
|
+
* Fetches all indexes with their columns, size, and scan statistics.
|
|
5
|
+
* Uses pg_catalog views and pg_stat_user_indexes for metrics.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getIndexesList(pool: Pool): Promise<IndexesListData>;
|
|
8
|
+
//# sourceMappingURL=indexes.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexes.service.d.ts","sourceRoot":"","sources":["../../services/indexes.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAC1B,OAAO,KAAK,EAAiB,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEtE;;;GAGG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,eAAe,CAAC,CAoDzE"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetches all indexes with their columns, size, and scan statistics.
|
|
3
|
+
* Uses pg_catalog views and pg_stat_user_indexes for metrics.
|
|
4
|
+
*/
|
|
5
|
+
export async function getIndexesList(pool) {
|
|
6
|
+
// Query to get indexes with their columns, size, and usage stats
|
|
7
|
+
// Uses generate_series to iterate over index columns via pg_get_indexdef
|
|
8
|
+
const indexesQuery = `
|
|
9
|
+
WITH index_columns AS (
|
|
10
|
+
SELECT
|
|
11
|
+
i.indexrelid,
|
|
12
|
+
array_agg(
|
|
13
|
+
pg_get_indexdef(i.indexrelid, k.n, true)
|
|
14
|
+
ORDER BY k.n
|
|
15
|
+
) FILTER (WHERE pg_get_indexdef(i.indexrelid, k.n, true) IS NOT NULL AND pg_get_indexdef(i.indexrelid, k.n, true) != '') AS columns
|
|
16
|
+
FROM pg_index i
|
|
17
|
+
CROSS JOIN LATERAL generate_series(1, i.indnkeyatts) AS k(n)
|
|
18
|
+
GROUP BY i.indexrelid
|
|
19
|
+
)
|
|
20
|
+
SELECT
|
|
21
|
+
n.nspname AS schema,
|
|
22
|
+
t.relname AS table_name,
|
|
23
|
+
i.relname AS index_name,
|
|
24
|
+
COALESCE(ic.columns, ARRAY[]::text[]) AS columns,
|
|
25
|
+
pg_relation_size(i.oid) AS size_bytes,
|
|
26
|
+
COALESCE(s.idx_scan, 0) AS index_scans,
|
|
27
|
+
ix.indisunique AS is_unique,
|
|
28
|
+
ix.indisprimary AS is_primary
|
|
29
|
+
FROM pg_class i
|
|
30
|
+
JOIN pg_index ix ON ix.indexrelid = i.oid
|
|
31
|
+
JOIN pg_class t ON t.oid = ix.indrelid
|
|
32
|
+
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
33
|
+
LEFT JOIN pg_stat_user_indexes s ON s.indexrelid = i.oid
|
|
34
|
+
LEFT JOIN index_columns ic ON ic.indexrelid = i.oid
|
|
35
|
+
WHERE i.relkind = 'i'
|
|
36
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
|
37
|
+
ORDER BY pg_relation_size(i.oid) DESC
|
|
38
|
+
`;
|
|
39
|
+
const result = await pool.query(indexesQuery);
|
|
40
|
+
const indexes = result.rows.map((row) => ({
|
|
41
|
+
schema: row.schema,
|
|
42
|
+
table: row.table_name,
|
|
43
|
+
indexName: row.index_name,
|
|
44
|
+
columns: row.columns || [],
|
|
45
|
+
sizeBytes: Number(row.size_bytes),
|
|
46
|
+
indexScans: Number(row.index_scans),
|
|
47
|
+
isUnique: row.is_unique === true,
|
|
48
|
+
isPrimary: row.is_primary === true,
|
|
49
|
+
}));
|
|
50
|
+
// Extract unique schemas for the dropdown
|
|
51
|
+
const schemas = [...new Set(indexes.map((idx) => idx.schema))].sort();
|
|
52
|
+
return { indexes, schemas };
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=indexes.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexes.service.js","sourceRoot":"","sources":["../../services/indexes.service.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAU;IAC7C,iEAAiE;IACjE,yEAAyE;IACzE,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BpB,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAE9C,MAAM,OAAO,GAAoB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACzD,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,KAAK,EAAE,GAAG,CAAC,UAAU;QACrB,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE;QAC1B,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC;QACjC,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;QACnC,QAAQ,EAAE,GAAG,CAAC,SAAS,KAAK,IAAI;QAChC,SAAS,EAAE,GAAG,CAAC,UAAU,KAAK,IAAI;KACnC,CAAC,CAAC,CAAC;IAEJ,0CAA0C;IAC1C,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAEtE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Pool } from "pg";
|
|
2
|
+
import type { SchemaData } from "../types/database.js";
|
|
3
|
+
/**
|
|
4
|
+
* Get complete schema data including tables, columns, and relationships
|
|
5
|
+
*/
|
|
6
|
+
export declare function getSchemaData(pool: Pool): Promise<SchemaData>;
|
|
7
|
+
//# sourceMappingURL=schema.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.service.d.ts","sourceRoot":"","sources":["../../services/schema.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAC1B,OAAO,KAAK,EAA6B,UAAU,EAAE,MAAM,sBAAsB,CAAC;AA0HlF;;GAEG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,CA8DnE"}
|