admindb 2.1.0 → 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 (191) hide show
  1. package/README.md +41 -12
  2. package/dist/app.d.ts +1 -69
  3. package/dist/app.js +3 -30
  4. package/dist/auth/config.d.ts +1 -19
  5. package/dist/auth/config.js +0 -26
  6. package/dist/auth/crypto.d.ts +1 -27
  7. package/dist/auth/crypto.js +1 -26
  8. package/dist/auth/index.d.ts +1 -5
  9. package/dist/auth/index.js +0 -1
  10. package/dist/auth/middleware.d.ts +1 -14
  11. package/dist/auth/middleware.js +0 -15
  12. package/dist/auth/routes.d.ts +1 -7
  13. package/dist/auth/routes.js +0 -8
  14. package/dist/auth/types.d.ts +1 -35
  15. package/dist/auth/types.js +1 -5
  16. package/dist/cli/args.d.ts +1 -45
  17. package/dist/cli/args.js +3 -11
  18. package/dist/cli/config.d.ts +1 -56
  19. package/dist/cli/config.js +0 -5
  20. package/dist/cli/index.d.ts +1 -4
  21. package/dist/cli/index.js +0 -1
  22. package/dist/cli/runner.d.ts +1 -1
  23. package/dist/cli/runner.js +6 -17
  24. package/dist/cli.d.ts +1 -2
  25. package/dist/cli.js +0 -1
  26. package/dist/data/chain.d.ts +1 -0
  27. package/dist/data/chain.js +707 -0
  28. package/dist/data/datasets.d.ts +1 -17
  29. package/dist/data/datasets.js +0 -4
  30. package/dist/data/detector.d.ts +1 -30
  31. package/dist/data/detector.js +53 -14
  32. package/dist/data/engine.d.ts +1 -10
  33. package/dist/data/engine.js +82 -48
  34. package/dist/data/index.d.ts +1 -5
  35. package/dist/data/index.js +1 -1
  36. package/dist/data/strategies.d.ts +1 -30
  37. package/dist/data/strategies.js +4 -8
  38. package/dist/data/types.d.ts +1 -73
  39. package/dist/data/types.js +0 -3
  40. package/dist/db/database.d.ts +1 -119
  41. package/dist/db/database.js +20 -14
  42. package/dist/db/export.d.ts +1 -12
  43. package/dist/db/export.js +54 -6
  44. package/dist/db/filters.d.ts +1 -22
  45. package/dist/db/filters.js +0 -16
  46. package/dist/db/index.d.ts +1 -8
  47. package/dist/db/index.js +0 -1
  48. package/dist/db/introspection.d.ts +1 -6
  49. package/dist/db/introspection.js +0 -5
  50. package/dist/db/manager.d.ts +1 -81
  51. package/dist/db/manager.js +13 -59
  52. package/dist/db/migrations.d.ts +1 -5
  53. package/dist/db/migrations.js +2 -5
  54. package/dist/db/postgres.d.ts +1 -85
  55. package/dist/db/postgres.js +21 -31
  56. package/dist/db/types.d.ts +1 -206
  57. package/dist/db/types.js +0 -1
  58. package/dist/index.d.ts +1 -23
  59. package/dist/index.js +0 -14
  60. package/dist/public/css/app.css +1 -1
  61. package/dist/public/js/api.js +1 -47
  62. package/dist/public/js/app.js +1 -262
  63. package/dist/public/js/browse.js +1 -1371
  64. package/dist/public/js/common.js +1 -0
  65. package/dist/public/js/databases.js +1 -235
  66. package/dist/public/js/designer.js +1 -258
  67. package/dist/public/js/forms.js +1 -826
  68. package/dist/public/js/home.js +1 -0
  69. package/dist/public/js/inspector.js +1 -638
  70. package/dist/public/js/query.js +1 -322
  71. package/dist/public/js/schema.js +1 -447
  72. package/dist/public/js/seed.js +1 -1090
  73. package/dist/routes/api/erd.d.ts +1 -0
  74. package/dist/routes/api/erd.js +50 -0
  75. package/dist/routes/api/helpers.d.ts +1 -42
  76. package/dist/routes/api/helpers.js +33 -3
  77. package/dist/routes/api/import-export.d.ts +1 -3
  78. package/dist/routes/api/import-export.js +0 -2
  79. package/dist/routes/api/index.d.ts +1 -4
  80. package/dist/routes/api/index.js +15 -4
  81. package/dist/routes/api/query.d.ts +1 -3
  82. package/dist/routes/api/query.js +13 -11
  83. package/dist/routes/api/rows.d.ts +1 -3
  84. package/dist/routes/api/rows.js +0 -11
  85. package/dist/routes/api/seed.d.ts +1 -3
  86. package/dist/routes/api/seed.js +79 -8
  87. package/dist/routes/api/tables.d.ts +1 -3
  88. package/dist/routes/api/tables.js +77 -6
  89. package/dist/routes/databases.d.ts +1 -26
  90. package/dist/routes/databases.js +1 -3
  91. package/dist/routes/index.d.ts +1 -3
  92. package/dist/routes/index.js +0 -1
  93. package/dist/routes/pages.d.ts +1 -9
  94. package/dist/routes/pages.js +18 -11
  95. package/dist/serverless.d.ts +1 -40
  96. package/dist/serverless.js +3 -39
  97. package/dist/sql/classifier.d.ts +1 -19
  98. package/dist/sql/classifier.js +0 -13
  99. package/dist/sql/error-analyzer.d.ts +1 -0
  100. package/dist/sql/error-analyzer.js +550 -0
  101. package/dist/sql/generator.d.ts +1 -81
  102. package/dist/sql/generator.js +0 -29
  103. package/dist/sql/index.d.ts +1 -2
  104. package/dist/sql/index.js +0 -1
  105. package/dist/types/api.d.ts +1 -412
  106. package/dist/types/api.js +0 -7
  107. package/dist/utils/colors.d.ts +1 -40
  108. package/dist/utils/colors.js +0 -10
  109. package/dist/utils/common.d.ts +1 -50
  110. package/dist/utils/common.js +19 -23
  111. package/dist/utils/csv.d.ts +1 -15
  112. package/dist/utils/csv.js +0 -13
  113. package/dist/utils/datatype.d.ts +1 -49
  114. package/dist/utils/datatype.js +0 -41
  115. package/dist/utils/icons.d.ts +1 -11
  116. package/dist/utils/icons.js +25 -16
  117. package/dist/utils/index.d.ts +1 -4
  118. package/dist/utils/index.js +0 -1
  119. package/dist/utils/logger.d.ts +1 -10
  120. package/dist/utils/logger.js +0 -4
  121. package/dist/views/layouts/main.hbs +14 -5
  122. package/dist/views/pages/databases.hbs +0 -18
  123. package/dist/views/pages/designer.hbs +0 -10
  124. package/dist/views/pages/erd.hbs +1241 -0
  125. package/dist/views/pages/error.hbs +0 -2
  126. package/dist/views/pages/form.hbs +0 -5
  127. package/dist/views/pages/home.hbs +98 -10
  128. package/dist/views/pages/info.hbs +107 -0
  129. package/dist/views/pages/login.hbs +0 -13
  130. package/dist/views/pages/query.hbs +54 -21
  131. package/dist/views/pages/schema.hbs +0 -20
  132. package/dist/views/pages/seed.hbs +435 -202
  133. package/dist/views/pages/table.hbs +0 -14
  134. package/dist/views/partials/icon.hbs +0 -1
  135. package/dist/views/partials/navbar.hbs +0 -6
  136. package/dist/views/partials/sidebar.hbs +12 -8
  137. package/docs/API.md +6 -1
  138. package/docs/EXAMPLES.md +3 -0
  139. package/package.json +7 -5
  140. package/dist/app.js.map +0 -1
  141. package/dist/auth/config.js.map +0 -1
  142. package/dist/auth/crypto.js.map +0 -1
  143. package/dist/auth/index.js.map +0 -1
  144. package/dist/auth/middleware.js.map +0 -1
  145. package/dist/auth/routes.js.map +0 -1
  146. package/dist/auth/types.js.map +0 -1
  147. package/dist/cli/args.js.map +0 -1
  148. package/dist/cli/config.js.map +0 -1
  149. package/dist/cli/index.js.map +0 -1
  150. package/dist/cli/runner.js.map +0 -1
  151. package/dist/cli.js.map +0 -1
  152. package/dist/data/datasets.js.map +0 -1
  153. package/dist/data/detector.js.map +0 -1
  154. package/dist/data/engine.js.map +0 -1
  155. package/dist/data/index.js.map +0 -1
  156. package/dist/data/strategies.js.map +0 -1
  157. package/dist/data/types.js.map +0 -1
  158. package/dist/db/database.js.map +0 -1
  159. package/dist/db/export.js.map +0 -1
  160. package/dist/db/filters.js.map +0 -1
  161. package/dist/db/index.js.map +0 -1
  162. package/dist/db/introspection.js.map +0 -1
  163. package/dist/db/manager.js.map +0 -1
  164. package/dist/db/migrations.js.map +0 -1
  165. package/dist/db/postgres.js.map +0 -1
  166. package/dist/db/types.js.map +0 -1
  167. package/dist/index.js.map +0 -1
  168. package/dist/public/css/input.css +0 -302
  169. package/dist/public/js/icons.js +0 -51
  170. package/dist/routes/api/helpers.js.map +0 -1
  171. package/dist/routes/api/import-export.js.map +0 -1
  172. package/dist/routes/api/index.js.map +0 -1
  173. package/dist/routes/api/query.js.map +0 -1
  174. package/dist/routes/api/rows.js.map +0 -1
  175. package/dist/routes/api/seed.js.map +0 -1
  176. package/dist/routes/api/tables.js.map +0 -1
  177. package/dist/routes/databases.js.map +0 -1
  178. package/dist/routes/index.js.map +0 -1
  179. package/dist/routes/pages.js.map +0 -1
  180. package/dist/serverless.js.map +0 -1
  181. package/dist/sql/classifier.js.map +0 -1
  182. package/dist/sql/generator.js.map +0 -1
  183. package/dist/sql/index.js.map +0 -1
  184. package/dist/types/api.js.map +0 -1
  185. package/dist/utils/colors.js.map +0 -1
  186. package/dist/utils/common.js.map +0 -1
  187. package/dist/utils/csv.js.map +0 -1
  188. package/dist/utils/datatype.js.map +0 -1
  189. package/dist/utils/icons.js.map +0 -1
  190. package/dist/utils/index.js.map +0 -1
  191. package/dist/utils/logger.js.map +0 -1
