admindb 2.1.1 → 2.2.1

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 (91) hide show
  1. package/README.md +41 -12
  2. package/dist/app.js +230 -1
  3. package/dist/auth/config.js +121 -1
  4. package/dist/auth/crypto.js +108 -1
  5. package/dist/auth/index.js +21 -1
  6. package/dist/auth/middleware.js +85 -1
  7. package/dist/auth/routes.js +70 -1
  8. package/dist/auth/types.js +7 -1
  9. package/dist/cli/args.js +320 -1
  10. package/dist/cli/config.js +128 -1
  11. package/dist/cli/index.js +20 -1
  12. package/dist/cli/runner.js +211 -1
  13. package/dist/cli.js +4 -1
  14. package/dist/data/chain.d.ts +1 -0
  15. package/dist/data/chain.js +707 -0
  16. package/dist/data/datasets.js +129 -1
  17. package/dist/data/detector.d.ts +1 -1
  18. package/dist/data/detector.js +511 -1
  19. package/dist/data/engine.d.ts +1 -1
  20. package/dist/data/engine.js +245 -1
  21. package/dist/data/index.d.ts +1 -1
  22. package/dist/data/index.js +22 -1
  23. package/dist/data/strategies.d.ts +1 -1
  24. package/dist/data/strategies.js +339 -1
  25. package/dist/data/types.d.ts +1 -1
  26. package/dist/data/types.js +5 -1
  27. package/dist/db/database.d.ts +1 -1
  28. package/dist/db/database.js +477 -1
  29. package/dist/db/export.d.ts +1 -1
  30. package/dist/db/export.js +90 -1
  31. package/dist/db/filters.js +142 -1
  32. package/dist/db/index.js +24 -1
  33. package/dist/db/introspection.js +167 -1
  34. package/dist/db/manager.js +351 -1
  35. package/dist/db/migrations.js +107 -1
  36. package/dist/db/postgres.d.ts +1 -1
  37. package/dist/db/postgres.js +735 -1
  38. package/dist/db/types.d.ts +1 -1
  39. package/dist/db/types.js +6 -1
  40. package/dist/index.js +52 -1
  41. package/dist/public/css/app.css +1 -1
  42. package/dist/public/js/api.js +1 -1
  43. package/dist/public/js/browse.js +1 -1
  44. package/dist/public/js/common.js +1 -0
  45. package/dist/public/js/databases.js +1 -1
  46. package/dist/public/js/designer.js +1 -1
  47. package/dist/public/js/forms.js +1 -1
  48. package/dist/public/js/home.js +1 -0
  49. package/dist/public/js/inspector.js +1 -1
  50. package/dist/public/js/query.js +1 -1
  51. package/dist/public/js/schema.js +1 -1
  52. package/dist/public/js/seed.js +1 -1
  53. package/dist/routes/api/erd.d.ts +1 -0
  54. package/dist/routes/api/erd.js +50 -0
  55. package/dist/routes/api/helpers.d.ts +1 -1
  56. package/dist/routes/api/helpers.js +163 -1
  57. package/dist/routes/api/import-export.js +83 -1
  58. package/dist/routes/api/index.js +48 -1
  59. package/dist/routes/api/query.js +78 -1
  60. package/dist/routes/api/rows.js +362 -1
  61. package/dist/routes/api/seed.js +153 -1
  62. package/dist/routes/api/tables.js +231 -1
  63. package/dist/routes/databases.js +228 -1
  64. package/dist/routes/index.js +19 -1
  65. package/dist/routes/pages.js +397 -1
  66. package/dist/serverless.js +174 -1
  67. package/dist/sql/classifier.js +24 -1
  68. package/dist/sql/error-analyzer.d.ts +1 -0
  69. package/dist/sql/error-analyzer.js +550 -0
  70. package/dist/sql/generator.js +319 -1
  71. package/dist/sql/index.js +18 -1
  72. package/dist/types/api.js +2 -1
  73. package/dist/utils/colors.js +54 -1
  74. package/dist/utils/common.d.ts +1 -1
  75. package/dist/utils/common.js +225 -1
  76. package/dist/utils/csv.js +67 -1
  77. package/dist/utils/datatype.js +181 -1
  78. package/dist/utils/icons.js +81 -1
  79. package/dist/utils/index.js +20 -1
  80. package/dist/utils/logger.js +36 -1
  81. package/dist/views/layouts/main.hbs +14 -1
  82. package/dist/views/pages/erd.hbs +1241 -0
  83. package/dist/views/pages/home.hbs +98 -2
  84. package/dist/views/pages/info.hbs +107 -0
  85. package/dist/views/pages/query.hbs +54 -12
  86. package/dist/views/pages/seed.hbs +435 -171
  87. package/dist/views/partials/sidebar.hbs +12 -0
  88. package/docs/API.md +6 -1
  89. package/docs/EXAMPLES.md +3 -0
  90. package/package.json +5 -4
  91. package/dist/public/js/icons.js +0 -1
