db-model-router 1.0.3 → 1.0.5

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 (97) hide show
  1. package/README.md +283 -25
  2. package/TODO.md +14 -0
  3. package/dbmr.schema.json +333 -0
  4. package/demo/.dockerignore +7 -0
  5. package/demo/.env.example +13 -0
  6. package/demo/Dockerfile +20 -0
  7. package/demo/app.js +37 -0
  8. package/demo/commons/add_migration.js +43 -0
  9. package/demo/commons/db.js +17 -0
  10. package/demo/commons/migrate.js +65 -0
  11. package/demo/commons/security.js +30 -0
  12. package/demo/commons/session.js +13 -0
  13. package/demo/dbmr.schema.json +362 -0
  14. package/demo/docs/llm.md +197 -0
  15. package/demo/llms.txt +70 -0
  16. package/demo/middleware/logger.js +67 -0
  17. package/demo/migrations/20260430155808_create_migrations_table.sql +6 -0
  18. package/demo/migrations/20260430155809_create_tables.sql +207 -0
  19. package/demo/models/addresses.js +22 -0
  20. package/demo/models/cart_items.js +18 -0
  21. package/demo/models/carts.js +16 -0
  22. package/demo/models/categories.js +20 -0
  23. package/demo/models/coupons.js +23 -0
  24. package/demo/models/order_items.js +21 -0
  25. package/demo/models/orders.js +25 -0
  26. package/demo/models/payments.js +21 -0
  27. package/demo/models/product_images.js +18 -0
  28. package/demo/models/product_reviews.js +20 -0
  29. package/demo/models/product_variants.js +20 -0
  30. package/demo/models/products.js +30 -0
  31. package/demo/models/shipments.js +19 -0
  32. package/demo/models/users.js +19 -0
  33. package/demo/models/wishlists.js +15 -0
  34. package/demo/openapi.json +5872 -0
  35. package/demo/package-lock.json +2810 -0
  36. package/demo/package.json +34 -0
  37. package/demo/routes/addresses.js +6 -0
  38. package/demo/routes/carts/cart_items.js +7 -0
  39. package/demo/routes/carts.js +6 -0
  40. package/demo/routes/categories.js +6 -0
  41. package/demo/routes/coupons.js +6 -0
  42. package/demo/routes/docs.js +18 -0
  43. package/demo/routes/health.js +35 -0
  44. package/demo/routes/index.js +39 -0
  45. package/demo/routes/orders/order_items.js +7 -0
  46. package/demo/routes/orders/payments.js +7 -0
  47. package/demo/routes/orders/shipments.js +7 -0
  48. package/demo/routes/orders.js +6 -0
  49. package/demo/routes/products/product_images.js +7 -0
  50. package/demo/routes/products/product_reviews.js +7 -0
  51. package/demo/routes/products/product_variants.js +7 -0
  52. package/demo/routes/products.js +6 -0
  53. package/demo/routes/users.js +6 -0
  54. package/demo/routes/wishlists.js +6 -0
  55. package/docker-compose.yml +1 -1
  56. package/package.json +16 -7
  57. package/scripts/demo-create.js +47 -0
  58. package/skill/SKILL.md +464 -0
  59. package/skill/references/cockroachdb.md +49 -0
  60. package/skill/references/dynamodb.md +53 -0
  61. package/skill/references/mongodb.md +56 -0
  62. package/skill/references/mssql.md +55 -0
  63. package/skill/references/oracle.md +52 -0
  64. package/skill/references/postgres.md +50 -0
  65. package/skill/references/redis.md +53 -0
  66. package/skill/references/sqlite3.md +43 -0
  67. package/src/cli/commands/generate.js +58 -17
  68. package/src/cli/commands/help.js +185 -0
  69. package/src/cli/commands/init.js +42 -14
  70. package/src/cli/commands/inspect.js +21 -3
  71. package/src/cli/diff-engine.js +52 -22
  72. package/src/cli/generate-docs-route.js +31 -0
  73. package/src/cli/generate-migration.js +356 -0
  74. package/src/cli/generate-model.js +5 -4
  75. package/src/cli/generate-route.js +79 -45
  76. package/src/cli/init/dependencies.js +17 -5
  77. package/src/cli/init/generators.js +1073 -64
  78. package/src/cli/init/prompt.js +37 -5
  79. package/src/cli/init.js +148 -25
  80. package/src/cli/main.js +90 -10
  81. package/src/cockroachdb/db.js +90 -59
  82. package/src/commons/route.js +20 -20
  83. package/src/commons/validator.js +58 -1
  84. package/src/dynamodb/db.js +50 -27
  85. package/src/index.js +2 -0
  86. package/src/mongodb/db.js +1 -0
  87. package/src/mssql/db.js +89 -61
  88. package/src/mysql/db.js +1 -0
  89. package/src/oracle/db.js +1 -0
  90. package/src/postgres/db.js +61 -41
  91. package/src/redis/db.js +1 -0
  92. package/src/schema/schema-parser.js +43 -1
  93. package/src/schema/schema-printer.js +8 -5
  94. package/src/schema/schema-to-meta.js +4 -0
  95. package/src/schema/schema-validator.js +20 -1
  96. package/src/sqlite3/db.js +1 -0
  97. package/docs/SKILL.md +0 -374
