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
@@ -0,0 +1,52 @@
1
+ # Oracle Adapter
2
+
3
+ Uses [oracledb](https://www.npmjs.com/package/oracledb) (Oracle Instant Client required).
4
+
5
+ ## Connection
6
+
7
+ ```js
8
+ const { init, db, model, route } = require("db-model-router");
9
+ init("oracle");
10
+
11
+ db.connect({
12
+ host: "localhost",
13
+ port: 1521,
14
+ database: "XEPDB1",
15
+ user: "system",
16
+ password: "oracle",
17
+ });
18
+ ```
19
+
20
+ ## Environment Variables
21
+
22
+ ```env
23
+ ORACLE_HOST=localhost
24
+ ORACLE_PORT=1521
25
+ ORACLE_DB=XEPDB1
26
+ ORACLE_USER=system
27
+ ORACLE_PASSWORD=oracle
28
+ ```
29
+
30
+ ## Notes
31
+
32
+ - Requires Oracle Instant Client installed on the host
33
+ - Uses connection pooling with session callbacks for NLS date format
34
+ - MySQL-style `?` placeholders are auto-translated to `:1, :2, ...`
35
+ - Includes a SQL translator that converts MySQL DDL/DML to Oracle syntax
36
+ - `MERGE INTO ... USING DUAL` is used for upsert operations
37
+ - `RETURNING ... INTO :pk_out` is used to retrieve auto-generated IDs
38
+ - Oracle reserved words in column names are auto-quoted
39
+ - `CLOB` values are fetched as strings
40
+
41
+ ## Table Creation
42
+
43
+ ```sql
44
+ CREATE TABLE users (
45
+ id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
46
+ name VARCHAR2(255) NOT NULL,
47
+ email VARCHAR2(255) NOT NULL,
48
+ age NUMBER NOT NULL
49
+ );
50
+ ```
51
+
52
+ [← Back to main docs](../../README.md)
@@ -0,0 +1,50 @@
1
+ # PostgreSQL Adapter
2
+
3
+ Uses the [pg](https://www.npmjs.com/package/pg) driver with connection pooling.
4
+
5
+ ## Connection
6
+
7
+ ```js
8
+ const { init, db, model, route } = require("db-model-router");
9
+ init("postgres");
10
+
11
+ db.connect({
12
+ host: "localhost",
13
+ port: 5432,
14
+ user: "postgres",
15
+ password: "password",
16
+ database: "my_app",
17
+ connectionLimit: 50, // pool max
18
+ dateStrings: false, // set true to return dates as strings
19
+ });
20
+ ```
21
+
22
+ ## Environment Variables
23
+
24
+ ```env
25
+ PG_HOST=localhost
26
+ PG_PORT=5432
27
+ PG_USER=postgres
28
+ PG_PASSWORD=password
29
+ PG_DB=test_db
30
+ ```
31
+
32
+ ## Notes
33
+
34
+ - Supports MySQL-style `?` placeholders in raw `db.query()` calls — they are auto-translated to `$1, $2, ...`
35
+ - `SERIAL` / `BIGSERIAL` primary keys are auto-detected via `pg_index`
36
+ - `ON CONFLICT` is used for upsert operations
37
+ - Includes a SQL translator layer that converts common MySQL DDL/DML to PostgreSQL syntax
38
+
39
+ ## Table Creation
40
+
41
+ ```sql
42
+ CREATE TABLE users (
43
+ id SERIAL PRIMARY KEY,
44
+ name VARCHAR NOT NULL,
45
+ email VARCHAR NOT NULL,
46
+ age INTEGER NOT NULL
47
+ );
48
+ ```
49
+
50
+ [← Back to main docs](../../README.md)
@@ -0,0 +1,53 @@
1
+ # Redis Adapter
2
+
3
+ Uses [ioredis](https://www.npmjs.com/package/ioredis). Records are stored as Redis hashes with key pattern `{table}:{id}`.
4
+
5
+ ## Connection
6
+
7
+ ```js
8
+ const { init, db, model, route } = require("db-model-router");
9
+ init("redis");
10
+
11
+ db.connect({
12
+ host: "localhost",
13
+ port: 6379,
14
+ password: "", // optional
15
+ db: 0, // Redis DB index, optional
16
+ primaryKey: "id", // field used as the hash key suffix
17
+ });
18
+ ```
19
+
20
+ ## Environment Variables
21
+
22
+ ```env
23
+ REDIS_HOST=localhost
24
+ REDIS_PORT=6379
25
+ ```
26
+
27
+ ## Notes
28
+
29
+ - Each record is a Redis hash at key `{table}:{primaryKey}`
30
+ - Auto-incrementing IDs use `INCR {table}:__seq`
31
+ - All filtering, sorting, and pagination happen in-memory (full SCAN)
32
+ - Redis stores all values as strings — numeric coercion is applied on read
33
+ - Nested objects are JSON-serialized into hash fields
34
+ - Best suited for small-to-medium datasets where Redis is already in the stack
35
+
36
+ ## Model Definition
37
+
38
+ ```js
39
+ const users = model(
40
+ db,
41
+ "users",
42
+ {
43
+ id: "string",
44
+ name: "required|string",
45
+ email: "required|string",
46
+ age: "required|integer",
47
+ },
48
+ "id",
49
+ ["id"],
50
+ );
51
+ ```
52
+
53
+ [← Back to main docs](../../README.md)
@@ -0,0 +1,43 @@
1
+ # SQLite3 Adapter
2
+
3
+ Uses [better-sqlite3](https://www.npmjs.com/package/better-sqlite3) for synchronous, high-performance SQLite access.
4
+
5
+ ## Connection
6
+
7
+ ```js
8
+ const { init, db, model, route } = require("db-model-router");
9
+ init("sqlite3");
10
+
11
+ db.connect({ database: "./data.db" });
12
+ // or in-memory:
13
+ db.connect({ database: ":memory:" });
14
+ ```
15
+
16
+ ## Options
17
+
18
+ | Option | Description |
19
+ | --------------- | --------------------------- |
20
+ | `database` | File path or `:memory:` |
21
+ | `readonly` | Open in read-only mode |
22
+ | `fileMustExist` | Throw if file doesn't exist |
23
+
24
+ ## Notes
25
+
26
+ - All operations are synchronous (wrapped to match the async model API)
27
+ - WAL journal mode is enabled by default for better concurrency
28
+ - Uses `INSERT OR IGNORE` for conflict handling
29
+ - `ON CONFLICT ... DO UPDATE SET` for upsert
30
+ - No Docker container needed — runs in-process
31
+
32
+ ## Table Creation
33
+
34
+ ```sql
35
+ CREATE TABLE users (
36
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37
+ name TEXT NOT NULL,
38
+ email TEXT NOT NULL,
39
+ age INTEGER NOT NULL
40
+ );
41
+ ```
42
+
43
+ [← Back to main docs](../../README.md)
@@ -14,21 +14,26 @@ const {
14
14
  } = require("../generate-route");
15
15
  const { generateOpenAPISpec } = require("../generate-openapi");
16
16
  const { generateLlmsTxt, generateLlmMd } = require("./generate-llm-docs");
17
+ const { generateMigrationFiles } = require("../generate-migration");
18
+ const { generateDocsRoute } = require("../generate-docs-route");
19
+ const { migrationTimestamp } = require("../init/generators");
17
20
 
18
21
  /**
19
22
  * Generate command handler for the unified CLI.
20
23
  *
21
24
  * Reads a schema file, converts to ModelMeta[], and generates
22
- * models, routes, tests, and OpenAPI spec files.
25
+ * models, routes, tests, OpenAPI spec, migrations, and docs route.
23
26
  *
24
27
  * Supported flags:
25
- * --from Path to schema file (default: dbmr.schema.json)
26
- * --models Generate only model files
27
- * --routes Generate only route files (including child routes and index)
28
- * --openapi Generate only OpenAPI spec
29
- * --tests Generate only test files
30
- * --dry-run Report planned files without writing
31
- * --json Output JSON result via ctx
28
+ * --from Path to schema file (default: dbmr.schema.json)
29
+ * --models Generate only model files
30
+ * --routes Generate only route files (including child routes and index)
31
+ * --openapi Generate only OpenAPI spec + docs route
32
+ * --tests Generate only test files
33
+ * --migrations Generate only migration files
34
+ * --llm-docs Generate only LLM documentation
35
+ * --dry-run Report planned files without writing
36
+ * --json Output JSON result via ctx
32
37
  *
33
38
  * When no artifact flags are provided, all artifact types are generated.
34
39
  *
@@ -80,12 +85,14 @@ async function generate(args, flags, ctx) {
80
85
  args.routes === true ||
81
86
  args.openapi === true ||
82
87
  args.tests === true ||
88
+ args.migrations === true ||
83
89
  args["llm-docs"] === true;
84
90
 
85
91
  const genModels = !hasArtifactFlag || args.models === true;
86
92
  const genRoutes = !hasArtifactFlag || args.routes === true;
87
93
  const genOpenapi = !hasArtifactFlag || args.openapi === true;
88
94
  const genTests = !hasArtifactFlag || args.tests === true;
95
+ const genMigrations = !hasArtifactFlag || args.migrations === true;
89
96
  const genLlmDocs = !hasArtifactFlag || args["llm-docs"] === true;
90
97
 
91
98
  const modelsRelPath = "../models";
@@ -106,57 +113,91 @@ async function generate(args, flags, ctx) {
106
113
 
107
114
  // --- Route files ---
108
115
  if (genRoutes) {
109
- // One route per table
116
+ // Collect child tables to skip generating top-level route files for them
117
+ const nestedChildren = new Set();
118
+ for (const rel of relationships) {
119
+ nestedChildren.add(rel.child);
120
+ }
121
+
122
+ // One route per top-level table (skip children)
110
123
  for (const m of meta) {
124
+ if (nestedChildren.has(m.table)) continue;
111
125
  planned.push({
112
126
  relPath: `routes/${m.table}.js`,
113
127
  content: generateRouteFile(m.table, modelsRelPath),
114
128
  });
115
129
  }
116
130
 
117
- // Child route files (one per relationship)
131
+ // Child route files in subfolders: routes/<parent>/<child>.js
118
132
  for (const rel of relationships) {
119
133
  planned.push({
120
- relPath: `routes/${rel.child}_child_of_${rel.parent}.js`,
134
+ relPath: `routes/${rel.parent}/${rel.child}.js`,
121
135
  content: generateChildRouteFile(
122
136
  rel.child,
123
137
  rel.parent,
124
138
  rel.foreignKey,
125
- modelsRelPath,
139
+ `../../models`,
126
140
  ),
127
141
  });
128
142
  }
129
143
 
130
- // Routes index file
144
+ // Routes index file (include docs route when openapi is being generated)
131
145
  planned.push({
132
146
  relPath: "routes/index.js",
133
- content: generateRoutesIndexFile(tableNames, relationships),
147
+ content: generateRoutesIndexFile(tableNames, relationships, {
148
+ includeDocs: genOpenapi,
149
+ }),
134
150
  });
135
151
  }
136
152
 
137
- // --- OpenAPI spec ---
153
+ // --- OpenAPI spec + docs route ---
138
154
  if (genOpenapi) {
139
155
  planned.push({
140
156
  relPath: "openapi.json",
141
157
  content: JSON.stringify(generateOpenAPISpec(meta), null, 2) + "\n",
142
158
  });
159
+
160
+ // Generate Swagger UI docs route
161
+ planned.push({
162
+ relPath: "routes/docs.js",
163
+ content: generateDocsRoute(),
164
+ });
165
+ }
166
+
167
+ // --- Migration files ---
168
+ if (genMigrations) {
169
+ const migrationFiles = generateMigrationFiles(schema);
170
+ const ts = migrationTimestamp(new Date());
171
+ for (const mf of migrationFiles) {
172
+ planned.push({
173
+ relPath: `migrations/${ts}_${mf.filename}`,
174
+ content: mf.content,
175
+ });
176
+ }
143
177
  }
144
178
 
145
179
  // --- Test files ---
146
180
  if (genTests) {
181
+ // Collect child tables to skip generating top-level test files for them
182
+ const nestedChildrenForTests = new Set();
183
+ for (const rel of relationships) {
184
+ nestedChildrenForTests.add(rel.child);
185
+ }
186
+
147
187
  for (const m of meta) {
188
+ if (nestedChildrenForTests.has(m.table)) continue;
148
189
  planned.push({
149
190
  relPath: `test/${m.table}.test.js`,
150
191
  content: generateTestFile(m.table, m.primary_key),
151
192
  });
152
193
  }
153
194
 
154
- // Child test files (one per relationship)
195
+ // Child test files in subfolders: test/<parent>/<child>.test.js
155
196
  for (const rel of relationships) {
156
197
  const childMeta = meta.find((m) => m.table === rel.child);
157
198
  const pk = childMeta ? childMeta.primary_key : "id";
158
199
  planned.push({
159
- relPath: `test/${rel.child}_child_of_${rel.parent}.test.js`,
200
+ relPath: `test/${rel.parent}/${rel.child}.test.js`,
160
201
  content: generateChildTestFile(
161
202
  rel.child,
162
203
  rel.parent,
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Per-command detailed help text.
5
+ * Each key matches a subcommand name from main.js.
6
+ */
7
+ const COMMAND_HELP = {
8
+ init: `Usage: db-model-router init [options]
9
+
10
+ Scaffold a new project from a schema file or interactively.
11
+ Creates app.js, .env, commons/, routes/, middleware/, and migrations/.
12
+
13
+ Options:
14
+ --from <path> Read adapter, framework, and options from a schema file
15
+ --framework <name> Express framework: express, ultimate-express
16
+ --database <name> Database adapter: mysql, mariadb, postgres, sqlite3, mongodb,
17
+ mssql, cockroachdb, oracle, redis, dynamodb
18
+ --db <name> Alias for --database
19
+ --session <type> Session store: memory, redis, database
20
+ --output <dir> Directory for backend source files (relative to cwd).
21
+ package.json and app.js stay in root; commons/, routes/,
22
+ middleware/, and migrations/ go inside this folder.
23
+ --rateLimiting Enable rate limiting (default: yes)
24
+ --helmet Enable Helmet security headers (default: yes)
25
+ --logger Enable Winston + Loki logger for Grafana (default: yes)
26
+ --yes Accept all defaults without prompting
27
+ --json Output machine-readable JSON
28
+ --dry-run Preview planned files without writing
29
+ --no-install Skip npm install after scaffolding
30
+ --help Show this help message
31
+
32
+ Generated files:
33
+ app.js Express app entry point
34
+ .env / .env.example Environment configuration
35
+ .gitignore Git ignore rules
36
+ <output>/commons/db.js Database init, connect, and global.db
37
+ <output>/commons/session.js Session configuration
38
+ <output>/commons/migrate.js Migration runner (also runs as script)
39
+ <output>/commons/add_migration.js Migration creation helper (also runs as script)
40
+ <output>/commons/security.js Helmet, rate limiting, custom headers
41
+ <output>/middleware/logger.js Winston + Loki request logger
42
+ <output>/routes/index.js Central route mounting
43
+ <output>/routes/health.js GET /health endpoint
44
+ <output>/migrations/ Initial migration files
45
+
46
+ Examples:
47
+ db-model-router init --from dbmr.schema.json --yes --no-install
48
+ db-model-router init --framework express --database postgres --output backend --yes
49
+ db-model-router init --database mysql --session redis --helmet --rateLimiting
50
+ db-model-router init --dry-run`,
51
+
52
+ inspect: `Usage: db-model-router inspect [options]
53
+
54
+ Introspect a live database and produce a dbmr.schema.json file.
55
+ Connects to the database, reads table structures, and outputs a schema.
56
+
57
+ Options:
58
+ --type <adapter> Database adapter (required): mysql, postgres, sqlite3,
59
+ mssql, oracle, cockroachdb
60
+ --env <path> Path to .env file for connection parameters
61
+ --out <path> Output file path (default: dbmr.schema.json)
62
+ --tables <list> Comma-separated list of tables to include (omit for all)
63
+ --yes Accept all defaults without prompting
64
+ --json Output schema as JSON to stdout (no file write)
65
+ --dry-run Output schema to stdout without writing file
66
+ --help Show this help message
67
+
68
+ Examples:
69
+ db-model-router inspect --type postgres --env .env
70
+ db-model-router inspect --type sqlite3 --out schema.json --tables users,posts
71
+ db-model-router inspect --type mysql --json`,
72
+
73
+ generate: `Usage: db-model-router generate [options]
74
+
75
+ Generate models, routes, tests, OpenAPI spec, and LLM docs from a schema file.
76
+ When no artifact flags are provided, all artifact types are generated.
77
+
78
+ Options:
79
+ --from <path> Path to schema file (default: dbmr.schema.json)
80
+ --models Generate only model files
81
+ --routes Generate only route files (including child routes and index)
82
+ --openapi Generate only OpenAPI spec + Swagger UI docs route
83
+ --tests Generate only test files
84
+ --migrations Generate only database migration files
85
+ --llm-docs Generate only LLM documentation (llms.txt + docs/llm.md)
86
+ --yes Accept all defaults without prompting
87
+ --json Output machine-readable JSON
88
+ --dry-run Report planned files without writing
89
+ --help Show this help message
90
+
91
+ Generated files:
92
+ models/<table>.js Model with CRUD operations
93
+ routes/<table>.js Express route handlers
94
+ routes/<parent>/<child>.js Child route (scoped by FK)
95
+ routes/docs.js Swagger UI at /docs
96
+ routes/index.js Route mounting index
97
+ migrations/<timestamp>_create_tables.sql Database migration (SQL adapters)
98
+ migrations/<timestamp>_create_<t>.js Database migration (NoSQL adapters)
99
+ test/<table>.test.js CRUD endpoint tests
100
+ openapi.json OpenAPI 3.0 spec
101
+ llms.txt LLM quick reference
102
+ docs/llm.md Full LLM reference
103
+
104
+ Examples:
105
+ db-model-router generate --from dbmr.schema.json
106
+ db-model-router generate --models --dry-run
107
+ db-model-router generate --routes --tests
108
+ db-model-router generate --migrations
109
+ db-model-router generate --from dbmr.schema.json --json`,
110
+
111
+ doctor: `Usage: db-model-router doctor [options]
112
+
113
+ Validate schema, check adapter driver dependencies, and verify generated
114
+ files are in sync with the schema.
115
+
116
+ Options:
117
+ --from <path> Path to schema file (default: dbmr.schema.json)
118
+ --yes Accept all defaults without prompting
119
+ --json Output machine-readable JSON
120
+ --help Show this help message
121
+
122
+ Checks performed:
123
+ 1. Schema validation Syntax and structure of dbmr.schema.json
124
+ 2. Dependency check Adapter driver present in package.json
125
+ 3. Sync check Generated files match what the schema would produce
126
+
127
+ Examples:
128
+ db-model-router doctor --from dbmr.schema.json
129
+ db-model-router doctor --json`,
130
+
131
+ diff: `Usage: db-model-router diff [options]
132
+
133
+ Preview changes between the current generated files and what the schema
134
+ would produce. Read-only — does not modify any files on disk.
135
+
136
+ Options:
137
+ --from <path> Path to schema file (default: dbmr.schema.json)
138
+ --yes Accept all defaults without prompting
139
+ --json Output machine-readable JSON
140
+ --help Show this help message
141
+
142
+ Output shows:
143
+ + Added New files that would be created
144
+ ~ Modified Files with changes (includes line diffs)
145
+ - Deleted Extra files that would be removed
146
+
147
+ Examples:
148
+ db-model-router diff --from dbmr.schema.json
149
+ db-model-router diff --json`,
150
+ };
151
+
152
+ /**
153
+ * Help command handler.
154
+ *
155
+ * When called with a command name in args (e.g. `help init`), prints
156
+ * detailed help for that command. Otherwise prints the general overview.
157
+ *
158
+ * @param {object} args - Parsed key-value args
159
+ * @param {object} flags - Universal flags
160
+ * @param {import('../flags').OutputContext} ctx - Output context
161
+ * @param {object} options - Injected dependencies
162
+ * @param {Function} options.printHelp - General help printer from main.js
163
+ */
164
+ async function help(args, flags, ctx, options) {
165
+ // The command to get help for is the first positional arg captured
166
+ // by parseFlags as a key-value. We also check args._command which
167
+ // main.js will inject.
168
+ const topic = args._command;
169
+
170
+ if (topic && COMMAND_HELP[topic]) {
171
+ ctx.log(COMMAND_HELP[topic]);
172
+ } else if (topic) {
173
+ ctx.log(`Unknown command: ${topic}\n`);
174
+ ctx.log(`Available commands: ${Object.keys(COMMAND_HELP).join(", ")}\n`);
175
+ ctx.log(`Run "db-model-router help <command>" for detailed help.`);
176
+ } else {
177
+ // No topic — print general help
178
+ if (options && options.printHelp) {
179
+ options.printHelp();
180
+ }
181
+ }
182
+ }
183
+
184
+ module.exports = help;
185
+ module.exports.COMMAND_HELP = COMMAND_HELP;
@@ -19,9 +19,10 @@ const DEFAULT_ANSWERS = {
19
19
  framework: "express",
20
20
  database: "postgres",
21
21
  session: "memory",
22
- rateLimiting: false,
23
- helmet: false,
24
- logger: false,
22
+ rateLimiting: true,
23
+ helmet: true,
24
+ logger: true,
25
+ loki: false,
25
26
  };
26
27
 
27
28
  /**
@@ -52,6 +53,7 @@ async function init(args, flags, ctx) {
52
53
  rateLimiting: !!(schema.options && schema.options.rateLimiting),
53
54
  helmet: !!(schema.options && schema.options.helmet),
54
55
  logger: !!(schema.options && schema.options.logger),
56
+ loki: !!(schema.options && schema.options.loki),
55
57
  };
56
58
  } else if (flags.yes) {
57
59
  // --yes with no schema: use defaults, but allow CLI overrides
@@ -66,9 +68,13 @@ async function init(args, flags, ctx) {
66
68
  answers = await promptUser(prefilled);
67
69
  }
68
70
 
71
+ // Resolve --output directory (relative to cwd)
72
+ // CLI --output flag takes precedence, then interactive prompt answer
73
+ const outputDir = args.output || answers.output || "";
74
+
69
75
  // --dry-run: report planned files without writing
70
76
  if (flags.dryRun) {
71
- const planned = planFiles(answers);
77
+ const planned = planFiles(answers, outputDir);
72
78
  if (flags.json) {
73
79
  ctx.result({
74
80
  files: planned,
@@ -89,10 +95,10 @@ async function init(args, flags, ctx) {
89
95
  ensurePackageJson();
90
96
 
91
97
  // Generate project files
92
- const generated = generateFiles(answers);
98
+ const generated = generateFiles(answers, outputDir);
93
99
 
94
100
  // Update package.json with deps and scripts
95
- updatePackageJson(answers);
101
+ updatePackageJson(answers, outputDir);
96
102
 
97
103
  // npm install (unless --no-install)
98
104
  const installed = !flags.noInstall;
@@ -103,7 +109,10 @@ async function init(args, flags, ctx) {
103
109
  // Output
104
110
  const allFiles = [
105
111
  ...generated.files,
106
- ...generated.migrationFiles.map((m) => `migrations/${m}`),
112
+ ...generated.migrationFiles.map((m) => {
113
+ const base = outputDir || ".";
114
+ return base === "." ? `migrations/${m}` : `${base}/migrations/${m}`;
115
+ }),
107
116
  ];
108
117
 
109
118
  if (flags.json) {
@@ -127,24 +136,43 @@ async function init(args, flags, ctx) {
127
136
  * This mirrors the file list from generateFiles() without writing anything.
128
137
  *
129
138
  * @param {object} answers
139
+ * @param {string} [outputDir] - relative output directory for source files
130
140
  * @returns {string[]}
131
141
  */
132
- function planFiles(answers) {
142
+ function planFiles(answers, outputDir) {
133
143
  const { isSql } = require("../init/generators");
144
+ const srcBase = outputDir || ".";
145
+ const prefix = srcBase === "." ? "" : srcBase + "/";
146
+
134
147
  const files = [
135
148
  "app.js",
136
149
  ".env",
137
150
  ".env.example",
138
- "middleware/logger.js",
139
- "migrate.js",
140
- "add_migration.js",
141
151
  ".gitignore",
142
- "migrations/<timestamp>_create_migrations_table" +
143
- (isSql(answers.database) ? ".sql" : ".js"),
152
+ "Dockerfile",
153
+ ".dockerignore",
144
154
  ];
145
155
 
156
+ // docker-compose.yml for databases that need Docker
157
+ if (answers.database !== "sqlite3") {
158
+ files.push("docker-compose.yml");
159
+ }
160
+
161
+ files.push(
162
+ `${prefix}middleware/logger.js`,
163
+ `${prefix}commons/session.js`,
164
+ `${prefix}commons/migrate.js`,
165
+ `${prefix}commons/add_migration.js`,
166
+ `${prefix}commons/security.js`,
167
+ `${prefix}commons/db.js`,
168
+ `${prefix}routes/health.js`,
169
+ `${prefix}routes/index.js`,
170
+ `${prefix}migrations/<timestamp>_create_migrations_table` +
171
+ (isSql(answers.database) ? ".sql" : ".js"),
172
+ );
173
+
146
174
  if (answers.session === "database" && isSql(answers.database)) {
147
- files.push("migrations/<timestamp>_create_sessions_table.sql");
175
+ files.push(`${prefix}migrations/<timestamp>_create_sessions_table.sql`);
148
176
  }
149
177
 
150
178
  return files;
@@ -40,15 +40,32 @@ function modelMetaToSchema(adapter, framework, models) {
40
40
  for (const m of models) {
41
41
  const columns = {};
42
42
 
43
- // Re-add columns from structure
43
+ const pk = m.primary_key || "id";
44
+ const opt = m.option || {};
45
+
46
+ // Add PK column as auto_increment
47
+ columns[pk] = "auto_increment";
48
+
49
+ // Add timestamp columns as datetime
50
+ if (opt.created_at) {
51
+ columns[opt.created_at] = "datetime";
52
+ }
53
+ if (opt.modified_at) {
54
+ columns[opt.modified_at] = "datetime";
55
+ }
56
+
57
+ // Add softDelete column
58
+ if (opt.safeDelete) {
59
+ columns[opt.safeDelete] = "boolean";
60
+ }
61
+
62
+ // Add remaining columns from structure
44
63
  for (const [col, rule] of Object.entries(m.structure)) {
45
64
  columns[col] = rule;
46
65
  }
47
66
 
48
- const pk = m.primary_key || "id";
49
67
  const unique = m.unique && m.unique.length > 0 ? [...m.unique] : [pk];
50
68
 
51
- const opt = m.option || {};
52
69
  const softDelete = opt.safeDelete || null;
53
70
  const timestamps = {
54
71
  created_at: opt.created_at || null,
@@ -62,6 +79,7 @@ function modelMetaToSchema(adapter, framework, models) {
62
79
  unique,
63
80
  softDelete,
64
81
  timestamps,
82
+ parent: null,
65
83
  };
66
84
  }
67
85