admindb 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -20
- package/dist/app.js +36 -25
- package/dist/core/context.d.ts +1 -0
- package/dist/core/context.js +2 -0
- package/dist/core/errors.d.ts +1 -0
- package/dist/core/errors.js +39 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +20 -0
- package/dist/core/result.d.ts +1 -0
- package/dist/core/result.js +10 -0
- package/dist/core/router.d.ts +1 -0
- package/dist/core/router.js +37 -0
- package/dist/data/chain.js +1 -1
- package/dist/data/engine.js +1 -1
- package/dist/db/database.d.ts +1 -1
- package/dist/db/database.js +9 -31
- package/dist/db/dialects/index.d.ts +1 -0
- package/dist/db/dialects/index.js +58 -0
- package/dist/db/dialects/postgres/ddl.d.ts +1 -0
- package/dist/db/dialects/postgres/ddl.js +107 -0
- package/dist/db/dialects/postgres/dialect.d.ts +1 -0
- package/dist/db/dialects/postgres/dialect.js +28 -0
- package/dist/db/dialects/postgres/introspector.d.ts +1 -0
- package/dist/db/dialects/postgres/introspector.js +265 -0
- package/dist/db/dialects/postgres/types.d.ts +1 -0
- package/dist/db/dialects/postgres/types.js +83 -0
- package/dist/db/dialects/sqlite/ddl.d.ts +1 -0
- package/dist/db/dialects/sqlite/ddl.js +244 -0
- package/dist/db/dialects/sqlite/dialect.d.ts +1 -0
- package/dist/db/dialects/sqlite/dialect.js +25 -0
- package/dist/db/dialects/sqlite/introspector.d.ts +1 -0
- package/dist/db/dialects/sqlite/introspector.js +214 -0
- package/dist/db/dialects/sqlite/types.d.ts +1 -0
- package/dist/db/dialects/sqlite/types.js +69 -0
- package/dist/db/dialects/types.d.ts +1 -0
- package/dist/db/dialects/types.js +15 -0
- package/dist/db/export.js +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/manager.d.ts +1 -1
- package/dist/db/manager.js +101 -21
- package/dist/db/postgres.d.ts +1 -1
- package/dist/db/postgres.js +20 -284
- package/dist/db/types.d.ts +1 -1
- package/dist/db/types.js +0 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -1
- package/dist/modules/databases/databases.routes.d.ts +1 -0
- package/dist/modules/databases/databases.routes.js +150 -0
- package/dist/modules/databases/databases.service.d.ts +1 -0
- package/dist/modules/databases/databases.service.js +134 -0
- package/dist/modules/databases/index.d.ts +1 -0
- package/dist/{routes → modules/databases}/index.js +2 -3
- package/dist/modules/erd/erd.routes.d.ts +1 -0
- package/dist/modules/erd/erd.routes.js +32 -0
- package/dist/modules/erd/erd.service.d.ts +1 -0
- package/dist/modules/erd/erd.service.js +39 -0
- package/dist/modules/erd/index.d.ts +1 -0
- package/dist/modules/erd/index.js +18 -0
- package/dist/modules/index.d.ts +1 -0
- package/dist/modules/index.js +62 -0
- package/dist/modules/query/index.d.ts +1 -0
- package/dist/modules/query/index.js +18 -0
- package/dist/modules/query/query.routes.d.ts +1 -0
- package/dist/modules/query/query.routes.js +54 -0
- package/dist/modules/query/query.service.d.ts +1 -0
- package/dist/modules/query/query.service.js +95 -0
- package/dist/modules/rows/blob.helper.d.ts +1 -0
- package/dist/modules/rows/blob.helper.js +40 -0
- package/dist/modules/rows/display.helper.d.ts +1 -0
- package/dist/modules/rows/display.helper.js +73 -0
- package/dist/modules/rows/index.d.ts +1 -0
- package/dist/modules/rows/index.js +20 -0
- package/dist/modules/rows/rows.routes.d.ts +1 -0
- package/dist/modules/rows/rows.routes.js +555 -0
- package/dist/modules/rows/rows.service.d.ts +1 -0
- package/dist/modules/rows/rows.service.js +433 -0
- package/dist/modules/schema/index.d.ts +1 -0
- package/dist/modules/schema/index.js +18 -0
- package/dist/modules/schema/schema.routes.d.ts +1 -0
- package/dist/modules/schema/schema.routes.js +171 -0
- package/dist/modules/schema/schema.service.d.ts +1 -0
- package/dist/modules/schema/schema.service.js +58 -0
- package/dist/modules/seed/index.d.ts +1 -0
- package/dist/modules/seed/index.js +18 -0
- package/dist/modules/seed/seed.routes.d.ts +1 -0
- package/dist/modules/seed/seed.routes.js +212 -0
- package/dist/modules/seed/seed.service.d.ts +1 -0
- package/dist/{routes/api/helpers.js → modules/seed/seed.service.js} +86 -109
- package/dist/modules/tables/index.d.ts +1 -0
- package/dist/modules/tables/index.js +18 -0
- package/dist/modules/tables/tables.routes.d.ts +1 -0
- package/dist/modules/tables/tables.routes.js +194 -0
- package/dist/modules/tables/tables.service.d.ts +1 -0
- package/dist/modules/tables/tables.service.js +218 -0
- package/dist/public/js/browse.js +1 -1
- package/dist/public/js/query.js +1 -1
- package/dist/types/api.d.ts +1 -1
- package/dist/utils/common.d.ts +1 -1
- package/dist/utils/common.js +1 -3
- package/dist/utils/stream.d.ts +1 -0
- package/dist/utils/stream.js +131 -0
- package/dist/views/pages/error.hbs +10 -1
- package/dist/views/pages/home.hbs +1 -1
- package/dist/views/pages/query.hbs +1 -1
- package/dist/views/pages/seed-select.hbs +1 -252
- package/dist/views/pages/seed.hbs +1 -307
- package/dist/views/pages/table.hbs +1 -1
- package/dist/views/partials/sidebar.hbs +1 -1
- package/docs/API.md +4 -17
- package/docs/EXAMPLES.md +579 -579
- package/docs/SECURITY.md +265 -265
- package/package.json +4 -2
- package/dist/db/introspection.d.ts +0 -1
- package/dist/db/introspection.js +0 -164
- package/dist/routes/api/erd.d.ts +0 -1
- package/dist/routes/api/erd.js +0 -45
- package/dist/routes/api/helpers.d.ts +0 -1
- package/dist/routes/api/import-export.d.ts +0 -1
- package/dist/routes/api/import-export.js +0 -84
- package/dist/routes/api/index.d.ts +0 -1
- package/dist/routes/api/index.js +0 -55
- package/dist/routes/api/query.d.ts +0 -1
- package/dist/routes/api/query.js +0 -77
- package/dist/routes/api/rows.d.ts +0 -1
- package/dist/routes/api/rows.js +0 -373
- package/dist/routes/api/seed.d.ts +0 -1
- package/dist/routes/api/seed.js +0 -180
- package/dist/routes/api/tables.d.ts +0 -1
- package/dist/routes/api/tables.js +0 -230
- package/dist/routes/databases.d.ts +0 -1
- package/dist/routes/databases.js +0 -212
- package/dist/routes/index.d.ts +0 -1
- package/dist/routes/pages.d.ts +0 -1
- package/dist/routes/pages.js +0 -540
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SqliteIntrospector = void 0;
|
|
4
|
+
const generator_1 = require("../../../sql/generator");
|
|
5
|
+
class SqliteIntrospector {
|
|
6
|
+
getNativeDb(driver) {
|
|
7
|
+
if (driver && typeof driver === 'object' && 'db' in driver) {
|
|
8
|
+
return driver.db;
|
|
9
|
+
}
|
|
10
|
+
return driver;
|
|
11
|
+
}
|
|
12
|
+
async listTables(driver) {
|
|
13
|
+
try {
|
|
14
|
+
const db = this.getNativeDb(driver);
|
|
15
|
+
const rows = db
|
|
16
|
+
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name")
|
|
17
|
+
.all();
|
|
18
|
+
return { success: true, data: rows.map((r) => ({ name: r.name })) };
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
getTableNames(db) {
|
|
25
|
+
return db
|
|
26
|
+
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'")
|
|
27
|
+
.all().map((r) => r.name);
|
|
28
|
+
}
|
|
29
|
+
getIncomingFks(db, targetTable) {
|
|
30
|
+
const refs = [];
|
|
31
|
+
for (const t of this.getTableNames(db)) {
|
|
32
|
+
if (t === targetTable)
|
|
33
|
+
continue;
|
|
34
|
+
try {
|
|
35
|
+
const fks = db.prepare(`PRAGMA foreign_key_list(${(0, generator_1.quoteIdentifier)(t)})`).all();
|
|
36
|
+
for (const fk of fks) {
|
|
37
|
+
if (fk.table === targetTable)
|
|
38
|
+
refs.push({ table: t, from: fk.from, to: fk.to ?? '' });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch { }
|
|
42
|
+
}
|
|
43
|
+
return refs;
|
|
44
|
+
}
|
|
45
|
+
async getTableInfo(driver, table) {
|
|
46
|
+
try {
|
|
47
|
+
const db = this.getNativeDb(driver);
|
|
48
|
+
const cols = db.prepare(`PRAGMA table_info(${(0, generator_1.quoteIdentifier)(table)})`).all();
|
|
49
|
+
const fks = db.prepare(`PRAGMA foreign_key_list(${(0, generator_1.quoteIdentifier)(table)})`).all();
|
|
50
|
+
const primaryKey = cols
|
|
51
|
+
.filter((c) => c.pk > 0)
|
|
52
|
+
.sort((a, b) => a.pk - b.pk)
|
|
53
|
+
.map((c) => c.name);
|
|
54
|
+
const indexRows = db
|
|
55
|
+
.prepare(`PRAGMA index_list(${(0, generator_1.quoteIdentifier)(table)})`)
|
|
56
|
+
.all();
|
|
57
|
+
const indexes = indexRows.map((ix) => {
|
|
58
|
+
const ixCols = db.prepare(`PRAGMA index_info(${(0, generator_1.quoteIdentifier)(ix.name)})`).all().map((c) => c.name);
|
|
59
|
+
const sqlRow = db
|
|
60
|
+
.prepare("SELECT sql FROM sqlite_master WHERE type = 'index' AND name = ?")
|
|
61
|
+
.get(ix.name);
|
|
62
|
+
return {
|
|
63
|
+
name: ix.name,
|
|
64
|
+
unique: Boolean(ix.unique),
|
|
65
|
+
partial: ix.partial,
|
|
66
|
+
origin: ix.origin,
|
|
67
|
+
columns: ixCols,
|
|
68
|
+
sql: sqlRow?.sql ?? null,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
const ddlRow = db
|
|
72
|
+
.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?")
|
|
73
|
+
.get(table);
|
|
74
|
+
return {
|
|
75
|
+
success: true,
|
|
76
|
+
data: {
|
|
77
|
+
table,
|
|
78
|
+
columns: cols.map((c) => ({
|
|
79
|
+
cid: c.cid,
|
|
80
|
+
name: c.name,
|
|
81
|
+
type: c.type,
|
|
82
|
+
notnull: c.notnull,
|
|
83
|
+
dflt_value: c.dflt_value,
|
|
84
|
+
pk: c.pk,
|
|
85
|
+
})),
|
|
86
|
+
foreignKeys: fks.map((f) => ({
|
|
87
|
+
id: f.id,
|
|
88
|
+
seq: f.seq,
|
|
89
|
+
table: f.table,
|
|
90
|
+
from: f.from,
|
|
91
|
+
to: f.to,
|
|
92
|
+
on_update: f.on_update,
|
|
93
|
+
on_delete: f.on_delete,
|
|
94
|
+
})),
|
|
95
|
+
primaryKey,
|
|
96
|
+
indexes,
|
|
97
|
+
sql: ddlRow?.sql ?? null,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async getSchema(driver, table) {
|
|
106
|
+
try {
|
|
107
|
+
const db = this.getNativeDb(driver);
|
|
108
|
+
const infoRes = await this.getTableInfo(driver, table);
|
|
109
|
+
if (!infoRes.success || !infoRes.data || infoRes.data.columns.length === 0) {
|
|
110
|
+
return { success: false, error: `Table "${table}" does not exist.` };
|
|
111
|
+
}
|
|
112
|
+
const info = infoRes.data;
|
|
113
|
+
const { foreignKeys, indexes } = info;
|
|
114
|
+
const uniqueCols = new Set();
|
|
115
|
+
const indexedCols = new Set();
|
|
116
|
+
for (const ix of indexes) {
|
|
117
|
+
if (ix.unique)
|
|
118
|
+
ix.columns.forEach((c) => uniqueCols.add(c));
|
|
119
|
+
if (ix.origin === 'c')
|
|
120
|
+
ix.columns.forEach((c) => indexedCols.add(c));
|
|
121
|
+
}
|
|
122
|
+
const refs = this.getIncomingFks(db, table);
|
|
123
|
+
const refsByTo = new Map();
|
|
124
|
+
for (const r of refs) {
|
|
125
|
+
const arr = refsByTo.get(r.to) ?? [];
|
|
126
|
+
arr.push({ table: r.table, from: r.from });
|
|
127
|
+
refsByTo.set(r.to, arr);
|
|
128
|
+
}
|
|
129
|
+
const fkByFrom = new Map(foreignKeys.map((fk) => [fk.from, fk]));
|
|
130
|
+
const columns = info.columns.map((c) => {
|
|
131
|
+
const unique = c.pk > 0 || uniqueCols.has(c.name);
|
|
132
|
+
const indexed = indexedCols.has(c.name);
|
|
133
|
+
const referencedBy = refsByTo.get(c.name) ?? [];
|
|
134
|
+
const fk = fkByFrom.get(c.name) ?? null;
|
|
135
|
+
const dropBlockers = [];
|
|
136
|
+
if (c.pk > 0)
|
|
137
|
+
dropBlockers.push('Is part of the primary key');
|
|
138
|
+
if (unique && c.pk === 0)
|
|
139
|
+
dropBlockers.push('Has a UNIQUE constraint');
|
|
140
|
+
if (indexed)
|
|
141
|
+
dropBlockers.push('Is used by an index');
|
|
142
|
+
if (referencedBy.length)
|
|
143
|
+
dropBlockers.push(`Referenced by a foreign key in "${referencedBy[0].table}"`);
|
|
144
|
+
if (fk)
|
|
145
|
+
dropBlockers.push('Is part of a foreign key');
|
|
146
|
+
return {
|
|
147
|
+
...c,
|
|
148
|
+
unique,
|
|
149
|
+
indexed,
|
|
150
|
+
referencedBy,
|
|
151
|
+
fk,
|
|
152
|
+
canDrop: dropBlockers.length === 0,
|
|
153
|
+
dropBlockers,
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
return {
|
|
157
|
+
success: true,
|
|
158
|
+
data: {
|
|
159
|
+
table,
|
|
160
|
+
columns,
|
|
161
|
+
foreignKeys,
|
|
162
|
+
indexes,
|
|
163
|
+
references: refs,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async getReferencingTables(driver, table) {
|
|
172
|
+
try {
|
|
173
|
+
const db = this.getNativeDb(driver);
|
|
174
|
+
const refs = this.getIncomingFks(db, table);
|
|
175
|
+
const byTable = new Map();
|
|
176
|
+
for (const r of refs) {
|
|
177
|
+
const arr = byTable.get(r.table) ?? [];
|
|
178
|
+
arr.push({ from: r.from, to: r.to });
|
|
179
|
+
byTable.set(r.table, arr);
|
|
180
|
+
}
|
|
181
|
+
const result = [];
|
|
182
|
+
for (const [t, tRefs] of byTable) {
|
|
183
|
+
let refCount = 0;
|
|
184
|
+
try {
|
|
185
|
+
const conds = tRefs.map((r) => `${(0, generator_1.quoteIdentifier)(r.from)} IS NOT NULL`);
|
|
186
|
+
const row = db
|
|
187
|
+
.prepare(`SELECT COUNT(*) AS c FROM ${(0, generator_1.quoteIdentifier)(t)} WHERE ${conds.join(' OR ')}`)
|
|
188
|
+
.get();
|
|
189
|
+
refCount = Number(row.c);
|
|
190
|
+
}
|
|
191
|
+
catch { }
|
|
192
|
+
result.push({ table: t, refs: tRefs, refCount });
|
|
193
|
+
}
|
|
194
|
+
result.sort((a, b) => a.table.localeCompare(b.table));
|
|
195
|
+
return { success: true, data: result };
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
async getCreateStatement(driver, table) {
|
|
202
|
+
try {
|
|
203
|
+
const db = this.getNativeDb(driver);
|
|
204
|
+
const row = db
|
|
205
|
+
.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?")
|
|
206
|
+
.get(table);
|
|
207
|
+
return { success: true, data: row?.sql ?? null };
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
exports.SqliteIntrospector = SqliteIntrospector;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SQLITE_DESIGNER_TYPES:readonly ["TEXT","INTEGER","REAL","BLOB","BOOLEAN","DATE","DATETIME","NUMERIC","VARCHAR(255)","JSON"];import type {MappedType} from '../types';export type {MappedType};export declare function mapSqliteColumnType(type:string):MappedType;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SQLITE_DESIGNER_TYPES = void 0;
|
|
4
|
+
exports.mapSqliteColumnType = mapSqliteColumnType;
|
|
5
|
+
exports.SQLITE_DESIGNER_TYPES = [
|
|
6
|
+
'TEXT',
|
|
7
|
+
'INTEGER',
|
|
8
|
+
'REAL',
|
|
9
|
+
'BLOB',
|
|
10
|
+
'BOOLEAN',
|
|
11
|
+
'DATE',
|
|
12
|
+
'DATETIME',
|
|
13
|
+
'NUMERIC',
|
|
14
|
+
'VARCHAR(255)',
|
|
15
|
+
'JSON',
|
|
16
|
+
];
|
|
17
|
+
function mapSqliteColumnType(type) {
|
|
18
|
+
const t = String(type ?? '').trim().toUpperCase();
|
|
19
|
+
if (!t)
|
|
20
|
+
return { sqlType: 'TEXT' };
|
|
21
|
+
switch (t) {
|
|
22
|
+
case 'INTEGER':
|
|
23
|
+
case 'INT':
|
|
24
|
+
case 'TINYINT':
|
|
25
|
+
case 'BIGINT':
|
|
26
|
+
case 'SMALLINT':
|
|
27
|
+
case 'SERIAL':
|
|
28
|
+
case 'BIGSERIAL':
|
|
29
|
+
case 'SMALLSERIAL':
|
|
30
|
+
return { sqlType: 'INTEGER' };
|
|
31
|
+
case 'TEXT':
|
|
32
|
+
case 'VARCHAR':
|
|
33
|
+
case 'CHAR':
|
|
34
|
+
case 'CLOB':
|
|
35
|
+
case 'STRING':
|
|
36
|
+
case 'CITEXT':
|
|
37
|
+
return { sqlType: 'TEXT' };
|
|
38
|
+
case 'REAL':
|
|
39
|
+
case 'FLOAT':
|
|
40
|
+
case 'DOUBLE':
|
|
41
|
+
case 'DECIMAL':
|
|
42
|
+
case 'NUMERIC':
|
|
43
|
+
case 'NUMBER':
|
|
44
|
+
case 'MONEY':
|
|
45
|
+
return { sqlType: 'REAL' };
|
|
46
|
+
case 'BLOB':
|
|
47
|
+
case 'BINARY':
|
|
48
|
+
case 'VARBINARY':
|
|
49
|
+
case 'BYTEA':
|
|
50
|
+
return { sqlType: 'BLOB' };
|
|
51
|
+
case 'BOOLEAN':
|
|
52
|
+
case 'BOOL':
|
|
53
|
+
return { sqlType: 'INTEGER' };
|
|
54
|
+
case 'DATE':
|
|
55
|
+
return { sqlType: 'DATETIME', defaultAuto: 'CURRENT_TIMESTAMP' };
|
|
56
|
+
case 'DATETIME':
|
|
57
|
+
case 'TIMESTAMP':
|
|
58
|
+
case 'TIMESTAMPTZ':
|
|
59
|
+
case 'TIME':
|
|
60
|
+
case 'JSON':
|
|
61
|
+
case 'JSONB':
|
|
62
|
+
case 'UUID':
|
|
63
|
+
return { sqlType: t };
|
|
64
|
+
default:
|
|
65
|
+
if (/^[A-Za-z0-9_(),\s\[\]]+$/.test(t))
|
|
66
|
+
return { sqlType: t };
|
|
67
|
+
return { sqlType: 'TEXT' };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type {Result} from '../../core/result';import type {ColumnDef,IndexDef} from '../../sql/generator';import type {DatabaseDialect,MutationResult,ReferencingTableInfo,SchemaInfo,SQLInputValue,TableInfoData,TableListItem} from '../types';export interface MappedType{sqlType:string;defaultAuto?:string;}export interface DialectCapabilities{supportsCascadeDrop:boolean;supportsNativeAlterColumn:boolean;supportsRegexFilter:boolean;supportsJsonb:boolean;}export declare const SQLITE_CAPABILITIES:DialectCapabilities;export declare const POSTGRES_CAPABILITIES:DialectCapabilities;export interface IDatabaseDriver{readonly dialect:DatabaseDialect;readonly isReadOnly:boolean;readonly path:string;all<T=unknown>(sql:string,params?:SQLInputValue[]):Promise<Result<T[]>>;run(sql:string,params?:SQLInputValue[]):Promise<Result<MutationResult>>;execResult(sql:string):Promise<Result<{changes?:number;}>>;close():Promise<void>|void;}export interface ISchemaIntrospector<D=unknown>{listTables(driver:D):Promise<Result<TableListItem[]>>;getTableInfo(driver:D,table:string):Promise<Result<TableInfoData>>;getSchema(driver:D,table:string):Promise<Result<SchemaInfo>>;getReferencingTables(driver:D,table:string):Promise<Result<ReferencingTableInfo[]>>;getCreateStatement(driver:D,table:string):Promise<Result<string|null>>;getColumnCountMap?(driver:D):Promise<Result<Record<string,number>>>;}export interface IDdlGenerator<D=unknown>{createTable(table:string,columns:ColumnDef[]):string;renameTable(oldName:string,newName:string):string;dropTable(table:string,options?:{cascade?:boolean;ifExists?:boolean;}):string;truncateTable(table:string,options?:{cascade?:boolean;restartIdentity?:boolean;}):string;setForeignKeys?(enabled:boolean):string;addColumn(table:string,column:ColumnDef):string;modifyColumn(driver:D,table:string,oldCol:string,newCol:ColumnDef):Promise<Result<{changes?:number;}>>;renameColumn(table:string,oldName:string,newName:string):string;dropColumn(table:string,column:string):string;createIndex(table:string,index:IndexDef):string;dropIndex(indexName:string):string;}export interface IDialect<D=unknown>{readonly name:DatabaseDialect;readonly introspector:ISchemaIntrospector<D>;readonly ddl:IDdlGenerator<D>;readonly capabilities:DialectCapabilities;quoteIdentifier(identifier:string):string;mapType(type:string):{sqlType:string;defaultAuto?:string;};getDesignerTypes():readonly string[];}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.POSTGRES_CAPABILITIES = exports.SQLITE_CAPABILITIES = void 0;
|
|
4
|
+
exports.SQLITE_CAPABILITIES = {
|
|
5
|
+
supportsCascadeDrop: false,
|
|
6
|
+
supportsNativeAlterColumn: false,
|
|
7
|
+
supportsRegexFilter: false,
|
|
8
|
+
supportsJsonb: false,
|
|
9
|
+
};
|
|
10
|
+
exports.POSTGRES_CAPABILITIES = {
|
|
11
|
+
supportsCascadeDrop: true,
|
|
12
|
+
supportsNativeAlterColumn: true,
|
|
13
|
+
supportsRegexFilter: true,
|
|
14
|
+
supportsJsonb: true,
|
|
15
|
+
};
|
package/dist/db/export.js
CHANGED
|
@@ -28,7 +28,7 @@ async function generateSqlDump(db) {
|
|
|
28
28
|
async function generateSchemaDump(db) {
|
|
29
29
|
const out = [];
|
|
30
30
|
if (db.dialect === 'sqlite') {
|
|
31
|
-
const allSchemas = await db.all(`SELECT sql FROM sqlite_master WHERE sql IS NOT NULL AND name NOT LIKE 'sqlite_%'
|
|
31
|
+
const allSchemas = await db.all(`SELECT sql FROM sqlite_master WHERE sql IS NOT NULL AND name NOT LIKE 'sqlite_%'`);
|
|
32
32
|
if (!allSchemas.success || !allSchemas.data) {
|
|
33
33
|
return { success: false, error: allSchemas.error ?? 'Failed to read sqlite_master.' };
|
|
34
34
|
}
|
package/dist/db/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from './types';export * from './filters';export * from './
|
|
1
|
+
export * from './types';export * from './filters';export * from './migrations';export * from './database';export * from './postgres';export * from './manager';export * from './export';export * from './dialects/index';export type {DatabaseContext} from '../core/context';
|
package/dist/db/index.js
CHANGED
|
@@ -16,9 +16,9 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./types"), exports);
|
|
18
18
|
__exportStar(require("./filters"), exports);
|
|
19
|
-
__exportStar(require("./introspection"), exports);
|
|
20
19
|
__exportStar(require("./migrations"), exports);
|
|
21
20
|
__exportStar(require("./database"), exports);
|
|
22
21
|
__exportStar(require("./postgres"), exports);
|
|
23
22
|
__exportStar(require("./manager"), exports);
|
|
24
23
|
__exportStar(require("./export"), exports);
|
|
24
|
+
__exportStar(require("./dialects/index"), exports);
|
package/dist/db/manager.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import type {IDatabase} from './types';import{type Logger} from '../utils/logger';export interface DbManagerOptions{dir?:string;files?:string[];connections?:Record<string,string>;readonly ?:boolean;serverless?:boolean;}export interface DatabaseEntry{id:string;name:string;path:string;dialect:'sqlite'|'postgres';size:number;modified:string;readonly :boolean;}export declare class DbManager{private logger;private dir?;private createDir;private readonly readonlyMode;private readonly isServerless;private files;private idByPath;private pathById;private nameById;private readonlyById;private openDbs;constructor(options:DbManagerOptions|string,logger?:Logger);get directory():string;get isReadOnly():boolean;isDbFile(name:string):boolean;private registerConnections;private registerDirFiles;private registerExplicitFiles;private addNamedTarget;private addFile;list():DatabaseEntry[];has(id:string):boolean;isDbReadOnly(id:string):boolean;setReadonly(id:string,readonly :boolean):void;open(id:string,readonlyOverride?:boolean):IDatabase;openFile(absPath:string,readonly ?:boolean):string;get(id:string):IDatabase|undefined;countTables(id:string):Promise<number>;create(name:string):string;addConnection(name:string,connectionString:string,readonly ?:boolean):string;remove(id:string):void;closeAll():void;}
|
|
1
|
+
import type {IDatabase} from './types';import{type Logger} from '../utils/logger';import type {DatabaseContext} from '../core/context';export interface DbManagerOptions{dir?:string;files?:string[];connections?:Record<string,string>;readonly ?:boolean;serverless?:boolean;maxOpenDbs?:number;idleTimeoutMs?:number;}export interface DatabaseEntry{id:string;name:string;path:string;dialect:'sqlite'|'postgres';size:number;modified:string;readonly :boolean;}export declare class DbManager{private logger;private dir?;private createDir;private readonly readonlyMode;private readonly isServerless;private readonly maxOpenDbs;private readonly idleTimeoutMs;private files;private idByPath;private pathById;private nameById;private readonlyById;private openDbs;private accessOrder;private lastAccessed;constructor(options:DbManagerOptions|string,logger?:Logger);get directory():string;get isReadOnly():boolean;isDbFile(name:string):boolean;private registerConnections;private registerDirFiles;private registerExplicitFiles;private addNamedTarget;private addFile;list():DatabaseEntry[];has(id:string):boolean;isDbReadOnly(id:string):boolean;private touch;private evictLru;pruneIdle(maxIdleAgeMs?:number):number;closeDb(id:string):void;getPoolStats():{openConnections:number;maxOpenDbs:number;lruOrder:string[];};setReadonly(id:string,readonly :boolean):void;open(id:string,readonlyOverride?:boolean):IDatabase;getContext(id:string,readonlyOverride?:boolean):DatabaseContext;openFile(absPath:string,readonly ?:boolean):string;get(id:string):IDatabase|undefined;countTables(id:string):Promise<number>;create(name:string):string;addConnection(name:string,connectionString:string,readonly ?:boolean):string;remove(id:string):void;closeAll():void;}
|
package/dist/db/manager.js
CHANGED
|
@@ -11,6 +11,7 @@ const postgres_1 = require("./postgres");
|
|
|
11
11
|
const logger_1 = require("../utils/logger");
|
|
12
12
|
const common_1 = require("../utils/common");
|
|
13
13
|
const serverless_1 = require("../serverless");
|
|
14
|
+
const index_1 = require("./dialects/index");
|
|
14
15
|
const DB_EXTENSIONS = ['.db', '.sqlite', '.sqlite3'];
|
|
15
16
|
class DbManager {
|
|
16
17
|
logger;
|
|
@@ -18,12 +19,16 @@ class DbManager {
|
|
|
18
19
|
createDir;
|
|
19
20
|
readonlyMode;
|
|
20
21
|
isServerless;
|
|
22
|
+
maxOpenDbs;
|
|
23
|
+
idleTimeoutMs;
|
|
21
24
|
files = [];
|
|
22
25
|
idByPath = new Map();
|
|
23
26
|
pathById = new Map();
|
|
24
27
|
nameById = new Map();
|
|
25
28
|
readonlyById = new Map();
|
|
26
29
|
openDbs = new Map();
|
|
30
|
+
accessOrder = [];
|
|
31
|
+
lastAccessed = new Map();
|
|
27
32
|
constructor(options, logger) {
|
|
28
33
|
const opts = typeof options === 'string' ? { dir: options } : (options ?? {});
|
|
29
34
|
this.logger = logger ?? (0, logger_1.createLogger)('info', 'admindb');
|
|
@@ -31,6 +36,8 @@ class DbManager {
|
|
|
31
36
|
this.createDir = this.dir ?? process.cwd();
|
|
32
37
|
this.readonlyMode = Boolean(opts.readonly);
|
|
33
38
|
this.isServerless = opts.serverless !== undefined ? opts.serverless : (0, serverless_1.isServerlessEnvironment)();
|
|
39
|
+
this.maxOpenDbs = Math.max(1, opts.maxOpenDbs ?? 25);
|
|
40
|
+
this.idleTimeoutMs = Math.max(0, opts.idleTimeoutMs ?? 0);
|
|
34
41
|
if (this.dir)
|
|
35
42
|
(0, node_fs_1.mkdirSync)(this.dir, { recursive: true });
|
|
36
43
|
if (opts.connections)
|
|
@@ -171,6 +178,70 @@ class DbManager {
|
|
|
171
178
|
}
|
|
172
179
|
return false;
|
|
173
180
|
}
|
|
181
|
+
touch(id) {
|
|
182
|
+
const idx = this.accessOrder.indexOf(id);
|
|
183
|
+
if (idx !== -1) {
|
|
184
|
+
this.accessOrder.splice(idx, 1);
|
|
185
|
+
}
|
|
186
|
+
this.accessOrder.push(id);
|
|
187
|
+
this.lastAccessed.set(id, Date.now());
|
|
188
|
+
}
|
|
189
|
+
evictLru() {
|
|
190
|
+
while (this.accessOrder.length > 0) {
|
|
191
|
+
const oldestId = this.accessOrder.shift();
|
|
192
|
+
const db = this.openDbs.get(oldestId);
|
|
193
|
+
if (db) {
|
|
194
|
+
this.logger.debug?.(`Evicting idle database connection "${oldestId}" from pool.`);
|
|
195
|
+
try {
|
|
196
|
+
db.close();
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
this.logger.warn(`Error closing evicted database "${oldestId}": ${(0, common_1.errorMessage)(err)}`);
|
|
200
|
+
}
|
|
201
|
+
this.openDbs.delete(oldestId);
|
|
202
|
+
this.lastAccessed.delete(oldestId);
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
pruneIdle(maxIdleAgeMs) {
|
|
209
|
+
const maxAge = maxIdleAgeMs ?? this.idleTimeoutMs;
|
|
210
|
+
if (maxAge <= 0)
|
|
211
|
+
return 0;
|
|
212
|
+
const now = Date.now();
|
|
213
|
+
let pruned = 0;
|
|
214
|
+
for (const [id, lastTime] of Array.from(this.lastAccessed.entries())) {
|
|
215
|
+
if (now - lastTime > maxAge) {
|
|
216
|
+
this.closeDb(id);
|
|
217
|
+
pruned++;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return pruned;
|
|
221
|
+
}
|
|
222
|
+
closeDb(id) {
|
|
223
|
+
const db = this.openDbs.get(id);
|
|
224
|
+
if (db) {
|
|
225
|
+
try {
|
|
226
|
+
db.close();
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
this.logger.warn(`Error closing database "${id}": ${(0, common_1.errorMessage)(err)}`);
|
|
230
|
+
}
|
|
231
|
+
this.openDbs.delete(id);
|
|
232
|
+
}
|
|
233
|
+
const idx = this.accessOrder.indexOf(id);
|
|
234
|
+
if (idx !== -1)
|
|
235
|
+
this.accessOrder.splice(idx, 1);
|
|
236
|
+
this.lastAccessed.delete(id);
|
|
237
|
+
}
|
|
238
|
+
getPoolStats() {
|
|
239
|
+
return {
|
|
240
|
+
openConnections: this.openDbs.size,
|
|
241
|
+
maxOpenDbs: this.maxOpenDbs,
|
|
242
|
+
lruOrder: [...this.accessOrder],
|
|
243
|
+
};
|
|
244
|
+
}
|
|
174
245
|
setReadonly(id, readonly) {
|
|
175
246
|
if (this.readonlyMode && !readonly) {
|
|
176
247
|
throw new Error('Manager is in global read-only mode — databases cannot be made writable.');
|
|
@@ -185,14 +256,7 @@ class DbManager {
|
|
|
185
256
|
if (this.readonlyById.get(id) === readonly)
|
|
186
257
|
return;
|
|
187
258
|
this.readonlyById.set(id, readonly);
|
|
188
|
-
|
|
189
|
-
if (db) {
|
|
190
|
-
try {
|
|
191
|
-
db.close();
|
|
192
|
-
}
|
|
193
|
-
catch { }
|
|
194
|
-
this.openDbs.delete(id);
|
|
195
|
-
}
|
|
259
|
+
this.closeDb(id);
|
|
196
260
|
}
|
|
197
261
|
open(id, readonlyOverride) {
|
|
198
262
|
const target = this.pathById.get(id);
|
|
@@ -201,22 +265,43 @@ class DbManager {
|
|
|
201
265
|
const isRo = readonlyOverride !== undefined ? readonlyOverride : this.isDbReadOnly(id);
|
|
202
266
|
let db = this.openDbs.get(id);
|
|
203
267
|
if (db && db.isReadOnly !== isRo) {
|
|
204
|
-
|
|
205
|
-
db.close();
|
|
206
|
-
}
|
|
207
|
-
catch { }
|
|
208
|
-
this.openDbs.delete(id);
|
|
268
|
+
this.closeDb(id);
|
|
209
269
|
db = undefined;
|
|
210
270
|
}
|
|
211
271
|
if (!db) {
|
|
272
|
+
if (this.idleTimeoutMs > 0) {
|
|
273
|
+
this.pruneIdle();
|
|
274
|
+
}
|
|
275
|
+
while (this.openDbs.size >= this.maxOpenDbs) {
|
|
276
|
+
if (!this.evictLru())
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
212
279
|
const childLogger = this.logger.child(`db:${id}`);
|
|
213
280
|
db = (0, common_1.isPostgresConnectionString)(target)
|
|
214
281
|
? new postgres_1.PostgresDatabase(target, childLogger, { readonly: isRo })
|
|
215
282
|
: new database_1.SqliteDatabase(target, childLogger, { readonly: isRo });
|
|
216
283
|
this.openDbs.set(id, db);
|
|
217
284
|
}
|
|
285
|
+
this.touch(id);
|
|
218
286
|
return db;
|
|
219
287
|
}
|
|
288
|
+
getContext(id, readonlyOverride) {
|
|
289
|
+
const db = this.open(id, readonlyOverride);
|
|
290
|
+
const target = this.pathById.get(id);
|
|
291
|
+
const isPg = (0, common_1.isPostgresConnectionString)(target);
|
|
292
|
+
const schema = isPg ? db.schema || 'public' : undefined;
|
|
293
|
+
const dialect = (0, index_1.getDialect)(isPg ? 'postgres' : 'sqlite', schema);
|
|
294
|
+
return {
|
|
295
|
+
id,
|
|
296
|
+
name: this.nameById.get(id) ?? id,
|
|
297
|
+
path: db.path,
|
|
298
|
+
db,
|
|
299
|
+
dialect,
|
|
300
|
+
capabilities: dialect.capabilities,
|
|
301
|
+
isReadOnly: db.isReadOnly,
|
|
302
|
+
logger: this.logger.child(`db:${id}`),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
220
305
|
openFile(absPath, readonly) {
|
|
221
306
|
const abs = node_path_1.default.resolve(String(absPath ?? '').trim());
|
|
222
307
|
if (!abs)
|
|
@@ -303,14 +388,7 @@ class DbManager {
|
|
|
303
388
|
const target = this.pathById.get(id);
|
|
304
389
|
if (!target)
|
|
305
390
|
return;
|
|
306
|
-
|
|
307
|
-
if (db) {
|
|
308
|
-
try {
|
|
309
|
-
db.close();
|
|
310
|
-
}
|
|
311
|
-
catch { }
|
|
312
|
-
this.openDbs.delete(id);
|
|
313
|
-
}
|
|
391
|
+
this.closeDb(id);
|
|
314
392
|
this.pathById.delete(id);
|
|
315
393
|
this.idByPath.delete(target);
|
|
316
394
|
this.nameById.delete(id);
|
|
@@ -333,6 +411,8 @@ class DbManager {
|
|
|
333
411
|
catch { }
|
|
334
412
|
}
|
|
335
413
|
this.openDbs.clear();
|
|
414
|
+
this.accessOrder = [];
|
|
415
|
+
this.lastAccessed.clear();
|
|
336
416
|
}
|
|
337
417
|
}
|
|
338
418
|
exports.DbManager = DbManager;
|
package/dist/db/postgres.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import type {Logger} from '../utils/logger';import{type ColumnDef,type IndexDef} from '../sql/generator';import type {DatabaseDialect,IDatabase,MutationResult,PostgresOptions,QueryOptions,ReferencingTableInfo,Result,RowFilters,
|
|
1
|
+
import type {Logger} from '../utils/logger';import{type ColumnDef,type IndexDef} from '../sql/generator';import type {DatabaseDialect,IDatabase,MutationResult,PostgresOptions,QueryOptions,ReferencingTableInfo,Result,RowFilters,SchemaInfo,SQLInputValue,TableInfoData,TableListItem,WhereClause} from './types';import type {IDialect} from './dialects/types';export declare class PostgresDatabase implements IDatabase{private pool;private logger;readonly path:string;readonly isReadOnly:boolean;readonly dialect:DatabaseDialect;readonly schema:string;readonly dialectInstance:IDialect;constructor(connection:string|PostgresOptions,logger:Logger,options?:PostgresOptions);private readonlyBlocked;close():Promise<void>;private tryRun;private transaction;hasMultipleStatements(sql:string):boolean;all<T=unknown>(sql:string,params?:SQLInputValue[]):Promise<Result<T[]>>;run(sql:string,params?:SQLInputValue[]):Promise<Result<MutationResult>>;execResult(sql:string):Promise<Result<{changes?:number;}>>;runWrite(sql:string):Promise<Result<{changes?:number;}>>;listTables():Promise<Result<TableListItem[]>>;getTableInfo(table:string):Promise<Result<TableInfoData>>;getRowCount(table:string,filters?:RowFilters):Promise<Result<number>>;getRows(table:string,opts?:QueryOptions):Promise<Result<Record<string,unknown>[]>>;getAllRows(table:string):Promise<Result<Record<string,unknown>[]>>;getRow(table:string,where:WhereClause[]):Promise<Result<Record<string,unknown>|null>>;insertRow(table:string,fields:WhereClause[]):Promise<Result<MutationResult>>;insertRows(table:string,rows:WhereClause[][]):Promise<Result<{inserted:number;skipped:number;}>>;updateRow(table:string,fields:WhereClause[],where:WhereClause[]):Promise<Result<MutationResult>>;updateRows(table:string,rows:{fields:WhereClause[];where:WhereClause[];}[]):Promise<Result<{updated:number;}>>;deleteRow(table:string,where:WhereClause[]):Promise<Result<MutationResult>>;deleteRows(table:string,rows:WhereClause[][]):Promise<Result<{deleted:number;}>>;getRowsByPks(table:string,pks:WhereClause[][]):Promise<Result<Record<string,unknown>[]>>;getRowsByFk(table:string,column:string,value:unknown,limit?:number):Promise<Result<{rows:Record<string,unknown>[];total:number;}>>;getCreateStatement(table:string):Promise<Result<string|null>>;getSettings():Promise<Result<Record<string,string>>>;getSchema(table:string):Promise<Result<SchemaInfo>>;getReferencingTables(table:string):Promise<Result<ReferencingTableInfo[]>>;renameTable(oldName:string,newName:string):Promise<Result<{changes?:number;}>>;addColumn(table:string,col:ColumnDef):Promise<Result<{changes?:number;}>>;modifyColumn(table:string,oldColName:string,newColDef:ColumnDef):Promise<Result<{changes?:number;}>>;renameColumn(table:string,oldName:string,newName:string):Promise<Result<{changes?:number;}>>;dropColumn(table:string,column:string):Promise<Result<{changes?:number;}>>;dropTable(table:string):Promise<Result<{changes?:number;}>>;createIndex(table:string,index:IndexDef):Promise<Result<{changes?:number;}>>;dropIndex(indexName:string):Promise<Result<{changes?:number;}>>;}
|