@@ -25,10 +25,12 @@ function safeVarName(name) {
25
25
  */
26
26
  function generateRouteFile(tableName, modelsRelPath) {
27
27
  const varName = safeVarName(tableName);
28
- return `const { route } = require("db-model-router");
29
- const ${varName} = require("${modelsRelPath}/${tableName}");
28
+ return `import dbModelRouter from "db-model-router";
29
+ import ${varName} from "${modelsRelPath}/${tableName}.js";
30
30
 
31
- module.exports = route(${varName});
31
+ const { route } = dbModelRouter;
32
+
33
+ export default route(${varName});
32
34
  `;
33
35
  }
34
36
 
@@ -43,20 +45,29 @@ function generateChildRouteFile(
43
45
  modelsRelPath,
44
46
  ) {
45
47
  const varName = safeVarName(childTable);
46
- return `const { route } = require("db-model-router");
47
- const ${varName} = require("${modelsRelPath}/${childTable}");
48
+ return `import dbModelRouter from "db-model-router";
49
+ import ${varName} from "${modelsRelPath}/${childTable}.js";
50
+
51
+ const { route } = dbModelRouter;
48
52
 
49
53
  // Child route: scoped by parent ${parentTable} via ${fkColumn}
50
- module.exports = route(${varName}, { ${fkColumn}: "params.${fkColumn}" });
54
+ export default route(${varName}, { ${fkColumn}: "params.${fkColumn}" });
51
55
  `;
52
56
  }
53
57
 
54
58
  /**
55
59
  * Generate the routes index file that mounts all routes on an express Router.
56
60
  * Supports parent-child nesting: parent/:pk/child
61
+ *
62
+ * Child routes are placed in subfolders: routes/<parent>/<child>.js
63
+ * Children are only mounted under their parent path (no duplicate top-level route).
64
+ *
65
+ * @param {string[]} tableNames
66
+ * @param {Array<{parent, child, foreignKey}>} relationships
67
+ * @param {{ includeDocs?: boolean }} [options]
57
68
  */
58
- function generateRoutesIndexFile(tableNames, relationships = []) {
59
- let imports = `let express;\ntry { express = require("ultimate-express"); } catch (_) { express = require("express"); }\nconst router = express.Router();\n\n`;
69
+ function generateRoutesIndexFile(tableNames, relationships = [], options = {}) {
70
+ let imports = `import express from "express";\n\nconst router = express.Router();\n\n`;
60
71
 
61
72
  // Collect child tables that are nested under parents
62
73
  const nestedChildren = new Set();
@@ -64,40 +75,45 @@ function generateRoutesIndexFile(tableNames, relationships = []) {
64
75
  nestedChildren.add(rel.child);
65
76
  }
66
77
 
78
+ // Import top-level routes only (not children)
67
79
  for (const table of tableNames) {
80
+ if (nestedChildren.has(table)) continue;
68
81
  const varName = safeVarName(table);
69
- imports += `const ${varName}Route = require("./${table}");\n`;
82
+ imports += `import ${varName}Route from "./${table}.js";\n`;
70
83
  }
71
- // Import child routes with _child suffix for nested ones
84
+
85
+ // Import child routes from subfolders
72
86
  for (const rel of relationships) {
73
87
  const varName = safeVarName(rel.child);
74
- imports += `const ${varName}ChildRoute = require("./${rel.child}_child_of_${rel.parent}");\n`;
88
+ imports += `import ${varName}ChildRoute from "./${rel.parent}/${rel.child}.js";\n`;
89
+ }
90
+
91
+ // Import docs route if openapi is generated
92
+ if (options.includeDocs) {
93
+ imports += `import docsRoute from "./docs.js";\n`;
75
94
  }
76
95
 
77
96
  imports += "\n";
78
97
 
79
- // Mount top-level routes (skip tables that are ONLY children)
98
+ // Mount docs route first
99
+ if (options.includeDocs) {
100
+ imports += `router.use("/docs", docsRoute);\n`;
101
+ }
102
+
103
+ // Mount top-level routes
80
104
  for (const table of tableNames) {
81
105
  if (nestedChildren.has(table)) continue;
82
106
  const varName = safeVarName(table);
83
107
  imports += `router.use("/${table}", ${varName}Route);\n`;
84
108
  }
85
109
 
86
- // Mount nested child routes under parent
110
+ // Mount child routes under parent path
87
111
  for (const rel of relationships) {
88
- const parentVar = safeVarName(rel.parent);
89
112
  const childVar = safeVarName(rel.child);
90
- // Find parent PK from model file name convention — use fkColumn without _id suffix as parent pk param
91
- imports += `router.use("/${rel.parent}/:${rel.fkColumn}/${rel.child}", ${childVar}ChildRoute);\n`;
92
- }
93
-
94
- // Also mount children as top-level for direct access
95
- for (const rel of relationships) {
96
- const varName = safeVarName(rel.child);
97
- imports += `router.use("/${rel.child}", ${varName}Route);\n`;
113
+ imports += `router.use("/${rel.parent}/:${rel.foreignKey}/${rel.child}", ${childVar}ChildRoute);\n`;
98
114
  }
99
115
 
100
- imports += "\nmodule.exports = router;\n";
116
+ imports += "\nexport default router;\n";
101
117
  return imports;
102
118
  }
103
119
 
@@ -114,13 +130,15 @@ function generateSimpleRoutesIndexFile(tableNames) {
114
130
  */
115
131
  function generateTestFile(tableName, pk) {
116
132
  const varName = safeVarName(tableName);
117
- return `const assert = require("assert");
118
- const express = require("express");
119
- const request = require("supertest");
120
- const { route } = require("db-model-router");
133
+ return `import assert from "assert";
134
+ import express from "express";
135
+ import request from "supertest";
136
+ import dbModelRouter from "db-model-router";
137
+
138
+ const { route } = dbModelRouter;
121
139
 
122
140
  // Adjust the path to your model file as needed
123
- const ${varName} = require("../models/${tableName}");
141
+ import ${varName} from "../models/${tableName}.js";
124
142
 
125
143
  function createApp() {
126
144
  const app = express();
@@ -220,12 +238,14 @@ describe("${tableName} routes", function () {
220
238
  */
221
239
  function generateChildTestFile(childTable, parentTable, fkColumn, pk) {
222
240
  const childVar = safeVarName(childTable);
223
- return `const assert = require("assert");
224
- const express = require("express");
225
- const request = require("supertest");
226
- const { route } = require("db-model-router");
241
+ return `import assert from "assert";
242
+ import express from "express";
243
+ import request from "supertest";
244
+ import dbModelRouter from "db-model-router";
245
+
246
+ const { route } = dbModelRouter;
227
247
 
228
- const ${childVar} = require("../models/${childTable}");
248
+ import ${childVar} from "../models/${childTable}.js";
229
249
 
230
250
  function createApp() {
231
251
  const app = express();
@@ -381,7 +401,7 @@ async function main() {
381
401
  const fkColumn = parent.replace(/s$/, "") + "_id";
382
402
  // Only add if both tables exist in our model set
383
403
  if (tableNames.includes(parent) && tableNames.includes(child)) {
384
- relationships.push({ parent, child, fkColumn });
404
+ relationships.push({ parent, child, foreignKey: fkColumn });
385
405
  }
386
406
  }
387
407
  }
@@ -392,23 +412,36 @@ async function main() {
392
412
  fs.mkdirSync(routesDir, { recursive: true });
393
413
  }
394
414
 
415
+ // Collect child tables to skip top-level route files
416
+ const nestedChildren = new Set();
417
+ for (const rel of relationships) {
418
+ nestedChildren.add(rel.child);
419
+ }
420
+
395
421
  for (const table of tableNames) {
422
+ if (nestedChildren.has(table)) continue;
396
423
  const filePath = path.join(routesDir, table + ".js");
397
424
  fs.writeFileSync(filePath, generateRouteFile(table, modelsRelPath));
398
425
  console.log(` Created ${filePath}`);
399
426
  }
400
427
 
401
- // Write child route files for parent-child relationships
428
+ // Write child route files in subfolders: routes/<parent>/<child>.js
402
429
  for (const rel of relationships) {
403
- const fileName = `${rel.child}_child_of_${rel.parent}.js`;
404
- const filePath = path.join(routesDir, fileName);
430
+ const parentDir = path.join(routesDir, rel.parent);
431
+ if (!fs.existsSync(parentDir)) {
432
+ fs.mkdirSync(parentDir, { recursive: true });
433
+ }
434
+ const filePath = path.join(parentDir, `${rel.child}.js`);
435
+ const childModelsRelPath = path
436
+ .relative(parentDir, path.resolve(modelsDir))
437
+ .replace(/\\/g, "/");
405
438
  fs.writeFileSync(
406
439
  filePath,
407
440
  generateChildRouteFile(
408
441
  rel.child,
409
442
  rel.parent,
410
- rel.fkColumn,
411
- modelsRelPath,
443
+ rel.foreignKey,
444
+ childModelsRelPath,
412
445
  ),
413
446
  );
414
447
  console.log(` Created ${filePath}`);
@@ -467,7 +500,7 @@ async function main() {
467
500
  console.log(` Created ${testPath}`);
468
501
  }
469
502
 
470
- // Generate child route test files
503
+ // Generate child route test files in subfolders
471
504
  for (const rel of relationships) {
472
505
  let pk = "id";
473
506
  const modelPath = path.join(modelsDir, rel.child + ".js");
@@ -478,13 +511,14 @@ async function main() {
478
511
  );
479
512
  if (meta && meta.primary_key) pk = meta.primary_key;
480
513
  }
481
- const testPath = path.join(
482
- testsDir,
483
- `${rel.child}_child_of_${rel.parent}.test.js`,
484
- );
514
+ const parentTestDir = path.join(testsDir, rel.parent);
515
+ if (!fs.existsSync(parentTestDir)) {
516
+ fs.mkdirSync(parentTestDir, { recursive: true });
517
+ }
518
+ const testPath = path.join(parentTestDir, `${rel.child}.test.js`);
485
519
  fs.writeFileSync(
486
520
  testPath,
487
- generateChildTestFile(rel.child, rel.parent, rel.fkColumn, pk),
521
+ generateChildTestFile(rel.child, rel.parent, rel.foreignKey, pk),
488
522
  );
489
523
  console.log(` Created ${testPath}`);
490
524
  }
@@ -5,6 +5,7 @@
5
5
  */
6
6
  const DRIVER_MAP = {
7
7
  mysql: ["mysql2"],
8
+ mariadb: ["mysql2"],
8
9
  postgres: ["pg"],
9
10
  sqlite3: ["better-sqlite3"],
10
11
  mongodb: ["mongodb"],
@@ -53,26 +54,37 @@ function collectDependencies(answers) {
53
54
  dependencies["helmet"] = "latest";
54
55
  }
55
56
  if (answers.logger) {
56
- dependencies["express-mung"] = "latest";
57
+ dependencies["winston"] = "latest";
58
+ if (answers.loki) {
59
+ dependencies["winston-loki"] = "latest";
60
+ }
57
61
  }
58
62
 
59
63
  // Dev dependencies
60
64
  devDependencies["nodemon"] = "latest";
61
65
 
66
+ // Swagger UI for API documentation
67
+ dependencies["swagger-ui-express"] = "latest";
68
+
62
69
  return { dependencies, devDependencies };
63
70
  }
64
71
 
65
72
  /**
66
- * Returns the 5 package.json scripts.
73
+ * Returns the package.json scripts.
74
+ * @param {string} [outputDir] - relative output directory for source files
67
75
  * @returns {Record<string, string>}
68
76
  */
69
- function getScripts() {
77
+ function getScripts(outputDir) {
78
+ const prefix = outputDir ? `${outputDir}/` : "";
70
79
  return {
71
80
  start: "node app.js",
72
81
  dev: "nodemon app.js",
73
82
  test: 'echo "Error: no test specified" && exit 1',
74
- migrate: "node migrate.js",
75
- add_migration: "node add_migration.js",
83
+ migrate: `node ${prefix}commons/migrate.js`,
84
+ add_migration: `node ${prefix}commons/add_migration.js`,
85
+ "docker:build": "docker build -t app .",
86
+ "docker:up": "docker compose up -d",
87
+ "docker:down": "docker compose down",
76
88
  };
77
89
  }
78
90