@@ -1 +1,83 @@
1
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.registerImportExportRoutes=function(router,ctx){const{db:db}=ctx;router.get("/api/tables/:table/export",(0,helpers_1.wrap)(async(req,res)=>{const info=await(0,helpers_1.requireTable)(db,req.params.table);if(!info)return(0,helpers_1.fail)(res,`Table "${req.params.table}" does not exist.`,404);const format=String(req.query.format??"csv").toLowerCase(),filters=(0,common_1.parseFilters)(req.query.f),r=await db.getRows(req.params.table,{limit:1e6,filters:filters});if(!r.success)return(0,helpers_1.fail)(res,r.error??"Failed to export table.");const rows=(r.data??[]).map(common_1.normalizeRow),columns=info.columns.map(c=>c.name);if("json"===format)return res.setHeader("Content-Type","application/json; charset=utf-8"),res.setHeader("Content-Disposition",`attachment; filename="${req.params.table}.json"`),res.send((0,csv_1.toJson)(rows));res.setHeader("Content-Type","text/csv; charset=utf-8"),res.setHeader("Content-Disposition",`attachment; filename="${req.params.table}.csv"`),res.send((0,csv_1.toCsv)(rows,columns))}));const importCsvHandler=(0,helpers_1.wrap)(async(req,res)=>{const info=await(0,helpers_1.requireTable)(db,req.params.table);if(!info)return(0,helpers_1.fail)(res,`Table "${req.params.table}" does not exist.`,404);const csvText="string"==typeof req.body?req.body:String(req.body?.csv??"");if(!csvText.trim())return(0,helpers_1.fail)(res,"No CSV data provided.");const parsed=(0,csv_1.parseCsv)(csvText);if(!parsed.length)return(0,helpers_1.fail)(res,"CSV file is empty.");const header=parsed[0].map(h=>h.trim()),typeMap=new Map(info.columns.map(c=>[c.name,c.type]));if(!header.filter(h=>typeMap.has(h)).length)return(0,helpers_1.fail)(res,"None of the CSV columns match columns in this table.");const rows=parsed.slice(1).map(line=>{const fields=[];for(let i=0;i<header.length;i++){const col=header[i];if(!typeMap.has(col))continue;const raw=line[i]??"";fields.push({column:col,value:(0,common_1.coerceFormValue)(raw,typeMap.get(col))})}return fields}),r=await db.insertRows(req.params.table,rows);if(!r.success)return(0,helpers_1.fail)(res,r.error??"Failed to import CSV.");(0,helpers_1.ok)(res,{message:`${r.data?.inserted??0} row(s) imported.`,inserted:r.data?.inserted})});router.post("/api/tables/:table/import",importCsvHandler),router.post("/api/tables/:table/rows/import",importCsvHandler),router.post("/api/tables/:table/import/csv",importCsvHandler),router.post("/api/query/export",(0,helpers_1.wrap)(async(req,res)=>{const sql=String(req.body?.sql??"").trim(),format=String(req.body?.format??"csv").toLowerCase();if(!sql)return(0,helpers_1.fail)(res,"SQL query is required.");const r=await db.all(sql);if(!r.success)return(0,helpers_1.fail)(res,r.error??"Query failed.",400);const rows=(r.data??[]).map(common_1.normalizeRow),columns=rows.length?Object.keys(rows[0]):[];if("json"===format)return res.setHeader("Content-Type","application/json; charset=utf-8"),res.setHeader("Content-Disposition",'attachment; filename="query_results.json"'),res.send((0,csv_1.toJson)(rows));res.setHeader("Content-Type","text/csv; charset=utf-8"),res.setHeader("Content-Disposition",'attachment; filename="query_results.csv"'),res.send((0,csv_1.toCsv)(rows,columns))}))};const common_1=require("../../utils/common"),csv_1=require("../../utils/csv"),helpers_1=require("./helpers");
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerImportExportRoutes = registerImportExportRoutes;
4
+ const common_1 = require("../../utils/common");
5
+ const csv_1 = require("../../utils/csv");
6
+ const helpers_1 = require("./helpers");
7
+ function registerImportExportRoutes(router, ctx) {
8
+ const { db } = ctx;
9
+ router.get('/api/tables/:table/export', (0, helpers_1.wrap)(async (req, res) => {
10
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
11
+ if (!info)
12
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
13
+ const format = String(req.query.format ?? 'csv').toLowerCase();
14
+ const filters = (0, common_1.parseFilters)(req.query.f);
15
+ const r = await db.getRows(req.params.table, { limit: 1_000_000, filters });
16
+ if (!r.success)
17
+ return (0, helpers_1.fail)(res, r.error ?? 'Failed to export table.');
18
+ const rows = (r.data ?? []).map(common_1.normalizeRow);
19
+ const columns = info.columns.map((c) => c.name);
20
+ if (format === 'json') {
21
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
22
+ res.setHeader('Content-Disposition', `attachment; filename="${req.params.table}.json"`);
23
+ return res.send((0, csv_1.toJson)(rows));
24
+ }
25
+ res.setHeader('Content-Type', 'text/csv; charset=utf-8');
26
+ res.setHeader('Content-Disposition', `attachment; filename="${req.params.table}.csv"`);
27
+ res.send((0, csv_1.toCsv)(rows, columns));
28
+ }));
29
+ const importCsvHandler = (0, helpers_1.wrap)(async (req, res) => {
30
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
31
+ if (!info)
32
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
33
+ const csvText = typeof req.body === 'string' ? req.body : String(req.body?.csv ?? '');
34
+ if (!csvText.trim())
35
+ return (0, helpers_1.fail)(res, 'No CSV data provided.');
36
+ const parsed = (0, csv_1.parseCsv)(csvText);
37
+ if (!parsed.length)
38
+ return (0, helpers_1.fail)(res, 'CSV file is empty.');
39
+ const header = parsed[0].map((h) => h.trim());
40
+ const typeMap = new Map(info.columns.map((c) => [c.name, c.type]));
41
+ const validCols = header.filter((h) => typeMap.has(h));
42
+ if (!validCols.length) {
43
+ return (0, helpers_1.fail)(res, 'None of the CSV columns match columns in this table.');
44
+ }
45
+ const rows = parsed.slice(1).map((line) => {
46
+ const fields = [];
47
+ for (let i = 0; i < header.length; i++) {
48
+ const col = header[i];
49
+ if (!typeMap.has(col))
50
+ continue;
51
+ const raw = line[i] ?? '';
52
+ fields.push({ column: col, value: (0, common_1.coerceFormValue)(raw, typeMap.get(col)) });
53
+ }
54
+ return fields;
55
+ });
56
+ const r = await db.insertRows(req.params.table, rows);
57
+ if (!r.success)
58
+ return (0, helpers_1.fail)(res, r.error ?? 'Failed to import CSV.');
59
+ (0, helpers_1.ok)(res, { message: `${r.data?.inserted ?? 0} row(s) imported.`, inserted: r.data?.inserted });
60
+ });
61
+ router.post('/api/tables/:table/import', importCsvHandler);
62
+ router.post('/api/tables/:table/rows/import', importCsvHandler);
63
+ router.post('/api/tables/:table/import/csv', importCsvHandler);
64
+ router.post('/api/query/export', (0, helpers_1.wrap)(async (req, res) => {
65
+ const sql = String(req.body?.sql ?? '').trim();
66
+ const format = String(req.body?.format ?? 'csv').toLowerCase();
67
+ if (!sql)
68
+ return (0, helpers_1.fail)(res, 'SQL query is required.');
69
+ const r = await db.all(sql);
70
+ if (!r.success)
71
+ return (0, helpers_1.fail)(res, r.error ?? 'Query failed.', 400);
72
+ const rows = (r.data ?? []).map(common_1.normalizeRow);
73
+ const columns = rows.length ? Object.keys(rows[0]) : [];
74
+ if (format === 'json') {
75
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
76
+ res.setHeader('Content-Disposition', 'attachment; filename="query_results.json"');
77
+ return res.send((0, csv_1.toJson)(rows));
78
+ }
79
+ res.setHeader('Content-Type', 'text/csv; charset=utf-8');
80
+ res.setHeader('Content-Disposition', 'attachment; filename="query_results.csv"');
81
+ res.send((0, csv_1.toCsv)(rows, columns));
82
+ }));
83
+ }
@@ -1 +1,48 @@
1
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.registerApi=function(router,ctx){const{db:db}=ctx;router.use((req,res,next)=>{if(!db.isReadOnly||"GET"===req.method)return next();const p=req.path;return p.endsWith("/generate")||p.endsWith("/preview")||p.endsWith("/api/query/export")||p.endsWith("/api/query")||p.endsWith("/rows/bulk-impact")||p.endsWith("/rows/bulk-export")?next():res.status(403).json({success:!1,error:"Database is open in read-only mode — write operations are disabled."})}),(0,tables_1.registerTableRoutes)(router,ctx),(0,rows_1.registerRowRoutes)(router,ctx),(0,import_export_1.registerImportExportRoutes)(router,ctx),(0,seed_1.registerSeedRoutes)(router,ctx),(0,query_1.registerQueryRoutes)(router,ctx)};const tables_1=require("./tables"),rows_1=require("./rows"),import_export_1=require("./import-export"),seed_1=require("./seed"),query_1=require("./query");
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerApi = registerApi;
4
+ const tables_1 = require("./tables");
5
+ const rows_1 = require("./rows");
6
+ const import_export_1 = require("./import-export");
7
+ const seed_1 = require("./seed");
8
+ const query_1 = require("./query");
9
+ const erd_1 = require("./erd");
10
+ const export_1 = require("../../db/export");
11
+ function registerApi(router, ctx) {
12
+ const { db } = ctx;
13
+ router.use((req, res, next) => {
14
+ if (!db.isReadOnly || req.method === 'GET')
15
+ return next();
16
+ const p = req.path;
17
+ if (p.endsWith('/generate') ||
18
+ p.endsWith('/preview') ||
19
+ p.endsWith('/api/query/export') ||
20
+ p.endsWith('/api/query') ||
21
+ p.endsWith('/rows/bulk-impact') ||
22
+ p.endsWith('/rows/bulk-export')) {
23
+ return next();
24
+ }
25
+ return res.status(403).json({
26
+ success: false,
27
+ error: 'Database is open in read-only mode — write operations are disabled.',
28
+ });
29
+ });
30
+ (0, tables_1.registerTableRoutes)(router, ctx);
31
+ (0, rows_1.registerRowRoutes)(router, ctx);
32
+ (0, import_export_1.registerImportExportRoutes)(router, ctx);
33
+ (0, seed_1.registerSeedRoutes)(router, ctx);
34
+ (0, query_1.registerQueryRoutes)(router, ctx);
35
+ (0, erd_1.registerErdRoutes)(router, ctx);
36
+ router.get('/api/info/ddl', async (_req, res) => {
37
+ try {
38
+ const dump = await (0, export_1.generateSchemaDump)(db);
39
+ if (!dump.success) {
40
+ return res.status(500).json({ success: false, error: dump.error });
41
+ }
42
+ res.json({ success: true, data: dump.data });
43
+ }
44
+ catch (err) {
45
+ res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
46
+ }
47
+ });
48
+ }
@@ -1 +1,78 @@
1
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.registerQueryRoutes=function(router,ctx){const{db:db}=ctx;router.post("/api/query",(0,helpers_1.wrap)(async(req,res)=>{const rawSql=String(req.body?.sql??"").trim();if(!rawSql)return(0,helpers_1.fail)(res,"SQL query is required.");const{kind:kind}=(0,classifier_1.classifySql)(rawSql);if(db.hasMultipleStatements(rawSql)){if(db.isReadOnly)return(0,helpers_1.fail)(res,"Database is open in read-only mode — write statements are disabled.",403);const r=await db.execResult(rawSql);return r.success?(0,helpers_1.ok)(res,{kind:"script",message:"Script executed successfully."}):(0,helpers_1.fail)(res,r.error??"Query failed.",400)}if("select"===kind||"read"===kind||"count"===kind){const r=await db.all(rawSql);if(!r.success)return(0,helpers_1.fail)(res,r.error??"Query failed.",400);const rows=(r.data??[]).map(common_1.normalizeRow),columns=rows.length?Object.keys(rows[0]):[];return(0,helpers_1.ok)(res,{kind:"select",columns:columns,rows:rows})}if(db.isReadOnly)return(0,helpers_1.fail)(res,"Database is open in read-only mode — write statements are disabled.",403);const r=await db.runWrite(rawSql);if(!r.success)return(0,helpers_1.fail)(res,r.error??"Query failed.",400);const changes=r.data?.changes;(0,helpers_1.ok)(res,{kind:"write",changes:changes??null,message:null!=changes?`${changes} row(s) affected.`:"Statement executed successfully."})})),router.get("/api/queries",(0,helpers_1.wrap)(async(_req,res)=>{const r=await db.listSavedQueries();if(!r.success)return(0,helpers_1.fail)(res,r.error??"Failed to load queries.",500);(0,helpers_1.ok)(res,r.data)})),router.post("/api/queries",(0,helpers_1.wrap)(async(req,res)=>{const name=String(req.body?.name??"").trim(),sql=String(req.body?.sql??"").trim();if(!name)return(0,helpers_1.fail)(res,"Query name is required.");if(!sql)return(0,helpers_1.fail)(res,"Query SQL is required.");const r=await db.saveQuery(name,sql);if(!r.success)return(0,helpers_1.fail)(res,r.error??"Failed to save query.",400);(0,helpers_1.ok)(res,{message:"Query saved."},201)})),router.delete("/api/queries/:id",(0,helpers_1.wrap)(async(req,res)=>{const r=await db.deleteSavedQuery(req.params.id);if(!r.success)return(0,helpers_1.fail)(res,r.error??"Failed to delete query.",400);(0,helpers_1.ok)(res,{message:"Query deleted."})}))};const classifier_1=require("../../sql/classifier"),common_1=require("../../utils/common"),helpers_1=require("./helpers");
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerQueryRoutes = registerQueryRoutes;
4
+ const classifier_1 = require("../../sql/classifier");
5
+ const error_analyzer_1 = require("../../sql/error-analyzer");
6
+ const common_1 = require("../../utils/common");
7
+ const helpers_1 = require("./helpers");
8
+ function registerQueryRoutes(router, ctx) {
9
+ const { db } = ctx;
10
+ router.post('/api/query', (0, helpers_1.wrap)(async (req, res) => {
11
+ const rawSql = String(req.body?.sql ?? '').trim();
12
+ if (!rawSql)
13
+ return (0, helpers_1.fail)(res, 'SQL query is required.');
14
+ const { kind } = (0, classifier_1.classifySql)(rawSql);
15
+ if (db.hasMultipleStatements(rawSql)) {
16
+ if (db.isReadOnly) {
17
+ return (0, helpers_1.fail)(res, 'Database is open in read-only mode — write statements are disabled.', 403);
18
+ }
19
+ const r = await db.execResult(rawSql);
20
+ if (!r.success) {
21
+ const details = await (0, error_analyzer_1.analyzeSqlError)(rawSql, r.error ?? 'Script execution failed.', db);
22
+ return (0, helpers_1.fail)(res, r.error ?? 'Query failed.', 400, details);
23
+ }
24
+ return (0, helpers_1.ok)(res, {
25
+ kind: 'script',
26
+ message: 'Script executed successfully.',
27
+ });
28
+ }
29
+ if (kind === 'select' || kind === 'read' || kind === 'count') {
30
+ const r = await db.all(rawSql);
31
+ if (!r.success) {
32
+ const details = await (0, error_analyzer_1.analyzeSqlError)(rawSql, r.error ?? 'Query execution failed.', db);
33
+ return (0, helpers_1.fail)(res, r.error ?? 'Query failed.', 400, details);
34
+ }
35
+ const rows = (r.data ?? []).map(common_1.normalizeRow);
36
+ const columns = rows.length ? Object.keys(rows[0]) : [];
37
+ return (0, helpers_1.ok)(res, { kind: 'select', columns, rows });
38
+ }
39
+ if (db.isReadOnly) {
40
+ return (0, helpers_1.fail)(res, 'Database is open in read-only mode — write statements are disabled.', 403);
41
+ }
42
+ const r = await db.runWrite(rawSql);
43
+ if (!r.success) {
44
+ const details = await (0, error_analyzer_1.analyzeSqlError)(rawSql, r.error ?? 'Statement execution failed.', db);
45
+ return (0, helpers_1.fail)(res, r.error ?? 'Query failed.', 400, details);
46
+ }
47
+ const changes = r.data?.changes;
48
+ (0, helpers_1.ok)(res, {
49
+ kind: 'write',
50
+ changes: changes ?? null,
51
+ message: changes != null ? `${changes} row(s) affected.` : 'Statement executed successfully.',
52
+ });
53
+ }));
54
+ router.get('/api/queries', (0, helpers_1.wrap)(async (_req, res) => {
55
+ const r = await db.listSavedQueries();
56
+ if (!r.success)
57
+ return (0, helpers_1.fail)(res, r.error ?? 'Failed to load queries.', 500);
58
+ (0, helpers_1.ok)(res, r.data);
59
+ }));
60
+ router.post('/api/queries', (0, helpers_1.wrap)(async (req, res) => {
61
+ const name = String(req.body?.name ?? '').trim();
62
+ const sql = String(req.body?.sql ?? '').trim();
63
+ if (!name)
64
+ return (0, helpers_1.fail)(res, 'Query name is required.');
65
+ if (!sql)
66
+ return (0, helpers_1.fail)(res, 'Query SQL is required.');
67
+ const r = await db.saveQuery(name, sql);
68
+ if (!r.success)
69
+ return (0, helpers_1.fail)(res, r.error ?? 'Failed to save query.', 400);
70
+ (0, helpers_1.ok)(res, { message: 'Query saved.' }, 201);
71
+ }));
72
+ router.delete('/api/queries/:id', (0, helpers_1.wrap)(async (req, res) => {
73
+ const r = await db.deleteSavedQuery(req.params.id);
74
+ if (!r.success)
75
+ return (0, helpers_1.fail)(res, r.error ?? 'Failed to delete query.', 400);
76
+ (0, helpers_1.ok)(res, { message: 'Query deleted.' });
77
+ }));
78
+ }
@@ -1 +1,362 @@
1
- Object.defineProperty(exports,"__esModule",{value:!0}),exports.registerRowRoutes=function(router,ctx){const{db:db}=ctx;router.get("/api/tables/:table/rows",(0,helpers_1.wrap)(async(req,res)=>{const page=Number.parseInt(String(req.query.page??"1"),10),limit=Number.parseInt(String(req.query.limit??"50"),10),orderBy=req.query.orderBy?String(req.query.orderBy):void 0,orderDir="desc"===req.query.orderDir?"desc":"asc",filters=(0,common_1.parseFilters)(req.query.f),[rowsR,totalR]=await Promise.all([db.getRows(req.params.table,{page:page,limit:limit,orderBy:orderBy,orderDir:orderDir,filters:filters}),db.getRowCount(req.params.table,filters)]);if(!rowsR.success)return(0,helpers_1.fail)(res,rowsR.error??"Failed to load rows.");const rows=(rowsR.data??[]).map(common_1.normalizeRow);(0,helpers_1.ok)(res,{rows:rows,total:totalR.success?totalR.data:rows.length,page:page,limit:limit})})),router.get("/api/tables/:table/rows/count",(0,helpers_1.wrap)(async(req,res)=>{const filters=(0,common_1.parseFilters)(req.query.f),r=await db.getRowCount(req.params.table,filters);if(!r.success)return(0,helpers_1.fail)(res,r.error??"Failed to count rows.");(0,helpers_1.ok)(res,{count:r.data})})),router.get("/api/tables/:table/row/:id",(0,helpers_1.wrap)(async(req,res)=>{const info=await(0,helpers_1.requireTable)(db,req.params.table);if(!info)return(0,helpers_1.fail)(res,`Table "${req.params.table}" does not exist.`,404);const where=(0,helpers_1.pkWhere)(info,req.params.id);if(!where)return(0,helpers_1.fail)(res,"Invalid primary key.",400);const r=await db.getRow(req.params.table,where);return r.success?r.data?void(0,helpers_1.ok)(res,(0,common_1.normalizeRow)(r.data)):(0,helpers_1.fail)(res,"Row not found.",404):(0,helpers_1.fail)(res,r.error??"Failed to load row.")})),router.get("/api/tables/:table/row/:id/blob/:column",(0,helpers_1.wrap)(async(req,res)=>{const info=await(0,helpers_1.requireTable)(db,req.params.table);if(!info)return(0,helpers_1.fail)(res,`Table "${req.params.table}" does not exist.`,404);const where=(0,helpers_1.pkWhere)(info,req.params.id);if(!where)return(0,helpers_1.fail)(res,"Invalid primary key.",400);const r=await db.getRow(req.params.table,where);if(!r.success)return(0,helpers_1.fail)(res,r.error??"Failed to load row.");if(!r.data)return(0,helpers_1.fail)(res,"Row not found.",404);const colName=req.params.column,rawVal=r.data[colName];if(null==rawVal)return(0,helpers_1.fail)(res,"Column value is NULL.",404);let buf;buf=Buffer.isBuffer(rawVal)||rawVal instanceof Uint8Array?Buffer.isBuffer(rawVal)?rawVal:Buffer.from(rawVal):"string"==typeof rawVal&&/^0x[0-9a-f]*$/i.test(rawVal)?Buffer.from(rawVal.slice(2),"hex"):Buffer.from(String(rawVal),"utf8");const mimeInfo=(0,datatype_1.sniffMimeType)(buf);if(res.setHeader("Content-Type",mimeInfo.mime),res.setHeader("Content-Length",buf.length),res.setHeader("Cache-Control","no-cache"),"1"===req.query.download||"true"===req.query.download){const filename=`${req.params.table}_${colName}_${req.params.id}.${mimeInfo.ext}`;res.setHeader("Content-Disposition",`attachment; filename="${filename}"`)}res.end(buf)})),router.get("/api/tables/:table/row/:id/blob/:column/meta",(0,helpers_1.wrap)(async(req,res)=>{const info=await(0,helpers_1.requireTable)(db,req.params.table);if(!info)return(0,helpers_1.fail)(res,`Table "${req.params.table}" does not exist.`,404);const where=(0,helpers_1.pkWhere)(info,req.params.id);if(!where)return(0,helpers_1.fail)(res,"Invalid primary key.",400);const r=await db.getRow(req.params.table,where);if(!r.success)return(0,helpers_1.fail)(res,r.error??"Failed to load row.");if(!r.data)return(0,helpers_1.fail)(res,"Row not found.",404);const colName=req.params.column,rawVal=r.data[colName];if(null==rawVal)return(0,helpers_1.ok)(res,{isNull:!0,size:0,sizeFormatted:"0 B"});const meta=(0,datatype_1.analyzeBlob)(rawVal);(0,helpers_1.ok)(res,{isNull:!1,...meta})})),router.put("/api/tables/:table/row/:id/blob/:column",(0,helpers_1.wrap)(async(req,res)=>{const info=await(0,helpers_1.requireTable)(db,req.params.table);if(!info)return(0,helpers_1.fail)(res,`Table "${req.params.table}" does not exist.`,404);const where=(0,helpers_1.pkWhere)(info,req.params.id);if(!where)return(0,helpers_1.fail)(res,"Invalid primary key.",400);const colName=req.params.column;let buf;if(void 0===req.body?.data)return(0,helpers_1.fail)(res,"Payload data is required.");{const format=req.body.format||"base64";if("base64"===format){const base64Str=String(req.body.data).replace(/^data:[^;]+;base64,/,"");buf=Buffer.from(base64Str,"base64")}else if("hex"===format){const hexStr=String(req.body.data).replace(/^0x/i,"");buf=Buffer.from(hexStr,"hex")}else buf=Buffer.from(String(req.body.data),"utf8")}const updateRes=await db.updateRow(req.params.table,[{column:colName,value:buf}],where);if(!updateRes.success)return(0,helpers_1.fail)(res,updateRes.error??"Failed to update BLOB.");(0,helpers_1.ok)(res,{message:`Updated BLOB in column "${colName}".`,size:buf.length})})),router.post("/api/tables/:table/rows/generate",(0,helpers_1.wrap)(async(req,res)=>{const info=await(0,helpers_1.requireTable)(db,req.params.table);if(!info)return(0,helpers_1.fail)(res,`Table "${req.params.table}" does not exist.`,404);const fields=(0,helpers_1.buildFields)(info,req.body?.values??req.body??{});if(!fields.length)return(0,helpers_1.fail)(res,"No valid column values provided.");const sql=(0,generator_1.generateInsert)(req.params.table,fields);(0,helpers_1.ok)(res,{sql:sql})}));const generateUpdateHandler=(0,helpers_1.wrap)(async(req,res)=>{const info=await(0,helpers_1.requireTable)(db,req.params.table);if(!info)return(0,helpers_1.fail)(res,`Table "${req.params.table}" does not exist.`,404);const id=req.params.id||(req.query.id?String(req.query.id):null);if(!id)return(0,helpers_1.fail)(res,"Primary key id is required.");const where=(0,helpers_1.pkWhere)(info,id);if(!where)return(0,helpers_1.fail)(res,"Invalid primary key.",400);const nulls=Array.isArray(req.body?.nulls)?req.body.nulls:[],fields=(0,helpers_1.buildUpdateFields)(info,req.body?.values??req.body??{},nulls);if(!fields.length)return(0,helpers_1.fail)(res,"No fields to update.");const sql=(0,generator_1.generateUpdate)(req.params.table,fields,where);(0,helpers_1.ok)(res,{sql:sql})});router.put("/api/tables/:table/row/:id/generate",generateUpdateHandler),router.post("/api/tables/:table/row/:id/generate",generateUpdateHandler),router.put("/api/tables/:table/rows/generate",generateUpdateHandler),router.post("/api/tables/:table/rows",(0,helpers_1.wrap)(async(req,res)=>{const info=await(0,helpers_1.requireTable)(db,req.params.table);if(!info)return(0,helpers_1.fail)(res,`Table "${req.params.table}" does not exist.`,404);const isBatch=Array.isArray(req.body?.rows),rowList=isBatch?req.body.rows:[req.body?.values??req.body??{}];if(!rowList.length)return(0,helpers_1.fail)(res,"No rows provided.");const fieldsList=rowList.map(row=>(0,helpers_1.buildFields)(info,row));if(!isBatch){const fields=fieldsList[0];if(!fields.length)return(0,helpers_1.fail)(res,"No valid column values provided.");const r=await db.insertRow(req.params.table,fields);return r.success?(0,helpers_1.ok)(res,{message:"Row inserted.",id:r.data?.lastInsertRowid},201):(0,helpers_1.fail)(res,r.error??"Failed to insert row.")}const r=await db.insertRows(req.params.table,fieldsList);if(!r.success)return(0,helpers_1.fail)(res,r.error??"Failed to insert rows.");(0,helpers_1.ok)(res,{message:`${r.data?.inserted??0} row(s) inserted.`,inserted:r.data?.inserted},201)}));const updateSingleRowHandler=(0,helpers_1.wrap)(async(req,res)=>{const info=await(0,helpers_1.requireTable)(db,req.params.table);if(!info)return(0,helpers_1.fail)(res,`Table "${req.params.table}" does not exist.`,404);const id=req.params.id||(req.query.id?String(req.query.id):null);if(!id)return(0,helpers_1.fail)(res,"Primary key id is required.");const where=(0,helpers_1.pkWhere)(info,id);if(!where)return(0,helpers_1.fail)(res,"Invalid primary key.");const nulls=Array.isArray(req.body?.nulls)?req.body.nulls:[],fields=(0,helpers_1.buildUpdateFields)(info,req.body?.values??req.body??{},nulls);if(!fields.length)return(0,helpers_1.fail)(res,"No fields to update.");const r=await db.updateRow(req.params.table,fields,where);if(!r.success)return(0,helpers_1.fail)(res,r.error??"Failed to update row.");(0,helpers_1.ok)(res,{message:"Row updated."})}),bulkUpdateHandler=(0,helpers_1.wrap)(async(req,res)=>{const info=await(0,helpers_1.requireTable)(db,req.params.table);if(!info)return(0,helpers_1.fail)(res,`Table "${req.params.table}" does not exist.`,404);if(Array.isArray(req.body?.updates)){const updates=req.body.updates;if(!updates.length)return(0,helpers_1.fail)(res,"No updates provided.");if(updates.length>helpers_1.MAX_BULK_ROWS)return(0,helpers_1.fail)(res,`Cannot update more than ${helpers_1.MAX_BULK_ROWS} rows at once.`);const rowUpdates=[];for(const u of updates){if(!u.id)return(0,helpers_1.fail)(res,"Every update requires an id.");const where=(0,helpers_1.pkWhere)(info,String(u.id));if(!where)return(0,helpers_1.fail)(res,`Invalid primary key: "${u.id}".`);const nulls=Array.isArray(u.nulls)?u.nulls.map(String):[],fields=(0,helpers_1.buildUpdateFields)(info,u.values??{},nulls);fields.length&&rowUpdates.push({fields:fields,where:where})}if(!rowUpdates.length)return(0,helpers_1.ok)(res,{message:"No changes to apply.",updated:0});const r=await db.updateRows(req.params.table,rowUpdates);return r.success?(0,helpers_1.ok)(res,{message:`${r.data?.updated??0} row(s) updated.`,updated:r.data?.updated}):(0,helpers_1.fail)(res,r.error??"Failed to update rows.")}return updateSingleRowHandler(req,res)});router.put("/api/tables/:table/row/:id",updateSingleRowHandler),router.put("/api/tables/:table/rows",bulkUpdateHandler),router.post("/api/tables/:table/rows/bulk-update",bulkUpdateHandler);const deleteSingleRowHandler=(0,helpers_1.wrap)(async(req,res)=>{const info=await(0,helpers_1.requireTable)(db,req.params.table);if(!info)return(0,helpers_1.fail)(res,`Table "${req.params.table}" does not exist.`,404);const id=req.params.id||(req.query.id?String(req.query.id):null);if(!id)return(0,helpers_1.fail)(res,"Primary key id is required.");const where=(0,helpers_1.pkWhere)(info,id);if(!where)return(0,helpers_1.fail)(res,"Invalid primary key.");const r=await db.deleteRow(req.params.table,where);if(!r.success)return(0,helpers_1.fail)(res,r.error??"Failed to delete row.");(0,helpers_1.ok)(res,{message:"Row deleted."})});router.delete("/api/tables/:table/row/:id",deleteSingleRowHandler),router.delete("/api/tables/:table/rows",deleteSingleRowHandler),router.get("/api/tables/:table/rows/:id/references",(0,helpers_1.wrap)(async(req,res)=>{const info=await(0,helpers_1.requireTable)(db,req.params.table);if(!info)return(0,helpers_1.fail)(res,`Table "${req.params.table}" does not exist.`,404);const where=(0,helpers_1.pkWhere)(info,req.params.id);if(!where)return(0,helpers_1.fail)(res,"Invalid primary key.",400);const rowR=await db.getRow(req.params.table,where);if(!rowR.success||!rowR.data)return(0,helpers_1.fail)(res,"Row not found.",404);const referencing=(await db.getReferencingTables(req.params.table)).data??[],results=[];for(const item of referencing)for(const ref of item.refs){const targetCol=ref.to||info.primaryKey[0];if(!targetCol)continue;const val=rowR.data[targetCol];if(null==val)continue;const fkR=await db.getRowsByFk(item.table,ref.from,val);if(fkR.success&&fkR.data){const rows=fkR.data.rows.map(common_1.normalizeRow),columns=rows.length>0?Object.keys(rows[0]):[];results.push({table:item.table,from:ref.from,to:targetCol,value:val,columns:columns,rows:rows,count:fkR.data.total,total:fkR.data.total})}}(0,helpers_1.ok)(res,{references:results})})),router.post("/api/tables/:table/rows/bulk-delete",(0,helpers_1.wrap)(async(req,res)=>{const info=await(0,helpers_1.requireTable)(db,req.params.table);if(!info)return(0,helpers_1.fail)(res,`Table "${req.params.table}" does not exist.`,404);const ids=req.body?.ids??[];if(!Array.isArray(ids)||!ids.length)return(0,helpers_1.fail)(res,"No row IDs provided.");if(ids.length>helpers_1.MAX_BULK_ROWS)return(0,helpers_1.fail)(res,`Cannot delete more than ${helpers_1.MAX_BULK_ROWS} rows at once.`);const wheres=(0,helpers_1.resolvePkRows)(info,ids);if(!wheres)return(0,helpers_1.fail)(res,"Invalid or unresolvable row IDs.");const r=await db.deleteRows(req.params.table,wheres);if(!r.success)return(0,helpers_1.fail)(res,r.error??"Failed to delete rows.");(0,helpers_1.ok)(res,{message:`${r.data?.deleted??0} row(s) deleted.`,deleted:r.data?.deleted})})),router.post("/api/tables/:table/rows/bulk-impact",(0,helpers_1.wrap)(async(req,res)=>{const info=await(0,helpers_1.requireTable)(db,req.params.table);if(!info)return(0,helpers_1.fail)(res,`Table "${req.params.table}" does not exist.`,404);const ids=req.body?.ids??[];if(!Array.isArray(ids)||!ids.length)return(0,helpers_1.ok)(res,{references:[],total:0});const wheres=(0,helpers_1.resolvePkRows)(info,ids);if(!wheres)return(0,helpers_1.ok)(res,{references:[],total:0});const impact=await(0,helpers_1.computeBulkImpact)(db,info,wheres);(0,helpers_1.ok)(res,impact)})),router.post("/api/tables/:table/rows/bulk-export",(0,helpers_1.wrap)(async(req,res)=>{const info=await(0,helpers_1.requireTable)(db,req.params.table);if(!info)return(0,helpers_1.fail)(res,`Table "${req.params.table}" does not exist.`,404);const ids=req.body?.ids??[];if(!Array.isArray(ids)||!ids.length)return(0,helpers_1.fail)(res,"No row IDs provided.");if(ids.length>helpers_1.MAX_BULK_ROWS)return(0,helpers_1.fail)(res,`Cannot export more than ${helpers_1.MAX_BULK_ROWS} rows at once.`);const wheres=(0,helpers_1.resolvePkRows)(info,ids);if(!wheres)return(0,helpers_1.fail)(res,"Invalid or unresolvable row IDs.");const format=String(req.body?.format??"csv").toLowerCase(),rowsR=await db.getRowsByPks(req.params.table,wheres);if(!rowsR.success)return(0,helpers_1.fail)(res,rowsR.error??"Failed to load rows.");const rows=(rowsR.data??[]).map(common_1.normalizeRow),columns=info.columns.map(c=>c.name);if("json"===format)return res.setHeader("Content-Type","application/json; charset=utf-8"),res.setHeader("Content-Disposition",`attachment; filename="${req.params.table}_selected.json"`),res.send((0,csv_1.toJson)(rows));res.setHeader("Content-Type","text/csv; charset=utf-8"),res.setHeader("Content-Disposition",`attachment; filename="${req.params.table}_selected.csv"`),res.send((0,csv_1.toCsv)(rows,columns))}))};const common_1=require("../../utils/common"),datatype_1=require("../../utils/datatype"),csv_1=require("../../utils/csv"),generator_1=require("../../sql/generator"),helpers_1=require("./helpers");
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerRowRoutes = registerRowRoutes;
4
+ const common_1 = require("../../utils/common");
5
+ const datatype_1 = require("../../utils/datatype");
6
+ const csv_1 = require("../../utils/csv");
7
+ const generator_1 = require("../../sql/generator");
8
+ const helpers_1 = require("./helpers");
9
+ function registerRowRoutes(router, ctx) {
10
+ const { db } = ctx;
11
+ router.get('/api/tables/:table/rows', (0, helpers_1.wrap)(async (req, res) => {
12
+ const page = Number.parseInt(String(req.query.page ?? '1'), 10);
13
+ const limit = Number.parseInt(String(req.query.limit ?? '50'), 10);
14
+ const orderBy = req.query.orderBy ? String(req.query.orderBy) : undefined;
15
+ const orderDir = req.query.orderDir === 'desc' ? 'desc' : 'asc';
16
+ const filters = (0, common_1.parseFilters)(req.query.f);
17
+ const [rowsR, totalR] = await Promise.all([
18
+ db.getRows(req.params.table, { page, limit, orderBy, orderDir, filters }),
19
+ db.getRowCount(req.params.table, filters),
20
+ ]);
21
+ if (!rowsR.success)
22
+ return (0, helpers_1.fail)(res, rowsR.error ?? 'Failed to load rows.');
23
+ const rows = (rowsR.data ?? []).map(common_1.normalizeRow);
24
+ (0, helpers_1.ok)(res, {
25
+ rows,
26
+ total: totalR.success ? totalR.data : rows.length,
27
+ page,
28
+ limit,
29
+ });
30
+ }));
31
+ router.get('/api/tables/:table/rows/count', (0, helpers_1.wrap)(async (req, res) => {
32
+ const filters = (0, common_1.parseFilters)(req.query.f);
33
+ const r = await db.getRowCount(req.params.table, filters);
34
+ if (!r.success)
35
+ return (0, helpers_1.fail)(res, r.error ?? 'Failed to count rows.');
36
+ (0, helpers_1.ok)(res, { count: r.data });
37
+ }));
38
+ router.get('/api/tables/:table/row/:id', (0, helpers_1.wrap)(async (req, res) => {
39
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
40
+ if (!info)
41
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
42
+ const where = (0, helpers_1.pkWhere)(info, req.params.id);
43
+ if (!where)
44
+ return (0, helpers_1.fail)(res, 'Invalid primary key.', 400);
45
+ const r = await db.getRow(req.params.table, where);
46
+ if (!r.success)
47
+ return (0, helpers_1.fail)(res, r.error ?? 'Failed to load row.');
48
+ if (!r.data)
49
+ return (0, helpers_1.fail)(res, 'Row not found.', 404);
50
+ (0, helpers_1.ok)(res, (0, common_1.normalizeRow)(r.data));
51
+ }));
52
+ router.get('/api/tables/:table/row/:id/blob/:column', (0, helpers_1.wrap)(async (req, res) => {
53
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
54
+ if (!info)
55
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
56
+ const where = (0, helpers_1.pkWhere)(info, req.params.id);
57
+ if (!where)
58
+ return (0, helpers_1.fail)(res, 'Invalid primary key.', 400);
59
+ const r = await db.getRow(req.params.table, where);
60
+ if (!r.success)
61
+ return (0, helpers_1.fail)(res, r.error ?? 'Failed to load row.');
62
+ if (!r.data)
63
+ return (0, helpers_1.fail)(res, 'Row not found.', 404);
64
+ const colName = req.params.column;
65
+ const rawVal = r.data[colName];
66
+ if (rawVal === null || rawVal === undefined) {
67
+ return (0, helpers_1.fail)(res, 'Column value is NULL.', 404);
68
+ }
69
+ let buf;
70
+ if (Buffer.isBuffer(rawVal) || rawVal instanceof Uint8Array) {
71
+ buf = Buffer.isBuffer(rawVal) ? rawVal : Buffer.from(rawVal);
72
+ }
73
+ else if (typeof rawVal === 'string' && (/^0x[0-9a-f]*$/i.test(rawVal))) {
74
+ buf = Buffer.from(rawVal.slice(2), 'hex');
75
+ }
76
+ else {
77
+ buf = Buffer.from(String(rawVal), 'utf8');
78
+ }
79
+ const mimeInfo = (0, datatype_1.sniffMimeType)(buf);
80
+ res.setHeader('Content-Type', mimeInfo.mime);
81
+ res.setHeader('Content-Length', buf.length);
82
+ res.setHeader('Cache-Control', 'no-cache');
83
+ if (req.query.download === '1' || req.query.download === 'true') {
84
+ const filename = `${req.params.table}_${colName}_${req.params.id}.${mimeInfo.ext}`;
85
+ res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
86
+ }
87
+ res.end(buf);
88
+ }));
89
+ router.get('/api/tables/:table/row/:id/blob/:column/meta', (0, helpers_1.wrap)(async (req, res) => {
90
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
91
+ if (!info)
92
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
93
+ const where = (0, helpers_1.pkWhere)(info, req.params.id);
94
+ if (!where)
95
+ return (0, helpers_1.fail)(res, 'Invalid primary key.', 400);
96
+ const r = await db.getRow(req.params.table, where);
97
+ if (!r.success)
98
+ return (0, helpers_1.fail)(res, r.error ?? 'Failed to load row.');
99
+ if (!r.data)
100
+ return (0, helpers_1.fail)(res, 'Row not found.', 404);
101
+ const colName = req.params.column;
102
+ const rawVal = r.data[colName];
103
+ if (rawVal === null || rawVal === undefined) {
104
+ return (0, helpers_1.ok)(res, { isNull: true, size: 0, sizeFormatted: '0 B' });
105
+ }
106
+ const meta = (0, datatype_1.analyzeBlob)(rawVal);
107
+ (0, helpers_1.ok)(res, { isNull: false, ...meta });
108
+ }));
109
+ router.put('/api/tables/:table/row/:id/blob/:column', (0, helpers_1.wrap)(async (req, res) => {
110
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
111
+ if (!info)
112
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
113
+ const where = (0, helpers_1.pkWhere)(info, req.params.id);
114
+ if (!where)
115
+ return (0, helpers_1.fail)(res, 'Invalid primary key.', 400);
116
+ const colName = req.params.column;
117
+ let buf;
118
+ if (req.body?.data !== undefined) {
119
+ const format = req.body.format || 'base64';
120
+ if (format === 'base64') {
121
+ const base64Str = String(req.body.data).replace(/^data:[^;]+;base64,/, '');
122
+ buf = Buffer.from(base64Str, 'base64');
123
+ }
124
+ else if (format === 'hex') {
125
+ const hexStr = String(req.body.data).replace(/^0x/i, '');
126
+ buf = Buffer.from(hexStr, 'hex');
127
+ }
128
+ else {
129
+ buf = Buffer.from(String(req.body.data), 'utf8');
130
+ }
131
+ }
132
+ else {
133
+ return (0, helpers_1.fail)(res, 'Payload data is required.');
134
+ }
135
+ const updateRes = await db.updateRow(req.params.table, [{ column: colName, value: buf }], where);
136
+ if (!updateRes.success)
137
+ return (0, helpers_1.fail)(res, updateRes.error ?? 'Failed to update BLOB.');
138
+ (0, helpers_1.ok)(res, { message: `Updated BLOB in column "${colName}".`, size: buf.length });
139
+ }));
140
+ router.post('/api/tables/:table/rows/generate', (0, helpers_1.wrap)(async (req, res) => {
141
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
142
+ if (!info)
143
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
144
+ const fields = (0, helpers_1.buildFields)(info, req.body?.values ?? req.body ?? {});
145
+ if (!fields.length)
146
+ return (0, helpers_1.fail)(res, 'No valid column values provided.');
147
+ const sql = (0, generator_1.generateInsert)(req.params.table, fields);
148
+ (0, helpers_1.ok)(res, { sql });
149
+ }));
150
+ const generateUpdateHandler = (0, helpers_1.wrap)(async (req, res) => {
151
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
152
+ if (!info)
153
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
154
+ const id = req.params.id || (req.query.id ? String(req.query.id) : null);
155
+ if (!id)
156
+ return (0, helpers_1.fail)(res, 'Primary key id is required.');
157
+ const where = (0, helpers_1.pkWhere)(info, id);
158
+ if (!where)
159
+ return (0, helpers_1.fail)(res, 'Invalid primary key.', 400);
160
+ const nulls = Array.isArray(req.body?.nulls) ? req.body.nulls : [];
161
+ const fields = (0, helpers_1.buildUpdateFields)(info, req.body?.values ?? req.body ?? {}, nulls);
162
+ if (!fields.length)
163
+ return (0, helpers_1.fail)(res, 'No fields to update.');
164
+ const sql = (0, generator_1.generateUpdate)(req.params.table, fields, where);
165
+ (0, helpers_1.ok)(res, { sql });
166
+ });
167
+ router.put('/api/tables/:table/row/:id/generate', generateUpdateHandler);
168
+ router.post('/api/tables/:table/row/:id/generate', generateUpdateHandler);
169
+ router.put('/api/tables/:table/rows/generate', generateUpdateHandler);
170
+ router.post('/api/tables/:table/rows', (0, helpers_1.wrap)(async (req, res) => {
171
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
172
+ if (!info)
173
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
174
+ const isBatch = Array.isArray(req.body?.rows);
175
+ const rowList = isBatch
176
+ ? req.body.rows
177
+ : [req.body?.values ?? req.body ?? {}];
178
+ if (!rowList.length)
179
+ return (0, helpers_1.fail)(res, 'No rows provided.');
180
+ const fieldsList = rowList.map((row) => (0, helpers_1.buildFields)(info, row));
181
+ if (!isBatch) {
182
+ const fields = fieldsList[0];
183
+ if (!fields.length)
184
+ return (0, helpers_1.fail)(res, 'No valid column values provided.');
185
+ const r = await db.insertRow(req.params.table, fields);
186
+ if (!r.success)
187
+ return (0, helpers_1.fail)(res, r.error ?? 'Failed to insert row.');
188
+ return (0, helpers_1.ok)(res, { message: 'Row inserted.', id: r.data?.lastInsertRowid }, 201);
189
+ }
190
+ const r = await db.insertRows(req.params.table, fieldsList);
191
+ if (!r.success)
192
+ return (0, helpers_1.fail)(res, r.error ?? 'Failed to insert rows.');
193
+ (0, helpers_1.ok)(res, { message: `${r.data?.inserted ?? 0} row(s) inserted.`, inserted: r.data?.inserted }, 201);
194
+ }));
195
+ const updateSingleRowHandler = (0, helpers_1.wrap)(async (req, res) => {
196
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
197
+ if (!info)
198
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
199
+ const id = req.params.id || (req.query.id ? String(req.query.id) : null);
200
+ if (!id)
201
+ return (0, helpers_1.fail)(res, 'Primary key id is required.');
202
+ const where = (0, helpers_1.pkWhere)(info, id);
203
+ if (!where)
204
+ return (0, helpers_1.fail)(res, 'Invalid primary key.');
205
+ const nulls = Array.isArray(req.body?.nulls) ? req.body.nulls : [];
206
+ const fields = (0, helpers_1.buildUpdateFields)(info, req.body?.values ?? req.body ?? {}, nulls);
207
+ if (!fields.length)
208
+ return (0, helpers_1.fail)(res, 'No fields to update.');
209
+ const r = await db.updateRow(req.params.table, fields, where);
210
+ if (!r.success)
211
+ return (0, helpers_1.fail)(res, r.error ?? 'Failed to update row.');
212
+ (0, helpers_1.ok)(res, { message: 'Row updated.' });
213
+ });
214
+ const bulkUpdateHandler = (0, helpers_1.wrap)(async (req, res) => {
215
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
216
+ if (!info)
217
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
218
+ if (Array.isArray(req.body?.updates)) {
219
+ const updates = req.body.updates;
220
+ if (!updates.length)
221
+ return (0, helpers_1.fail)(res, 'No updates provided.');
222
+ if (updates.length > helpers_1.MAX_BULK_ROWS)
223
+ return (0, helpers_1.fail)(res, `Cannot update more than ${helpers_1.MAX_BULK_ROWS} rows at once.`);
224
+ const rowUpdates = [];
225
+ for (const u of updates) {
226
+ if (!u.id)
227
+ return (0, helpers_1.fail)(res, 'Every update requires an id.');
228
+ const where = (0, helpers_1.pkWhere)(info, String(u.id));
229
+ if (!where)
230
+ return (0, helpers_1.fail)(res, `Invalid primary key: "${u.id}".`);
231
+ const nulls = Array.isArray(u.nulls) ? u.nulls.map(String) : [];
232
+ const fields = (0, helpers_1.buildUpdateFields)(info, u.values ?? {}, nulls);
233
+ if (fields.length)
234
+ rowUpdates.push({ fields, where });
235
+ }
236
+ if (!rowUpdates.length)
237
+ return (0, helpers_1.ok)(res, { message: 'No changes to apply.', updated: 0 });
238
+ const r = await db.updateRows(req.params.table, rowUpdates);
239
+ if (!r.success)
240
+ return (0, helpers_1.fail)(res, r.error ?? 'Failed to update rows.');
241
+ return (0, helpers_1.ok)(res, { message: `${r.data?.updated ?? 0} row(s) updated.`, updated: r.data?.updated });
242
+ }
243
+ return updateSingleRowHandler(req, res);
244
+ });
245
+ router.put('/api/tables/:table/row/:id', updateSingleRowHandler);
246
+ router.put('/api/tables/:table/rows', bulkUpdateHandler);
247
+ router.post('/api/tables/:table/rows/bulk-update', bulkUpdateHandler);
248
+ const deleteSingleRowHandler = (0, helpers_1.wrap)(async (req, res) => {
249
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
250
+ if (!info)
251
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
252
+ const id = req.params.id || (req.query.id ? String(req.query.id) : null);
253
+ if (!id)
254
+ return (0, helpers_1.fail)(res, 'Primary key id is required.');
255
+ const where = (0, helpers_1.pkWhere)(info, id);
256
+ if (!where)
257
+ return (0, helpers_1.fail)(res, 'Invalid primary key.');
258
+ const r = await db.deleteRow(req.params.table, where);
259
+ if (!r.success)
260
+ return (0, helpers_1.fail)(res, r.error ?? 'Failed to delete row.');
261
+ (0, helpers_1.ok)(res, { message: 'Row deleted.' });
262
+ });
263
+ router.delete('/api/tables/:table/row/:id', deleteSingleRowHandler);
264
+ router.delete('/api/tables/:table/rows', deleteSingleRowHandler);
265
+ router.get('/api/tables/:table/rows/:id/references', (0, helpers_1.wrap)(async (req, res) => {
266
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
267
+ if (!info)
268
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
269
+ const where = (0, helpers_1.pkWhere)(info, req.params.id);
270
+ if (!where)
271
+ return (0, helpers_1.fail)(res, 'Invalid primary key.', 400);
272
+ const rowR = await db.getRow(req.params.table, where);
273
+ if (!rowR.success || !rowR.data)
274
+ return (0, helpers_1.fail)(res, 'Row not found.', 404);
275
+ const refInfoR = await db.getReferencingTables(req.params.table);
276
+ const referencing = refInfoR.data ?? [];
277
+ const results = [];
278
+ for (const item of referencing) {
279
+ for (const ref of item.refs) {
280
+ const targetCol = ref.to || info.primaryKey[0];
281
+ if (!targetCol)
282
+ continue;
283
+ const val = rowR.data[targetCol];
284
+ if (val == null)
285
+ continue;
286
+ const fkR = await db.getRowsByFk(item.table, ref.from, val);
287
+ if (fkR.success && fkR.data) {
288
+ const rows = fkR.data.rows.map(common_1.normalizeRow);
289
+ const columns = rows.length > 0 ? Object.keys(rows[0]) : [];
290
+ results.push({
291
+ table: item.table,
292
+ from: ref.from,
293
+ to: targetCol,
294
+ value: val,
295
+ columns,
296
+ rows,
297
+ count: fkR.data.total,
298
+ total: fkR.data.total,
299
+ });
300
+ }
301
+ }
302
+ }
303
+ (0, helpers_1.ok)(res, { references: results });
304
+ }));
305
+ router.post('/api/tables/:table/rows/bulk-delete', (0, helpers_1.wrap)(async (req, res) => {
306
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
307
+ if (!info)
308
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
309
+ const ids = (req.body?.ids ?? []);
310
+ if (!Array.isArray(ids) || !ids.length)
311
+ return (0, helpers_1.fail)(res, 'No row IDs provided.');
312
+ if (ids.length > helpers_1.MAX_BULK_ROWS)
313
+ return (0, helpers_1.fail)(res, `Cannot delete more than ${helpers_1.MAX_BULK_ROWS} rows at once.`);
314
+ const wheres = (0, helpers_1.resolvePkRows)(info, ids);
315
+ if (!wheres)
316
+ return (0, helpers_1.fail)(res, 'Invalid or unresolvable row IDs.');
317
+ const r = await db.deleteRows(req.params.table, wheres);
318
+ if (!r.success)
319
+ return (0, helpers_1.fail)(res, r.error ?? 'Failed to delete rows.');
320
+ (0, helpers_1.ok)(res, { message: `${r.data?.deleted ?? 0} row(s) deleted.`, deleted: r.data?.deleted });
321
+ }));
322
+ router.post('/api/tables/:table/rows/bulk-impact', (0, helpers_1.wrap)(async (req, res) => {
323
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
324
+ if (!info)
325
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
326
+ const ids = (req.body?.ids ?? []);
327
+ if (!Array.isArray(ids) || !ids.length)
328
+ return (0, helpers_1.ok)(res, { references: [], total: 0 });
329
+ const wheres = (0, helpers_1.resolvePkRows)(info, ids);
330
+ if (!wheres)
331
+ return (0, helpers_1.ok)(res, { references: [], total: 0 });
332
+ const impact = await (0, helpers_1.computeBulkImpact)(db, info, wheres);
333
+ (0, helpers_1.ok)(res, impact);
334
+ }));
335
+ router.post('/api/tables/:table/rows/bulk-export', (0, helpers_1.wrap)(async (req, res) => {
336
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
337
+ if (!info)
338
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
339
+ const ids = (req.body?.ids ?? []);
340
+ if (!Array.isArray(ids) || !ids.length)
341
+ return (0, helpers_1.fail)(res, 'No row IDs provided.');
342
+ if (ids.length > helpers_1.MAX_BULK_ROWS)
343
+ return (0, helpers_1.fail)(res, `Cannot export more than ${helpers_1.MAX_BULK_ROWS} rows at once.`);
344
+ const wheres = (0, helpers_1.resolvePkRows)(info, ids);
345
+ if (!wheres)
346
+ return (0, helpers_1.fail)(res, 'Invalid or unresolvable row IDs.');
347
+ const format = String(req.body?.format ?? 'csv').toLowerCase();
348
+ const rowsR = await db.getRowsByPks(req.params.table, wheres);
349
+ if (!rowsR.success)
350
+ return (0, helpers_1.fail)(res, rowsR.error ?? 'Failed to load rows.');
351
+ const rows = (rowsR.data ?? []).map(common_1.normalizeRow);
352
+ const columns = info.columns.map((c) => c.name);
353
+ if (format === 'json') {
354
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
355
+ res.setHeader('Content-Disposition', `attachment; filename="${req.params.table}_selected.json"`);
356
+ return res.send((0, csv_1.toJson)(rows));
357
+ }
358
+ res.setHeader('Content-Type', 'text/csv; charset=utf-8');
359
+ res.setHeader('Content-Disposition', `attachment; filename="${req.params.table}_selected.csv"`);
360
+ res.send((0, csv_1.toCsv)(rows, columns));
361
+ }));
362
+ }