admindb 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/LICENSE +20 -20
  2. package/dist/app.js +36 -25
  3. package/dist/core/context.d.ts +1 -0
  4. package/dist/core/context.js +2 -0
  5. package/dist/core/errors.d.ts +1 -0
  6. package/dist/core/errors.js +39 -0
  7. package/dist/core/index.d.ts +1 -0
  8. package/dist/core/index.js +20 -0
  9. package/dist/core/result.d.ts +1 -0
  10. package/dist/core/result.js +10 -0
  11. package/dist/core/router.d.ts +1 -0
  12. package/dist/core/router.js +37 -0
  13. package/dist/data/chain.js +1 -1
  14. package/dist/data/engine.js +1 -1
  15. package/dist/db/database.d.ts +1 -1
  16. package/dist/db/database.js +9 -31
  17. package/dist/db/dialects/index.d.ts +1 -0
  18. package/dist/db/dialects/index.js +58 -0
  19. package/dist/db/dialects/postgres/ddl.d.ts +1 -0
  20. package/dist/db/dialects/postgres/ddl.js +107 -0
  21. package/dist/db/dialects/postgres/dialect.d.ts +1 -0
  22. package/dist/db/dialects/postgres/dialect.js +28 -0
  23. package/dist/db/dialects/postgres/introspector.d.ts +1 -0
  24. package/dist/db/dialects/postgres/introspector.js +265 -0
  25. package/dist/db/dialects/postgres/types.d.ts +1 -0
  26. package/dist/db/dialects/postgres/types.js +83 -0
  27. package/dist/db/dialects/sqlite/ddl.d.ts +1 -0
  28. package/dist/db/dialects/sqlite/ddl.js +244 -0
  29. package/dist/db/dialects/sqlite/dialect.d.ts +1 -0
  30. package/dist/db/dialects/sqlite/dialect.js +25 -0
  31. package/dist/db/dialects/sqlite/introspector.d.ts +1 -0
  32. package/dist/db/dialects/sqlite/introspector.js +214 -0
  33. package/dist/db/dialects/sqlite/types.d.ts +1 -0
  34. package/dist/db/dialects/sqlite/types.js +69 -0
  35. package/dist/db/dialects/types.d.ts +1 -0
  36. package/dist/db/dialects/types.js +15 -0
  37. package/dist/db/export.js +1 -1
  38. package/dist/db/index.d.ts +1 -1
  39. package/dist/db/index.js +1 -1
  40. package/dist/db/manager.d.ts +1 -1
  41. package/dist/db/manager.js +101 -21
  42. package/dist/db/postgres.d.ts +1 -1
  43. package/dist/db/postgres.js +20 -284
  44. package/dist/db/types.d.ts +1 -1
  45. package/dist/db/types.js +0 -4
  46. package/dist/index.d.ts +1 -1
  47. package/dist/index.js +4 -1
  48. package/dist/modules/databases/databases.routes.d.ts +1 -0
  49. package/dist/modules/databases/databases.routes.js +150 -0
  50. package/dist/modules/databases/databases.service.d.ts +1 -0
  51. package/dist/modules/databases/databases.service.js +134 -0
  52. package/dist/modules/databases/index.d.ts +1 -0
  53. package/dist/{routes → modules/databases}/index.js +2 -3
  54. package/dist/modules/erd/erd.routes.d.ts +1 -0
  55. package/dist/modules/erd/erd.routes.js +32 -0
  56. package/dist/modules/erd/erd.service.d.ts +1 -0
  57. package/dist/modules/erd/erd.service.js +39 -0
  58. package/dist/modules/erd/index.d.ts +1 -0
  59. package/dist/modules/erd/index.js +18 -0
  60. package/dist/modules/index.d.ts +1 -0
  61. package/dist/modules/index.js +62 -0
  62. package/dist/modules/query/index.d.ts +1 -0
  63. package/dist/modules/query/index.js +18 -0
  64. package/dist/modules/query/query.routes.d.ts +1 -0
  65. package/dist/modules/query/query.routes.js +54 -0
  66. package/dist/modules/query/query.service.d.ts +1 -0
  67. package/dist/modules/query/query.service.js +95 -0
  68. package/dist/modules/rows/blob.helper.d.ts +1 -0
  69. package/dist/modules/rows/blob.helper.js +40 -0
  70. package/dist/modules/rows/display.helper.d.ts +1 -0
  71. package/dist/modules/rows/display.helper.js +73 -0
  72. package/dist/modules/rows/index.d.ts +1 -0
  73. package/dist/modules/rows/index.js +20 -0
  74. package/dist/modules/rows/rows.routes.d.ts +1 -0
  75. package/dist/modules/rows/rows.routes.js +555 -0
  76. package/dist/modules/rows/rows.service.d.ts +1 -0
  77. package/dist/modules/rows/rows.service.js +433 -0
  78. package/dist/modules/schema/index.d.ts +1 -0
  79. package/dist/modules/schema/index.js +18 -0
  80. package/dist/modules/schema/schema.routes.d.ts +1 -0
  81. package/dist/modules/schema/schema.routes.js +171 -0
  82. package/dist/modules/schema/schema.service.d.ts +1 -0
  83. package/dist/modules/schema/schema.service.js +58 -0
  84. package/dist/modules/seed/index.d.ts +1 -0
  85. package/dist/modules/seed/index.js +18 -0
  86. package/dist/modules/seed/seed.routes.d.ts +1 -0
  87. package/dist/modules/seed/seed.routes.js +212 -0
  88. package/dist/modules/seed/seed.service.d.ts +1 -0
  89. package/dist/{routes/api/helpers.js → modules/seed/seed.service.js} +86 -109
  90. package/dist/modules/tables/index.d.ts +1 -0
  91. package/dist/modules/tables/index.js +18 -0
  92. package/dist/modules/tables/tables.routes.d.ts +1 -0
  93. package/dist/modules/tables/tables.routes.js +194 -0
  94. package/dist/modules/tables/tables.service.d.ts +1 -0
  95. package/dist/modules/tables/tables.service.js +218 -0
  96. package/dist/public/js/browse.js +1 -1
  97. package/dist/public/js/query.js +1 -1
  98. package/dist/types/api.d.ts +1 -1
  99. package/dist/utils/common.d.ts +1 -1
  100. package/dist/utils/common.js +1 -3
  101. package/dist/utils/stream.d.ts +1 -0
  102. package/dist/utils/stream.js +131 -0
  103. package/dist/views/pages/error.hbs +10 -1
  104. package/dist/views/pages/home.hbs +1 -1
  105. package/dist/views/pages/query.hbs +1 -1
  106. package/dist/views/pages/seed-select.hbs +1 -252
  107. package/dist/views/pages/seed.hbs +1 -307
  108. package/dist/views/pages/table.hbs +1 -1
  109. package/dist/views/partials/sidebar.hbs +1 -1
  110. package/docs/API.md +4 -17
  111. package/docs/EXAMPLES.md +579 -579
  112. package/docs/SECURITY.md +265 -265
  113. package/package.json +4 -2
  114. package/dist/db/introspection.d.ts +0 -1
  115. package/dist/db/introspection.js +0 -164
  116. package/dist/routes/api/erd.d.ts +0 -1
  117. package/dist/routes/api/erd.js +0 -45
  118. package/dist/routes/api/helpers.d.ts +0 -1
  119. package/dist/routes/api/import-export.d.ts +0 -1
  120. package/dist/routes/api/import-export.js +0 -84
  121. package/dist/routes/api/index.d.ts +0 -1
  122. package/dist/routes/api/index.js +0 -55
  123. package/dist/routes/api/query.d.ts +0 -1
  124. package/dist/routes/api/query.js +0 -77
  125. package/dist/routes/api/rows.d.ts +0 -1
  126. package/dist/routes/api/rows.js +0 -373
  127. package/dist/routes/api/seed.d.ts +0 -1
  128. package/dist/routes/api/seed.js +0 -180
  129. package/dist/routes/api/tables.d.ts +0 -1
  130. package/dist/routes/api/tables.js +0 -230
  131. package/dist/routes/databases.d.ts +0 -1
  132. package/dist/routes/databases.js +0 -212
  133. package/dist/routes/index.d.ts +0 -1
  134. package/dist/routes/pages.d.ts +0 -1
  135. package/dist/routes/pages.js +0 -540
