db-model-router 1.0.2 → 1.0.4

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.
package/docs/SKILL.md CHANGED
@@ -1,15 +1,47 @@
1
+ ---
2
+ name: db-model-router
3
+ description: Database-agnostic REST API generator for Node.js/Express. Define model → get CRUD API + Express routes. 10 adapters, identical API. Works with express or ultimate-express.
4
+ ---
5
+
1
6
  # db-model-router — LLM Skill Reference
2
7
 
3
- Database-agnostic REST API generator for Node.js/Express. Define model → get CRUD API + Express routes. 9 adapters, identical API.
8
+ Database-agnostic REST API generator for Node.js/Express. Define model → get CRUD API + Express routes. 10 adapters, identical API. Works with `express` or `ultimate-express`. Generated projects use ESM (`import`/`export`).
9
+
10
+ ## LLM Workflow (follow this order)
11
+
12
+ 1. **Scaffold**: `db-model-router init --framework express --database postgres --session redis --rateLimiting --helmet --logger --yes` (all flags → zero prompts)
13
+ 2. **Start infra**: `npm run docker:up` (starts DB + CloudBeaver + optional Loki/Grafana)
14
+ 3. **Migrations**: Write SQL/JS migration files into `migrations/`, then `npm run migrate`
15
+ 4. **Generate models**: `db-model-router generate --from dbmr.schema.json --models`
16
+ 5. **Generate routes + tests**: `db-model-router generate --from dbmr.schema.json --routes --tests`
17
+ 6. **Run**: `npm run dev`
18
+
19
+ Step 1 creates the project with ESM, Docker, and all infrastructure. Step 2 starts containers. Steps 3-5 define schema and generate code. Step 6 starts the server.
4
20
 
5
21
  ## Install
6
22
 
7
23
  ```bash
8
- npm install db-model-router <driver>
24
+ npm install db-model-router <framework> <driver>
9
25
  ```
10
26
 
27
+ Frameworks: `express` or `ultimate-express` (auto-detected, prefers ultimate-express)
11
28
  Drivers: `mysql2`, `pg`, `better-sqlite3`, `mongodb`, `mssql`, `oracledb`, `ioredis`, `@aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb`
12
29
 
30
+ ## Adapters
31
+
32
+ | Module Key | Driver | Default Port |
33
+ | ------------- | ------------------------------------------------ | ------------ |
34
+ | `mysql` | mysql2 | 3306 |
35
+ | `mariadb` | mysql2 | 3306 |
36
+ | `postgres` | pg | 5432 |
37
+ | `sqlite3` | better-sqlite3 | — |
38
+ | `mongodb` | mongodb | 27017 |
39
+ | `mssql` | mssql | 1433 |
40
+ | `cockroachdb` | pg | 26257 |
41
+ | `oracle` | oracledb | 1521 |
42
+ | `redis` | ioredis | 6379 |
43
+ | `dynamodb` | @aws-sdk/client-dynamodb + @aws-sdk/lib-dynamodb | — |
44
+
13
45
  ## Init → Connect → Model → Route
14
46
 
15
47
  ```js
@@ -42,12 +74,12 @@ app.use("/users", route(users));
42
74
 
43
75
  ## model(db, table, structure, pk, unique, option)
44
76
 
45
- | Param | Type | Description |
46
- | --------- | --------------- | --------------------------------------------------------------------------------------------------------------------- |
47
- | structure | `{col: "rule"}` | Types: `string\|integer\|numeric\|object`. Prefix `required\|` for NOT NULL. Exclude PK, timestamps, soft-delete cols |
48
- | pk | string | Primary key column. Default `"id"` |
49
- | unique | string[] | Columns for upsert conflict resolution |
50
- | option | object | `{ safeDelete, created_at, modified_at }` — column names or null |
77
+ | Param | Type | Description |
78
+ | --------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
79
+ | structure | `{col: "rule"}` | Types: `string\|integer\|numeric\|boolean\|object\|datetime\|auto_increment`. Prefix `required\|` for NOT NULL. PK, timestamps, soft-delete cols are auto-excluded by the code generator |
80
+ | pk | string | Primary key column. Convention: `<table>_id` |
81
+ | unique | string[] | Columns for upsert conflict resolution |
82
+ | option | object | `{ safeDelete, created_at, modified_at }` — column names or null |
51
83
 
52
84
  ## Model Methods (all async)
53
85
 
@@ -83,14 +115,22 @@ Structure: `[OR_groups[AND_conditions[col, op, val]]]`
83
115
  Ops: `= != < > <= >= like not like in not in`
84
116
 
85
117
  ```js
