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/README.md +317 -202
- package/docs/SKILL.md +250 -33
- package/docs/adapters/cockroachdb.md +1 -1
- package/docs/adapters/dynamodb.md +1 -1
- package/docs/adapters/mongodb.md +1 -1
- package/docs/adapters/mssql.md +1 -1
- package/docs/adapters/oracle.md +1 -1
- package/docs/adapters/postgres.md +1 -1
- package/docs/adapters/redis.md +1 -1
- package/docs/adapters/sqlite3.md +1 -1
- package/package.json +12 -6
- package/src/cli/commands/diff.js +114 -0
- package/src/cli/commands/doctor.js +181 -0
- package/src/cli/commands/generate-llm-docs.js +418 -0
- package/src/cli/commands/generate.js +240 -0
- package/src/cli/commands/help.js +180 -0
- package/src/cli/commands/init.js +181 -0
- package/src/cli/commands/inspect.js +222 -0
- package/src/cli/diff-engine.js +198 -0
- package/src/cli/flags.js +112 -0
- package/src/cli/generate-model.js +5 -4
- package/src/cli/generate-route.js +255 -14
- package/src/cli/init/dependencies.js +92 -0
- package/src/cli/init/generators.js +1791 -0
- package/src/cli/init/prompt.js +191 -0
- package/src/cli/init.js +404 -0
- package/src/cli/main.js +175 -0
- package/src/commons/model.js +5 -6
- package/src/commons/route.js +24 -0
- package/src/index.js +2 -0
- package/src/schema/schema-parser.js +78 -0
- package/src/schema/schema-printer.js +77 -0
- package/src/schema/schema-to-meta.js +78 -0
- package/src/schema/schema-validator.js +255 -0
- package/src/serve.js +5 -3
- package/docs/README.md +0 -208
- package/src/cli/generate-app.js +0 -359
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const VALID_ADAPTERS = new Set([
|
|
4
|
+
"mysql",
|
|
5
|
+
"mariadb",
|
|
6
|
+
"postgres",
|
|
7
|
+
"sqlite3",
|
|
8
|
+
"mongodb",
|
|
9
|
+
"mssql",
|
|
10
|
+
"cockroachdb",
|
|
11
|
+
"oracle",
|
|
12
|
+
"redis",
|
|
13
|
+
"dynamodb",
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const VALID_FRAMEWORKS = new Set(["express", "ultimate-express"]);
|
|
17
|
+
|
|
18
|
+
const COLUMN_RULE_RE =
|
|
19
|
+
/^(required\|)?(string|integer|numeric|boolean|object|datetime|auto_increment)$/;
|
|
20
|
+
|
|
21
|
+
class SchemaValidationError extends Error {
|
|
22
|
+
constructor(errors) {
|
|
23
|
+
super(`Schema validation failed: ${errors.length} error(s)`);
|
|
24
|
+
this.errors = errors;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Validate a raw schema object and collect all errors.
|
|
30
|
+
* @param {object} raw — parsed JSON object
|
|
31
|
+
* @returns {{ valid: boolean, errors: Array<{ path: string, message: string }> }}
|
|
32
|
+
*/
|
|
33
|
+
function validateSchema(raw) {
|
|
34
|
+
const errors = [];
|
|
35
|
+
|
|
36
|
+
if (raw == null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
37
|
+
errors.push({ path: "", message: "Schema must be a non-null object" });
|
|
38
|
+
return { valid: false, errors };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// adapter
|
|
42
|
+
if (!raw.adapter || typeof raw.adapter !== "string") {
|
|
43
|
+
errors.push({
|
|
44
|
+
path: "adapter",
|
|
45
|
+
message: "adapter is required and must be a string",
|
|
46
|
+
});
|
|
47
|
+
} else if (!VALID_ADAPTERS.has(raw.adapter)) {
|
|
48
|
+
errors.push({
|
|
49
|
+
path: "adapter",
|
|
50
|
+
message: `Invalid adapter "${raw.adapter}". Must be one of: ${[...VALID_ADAPTERS].join(", ")}`,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// framework
|
|
55
|
+
if (!raw.framework || typeof raw.framework !== "string") {
|
|
56
|
+
errors.push({
|
|
57
|
+
path: "framework",
|
|
58
|
+
message: "framework is required and must be a string",
|
|
59
|
+
});
|
|
60
|
+
} else if (!VALID_FRAMEWORKS.has(raw.framework)) {
|
|
61
|
+
errors.push({
|
|
62
|
+
path: "framework",
|
|
63
|
+
message: `Invalid framework "${raw.framework}". Must be one of: ${[...VALID_FRAMEWORKS].join(", ")}`,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// tables
|
|
68
|
+
if (
|
|
69
|
+
raw.tables == null ||
|
|
70
|
+
typeof raw.tables !== "object" ||
|
|
71
|
+
Array.isArray(raw.tables)
|
|
72
|
+
) {
|
|
73
|
+
errors.push({
|
|
74
|
+
path: "tables",
|
|
75
|
+
message: "tables is required and must be an object",
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
validateTables(raw.tables, errors);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// relationships
|
|
82
|
+
if (raw.relationships !== undefined) {
|
|
83
|
+
if (!Array.isArray(raw.relationships)) {
|
|
84
|
+
errors.push({
|
|
85
|
+
path: "relationships",
|
|
86
|
+
message: "relationships must be an array",
|
|
87
|
+
});
|
|
88
|
+
} else {
|
|
89
|
+
const tableNames =
|
|
90
|
+
raw.tables &&
|
|
91
|
+
typeof raw.tables === "object" &&
|
|
92
|
+
!Array.isArray(raw.tables)
|
|
93
|
+
? new Set(Object.keys(raw.tables))
|
|
94
|
+
: new Set();
|
|
95
|
+
validateRelationships(raw.relationships, tableNames, errors);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// options
|
|
100
|
+
if (raw.options !== undefined) {
|
|
101
|
+
if (
|
|
102
|
+
raw.options == null ||
|
|
103
|
+
typeof raw.options !== "object" ||
|
|
104
|
+
Array.isArray(raw.options)
|
|
105
|
+
) {
|
|
106
|
+
errors.push({ path: "options", message: "options must be an object" });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { valid: errors.length === 0, errors };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Validate all table entries.
|
|
115
|
+
*/
|
|
116
|
+
function validateTables(tables, errors) {
|
|
117
|
+
for (const [tableName, tableDef] of Object.entries(tables)) {
|
|
118
|
+
const basePath = `tables.${tableName}`;
|
|
119
|
+
|
|
120
|
+
if (
|
|
121
|
+
tableDef == null ||
|
|
122
|
+
typeof tableDef !== "object" ||
|
|
123
|
+
Array.isArray(tableDef)
|
|
124
|
+
) {
|
|
125
|
+
errors.push({
|
|
126
|
+
path: basePath,
|
|
127
|
+
message: `Table "${tableName}" must be an object`,
|
|
128
|
+
});
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// columns
|
|
133
|
+
if (
|
|
134
|
+
tableDef.columns == null ||
|
|
135
|
+
typeof tableDef.columns !== "object" ||
|
|
136
|
+
Array.isArray(tableDef.columns)
|
|
137
|
+
) {
|
|
138
|
+
errors.push({
|
|
139
|
+
path: `${basePath}.columns`,
|
|
140
|
+
message: `Table "${tableName}" must have a columns object`,
|
|
141
|
+
});
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const columnNames = new Set(Object.keys(tableDef.columns));
|
|
146
|
+
const pk = tableDef.pk || "id";
|
|
147
|
+
|
|
148
|
+
// Validate each column rule
|
|
149
|
+
for (const [colName, rule] of Object.entries(tableDef.columns)) {
|
|
150
|
+
if (typeof rule !== "string" || !COLUMN_RULE_RE.test(rule)) {
|
|
151
|
+
errors.push({
|
|
152
|
+
path: `${basePath}.columns.${colName}`,
|
|
153
|
+
message: `Invalid column rule "${rule}" for column "${colName}". Must match pattern: (required|)?(string|integer|numeric|boolean|object)`,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Validate unique entries
|
|
159
|
+
if (tableDef.unique !== undefined) {
|
|
160
|
+
if (!Array.isArray(tableDef.unique)) {
|
|
161
|
+
errors.push({
|
|
162
|
+
path: `${basePath}.unique`,
|
|
163
|
+
message: `unique must be an array in table "${tableName}"`,
|
|
164
|
+
});
|
|
165
|
+
} else {
|
|
166
|
+
for (let i = 0; i < tableDef.unique.length; i++) {
|
|
167
|
+
const entry = tableDef.unique[i];
|
|
168
|
+
if (typeof entry !== "string") {
|
|
169
|
+
errors.push({
|
|
170
|
+
path: `${basePath}.unique[${i}]`,
|
|
171
|
+
message: `unique entry must be a string in table "${tableName}"`,
|
|
172
|
+
});
|
|
173
|
+
} else if (entry !== pk && !columnNames.has(entry)) {
|
|
174
|
+
errors.push({
|
|
175
|
+
path: `${basePath}.unique[${i}]`,
|
|
176
|
+
message: `unique entry "${entry}" does not match any column or the primary key "${pk}" in table "${tableName}"`,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Validate softDelete
|
|
184
|
+
if (tableDef.softDelete !== undefined && tableDef.softDelete !== null) {
|
|
185
|
+
if (typeof tableDef.softDelete !== "string") {
|
|
186
|
+
errors.push({
|
|
187
|
+
path: `${basePath}.softDelete`,
|
|
188
|
+
message: `softDelete must be a string in table "${tableName}"`,
|
|
189
|
+
});
|
|
190
|
+
} else if (!columnNames.has(tableDef.softDelete)) {
|
|
191
|
+
errors.push({
|
|
192
|
+
path: `${basePath}.softDelete`,
|
|
193
|
+
message: `softDelete column "${tableDef.softDelete}" does not exist in table "${tableName}"`,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Validate all relationship entries.
|
|
202
|
+
*/
|
|
203
|
+
function validateRelationships(relationships, tableNames, errors) {
|
|
204
|
+
for (let i = 0; i < relationships.length; i++) {
|
|
205
|
+
const rel = relationships[i];
|
|
206
|
+
const basePath = `relationships[${i}]`;
|
|
207
|
+
|
|
208
|
+
if (rel == null || typeof rel !== "object" || Array.isArray(rel)) {
|
|
209
|
+
errors.push({
|
|
210
|
+
path: basePath,
|
|
211
|
+
message: "Each relationship must be an object",
|
|
212
|
+
});
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!rel.parent || typeof rel.parent !== "string") {
|
|
217
|
+
errors.push({
|
|
218
|
+
path: `${basePath}.parent`,
|
|
219
|
+
message: "Relationship must have a parent string",
|
|
220
|
+
});
|
|
221
|
+
} else if (!tableNames.has(rel.parent)) {
|
|
222
|
+
errors.push({
|
|
223
|
+
path: `${basePath}.parent`,
|
|
224
|
+
message: `Relationship parent "${rel.parent}" does not reference an existing table`,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!rel.child || typeof rel.child !== "string") {
|
|
229
|
+
errors.push({
|
|
230
|
+
path: `${basePath}.child`,
|
|
231
|
+
message: "Relationship must have a child string",
|
|
232
|
+
});
|
|
233
|
+
} else if (!tableNames.has(rel.child)) {
|
|
234
|
+
errors.push({
|
|
235
|
+
path: `${basePath}.child`,
|
|
236
|
+
message: `Relationship child "${rel.child}" does not reference an existing table`,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!rel.foreignKey || typeof rel.foreignKey !== "string") {
|
|
241
|
+
errors.push({
|
|
242
|
+
path: `${basePath}.foreignKey`,
|
|
243
|
+
message: "Relationship must have a foreignKey string",
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
module.exports = {
|
|
250
|
+
SchemaValidationError,
|
|
251
|
+
validateSchema,
|
|
252
|
+
VALID_ADAPTERS,
|
|
253
|
+
VALID_FRAMEWORKS,
|
|
254
|
+
COLUMN_RULE_RE,
|
|
255
|
+
};
|
package/src/serve.js
CHANGED
|
@@ -21,8 +21,10 @@ if (process.env.NODE_ENV === "TEST") {
|
|
|
21
21
|
port = process.env.port;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
const {
|
|
25
|
-
|
|
24
|
+
const { init, model, route } = require("./index");
|
|
25
|
+
const dbModule = require("./index");
|
|
26
|
+
init("mysql");
|
|
27
|
+
dbModule.db.connect({
|
|
26
28
|
connectionLimit: 100,
|
|
27
29
|
host: process.env.DB_HOST,
|
|
28
30
|
user: process.env.DB_USER,
|
|
@@ -32,7 +34,7 @@ db.connect({
|
|
|
32
34
|
charset: "utf8mb4",
|
|
33
35
|
});
|
|
34
36
|
const test = model(
|
|
35
|
-
db,
|
|
37
|
+
dbModule.db,
|
|
36
38
|
"test",
|
|
37
39
|
{
|
|
38
40
|
test_id: "required|integer",
|
package/docs/README.md
DELETED
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
# db-model-router
|
|
2
|
-
|
|
3
|
-
A database-agnostic REST API generator for Node.js. Works with Express or ultimate-express (a high-performance drop-in replacement). Define a model, get a full CRUD API with filtering, pagination, and bulk operations — backed by any of 9 supported databases.
|
|
4
|
-
|
|
5
|
-
## Supported Adapters
|
|
6
|
-
|
|
7
|
-
| Adapter | Module Key | Driver | Install |
|
|
8
|
-
| ---------------------------------------- | ------------- | ------------------------ | ---------------------------------------------------------------------- |
|
|
9
|
-
| [MySQL](#mysql-example) | `mysql` | mysql2 | `npm i db-model-router mysql2` |
|
|
10
|
-
| [PostgreSQL](./adapters/postgres.md) | `postgres` | pg | `npm i db-model-router pg` |
|
|
11
|
-
| [SQLite3](./adapters/sqlite3.md) | `sqlite3` | better-sqlite3 | `npm i db-model-router better-sqlite3` |
|
|
12
|
-
| [MongoDB](./adapters/mongodb.md) | `mongodb` | mongodb | `npm i db-model-router mongodb` |
|
|
13
|
-
| [MSSQL](./adapters/mssql.md) | `mssql` | mssql | `npm i db-model-router mssql` |
|
|
14
|
-
| [CockroachDB](./adapters/cockroachdb.md) | `cockroachdb` | pg | `npm i db-model-router pg` |
|
|
15
|
-
| [Oracle](./adapters/oracle.md) | `oracle` | oracledb | `npm i db-model-router oracledb` |
|
|
16
|
-
| [Redis](./adapters/redis.md) | `redis` | ioredis | `npm i db-model-router ioredis` |
|
|
17
|
-
| [DynamoDB](./adapters/dynamodb.md) | `dynamodb` | @aws-sdk/client-dynamodb | `npm i db-model-router @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb` |
|
|
18
|
-
|
|
19
|
-
## Installation
|
|
20
|
-
|
|
21
|
-
Install the core package, your preferred Express framework, and the driver for your database:
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
# Pick your Express framework (one of the two)
|
|
25
|
-
npm install express
|
|
26
|
-
# OR for ~6x faster performance:
|
|
27
|
-
npm install ultimate-express
|
|
28
|
-
|
|
29
|
-
# Then install db-model-router + your database driver
|
|
30
|
-
npm install db-model-router <driver>
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Both `express` and `ultimate-express` are optional peer dependencies — the library auto-detects which one is installed (preferring `ultimate-express` when both are present). All database drivers are also optional peer dependencies.
|
|
34
|
-
|
|
35
|
-
## MySQL Example
|
|
36
|
-
|
|
37
|
-
### 1. Connect
|
|
38
|
-
|
|
39
|
-
```js
|
|
40
|
-
const { init, db, model, route } = require("db-model-router");
|
|
41
|
-
|
|
42
|
-
// Default adapter is mysql, so init() is optional
|
|
43
|
-
db.connect({
|
|
44
|
-
host: "localhost",
|
|
45
|
-
port: 3306,
|
|
46
|
-
user: "root",
|
|
47
|
-
password: "password",
|
|
48
|
-
database: "my_app",
|
|
49
|
-
connectionLimit: 100,
|
|
50
|
-
});
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### 2. Define a Model
|
|
54
|
-
|
|
55
|
-
```js
|
|
56
|
-
const users = model(
|
|
57
|
-
db,
|
|
58
|
-
"users",
|
|
59
|
-
{
|
|
60
|
-
name: "required|string",
|
|
61
|
-
email: "required|string",
|
|
62
|
-
age: "required|integer",
|
|
63
|
-
meta: "object",
|
|
64
|
-
},
|
|
65
|
-
"id",
|
|
66
|
-
["email"],
|
|
67
|
-
{ safeDelete: "is_deleted" },
|
|
68
|
-
);
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
Schema types: `string`, `integer`, `numeric`, `object`. Prefix with `required|` to enforce on insert/update.
|
|
72
|
-
|
|
73
|
-
### 3. Mount REST Routes
|
|
74
|
-
|
|
75
|
-
```js
|
|
76
|
-
// Works with either express or ultimate-express
|
|
77
|
-
const express = require("express"); // or require("ultimate-express")
|
|
78
|
-
const app = express();
|
|
79
|
-
app.use(express.json());
|
|
80
|
-
app.use("/users", route(users));
|
|
81
|
-
app.listen(3000);
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
This creates 9 endpoints:
|
|
85
|
-
|
|
86
|
-
| Method | Path | Description |
|
|
87
|
-
| ------ | ------------ | ------------------------------- |
|
|
88
|
-
| GET | `/users/:id` | Get one record by PK |
|
|
89
|
-
| POST | `/users/:id` | Insert a single record |
|
|
90
|
-
| PUT | `/users/:id` | Update a single record |
|
|
91
|
-
| PATCH | `/users/:id` | Partial update (changed fields) |
|
|
92
|
-
| DELETE | `/users/:id` | Delete a single record |
|
|
93
|
-
| GET | `/users/` | List with pagination |
|
|
94
|
-
| POST | `/users/` | Bulk insert (`{ data: [...] }`) |
|
|
95
|
-
| PUT | `/users/` | Bulk update (`{ data: [...] }`) |
|
|
96
|
-
| DELETE | `/users/` | Bulk delete |
|
|
97
|
-
|
|
98
|
-
### 4. Payload Override
|
|
99
|
-
|
|
100
|
-
Inject values from the request into every payload (useful for multi-tenant apps):
|
|
101
|
-
|
|
102
|
-
```js
|
|
103
|
-
app.use("/users", route(users, { user_id: "user.user_id" }));
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## Model API
|
|
107
|
-
|
|
108
|
-
All model methods are async.
|
|
109
|
-
|
|
110
|
-
### insert / update / patch / upsert
|
|
111
|
-
|
|
112
|
-
```js
|
|
113
|
-
const user = await users.insert({ name: "Alice", email: "a@b.com", age: 30 });
|
|
114
|
-
const bulk = await users.insert({ data: [{ ... }, { ... }] });
|
|
115
|
-
const updated = await users.update({ id: 1, name: "Alice V2", email: "a@b.com", age: 31 });
|
|
116
|
-
const patched = await users.patch({ id: 1, age: 35 }); // partial — only updates age
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
### byId / find / findOne / list
|
|
120
|
-
|
|
121
|
-
```js
|
|
122
|
-
await users.byId(1); // record or null
|
|
123
|
-
await users.find({ name: "Alice" }); // { data: [...], count }
|
|
124
|
-
await users.findOne({ email: "a@b.com" }); // record or false
|
|
125
|
-
await users.list({ page: 0, size: 10, sort: ["-age"] }); // { data: [...], count }
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### remove
|
|
129
|
-
|
|
130
|
-
```js
|
|
131
|
-
await users.remove(1);
|
|
132
|
-
await users.remove({ name: "Bob" });
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## Filter System
|
|
136
|
-
|
|
137
|
-
Structure: `[OR_groups[AND_conditions[column, operator, value]]]`
|
|
138
|
-
|
|
139
|
-
Operators: `=`, `like`, `not like`, `in`, `not in`, `<`, `>`, `<=`, `>=`, `!=`
|
|
140
|
-
|
|
141
|
-
```js
|
|
142
|
-
// Alice AND age 30
|
|
143
|
-
await db.get("users", [
|
|
144
|
-
[
|
|
145
|
-
["name", "=", "Alice"],
|
|
146
|
-
["age", "=", 30],
|
|
147
|
-
],
|
|
148
|
-
]);
|
|
149
|
-
|
|
150
|
-
// Alice OR age > 30
|
|
151
|
-
await db.get("users", [[["name", "=", "Alice"]], [["age", ">", 30]]]);
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
## Switching Adapters
|
|
155
|
-
|
|
156
|
-
```js
|
|
157
|
-
const { init, db, model, route } = require("db-model-router");
|
|
158
|
-
init("postgres"); // or "mongodb", "sqlite3", "mssql", etc.
|
|
159
|
-
db.connect({
|
|
160
|
-
host: "localhost",
|
|
161
|
-
port: 5432,
|
|
162
|
-
user: "postgres",
|
|
163
|
-
password: "password",
|
|
164
|
-
database: "my_app",
|
|
165
|
-
});
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
The model and route APIs remain identical across all adapters.
|
|
169
|
-
|
|
170
|
-
## CLI Tools
|
|
171
|
-
|
|
172
|
-
### generate-app
|
|
173
|
-
|
|
174
|
-
Scaffolds a complete Express REST API from an existing database.
|
|
175
|
-
|
|
176
|
-
```bash
|
|
177
|
-
db-model-router-generate-app --type mysql --env .env
|
|
178
|
-
db-model-router-generate-app --type sqlite3 --database ./myapp.db --output ./my-api
|
|
179
|
-
db-model-router-generate-app --type postgres --env .env --tables users,posts,posts.comments
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
Creates: `app.js`, `models/`, `routes/`, `middleware/logger.js`, `.env.example`, `openapi.json`
|
|
183
|
-
|
|
184
|
-
### generate-model
|
|
185
|
-
|
|
186
|
-
Introspects DB → generates model files with auto-detected PK, unique indexes, timestamps, soft-delete.
|
|
187
|
-
|
|
188
|
-
```bash
|
|
189
|
-
db-model-router-generate-model --type mysql --env .env --output ./models [--tables users,posts]
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
### generate-route
|
|
193
|
-
|
|
194
|
-
Generates route files + OpenAPI spec from models. Supports parent-child via dot notation.
|
|
195
|
-
|
|
196
|
-
```bash
|
|
197
|
-
db-model-router-generate-route --models ./models --output ./routes [--tables posts,posts.comments]
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
`posts.comments` → nested route `posts/:post_id/comments` with FK scoping.
|
|
201
|
-
|
|
202
|
-
## License
|
|
203
|
-
|
|
204
|
-
Apache-2.0
|
|
205
|
-
|
|
206
|
-
## LLM Skill Reference
|
|
207
|
-
|
|
208
|
-
For AI/LLM integration, see the [Skill Reference](./SKILL.md).
|