@@ -0,0 +1,555 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerRowRoutes = registerRowRoutes;
4
+ const common_1 = require("../../utils/common");
5
+ const router_1 = require("../../core/router");
6
+ const tables_service_1 = require("../tables/tables.service");
7
+ const schema_service_1 = require("../schema/schema.service");
8
+ const index_1 = require("./index");
9
+ function registerRowRoutes(router, ctx) {
10
+ router.get('/tables/:db/:table', async (req, res, next) => {
11
+ try {
12
+ const db = ctx.getContext(req);
13
+ const dbId = req.params.db;
14
+ const table = req.params.table;
15
+ res.locals.dbId = dbId;
16
+ const tablesList = await tables_service_1.TableService.listTables(db);
17
+ const allNames = tablesList.map((t) => t.name);
18
+ (0, router_1.initPageLocals)(res, db, dbId, allNames);
19
+ const info = await tables_service_1.TableService.getTableInfo(db, table);
20
+ if (!info) {
21
+ return (0, router_1.notFound)(res, `Table "${table}" does not exist.`);
22
+ }
23
+ const page = Math.max(1, Number.parseInt(String(req.query.page ?? '1'), 10) || 1);
24
+ const pageSize = Math.max(1, Number.parseInt(String(req.query.size ?? '50'), 10) || 50);
25
+ const orderBy = req.query.orderBy ? String(req.query.orderBy) : undefined;
26
+ const orderDir = String(req.query.orderDir ?? 'asc').toLowerCase() === 'desc' ? 'desc' : 'asc';
27
+ const filters = (0, common_1.parseFilters)(req.query.f);
28
+ const filterQS = (0, common_1.filtersToQS)(filters);
29
+ const [count, paginated, refs] = await Promise.all([
30
+ index_1.RowsService.getRowCount(db, table, filters),
31
+ index_1.RowsService.getPaginatedRows(db, table, { page, limit: pageSize, orderBy, orderDir, filters }),
32
+ schema_service_1.SchemaService.getReferencingTables(db, table),
33
+ ]);
34
+ const rawRows = paginated.rows;
35
+ const pages = Math.max(1, Math.ceil(count / pageSize));
36
+ const basePath = String(res.locals.basePath ?? '');
37
+ const sizes = [25, 50, 100, 250];
38
+ const colHeaders = info.columns.map((c) => {
39
+ const active = c.name === orderBy;
40
+ const nextDir = active ? (orderDir === 'asc' ? 'desc' : 'asc') : 'asc';
41
+ const qs = `page=1&size=${pageSize}&orderBy=${encodeURIComponent(c.name)}&orderDir=${nextDir}`;
42
+ return {
43
+ name: c.name,
44
+ pk: c.pk,
45
+ type: c.type,
46
+ active,
47
+ dir: active ? orderDir : null,
48
+ href: `${basePath}/tables/${encodeURIComponent(dbId)}/${encodeURIComponent(table)}?${qs}${filterQS ? `&${filterQS}` : ''}`,
49
+ };
50
+ });
51
+ const refColumns = refs
52
+ .filter((r) => !r.table.startsWith('_'))
53
+ .map((rt) => ({ table: rt.table, from: rt.refs[0]?.from ?? '', to: rt.refs[0]?.to ?? '' }));
54
+ const countMap = await tables_service_1.TableService.getIncomingForeignKeyCounts(db, rawRows, refColumns);
55
+ const rows = (0, index_1.buildDisplayRows)(rawRows, info, dbId, table, basePath).map((dr, i) => {
56
+ const raw = rawRows[i];
57
+ const rowRefs = refColumns.map((col) => {
58
+ const v = raw[col.to];
59
+ return {
60
+ table: col.table,
61
+ from: col.from,
62
+ to: col.to,
63
+ value: v == null ? '' : String(v),
64
+ count: v == null ? 0 : (countMap.get(col.table)?.get(String(v)) ?? 0),
65
+ };
66
+ });
67
+ return { ...dr, refs: rowRefs };
68
+ });
69
+ res.locals.currentTable = table;
70
+ res.render('pages/table', {
71
+ title: table,
72
+ dbId,
73
+ table,
74
+ info,
75
+ rows,
76
+ count,
77
+ page,
78
+ pageSize,
79
+ pages,
80
+ pkCols: info.primaryKey,
81
+ colNames: info.columns.map((c) => c.name),
82
+ colHeaders,
83
+ refColumns,
84
+ hasPk: true,
85
+ orderBy: orderBy ?? '',
86
+ orderDir,
87
+ sizes,
88
+ filters,
89
+ filterQS,
90
+ firstRow: count === 0 ? 0 : (page - 1) * pageSize + 1,
91
+ lastRow: Math.min(page * pageSize, count),
92
+ browseConfig: {
93
+ dbId,
94
+ table,
95
+ filters,
96
+ pkCols: info.primaryKey.length ? info.primaryKey : ['_rowid_'],
97
+ hasPk: true,
98
+ readonly: db.isReadOnly,
99
+ },
100
+ });
101
+ }
102
+ catch (err) {
103
+ next(err);
104
+ }
105
+ });
106
+ router.get('/tables/:db/:table/rows/new', async (req, res, next) => {
107
+ try {
108
+ const db = ctx.getContext(req);
109
+ const dbId = req.params.db;
110
+ const table = req.params.table;
111
+ res.locals.dbId = dbId;
112
+ const tablesList = await tables_service_1.TableService.listTables(db);
113
+ const allNames = tablesList.map((t) => t.name);
114
+ (0, router_1.initPageLocals)(res, db, dbId, allNames);
115
+ const info = await tables_service_1.TableService.getTableInfo(db, table);
116
+ if (!info) {
117
+ return (0, router_1.notFound)(res, `Table "${table}" does not exist.`);
118
+ }
119
+ const pkColumns = info.primaryKey;
120
+ res.locals.currentTable = table;
121
+ res.render('pages/form', {
122
+ title: `New row · ${table}`,
123
+ dbId,
124
+ table,
125
+ mode: 'insert',
126
+ pk: null,
127
+ pkColumns,
128
+ infoSummary: `${info.columns.length} column(s)` + (pkColumns.length ? ` · PK: ${pkColumns.join(', ')}` : ''),
129
+ config: { dbId, table, mode: 'insert', pk: null, pkColumns },
130
+ });
131
+ }
132
+ catch (err) {
133
+ next(err);
134
+ }
135
+ });
136
+ router.get('/tables/:db/:table/rows/:id/edit', async (req, res, next) => {
137
+ try {
138
+ const db = ctx.getContext(req);
139
+ const dbId = req.params.db;
140
+ const table = req.params.table;
141
+ res.locals.dbId = dbId;
142
+ const tablesList = await tables_service_1.TableService.listTables(db);
143
+ const allNames = tablesList.map((t) => t.name);
144
+ (0, router_1.initPageLocals)(res, db, dbId, allNames);
145
+ const info = await tables_service_1.TableService.getTableInfo(db, table);
146
+ if (!info) {
147
+ return (0, router_1.notFound)(res, `Table "${table}" does not exist.`);
148
+ }
149
+ const pkColumns = info.primaryKey.length ? info.primaryKey : ['_rowid_'];
150
+ const pk = (0, common_1.decodePk)(req.params.id);
151
+ if (pk.length !== pkColumns.length) {
152
+ return (0, router_1.notFound)(res, 'Invalid row identifier.');
153
+ }
154
+ res.locals.currentTable = table;
155
+ res.render('pages/form', {
156
+ title: `Edit row · ${table}`,
157
+ dbId,
158
+ table,
159
+ mode: 'edit',
160
+ pk: req.params.id,
161
+ pkColumns,
162
+ infoSummary: `${info.columns.length} column(s)` + (info.primaryKey.length ? ` · PK: ${info.primaryKey.join(', ')}` : ''),
163
+ config: { dbId, table, mode: 'edit', pk: req.params.id, pkColumns },
164
+ });
165
+ }
166
+ catch (err) {
167
+ next(err);
168
+ }
169
+ });
170
+ router.get('/api/tables/:db/:table/rows', (0, router_1.wrap)(async (req, res) => {
171
+ const db = ctx.getContext(req);
172
+ const page = Number.parseInt(String(req.query.page ?? '1'), 10);
173
+ const limit = Number.parseInt(String(req.query.limit ?? '50'), 10);
174
+ const orderBy = req.query.orderBy ? String(req.query.orderBy) : undefined;
175
+ const orderDir = req.query.orderDir === 'desc' ? 'desc' : 'asc';
176
+ const filters = (0, common_1.parseFilters)(req.query.f);
177
+ try {
178
+ const data = await index_1.RowsService.getPaginatedRows(db, req.params.table, {
179
+ page,
180
+ limit,
181
+ orderBy,
182
+ orderDir,
183
+ filters,
184
+ });
185
+ (0, router_1.ok)(res, data);
186
+ }
187
+ catch (err) {
188
+ (0, router_1.fail)(res, err.message ?? 'Failed to load rows.');
189
+ }
190
+ }));
191
+ router.get('/api/tables/:db/:table/cursor-rows', (0, router_1.wrap)(async (req, res) => {
192
+ const db = ctx.getContext(req);
193
+ const cursor = req.query.cursor ? String(req.query.cursor) : undefined;
194
+ const limit = Number.parseInt(String(req.query.limit ?? '50'), 10);
195
+ const orderBy = req.query.orderBy ? String(req.query.orderBy) : undefined;
196
+ const orderDir = req.query.orderDir === 'desc' ? 'desc' : 'asc';
197
+ const filters = (0, common_1.parseFilters)(req.query.f);
198
+ try {
199
+ const data = await index_1.RowsService.getCursorPaginatedRows(db, req.params.table, {
200
+ cursor,
201
+ limit,
202
+ orderBy,
203
+ orderDir,
204
+ filters,
205
+ });
206
+ (0, router_1.ok)(res, data);
207
+ }
208
+ catch (err) {
209
+ (0, router_1.fail)(res, err.message ?? 'Failed to load rows.');
210
+ }
211
+ }));
212
+ router.get('/api/tables/:db/:table/rows/count', (0, router_1.wrap)(async (req, res) => {
213
+ const db = ctx.getContext(req);
214
+ const filters = (0, common_1.parseFilters)(req.query.f);
215
+ try {
216
+ const count = await index_1.RowsService.getRowCount(db, req.params.table, filters);
217
+ (0, router_1.ok)(res, { count });
218
+ }
219
+ catch (err) {
220
+ (0, router_1.fail)(res, err.message ?? 'Failed to count rows.');
221
+ }
222
+ }));
223
+ router.get('/api/tables/:db/:table/row/:id', (0, router_1.wrap)(async (req, res) => {
224
+ const db = ctx.getContext(req);
225
+ const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
226
+ if (!info)
227
+ return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
228
+ const where = (0, index_1.pkWhere)(info, req.params.id);
229
+ if (!where)
230
+ return (0, router_1.fail)(res, 'Invalid primary key.', 400);
231
+ try {
232
+ const row = await index_1.RowsService.getRow(db, req.params.table, where);
233
+ if (!row)
234
+ return (0, router_1.fail)(res, 'Row not found.', 404);
235
+ (0, router_1.ok)(res, row);
236
+ }
237
+ catch (err) {
238
+ (0, router_1.fail)(res, err.message ?? 'Failed to load row.');
239
+ }
240
+ }));
241
+ router.get('/api/tables/:db/:table/row/:id/blob/:column', (0, router_1.wrap)(async (req, res) => {
242
+ const db = ctx.getContext(req);
243
+ const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
244
+ if (!info)
245
+ return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
246
+ const where = (0, index_1.pkWhere)(info, req.params.id);
247
+ if (!where)
248
+ return (0, router_1.fail)(res, 'Invalid primary key.', 400);
249
+ try {
250
+ const blobResult = await index_1.RowsService.getBlob(db, req.params.table, where, req.params.column);
251
+ if (!blobResult) {
252
+ return (0, router_1.fail)(res, 'Column value is NULL.', 404);
253
+ }
254
+ const { buffer, mimeInfo } = blobResult;
255
+ res.setHeader('Content-Type', mimeInfo.mime);
256
+ res.setHeader('Content-Length', buffer.length);
257
+ res.setHeader('Cache-Control', 'no-cache');
258
+ if (req.query.download === '1' || req.query.download === 'true') {
259
+ const filename = `${req.params.table}_${req.params.column}_${req.params.id}.${mimeInfo.ext}`;
260
+ res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
261
+ }
262
+ res.end(buffer);
263
+ }
264
+ catch (err) {
265
+ (0, router_1.fail)(res, err.message ?? 'Failed to load row.');
266
+ }
267
+ }));
268
+ router.get('/api/tables/:db/:table/row/:id/blob/:column/meta', (0, router_1.wrap)(async (req, res) => {
269
+ const db = ctx.getContext(req);
270
+ const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
271
+ if (!info)
272
+ return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
273
+ const where = (0, index_1.pkWhere)(info, req.params.id);
274
+ if (!where)
275
+ return (0, router_1.fail)(res, 'Invalid primary key.', 400);
276
+ try {
277
+ const meta = await index_1.RowsService.getBlobMeta(db, req.params.table, where, req.params.column);
278
+ if (!meta)
279
+ return (0, router_1.fail)(res, 'Row not found.', 404);
280
+ (0, router_1.ok)(res, meta);
281
+ }
282
+ catch (err) {
283
+ (0, router_1.fail)(res, err.message ?? 'Failed to load row.');
284
+ }
285
+ }));
286
+ router.put('/api/tables/:db/:table/row/:id/blob/:column', (0, router_1.wrap)(async (req, res) => {
287
+ const db = ctx.getContext(req);
288
+ const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
289
+ if (!info)
290
+ return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
291
+ const where = (0, index_1.pkWhere)(info, req.params.id);
292
+ if (!where)
293
+ return (0, router_1.fail)(res, 'Invalid primary key.', 400);
294
+ const colName = req.params.column;
295
+ if (req.body?.data === undefined) {
296
+ return (0, router_1.fail)(res, 'Payload data is required.');
297
+ }
298
+ try {
299
+ const size = await index_1.RowsService.updateBlob(db, req.params.table, where, colName, req.body.data, req.body.format);
300
+ (0, router_1.ok)(res, { message: `Updated BLOB in column "${colName}".`, size });
301
+ }
302
+ catch (err) {
303
+ (0, router_1.fail)(res, err.message ?? 'Failed to update BLOB.');
304
+ }
305
+ }));
306
+ router.post('/api/tables/:db/:table/rows/generate', (0, router_1.wrap)(async (req, res) => {
307
+ const db = ctx.getContext(req);
308
+ const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
309
+ if (!info)
310
+ return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
311
+ const fields = (0, index_1.buildFields)(info, req.body?.values ?? req.body ?? {});
312
+ try {
313
+ (0, router_1.ok)(res, { sql: index_1.RowsService.generateInsertSql(req.params.table, fields) });
314
+ }
315
+ catch (err) {
316
+ (0, router_1.fail)(res, err.message);
317
+ }
318
+ }));
319
+ const generateUpdateHandler = (0, router_1.wrap)(async (req, res) => {
320
+ const db = ctx.getContext(req);
321
+ const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
322
+ if (!info)
323
+ return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
324
+ const id = req.params.id || (req.query.id ? String(req.query.id) : null);
325
+ if (!id)
326
+ return (0, router_1.fail)(res, 'Primary key id is required.');
327
+ const where = (0, index_1.pkWhere)(info, id);
328
+ if (!where)
329
+ return (0, router_1.fail)(res, 'Invalid primary key.', 400);
330
+ const nulls = Array.isArray(req.body?.nulls) ? req.body.nulls : [];
331
+ const fields = (0, index_1.buildUpdateFields)(info, req.body?.values ?? req.body ?? {}, nulls);
332
+ try {
333
+ (0, router_1.ok)(res, { sql: index_1.RowsService.generateUpdateSql(req.params.table, fields, where) });
334
+ }
335
+ catch (err) {
336
+ (0, router_1.fail)(res, err.message);
337
+ }
338
+ });
339
+ router.put('/api/tables/:db/:table/row/:id/generate', generateUpdateHandler);
340
+ router.post('/api/tables/:db/:table/row/:id/generate', generateUpdateHandler);
341
+ router.put('/api/tables/:db/:table/rows/generate', generateUpdateHandler);
342
+ router.post('/api/tables/:db/:table/rows', (0, router_1.wrap)(async (req, res) => {
343
+ const db = ctx.getContext(req);
344
+ const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
345
+ if (!info)
346
+ return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
347
+ const isBatch = Array.isArray(req.body?.rows);
348
+ const rowList = isBatch
349
+ ? req.body.rows
350
+ : [req.body?.values ?? req.body ?? {}];
351
+ if (!rowList.length)
352
+ return (0, router_1.fail)(res, 'No rows provided.');
353
+ const fieldsList = rowList.map((row) => (0, index_1.buildFields)(info, row));
354
+ try {
355
+ if (!isBatch) {
356
+ const id = await index_1.RowsService.insertRow(db, req.params.table, fieldsList[0]);
357
+ return (0, router_1.ok)(res, { message: 'Row inserted.', id }, 201);
358
+ }
359
+ const inserted = await index_1.RowsService.insertRows(db, req.params.table, fieldsList);
360
+ (0, router_1.ok)(res, { message: `${inserted} row(s) inserted.`, inserted }, 201);
361
+ }
362
+ catch (err) {
363
+ (0, router_1.fail)(res, err.message ?? 'Failed to insert row(s).');
364
+ }
365
+ }));
366
+ const updateSingleRowHandler = (0, router_1.wrap)(async (req, res) => {
367
+ const db = ctx.getContext(req);
368
+ const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
369
+ if (!info)
370
+ return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
371
+ const id = req.params.id || (req.query.id ? String(req.query.id) : null);
372
+ if (!id)
373
+ return (0, router_1.fail)(res, 'Primary key id is required.');
374
+ const where = (0, index_1.pkWhere)(info, id);
375
+ if (!where)
376
+ return (0, router_1.fail)(res, 'Invalid primary key.');
377
+ const nulls = Array.isArray(req.body?.nulls) ? req.body.nulls : [];
378
+ const fields = (0, index_1.buildUpdateFields)(info, req.body?.values ?? req.body ?? {}, nulls);
379
+ try {
380
+ await index_1.RowsService.updateRow(db, req.params.table, fields, where);
381
+ (0, router_1.ok)(res, { message: 'Row updated.' });
382
+ }
383
+ catch (err) {
384
+ (0, router_1.fail)(res, err.message ?? 'Failed to update row.');
385
+ }
386
+ });
387
+ const bulkUpdateHandler = (0, router_1.wrap)(async (req, res) => {
388
+ const db = ctx.getContext(req);
389
+ const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
390
+ if (!info)
391
+ return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
392
+ if (Array.isArray(req.body?.updates)) {
393
+ const updates = req.body.updates;
394
+ if (!updates.length)
395
+ return (0, router_1.fail)(res, 'No updates provided.');
396
+ if (updates.length > index_1.MAX_BULK_ROWS)
397
+ return (0, router_1.fail)(res, `Cannot update more than ${index_1.MAX_BULK_ROWS} rows at once.`);
398
+ const rowUpdates = [];
399
+ for (const u of updates) {
400
+ if (!u.id)
401
+ return (0, router_1.fail)(res, 'Every update requires an id.');
402
+ const where = (0, index_1.pkWhere)(info, String(u.id));
403
+ if (!where)
404
+ return (0, router_1.fail)(res, `Invalid primary key: "${u.id}".`);
405
+ const nulls = Array.isArray(u.nulls) ? u.nulls.map(String) : [];
406
+ const fields = (0, index_1.buildUpdateFields)(info, u.values ?? {}, nulls);
407
+ if (fields.length)
408
+ rowUpdates.push({ fields, where });
409
+ }
410
+ try {
411
+ const updated = await index_1.RowsService.updateRows(db, req.params.table, rowUpdates);
412
+ return (0, router_1.ok)(res, { message: updated ? `${updated} row(s) updated.` : 'No changes to apply.', updated });
413
+ }
414
+ catch (err) {
415
+ return (0, router_1.fail)(res, err.message ?? 'Failed to update rows.');
416
+ }
417
+ }
418
+ return updateSingleRowHandler(req, res);
419
+ });
420
+ router.put('/api/tables/:db/:table/row/:id', updateSingleRowHandler);
421
+ router.put('/api/tables/:db/:table/rows', bulkUpdateHandler);
422
+ router.post('/api/tables/:db/:table/rows/bulk-update', bulkUpdateHandler);
423
+ const deleteSingleRowHandler = (0, router_1.wrap)(async (req, res) => {
424
+ const db = ctx.getContext(req);
425
+ const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
426
+ if (!info)
427
+ return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
428
+ const id = req.params.id || (req.query.id ? String(req.query.id) : null);
429
+ if (!id)
430
+ return (0, router_1.fail)(res, 'Primary key id is required.');
431
+ const where = (0, index_1.pkWhere)(info, id);
432
+ if (!where)
433
+ return (0, router_1.fail)(res, 'Invalid primary key.');
434
+ try {
435
+ await index_1.RowsService.deleteRow(db, req.params.table, where);
436
+ (0, router_1.ok)(res, { message: 'Row deleted.' });
437
+ }
438
+ catch (err) {
439
+ (0, router_1.fail)(res, err.message ?? 'Failed to delete row.');
440
+ }
441
+ });
442
+ router.delete('/api/tables/:db/:table/row/:id', deleteSingleRowHandler);
443
+ router.delete('/api/tables/:db/:table/rows', deleteSingleRowHandler);
444
+ router.get('/api/tables/:db/:table/rows/:id/references', (0, router_1.wrap)(async (req, res) => {
445
+ const db = ctx.getContext(req);
446
+ const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
447
+ if (!info)
448
+ return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
449
+ const where = (0, index_1.pkWhere)(info, req.params.id);
450
+ if (!where)
451
+ return (0, router_1.fail)(res, 'Invalid primary key.', 400);
452
+ try {
453
+ const references = await index_1.RowsService.getRowReferences(db, req.params.table, info, where);
454
+ (0, router_1.ok)(res, { references });
455
+ }
456
+ catch (err) {
457
+ const msg = err.message;
458
+ if (msg === 'Row not found.')
459
+ return (0, router_1.fail)(res, msg, 404);
460
+ (0, router_1.fail)(res, msg ?? 'Failed to fetch references.');
461
+ }
462
+ }));
463
+ router.post('/api/tables/:db/:table/rows/bulk-delete', (0, router_1.wrap)(async (req, res) => {
464
+ const db = ctx.getContext(req);
465
+ const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
466
+ if (!info)
467
+ return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
468
+ const ids = (req.body?.ids ?? []);
469
+ if (!Array.isArray(ids) || !ids.length)
470
+ return (0, router_1.fail)(res, 'No row IDs provided.');
471
+ if (ids.length > index_1.MAX_BULK_ROWS)
472
+ return (0, router_1.fail)(res, `Cannot delete more than ${index_1.MAX_BULK_ROWS} rows at once.`);
473
+ const wheres = (0, index_1.resolvePkRows)(info, ids);
474
+ if (!wheres)
475
+ return (0, router_1.fail)(res, 'Invalid or unresolvable row IDs.');
476
+ try {
477
+ const deleted = await index_1.RowsService.deleteRows(db, req.params.table, wheres);
478
+ (0, router_1.ok)(res, { message: `${deleted} row(s) deleted.`, deleted });
479
+ }
480
+ catch (err) {
481
+ (0, router_1.fail)(res, err.message ?? 'Failed to delete rows.');
482
+ }
483
+ }));
484
+ router.post('/api/tables/:db/:table/rows/bulk-impact', (0, router_1.wrap)(async (req, res) => {
485
+ const db = ctx.getContext(req);
486
+ const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
487
+ if (!info)
488
+ return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
489
+ const ids = (req.body?.ids ?? []);
490
+ if (!Array.isArray(ids) || !ids.length)
491
+ return (0, router_1.ok)(res, { references: [], total: 0 });
492
+ const wheres = (0, index_1.resolvePkRows)(info, ids);
493
+ if (!wheres)
494
+ return (0, router_1.ok)(res, { references: [], total: 0 });
495
+ (0, router_1.ok)(res, await (0, index_1.computeBulkImpact)(db, info, wheres));
496
+ }));
497
+ router.post('/api/tables/:db/:table/rows/bulk-export', (0, router_1.wrap)(async (req, res) => {
498
+ const db = ctx.getContext(req);
499
+ const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
500
+ if (!info)
501
+ return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
502
+ const ids = (req.body?.ids ?? []);
503
+ if (!Array.isArray(ids) || !ids.length)
504
+ return (0, router_1.fail)(res, 'No row IDs provided.');
505
+ if (ids.length > index_1.MAX_BULK_ROWS)
506
+ return (0, router_1.fail)(res, `Cannot export more than ${index_1.MAX_BULK_ROWS} rows at once.`);
507
+ const wheres = (0, index_1.resolvePkRows)(info, ids);
508
+ if (!wheres)
509
+ return (0, router_1.fail)(res, 'Invalid or unresolvable row IDs.');
510
+ const format = String(req.body?.format ?? 'csv').toLowerCase() === 'json' ? 'json' : 'csv';
511
+ try {
512
+ const exp = await index_1.RowsService.exportBulkRows(db, req.params.table, info, wheres, format);
513
+ res.setHeader('Content-Type', exp.contentType);
514
+ res.setHeader('Content-Disposition', `attachment; filename="${exp.filename}"`);
515
+ res.send(exp.data);
516
+ }
517
+ catch (err) {
518
+ (0, router_1.fail)(res, err.message ?? 'Failed to load rows.');
519
+ }
520
+ }));
521
+ router.get('/api/tables/:db/:table/export', (0, router_1.wrap)(async (req, res) => {
522
+ const db = ctx.getContext(req);
523
+ const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
524
+ if (!info)
525
+ return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
526
+ const format = String(req.query.format ?? 'csv').toLowerCase() === 'json' ? 'json' : 'csv';
527
+ const filters = (0, common_1.parseFilters)(req.query.f);
528
+ try {
529
+ const exp = await index_1.RowsService.streamExportTable(db, req.params.table, info, { filters, format });
530
+ res.setHeader('Content-Type', exp.contentType);
531
+ res.setHeader('Content-Disposition', `attachment; filename="${exp.filename}"`);
532
+ exp.stream.pipe(res);
533
+ }
534
+ catch (err) {
535
+ (0, router_1.fail)(res, err.message ?? 'Failed to export table.');
536
+ }
537
+ }));
538
+ const importCsvHandler = (0, router_1.wrap)(async (req, res) => {
539
+ const db = ctx.getContext(req);
540
+ const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
541
+ if (!info)
542
+ return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
543
+ const csvText = typeof req.body === 'string' ? req.body : String(req.body?.csv ?? '');
544
+ try {
545
+ const inserted = await index_1.RowsService.importCsv(db, req.params.table, info, csvText);
546
+ (0, router_1.ok)(res, { message: `${inserted} row(s) imported.`, inserted });
547
+ }
548
+ catch (err) {
549
+ (0, router_1.fail)(res, err.message ?? 'Failed to import CSV.');
550
+ }
551
+ });
552
+ router.post('/api/tables/:db/:table/import', importCsvHandler);
553
+ router.post('/api/tables/:db/:table/rows/import', importCsvHandler);
554
+ router.post('/api/tables/:db/:table/import/csv', importCsvHandler);
555
+ }
@@ -0,0 +1 @@
1
+ import type {TableInfoData,WhereClause,RowFilters} from '../../db/index';import type {DatabaseContext} from '../../core/context';export declare const MAX_BULK_ROWS=1000;export interface CursorPaginationOptions{cursor?:string|null;limit?:number;orderBy?:string;orderDir?:'asc'|'desc';filters?:RowFilters;}export interface CursorPaginatedResult{rows:Record<string,unknown>[];nextCursor:string|null;prevCursor:string|null;hasMore:boolean;limit:number;}export interface BulkImpactResult{references:{table:string; from :string;to:string;count:number;}[];total:number;}export interface RowReferenceResult{table:string; from :string;to:string;value?:unknown;columns?:string[];rows:Record<string,unknown>[];count?:number;total:number;}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):WhereClause[]|null;export declare function resolvePkRows(info:TableInfoData,ids:unknown):WhereClause[][]|null;export declare function computeBulkImpact(ctx:DatabaseContext,info:TableInfoData,wheres:WhereClause[][]):Promise<BulkImpactResult>;export declare function encodeCursor(values:Record<string,unknown>):string;export declare function decodeCursor(cursor:string):Record<string,unknown>|null;export declare class RowsService{static getPaginatedRows(ctx:DatabaseContext,table:string,options?:{page?:number;limit?:number;orderBy?:string;orderDir?:'asc'|'desc';filters?:RowFilters;}):Promise<{rows:Record<string,unknown>[];total:number;page:number;limit:number;}>;static getCursorPaginatedRows(ctx:DatabaseContext,table:string,options?:CursorPaginationOptions):Promise<CursorPaginatedResult>;static getRowCount(ctx:DatabaseContext,table:string,filters?:RowFilters):Promise<number>;static getRow(ctx:DatabaseContext,table:string,where:WhereClause[]):Promise<Record<string,unknown>|null>;static getBlob(ctx:DatabaseContext,table:string,where:WhereClause[],column:string):Promise<{buffer:Buffer<ArrayBufferLike>;mimeInfo:import("../..").MimeAnalysis;}|null>;static getBlobMeta(ctx:DatabaseContext,table:string,where:WhereClause[],column:string):Promise<({isNull:boolean;}&Partial<import("../..").BlobMetadata>)|null>;static updateBlob(ctx:DatabaseContext,table:string,where:WhereClause[],column:string,data:unknown,format?:string):Promise<number>;static generateInsertSql(table:string,fields:{column:string;value:unknown;}[]):string;static generateUpdateSql(table:string,fields:{column:string;value:unknown;}[],where:WhereClause[]):string;static insertRow(ctx:DatabaseContext,table:string,fields:{column:string;value:unknown;}[]):Promise<number|null|undefined>;static insertRows(ctx:DatabaseContext,table:string,fieldsList:{column:string;value:unknown;}[][]):Promise<number>;static updateRow(ctx:DatabaseContext,table:string,fields:{column:string;value:unknown;}[],where:WhereClause[]):Promise<void>;static updateRows(ctx:DatabaseContext,table:string,rowUpdates:{fields:WhereClause[];where:WhereClause[];}[]):Promise<number>;static deleteRow(ctx:DatabaseContext,table:string,where:WhereClause[]):Promise<void>;static deleteRows(ctx:DatabaseContext,table:string,wheres:WhereClause[][]):Promise<number>;static getRowReferences(ctx:DatabaseContext,table:string,info:TableInfoData,where:WhereClause[]):Promise<RowReferenceResult[]>;static exportTable(ctx:DatabaseContext,table:string,info:TableInfoData,options:{filters?:RowFilters;limit?:number;format:'csv'|'json';}):Promise<{data:string;contentType:string;filename:string;}>;static streamExportTable(ctx:DatabaseContext,table:string,info:TableInfoData,options:{filters?:RowFilters;format:'csv'|'json';chunkSize?:number;}):Promise<{stream:NodeJS.ReadableStream;contentType:string;filename:string;}>;static exportBulkRows(ctx:DatabaseContext,table:string,info:TableInfoData,wheres:WhereClause[][],format:'csv'|'json'):Promise<{data:string;contentType:string;filename:string;}>;static importCsv(ctx:DatabaseContext,table:string,info:TableInfoData,csvText:string):Promise<number>;static streamImportCsv(ctx:DatabaseContext,table:string,info:TableInfoData,csvStream:AsyncIterable<string|Buffer>,batchSize?:number):Promise<number>;}