118
+ // AND: age > 25 AND type = 1
86
119
  [
87
120
  [
88
121
  ["age", ">", 25],
89
122
  ["type", "=", 1],
90
123
  ],
91
- ][([["name", "=", "A"]], [["name", "=", "B"]])][[["type", "in", [1, 2, 3]]]][ // AND // OR // IN
124
+ ][
125
+ // OR: name = "A" OR name = "B"
126
+ ([["name", "=", "A"]], [["name", "=", "B"]])
127
+ ][
128
+ // IN
129
+ [["type", "in", [1, 2, 3]]]
130
+ ][
131
+ // LIKE (auto-wraps with %)
92
132
  [["name", "like", "Ali"]]
93
- ]; // LIKE %Ali%
133
+ ];
94
134
  ```
95
135
 
96
136
  ## route(model, override?)
@@ -100,14 +140,14 @@ Generates Express Router with 9 endpoints:
100
140
  | Method | Path | Action |
101
141
  | ------ | ------ | -------------------------------- |
102
142
  | GET | `/:pk` | Get by PK |
103
- | POST | `/:id` | Insert single |
104
- | PUT | `/:id` | Update single (PK from URL) |
105
- | PATCH | `/:id` | Partial update |
106
- | DELETE | `/:id` | Delete single |
143
+ | POST | `/add` | Insert single |
144
+ | PUT | `/:pk` | Update single (PK from URL) |
145
+ | PATCH | `/:pk` | Partial update (PK from URL) |
146
+ | DELETE | `/:pk` | Delete single |
107
147
  | GET | `/` | List (page, size, sort, filters) |
108
148
  | POST | `/` | Bulk insert `{data:[...]}` |
109
149
  | PUT | `/` | Bulk update `{data:[...]}` |
110
- | DELETE | `/` | Bulk delete `{data:[...]}` |
150
+ | DELETE | `/` | Bulk delete |
111
151
 
112
152
  **Payload override** (multi-tenancy): `route(m, { tenant_id: "user.tenant_id" })` — maps cols to `req` paths via lodash.get.
113
153
 
@@ -115,29 +155,128 @@ Generates Express Router with 9 endpoints:
115
155
 
116
156
  ## CLI Tools
117
157
 
118
- ### generate-app (full scaffold)
158
+ ### Unified CLI: `db-model-router`
159
+
160
+ ```bash
161
+ db-model-router <command> [options]
162
+ db-model-router help <command> # detailed help for any command
163
+ ```
164
+
165
+ #### `init` — Project Scaffold
166
+
167
+ Scaffolds a complete ESM-based Express REST API project with Docker support.
168
+
169
+ ```bash
170
+ # Fully non-interactive (LLM-friendly — zero prompts)
171
+ db-model-router init --framework express --database postgres --session redis \
172
+ --rateLimiting --helmet --logger --yes
173
+
174
+ # With Loki logging + Grafana
175
+ db-model-router init --database postgres --logger --loki --yes
176
+
177
+ # With output directory
178
+ db-model-router init --database mysql --output backend --yes
179
+
180
+ # From schema file
181
+ db-model-router init --from dbmr.schema.json --yes --no-install
182
+ ```
183
+
184
+ | Flag | Description | Default |
185
+ | -------------------- | ----------------------------------------------------------------------------------------------------------- | ---------- |
186
+ | `--from <path>` | Read config from schema file | |
187
+ | `--framework <name>` | `express` or `ultimate-express` | (prompted) |
188
+ | `--database <name>` | `mysql`, `mariadb`, `postgres`, `sqlite3`, `mongodb`, `mssql`, `cockroachdb`, `oracle`, `redis`, `dynamodb` | (prompted) |
189
+ | `--db <name>` | Alias for `--database` | |
190
+ | `--session <type>` | `memory`, `redis`, `database` | (prompted) |
191
+ | `--output <dir>` | Backend source directory (relative to cwd) | (root) |
192
+ | `--rateLimiting` | Enable rate limiting | yes |
193
+ | `--helmet` | Enable Helmet security headers | yes |
194
+ | `--logger` | Enable Winston request logger | yes |
195
+ | `--loki` | Enable Loki transport + Loki/Grafana in docker-compose | no |
196
+
197
+ Generated files (ESM, `"type": "module"`):
198
+
199
+ ```
200
+ app.js Express entry point
201
+ .env / .env.example Environment config (random passwords)
202
+ Dockerfile node:alpine production image
203
+ docker-compose.yml DB + CloudBeaver + optional Loki/Grafana
204
+ .cloudbeaver/data-sources.json Auto-connects CloudBeaver to DB
205
+ .grafana/datasources.yml Auto-connects Grafana to Loki (when --loki)
206
+ <output>/commons/db.js Database init, connect, global.db
207
+ <output>/commons/session.js Session configuration
208
+ <output>/commons/security.js Helmet, rate limiting, custom headers
209
+ <output>/commons/migrate.js Migration runner (importable + standalone)
210
+ <output>/commons/add_migration.js Migration creator (importable + standalone)
211
+ <output>/middleware/logger.js Winston logger (+ Loki when LOKI_HOST is set)
212
+ <output>/route/index.js Central route mounting
213
+ <output>/route/health.js GET /health with DB connectivity check
214
+ <output>/migrations/ Initial migration files
215
+ ```
216
+
217
+ Docker services (auto-generated in docker-compose.yml):
218
+
219
+ | Service | When | Port | Notes |
220
+ | ----------- | --------------------------- | ------ | ------------------------------------- |
221
+ | Database | Always (except sqlite3) | Varies | Random password, bind mount `./data/` |
222
+ | Redis | `session=redis`, DB ≠ redis | 6379 | Session store |
223
+ | CloudBeaver | SQL/MongoDB databases | 8978 | Web DB admin, auto-connected |
224
+ | Loki | `--loki` | 3100 | Log aggregation |
225
+ | Grafana | `--loki` | 3001 | Log visualization |
226
+
227
+ Scripts: `start`, `dev`, `test`, `migrate`, `add_migration`, `docker:build`, `docker:up`, `docker:down`.
228
+
229
+ **Critical**: `db-model-router` is CJS. Generated ESM code must use: `import dbModelRouter from "db-model-router"; const { init, db } = dbModelRouter;` — NOT named imports.
230
+
231
+ #### `inspect` — DB Introspection
119
232
 
120
233
  ```bash