@@ -0,0 +1 @@
1
+ import{type ApiContext} from './helpers';import type {Router} from 'express';export declare function registerErdRoutes(router:Router,ctx:ApiContext):void;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerErdRoutes = registerErdRoutes;
4
+ const helpers_1 = require("./helpers");
5
+ function registerErdRoutes(router, ctx) {
6
+ const { db } = ctx;
7
+ router.get('/api/erd', async (_req, res) => {
8
+ try {
9
+ const tablesResult = await db.listTables();
10
+ if (!tablesResult.success || !tablesResult.data) {
11
+ return (0, helpers_1.fail)(res, tablesResult.error ?? 'Failed to list tables.', 500);
12
+ }
13
+ const tableNames = tablesResult.data.map((t) => t.name);
14
+ const tableDetails = await Promise.all(tableNames.map(async (name) => {
15
+ const infoResult = await db.getTableInfo(name);
16
+ if (!infoResult.success || !infoResult.data) {
17
+ return {
18
+ name,
19
+ columns: [],
20
+ foreignKeys: [],
21
+ primaryKey: [],
22
+ };
23
+ }
24
+ const { columns, foreignKeys, primaryKey } = infoResult.data;
25
+ return {
26
+ name,
27
+ columns: columns.map((c) => ({
28
+ name: c.name,
29
+ type: c.type,
30
+ pk: c.pk,
31
+ notnull: c.notnull,
32
+ dflt_value: c.dflt_value,
33
+ })),
34
+ foreignKeys: foreignKeys.map((fk) => ({
35
+ from: fk.from,
36
+ table: fk.table,
37
+ to: fk.to ?? '',
38
+ on_delete: fk.on_delete,
39
+ on_update: fk.on_update,
40
+ })),
41
+ primaryKey,
42
+ };
43
+ }));
44
+ (0, helpers_1.ok)(res, { tables: tableDetails });
45
+ }
46
+ catch (err) {
47
+ (0, helpers_1.fail)(res, err.message ?? 'Unknown error.', 500);
48
+ }
49
+ });
50
+ }
@@ -1,42 +1 @@
1
- import type { Request, Response } from 'express';
2
- import type { IDatabase, TableInfoData, WhereClause } from '../../db/index';
3
- import type { Logger } from '../../utils/logger';
4
- import { type ColumnPlan } from '../../data/index';
5
- export interface ApiContext {
6
- db: IDatabase;
7
- logger: Logger;
8
- }
9
- export declare function ok(res: Response, data: unknown, status?: number): Response;
10
- export declare function fail(res: Response, error: string, status?: number): Response;
11
- export declare function wrap(fn: (req: Request, res: Response) => unknown): (req: Request, res: Response) => Promise<void>;
12
- export declare function requireTable(db: IDatabase, table: string): Promise<TableInfoData | null>;
13
- export declare function buildFields(info: TableInfoData, values: Record<string, unknown>, opts?: {
14
- excludePk?: boolean;
15
- }): {
16
- column: string;
17
- value: unknown;
18
- }[];
19
- export declare function buildUpdateFields(info: TableInfoData, values: Record<string, unknown>, nulls: string[]): {
20
- column: string;
21
- value: unknown;
22
- }[];
23
- export declare function pkWhere(info: TableInfoData, encodedId: string): {
24
- column: string;
25
- value: unknown;
26
- }[] | null;
27
- export declare const MAX_BULK_ROWS = 1000;
28
- export declare function parseSeedRequest(body: unknown): {
29
- count: number;
30
- plan: Record<string, ColumnPlan>;
31
- truncate: boolean;
32
- };
33
- export declare function resolvePkRows(info: TableInfoData, ids: unknown): WhereClause[][] | null;
34
- export declare function computeBulkImpact(db: IDatabase, info: TableInfoData, wheres: WhereClause[][]): Promise<{
35
- references: {
36
- table: string;
37
- from: string;
38
- to: string;
39
- count: number;
40
- }[];
41
- total: number;
42
- }>;
1
+ import type {Request,Response} from 'express';import type {IDatabase,TableInfoData,WhereClause} from '../../db/index';import type {Logger} from '../../utils/logger';import{type ColumnPlan,type ErChainScope} from '../../data/index';export interface ApiContext{db:IDatabase;logger:Logger;}export declare function ok(res:Response,data:unknown,status?:number):Response;export declare function fail(res:Response,error:string,status?:number,details?:Record<string,unknown>|null):Response;export declare function wrap(fn:(req:Request,res:Response)=>unknown):(req:Request,res:Response)=>Promise<void>;export declare function requireTable(db:IDatabase,table:string):Promise<TableInfoData|null>;export declare function buildFields(info:TableInfoData,values:Record<string,unknown>,opts?:{excludePk?:boolean;}):{column:string;value:unknown;}[];export declare function buildUpdateFields(info:TableInfoData,values:Record<string,unknown>,nulls:string[]):{column:string;value:unknown;}[];export declare function pkWhere(info:TableInfoData,encodedId:string):{column:string;value:unknown;}[]|null;export declare const MAX_BULK_ROWS=1000;export declare function parseSeedRequest(body:unknown):{count:number;plan:Record<string,ColumnPlan>;truncate:boolean;};export declare function parseChainSeedRequest(body:unknown):{scope:ErChainScope;counts:Record<string,number>;plans:Record<string,Record<string,ColumnPlan>>;truncate:boolean;};export declare function resolvePkRows(info:TableInfoData,ids:unknown):WhereClause[][]|null;export declare function computeBulkImpact(db:IDatabase,info:TableInfoData,wheres:WhereClause[][]):Promise<{references:{table:string; from :string;to:string;count:number;}[];total:number;}>;
@@ -9,6 +9,7 @@ exports.buildFields = buildFields;
9
9
  exports.buildUpdateFields = buildUpdateFields;
