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.
Files changed (135) hide show
  1. package/LICENSE +20 -20
  2. package/dist/app.js +36 -25
  3. package/dist/core/context.d.ts +1 -0
  4. package/dist/core/context.js +2 -0
  5. package/dist/core/errors.d.ts +1 -0
  6. package/dist/core/errors.js +39 -0
  7. package/dist/core/index.d.ts +1 -0
  8. package/dist/core/index.js +20 -0
  9. package/dist/core/result.d.ts +1 -0
  10. package/dist/core/result.js +10 -0
  11. package/dist/core/router.d.ts +1 -0
  12. package/dist/core/router.js +37 -0
  13. package/dist/data/chain.js +1 -1
  14. package/dist/data/engine.js +1 -1
  15. package/dist/db/database.d.ts +1 -1
  16. package/dist/db/database.js +9 -31
  17. package/dist/db/dialects/index.d.ts +1 -0
  18. package/dist/db/dialects/index.js +58 -0
  19. package/dist/db/dialects/postgres/ddl.d.ts +1 -0
  20. package/dist/db/dialects/postgres/ddl.js +107 -0
  21. package/dist/db/dialects/postgres/dialect.d.ts +1 -0
  22. package/dist/db/dialects/postgres/dialect.js +28 -0
  23. package/dist/db/dialects/postgres/introspector.d.ts +1 -0
  24. package/dist/db/dialects/postgres/introspector.js +265 -0
  25. package/dist/db/dialects/postgres/types.d.ts +1 -0
  26. package/dist/db/dialects/postgres/types.js +83 -0
  27. package/dist/db/dialects/sqlite/ddl.d.ts +1 -0
  28. package/dist/db/dialects/sqlite/ddl.js +244 -0
  29. package/dist/db/dialects/sqlite/dialect.d.ts +1 -0
  30. package/dist/db/dialects/sqlite/dialect.js +25 -0
  31. package/dist/db/dialects/sqlite/introspector.d.ts +1 -0
  32. package/dist/db/dialects/sqlite/introspector.js +214 -0
  33. package/dist/db/dialects/sqlite/types.d.ts +1 -0
  34. package/dist/db/dialects/sqlite/types.js +69 -0
  35. package/dist/db/dialects/types.d.ts +1 -0
  36. package/dist/db/dialects/types.js +15 -0
  37. package/dist/db/export.js +1 -1
  38. package/dist/db/index.d.ts +1 -1
  39. package/dist/db/index.js +1 -1
  40. package/dist/db/manager.d.ts +1 -1
  41. package/dist/db/manager.js +101 -21
  42. package/dist/db/postgres.d.ts +1 -1
  43. package/dist/db/postgres.js +20 -284
  44. package/dist/db/types.d.ts +1 -1
  45. package/dist/db/types.js +0 -4
  46. package/dist/index.d.ts +1 -1
  47. package/dist/index.js +4 -1
  48. package/dist/modules/databases/databases.routes.d.ts +1 -0
  49. package/dist/modules/databases/databases.routes.js +150 -0
  50. package/dist/modules/databases/databases.service.d.ts +1 -0
  51. package/dist/modules/databases/databases.service.js +134 -0
  52. package/dist/modules/databases/index.d.ts +1 -0
  53. package/dist/{routes → modules/databases}/index.js +2 -3
  54. package/dist/modules/erd/erd.routes.d.ts +1 -0
  55. package/dist/modules/erd/erd.routes.js +32 -0
  56. package/dist/modules/erd/erd.service.d.ts +1 -0
  57. package/dist/modules/erd/erd.service.js +39 -0
  58. package/dist/modules/erd/index.d.ts +1 -0
  59. package/dist/modules/erd/index.js +18 -0
  60. package/dist/modules/index.d.ts +1 -0
  61. package/dist/modules/index.js +62 -0
  62. package/dist/modules/query/index.d.ts +1 -0
  63. package/dist/modules/query/index.js +18 -0
  64. package/dist/modules/query/query.routes.d.ts +1 -0
  65. package/dist/modules/query/query.routes.js +54 -0
  66. package/dist/modules/query/query.service.d.ts +1 -0
  67. package/dist/modules/query/query.service.js +95 -0
  68. package/dist/modules/rows/blob.helper.d.ts +1 -0
  69. package/dist/modules/rows/blob.helper.js +40 -0
  70. package/dist/modules/rows/display.helper.d.ts +1 -0
  71. package/dist/modules/rows/display.helper.js +73 -0
  72. package/dist/modules/rows/index.d.ts +1 -0
  73. package/dist/modules/rows/index.js +20 -0
  74. package/dist/modules/rows/rows.routes.d.ts +1 -0
  75. package/dist/modules/rows/rows.routes.js +555 -0
  76. package/dist/modules/rows/rows.service.d.ts +1 -0
  77. package/dist/modules/rows/rows.service.js +433 -0
  78. package/dist/modules/schema/index.d.ts +1 -0
  79. package/dist/modules/schema/index.js +18 -0
  80. package/dist/modules/schema/schema.routes.d.ts +1 -0
  81. package/dist/modules/schema/schema.routes.js +171 -0
  82. package/dist/modules/schema/schema.service.d.ts +1 -0
  83. package/dist/modules/schema/schema.service.js +58 -0
  84. package/dist/modules/seed/index.d.ts +1 -0
  85. package/dist/modules/seed/index.js +18 -0
  86. package/dist/modules/seed/seed.routes.d.ts +1 -0
  87. package/dist/modules/seed/seed.routes.js +212 -0
  88. package/dist/modules/seed/seed.service.d.ts +1 -0
  89. package/dist/{routes/api/helpers.js → modules/seed/seed.service.js} +86 -109
  90. package/dist/modules/tables/index.d.ts +1 -0
  91. package/dist/modules/tables/index.js +18 -0
  92. package/dist/modules/tables/tables.routes.d.ts +1 -0
  93. package/dist/modules/tables/tables.routes.js +194 -0
  94. package/dist/modules/tables/tables.service.d.ts +1 -0
  95. package/dist/modules/tables/tables.service.js +218 -0
  96. package/dist/public/js/browse.js +1 -1
  97. package/dist/public/js/query.js +1 -1
  98. package/dist/types/api.d.ts +1 -1
  99. package/dist/utils/common.d.ts +1 -1
  100. package/dist/utils/common.js +1 -3
  101. package/dist/utils/stream.d.ts +1 -0
  102. package/dist/utils/stream.js +131 -0
  103. package/dist/views/pages/error.hbs +10 -1
  104. package/dist/views/pages/home.hbs +1 -1
  105. package/dist/views/pages/query.hbs +1 -1
  106. package/dist/views/pages/seed-select.hbs +1 -252
  107. package/dist/views/pages/seed.hbs +1 -307
  108. package/dist/views/pages/table.hbs +1 -1
  109. package/dist/views/partials/sidebar.hbs +1 -1
  110. package/docs/API.md +4 -17
  111. package/docs/EXAMPLES.md +579 -579
  112. package/docs/SECURITY.md +265 -265
  113. package/package.json +4 -2
  114. package/dist/db/introspection.d.ts +0 -1
  115. package/dist/db/introspection.js +0 -164
  116. package/dist/routes/api/erd.d.ts +0 -1
  117. package/dist/routes/api/erd.js +0 -45
  118. package/dist/routes/api/helpers.d.ts +0 -1
  119. package/dist/routes/api/import-export.d.ts +0 -1
  120. package/dist/routes/api/import-export.js +0 -84
  121. package/dist/routes/api/index.d.ts +0 -1
  122. package/dist/routes/api/index.js +0 -55
  123. package/dist/routes/api/query.d.ts +0 -1
  124. package/dist/routes/api/query.js +0 -77
  125. package/dist/routes/api/rows.d.ts +0 -1
  126. package/dist/routes/api/rows.js +0 -373
  127. package/dist/routes/api/seed.d.ts +0 -1
  128. package/dist/routes/api/seed.js +0 -180
  129. package/dist/routes/api/tables.d.ts +0 -1
  130. package/dist/routes/api/tables.js +0 -230
  131. package/dist/routes/databases.d.ts +0 -1
  132. package/dist/routes/databases.js +0 -212
  133. package/dist/routes/index.d.ts +0 -1
  134. package/dist/routes/pages.d.ts +0 -1
  135. package/dist/routes/pages.js +0 -540