121
- db-model-router-generate-app --type mysql --env .env [--output ./dir] [--tables users,posts,posts.comments]
234
+ db-model-router inspect --type postgres --env .env [--out schema.json] [--tables t1,t2]
122
235
  ```
123
236
 
124
- Creates: `app.js`, `models/`, `routes/`, `middleware/logger.js`, `.env.example`, `.gitignore`, `migrations/`, `sessions/`, `openapi.json`
237
+ | Flag | Description |
238
+ | ------------------ | --------------------------------------- |
239
+ | `--type <adapter>` | Database adapter (required) |
240
+ | `--env <path>` | Path to .env file |
241
+ | `--out <path>` | Output file (default: dbmr.schema.json) |
242
+ | `--tables <list>` | Comma-separated table filter |
125
243
 
126
- ### generate-model (DB introspection → model files)
244
+ #### `generate` Code Generation
127
245
 
128
246
  ```bash
129
- db-model-router-generate-model --type <db> --env .env [--output ./models] [--tables t1,t2] [--schema public]
247
+ db-model-router generate --from dbmr.schema.json [--models] [--routes] [--openapi] [--tests] [--llm-docs]
130
248
  ```
131
249
 
132
- Auto-detects: PK, unique indexes, DEFAULT→optional, timestamp cols, soft-delete cols.
250
+ | Flag | Description |
251
+ | --------------- | --------------------------------------- |
252
+ | `--from <path>` | Schema file (default: dbmr.schema.json) |
253
+ | `--models` | Generate only model files |
254
+ | `--routes` | Generate only route files + index |
255
+ | `--openapi` | Generate only OpenAPI spec |
256
+ | `--tests` | Generate only test files |
257
+ | `--llm-docs` | Generate only LLM docs |
258
+
259
+ No flags = generate all. Generated routes/tests use ESM imports.
133
260
 
134
- ### generate-route (model files → route files + OpenAPI)
261
+ #### `doctor` Validation
135
262
 
136
263
  ```bash
