db-model-router 1.0.5 → 1.0.7

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 (136) hide show
  1. package/README.md +150 -11
  2. package/TODO.md +0 -14
  3. package/db-manager/.dbmanager.sqlite +0 -0
  4. package/db-manager/README.md +223 -0
  5. package/db-manager/adapter-proxy.js +361 -0
  6. package/db-manager/demo/cockroachdb.env +6 -0
  7. package/db-manager/demo/demo.sqlite +0 -0
  8. package/db-manager/demo/dynamodb.env +7 -0
  9. package/db-manager/demo/mongodb.env +4 -0
  10. package/db-manager/demo/mssql.env +6 -0
  11. package/db-manager/demo/mysql.env +6 -0
  12. package/db-manager/demo/oracle.env +6 -0
  13. package/db-manager/demo/postgres.env +6 -0
  14. package/db-manager/demo/redis.env +4 -0
  15. package/db-manager/demo/seeds/cockroachdb.sql +32 -0
  16. package/db-manager/demo/seeds/mssql.sql +32 -0
  17. package/db-manager/demo/seeds/mysql.sql +32 -0
  18. package/db-manager/demo/seeds/oracle.sql +43 -0
  19. package/db-manager/demo/seeds/postgres.sql +32 -0
  20. package/db-manager/demo/seeds/sqlite3.sql +32 -0
  21. package/db-manager/demo/sqlite3.env +2 -0
  22. package/db-manager/metadata-db.js +170 -0
  23. package/db-manager/public/.gitkeep +1 -0
  24. package/db-manager/public/css/style.css +1413 -0
  25. package/db-manager/public/js/app.js +1370 -0
  26. package/db-manager/routes/api.js +388 -0
  27. package/db-manager/routes/views.js +61 -0
  28. package/db-manager/server.js +39 -0
  29. package/db-manager/utils/build-filter-config.js +18 -0
  30. package/db-manager/utils/csv-export.js +59 -0
  31. package/db-manager/utils/export-filename.js +39 -0
  32. package/db-manager/utils/filter-tables.js +20 -0
  33. package/db-manager/utils/parse-filters.js +93 -0
  34. package/db-manager/utils/sort-state.js +35 -0
  35. package/db-manager/views/.gitkeep +1 -0
  36. package/db-manager/views/dashboard.ejs +53 -0
  37. package/db-manager/views/history.ejs +52 -0
  38. package/db-manager/views/index.ejs +35 -0
  39. package/db-manager/views/layout.ejs +31 -0
  40. package/db-manager/views/partials/data-panel.ejs +74 -0
  41. package/db-manager/views/partials/header.ejs +36 -0
  42. package/db-manager/views/partials/sidebar.ejs +30 -0
  43. package/db-manager/views/query.ejs +58 -0
  44. package/dbmr.schema.json +23 -45
  45. package/demo/.env.example +1 -0
  46. package/demo/app.js +3 -1
  47. package/demo/commons/db.js +11 -0
  48. package/demo/commons/migrate.js +3 -0
  49. package/demo/commons/modules.js +18 -0
  50. package/demo/commons/password.js +36 -0
  51. package/demo/commons/webhook.js +81 -0
  52. package/demo/dbmr.schema.json +22 -46
  53. package/demo/middleware/authenticate.js +14 -0
  54. package/demo/middleware/hasPermission.js +30 -0
  55. package/demo/middleware/tenantIsolation.js +17 -0
  56. package/demo/migrations/20260509170349_create_saas_tables.sql +69 -0
  57. package/demo/migrations/{20260430155809_create_tables.sql → 20260509170349_create_tables.sql} +11 -25
  58. package/demo/models/addresses.js +5 -3
  59. package/demo/models/cart_items.js +5 -3
  60. package/demo/models/carts.js +5 -3
  61. package/demo/models/categories.js +5 -3
  62. package/demo/models/coupons.js +5 -3
  63. package/demo/models/index.js +43 -0
  64. package/demo/models/order_items.js +4 -2
  65. package/demo/models/orders.js +5 -3
  66. package/demo/models/payments.js +5 -3
  67. package/demo/models/product_images.js +4 -2
  68. package/demo/models/product_reviews.js +5 -3
  69. package/demo/models/product_variants.js +5 -3
  70. package/demo/models/products.js +5 -3
  71. package/demo/models/role_permissions.js +17 -0
  72. package/demo/models/roles.js +17 -0
  73. package/demo/models/shipments.js +5 -3
  74. package/demo/models/tenants.js +18 -0
  75. package/demo/models/users.js +12 -8
  76. package/demo/models/webhook_logs.js +22 -0
  77. package/demo/models/webhooks.js +19 -0
  78. package/demo/models/wishlists.js +4 -2
  79. package/demo/openapi.json +1744 -616
  80. package/demo/package-lock.json +24 -24
  81. package/demo/package.json +9 -0
  82. package/demo/routes/{addresses.js → addresses/index.js} +1 -1
  83. package/demo/routes/auth/index.js +55 -0
  84. package/demo/routes/carts/{cart_items.js → cart_items/index.js} +1 -1
  85. package/demo/routes/{carts.js → carts/index.js} +1 -1
  86. package/demo/routes/{categories.js → categories/index.js} +1 -1
  87. package/demo/routes/{coupons.js → coupons/index.js} +1 -1
  88. package/demo/routes/index.js +39 -24
  89. package/demo/routes/{orders.js → orders/index.js} +1 -1
  90. package/demo/routes/orders/{order_items.js → order_items/index.js} +1 -1
  91. package/demo/routes/orders/{payments.js → payments/index.js} +1 -1
  92. package/demo/routes/orders/{shipments.js → shipments/index.js} +1 -1
  93. package/demo/routes/{products.js → products/index.js} +1 -1
  94. package/demo/routes/products/{product_images.js → product_images/index.js} +1 -1
  95. package/demo/routes/products/{product_reviews.js → product_reviews/index.js} +1 -1
  96. package/demo/routes/products/{product_variants.js → product_variants/index.js} +1 -1
  97. package/demo/routes/roles/index.js +75 -0
  98. package/demo/routes/roles/permissions/index.js +47 -0
  99. package/demo/routes/tenants/index.js +45 -0
  100. package/demo/routes/users/index.js +45 -0
  101. package/demo/routes/{wishlists.js → wishlists/index.js} +1 -1
  102. package/demo/seeds/saas-seed.js +329 -0
  103. package/docker-compose.yml +61 -0
  104. package/package.json +120 -113
  105. package/scripts/demo-create.js +1 -1
  106. package/skill/SKILL.md +119 -3
  107. package/src/cli/commands/db-manager.js +134 -0
  108. package/src/cli/commands/generate.js +112 -43
  109. package/src/cli/commands/help.js +0 -1
  110. package/src/cli/diff-engine.js +2 -1
  111. package/src/cli/generate-model.js +9 -4
  112. package/src/cli/generate-openapi.js +40 -13
  113. package/src/cli/generate-route.js +61 -22
  114. package/src/cli/generate-saas-structure.js +122 -0
  115. package/src/cli/init/generators.js +42 -30
  116. package/src/cli/init.js +8 -0
  117. package/src/cli/main.js +8 -1
  118. package/src/cli/saas/generate-saas-middleware.js +108 -0
  119. package/src/cli/saas/generate-saas-migrations.js +480 -0
  120. package/src/cli/saas/generate-saas-models.js +211 -0
  121. package/src/cli/saas/generate-saas-openapi.js +419 -0
  122. package/src/cli/saas/generate-saas-routes.js +435 -0
  123. package/src/cli/saas/generate-saas-seeds.js +243 -0
  124. package/src/cli/saas/generate-saas-utils.js +176 -0
  125. package/src/commons/kafka.js +139 -0
  126. package/src/commons/model.js +29 -9
  127. package/src/index.js +2 -0
  128. package/src/mssql/db.js +41 -3
  129. package/src/mysql/db.js +3 -0
  130. package/src/postgres/db.js +6 -0
  131. package/src/sqlite3/db.js +11 -0
  132. package/demo/docs/llm.md +0 -197
  133. package/demo/llms.txt +0 -70
  134. package/demo/routes/users.js +0 -6
  135. package/src/cli/commands/generate-llm-docs.js +0 -418
  136. /package/demo/migrations/{20260430155808_create_migrations_table.sql → 20260509170349_create_migrations_table.sql} +0 -0
