admindb 2.2.1 → 2.3.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 (87) hide show
  1. package/README.md +368 -364
  2. package/dist/app.js +6 -9
  3. package/dist/auth/config.d.ts +1 -1
  4. package/dist/auth/config.js +18 -30
  5. package/dist/auth/crypto.js +11 -21
  6. package/dist/auth/middleware.js +9 -16
  7. package/dist/auth/routes.js +9 -15
  8. package/dist/cli/args.d.ts +1 -1
  9. package/dist/cli/args.js +15 -26
  10. package/dist/cli/config.js +6 -16
  11. package/dist/cli/runner.js +4 -7
  12. package/dist/data/chain.d.ts +1 -1
  13. package/dist/data/chain.js +40 -617
  14. package/dist/data/datasets.d.ts +1 -1
  15. package/dist/data/datasets.js +43 -1
  16. package/dist/data/detector.js +45 -21
  17. package/dist/data/engine.d.ts +1 -1
  18. package/dist/data/engine.js +483 -142
  19. package/dist/data/index.d.ts +1 -1
  20. package/dist/data/index.js +8 -0
  21. package/dist/data/prng.d.ts +1 -0
  22. package/dist/data/prng.js +73 -0
  23. package/dist/data/registry.d.ts +1 -0
  24. package/dist/data/registry.js +805 -0
  25. package/dist/data/schema-graph.d.ts +1 -0
  26. package/dist/data/schema-graph.js +210 -0
  27. package/dist/data/strategies.d.ts +1 -1
  28. package/dist/data/strategies.js +44 -283
  29. package/dist/data/templates.d.ts +1 -0
  30. package/dist/data/templates.js +134 -0
  31. package/dist/data/types.d.ts +1 -1
  32. package/dist/data/validator.d.ts +1 -0
  33. package/dist/data/validator.js +160 -0
  34. package/dist/db/database.js +9 -14
  35. package/dist/db/export.js +32 -55
  36. package/dist/db/filters.d.ts +1 -1
  37. package/dist/db/filters.js +15 -38
  38. package/dist/db/introspection.js +4 -7
  39. package/dist/db/manager.js +27 -51
  40. package/dist/db/migrations.js +7 -14
  41. package/dist/db/postgres.js +30 -78
  42. package/dist/index.d.ts +1 -1
  43. package/dist/public/css/app.css +1 -1
  44. package/dist/public/js/browse.js +1 -1
  45. package/dist/public/js/inspector.js +1 -1
  46. package/dist/public/js/query.js +1 -1
  47. package/dist/public/js/seed.js +1 -1
  48. package/dist/routes/api/erd.js +3 -8
  49. package/dist/routes/api/helpers.d.ts +1 -1
  50. package/dist/routes/api/helpers.js +75 -25
  51. package/dist/routes/api/import-export.js +1 -2
  52. package/dist/routes/api/query.js +2 -6
  53. package/dist/routes/api/rows.js +4 -8
  54. package/dist/routes/api/seed.js +35 -21
  55. package/dist/routes/api/tables.js +15 -30
  56. package/dist/routes/databases.js +8 -26
  57. package/dist/routes/pages.js +67 -18
  58. package/dist/serverless.js +15 -40
  59. package/dist/sql/generator.d.ts +1 -1
  60. package/dist/sql/generator.js +35 -109
  61. package/dist/types/api.d.ts +1 -1
  62. package/dist/utils/common.d.ts +1 -1
  63. package/dist/utils/common.js +45 -85
  64. package/dist/utils/csv.d.ts +1 -1
  65. package/dist/utils/csv.js +11 -17
  66. package/dist/utils/datatype.js +46 -60
  67. package/dist/utils/icons.js +8 -8
  68. package/dist/views/layouts/main.hbs +1 -87
  69. package/dist/views/pages/databases.hbs +1 -233
  70. package/dist/views/pages/designer.hbs +1 -89
  71. package/dist/views/pages/erd.hbs +1 -1241
  72. package/dist/views/pages/error.hbs +1 -10
  73. package/dist/views/pages/form.hbs +1 -59
  74. package/dist/views/pages/home.hbs +1 -220
  75. package/dist/views/pages/info.hbs +1 -107
  76. package/dist/views/pages/login.hbs +1 -98
  77. package/dist/views/pages/query.hbs +1 -129
  78. package/dist/views/pages/schema.hbs +1 -298
  79. package/dist/views/pages/seed-select.hbs +251 -0
  80. package/dist/views/pages/seed.hbs +256 -414
  81. package/dist/views/pages/table.hbs +1 -327
  82. package/dist/views/partials/navbar.hbs +1 -83
  83. package/dist/views/partials/sidebar.hbs +1 -129
  84. package/docs/API.md +149 -149
  85. package/docs/EXAMPLES.md +579 -579
  86. package/docs/SECURITY.md +265 -265
  87. package/package.json +79 -79