137
- db-model-router-generate-route --models ./models --output ./routes [--tables posts,posts.comments]
264
+ db-model-router doctor [--from dbmr.schema.json] [--json]
138
265
  ```
139
266
 
140
- Dot notation `parent.child` creates nested routes: `parent/:parent_id/child` with FK scoping via `<parent_singular>_id`.
267
+ Checks: schema validation, dependency check, file sync.
268
+
269
+ #### `diff` — Preview Changes
270
+
271
+ ```bash
272
+ db-model-router diff [--from dbmr.schema.json] [--json]
273
+ ```
274
+
275
+ Shows added/modified/deleted files without writing.
276
+
277
+ #### Universal Flags (all commands)
278
+
279
+ `--yes`, `--json`, `--dry-run`, `--no-install`, `--help`
141
280
 
142
281
  ## Connection Configs
143
282
 
@@ -191,12 +330,90 @@ db.connect({ region, endpoint, accessKeyId, secretAccessKey });
191
330
 
192
331
  ## Rules
193
332
 
194
- 1. `init()` before `db.connect()`. Don't destructure `db` before `init()`.
195
- 2. `modelStructure` excludes: PK col, timestamp cols, soft-delete cols.
196
- 3. `update()`/`patch()` require PK in payload. `upsert()` PK is optional.
197
- 4. `findOne()` returns `false` on no match. `byId()` returns `null`.
198
- 5. Bulk ops wrap in `{ data: [...] }`. Single ops use flat object.
199
- 6. Timestamps auto-stripped from payloads. DB handles defaults/triggers.
200
- 7. `safeDelete` makes `remove()` soft-delete; all reads auto-filter deleted rows.
201
- 8. `list()` defaults: page=0, size=30. `sort` array: `["-col"]` for DESC.
202
- 9. CommonJS only (`require`). Use dynamic `import()` for ESM.
333
+ 1. `init()` before `db.connect()`. Don't destructure `db` before `init()` — it's a getter.
334
+ 2. Generated projects are ESM (`"type": "module"`). The library itself is CJS. Use default import: `import dbModelRouter from "db-model-router"; const { init, db } = dbModelRouter;`
335
+ 3. `model structure` excludes: PK col, timestamp cols, soft-delete cols.
336
+ 4. `update()`/`patch()` require PK in payload. `upsert()` PK is optional.
337
+ 5. `findOne()` returns `false` on no match. `byId()` returns `null`.
338
+ 6. Bulk ops wrap in `{ data: [...] }`. Single ops use flat object.
339
+ 7. Timestamps auto-stripped from payloads. DB handles defaults/triggers.
340
+ 8. `safeDelete` makes `remove()` soft-delete; all reads auto-filter deleted rows.
341
+ 9. `list()` defaults: page=0, size=30. `sort` array: `["-col"]` for DESC.
342
+ 10. Use the unified `db-model-router` CLI with `dbmr.schema.json` for new projects.
343
+ 11. `generate` auto-generates test files alongside routes. Tests use `supertest`.
344
+ 12. `global.db` is set by `commons/db.js` — accessible anywhere without imports.
345
+ 13. Logger dynamically loads `winston-loki` only when `LOKI_HOST` env var is set.
346
+ 14. Docker passwords are randomly generated and shared between `.env` and `docker-compose.yml`.
347
+
348
+ ## Environment Variables by Database
349
+
350
+ | Database | Variables |
351
+ | ----------- | ---------------------------------------------------------------------- |
352
+ | mysql | `PORT DB_HOST DB_PORT=3306 DB_NAME DB_USER DB_PASS` |
353
+ | mariadb | `PORT DB_HOST DB_PORT=3306 DB_NAME DB_USER DB_PASS` |
354
+ | postgres | `PORT DB_HOST DB_PORT=5432 DB_NAME DB_USER DB_PASS` |
355
+ | cockroachdb | `PORT DB_HOST DB_PORT=26257 DB_NAME DB_USER DB_PASS` |
356
+ | sqlite3 | `PORT DB_NAME=./data/data.db` |
357
+ | mongodb | `PORT DB_HOST DB_PORT=27017 DB_NAME DB_USER DB_PASS` |
358
+ | mssql | `PORT DB_HOST DB_PORT=1433 DB_NAME DB_USER DB_PASS` |
359
+ | oracle | `PORT DB_HOST DB_PORT=1521 DB_NAME DB_USER DB_PASS` |
360
+ | redis | `PORT DB_HOST DB_PORT=6379 DB_PASS` |
361
+ | dynamodb | `PORT AWS_REGION AWS_ENDPOINT AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY` |
362
+
363
+ When `session=redis` and database ≠ redis: adds `REDIS_HOST REDIS_PORT REDIS_PASS`.
364
+ When `logger=true`: adds `APP_NAME LOG_LEVEL LOKI_HOST` (LOKI_HOST empty unless `--loki`).
365
+
366
+ ## Schema-Driven Workflow
367
+
368
+ The `db-model-router` command uses `dbmr.schema.json` as the source of truth.
369
+
370
+ ### LLM Workflow (schema-driven)
371
+
372
+ 1. **Inspect** (existing DB): `db-model-router inspect --type postgres --env .env` → writes `dbmr.schema.json`
373
+ 2. **Edit** `dbmr.schema.json` — add relationships, tweak columns, set options
374
+ 3. **Generate**: `db-model-router generate --from dbmr.schema.json` → models, routes, tests, OpenAPI
375
+ 4. **Doctor**: `db-model-router doctor --from dbmr.schema.json` → validate schema + check sync
376
+ 5. **Run**: `npm run dev`
377
+
378
+ ### dbmr.schema.json Format
379
+
380
+ ```json
381
+ {
382
+ "adapter": "postgres",
383
+ "framework": "express",
384
+ "options": {
385
+ "session": "redis",
386
+ "rateLimiting": true,
387
+ "helmet": true,
388
+ "logger": true,
389
+ "loki": false
390
+ },
391
+ "tables": {
392
+ "users": {
393
+ "columns": {
394
+ "user_id": "auto_increment",
395
+ "name": "required|string",
396
+ "email": "required|string",
397
+ "age": "integer",
398
+ "is_deleted": "boolean",
399
+ "created_at": "datetime",
400
+ "updated_at": "datetime"
401
+ },
402
+ "pk": "user_id",
403
+ "unique": ["email"],
404
+ "softDelete": "is_deleted",
405
+ "timestamps": { "created_at": "created_at", "modified_at": "updated_at" }
406
+ }
407
+ },
408
+ "relationships": [
409
+ { "parent": "users", "child": "posts", "foreignKey": "user_id" }
410
+ ]
411
+ }
412
+ ```
413
+
414
+ Fields per table: `columns` (required, include ALL columns), `pk` (required, convention: `<table>_id`), `unique` (default `[pk]`), `softDelete`, `timestamps`.
415
+ Column rules: `(required|)?(string|integer|numeric|boolean|object|datetime|auto_increment)`.
416
+
417
+ - `auto_increment` — auto-incrementing PK (SERIAL in Postgres, AUTO_INCREMENT in MySQL/MariaDB)
418
+ - `datetime` — date/time columns (TIMESTAMP, DATETIME, DATE)
419
+ - `required|<type>` — NOT NULL constraint
@@ -46,4 +46,4 @@ CREATE TABLE users (
46
46
  );
47
47
  ```