@@ -0,0 +1,433 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RowsService = exports.MAX_BULK_ROWS = void 0;
4
+ exports.buildFields = buildFields;
5
+ exports.buildUpdateFields = buildUpdateFields;
6
+ exports.pkWhere = pkWhere;
7
+ exports.resolvePkRows = resolvePkRows;
8
+ exports.computeBulkImpact = computeBulkImpact;
9
+ exports.encodeCursor = encodeCursor;
10
+ exports.decodeCursor = decodeCursor;
11
+ const common_1 = require("../../utils/common");
12
+ const csv_1 = require("../../utils/csv");
13
+ const stream_1 = require("../../utils/stream");
14
+ const generator_1 = require("../../sql/generator");
15
+ const blob_helper_1 = require("./blob.helper");
16
+ exports.MAX_BULK_ROWS = 1000;
17
+ function buildFields(info, values, opts = {}) {
18
+ const typeMap = new Map(info.columns.map((c) => [c.name, c.type]));
19
+ const pkSet = new Set(info.primaryKey);
20
+ const fields = [];
21
+ for (const [col, raw] of Object.entries(values ?? {})) {
22
+ if (!typeMap.has(col) || (opts.excludePk && pkSet.has(col)))
23
+ continue;
24
+ const val = (0, common_1.coerceFormValue)(raw, typeMap.get(col));
25
+ if (val !== null)
26
+ fields.push({ column: col, value: val });
27
+ }
28
+ return fields;
29
+ }
30
+ function buildUpdateFields(info, values, nulls) {
31
+ const fields = buildFields(info, values, { excludePk: true });
32
+ const typeMap = new Map(info.columns.map((c) => [c.name, c.type]));
33
+ const pkSet = new Set(info.primaryKey);
34
+ for (const col of nulls) {
35
+ if (!pkSet.has(col) && typeMap.has(col) && !fields.some((f) => f.column === col)) {
36
+ fields.push({ column: col, value: null });
37
+ }
38
+ }
39
+ return fields;
40
+ }
41
+ function pkWhere(info, encodedId) {
42
+ const vals = (0, common_1.decodePk)(encodedId);
43
+ if (!info.primaryKey.length) {
44
+ if (vals.length === 1 && vals[0] !== '') {
45
+ const num = Number(vals[0]);
46
+ return [{ column: '_rowid_', value: Number.isFinite(num) ? num : vals[0] }];
47
+ }
48
+ return null;
49
+ }
50
+ if (vals.length !== info.primaryKey.length)
51
+ return null;
52
+ const typeMap = new Map(info.columns.map((c) => [c.name, c.type]));
53
+ return info.primaryKey.map((col, i) => ({ column: col, value: (0, common_1.coerceFormValue)(vals[i], typeMap.get(col)) }));
54
+ }
55
+ function resolvePkRows(info, ids) {
56
+ const list = Array.isArray(ids) ? ids : ids == null ? [] : [ids];
57
+ const out = [];
58
+ for (const id of list) {
59
+ const where = pkWhere(info, String(id));
60
+ if (where)
61
+ out.push(where);
62
+ }
63
+ return out.length ? out : null;
64
+ }
65
+ async function computeBulkImpact(ctx, info, wheres) {
66
+ const { db } = ctx;
67
+ const [rowsR, refInfoR] = await Promise.all([
68
+ db.getRowsByPks(info.table, wheres),
69
+ db.getReferencingTables(info.table),
70
+ ]);
71
+ const rows = (rowsR.data ?? []);
72
+ const referencing = refInfoR.data ?? [];
73
+ if (!rows.length || !referencing.length)
74
+ return { references: [], total: 0 };
75
+ const queries = referencing.flatMap((item) => item.refs.map(async (ref) => {
76
+ const targetCol = ref.to || info.primaryKey[0];
77
+ if (!targetCol)
78
+ return null;
79
+ const values = Array.from(new Set(rows.map((r) => r[targetCol]).filter((v) => v != null)));
80
+ if (!values.length)
81
+ return null;
82
+ try {
83
+ const placeholders = values.map(() => '?').join(', ');
84
+ const sql = `SELECT COUNT(*) AS c FROM ${(0, generator_1.quoteIdentifier)(item.table)} WHERE ${(0, generator_1.quoteIdentifier)(ref.from)} IN (${placeholders})`;
85
+ const countR = await db.all(sql, values);
86
+ const count = countR.success && countR.data?.[0] ? Number(countR.data[0].c ?? 0) : 0;
87
+ return count > 0 ? { table: item.table, from: ref.from, to: targetCol, count } : null;
88
+ }
89
+ catch {
90
+ return null;
91
+ }
92
+ }));
93
+ const results = (await Promise.all(queries)).filter((r) => r !== null);
94
+ const total = results.reduce((acc, r) => acc + r.count, 0);
95
+ return { references: results, total };
96
+ }
97
+ function encodeCursor(values) {
98
+ return Buffer.from(JSON.stringify(values)).toString('base64url');
99
+ }
100
+ function decodeCursor(cursor) {
101
+ try {
102
+ return JSON.parse(Buffer.from(cursor, 'base64url').toString('utf-8'));
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ }
108
+ class RowsService {
109
+ static async getPaginatedRows(ctx, table, options = {}) {
110
+ const { db } = ctx;
111
+ const page = options.page ?? 1;
112
+ const limit = options.limit ?? 50;
113
+ const [rowsR, totalR] = await Promise.all([
114
+ db.getRows(table, { ...options, page, limit }),
115
+ db.getRowCount(table, options.filters),
116
+ ]);
117
+ if (!rowsR.success) {
118
+ throw new Error(rowsR.error ?? 'Failed to load rows.');
119
+ }
120
+ const rows = (rowsR.data ?? []).map(common_1.normalizeRow);
121
+ const total = totalR.success && typeof totalR.data === 'number' ? totalR.data : rows.length;
122
+ return {
123
+ rows,
124
+ total,
125
+ page,
126
+ limit,
127
+ };
128
+ }
129
+ static async getCursorPaginatedRows(ctx, table, options = {}) {
130
+ const { db } = ctx;
131
+ const limit = Math.max(1, Math.min(500, options.limit ?? 50));
132
+ const orderDir = options.orderDir === 'desc' ? 'desc' : 'asc';
133
+ const info = await db.getTableInfo(table);
134
+ const tableInfo = info.data;
135
+ const pkCol = tableInfo?.primaryKey[0] ?? '_rowid_';
136
+ const orderBy = options.orderBy ?? pkCol;
137
+ const filters = { ...(options.filters ?? {}) };
138
+ const decodedCursor = options.cursor ? decodeCursor(options.cursor) : null;
139
+ if (decodedCursor && decodedCursor[orderBy] !== undefined) {
140
+ const cursorVal = decodedCursor[orderBy];
141
+ const op = orderDir === 'asc' ? 'gt' : 'lt';
142
+ filters[orderBy] = { op, value: String(cursorVal) };
143
+ }
144
+ const rowsR = await db.getRows(table, {
145
+ filters,
146
+ orderBy,
147
+ orderDir,
148
+ limit: limit + 1,
149
+ });
150
+ if (!rowsR.success) {
151
+ throw new Error(rowsR.error ?? 'Failed to load cursor-paginated rows.');
152
+ }
153
+ const rawRows = (rowsR.data ?? []).map(common_1.normalizeRow);
154
+ const hasMore = rawRows.length > limit;
155
+ const rows = hasMore ? rawRows.slice(0, limit) : rawRows;
156
+ let nextCursor = null;
157
+ let prevCursor = null;
158
+ if (rows.length > 0) {
159
+ const firstRow = rows[0];
160
+ const lastRow = rows[rows.length - 1];
161
+ if (hasMore) {
162
+ nextCursor = encodeCursor({ [orderBy]: lastRow[orderBy], [pkCol]: lastRow[pkCol] });
163
+ }
164
+ if (options.cursor) {
165
+ prevCursor = encodeCursor({ [orderBy]: firstRow[orderBy], [pkCol]: firstRow[pkCol] });
166
+ }
167
+ }
168
+ return {
169
+ rows,
170
+ nextCursor,
171
+ prevCursor,
172
+ hasMore,
173
+ limit,
174
+ };
175
+ }
176
+ static async getRowCount(ctx, table, filters) {
177
+ const r = await ctx.db.getRowCount(table, filters);
178
+ if (!r.success) {
179
+ throw new Error(r.error ?? 'Failed to count rows.');
180
+ }
181
+ return r.data ?? 0;
182
+ }
183
+ static async getRow(ctx, table, where) {
184
+ const r = await ctx.db.getRow(table, where);
185
+ if (!r.success) {
186
+ throw new Error(r.error ?? 'Failed to load row.');
187
+ }
188
+ return r.data ? (0, common_1.normalizeRow)(r.data) : null;
189
+ }
190
+ static async getBlob(ctx, table, where, column) {
191
+ const r = await ctx.db.getRow(table, where);
192
+ if (!r.success) {
193
+ throw new Error(r.error ?? 'Failed to load row.');
194
+ }
195
+ if (!r.data)
196
+ return null;
197
+ const rawVal = r.data[column];
198
+ if (rawVal === null || rawVal === undefined)
199
+ return null;
200
+ const buf = (0, blob_helper_1.resolveBlobBuffer)(rawVal);
201
+ const mimeInfo = (0, blob_helper_1.sniffBlobMime)(buf);
202
+ return { buffer: buf, mimeInfo };
203
+ }
204
+ static async getBlobMeta(ctx, table, where, column) {
205
+ const r = await ctx.db.getRow(table, where);
206
+ if (!r.success) {
207
+ throw new Error(r.error ?? 'Failed to load row.');
208
+ }
209
+ if (!r.data)
210
+ return null;
211
+ const rawVal = r.data[column];
212
+ return (0, blob_helper_1.getBlobMetadata)(rawVal);
213
+ }
214
+ static async updateBlob(ctx, table, where, column, data, format) {
215
+ const buf = (0, blob_helper_1.parseBlobPayload)(data, format);
216
+ const updateRes = await ctx.db.updateRow(table, [{ column, value: buf }], where);
217
+ if (!updateRes.success) {
218
+ throw new Error(updateRes.error ?? 'Failed to update BLOB.');
219
+ }
220
+ return buf.length;
221
+ }
222
+ static generateInsertSql(table, fields) {
223
+ if (!fields.length)
224
+ throw new Error('No valid column values provided.');
225
+ return (0, generator_1.generateInsert)(table, fields);
226
+ }
227
+ static generateUpdateSql(table, fields, where) {
228
+ if (!fields.length)
229
+ throw new Error('No fields to update.');
230
+ return (0, generator_1.generateUpdate)(table, fields, where);
231
+ }
232
+ static async insertRow(ctx, table, fields) {
233
+ if (!fields.length)
234
+ throw new Error('No valid column values provided.');
235
+ const r = await ctx.db.insertRow(table, fields);
236
+ if (!r.success)
237
+ throw new Error(r.error ?? 'Failed to insert row.');
238
+ return r.data?.lastInsertRowid;
239
+ }
240
+ static async insertRows(ctx, table, fieldsList) {
241
+ if (!fieldsList.length)
242
+ throw new Error('No rows provided.');
243
+ const r = await ctx.db.insertRows(table, fieldsList);
244
+ if (!r.success)
245
+ throw new Error(r.error ?? 'Failed to insert rows.');
246
+ return r.data?.inserted ?? 0;
247
+ }
248
+ static async updateRow(ctx, table, fields, where) {
249
+ if (!fields.length)
250
+ throw new Error('No fields to update.');
251
+ const r = await ctx.db.updateRow(table, fields, where);
252
+ if (!r.success)
253
+ throw new Error(r.error ?? 'Failed to update row.');
254
+ }
255
+ static async updateRows(ctx, table, rowUpdates) {
256
+ if (!rowUpdates.length)
257
+ return 0;
258
+ const r = await ctx.db.updateRows(table, rowUpdates);
259
+ if (!r.success)
260
+ throw new Error(r.error ?? 'Failed to update rows.');
261
+ return r.data?.updated ?? 0;
262
+ }
263
+ static async deleteRow(ctx, table, where) {
264
+ const r = await ctx.db.deleteRow(table, where);
265
+ if (!r.success)
266
+ throw new Error(r.error ?? 'Failed to delete row.');
267
+ }
268
+ static async deleteRows(ctx, table, wheres) {
269
+ const r = await ctx.db.deleteRows(table, wheres);
270
+ if (!r.success)
271
+ throw new Error(r.error ?? 'Failed to delete rows.');
272
+ return r.data?.deleted ?? 0;
273
+ }
274
+ static async getRowReferences(ctx, table, info, where) {
275
+ const { db } = ctx;
276
+ const rowR = await db.getRow(table, where);
277
+ if (!rowR.success || !rowR.data) {
278
+ throw new Error('Row not found.');
279
+ }
280
+ const refInfoR = await db.getReferencingTables(table);
281
+ const referencing = refInfoR.data ?? [];
282
+ const results = [];
283
+ for (const item of referencing) {
284
+ for (const ref of item.refs) {
285
+ const targetCol = ref.to || info.primaryKey[0];
286
+ if (!targetCol)
287
+ continue;
288
+ const val = rowR.data[targetCol];
289
+ if (val == null)
290
+ continue;
291
+ const fkR = await db.getRowsByFk(item.table, ref.from, val);
292
+ if (fkR.success && fkR.data) {
293
+ const rows = fkR.data.rows.map(common_1.normalizeRow);
294
+ results.push({
295
+ table: item.table,
296
+ from: ref.from,
297
+ to: targetCol,
298
+ value: val,
299
+ columns: rows.length > 0 ? Object.keys(rows[0]) : [],
300
+ rows,
301
+ count: fkR.data.total,
302
+ total: fkR.data.total,
303
+ });
304
+ }
305
+ }
306
+ }
307
+ return results;
308
+ }
309
+ static async exportTable(ctx, table, info, options) {
310
+ const r = await ctx.db.getRows(table, { limit: options.limit ?? 1_000_000, filters: options.filters });
311
+ if (!r.success)
312
+ throw new Error(r.error ?? 'Failed to export table.');
313
+ const rows = (r.data ?? []).map(common_1.normalizeRow);
314
+ const columns = info.columns.map((c) => c.name);
315
+ if (options.format === 'json') {
316
+ return { data: (0, csv_1.toJson)(rows), contentType: 'application/json; charset=utf-8', filename: `${table}.json` };
317
+ }
318
+ return { data: (0, csv_1.toCsv)(rows, columns), contentType: 'text/csv; charset=utf-8', filename: `${table}.csv` };
319
+ }
320
+ static async streamExportTable(ctx, table, info, options) {
321
+ const chunkSize = Math.max(10, options.chunkSize ?? 500);
322
+ const columns = info.columns.map((c) => c.name);
323
+ const transform = options.format === 'json' ? (0, stream_1.createJsonStream)() : (0, stream_1.createCsvStream)(columns);
324
+ (async () => {
325
+ let page = 1;
326
+ let hasMore = true;
327
+ try {
328
+ while (hasMore) {
329
+ const r = await ctx.db.getRows(table, { limit: chunkSize, page, filters: options.filters });
330
+ if (!r.success) {
331
+ transform.destroy(new Error(r.error ?? 'Failed to stream rows.'));
332
+ return;
333
+ }
334
+ const rows = (r.data ?? []);
335
+ if (rows.length === 0) {
336
+ hasMore = false;
337
+ break;
338
+ }
339
+ for (const row of rows) {
340
+ transform.write((0, common_1.normalizeRow)(row));
341
+ }
342
+ if (rows.length < chunkSize) {
343
+ hasMore = false;
344
+ }
345
+ else {
346
+ page++;
347
+ }
348
+ }
349
+ transform.end();
350
+ }
351
+ catch (err) {
352
+ transform.destroy(err);
353
+ }
354
+ })();
355
+ const contentType = options.format === 'json' ? 'application/json; charset=utf-8' : 'text/csv; charset=utf-8';
356
+ const filename = `${table}.${options.format}`;
357
+ return { stream: transform, contentType, filename };
358
+ }
359
+ static async exportBulkRows(ctx, table, info, wheres, format) {
360
+ const rowsR = await ctx.db.getRowsByPks(table, wheres);
361
+ if (!rowsR.success)
362
+ throw new Error(rowsR.error ?? 'Failed to load rows.');
363
+ const rows = (rowsR.data ?? []).map(common_1.normalizeRow);
364
+ const columns = info.columns.map((c) => c.name);
365
+ if (format === 'json') {
366
+ return { data: (0, csv_1.toJson)(rows), contentType: 'application/json; charset=utf-8', filename: `${table}_selected.json` };
367
+ }
368
+ return { data: (0, csv_1.toCsv)(rows, columns), contentType: 'text/csv; charset=utf-8', filename: `${table}_selected.csv` };
369
+ }
370
+ static async importCsv(ctx, table, info, csvText) {
371
+ if (!csvText.trim())
372
+ throw new Error('No CSV data provided.');
373
+ const parsed = (0, csv_1.parseCsv)(csvText);
374
+ if (!parsed.length)
375
+ throw new Error('CSV file is empty.');
376
+ const header = parsed[0].map((h) => h.trim());
377
+ const typeMap = new Map(info.columns.map((c) => [c.name, c.type]));
378
+ const validCols = header.filter((h) => typeMap.has(h));
379
+ if (!validCols.length) {
380
+ throw new Error('None of the CSV columns match columns in this table.');
381
+ }
382
+ const rows = parsed.slice(1).map((line) => {
383
+ const fields = [];
384
+ for (let i = 0; i < header.length; i++) {
385
+ const col = header[i];
386
+ if (!typeMap.has(col))
387
+ continue;
388
+ fields.push({ column: col, value: (0, common_1.coerceFormValue)(line[i] ?? '', typeMap.get(col)) });
389
+ }
390
+ return fields;
391
+ });
392
+ const r = await ctx.db.insertRows(table, rows);
393
+ if (!r.success)
394
+ throw new Error(r.error ?? 'Failed to import CSV.');
395
+ return r.data?.inserted ?? 0;
396
+ }
397
+ static async streamImportCsv(ctx, table, info, csvStream, batchSize = 500) {
398
+ const typeMap = new Map(info.columns.map((c) => [c.name, c.type]));
399
+ let header = null;
400
+ let totalInserted = 0;
401
+ await (0, stream_1.parseCsvStreamBatched)(csvStream, async (batch) => {
402
+ let dataRows = batch;
403
+ if (!header) {
404
+ if (batch.length === 0)
405
+ return;
406
+ header = batch[0].map((h) => h.trim());
407
+ dataRows = batch.slice(1);
408
+ }
409
+ if (dataRows.length === 0)
410
+ return;
411
+ const validColIndices = header
412
+ .map((col, idx) => (typeMap.has(col) ? idx : -1))
413
+ .filter((idx) => idx !== -1);
414
+ if (!validColIndices.length) {
415
+ throw new Error('None of the CSV columns match columns in this table.');
416
+ }
417
+ const rowsToInsert = dataRows.map((line) => {
418
+ const fields = [];
419
+ for (const i of validColIndices) {
420
+ const col = header[i];
421
+ fields.push({ column: col, value: (0, common_1.coerceFormValue)(line[i] ?? '', typeMap.get(col)) });
422
+ }
423
+ return fields;
424
+ });
425
+ const r = await ctx.db.insertRows(table, rowsToInsert);
426
+ if (!r.success)
427
+ throw new Error(r.error ?? 'Failed to import CSV batch.');
428
+ totalInserted += r.data?.inserted ?? rowsToInsert.length;
429
+ }, batchSize);
430
+ return totalInserted;
431
+ }
432
+ }
433
+ exports.RowsService = RowsService;
@@ -0,0 +1 @@
1
+ export * from './schema.service';export * from './schema.routes';
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./schema.service"), exports);
18
+ __exportStar(require("./schema.routes"), exports);
@@ -0,0 +1 @@
1
+ import type {Router} from 'express';import{type RouteContext} from '../../core/router';export declare function registerSchemaRoutes(router:Router,ctx:RouteContext):void;
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerSchemaRoutes = registerSchemaRoutes;
4
+ const router_1 = require("../../core/router");
5
+ const tables_service_1 = require("../tables/tables.service");
6
+ const schema_service_1 = require("./schema.service");
7
+ function registerSchemaRoutes(router, ctx) {
8
+ router.get('/schema/:db/:table', async (req, res, next) => {
9
+ try {
10
+ const db = ctx.getContext(req);
11
+ const dbId = req.params.db;
12
+ const table = req.params.table;
13
+ res.locals.dbId = dbId;
14
+ const tablesList = await tables_service_1.TableService.listTables(db);
15
+ const allNames = tablesList.map((t) => t.name);
16
+ (0, router_1.initPageLocals)(res, db, dbId, allNames);
17
+ const [info, schema] = await Promise.all([
18
+ tables_service_1.TableService.getTableInfo(db, table),
19
+ schema_service_1.SchemaService.getSchema(db, table),
20
+ ]);
21
+ if (!info) {
22
+ return (0, router_1.notFound)(res, `Table "${table}" does not exist.`);
23
+ }
24
+ res.locals.currentTable = table;
25
+ res.render('pages/schema', {
26
+ title: `Schema · ${table}`,
27
+ dbId,
28
+ table,
29
+ schema,
30
+ isInternal: table.startsWith('_'),
31
+ dialect: db.dialect.name,
32
+ isSqlite: db.dialect.name === 'sqlite',
33
+ isPostgres: db.dialect.name === 'postgres',
34
+ config: { dbId, table, isInternal: table.startsWith('_'), dialect: db.dialect.name },
35
+ });
36
+ }
37
+ catch (err) {
38
+ next(err);
39
+ }
40
+ });
41
+ router.get('/designer/:db', async (req, res, next) => {
42
+ try {
43
+ const db = ctx.getContext(req);
44
+ const dbId = req.params.db;
45
+ res.locals.dbId = dbId;
46
+ const tablesList = await tables_service_1.TableService.listTables(db);
47
+ const allNames = tablesList.map((t) => t.name);
48
+ (0, router_1.initPageLocals)(res, db, dbId, allNames);
49
+ res.render('pages/designer', {
50
+ title: 'New table',
51
+ dbId,
52
+ dialect: db.dialect.name,
53
+ isSqlite: db.dialect.name === 'sqlite',
54
+ isPostgres: db.dialect.name === 'postgres',
55
+ });
56
+ }
57
+ catch (err) {
58
+ next(err);
59
+ }
60
+ });
61
+ router.get('/designer/:db/:table', async (req, res, next) => {
62
+ try {
63
+ const db = ctx.getContext(req);
64
+ const dbId = req.params.db;
65
+ const table = req.params.table;
66
+ res.locals.dbId = dbId;
67
+ const tablesList = await tables_service_1.TableService.listTables(db);
68
+ const allNames = tablesList.map((t) => t.name);
69
+ (0, router_1.initPageLocals)(res, db, dbId, allNames);
70
+ const [info, schema] = await Promise.all([
71
+ tables_service_1.TableService.getTableInfo(db, table),
72
+ schema_service_1.SchemaService.getSchema(db, table),
73
+ ]);
74
+ if (!info) {
75
+ return (0, router_1.notFound)(res, `Table "${table}" does not exist.`);
76
+ }
77
+ res.locals.currentTable = table;
78
+ res.render('pages/designer', {
79
+ title: `Edit table · ${table}`,
80
+ dbId,
81
+ table,
82
+ info,
83
+ schema,
84
+ dialect: db.dialect.name,
85
+ isSqlite: db.dialect.name === 'sqlite',
86
+ isPostgres: db.dialect.name === 'postgres',
87
+ });
88
+ }
89
+ catch (err) {
90
+ next(err);
91
+ }
92
+ });
93
+ router.get('/api/tables/:db/:table/schema', (0, router_1.wrap)(async (req, res) => {
94
+ const db = ctx.getContext(req);
95
+ const schema = await schema_service_1.SchemaService.getSchema(db, req.params.table);
96
+ (0, router_1.ok)(res, schema);
97
+ }));
98
+ router.post('/api/tables/:db/:table/columns', (0, router_1.wrap)(async (req, res) => {
99
+ const db = ctx.getContext(req);
100
+ const col = req.body;
101
+ if (!col?.name)
102
+ return (0, router_1.fail)(res, 'Column name is required.');
103
+ try {
104
+ await schema_service_1.SchemaService.addColumn(db, req.params.table, col);
105
+ (0, router_1.ok)(res, { message: `Column "${col.name}" added.` }, 201);
106
+ }
107
+ catch (err) {
108
+ (0, router_1.fail)(res, err.message ?? 'Failed to add column.');
109
+ }
110
+ }));
111
+ router.put('/api/tables/:db/:table/columns/:column', (0, router_1.wrap)(async (req, res) => {
112
+ const db = ctx.getContext(req);
113
+ const col = req.body;
114
+ if (!col)
115
+ return (0, router_1.fail)(res, 'Column definition is required.');
116
+ try {
117
+ await schema_service_1.SchemaService.modifyColumn(db, req.params.table, req.params.column, col);
118
+ (0, router_1.ok)(res, { message: `Column "${req.params.column}" updated.` });
119
+ }
120
+ catch (err) {
121
+ (0, router_1.fail)(res, err.message ?? 'Failed to modify column.');
122
+ }
123
+ }));
124
+ router.post('/api/tables/:db/:table/columns/:column/rename', (0, router_1.wrap)(async (req, res) => {
125
+ const db = ctx.getContext(req);
126
+ const newName = String(req.body?.name ?? '').trim();
127
+ if (!newName)
128
+ return (0, router_1.fail)(res, 'New column name is required.');
129
+ try {
130
+ await schema_service_1.SchemaService.renameColumn(db, req.params.table, req.params.column, newName);
131
+ (0, router_1.ok)(res, { message: `Column renamed to "${newName}".`, name: newName });
132
+ }
133
+ catch (err) {
134
+ (0, router_1.fail)(res, err.message ?? 'Failed to rename column.');
135
+ }
136
+ }));
137
+ router.delete('/api/tables/:db/:table/columns/:column', (0, router_1.wrap)(async (req, res) => {
138
+ const db = ctx.getContext(req);
139
+ try {
140
+ await schema_service_1.SchemaService.dropColumn(db, req.params.table, req.params.column);
141
+ (0, router_1.ok)(res, { message: `Column "${req.params.column}" dropped.` });
142
+ }
143
+ catch (err) {
144
+ (0, router_1.fail)(res, err.message ?? 'Failed to drop column.');
145
+ }
146
+ }));
147
+ router.post('/api/tables/:db/:table/indexes', (0, router_1.wrap)(async (req, res) => {
148
+ const db = ctx.getContext(req);
149
+ const def = req.body;
150
+ if (!def || !Array.isArray(def.columns) || def.columns.length === 0) {
151
+ return (0, router_1.fail)(res, 'At least one column is required for an index.');
152
+ }
153
+ try {
154
+ await schema_service_1.SchemaService.createIndex(db, req.params.table, def);
155
+ (0, router_1.ok)(res, { message: 'Index created.' }, 201);
156
+ }
157
+ catch (err) {
158
+ (0, router_1.fail)(res, err.message ?? 'Failed to create index.');
159
+ }
160
+ }));
161
+ router.delete('/api/tables/:db/:table/indexes/:index', (0, router_1.wrap)(async (req, res) => {
162
+ const db = ctx.getContext(req);
163
+ try {
164
+ await schema_service_1.SchemaService.dropIndex(db, req.params.index);
165
+ (0, router_1.ok)(res, { message: `Index "${req.params.index}" dropped.` });
166
+ }
167
+ catch (err) {
168
+ (0, router_1.fail)(res, err.message ?? 'Failed to drop index.');
169
+ }
170
+ }));
171
+ }
@@ -0,0 +1 @@
1
+ import type {ReferencingTableInfo,SchemaInfo} from '../../db/index';import type {DatabaseContext} from '../../core/context';import type {ColumnDef,IndexDef} from '../../sql/generator';export declare class SchemaService{static getSchema(ctx:DatabaseContext,table:string):Promise<SchemaInfo>;static getReferencingTables(ctx:DatabaseContext,table:string):Promise<ReferencingTableInfo[]>;static addColumn(ctx:DatabaseContext,table:string,col:ColumnDef):Promise<void>;static modifyColumn(ctx:DatabaseContext,table:string,colName:string,col:ColumnDef):Promise<void>;static renameColumn(ctx:DatabaseContext,table:string,colName:string,newName:string):Promise<void>;static dropColumn(ctx:DatabaseContext,table:string,colName:string):Promise<void>;static createIndex(ctx:DatabaseContext,table:string,def:IndexDef):Promise<void>;static dropIndex(ctx:DatabaseContext,indexName:string):Promise<void>;}