@@ -1,314 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildErGraph = buildErGraph;
4
- exports.resolveErChain = resolveErChain;
5
- exports.topologicalSort = topologicalSort;
3
+ exports.topologicalSort = exports.resolveErChain = exports.buildErGraph = exports.buildChainInsertSql = void 0;
6
4
  exports.getErChainConfig = getErChainConfig;
7
5
  exports.generateChainRows = generateChainRows;
8
- exports.buildChainInsertSql = buildChainInsertSql;
9
6
  exports.executeChainInsert = executeChainInsert;
10
7
  const generator_1 = require("../sql/generator");
11
- const types_1 = require("./types");
12
- const detector_1 = require("./detector");
13
- const strategies_1 = require("./strategies");
14
- async function sampleFkValues(db, fk) {
15
- if (!fk)
16
- return [];
17
- try {
18
- const ref = await db.getTableInfo(fk.table);
19
- if (!ref.success || !ref.data || !ref.data.columns.length)
20
- return [];
21
- const refCol = fk.to || ref.data.primaryKey[0] || ref.data.columns[0].name;
22
- if (!refCol)
23
- return [];
24
- const r = await db.all(`SELECT DISTINCT ${(0, generator_1.quoteIdentifier)(refCol)} AS v FROM ${(0, generator_1.quoteIdentifier)(fk.table)} WHERE ${(0, generator_1.quoteIdentifier)(refCol)} IS NOT NULL LIMIT 1000`);
25
- return (r.data ?? []).map((row) => row.v).filter((v) => v != null);
26
- }
27
- catch {
28
- return [];
29
- }
30
- }
31
- function stringifyKey(v) {
32
- if (v instanceof Uint8Array || Buffer.isBuffer(v))
33
- return `buf:${Buffer.from(v).toString('hex')}`;
34
- return `${typeof v}:${String(v)}`;
35
- }
36
- function orderedValue(value, base, dir, orEqual) {
37
- const v = value;
38
- const b = base;
39
- if (typeof v === 'string' && typeof b === 'string') {
40
- const tv = Date.parse(v);
41
- const tb = Date.parse(b);
42
- if (Number.isFinite(tv) && Number.isFinite(tb)) {
43
- if (dir === 'after') {
44
- if (orEqual ? tv < tb : tv <= tb)
45
- return new Date(tb + 1000).toISOString();
46
- }
47
- else if (orEqual ? tv > tb : tv >= tb) {
48
- return new Date(tb - 1000).toISOString();
49
- }
50
- return v;
51
- }
52
- if (/^\d{4}-\d{2}-\d{2}$/.test(v) && /^\d{4}-\d{2}-\d{2}$/.test(b)) {
53
- const dv = Date.parse(`${v}T00:00:00Z`);
54
- const db = Date.parse(`${b}T00:00:00Z`);
55
- if (dir === 'after') {
56
- if (orEqual ? dv < db : dv <= db)
57
- return new Date(db + 86400_000).toISOString().slice(0, 10);
58
- }
59
- else if (orEqual ? dv > db : dv >= db) {
60
- return new Date(db - 86400_000).toISOString().slice(0, 10);
61
- }
62
- return v;
63
- }
64
- }
65
- if (typeof v === 'number' && typeof b === 'number') {
66
- if (dir === 'after')
67
- return orEqual ? Math.max(v, b) : Math.max(v, b + 1);
68
- return orEqual ? Math.min(v, b) : Math.min(v, b - 1);
69
- }
70
- return v;
71
- }
72
- function fitStringLength(val, len) {
73
- if (!len || typeof val !== 'string')
74
- return val;
75
- let v = val;
76
- if (len.exact != null) {
77
- if (v.length > len.exact)
78
- v = v.slice(0, len.exact);
79
- const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
80
- while (v.length < len.exact) {
81
- v += chars[Math.floor(Math.random() * chars.length)];
82
- }
83
- return v;
84
- }
85
- if (len.max != null && v.length > len.max) {
86
- v = v.slice(0, len.max);
87
- }
88
- if (len.min != null && v.length < len.min) {
89
- const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
90
- while (v.length < len.min) {
91
- v += chars[Math.floor(Math.random() * chars.length)];
92
- }
93
- }
94
- return v;
95
- }
96
- function applyOrdering(fields, ordering) {
97
- if (!ordering.size)
98
- return;
99
- const byCol = new Map(fields.map((f) => [f.column, f]));
100
- for (const f of fields) {
101
- const rel = ordering.get(f.column);
102
- if (!rel)
103
- continue;
104
- if (rel.after && f.value != null) {
105
- const other = byCol.get(rel.after.column);
106
- if (other && other.value != null)
107
- f.value = orderedValue(f.value, other.value, 'after', rel.after.orEqual);
108
- }
109
- if (rel.before && f.value != null) {
110
- const other = byCol.get(rel.before.column);
111
- if (other && other.value != null)
112
- f.value = orderedValue(f.value, other.value, 'before', rel.before.orEqual);
113
- }
114
- }
115
- }
116
- function buildErGraph(tables) {
117
- const graph = new Map();
118
- for (const info of tables) {
119
- const columns = (0, detector_1.buildColumnConfigs)(info);
120
- const colMap = new Map(info.columns.map((c) => [c.name, c]));
121
- const parents = [];
122
- let hasSelfRef = false;
123
- for (const fk of info.foreignKeys) {
124
- const toCol = fk.to || (tables.find((t) => t.table === fk.table)?.primaryKey[0] ?? 'id');
125
- if (fk.table === info.table) {
126
- hasSelfRef = true;
127
- }
128
- const c = colMap.get(fk.from);
129
- const notnull = Boolean(c?.notnull && !c?.pk);
130
- parents.push({ table: fk.table, from: fk.from, to: toCol, notnull });
131
- }
132
- graph.set(info.table, {
133
- name: info.table,
134
- info,
135
- columns,
136
- parents,
137
- children: [],
138
- hasSelfRef,
139
- depth: 0,
140
- });
141
- }
142
- for (const node of graph.values()) {
143
- for (const p of node.parents) {
144
- if (p.table !== node.name && graph.has(p.table)) {
145
- graph.get(p.table).children.push({ table: node.name, from: p.from, to: p.to, notnull: p.notnull });
146
- }
147
- }
148
- }
149
- return graph;
150
- }
151
- function resolveErChain(graph, rootTable, scope = 'chain') {
152
- if (scope === 'all') {
153
- return Array.from(graph.values());
154
- }
155
- const root = graph.get(rootTable);
156
- if (!root)
157
- return [];
158
- if (scope === 'single') {
159
- return [root];
160
- }
161
- const selected = new Set();
162
- if (scope === 'ancestors') {
163
- const queue = [rootTable];
164
- selected.add(rootTable);
165
- while (queue.length > 0) {
166
- const current = queue.shift();
167
- const node = graph.get(current);
168
- if (!node)
169
- continue;
170
- for (const p of node.parents) {
171
- if (p.table !== current && graph.has(p.table) && !selected.has(p.table)) {
172
- selected.add(p.table);
173
- queue.push(p.table);
174
- }
175
- }
176
- }
177
- }
178
- else if (scope === 'descendants') {
179
- const queue = [rootTable];
180
- selected.add(rootTable);
181
- while (queue.length > 0) {
182
- const current = queue.shift();
183
- const node = graph.get(current);
184
- if (!node)
185
- continue;
186
- for (const c of node.children) {
187
- if (c.table !== current && graph.has(c.table) && !selected.has(c.table)) {
188
- selected.add(c.table);
189
- queue.push(c.table);
190
- }
191
- }
192
- }
193
- }
194
- else {
195
- const queue = [rootTable];
196
- selected.add(rootTable);
197
- while (queue.length > 0) {
198
- const current = queue.shift();
199
- const node = graph.get(current);
200
- if (!node)
201
- continue;
202
- for (const p of node.parents) {
203
- if (p.table !== current && graph.has(p.table) && !selected.has(p.table)) {
204
- selected.add(p.table);
205
- queue.push(p.table);
206
- }
207
- }
208
- for (const c of node.children) {
209
- if (c.table !== current && graph.has(c.table) && !selected.has(c.table)) {
210
- selected.add(c.table);
211
- queue.push(c.table);
212
- }
213
- }
214
- }
215
- }
216
- return Array.from(selected).map((name) => graph.get(name));
217
- }
218
- function topologicalSort(nodes) {
219
- const activeNames = new Set(nodes.map((n) => n.name));
220
- const nodeMap = new Map(nodes.map((n) => [n.name, n]));
221
- const hardInDegree = new Map();
222
- const totalInDegree = new Map();
223
- const hardForwardEdges = new Map();
224
- const softForwardEdges = new Map();
225
- for (const n of nodes) {
226
- hardInDegree.set(n.name, 0);
227
- totalInDegree.set(n.name, 0);
228
- hardForwardEdges.set(n.name, new Set());
229
- softForwardEdges.set(n.name, new Set());
230
- }
231
- for (const n of nodes) {
232
- for (const p of n.parents) {
233
- if (p.table !== n.name && activeNames.has(p.table)) {
234
- if (p.notnull) {
235
- if (!hardForwardEdges.get(p.table).has(n.name)) {
236
- hardForwardEdges.get(p.table).add(n.name);
237
- hardInDegree.set(n.name, (hardInDegree.get(n.name) ?? 0) + 1);
238
- }
239
- }
240
- else {
241
- if (!softForwardEdges.get(p.table).has(n.name)) {
242
- softForwardEdges.get(p.table).add(n.name);
243
- }
244
- }
245
- totalInDegree.set(n.name, (totalInDegree.get(n.name) ?? 0) + 1);
246
- }
247
- }
248
- }
249
- const queue = [];
250
- const visited = new Set();
251
- for (const [name, deg] of hardInDegree.entries()) {
252
- if (deg === 0) {
253
- queue.push(name);
254
- visited.add(name);
255
- }
256
- }
257
- const sortedNames = [];
258
- const depths = new Map();
259
- for (const name of queue)
260
- depths.set(name, 0);
261
- let cycleDetected = false;
262
- const cycleTables = [];
263
- while (visited.size < nodes.length) {
264
- while (queue.length > 0) {
265
- const curr = queue.shift();
266
- sortedNames.push(curr);
267
- const currDepth = depths.get(curr) ?? 0;
268
- for (const child of hardForwardEdges.get(curr) ?? []) {
269
- const newDeg = (hardInDegree.get(child) ?? 1) - 1;
270
- hardInDegree.set(child, newDeg);
271
- depths.set(child, Math.max(depths.get(child) ?? 0, currDepth + 1));
272
- if (newDeg === 0 && !visited.has(child)) {
273
- visited.add(child);
274
- queue.push(child);
275
- }
276
- }
277
- for (const child of softForwardEdges.get(curr) ?? []) {
278
- depths.set(child, Math.max(depths.get(child) ?? 0, currDepth + 1));
279
- }
280
- }
281
- if (visited.size < nodes.length) {
282
- cycleDetected = true;
283
- let bestCandidate = '';
284
- let lowestDeg = Infinity;
285
- for (const n of nodes) {
286
- if (!visited.has(n.name)) {
287
- const deg = hardInDegree.get(n.name) ?? 0;
288
- if (deg < lowestDeg) {
289
- lowestDeg = deg;
290
- bestCandidate = n.name;
291
- }
292
- }
293
- }
294
- if (bestCandidate) {
295
- cycleTables.push(bestCandidate);
296
- visited.add(bestCandidate);
297
- depths.set(bestCandidate, (depths.get(bestCandidate) ?? 1) + 1);
298
- queue.push(bestCandidate);
299
- }
300
- else {
301
- break;
302
- }
303
- }
304
- }
305
- const sorted = sortedNames.map((name) => {
306
- const node = { ...nodeMap.get(name) };
307
- node.depth = depths.get(name) ?? 0;
308
- return node;
309
- });
310
- return { sorted, cycleDetected, cycleTables };
311
- }
8
+ const engine_1 = require("./engine");
9
+ Object.defineProperty(exports, "buildChainInsertSql", { enumerable: true, get: function () { return engine_1.buildChainInsertSql; } });
10
+ const schema_graph_1 = require("./schema-graph");
11
+ const buildErGraph = (tables) => (0, schema_graph_1.buildSchemaGraph)(tables).nodes;
12
+ exports.buildErGraph = buildErGraph;
13
+ const resolveErChain = (graph, rootTable, scope = 'chain') => (0, schema_graph_1.resolveScopeTables)({ nodes: graph, relationships: [] }, rootTable, scope);
14
+ exports.resolveErChain = resolveErChain;
15
+ const topologicalSort = (nodes) => (0, schema_graph_1.sortTopologically)(nodes);
16
+ exports.topologicalSort = topologicalSort;
312
17
  async function getErChainConfig(db, rootTable, scope = 'chain') {
313
18
  const tableListRes = await db.listTables();
314
19
  const tableNames = (tableListRes.data ?? [])
@@ -320,9 +25,9 @@ async function getErChainConfig(db, rootTable, scope = 'chain') {
320
25
  if (info.success && info.data)
321
26
  tables.push(info.data);
322
27
  }
323
- const graph = buildErGraph(tables);
324
- const chainNodes = resolveErChain(graph, rootTable, scope);
325
- const { sorted, cycleDetected, cycleTables } = topologicalSort(chainNodes);
28
+ const graph = (0, schema_graph_1.buildSchemaGraph)(tables);
29
+ const chainNodes = (0, schema_graph_1.resolveScopeTables)(graph, rootTable, scope);
30
+ const { sorted, cycleDetected, cycleTables } = (0, schema_graph_1.sortTopologically)(chainNodes);
326
31
  const tableNodes = [];
327
32
  for (let i = 0; i < sorted.length; i++) {
328
33
  const node = sorted[i];
@@ -332,15 +37,8 @@ async function getErChainConfig(db, rootTable, scope = 'chain') {
332
37
  if (countRes.success && typeof countRes.data === 'number')
333
38
  rowCount = countRes.data;
334
39
  }
335
- catch {
336
- }
337
- let suggestedCount = 10;
338
- if (node.depth === 0)
339
- suggestedCount = 5;
340
- else if (node.depth === 1)
341
- suggestedCount = 15;
342
- else
343
- suggestedCount = 25;
40
+ catch { }
41
+ const suggestedCount = node.depth === 0 ? 5 : node.depth === 1 ? 15 : 25;
344
42
  tableNodes.push({
345
43
  name: node.name,
346
44
  order: i + 1,
@@ -352,6 +50,8 @@ async function getErChainConfig(db, rootTable, scope = 'chain') {
352
50
  rowCount,
353
51
  isRoot: node.name === rootTable,
354
52
  hasSelfRef: node.hasSelfRef,
53
+ isJunction: node.isJunction,
54
+ mode: 'generate',
355
55
  });
356
56
  }
357
57
  return {
@@ -361,314 +61,38 @@ async function getErChainConfig(db, rootTable, scope = 'chain') {
361
61
  cycleDetected,
362
62
  cycleTables,
363
63
  maxTotalRows: 10000,
64
+ relationships: graph.relationships,
364
65
  };
365
66
  }
366
- async function generateChainRows(db, chainConfig, customPlans = {}, customCounts = {}, truncate = false) {
367
- const warnings = [];
368
- const executionOrder = chainConfig.tables.map((t) => t.name);
369
- const relationalPool = new Map();
370
- const tableResults = {};
371
- let totalRows = 0;
67
+ async function generateChainRows(db, chainConfig, customPlans = {}, customCounts = {}, truncate = false, modes = {}, seed) {
68
+ const planTables = {};
372
69
  for (const tableNode of chainConfig.tables) {
373
70
  const tableName = tableNode.name;
374
- const tableInfoRes = await db.getTableInfo(tableName);
375
- if (!tableInfoRes.success || !tableInfoRes.data) {
376
- warnings.push(`Could not load table metadata for "${tableName}".`);
377
- continue;
378
- }
379
- const info = tableInfoRes.data;
380
- let count = Math.max(1, Math.min(2000, customCounts[tableName] ?? tableNode.suggestedCount ?? 10));
381
- const tablePlan = customPlans[tableName] ?? {};
382
- const configs = tableNode.columns;
383
- const fkByColumn = new Map();
384
- for (const fk of info.foreignKeys)
385
- fkByColumn.set(fk.from, fk);
386
- const uniqueCols = new Set();
387
- for (const cfg of configs)
388
- if (cfg.unique)
389
- uniqueCols.add(cfg.name);
390
- const compositeUniques = [];
391
- for (const ix of info.indexes ?? []) {
392
- if (ix.unique && ix.columns.length === 1 && ix.columns[0])
393
- uniqueCols.add(ix.columns[0]);
394
- if (ix.unique && ix.columns.length > 1)
395
- compositeUniques.push(ix.columns);
396
- }
397
- if (info.sql) {
398
- const uqRe = /UNIQUE\s*\(\s*([^)]+)\s*\)/gi;
399
- let uqM;
400
- while ((uqM = uqRe.exec(info.sql))) {
401
- const rawCols = uqM[1].split(',').map((s) => s.trim().replace(/[`"\[\]]/g, ''));
402
- if (rawCols.length === 1 && rawCols[0]) {
403
- uniqueCols.add(rawCols[0]);
404
- }
405
- else if (rawCols.length > 1 && !compositeUniques.some((cu) => cu.join(',') === rawCols.join(','))) {
406
- compositeUniques.push(rawCols);
407
- }
408
- }
409
- }
410
- const compositeSeen = new Map();
411
- for (const cu of compositeUniques) {
412
- compositeSeen.set(cu.join('::'), new Set());
413
- }
414
- const checkInfo = (0, detector_1.parseChecks)(info.sql ?? null);
415
- const prepared = [];
416
- const fkPools = new Map();
417
- const fkOmit = new Set();
418
- for (const col of info.columns) {
419
- const cfg = configs.find((c) => c.name === col.name);
420
- const raw = tablePlan[col.name];
421
- const p = raw && raw.strategy ? raw : (cfg?.defaultPlan ?? { strategy: 'skip' });
422
- if (p.strategy === 'list' && (!p.values || !p.values.length)) {
423
- const allowed = (0, detector_1.findInMap)(checkInfo.columnAllowed, col.name);
424
- if (allowed && allowed.length)
425
- p.values = [...allowed];
426
- }
427
- if (p.strategy === 'fk') {
428
- const fk = fkByColumn.get(col.name);
429
- const refTable = fk?.table ?? '';
430
- const refCol = fk?.to || 'id';
431
- const poolKey = `${refTable}.${refCol}`;
432
- let pool = relationalPool.get(poolKey) ?? [];
433
- if (!pool.length) {
434
- for (const [k, v] of relationalPool.entries()) {
435
- if (k.toLowerCase() === poolKey.toLowerCase() && v.length) {
436
- pool = v;
437
- break;
438
- }
439
- }
440
- }
441
- if (!pool.length) {
442
- pool = await sampleFkValues(db, fk ?? null);
443
- }
444
- fkPools.set(col.name, pool);
445
- if (!pool.length) {
446
- if (col.notnull && !col.pk) {
447
- pool = [1];
448
- fkPools.set(col.name, pool);
449
- warnings.push(`Table "${tableName}" Column "${col.name}" is a NOT NULL foreign key to "${refTable}". Fallback ID 1 was used.`);
450
- }
451
- else {
452
- warnings.push(`Table "${tableName}" Column "${col.name}": "${refTable}" has no rows; NULL will be used.`);
453
- }
454
- }
455
- }
456
- prepared.push({ col, plan: p });
457
- }
458
- const rows = [];
459
- const previewRows = [];
460
- const uniqueSeen = new Map();
461
- if (!truncate) {
462
- for (const col of uniqueCols) {
463
- try {
464
- const existing = await db.all(`SELECT ${(0, generator_1.quoteIdentifier)(col)} AS v FROM ${(0, generator_1.quoteIdentifier)(tableName)} WHERE ${(0, generator_1.quoteIdentifier)(col)} IS NOT NULL LIMIT 10000`);
465
- const seen = new Set();
466
- for (const r of (existing.data ?? [])) {
467
- if (r.v != null)
468
- seen.add(stringifyKey(r.v));
469
- }
470
- uniqueSeen.set(col, seen);
471
- }
472
- catch { }
473
- }
474
- }
475
- const tableGeneratedPkVals = {};
476
- let pkStartOffset = 0;
477
- for (const col of info.columns) {
478
- if (col.pk && (col.type || '').toUpperCase().includes('INT')) {
479
- try {
480
- const maxRes = await db.all(`SELECT MAX(${(0, generator_1.quoteIdentifier)(col.name)}) AS mx FROM ${(0, generator_1.quoteIdentifier)(tableName)}`);
481
- const maxVal = (maxRes.data ?? [])[0]?.mx;
482
- if (typeof maxVal === 'number' && Number.isFinite(maxVal))
483
- pkStartOffset = maxVal;
484
- }
485
- catch {
486
- }
487
- }
488
- }
489
- const isUniqueCol = (name) => {
490
- const norm = name.toLowerCase().replace(/[`"\[\]]/g, '');
491
- for (const u of uniqueCols) {
492
- if (u.toLowerCase().replace(/[`"\[\]]/g, '') === norm)
493
- return true;
494
- }
495
- return false;
496
- };
497
- for (const { col, plan: p } of prepared) {
498
- if (p.strategy === 'fk' && (col.pk || isUniqueCol(col.name))) {
499
- const pool = fkPools.get(col.name);
500
- if (pool && pool.length > 0 && pool.length < count) {
501
- count = pool.length;
502
- }
503
- }
504
- }
505
- if (compositeUniques.length > 0) {
506
- for (const cu of compositeUniques) {
507
- const isAllFk = cu.every((colName) => prepared.some((p) => (p.col.name === colName || p.col.name.replace(/[`"\[\]]/g, '') === colName.replace(/[`"\[\]]/g, '')) && p.plan.strategy === 'fk'));
508
- if (isAllFk) {
509
- const maxCombos = cu.reduce((acc, colName) => {
510
- const pool = fkPools.get(colName) ?? [];
511
- return acc * Math.max(1, pool.length);
512
- }, 1);
513
- if (maxCombos > 0 && maxCombos < count) {
514
- count = maxCombos;
515
- }
516
- }
517
- }
518
- }
519
- for (let i = 0; i < count; i++) {
520
- let fields = [];
521
- let previewRow = {};
522
- let rowValid = false;
523
- let attempt = 0;
524
- while (!rowValid && attempt < 30) {
525
- fields = [];
526
- previewRow = {};
527
- for (const { col, plan: p } of prepared) {
528
- if (p.strategy === 'fk' && fkOmit.has(col.name)) {
529
- previewRow[col.name] = null;
530
- continue;
531
- }
532
- const isSelfFk = fkByColumn.get(col.name)?.table === tableName;
533
- let pool = fkPools.get(col.name);
534
- if (isSelfFk) {
535
- const existingSelfIds = tableGeneratedPkVals[fkByColumn.get(col.name)?.to || info.primaryKey[0] || 'id'] ?? [];
536
- if (i === 0 || existingSelfIds.length === 0 || (Math.random() < 0.35 && !col.notnull)) {
537
- pool = [];
538
- }
539
- else {
540
- pool = existingSelfIds;
541
- }
542
- }
543
- let v = (0, strategies_1.generateOne)(col, p, pool, i + attempt * 50);
544
- if (v === types_1.SKIP && col.pk && (col.type || '').toUpperCase().includes('INT')) {
545
- v = pkStartOffset + i + 1;
546
- }
547
- if (v === types_1.SKIP) {
548
- previewRow[col.name] = '(default)';
549
- continue;
550
- }
551
- if (isUniqueCol(col.name)) {
552
- const seen = uniqueSeen.get(col.name) ?? new Set();
553
- if (p.strategy === 'fk') {
554
- const unused = (pool ?? []).filter((x) => !seen.has(stringifyKey(x)));
555
- if (unused.length > 0) {
556
- v = unused[0];
557
- }
558
- else {
559
- rowValid = false;
560
- break;
561
- }
562
- }
563
- else {
564
- let singleAtt = 0;
565
- while (seen.has(stringifyKey(v)) && singleAtt < 100) {
566
- v = (0, strategies_1.generateOne)(col, p, pool, i + attempt * 50 + singleAtt * 100);
567
- singleAtt += 1;
568
- }
569
- if (seen.has(stringifyKey(v))) {
570
- rowValid = false;
571
- break;
572
- }
573
- }
574
- }
575
- v = fitStringLength(v, (0, detector_1.findInMap)(checkInfo.columnLength, col.name));
576
- fields.push({ column: col.name, value: v });
577
- previewRow[col.name] = v;
578
- }
579
- applyOrdering(fields, checkInfo.ordering);
580
- for (const f of fields) {
581
- previewRow[f.column] = f.value;
582
- }
583
- let compositeCollision = false;
584
- if (compositeUniques.length > 0) {
585
- const fieldMap = new Map(fields.map((f) => [f.column, f.value]));
586
- for (const cu of compositeUniques) {
587
- const key = cu.join('::');
588
- const compositeVal = cu.map((colName) => stringifyKey(fieldMap.get(colName))).join('|');
589
- const seenSet = compositeSeen.get(key) ?? new Set();
590
- if (seenSet.has(compositeVal)) {
591
- compositeCollision = true;
592
- break;
593
- }
594
- }
595
- }
596
- if (!compositeCollision) {
597
- rowValid = true;
598
- if (compositeUniques.length > 0) {
599
- const fieldMap = new Map(fields.map((f) => [f.column, f.value]));
600
- for (const cu of compositeUniques) {
601
- const key = cu.join('::');
602
- const compositeVal = cu.map((colName) => stringifyKey(fieldMap.get(colName))).join('|');
603
- const seenSet = compositeSeen.get(key) ?? new Set();
604
- seenSet.add(compositeVal);
605
- compositeSeen.set(key, seenSet);
606
- }
607
- }
608
- }
609
- attempt += 1;
610
- }
611
- if (!rowValid) {
612
- break;
613
- }
614
- for (const f of fields) {
615
- if (isUniqueCol(f.column)) {
616
- const seen = uniqueSeen.get(f.column) ?? new Set();
617
- seen.add(stringifyKey(f.value));
618
- uniqueSeen.set(f.column, seen);
619
- }
620
- const poolKey = `${tableName}.${f.column}`;
621
- const cur = relationalPool.get(poolKey) ?? [];
622
- cur.push(f.value);
623
- relationalPool.set(poolKey, cur);
624
- const selfCur = tableGeneratedPkVals[f.column] ?? [];
625
- selfCur.push(f.value);
626
- tableGeneratedPkVals[f.column] = selfCur;
627
- }
628
- rows.push(fields);
629
- if (previewRows.length < 50) {
630
- previewRows.push(previewRow);
631
- }
632
- }
633
- tableResults[tableName] = {
634
- count: rows.length,
71
+ const mode = modes[tableName] || 'generate';
72
+ const rows = customCounts[tableName] ?? tableNode.suggestedCount ?? 10;
73
+ const columns = {};
74
+ for (const col of tableNode.columns) {
75
+ const raw = customPlans[tableName]?.[col.name];
76
+ columns[col.name] = raw && raw.strategy ? raw : col.defaultPlan;
77
+ }
78
+ planTables[tableName] = {
79
+ mode,
635
80
  rows,
636
- previewRows,
637
- warnings,
81
+ columns,
82
+ truncate,
638
83
  };
639
- totalRows += rows.length;
640
84
  }
641
- const sql = buildChainInsertSql(executionOrder, tableResults);
642
- return {
85
+ const generationPlan = {
643
86
  rootTable: chainConfig.rootTable,
644
87
  scope: chainConfig.scope,
645
- executionOrder,
646
- tableResults,
647
- totalRows,
648
- sql,
649
- warnings,
88
+ tables: planTables,
89
+ relationships: chainConfig.relationships,
90
+ options: {
91
+ seed: seed ?? null,
92
+ truncateAll: truncate,
93
+ },
650
94
  };
651
- }
652
- function buildChainInsertSql(executionOrder, tableResults) {
653
- const sqlParts = ['-- AdminDB ER Chain Seed Script', '-- Order of execution: ' + executionOrder.join(' -> '), 'BEGIN TRANSACTION;\n'];
654
- for (const table of executionOrder) {
655
- const res = tableResults[table];
656
- if (!res || !res.rows.length)
657
- continue;
658
- sqlParts.push(`-- --------------------------------------------------------`);
659
- sqlParts.push(`-- Table: ${table} (${res.rows.length} rows)`);
660
- sqlParts.push(`-- --------------------------------------------------------`);
661
- for (const fields of res.rows) {
662
- if (!fields.length)
663
- continue;
664
- const cols = fields.map((f) => (0, generator_1.quoteIdentifier)(f.column)).join(', ');
665
- const vals = fields.map((f) => (0, generator_1.sqlValue)(f.value)).join(', ');
666
- sqlParts.push(`INSERT INTO ${(0, generator_1.quoteIdentifier)(table)} (${cols}) VALUES (${vals});`);
667
- }
668
- sqlParts.push('');
669
- }
670
- sqlParts.push('COMMIT;');
671
- return sqlParts.join('\n');
95
+ return engine_1.SeedEngine.executePlan(db, generationPlan);
672
96
  }
673
97
  async function executeChainInsert(db, chainResult, truncate = false) {
674
98
  const t0 = Date.now();
@@ -697,11 +121,10 @@ async function executeChainInsert(db, chainResult, truncate = false) {
697
121
  inserted[table] = count;
698
122
  totalInserted += count;
699
123
  }
700
- const elapsedMs = Date.now() - t0;
701
124
  return {
702
125
  inserted,
703
126
  totalInserted,
704
- elapsedMs,
127
+ elapsedMs: Date.now() - t0,
705
128
  warnings: chainResult.warnings,
706
129
  };
707
130
  }