48
48
 
49
- [← Back to main docs](../README.md)
49
+ [← Back to main docs](../../README.md)
@@ -50,4 +50,4 @@ aws dynamodb create-table \
50
50
  --endpoint-url http://localhost:8000
51
51
  ```
52
52
 
53
- [← Back to main docs](../README.md)
53
+ [← Back to main docs](../../README.md)
@@ -53,4 +53,4 @@ const users = model(
53
53
  );
54
54
  ```
55
55
 
56
- [← Back to main docs](../README.md)
56
+ [← Back to main docs](../../README.md)
@@ -52,4 +52,4 @@ CREATE TABLE users (
52
52
  );
53
53
  ```
54
54
 
55
- [← Back to main docs](../README.md)
55
+ [← Back to main docs](../../README.md)
@@ -49,4 +49,4 @@ CREATE TABLE users (
49
49
  );
50
50
  ```
51
51
 
52
- [← Back to main docs](../README.md)
52
+ [← Back to main docs](../../README.md)
@@ -47,4 +47,4 @@ CREATE TABLE users (
47
47
  );
48
48
  ```
49
49
 
50
- [← Back to main docs](../README.md)
50
+ [← Back to main docs](../../README.md)
@@ -50,4 +50,4 @@ const users = model(
50
50
  );
51
51
  ```
