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.
- package/README.md +283 -25
- package/TODO.md +14 -0
- package/dbmr.schema.json +333 -0
- package/demo/.dockerignore +7 -0
- package/demo/.env.example +13 -0
- package/demo/Dockerfile +20 -0
- package/demo/app.js +37 -0
- package/demo/commons/add_migration.js +43 -0
- package/demo/commons/db.js +17 -0
- package/demo/commons/migrate.js +65 -0
- package/demo/commons/security.js +30 -0
- package/demo/commons/session.js +13 -0
- package/demo/dbmr.schema.json +362 -0
- package/demo/docs/llm.md +197 -0
- package/demo/llms.txt +70 -0
- package/demo/middleware/logger.js +67 -0
- package/demo/migrations/20260430155808_create_migrations_table.sql +6 -0
- package/demo/migrations/20260430155809_create_tables.sql +207 -0
- package/demo/models/addresses.js +22 -0
- package/demo/models/cart_items.js +18 -0
- package/demo/models/carts.js +16 -0
- package/demo/models/categories.js +20 -0
- package/demo/models/coupons.js +23 -0
- package/demo/models/order_items.js +21 -0
- package/demo/models/orders.js +25 -0
- package/demo/models/payments.js +21 -0
- package/demo/models/product_images.js +18 -0
- package/demo/models/product_reviews.js +20 -0
- package/demo/models/product_variants.js +20 -0
- package/demo/models/products.js +30 -0
- package/demo/models/shipments.js +19 -0
- package/demo/models/users.js +19 -0
- package/demo/models/wishlists.js +15 -0
- package/demo/openapi.json +5872 -0
- package/demo/package-lock.json +2810 -0
- package/demo/package.json +34 -0
- package/demo/routes/addresses.js +6 -0
- package/demo/routes/carts/cart_items.js +7 -0
- package/demo/routes/carts.js +6 -0
- package/demo/routes/categories.js +6 -0
- package/demo/routes/coupons.js +6 -0
- package/demo/routes/docs.js +18 -0
- package/demo/routes/health.js +35 -0
- package/demo/routes/index.js +39 -0
- package/demo/routes/orders/order_items.js +7 -0
- package/demo/routes/orders/payments.js +7 -0
- package/demo/routes/orders/shipments.js +7 -0
- package/demo/routes/orders.js +6 -0
- package/demo/routes/products/product_images.js +7 -0
- package/demo/routes/products/product_reviews.js +7 -0
- package/demo/routes/products/product_variants.js +7 -0
- package/demo/routes/products.js +6 -0
- package/demo/routes/users.js +6 -0
- package/demo/routes/wishlists.js +6 -0
- package/docker-compose.yml +1 -1
- package/package.json +16 -7
- package/scripts/demo-create.js +47 -0
- package/skill/SKILL.md +464 -0
- package/skill/references/cockroachdb.md +49 -0
- package/skill/references/dynamodb.md +53 -0
- package/skill/references/mongodb.md +56 -0
- package/skill/references/mssql.md +55 -0
- package/skill/references/oracle.md +52 -0
- package/skill/references/postgres.md +50 -0
- package/skill/references/redis.md +53 -0
- package/skill/references/sqlite3.md +43 -0
- package/src/cli/commands/generate.js +58 -17
- package/src/cli/commands/help.js +185 -0
- package/src/cli/commands/init.js +42 -14
- package/src/cli/commands/inspect.js +21 -3
- package/src/cli/diff-engine.js +52 -22
- package/src/cli/generate-docs-route.js +31 -0
- package/src/cli/generate-migration.js +356 -0
- package/src/cli/generate-model.js +5 -4
- package/src/cli/generate-route.js +79 -45
- package/src/cli/init/dependencies.js +17 -5
- package/src/cli/init/generators.js +1073 -64
- package/src/cli/init/prompt.js +37 -5
- package/src/cli/init.js +148 -25
- package/src/cli/main.js +90 -10
- package/src/cockroachdb/db.js +90 -59
- package/src/commons/route.js +20 -20
- package/src/commons/validator.js +58 -1
- package/src/dynamodb/db.js +50 -27
- package/src/index.js +2 -0
- package/src/mongodb/db.js +1 -0
- package/src/mssql/db.js +89 -61
- package/src/mysql/db.js +1 -0
- package/src/oracle/db.js +1 -0
- package/src/postgres/db.js +61 -41
- package/src/redis/db.js +1 -0
- package/src/schema/schema-parser.js +43 -1
- package/src/schema/schema-printer.js +8 -5
- package/src/schema/schema-to-meta.js +4 -0
- package/src/schema/schema-validator.js +20 -1
- package/src/sqlite3/db.js +1 -0
- package/docs/SKILL.md +0 -374
|
@@ -25,10 +25,12 @@ function safeVarName(name) {
|
|
|
25
25
|
*/
|
|
26
26
|
function generateRouteFile(tableName, modelsRelPath) {
|
|
27
27
|
const varName = safeVarName(tableName);
|
|
28
|
-
return `
|
|
29
|
-
|
|
28
|
+
return `import dbModelRouter from "db-model-router";
|
|
29
|
+
import ${varName} from "${modelsRelPath}/${tableName}.js";
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
const { route } = dbModelRouter;
|
|
32
|
+
|
|
33
|
+
export default route(${varName});
|
|
32
34
|
`;
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -43,20 +45,29 @@ function generateChildRouteFile(
|
|
|
43
45
|
modelsRelPath,
|
|
44
46
|
) {
|
|
45
47
|
const varName = safeVarName(childTable);
|
|
46
|
-
return `
|
|
47
|
-
|
|
48
|
+
return `import dbModelRouter from "db-model-router";
|
|
49
|
+
import ${varName} from "${modelsRelPath}/${childTable}.js";
|
|
50
|
+
|
|
51
|
+
const { route } = dbModelRouter;
|
|
48
52
|
|
|
49
53
|
// Child route: scoped by parent ${parentTable} via ${fkColumn}
|
|
50
|
-
|
|
54
|
+
export default route(${varName}, { ${fkColumn}: "params.${fkColumn}" });
|
|
51
55
|
`;
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
/**
|
|
55
59
|
* Generate the routes index file that mounts all routes on an express Router.
|
|
56
60
|
* Supports parent-child nesting: parent/:pk/child
|
|
61
|
+
*
|
|
62
|
+
* Child routes are placed in subfolders: routes/<parent>/<child>.js
|
|
63
|
+
* Children are only mounted under their parent path (no duplicate top-level route).
|
|
64
|
+
*
|
|
65
|
+
* @param {string[]} tableNames
|
|
66
|
+
* @param {Array<{parent, child, foreignKey}>} relationships
|
|
67
|
+
* @param {{ includeDocs?: boolean }} [options]
|
|
57
68
|
*/
|
|
58
|
-
function generateRoutesIndexFile(tableNames, relationships = []) {
|
|
59
|
-
let imports = `
|
|
69
|
+
function generateRoutesIndexFile(tableNames, relationships = [], options = {}) {
|
|
70
|
+
let imports = `import express from "express";\n\nconst router = express.Router();\n\n`;
|
|
60
71
|
|
|
61
72
|
// Collect child tables that are nested under parents
|
|
62
73
|
const nestedChildren = new Set();
|
|
@@ -64,40 +75,45 @@ function generateRoutesIndexFile(tableNames, relationships = []) {
|
|
|
64
75
|
nestedChildren.add(rel.child);
|
|
65
76
|
}
|
|
66
77
|
|
|
78
|
+
// Import top-level routes only (not children)
|
|
67
79
|
for (const table of tableNames) {
|
|
80
|
+
if (nestedChildren.has(table)) continue;
|
|
68
81
|
const varName = safeVarName(table);
|
|
69
|
-
imports += `
|
|
82
|
+
imports += `import ${varName}Route from "./${table}.js";\n`;
|
|
70
83
|
}
|
|
71
|
-
|
|
84
|
+
|
|
85
|
+
// Import child routes from subfolders
|
|
72
86
|
for (const rel of relationships) {
|
|
73
87
|
const varName = safeVarName(rel.child);
|
|
74
|
-
imports += `
|
|
88
|
+
imports += `import ${varName}ChildRoute from "./${rel.parent}/${rel.child}.js";\n`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Import docs route if openapi is generated
|
|
92
|
+
if (options.includeDocs) {
|
|
93
|
+
imports += `import docsRoute from "./docs.js";\n`;
|
|
75
94
|
}
|
|
76
95
|
|
|
77
96
|
imports += "\n";
|
|
78
97
|
|
|
79
|
-
// Mount
|
|
98
|
+
// Mount docs route first
|
|
99
|
+
if (options.includeDocs) {
|
|
100
|
+
imports += `router.use("/docs", docsRoute);\n`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Mount top-level routes
|
|
80
104
|
for (const table of tableNames) {
|
|
81
105
|
if (nestedChildren.has(table)) continue;
|
|
82
106
|
const varName = safeVarName(table);
|
|
83
107
|
imports += `router.use("/${table}", ${varName}Route);\n`;
|
|
84
108
|
}
|
|
85
109
|
|
|
86
|
-
// Mount
|
|
110
|
+
// Mount child routes under parent path
|
|
87
111
|
for (const rel of relationships) {
|
|
88
|
-
const parentVar = safeVarName(rel.parent);
|
|
89
112
|
const childVar = safeVarName(rel.child);
|
|
90
|
-
|
|
91
|
-
imports += `router.use("/${rel.parent}/:${rel.fkColumn}/${rel.child}", ${childVar}ChildRoute);\n`;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Also mount children as top-level for direct access
|
|
95
|
-
for (const rel of relationships) {
|
|
96
|
-
const varName = safeVarName(rel.child);
|
|
97
|
-
imports += `router.use("/${rel.child}", ${varName}Route);\n`;
|
|
113
|
+
imports += `router.use("/${rel.parent}/:${rel.foreignKey}/${rel.child}", ${childVar}ChildRoute);\n`;
|
|
98
114
|
}
|
|
99
115
|
|
|
100
|
-
imports += "\
|
|
116
|
+
imports += "\nexport default router;\n";
|
|
101
117
|
return imports;
|
|
102
118
|
}
|
|
103
119
|
|
|
@@ -114,13 +130,15 @@ function generateSimpleRoutesIndexFile(tableNames) {
|
|
|
114
130
|
*/
|
|
115
131
|
function generateTestFile(tableName, pk) {
|
|
116
132
|
const varName = safeVarName(tableName);
|
|
117
|
-
return `
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
133
|
+
return `import assert from "assert";
|
|
134
|
+
import express from "express";
|
|
135
|
+
import request from "supertest";
|
|
136
|
+
import dbModelRouter from "db-model-router";
|
|
137
|
+
|
|
138
|
+
const { route } = dbModelRouter;
|
|
121
139
|
|
|
122
140
|
// Adjust the path to your model file as needed
|
|
123
|
-
|
|
141
|
+
import ${varName} from "../models/${tableName}.js";
|
|
124
142
|
|
|
125
143
|
function createApp() {
|
|
126
144
|
const app = express();
|
|
@@ -220,12 +238,14 @@ describe("${tableName} routes", function () {
|
|
|
220
238
|
*/
|
|
221
239
|
function generateChildTestFile(childTable, parentTable, fkColumn, pk) {
|
|
222
240
|
const childVar = safeVarName(childTable);
|
|
223
|
-
return `
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
241
|
+
return `import assert from "assert";
|
|
242
|
+
import express from "express";
|
|
243
|
+
import request from "supertest";
|
|
244
|
+
import dbModelRouter from "db-model-router";
|
|
245
|
+
|
|
246
|
+
const { route } = dbModelRouter;
|
|
227
247
|
|
|
228
|
-
|
|
248
|
+
import ${childVar} from "../models/${childTable}.js";
|
|
229
249
|
|
|
230
250
|
function createApp() {
|
|
231
251
|
const app = express();
|
|
@@ -381,7 +401,7 @@ async function main() {
|
|
|
381
401
|
const fkColumn = parent.replace(/s$/, "") + "_id";
|
|
382
402
|
// Only add if both tables exist in our model set
|
|
383
403
|
if (tableNames.includes(parent) && tableNames.includes(child)) {
|
|
384
|
-
relationships.push({ parent, child, fkColumn });
|
|
404
|
+
relationships.push({ parent, child, foreignKey: fkColumn });
|
|
385
405
|
}
|
|
386
406
|
}
|
|
387
407
|
}
|
|
@@ -392,23 +412,36 @@ async function main() {
|
|
|
392
412
|
fs.mkdirSync(routesDir, { recursive: true });
|
|
393
413
|
}
|
|
394
414
|
|
|
415
|
+
// Collect child tables to skip top-level route files
|
|
416
|
+
const nestedChildren = new Set();
|
|
417
|
+
for (const rel of relationships) {
|
|
418
|
+
nestedChildren.add(rel.child);
|
|
419
|
+
}
|
|
420
|
+
|
|
395
421
|
for (const table of tableNames) {
|
|
422
|
+
if (nestedChildren.has(table)) continue;
|
|
396
423
|
const filePath = path.join(routesDir, table + ".js");
|
|
397
424
|
fs.writeFileSync(filePath, generateRouteFile(table, modelsRelPath));
|
|
398
425
|
console.log(` Created ${filePath}`);
|
|
399
426
|
}
|
|
400
427
|
|
|
401
|
-
// Write child route files
|
|
428
|
+
// Write child route files in subfolders: routes/<parent>/<child>.js
|
|
402
429
|
for (const rel of relationships) {
|
|
403
|
-
const
|
|
404
|
-
|
|
430
|
+
const parentDir = path.join(routesDir, rel.parent);
|
|
431
|
+
if (!fs.existsSync(parentDir)) {
|
|
432
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
433
|
+
}
|
|
434
|
+
const filePath = path.join(parentDir, `${rel.child}.js`);
|
|
435
|
+
const childModelsRelPath = path
|
|
436
|
+
.relative(parentDir, path.resolve(modelsDir))
|
|
437
|
+
.replace(/\\/g, "/");
|
|
405
438
|
fs.writeFileSync(
|
|
406
439
|
filePath,
|
|
407
440
|
generateChildRouteFile(
|
|
408
441
|
rel.child,
|
|
409
442
|
rel.parent,
|
|
410
|
-
rel.
|
|
411
|
-
|
|
443
|
+
rel.foreignKey,
|
|
444
|
+
childModelsRelPath,
|
|
412
445
|
),
|
|
413
446
|
);
|
|
414
447
|
console.log(` Created ${filePath}`);
|
|
@@ -467,7 +500,7 @@ async function main() {
|
|
|
467
500
|
console.log(` Created ${testPath}`);
|
|
468
501
|
}
|
|
469
502
|
|
|
470
|
-
// Generate child route test files
|
|
503
|
+
// Generate child route test files in subfolders
|
|
471
504
|
for (const rel of relationships) {
|
|
472
505
|
let pk = "id";
|
|
473
506
|
const modelPath = path.join(modelsDir, rel.child + ".js");
|
|
@@ -478,13 +511,14 @@ async function main() {
|
|
|
478
511
|
);
|
|
479
512
|
if (meta && meta.primary_key) pk = meta.primary_key;
|
|
480
513
|
}
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
514
|
+
const parentTestDir = path.join(testsDir, rel.parent);
|
|
515
|
+
if (!fs.existsSync(parentTestDir)) {
|
|
516
|
+
fs.mkdirSync(parentTestDir, { recursive: true });
|
|
517
|
+
}
|
|
518
|
+
const testPath = path.join(parentTestDir, `${rel.child}.test.js`);
|
|
485
519
|
fs.writeFileSync(
|
|
486
520
|
testPath,
|
|
487
|
-
generateChildTestFile(rel.child, rel.parent, rel.
|
|
521
|
+
generateChildTestFile(rel.child, rel.parent, rel.foreignKey, pk),
|
|
488
522
|
);
|
|
489
523
|
console.log(` Created ${testPath}`);
|
|
490
524
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
const DRIVER_MAP = {
|
|
7
7
|
mysql: ["mysql2"],
|
|
8
|
+
mariadb: ["mysql2"],
|
|
8
9
|
postgres: ["pg"],
|
|
9
10
|
sqlite3: ["better-sqlite3"],
|
|
10
11
|
mongodb: ["mongodb"],
|
|
@@ -53,26 +54,37 @@ function collectDependencies(answers) {
|
|
|
53
54
|
dependencies["helmet"] = "latest";
|
|
54
55
|
}
|
|
55
56
|
if (answers.logger) {
|
|
56
|
-
dependencies["
|
|
57
|
+
dependencies["winston"] = "latest";
|
|
58
|
+
if (answers.loki) {
|
|
59
|
+
dependencies["winston-loki"] = "latest";
|
|
60
|
+
}
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
// Dev dependencies
|
|
60
64
|
devDependencies["nodemon"] = "latest";
|
|
61
65
|
|
|
66
|
+
// Swagger UI for API documentation
|
|
67
|
+
dependencies["swagger-ui-express"] = "latest";
|
|
68
|
+
|
|
62
69
|
return { dependencies, devDependencies };
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
/**
|
|
66
|
-
* Returns the
|
|
73
|
+
* Returns the package.json scripts.
|
|
74
|
+
* @param {string} [outputDir] - relative output directory for source files
|
|
67
75
|
* @returns {Record<string, string>}
|
|
68
76
|
*/
|
|
69
|
-
function getScripts() {
|
|
77
|
+
function getScripts(outputDir) {
|
|
78
|
+
const prefix = outputDir ? `${outputDir}/` : "";
|
|
70
79
|
return {
|
|
71
80
|
start: "node app.js",
|
|
72
81
|
dev: "nodemon app.js",
|
|
73
82
|
test: 'echo "Error: no test specified" && exit 1',
|
|
74
|
-
migrate:
|
|
75
|
-
add_migration:
|
|
83
|
+
migrate: `node ${prefix}commons/migrate.js`,
|
|
84
|
+
add_migration: `node ${prefix}commons/add_migration.js`,
|
|
85
|
+
"docker:build": "docker build -t app .",
|
|
86
|
+
"docker:up": "docker compose up -d",
|
|
87
|
+
"docker:down": "docker compose down",
|
|
76
88
|
};
|
|
77
89
|
}
|
|
78
90
|
|