db-model-router 1.0.6 → 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 (137) hide show
  1. package/README.md +150 -11
  2. package/TODO.md +0 -15
  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 +22 -44
  45. package/demo/.dockerignore +7 -0
  46. package/demo/.env.example +14 -0
  47. package/demo/Dockerfile +20 -0
  48. package/demo/app.js +39 -0
  49. package/demo/commons/add_migration.js +43 -0
  50. package/demo/commons/db.js +28 -0
  51. package/demo/commons/migrate.js +68 -0
  52. package/demo/commons/modules.js +18 -0
  53. package/demo/commons/password.js +36 -0
  54. package/demo/commons/security.js +30 -0
  55. package/demo/commons/session.js +13 -0
  56. package/demo/commons/webhook.js +81 -0
  57. package/demo/dbmr.schema.json +338 -0
  58. package/demo/middleware/authenticate.js +14 -0
  59. package/demo/middleware/hasPermission.js +30 -0
  60. package/demo/middleware/logger.js +67 -0
  61. package/demo/middleware/tenantIsolation.js +17 -0
  62. package/demo/migrations/20260509170349_create_migrations_table.sql +6 -0
  63. package/demo/migrations/20260509170349_create_saas_tables.sql +69 -0
  64. package/demo/migrations/20260509170349_create_tables.sql +193 -0
  65. package/demo/models/addresses.js +24 -0
  66. package/demo/models/cart_items.js +20 -0
  67. package/demo/models/carts.js +18 -0
  68. package/demo/models/categories.js +22 -0
  69. package/demo/models/coupons.js +25 -0
  70. package/demo/models/index.js +43 -0
  71. package/demo/models/order_items.js +23 -0
  72. package/demo/models/orders.js +27 -0
  73. package/demo/models/payments.js +23 -0
  74. package/demo/models/product_images.js +20 -0
  75. package/demo/models/product_reviews.js +22 -0
  76. package/demo/models/product_variants.js +22 -0
  77. package/demo/models/products.js +32 -0
  78. package/demo/models/role_permissions.js +17 -0
  79. package/demo/models/roles.js +17 -0
  80. package/demo/models/shipments.js +21 -0
  81. package/demo/models/tenants.js +18 -0
  82. package/demo/models/users.js +23 -0
  83. package/demo/models/webhook_logs.js +22 -0
  84. package/demo/models/webhooks.js +19 -0
  85. package/demo/models/wishlists.js +17 -0
  86. package/demo/openapi.json +7000 -0
  87. package/demo/package-lock.json +2810 -0
  88. package/demo/package.json +43 -0
  89. package/demo/routes/addresses/index.js +6 -0
  90. package/demo/routes/auth/index.js +55 -0
  91. package/demo/routes/carts/cart_items/index.js +7 -0
  92. package/demo/routes/carts/index.js +6 -0
  93. package/demo/routes/categories/index.js +6 -0
  94. package/demo/routes/coupons/index.js +6 -0
  95. package/demo/routes/docs.js +18 -0
  96. package/demo/routes/health.js +35 -0
  97. package/demo/routes/index.js +54 -0
  98. package/demo/routes/orders/index.js +6 -0
  99. package/demo/routes/orders/order_items/index.js +7 -0
  100. package/demo/routes/orders/payments/index.js +7 -0
  101. package/demo/routes/orders/shipments/index.js +7 -0
  102. package/demo/routes/products/index.js +6 -0
  103. package/demo/routes/products/product_images/index.js +7 -0
  104. package/demo/routes/products/product_reviews/index.js +7 -0
  105. package/demo/routes/products/product_variants/index.js +7 -0
  106. package/demo/routes/roles/index.js +75 -0
  107. package/demo/routes/roles/permissions/index.js +47 -0
  108. package/demo/routes/tenants/index.js +45 -0
  109. package/demo/routes/users/index.js +45 -0
  110. package/demo/routes/wishlists/index.js +6 -0
  111. package/demo/seeds/saas-seed.js +329 -0
  112. package/docker-compose.yml +61 -0
  113. package/package.json +120 -113
  114. package/scripts/demo-create.js +1 -1
  115. package/skill/SKILL.md +119 -3
  116. package/src/cli/commands/db-manager.js +134 -0
  117. package/src/cli/commands/generate.js +106 -60
  118. package/src/cli/commands/help.js +0 -1
  119. package/src/cli/generate-route.js +60 -21
  120. package/src/cli/generate-saas-structure.js +122 -0
  121. package/src/cli/init/generators.js +6 -0
  122. package/src/cli/init.js +8 -0
  123. package/src/cli/main.js +8 -1
  124. package/src/cli/saas/generate-saas-middleware.js +108 -0
  125. package/src/cli/saas/generate-saas-migrations.js +480 -0
  126. package/src/cli/saas/generate-saas-models.js +211 -0
  127. package/src/cli/saas/generate-saas-openapi.js +419 -0
  128. package/src/cli/saas/generate-saas-routes.js +435 -0
  129. package/src/cli/saas/generate-saas-seeds.js +243 -0
  130. package/src/cli/saas/generate-saas-utils.js +176 -0
  131. package/src/commons/kafka.js +139 -0
  132. package/src/commons/model.js +29 -9
  133. package/src/index.js +2 -0
  134. package/src/mssql/db.js +41 -3
  135. package/src/mysql/db.js +3 -0
  136. package/src/postgres/db.js +6 -0
  137. package/src/cli/generate-db-manager.js +0 -1573