10
10
  exports.pkWhere = pkWhere;
11
11
  exports.parseSeedRequest = parseSeedRequest;
12
+ exports.parseChainSeedRequest = parseChainSeedRequest;
12
13
  exports.resolvePkRows = resolvePkRows;
13
14
  exports.computeBulkImpact = computeBulkImpact;
14
15
  const common_1 = require("../../utils/common");
@@ -17,8 +18,8 @@ const generator_1 = require("../../sql/generator");
17
18
  function ok(res, data, status = 200) {
18
19
  return res.status(status).json({ success: true, data });
19
20
  }
20
- function fail(res, error, status = 400) {
21
- return res.status(status).json({ success: false, error });
21
+ function fail(res, error, status = 400, details) {
22
+ return res.status(status).json({ success: false, error, ...(details ? { details } : {}) });
22
23
  }
23
24
  function wrap(fn) {
24
25
  return async (req, res) => {
@@ -89,6 +90,36 @@ function parseSeedRequest(body) {
89
90
  }
90
91
  return { count, plan, truncate };
91
92
  }
93
+ function parseChainSeedRequest(body) {
94
+ const b = (body ?? {});
95
+ const scope = b.scope === 'ancestors' || b.scope === 'descendants' || b.scope === 'all' || b.scope === 'single'
96
+ ? b.scope
97
+ : 'chain';
98
+ const truncate = Boolean(b.truncate);
99
+ const counts = {};
100
+ if (b.counts && typeof b.counts === 'object') {
101
+ for (const [table, rawN] of Object.entries(b.counts)) {
102
+ const n = Number.parseInt(String(rawN), 10);
103
+ if (Number.isFinite(n) && n > 0)
104
+ counts[table] = Math.min(2000, n);
105
+ }
106
+ }
107
+ const plans = {};
108
+ if (b.plans && typeof b.plans === 'object') {
109
+ for (const [table, tablePlans] of Object.entries(b.plans)) {
110
+ if (tablePlans && typeof tablePlans === 'object') {
111
+ const pMap = {};
112
+ for (const [col, v] of Object.entries(tablePlans)) {
113
+ const p = (0, index_1.sanitizeColumnPlan)(v);
114
+ if (p)
115
+ pMap[col] = p;
116
+ }
117
+ plans[table] = pMap;
118
+ }
119
+ }
120
+ }
121
+ return { scope, counts, plans, truncate };
122
+ }
92
123
  function resolvePkRows(info, ids) {
93
124
  const list = Array.isArray(ids) ? ids : ids == null ? [] : [ids];
94
125
  const out = [];
@@ -130,4 +161,3 @@ async function computeBulkImpact(db, info, wheres) {
130
161
  const total = results.reduce((acc, r) => acc + r.count, 0);
131
162
  return { references: results, total };
132
163
  }
133
- //# sourceMappingURL=helpers.js.map
@@ -1,3 +1 @@
1
- import type { Router } from 'express';
2
- import { type ApiContext } from './helpers';
3
- export declare function registerImportExportRoutes(router: Router, ctx: ApiContext): void;
1
+ import type {Router} from 'express';import{type ApiContext} from './helpers';export declare function registerImportExportRoutes(router:Router,ctx:ApiContext):void;
@@ -6,7 +6,6 @@ const csv_1 = require("../../utils/csv");
6
6
  const helpers_1 = require("./helpers");
7
7
  function registerImportExportRoutes(router, ctx) {
8
8
  const { db } = ctx;
9
- // ---- Export / Import ---------------------------------------------------
10
9
  router.get('/api/tables/:table/export', (0, helpers_1.wrap)(async (req, res) => {
11
10
  const info = await (0, helpers_1.requireTable)(db, req.params.table);
12
11
  if (!info)
@@ -82,4 +81,3 @@ function registerImportExportRoutes(router, ctx) {
82
81
  res.send((0, csv_1.toCsv)(rows, columns));
83
82
  }));
84
83
  }
85
- //# sourceMappingURL=import-export.js.map
@@ -1,4 +1 @@
1
- import type { Router } from 'express';
2
- import { type ApiContext } from './helpers';
3
- export { type ApiContext } from './helpers';
4
- export declare function registerApi(router: Router, ctx: ApiContext): void;
1
+ import type {Router} from 'express';import{type ApiContext} from './helpers';export{type ApiContext} from './helpers';export declare function registerApi(router:Router,ctx:ApiContext):void;
@@ -6,11 +6,10 @@ const rows_1 = require("./rows");
6
6
  const import_export_1 = require("./import-export");
7
7
  const seed_1 = require("./seed");
8
8
  const query_1 = require("./query");
9
+ const erd_1 = require("./erd");
10
+ const export_1 = require("../../db/export");
9
11
  function registerApi(router, ctx) {
10
12
  const { db } = ctx;
11
- // In read-only mode every mutating request is rejected (GET, the generate-only
12
- // preview endpoints and the query runner stay available; the query runner
13
- // rejects write statements itself).
14
13
  router.use((req, res, next) => {
15
14
  if (!db.isReadOnly || req.method === 'GET')
16
15
  return next();
@@ -33,5 +32,17 @@ function registerApi(router, ctx) {
33
32
  (0, import_export_1.registerImportExportRoutes)(router, ctx);
34
33
  (0, seed_1.registerSeedRoutes)(router, ctx);
35
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
+ });
36
48
  }
37
- //# sourceMappingURL=index.js.map
@@ -1,3 +1 @@
1
- import type { Router } from 'express';
2
- import { type ApiContext } from './helpers';
3
- export declare function registerQueryRoutes(router: Router, ctx: ApiContext): void;
1
+ import type {Router} from 'express';import{type ApiContext} from './helpers';export declare function registerQueryRoutes(router:Router,ctx:ApiContext):void;
@@ -2,24 +2,25 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.registerQueryRoutes = registerQueryRoutes;
4
4
  const classifier_1 = require("../../sql/classifier");
5
+ const error_analyzer_1 = require("../../sql/error-analyzer");
5
6
  const common_1 = require("../../utils/common");
6
7
  const helpers_1 = require("./helpers");
7
8
  function registerQueryRoutes(router, ctx) {
8
9
  const { db } = ctx;
9
- // ---- Query runner ------------------------------------------------------
10
10
  router.post('/api/query', (0, helpers_1.wrap)(async (req, res) => {
11
11
  const rawSql = String(req.body?.sql ?? '').trim();
12
12
  if (!rawSql)
13
13
  return (0, helpers_1.fail)(res, 'SQL query is required.');
14
14
  const { kind } = (0, classifier_1.classifySql)(rawSql);
15
- // Multi-statement script detection
16
15
  if (db.hasMultipleStatements(rawSql)) {
17
16
  if (db.isReadOnly) {
18
17
  return (0, helpers_1.fail)(res, 'Database is open in read-only mode — write statements are disabled.', 403);
19
18
  }
20
19
  const r = await db.execResult(rawSql);
21
- if (!r.success)
22
- return (0, helpers_1.fail)(res, r.error ?? 'Query failed.', 400);
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
+ }
23
24
  return (0, helpers_1.ok)(res, {
24
25
  kind: 'script',
25
26
  message: 'Script executed successfully.',
@@ -27,19 +28,22 @@ function registerQueryRoutes(router, ctx) {
27
28
  }
28
29
  if (kind === 'select' || kind === 'read' || kind === 'count') {
29
30
  const r = await db.all(rawSql);
30
- if (!r.success)
31
- return (0, helpers_1.fail)(res, r.error ?? 'Query failed.', 400);
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
+ }
32
35
  const rows = (r.data ?? []).map(common_1.normalizeRow);
33
36
  const columns = rows.length ? Object.keys(rows[0]) : [];
34
37
  return (0, helpers_1.ok)(res, { kind: 'select', columns, rows });
35
38
  }
36
- // write statement
37
39
  if (db.isReadOnly) {
38
40
  return (0, helpers_1.fail)(res, 'Database is open in read-only mode — write statements are disabled.', 403);
39
41
  }
40
42
  const r = await db.runWrite(rawSql);
41
- if (!r.success)
42
- return (0, helpers_1.fail)(res, r.error ?? 'Query failed.', 400);
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
+ }
43
47
  const changes = r.data?.changes;
44
48
  (0, helpers_1.ok)(res, {
45
49
  kind: 'write',
@@ -47,7 +51,6 @@ function registerQueryRoutes(router, ctx) {
47
51
  message: changes != null ? `${changes} row(s) affected.` : 'Statement executed successfully.',
48
52
  });
49
53
  }));
50
- // ---- Saved queries -----------------------------------------------------
51
54
  router.get('/api/queries', (0, helpers_1.wrap)(async (_req, res) => {
52
55
  const r = await db.listSavedQueries();
53
56
  if (!r.success)
@@ -73,4 +76,3 @@ function registerQueryRoutes(router, ctx) {
73
76
  (0, helpers_1.ok)(res, { message: 'Query deleted.' });
74
77
  }));
75
78
  }
76
- //# sourceMappingURL=query.js.map
@@ -1,3 +1 @@
1
- import type { Router } from 'express';
2
- import { type ApiContext } from './helpers';
3
- export declare function registerRowRoutes(router: Router, ctx: ApiContext): void;
1
+ import type {Router} from 'express';import{type ApiContext} from './helpers';export declare function registerRowRoutes(router:Router,ctx:ApiContext):void;
@@ -8,7 +8,6 @@ const generator_1 = require("../../sql/generator");
8
8
  const helpers_1 = require("./helpers");
9
9
  function registerRowRoutes(router, ctx) {
10
10
  const { db } = ctx;
11
- // ---- Rows --------------------------------------------------------------
12
11
  router.get('/api/tables/:table/rows', (0, helpers_1.wrap)(async (req, res) => {
13
12
  const page = Number.parseInt(String(req.query.page ?? '1'), 10);
14
13
  const limit = Number.parseInt(String(req.query.limit ?? '50'), 10);
@@ -36,7 +35,6 @@ function registerRowRoutes(router, ctx) {
36
35
  return (0, helpers_1.fail)(res, r.error ?? 'Failed to count rows.');
37
36
  (0, helpers_1.ok)(res, { count: r.data });
38
37
  }));
39
- // Fetch single row
40
38
  router.get('/api/tables/:table/row/:id', (0, helpers_1.wrap)(async (req, res) => {
41
39
  const info = await (0, helpers_1.requireTable)(db, req.params.table);
42
40
  if (!info)
@@ -51,7 +49,6 @@ function registerRowRoutes(router, ctx) {
51
49
  return (0, helpers_1.fail)(res, 'Row not found.', 404);
52
50
  (0, helpers_1.ok)(res, (0, common_1.normalizeRow)(r.data));
53
51
  }));
54
- // Serve raw BLOB binary data with auto-detected Content-Type and download support
55
52
  router.get('/api/tables/:table/row/:id/blob/:column', (0, helpers_1.wrap)(async (req, res) => {
56
53
  const info = await (0, helpers_1.requireTable)(db, req.params.table);
57
54
  if (!info)
@@ -89,7 +86,6 @@ function registerRowRoutes(router, ctx) {
89
86
  }
90
87
  res.end(buf);
91
88
  }));
92
- // Return BLOB metadata and formatted hex dump
93
89
  router.get('/api/tables/:table/row/:id/blob/:column/meta', (0, helpers_1.wrap)(async (req, res) => {
94
90
  const info = await (0, helpers_1.requireTable)(db, req.params.table);
95
91
  if (!info)
@@ -110,7 +106,6 @@ function registerRowRoutes(router, ctx) {
110
106
  const meta = (0, datatype_1.analyzeBlob)(rawVal);
111
107
  (0, helpers_1.ok)(res, { isNull: false, ...meta });
112
108
  }));
113
- // Update BLOB binary value directly (accepts Base64 or Hex payload)
114
109
  router.put('/api/tables/:table/row/:id/blob/:column', (0, helpers_1.wrap)(async (req, res) => {
115
110
  const info = await (0, helpers_1.requireTable)(db, req.params.table);
116
111
  if (!info)
@@ -123,7 +118,6 @@ function registerRowRoutes(router, ctx) {
123
118
  if (req.body?.data !== undefined) {
124
119
  const format = req.body.format || 'base64';
125
120
  if (format === 'base64') {
126
- // Strip data:image/...;base64, prefix if present
127
121
  const base64Str = String(req.body.data).replace(/^data:[^;]+;base64,/, '');
128
122
  buf = Buffer.from(base64Str, 'base64');
129
123
  }
@@ -143,7 +137,6 @@ function registerRowRoutes(router, ctx) {
143
137
  return (0, helpers_1.fail)(res, updateRes.error ?? 'Failed to update BLOB.');
144
138
  (0, helpers_1.ok)(res, { message: `Updated BLOB in column "${colName}".`, size: buf.length });
145
139
  }));
146
- // Preview generated INSERT SQL without executing
147
140
  router.post('/api/tables/:table/rows/generate', (0, helpers_1.wrap)(async (req, res) => {
148
141
  const info = await (0, helpers_1.requireTable)(db, req.params.table);
149
142
  if (!info)
@@ -154,7 +147,6 @@ function registerRowRoutes(router, ctx) {
154
147
  const sql = (0, generator_1.generateInsert)(req.params.table, fields);
155
148
  (0, helpers_1.ok)(res, { sql });
156
149
  }));
157
- // Preview generated UPDATE SQL without executing
158
150
  const generateUpdateHandler = (0, helpers_1.wrap)(async (req, res) => {
159
151
  const info = await (0, helpers_1.requireTable)(db, req.params.table);
160
152
  if (!info)
@@ -270,7 +262,6 @@ function registerRowRoutes(router, ctx) {
270
262
  });
271
263
  router.delete('/api/tables/:table/row/:id', deleteSingleRowHandler);
272
264
  router.delete('/api/tables/:table/rows', deleteSingleRowHandler);
273
- // ---- Row references ----------------------------------------------------
274
265
  router.get('/api/tables/:table/rows/:id/references', (0, helpers_1.wrap)(async (req, res) => {
275
266
  const info = await (0, helpers_1.requireTable)(db, req.params.table);
276
267
  if (!info)
@@ -311,7 +302,6 @@ function registerRowRoutes(router, ctx) {
311
302
  }
312
303
  (0, helpers_1.ok)(res, { references: results });
313
304
  }));
314
- // ---- Bulk row operations -----------------------------------------------
315
305
  router.post('/api/tables/:table/rows/bulk-delete', (0, helpers_1.wrap)(async (req, res) => {
316
306
  const info = await (0, helpers_1.requireTable)(db, req.params.table);
317
307
  if (!info)
@@ -370,4 +360,3 @@ function registerRowRoutes(router, ctx) {
370
360
  res.send((0, csv_1.toCsv)(rows, columns));
371
361
  }));
372
362
  }
373
- //# sourceMappingURL=rows.js.map
@@ -1,3 +1 @@
1
- import type { Router } from 'express';
2
- import { type ApiContext } from './helpers';
3
- export declare function registerSeedRoutes(router: Router, ctx: ApiContext): void;
1
+ import type {Router} from 'express';import{type ApiContext} from './helpers';export declare function registerSeedRoutes(router:Router,ctx:ApiContext):void;
@@ -6,22 +6,18 @@ const index_1 = require("../../data/index");
6
6
  const helpers_1 = require("./helpers");
7
7
  function registerSeedRoutes(router, ctx) {
8
8
  const { db } = ctx;
9
- // ---- Data generator / seeder ------------------------------------------
10
- // Per-column generator config (strategies + detected defaults) for the "Seed data" page
11
9
  router.get('/api/tables/:table/seed/config', (0, helpers_1.wrap)(async (req, res) => {
12
10
  const info = await (0, helpers_1.requireTable)(db, req.params.table);
13
11
  if (!info)
14
12
  return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
15
13
  (0, helpers_1.ok)(res, { table: req.params.table, columns: (0, index_1.buildColumnConfigs)(info), maxRows: index_1.MAX_SEED_ROWS });
16
14
  }));
17
- // Legacy alias for seed config
18
15
  router.get('/api/tables/:table/seed/plan', (0, helpers_1.wrap)(async (req, res) => {
19
16
  const info = await (0, helpers_1.requireTable)(db, req.params.table);
20
17
  if (!info)
21
18
  return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
22
19
  (0, helpers_1.ok)(res, { table: req.params.table, columns: (0, index_1.buildColumnConfigs)(info), maxRows: index_1.MAX_SEED_ROWS });
23
20
  }));
24
- // Preview: generate the rows and return the INSERT statements without executing them
25
21
  router.post('/api/tables/:table/seed/generate', (0, helpers_1.wrap)(async (req, res) => {
26
22
  const info = await (0, helpers_1.requireTable)(db, req.params.table);
27
23
  if (!info)
@@ -37,7 +33,6 @@ function registerSeedRoutes(router, ctx) {
37
33
  warnings: gen.warnings,
38
34
  });
39
35
  }));
40
- // Interactive grid preview
41
36
  router.post('/api/tables/:table/seed/preview', (0, helpers_1.wrap)(async (req, res) => {
42
37
  const info = await (0, helpers_1.requireTable)(db, req.params.table);
43
38
  if (!info)
@@ -51,7 +46,6 @@ function registerSeedRoutes(router, ctx) {
51
46
  warnings: gen.warnings,
52
47
  });
53
48
  }));
54
- // Insert the generated rows inside a single transaction
55
49
  router.post('/api/tables/:table/seed', (0, helpers_1.wrap)(async (req, res) => {
56
50
  const info = await (0, helpers_1.requireTable)(db, req.params.table);
57
51
  if (!info)
@@ -65,7 +59,7 @@ function registerSeedRoutes(router, ctx) {
65
59
  return (0, helpers_1.fail)(res, clearRes.error ?? 'Failed to clear table before seeding.', 400);
66
60
  }
67
61
  }
68
- const gen = await (0, index_1.generateRows)(db, info, configs, count, plan);
62
+ const gen = await (0, index_1.generateRows)(db, info, configs, count, plan, truncate);
69
63
  const result = await db.insertRows(req.params.table, gen.rows);
70
64
  if (!result.success)
71
65
  return (0, helpers_1.fail)(res, result.error ?? 'Seed failed.', 400);
@@ -78,5 +72,82 @@ function registerSeedRoutes(router, ctx) {
78
72
  elapsedMs,
79
73
  }, 201);
80
74
  }));
75
+ router.get('/api/tables/:table/seed/chain', (0, helpers_1.wrap)(async (req, res) => {
76
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
77
+ if (!info)
78
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
79
+ const rawScope = String(req.query.scope ?? 'chain');
80
+ const scope = rawScope === 'ancestors' || rawScope === 'descendants' || rawScope === 'all' || rawScope === 'single'
81
+ ? rawScope
82
+ : 'chain';
83
+ const config = await (0, index_1.getErChainConfig)(db, req.params.table, scope);
84
+ (0, helpers_1.ok)(res, config);
85
+ }));
86
+ router.post('/api/tables/:table/seed/chain/preview', (0, helpers_1.wrap)(async (req, res) => {
87
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
88
+ if (!info)
89
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
90
+ const { scope, counts, plans } = (0, helpers_1.parseChainSeedRequest)(req.body);
91
+ const config = await (0, index_1.getErChainConfig)(db, req.params.table, scope);
92
+ const previewCounts = {};
93
+ for (const t of config.tables) {
94
+ previewCounts[t.name] = Math.min(50, counts[t.name] ?? t.suggestedCount ?? 10);
95
+ }
96
+ const gen = await (0, index_1.generateChainRows)(db, config, plans, previewCounts);
97
+ (0, helpers_1.ok)(res, gen);
98
+ }));
99
+ router.post('/api/tables/:table/seed/chain/generate', (0, helpers_1.wrap)(async (req, res) => {
100
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
101
+ if (!info)
102
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
103
+ const { scope, counts, plans } = (0, helpers_1.parseChainSeedRequest)(req.body);
104
+ const config = await (0, index_1.getErChainConfig)(db, req.params.table, scope);
105
+ const gen = await (0, index_1.generateChainRows)(db, config, plans, counts);
106
+ (0, helpers_1.ok)(res, gen);
107
+ }));
108
+ router.post('/api/tables/:table/seed/chain', (0, helpers_1.wrap)(async (req, res) => {
109
+ const info = await (0, helpers_1.requireTable)(db, req.params.table);
110
+ if (!info)
111
+ return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
112
+ const { scope, counts, plans, truncate } = (0, helpers_1.parseChainSeedRequest)(req.body);
113
+ const config = await (0, index_1.getErChainConfig)(db, req.params.table, scope);
114
+ const gen = await (0, index_1.generateChainRows)(db, config, plans, counts, truncate);
115
+ const exec = await (0, index_1.executeChainInsert)(db, gen, truncate);
116
+ (0, helpers_1.ok)(res, {
117
+ message: `${truncate ? 'Cleared tables in reverse order and seeded' : 'Seeded'} ${exec.totalInserted} row(s) across ${Object.keys(exec.inserted).length} table(s) in ${exec.elapsedMs}ms.`,
118
+ inserted: exec.inserted,
119
+ totalInserted: exec.totalInserted,
120
+ elapsedMs: exec.elapsedMs,
121
+ warnings: exec.warnings,
122
+ }, 201);
123
+ }));
124
+ router.get('/api/seed/chain', (0, helpers_1.wrap)(async (req, res) => {
125
+ const rawRoot = String(req.query.table ?? '');
126
+ const rawScope = String(req.query.scope ?? (rawRoot ? 'chain' : 'all'));
127
+ const scope = rawScope === 'ancestors' || rawScope === 'descendants' || rawScope === 'all' || rawScope === 'single'
128
+ ? rawScope
129
+ : 'all';
130
+ const rootTable = rawRoot || (await db.listTables()).data?.[0]?.name || '';
131
+ if (!rootTable)
132
+ return (0, helpers_1.fail)(res, 'No tables found in database.', 404);
133
+ const config = await (0, index_1.getErChainConfig)(db, rootTable, scope);
134
+ (0, helpers_1.ok)(res, config);
135
+ }));
136
+ router.post('/api/seed/chain', (0, helpers_1.wrap)(async (req, res) => {
137
+ const { scope, counts, plans, truncate } = (0, helpers_1.parseChainSeedRequest)(req.body);
138
+ const rawRoot = String(req.body?.rootTable ?? '');
139
+ const rootTable = rawRoot || (await db.listTables()).data?.[0]?.name || '';
140
+ if (!rootTable)
141
+ return (0, helpers_1.fail)(res, 'No tables found in database.', 404);
142
+ const config = await (0, index_1.getErChainConfig)(db, rootTable, scope);
143
+ const gen = await (0, index_1.generateChainRows)(db, config, plans, counts, truncate);
144
+ const exec = await (0, index_1.executeChainInsert)(db, gen, truncate);
145
+ (0, helpers_1.ok)(res, {
146
+ message: `${truncate ? 'Cleared tables in reverse order and seeded' : 'Seeded'} ${exec.totalInserted} row(s) across ${Object.keys(exec.inserted).length} table(s) in ${exec.elapsedMs}ms.`,
147
+ inserted: exec.inserted,
148
+ totalInserted: exec.totalInserted,
149
+ elapsedMs: exec.elapsedMs,
150
+ warnings: exec.warnings,
151
+ }, 201);
152
+ }));
81
153
  }
82
- //# sourceMappingURL=seed.js.map
@@ -1,3 +1 @@
1
- import type { Router } from 'express';
2
- import { type ApiContext } from './helpers';
3
- export declare function registerTableRoutes(router: Router, ctx: ApiContext): void;
1
+ import type {Router} from 'express';import{type ApiContext} from './helpers';export declare function registerTableRoutes(router:Router,ctx:ApiContext):void;
@@ -5,7 +5,6 @@ const generator_1 = require("../../sql/generator");
5
5
  const helpers_1 = require("./helpers");
6
6
  function registerTableRoutes(router, ctx) {
7
7
  const { db } = ctx;
8
- // ---- Tables ------------------------------------------------------------
9
8
  router.get('/api/tables', (0, helpers_1.wrap)(async (_req, res) => {
10
9
  const r = await db.listTables();
11
10
  if (!r.success)
@@ -40,7 +39,6 @@ function registerTableRoutes(router, ctx) {
40
39
  }
41
40
  }
42
41
  catch {
43
- // FK option loading is best-effort.
44
42
  }
45
43
  }
46
44
  (0, helpers_1.ok)(res, options);
@@ -68,7 +66,83 @@ function registerTableRoutes(router, ctx) {
68
66
  return (0, helpers_1.fail)(res, r.error ?? 'Failed to drop table.');
69
67
  (0, helpers_1.ok)(res, { message: `Table "${req.params.table}" dropped.` });
70
68
  }));
71
- // Preview generated CREATE TABLE SQL without executing
69
+ router.post('/api/tables/bulk-drop', (0, helpers_1.wrap)(async (req, res) => {
70
+ const tables = Array.isArray(req.body?.tables)
71
+ ? req.body.tables.filter((t) => typeof t === 'string' && t.trim().length > 0)
72
+ : [];
73
+ const force = Boolean(req.body?.force);
74
+ if (tables.length === 0)
75
+ return (0, helpers_1.fail)(res, 'No tables specified.');
76
+ const dropped = [];
77
+ const failed = [];
78
+ if (force && db.dialect === 'sqlite') {
79
+ await db.run('PRAGMA foreign_keys = OFF');
80
+ }
81
+ for (const table of tables) {
82
+ try {
83
+ if (force && db.dialect === 'postgres') {
84
+ const r = await db.run(`DROP TABLE IF EXISTS ${(0, generator_1.quoteIdentifier)(table)} CASCADE`);
85
+ if (!r.success)
86
+ throw new Error(r.error ?? 'Failed to drop table.');
87
+ }
88
+ else {
89
+ const r = await db.dropTable(table);
90
+ if (!r.success)
91
+ throw new Error(r.error ?? 'Failed to drop table.');
92
+ }
93
+ dropped.push(table);
94
+ }
95
+ catch (err) {
96
+ failed.push({ table, error: err.message });
97
+ }
98
+ }
99
+ if (force && db.dialect === 'sqlite') {
100
+ await db.run('PRAGMA foreign_keys = ON');
101
+ }
102
+ (0, helpers_1.ok)(res, {
103
+ dropped,
104
+ failed,
105
+ message: `Dropped ${dropped.length} table(s)${failed.length ? `, ${failed.length} failed` : ''}.`,
106
+ });
107
+ }));
108
+ router.post('/api/tables/bulk-truncate', (0, helpers_1.wrap)(async (req, res) => {
109
+ const tables = Array.isArray(req.body?.tables)
110
+ ? req.body.tables.filter((t) => typeof t === 'string' && t.trim().length > 0)
111
+ : [];
112
+ const force = Boolean(req.body?.force);
113
+ if (tables.length === 0)
114
+ return (0, helpers_1.fail)(res, 'No tables specified.');
115
+ const cleared = [];
116
+ const failed = [];
117
+ if (force && db.dialect === 'sqlite') {
118
+ await db.run('PRAGMA foreign_keys = OFF');
119
+ }
120
+ for (const table of tables) {
121
+ try {
122
+ let r;
123
+ if (force && db.dialect === 'postgres') {
124
+ r = await db.run(`TRUNCATE ${(0, generator_1.quoteIdentifier)(table)} RESTART IDENTITY CASCADE`);
125
+ }
126
+ else {
127
+ r = await db.run(`DELETE FROM ${(0, generator_1.quoteIdentifier)(table)}`);
128
+ }
129
+ if (!r.success)
130
+ throw new Error(r.error ?? 'Failed to clear table.');
131
+ cleared.push(table);
132
+ }
133
+ catch (err) {
134
+ failed.push({ table, error: err.message });
135
+ }
136
+ }
137
+ if (force && db.dialect === 'sqlite') {
138
+ await db.run('PRAGMA foreign_keys = ON');
139
+ }
140
+ (0, helpers_1.ok)(res, {
141
+ cleared,
142
+ failed,
143
+ message: `Cleared ${cleared.length} table(s)${failed.length ? `, ${failed.length} failed` : ''}.`,
144
+ });
145
+ }));
72
146
  router.post('/api/tables/generate', (0, helpers_1.wrap)(async (req, res) => {
73
147
  const name = String(req.body?.name ?? '').trim();
74
148
  const columns = (req.body?.columns ?? []);
@@ -105,7 +179,6 @@ function registerTableRoutes(router, ctx) {
105
179
  });
106
180
  router.post('/api/tables', createTableHandler);
107
181
  router.post('/api/tables/create', createTableHandler);
108
- // ---- Columns -----------------------------------------------------------
109
182
  router.post('/api/tables/:table/columns', (0, helpers_1.wrap)(async (req, res) => {
110
183
  const col = req.body;
111
184
  if (!col || !col.name)
@@ -139,7 +212,6 @@ function registerTableRoutes(router, ctx) {
139
212
  return (0, helpers_1.fail)(res, r.error ?? 'Failed to drop column.');
140
213
  (0, helpers_1.ok)(res, { message: `Column "${req.params.column}" dropped.` });
141
214
  }));
142
- // ---- Indexes -----------------------------------------------------------
143
215
  router.post('/api/tables/:table/indexes', (0, helpers_1.wrap)(async (req, res) => {
144
216
  const def = req.body;
145
217
  if (!def || !Array.isArray(def.columns) || def.columns.length === 0) {
@@ -157,4 +229,3 @@ function registerTableRoutes(router, ctx) {
157
229
  (0, helpers_1.ok)(res, { message: `Index "${req.params.index}" dropped.` });
158
230
  }));
159
231
  }
160
- //# sourceMappingURL=tables.js.map