52
52
 
53
- [← Back to main docs](../README.md)
53
+ [← Back to main docs](../../README.md)
@@ -40,4 +40,4 @@ CREATE TABLE users (
40
40
  );
41
41
  ```
42
42
 
43
- [← Back to main docs](../README.md)
43
+ [← Back to main docs](../../README.md)
package/package.json CHANGED
@@ -1,12 +1,10 @@
1
1
  {
2
2
  "name": "db-model-router",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Generative API Creation using mysql2 and express libraries in node js",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
- "db-model-router-generate-model": "src/cli/generate-model.js",
8
- "db-model-router-generate-route": "src/cli/generate-route.js",
9
- "db-model-router-generate-app": "src/cli/generate-app.js"
7
+ "db-model-router": "src/cli/main.js"
10
8
  },
11
9
  "scripts": {
12
10
  "dev": "nodemon src/serve.js",
@@ -21,7 +19,8 @@
21
19
  "test:cockroachdb": "dotenv -e env/.env.cockroachdb -- mocha test/adapters/cockroachdb.*.test.js --timeout 15000 --exit",
22
20
  "test:mssql": "dotenv -e env/.env.mssql -- mocha test/adapters/mssql.*.test.js --timeout 30000 --exit",
23
21
  "test:properties": "mocha test/properties/*.property.test.js --timeout 30000 --exit",
24
- "test:all": "mocha test/adapters/*.test.js test/properties/*.property.test.js test/function.test.js --timeout 30000 --exit"
22
+ "test:all": "mocha test/adapters/*.test.js test/properties/*.property.test.js test/function.test.js --timeout 30000 --exit",
23
+ "clean:demo": "rm -rf demo/* demo/.*"
25
24
  },
26
25
  "repository": {
27
26
  "type": "git",
@@ -29,6 +28,13 @@
29
28
  },
30
29
  "keywords": [
31
30
  "mysql2",
31
+ "sqlite3",
32
+ "oracledb",
33
+ "postgres",
34
+ "pg",
35
+ "dynamodb",
36
+ "mongodb",
37
+ "mssql",
32
38
  "express",
33
39
  "ultimate-express",
34
40
  "generative",
@@ -43,6 +49,7 @@
43
49
  "homepage": "https://github.com/AvinashSKaranth/db-model-router#readme",
44
50
  "dependencies": {
45
51
  "dotenv": "^10.0.0",
52
+ "inquirer": "^8.2.6",
46
53
  "lodash": "^4.17.21",
47
54
  "node-input-validator": "^4.5.0"
48
55
  },
@@ -95,7 +102,6 @@
95
102
  }
96
103
  },
97
104
  "devDependencies": {
98
- "better-sqlite3": "^12.9.0",
99
105
  "dotenv-cli": "^11.0.0",
100
106
  "express": "^4.21.0",
101
107
  "faker": "^5.5.3",
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { parseSchema } = require("../../schema/schema-parser");
6
+ const { SchemaValidationError } = require("../../schema/schema-validator");
7
+ const { schemaToModelMeta } = require("../../schema/schema-to-meta");
8
+ const { computeDiff } = require("../diff-engine");
9
+
10
+ /**
11
+ * Diff command handler for the unified CLI.
12
+ *
13
+ * Compares the current generated files against what the schema would produce,
14
+ * reporting additions, modifications (with line diffs), and deletions.
15
+ * Does NOT modify any files on disk.
16
+ *
17
+ * Supported flags:
18
+ * --from Path to schema file (default: dbmr.schema.json)
19
+ * --json Output JSON result via ctx
20
+ *
21
+ * @param {object} args - Parsed key-value args
22
+ * @param {object} flags - Universal flags: { yes, json, dryRun, noInstall, help }
23
+ * @param {import('../flags').OutputContext} ctx - Output context
24
+ */
25
+ async function diff(args, flags, ctx) {
26
+ const schemaFile = args.from || "dbmr.schema.json";
27
+ const schemaPath = path.resolve(schemaFile);
28
+ const baseDir = process.cwd();
29
+
30
+ // --- 1. Read and parse schema ---
31
+ if (!fs.existsSync(schemaPath)) {
32
+ const msg = `Schema file not found: ${schemaFile}`;
33
+ if (flags.json) {
34
+ ctx.result({ error: true, code: "SCHEMA_NOT_FOUND", message: msg });
35
+ } else {
36
+ ctx.log(`Error: ${msg}`);
37
+ }
38
+ process.exitCode = 1;
39
+ return;
40
+ }
41
+
42
+ let schema;
43
+ try {
44
+ const raw = fs.readFileSync(schemaPath, "utf8");
45
+ schema = parseSchema(raw);
46
+ } catch (err) {
47
+ const msg = `Schema parse error: ${err.message}`;
48
+ if (flags.json) {
49
+ ctx.result({
50
+ error: true,
51
+ code: "SCHEMA_VALIDATION",
52
+ message: msg,
53
+ errors: err.errors || [],
54
+ });
55
+ } else {
56
+ ctx.log(`Error: ${msg}`);
57
+ }
58
+ process.exitCode = 1;
59
+ return;
60
+ }
61
+
62
+ // --- 2. Compute diff ---
63
+ const meta = schemaToModelMeta(schema);
64
+ const relationships = schema.relationships || [];
65
+ const result = computeDiff(baseDir, meta, relationships);
66
+
67
+ // --- 3. Output results ---
68
+ if (flags.json) {
69
+ ctx.result({
70
+ added: result.added,
71
+ modified: result.modified,
72
+ deleted: result.deleted,
73
+ });
74
+ } else {
75
+ const total =
76
+ result.added.length + result.modified.length + result.deleted.length;
77
+
78
+ if (total === 0) {
79
+ ctx.log("All generated files are up to date.");
80
+ return;
81
+ }
82
+
83
+ if (result.added.length > 0) {
84
+ ctx.log("Added (new files to create):");
85
+ for (const f of result.added) {
86
+ ctx.log(` + ${f}`);
87
+ }
88
+ }
89
+
90
+ if (result.modified.length > 0) {
91
+ ctx.log("Modified (files with changes):");
92
+ for (const m of result.modified) {
93
+ ctx.log(` ~ ${m.file}`);
94
+ // Display line diffs indented
95
+ for (const line of m.diff.split("\n")) {
96
+ if (line) {
97
+ ctx.log(` ${line}`);
98
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ if (result.deleted.length > 0) {
104
+ ctx.log("Deleted (extra files to remove):");
105
+ for (const f of result.deleted) {
106
+ ctx.log(` - ${f}`);
107
+ }
108
+ }
109
+
110
+ ctx.log(`\n${total} file(s) differ.`);
111
+ }
112
+ }
113
+
114
+ module.exports = diff;