@@ -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
 
@@ -411,6 +412,10 @@ app.use(express.urlencoded({ extended: true }));
411
412
  ${helmetBlock ? helmetBlock + "\n" : ""}${rateLimitBlock ? rateLimitBlock + "\n" : ""}${sessionBlock(answers)}
412
413
  app.use(logger);
413
414
 
415
+ // Routes
416
+ import routes from "#routes/index.js";
417
+ app.use(process.env.API_BASE_PATH || "/api", routes);
418
+
414
419
  // Health check
415
420
  app.get("/health", (req, res) => {
416
421
  res.json({ status: "ok", timestamp: new Date().toISOString() });
@@ -1732,6 +1737,7 @@ import applySecurity from "${commonsPrefix}/security.js";
1732
1737
  import logger from "${middlewarePrefix}/logger.js";
1733
1738
  import route from "${routePrefix}/index.js";
1734
1739
  import { fileURLToPath } from 'node:url';
1740
+ import path from "path";
1735
1741
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
1736
1742
  const app = express();
1737
1743
  const PORT = process.env.PORT || 3000;
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
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  "use strict";
3
3
 
4
4
  const { parseFlags, OutputContext } = require("./flags");
@@ -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
 
@@ -68,6 +71,10 @@ const COMMAND_FLAGS = {
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
  /**
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * SaaS middleware generators.
5
+ *
6
+ * Each function returns a { relPath, content } object for a middleware file.
7
+ * Generated code uses ES6 module syntax (import/export).
8
+ */
9
+
10
+ /**
11
+ * Generate the authenticate middleware file.
12
+ *
13
+ * @returns {{ relPath: string, content: string }}
14
+ */
15
+ function generateAuthenticateMiddleware() {
16
+ const relPath = "middleware/authenticate.js";
17
+ const content = `/**
18
+ * Authentication middleware.
19
+ *
20
+ * Validates that the request has an active session with a user object.
21
+ * Responds with 401 Unauthorized if no valid session exists.
22
+ */
23
+ function authenticate(req, res, next) {
24
+ if (!req.session || !req.session.user) {
25
+ return res.status(401).json({ message: "Unauthorized" });
26
+ }
27
+ next();
28
+ }
29
+
30
+ export default authenticate;
31
+ `;
32
+ return { relPath, content };
33
+ }
34
+
35
+ /**
36
+ * Generate the tenant isolation middleware file.
37
+ *
38
+ * @returns {{ relPath: string, content: string }}
39
+ */
40
+ function generateTenantIsolationMiddleware() {
41
+ const relPath = "middleware/tenantIsolation.js";
42
+ const content = `/**
43
+ * Tenant isolation middleware.
44
+ *
45
+ * Restricts data access to the user's own tenant unless the user
46
+ * has a global-scoped permission. Injects tenant_id into query
47
+ * and body parameters for non-global users.
48
+ */
49
+ function tenantIsolation(req, res, next) {
50
+ const hasGlobal = req.session.permission.some((p) => p.scope === "global");
51
+ if (!hasGlobal) {
52
+ req.query.tenant_id = req.session.user.tenant_id;
53
+ req.body.tenant_id = req.session.user.tenant_id;
54
+ }
55
+ next();
56
+ }
57
+
58
+ export default tenantIsolation;
59
+ `;
60
+ return { relPath, content };
61
+ }
62
+
63
+ /**
64
+ * Generate the hasPermission middleware file.
65
+ *
66
+ * @returns {{ relPath: string, content: string }}
67
+ */
68
+ function generateHasPermissionMiddleware() {
69
+ const relPath = "middleware/hasPermission.js";
70
+ const content = `import { isValidModule } from "#commons/modules.js";
71
+
72
+ /**
73
+ * Permission validation middleware factory.
74
+ *
75
+ * Returns a middleware function that checks whether the authenticated user
76
+ * has the required permission for the specified module and action.
77
+ * A permission entry with action "global" grants access to any action
78
+ * on that module.
79
+ *
80
+ * @param {string} module - The module name to check permission for
81
+ * @param {string} action - The required action
82
+ * @returns {function} Express middleware function
83
+ */
84
+ function hasPermission(module, action) {
85
+ return (req, res, next) => {
86
+ if (!isValidModule(module)) {
87
+ return res.status(403).json({ message: "Invalid module" });
88
+ }
89
+ const match = req.session.permission.find(
90
+ (p) => p.module === module && (p.action === action || p.action === "global")
91
+ );
92
+ if (!match) {
93
+ return res.status(403).json({ message: "Forbidden" });
94
+ }
95
+ next();
96
+ };
97
+ }
98
+
99
+ export default hasPermission;
100
+ `;
101
+ return { relPath, content };
102
+ }
103
+
104
+ module.exports = {
105
+ generateAuthenticateMiddleware,
106
+ generateTenantIsolationMiddleware,
107
+ generateHasPermissionMiddleware,
108
+ };