@@ -5,6 +5,13 @@ function generateOpenAPISpec(models, options = {}) {
5
5
  const basePath = options.basePath || "/api";
6
6
  const title = options.title || "REST Router API";
7
7
  const version = options.version || "1.0.0";
8
+ const relationships = options.relationships || [];
9
+
10
+ // Build a lookup: child table -> { parent, foreignKey }
11
+ const childMap = {};
12
+ for (const rel of relationships) {
13
+ childMap[rel.child] = { parent: rel.parent, foreignKey: rel.foreignKey };
14
+ }
8
15
 
9
16
  const spec = {
10
17
  openapi: "3.0.3",
@@ -39,14 +46,34 @@ function generateOpenAPISpec(models, options = {}) {
39
46
  };
40
47
 
41
48
  const ref = { $ref: `#/components/schemas/${schemaName}` };
42
- const prefix = `${basePath}/${m.table}`;
49
+
50
+ // Determine path prefix: nested under parent if this is a child table
51
+ let prefix;
52
+ const isChild = !!childMap[m.table];
53
+ let fkParam = null;
54
+ if (isChild) {
55
+ const { parent, foreignKey } = childMap[m.table];
56
+ prefix = `${basePath}/${parent}/{${foreignKey}}/${m.table}`;
57
+ fkParam = {
58
+ name: foreignKey,
59
+ in: "path",
60
+ required: true,
61
+ schema: { type: "string" },
62
+ description: `${capitalize(parent)} foreign key`,
63
+ };
64
+ } else {
65
+ prefix = `${basePath}/${m.table}`;
66
+ }
67
+
68
+ // Helper: prepend FK path param for child routes
69
+ const withFk = (params) => (fkParam ? [fkParam, ...params] : params);
43
70
 
44
71
  // GET / — list
45
72
  spec.paths[`${prefix}/`] = {
46
73
  get: {
47
74
  tags: [tag],
48
75
  summary: `List ${m.table}`,
49
- parameters: [
76
+ parameters: withFk([
50
77
  {
51
78
  name: "page",
52
79
  in: "query",
@@ -69,7 +96,7 @@ function generateOpenAPISpec(models, options = {}) {
69
96
  in: "query",
70
97
  schema: { type: "string", enum: ["json", "csv", "xml"] },
71
98
  },
72
- ],
99
+ ]),
73
100
  responses: {
74
101
  200: {
75
102
  description: "Success",
@@ -141,7 +168,7 @@ function generateOpenAPISpec(models, options = {}) {
141
168
  get: {
142
169
  tags: [tag],
143
170
  summary: `Get ${m.table} by ${pk}`,
144
- parameters: [
171
+ parameters: withFk([
145
172
  { name: pk, in: "path", required: true, schema: { type: "string" } },
146
173
  { name: "select_columns", in: "query", schema: { type: "string" } },
147
174
  {
@@ -149,7 +176,7 @@ function generateOpenAPISpec(models, options = {}) {
149
176
  in: "query",
150
177
  schema: { type: "string", enum: ["json", "csv", "xml"] },
151
178
  },
152
- ],
179
+ ]),
153
180
  responses: {
154
181
  200: {
155
182
  description: "Success",
@@ -161,9 +188,9 @@ function generateOpenAPISpec(models, options = {}) {
161
188
  post: {
162
189
  tags: [tag],
163
190
  summary: `Insert a ${m.table}`,
164
- parameters: [
191
+ parameters: withFk([
165
192
  { name: pk, in: "path", required: true, schema: { type: "string" } },
166
- ],
193
+ ]),
167
194
  requestBody: { content: { "application/json": { schema: ref } } },
168
195
  responses: {
169
196
  200: {
@@ -175,9 +202,9 @@ function generateOpenAPISpec(models, options = {}) {
175
202
  put: {
176
203
  tags: [tag],
177
204
  summary: `Update a ${m.table}`,
178
- parameters: [
205
+ parameters: withFk([
179
206
  { name: pk, in: "path", required: true, schema: { type: "string" } },
180
- ],
207
+ ]),
181
208
  requestBody: { content: { "application/json": { schema: ref } } },
182
209
  responses: {
183
210
  200: {
@@ -190,9 +217,9 @@ function generateOpenAPISpec(models, options = {}) {
190
217
  patch: {
191
218
  tags: [tag],
192
219
  summary: `Partial update a ${m.table}`,
193
- parameters: [
220
+ parameters: withFk([
194
221
  { name: pk, in: "path", required: true, schema: { type: "string" } },
195
- ],
222
+ ]),
196
223
  requestBody: {
197
224
  content: { "application/json": { schema: { type: "object" } } },
198
225
  },
@@ -207,9 +234,9 @@ function generateOpenAPISpec(models, options = {}) {
207
234
  delete: {
208
235
  tags: [tag],
209
236
  summary: `Delete a ${m.table}`,
210
- parameters: [
237
+ parameters: withFk([
211
238
  { name: pk, in: "path", required: true, schema: { type: "string" } },
212
- ],
239
+ ]),
213
240
  responses: {
214
241
  200: { description: "Deleted" },
215
242
  404: { description: "Not Found" },
@@ -26,17 +26,63 @@ function safeVarName(name) {
26
26
  function generateRouteFile(tableName, modelsRelPath) {
27
27
  const varName = safeVarName(tableName);
28
28
  return `import dbModelRouter from "db-model-router";
29
- import ${varName} from "${modelsRelPath}/${tableName}.js";
29
+ import express from "express";
30
+ import { ${varName} } from "#models";
30
31
 
32
+ const router = express.Router({ mergeParams: true });
31
33
  const { route } = dbModelRouter;
32
34
 
33
- export default route(${varName});
35
+ router.use("/", route(${varName}));
36
+
37
+ export default router;
34
38
  `;
35
39
  }
36
40
 
41
+ /**
42
+ * Generate a parent route file that includes its own CRUD and mounts child routes.
43
+ * e.g., routes/orders/index.js mounts order_items under /:order_id/items
44
+ *
45
+ * @param {string} tableName - Parent table name
46
+ * @param {Array<{child, foreignKey}>} children - Child relationships for this parent
47
+ * @returns {string}
48
+ */
49
+ function generateParentRouteFile(tableName, children) {
50
+ const varName = safeVarName(tableName);
51
+ let code = `import dbModelRouter from "db-model-router";
52
+ import express from "express";
53
+ import { ${varName} } from "#models";
54
+ `;
55
+
56
+ // Import child routes
57
+ for (const child of children) {
58
+ const childVar = safeVarName(child.child);
59
+ code += `import ${childVar}Route from "./${child.child}/index.js";\n`;
60
+ }
61
+
62
+ code += `
63
+ const router = express.Router({ mergeParams: true });
64
+ const { route } = dbModelRouter;
65
+
66
+ `;
67
+
68
+ // Mount child routes BEFORE own CRUD to prevent path clashing
69
+ for (const child of children) {
70
+ const childVar = safeVarName(child.child);
71
+ code += `router.use("/:${child.foreignKey}/${child.child}", ${childVar}Route);\n`;
72
+ }
73
+
74
+ code += `
75
+ // CRUD routes for ${tableName}
76
+ router.use("/", route(${varName}));
77
+
78
+ export default router;
79
+ `;
80
+ return code;
81
+ }
82
+
37
83
  /**
38
84
  * Generate a child route file that scopes queries by parent FK.
39
- * e.g., posts/:post_id/comments — filters comments where post_id = :post_id
85
+ * e.g., routes/orders/items/index.js — filters items where order_id = :order_id
40
86
  */
41
87
  function generateChildRouteFile(
42
88
  childTable,
@@ -46,12 +92,16 @@ function generateChildRouteFile(
46
92
  ) {
47
93
  const varName = safeVarName(childTable);
48
94
  return `import dbModelRouter from "db-model-router";
49
- import ${varName} from "${modelsRelPath}/${childTable}.js";
95
+ import express from "express";
96
+ import { ${varName} } from "#models";
50
97
 
98
+ const router = express.Router({ mergeParams: true });
51
99
  const { route } = dbModelRouter;
52
100
 
53
101
  // Child route: scoped by parent ${parentTable} via ${fkColumn}
54
- export default route(${varName}, { ${fkColumn}: "params.${fkColumn}" });
102
+ router.use("/", route(${varName}, { ${fkColumn}: "params.${fkColumn}" }));
103
+
104
+ export default router;
55
105
  `;
56
106
  }
57
107
 
@@ -67,7 +117,7 @@ export default route(${varName}, { ${fkColumn}: "params.${fkColumn}" });
67
117
  * @param {{ includeDocs?: boolean }} [options]
68
118
  */
69
119
  function generateRoutesIndexFile(tableNames, relationships = [], options = {}) {
70
- let imports = `import express from "express";\n\nconst router = express.Router();\n\n`;
120
+ let imports = `import express from "express";\n\nconst router = express.Router({ mergeParams: true });\n\n`;
71
121
 
72
122
  // Collect child tables that are nested under parents
73
123
  const nestedChildren = new Set();
@@ -75,17 +125,11 @@ function generateRoutesIndexFile(tableNames, relationships = [], options = {}) {
75
125
  nestedChildren.add(rel.child);
76
126
  }
77
127
 
78
- // Import top-level routes only (not children)
128
+ // Import top-level routes only (children are mounted inside parent folders)
79
129
  for (const table of tableNames) {
80
130
  if (nestedChildren.has(table)) continue;
81
131
  const varName = safeVarName(table);
82
- imports += `import ${varName}Route from "./${table}.js";\n`;
83
- }
84
-
85
- // Import child routes from subfolders
86
- for (const rel of relationships) {
87
- const varName = safeVarName(rel.child);
88
- imports += `import ${varName}ChildRoute from "./${rel.parent}/${rel.child}.js";\n`;
132
+ imports += `import ${varName}Route from "./${table}/index.js";\n`;
89
133
  }
90
134
 
91
135
  // Import docs route if openapi is generated
@@ -100,19 +144,13 @@ function generateRoutesIndexFile(tableNames, relationships = [], options = {}) {
100
144
  imports += `router.use("/docs", docsRoute);\n`;
101
145
  }
102
146
 
103
- // Mount top-level routes
147
+ // Mount top-level routes (children are already mounted inside their parent's index.js)
104
148
  for (const table of tableNames) {
105
149
  if (nestedChildren.has(table)) continue;
106
150
  const varName = safeVarName(table);
107
151
  imports += `router.use("/${table}", ${varName}Route);\n`;
108
152
  }
109
153
 
110
- // Mount child routes under parent path
111
- for (const rel of relationships) {
112
- const childVar = safeVarName(rel.child);
113
- imports += `router.use("/${rel.parent}/:${rel.foreignKey}/${rel.child}", ${childVar}ChildRoute);\n`;
114
- }
115
-
116
154
  imports += "\nexport default router;\n";
117
155
  return imports;
118
156
  }
@@ -472,7 +510,7 @@ async function main() {
472
510
  }
473
511
  }
474
512
  if (modelMeta.length > 0) {
475
- const spec = generateOpenAPISpec(modelMeta);
513
+ const spec = generateOpenAPISpec(modelMeta, { relationships });
476
514
  const specPath = path.join(routesDir, "openapi.json");
477
515
  fs.writeFileSync(specPath, JSON.stringify(spec, null, 2));
478
516
  console.log(` Created ${specPath}`);
@@ -606,6 +644,7 @@ if (require.main === module) {
606
644
 
607
645
  module.exports = {
608
646
  generateRouteFile,
647
+ generateParentRouteFile,
609
648
  generateChildRouteFile,
610
649
  generateRoutesIndexFile,
611
650
  generateTestFile,
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ const { generateSaasMigrations } = require("./saas/generate-saas-migrations");
7
+ const { generateSaasModels } = require("./saas/generate-saas-models");
8
+ const {
9
+ generateAuthenticateMiddleware,
10
+ generateTenantIsolationMiddleware,
11
+ generateHasPermissionMiddleware,
12
+ } = require("./saas/generate-saas-middleware");
13
+ const {
14
+ generateCrudRoutes,
15
+ generateAuthRoutes,
16
+ generateRoutesIndex,
17
+ } = require("./saas/generate-saas-routes");
18
+ const { generateSaasSeeds } = require("./saas/generate-saas-seeds");
19
+ const {
20
+ generatePasswordUtil,
21
+ generateModulesUtil,
22
+ generateWebhookUtil,
23
+ } = require("./saas/generate-saas-utils");
24
+
25
+ /**
26
+ * Read the existing .gitignore file and append `credentials.md` if not already present.
27
+ * Returns the full .gitignore content to be written.
28
+ *
29
+ * @returns {string} Updated .gitignore content
30
+ */
31
+ function getGitignoreContent() {
32
+ const gitignorePath = path.join(process.cwd(), ".gitignore");
33
+ let content = "";
34
+ if (fs.existsSync(gitignorePath)) {
35
+ content = fs.readFileSync(gitignorePath, "utf8");
36
+ }
37
+ if (!content.includes("credentials.md")) {
38
+ content = content.trimEnd() + "\ncredentials.md\n";
39
+ }
40
+ return content;
41
+ }
42
+
43
+ /**
44
+ * Main SaaS structure generator orchestrator.
45
+ *
46
+ * Calls all sub-generators and aggregates their output into a single
47
+ * planned[] array of { relPath, content } objects compatible with the
48
+ * existing generate command's file write loop.
49
+ *
50
+ * @param {string} adapter - Database adapter name (e.g. "postgres", "mysql", "sqlite3")
51
+ * @param {object} [options] - Generation options
52
+ * @param {boolean} [options.dryRun] - If true, files will not be written (handled by caller)
53
+ * @param {boolean} [options.json] - If true, output JSON format (handled by caller)
54
+ * @param {Date|number} [options.timestamp] - Base timestamp for migration files
55
+ * @param {string[]} [options.tableNames] - Schema-generated table names for routes index
56
+ * @param {Array<{parent, child, foreignKey}>} [options.relationships] - Schema relationships for routes index
57
+ * @param {{ includeDocs?: boolean }} [options.routeOptions] - Options for routes index generation
58
+ * @returns {Array<{ relPath: string, content: string }>} Combined planned file array
59
+ */
60
+ function generateSaasStructure(adapter, options) {
61
+ const opts = options || {};
62
+ const planned = [];
63
+
64
+ // 1. Migrations
65
+ const timestamp = opts.timestamp || new Date();
66
+ const migrations = generateSaasMigrations(adapter, timestamp);
67
+ for (const entry of migrations) {
68
+ planned.push(entry);
69
+ }
70
+
71
+ // 2. Models (includes models/index.js barrel with both SaaS + dbmr tables)
72
+ const models = generateSaasModels(adapter, opts.tableNames || []);
73
+ for (const entry of models) {
74
+ planned.push(entry);
75
+ }
76
+
77
+ // 3. Middleware
78
+ planned.push(generateAuthenticateMiddleware());
79
+ planned.push(generateTenantIsolationMiddleware());
80
+ planned.push(generateHasPermissionMiddleware());
81
+
82
+ // 4. Routes (CRUD + auth + combined index with dbmr routes)
83
+ const crudRoutes = generateCrudRoutes();
84
+ for (const entry of crudRoutes) {
85
+ planned.push(entry);
86
+ }
87
+ planned.push(generateAuthRoutes());
88
+ planned.push(
89
+ generateRoutesIndex(
90
+ opts.tableNames || [],
91
+ opts.relationships || [],
92
+ opts.routeOptions || {},
93
+ ),
94
+ );
95
+
96
+ // 5. Seeds
97
+ const seeds = generateSaasSeeds(adapter);
98
+ for (const entry of seeds) {
99
+ planned.push(entry);
100
+ }
101
+
102
+ // 6. Utilities
103
+ planned.push({
104
+ relPath: "commons/password.js",
105
+ content: generatePasswordUtil(),
106
+ });
107
+ planned.push({
108
+ relPath: "commons/modules.js",
109
+ content: generateModulesUtil(),
110
+ });
111
+ planned.push({
112
+ relPath: "commons/webhook.js",
113
+ content: generateWebhookUtil(),
114
+ });
115
+
116
+ // 7. .gitignore update (add credentials.md)
117
+ planned.push({ relPath: ".gitignore", content: getGitignoreContent() });
118
+
119
+ return planned;
120
+ }
121
+
122
+ module.exports = { generateSaasStructure, getGitignoreContent };
@@ -169,6 +169,7 @@ function buildEnvContent(answers, mode, secrets) {
169
169
  const lines = [];
170
170
  lines.push("# Server");
171
171
  lines.push("PORT=3000");
172
+ lines.push("API_BASE_PATH=/api");
172
173
  lines.push("");
173
174
  lines.push("# Database");
174
175
 
@@ -367,21 +368,21 @@ function generateAppJs(answers) {
367
368
  answers.framework === "ultimate-express" ? "ultimate-express" : "express";
368
369
 
369
370
  // Imports
370
- let imports = `const express = require("${frameworkPkg}");
371
- const { init, db } = require("db-model-router");
372
- const session = require("express-session");`;
371
+ let imports = `import express from "${frameworkPkg}";
372
+ import { init, db } from "db-model-router";
373
+ import session from "express-session";`;
373
374
 
374
375
  if (answers.session === "redis") {
375
- imports += `\nconst RedisStore = require("connect-redis").default;
376
- const { Redis } = require("ioredis");`;
376
+ imports += `\nimport RedisStore from "connect-redis";
377
+ import { Redis } from "ioredis";`;
377
378
  }
378
379
  if (answers.rateLimiting) {
379
- imports += `\nconst rateLimit = require("express-rate-limit");`;
380
+ imports += `\nimport rateLimit from "express-rate-limit";`;
380
381
  }
381
382
  if (answers.helmet) {
382
- imports += `\nconst helmet = require("helmet");`;
383
+ imports += `\nimport helmet from "helmet";`;
383
384
  }
384
- imports += `\nconst logger = require("./middleware/logger");`;
385
+ imports += `\nimport logger from "./middleware/logger.js";`;
385
386
 
386
387
  // Rate limiting block
387
388
  const rateLimitBlock = answers.rateLimiting
@@ -396,9 +397,7 @@ const { Redis } = require("ioredis");`;
396
397
  const helmetBlock = answers.helmet ? `app.use(helmet());` : "";
397
398
 
398
399
  return `${imports}
399
-
400
- // Load environment variables
401
- require("dotenv").config();
400
+ import "dotenv/config";
402
401
 
403
402
  // Initialize database adapter
404
403
  init("${answers.database}");
@@ -413,6 +412,10 @@ app.use(express.urlencoded({ extended: true }));
413
412
  ${helmetBlock ? helmetBlock + "\n" : ""}${rateLimitBlock ? rateLimitBlock + "\n" : ""}${sessionBlock(answers)}
414
413
  app.use(logger);
415
414
 
415
+ // Routes
416
+ import routes from "#routes/index.js";
417
+ app.use(process.env.API_BASE_PATH || "/api", routes);
418
+
416
419
  // Health check
417
420
  app.get("/health", (req, res) => {
418
421
  res.json({ status: "ok", timestamp: new Date().toISOString() });
@@ -428,7 +431,7 @@ app.listen(PORT, () => {
428
431
  console.log(\`Server running on port \${PORT}\`);
429
432
  });
430
433
 
431
- module.exports = app;
434
+ export default app;
432
435
  `;
433
436
  }
434
437
 
@@ -551,14 +554,15 @@ function generateMigrateScript(answers) {
551
554
 
552
555
  if (isNoSql) {
553
556
  return `#!/usr/bin/env node
554
- "use strict";
557
+ import fs from "fs";
558
+ import path from "path";
559
+ import crypto from "crypto";
560
+ import { fileURLToPath } from "url";
561
+ import "dotenv/config";
555
562
 
556
- const fs = require("fs");
557
- const path = require("path");
558
- const crypto = require("crypto");
559
- require("dotenv").config();
563
+ import { init, db } from "db-model-router";
560
564
 
561
- const { init, db } = require("db-model-router");
565
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
562
566
 
563
567
  init("${answers.database}");
564
568
 
@@ -598,7 +602,7 @@ async function migrate() {
598
602
  const content = fs.readFileSync(filePath, "utf8");
599
603
  const checksum = crypto.createHash("md5").update(content).digest("hex");
600
604
 
601
- const migration = require(filePath);
605
+ const migration = await import(filePath);
602
606
  console.log(\` Running migration: \${file}\`);
603
607
  await migration.up(db);
604
608
  await recordMigration(file, checksum);
@@ -622,14 +626,15 @@ migrate().catch(err => {
622
626
  }
623
627
 
624
628
  return `#!/usr/bin/env node
625
- "use strict";
629
+ import fs from "fs";
630
+ import path from "path";
631
+ import crypto from "crypto";
632
+ import { fileURLToPath } from "url";
633
+ import "dotenv/config";
626
634
 
627
- const fs = require("fs");
628
- const path = require("path");
629
- const crypto = require("crypto");
630
- require("dotenv").config();
635
+ import { init, db } from "db-model-router";
631
636
 
632
- const { init, db } = require("db-model-router");
637
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
633
638
 
634
639
  init("${answers.database}");
635
640
 
@@ -700,14 +705,15 @@ function generateAddMigrationScript(answers) {
700
705
  const isNoSql = NOSQL_DATABASES.includes(answers.database);
701
706
  const ext = isNoSql ? "js" : "sql";
702
707
  const template = isNoSql
703
- ? `"use strict";\\n\\nmodule.exports = {\\n async up(db) {\\n // Write your migration here\\n },\\n\\n async down(db) {\\n // Write your rollback here\\n },\\n};\\n`
708
+ ? `export async function up(db) {\\n // Write your migration here\\n}\\n\\nexport async function down(db) {\\n // Write your rollback here\\n}\\n`
704
709
  : `-- Write your migration SQL here\\n`;
705
710
 
706
711
  return `#!/usr/bin/env node
707
- "use strict";
712
+ import fs from "fs";
713
+ import path from "path";
714
+ import { fileURLToPath } from "url";
708
715
 
709
- const fs = require("fs");
710
- const path = require("path");
716
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
711
717
 
712
718
  const migrationsDir = path.join(__dirname, "migrations");
713
719
 
@@ -1422,6 +1428,8 @@ if (isMain) {
1422
1428
  const pkg = await import("db-model-router");
1423
1429
  const mod = pkg.default || pkg;
1424
1430
  mod.init("${answers.database}");
1431
+ mod.db.connect({
1432
+ ${dbConnectArgs(answers.database)}});
1425
1433
  const migrationsDir = path.join(__dirname, "${migrationsRel}");
1426
1434
  runMigrations(mod.db, migrationsDir)
1427
1435
  .then(() => process.exit(0))
@@ -1490,6 +1498,8 @@ if (isMain) {
1490
1498
  const pkg = await import("db-model-router");
1491
1499
  const mod = pkg.default || pkg;
1492
1500
  mod.init("${answers.database}");
1501
+ mod.db.connect({
1502
+ ${dbConnectArgs(answers.database)}});
1493
1503
  const migrationsDir = path.join(__dirname, "${migrationsRel}");
1494
1504
  runMigrations(mod.db, migrationsDir)
1495
1505
  .then(() => process.exit(0))
@@ -1726,7 +1736,9 @@ import configureSession from "${commonsPrefix}/session.js";
1726
1736
  import applySecurity from "${commonsPrefix}/security.js";
1727
1737
  import logger from "${middlewarePrefix}/logger.js";
1728
1738
  import route from "${routePrefix}/index.js";
1729
-
1739
+ import { fileURLToPath } from 'node:url';
1740
+ import path from "path";
1741
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
1730
1742
  const app = express();
1731
1743
  const PORT = process.env.PORT || 3000;
1732
1744
 
package/src/cli/init.js CHANGED
@@ -254,6 +254,14 @@ function updatePackageJson(answers, outputDir) {
254
254
  const scripts = getScripts(outputDir);
255
255
 
256
256
  pkg.type = "module";
257
+ pkg.imports = {
258
+ "#root/*.js": "./*.js",
259
+ "#models": "./models/index.js",
260
+ "#models/*.js": "./models/*.js",
261
+ "#routes/*.js": "./routes/*.js",
262
+ "#commons/*.js": "./commons/*.js",
263
+ "#middleware/*.js": "./middleware/*.js",
264
+ };
257
265
  pkg.scripts = Object.assign({}, pkg.scripts || {}, scripts);
258
266
  pkg.dependencies = Object.assign({}, pkg.dependencies || {}, dependencies);
259
267
  pkg.devDependencies = Object.assign(
package/src/cli/main.js CHANGED
@@ -9,6 +9,7 @@ const generateCmd = require("./commands/generate");
9
9
  const doctorCmd = require("./commands/doctor");
10
10
  const diffCmd = require("./commands/diff");
11
11
  const helpCmd = require("./commands/help");
12
+ const dbManagerCmd = require("./commands/db-manager");
12
13
 
13
14
  /**
14
15
  * Map of subcommand names to their handler functions.
@@ -19,6 +20,7 @@ const COMMANDS = {
19
20
  generate: generateCmd,
20
21
  doctor: doctorCmd,
21
22
  diff: diffCmd,
23
+ "db-manager": dbManagerCmd,
22
24
  help: helpCmd,
23
25
  };
24
26
 
@@ -31,6 +33,7 @@ const COMMAND_DESCRIPTIONS = {
31
33
  generate: "Generate models, routes, tests, and OpenAPI spec from a schema",
32
34
  doctor: "Validate schema, check dependencies, and verify file sync",
33
35
  diff: "Preview changes between current files and what the schema would produce",
36
+ "db-manager": "Start a live database management UI",
34
37
  help: "Show help for a command",
35
38
  };
36
39
 
@@ -64,10 +67,14 @@ const COMMAND_FLAGS = {
64
67
  ["--routes", "Generate only route files"],
65
68
  ["--openapi", "Generate only OpenAPI spec"],
66
69
  ["--tests", "Generate only test files"],
67
- ["--llm-docs", "Generate only LLM documentation"],
70
+ ["--db-manager", "Generate DB Manager UI (SQL adapters only)"],
68
71
  ],
69
72
  doctor: [["--from <path>", "Schema file (default: dbmr.schema.json)"]],
70
73
  diff: [["--from <path>", "Schema file (default: dbmr.schema.json)"]],
74
+ "db-manager": [
75
+ ["--env <path>", "Path to .env file (default: .env)"],
76
+ ["--port <number>", "Server port (default: 4000)"],
77
+ ],
71
78
  };
72
79
 
73
80
  /**