db-model-router 1.0.5 → 1.0.6

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 (65) hide show
  1. package/TODO.md +1 -0
  2. package/dbmr.schema.json +1 -1
  3. package/package.json +1 -1
  4. package/src/cli/commands/generate.js +38 -15
  5. package/src/cli/commands/help.js +1 -1
  6. package/src/cli/diff-engine.js +2 -1
  7. package/src/cli/generate-db-manager.js +1573 -0
  8. package/src/cli/generate-model.js +9 -4
  9. package/src/cli/generate-openapi.js +40 -13
  10. package/src/cli/generate-route.js +7 -7
  11. package/src/cli/init/generators.js +36 -30
  12. package/src/cli/main.js +2 -2
  13. package/src/sqlite3/db.js +11 -0
  14. package/demo/.dockerignore +0 -7
  15. package/demo/.env.example +0 -13
  16. package/demo/Dockerfile +0 -20
  17. package/demo/app.js +0 -37
  18. package/demo/commons/add_migration.js +0 -43
  19. package/demo/commons/db.js +0 -17
  20. package/demo/commons/migrate.js +0 -65
  21. package/demo/commons/security.js +0 -30
  22. package/demo/commons/session.js +0 -13
  23. package/demo/dbmr.schema.json +0 -362
  24. package/demo/docs/llm.md +0 -197
  25. package/demo/llms.txt +0 -70
  26. package/demo/middleware/logger.js +0 -67
  27. package/demo/migrations/20260430155808_create_migrations_table.sql +0 -6
  28. package/demo/migrations/20260430155809_create_tables.sql +0 -207
  29. package/demo/models/addresses.js +0 -22
  30. package/demo/models/cart_items.js +0 -18
  31. package/demo/models/carts.js +0 -16
  32. package/demo/models/categories.js +0 -20
  33. package/demo/models/coupons.js +0 -23
  34. package/demo/models/order_items.js +0 -21
  35. package/demo/models/orders.js +0 -25
  36. package/demo/models/payments.js +0 -21
  37. package/demo/models/product_images.js +0 -18
  38. package/demo/models/product_reviews.js +0 -20
  39. package/demo/models/product_variants.js +0 -20
  40. package/demo/models/products.js +0 -30
  41. package/demo/models/shipments.js +0 -19
  42. package/demo/models/users.js +0 -19
  43. package/demo/models/wishlists.js +0 -15
  44. package/demo/openapi.json +0 -5872
  45. package/demo/package-lock.json +0 -2810
  46. package/demo/package.json +0 -34
  47. package/demo/routes/addresses.js +0 -6
  48. package/demo/routes/carts/cart_items.js +0 -7
  49. package/demo/routes/carts.js +0 -6
  50. package/demo/routes/categories.js +0 -6
  51. package/demo/routes/coupons.js +0 -6
  52. package/demo/routes/docs.js +0 -18
  53. package/demo/routes/health.js +0 -35
  54. package/demo/routes/index.js +0 -39
  55. package/demo/routes/orders/order_items.js +0 -7
  56. package/demo/routes/orders/payments.js +0 -7
  57. package/demo/routes/orders/shipments.js +0 -7
  58. package/demo/routes/orders.js +0 -6
  59. package/demo/routes/products/product_images.js +0 -7
  60. package/demo/routes/products/product_reviews.js +0 -7
  61. package/demo/routes/products/product_variants.js +0 -7
  62. package/demo/routes/products.js +0 -6
  63. package/demo/routes/users.js +0 -6
  64. package/demo/routes/wishlists.js +0 -6
  65. package/src/cli/commands/generate-llm-docs.js +0 -418
@@ -544,7 +544,9 @@ function generateModelFile(m) {
544
544
  if (opt.modified_at) parts.push(`modified_at: "${opt.modified_at}"`);
545
545
  optionStr = `\n { ${parts.join(", ")} },`;
546
546
  }
547
- return `const { db, model } = require("db-model-router");
547
+ return `import dbModelRouter from "db-model-router";
548
+
549
+ const { db, model } = dbModelRouter;
548
550
 
549
551
  const ${varName} = model(
550
552
  db,
@@ -554,7 +556,7 @@ const ${varName} = model(
554
556
  ${uniqueStr},${optionStr}
555
557
  );
556
558
 
557
- module.exports = ${varName};
559
+ export default ${varName};
558
560
  `;
559
561
  }
560
562
 
@@ -563,11 +565,14 @@ function generateIndexFile(models) {
563
565
  let exports = "";
564
566
  for (const m of models) {
565
567
  const varName = safeVarName(m.table);
566
- imports += `const ${varName} = require("./${m.table}");\n`;
568
+ imports += `import ${varName} from "./${m.table}.js";\n`;
567
569
  exports += ` ${varName},\n`;
568
570
  }
569
571
  return `${imports}
570
- module.exports = {
572
+ export {
573
+ ${exports}};
574
+
575
+ export default {
571
576
  ${exports}};
572
577
  `;
573
578
  }
@@ -5,6 +5,13 @@ function generateOpenAPISpec(models, options = {}) {
5
5
  const basePath = options.basePath || "/api";
6
6
  const title = options.title || "REST Router API";
7
7
  const version = options.version || "1.0.0";
8
+ const relationships = options.relationships || [];
9
+
10
+ // Build a lookup: child table -> { parent, foreignKey }
11
+ const childMap = {};
12
+ for (const rel of relationships) {
13
+ childMap[rel.child] = { parent: rel.parent, foreignKey: rel.foreignKey };
14
+ }
8
15
 
9
16
  const spec = {
10
17
  openapi: "3.0.3",
@@ -39,14 +46,34 @@ function generateOpenAPISpec(models, options = {}) {
39
46
  };
40
47
 
41
48
  const ref = { $ref: `#/components/schemas/${schemaName}` };
42
- const prefix = `${basePath}/${m.table}`;
49
+
50
+ // Determine path prefix: nested under parent if this is a child table
51
+ let prefix;
52
+ const isChild = !!childMap[m.table];
53
+ let fkParam = null;
54
+ if (isChild) {
55
+ const { parent, foreignKey } = childMap[m.table];
56
+ prefix = `${basePath}/${parent}/{${foreignKey}}/${m.table}`;
57
+ fkParam = {
58
+ name: foreignKey,
59
+ in: "path",
60
+ required: true,
61
+ schema: { type: "string" },
62
+ description: `${capitalize(parent)} foreign key`,
63
+ };
64
+ } else {
65
+ prefix = `${basePath}/${m.table}`;
66
+ }
67
+
68
+ // Helper: prepend FK path param for child routes
69
+ const withFk = (params) => (fkParam ? [fkParam, ...params] : params);
43
70
 
44
71
  // GET / — list
45
72
  spec.paths[`${prefix}/`] = {
46
73
  get: {
47
74
  tags: [tag],
48
75
  summary: `List ${m.table}`,
49
- parameters: [
76
+ parameters: withFk([
50
77
  {
51
78
  name: "page",
52
79
  in: "query",
@@ -69,7 +96,7 @@ function generateOpenAPISpec(models, options = {}) {
69
96
  in: "query",
70
97
  schema: { type: "string", enum: ["json", "csv", "xml"] },
71
98
  },
72
- ],
99
+ ]),
73
100
  responses: {
74
101
  200: {
75
102
  description: "Success",
@@ -141,7 +168,7 @@ function generateOpenAPISpec(models, options = {}) {
141
168
  get: {
142
169
  tags: [tag],
143
170
  summary: `Get ${m.table} by ${pk}`,
144
- parameters: [
171
+ parameters: withFk([
145
172
  { name: pk, in: "path", required: true, schema: { type: "string" } },
146
173
  { name: "select_columns", in: "query", schema: { type: "string" } },
147
174
  {
@@ -149,7 +176,7 @@ function generateOpenAPISpec(models, options = {}) {
149
176
  in: "query",
150
177
  schema: { type: "string", enum: ["json", "csv", "xml"] },
151
178
  },
152
- ],
179
+ ]),
153
180
  responses: {
154
181
  200: {
155
182
  description: "Success",
@@ -161,9 +188,9 @@ function generateOpenAPISpec(models, options = {}) {
161
188
  post: {
162
189
  tags: [tag],
163
190
  summary: `Insert a ${m.table}`,
164
- parameters: [
191
+ parameters: withFk([
165
192
  { name: pk, in: "path", required: true, schema: { type: "string" } },
166
- ],
193
+ ]),
167
194
  requestBody: { content: { "application/json": { schema: ref } } },
168
195
  responses: {
169
196
  200: {
@@ -175,9 +202,9 @@ function generateOpenAPISpec(models, options = {}) {
175
202
  put: {
176
203
  tags: [tag],
177
204
  summary: `Update a ${m.table}`,
178
- parameters: [
205
+ parameters: withFk([
179
206
  { name: pk, in: "path", required: true, schema: { type: "string" } },
180
- ],
207
+ ]),
181
208
  requestBody: { content: { "application/json": { schema: ref } } },
182
209
  responses: {
183
210
  200: {
@@ -190,9 +217,9 @@ function generateOpenAPISpec(models, options = {}) {
190
217
  patch: {
191
218
  tags: [tag],
192
219
  summary: `Partial update a ${m.table}`,
193
- parameters: [
220
+ parameters: withFk([
194
221
  { name: pk, in: "path", required: true, schema: { type: "string" } },
195
- ],
222
+ ]),
196
223
  requestBody: {
197
224
  content: { "application/json": { schema: { type: "object" } } },
198
225
  },
@@ -207,9 +234,9 @@ function generateOpenAPISpec(models, options = {}) {
207
234
  delete: {
208
235
  tags: [tag],
209
236
  summary: `Delete a ${m.table}`,
210
- parameters: [
237
+ parameters: withFk([
211
238
  { name: pk, in: "path", required: true, schema: { type: "string" } },
212
- ],
239
+ ]),
213
240
  responses: {
214
241
  200: { description: "Deleted" },
215
242
  404: { description: "Not Found" },
@@ -100,6 +100,12 @@ function generateRoutesIndexFile(tableNames, relationships = [], options = {}) {
100
100
  imports += `router.use("/docs", docsRoute);\n`;
101
101
  }
102
102
 
103
+ // Mount child routes BEFORE parent routes to prevent path clashing
104
+ for (const rel of relationships) {
105
+ const childVar = safeVarName(rel.child);
106
+ imports += `router.use("/${rel.parent}/:${rel.foreignKey}/${rel.child}", ${childVar}ChildRoute);\n`;
107
+ }
108
+
103
109
  // Mount top-level routes
104
110
  for (const table of tableNames) {
105
111
  if (nestedChildren.has(table)) continue;
@@ -107,12 +113,6 @@ function generateRoutesIndexFile(tableNames, relationships = [], options = {}) {
107
113
  imports += `router.use("/${table}", ${varName}Route);\n`;
108
114
  }
109
115
 
110
- // Mount child routes under parent path
111
- for (const rel of relationships) {
112
- const childVar = safeVarName(rel.child);
113
- imports += `router.use("/${rel.parent}/:${rel.foreignKey}/${rel.child}", ${childVar}ChildRoute);\n`;
114
- }
115
-
116
116
  imports += "\nexport default router;\n";
117
117
  return imports;
118
118
  }
@@ -472,7 +472,7 @@ async function main() {
472
472
  }
473
473
  }
474
474
  if (modelMeta.length > 0) {
475
- const spec = generateOpenAPISpec(modelMeta);
475
+ const spec = generateOpenAPISpec(modelMeta, { relationships });
476
476
  const specPath = path.join(routesDir, "openapi.json");
477
477
  fs.writeFileSync(specPath, JSON.stringify(spec, null, 2));
478
478
  console.log(` Created ${specPath}`);
@@ -367,21 +367,21 @@ function generateAppJs(answers) {
367
367
  answers.framework === "ultimate-express" ? "ultimate-express" : "express";
368
368
 
369
369
  // Imports
370
- let imports = `const express = require("${frameworkPkg}");
371
- const { init, db } = require("db-model-router");
372
- const session = require("express-session");`;
370
+ let imports = `import express from "${frameworkPkg}";
371
+ import { init, db } from "db-model-router";
372
+ import session from "express-session";`;
373
373
 
374
374
  if (answers.session === "redis") {
375
- imports += `\nconst RedisStore = require("connect-redis").default;
376
- const { Redis } = require("ioredis");`;
375
+ imports += `\nimport RedisStore from "connect-redis";
376
+ import { Redis } from "ioredis";`;
377
377
  }
378
378
  if (answers.rateLimiting) {
379
- imports += `\nconst rateLimit = require("express-rate-limit");`;
379
+ imports += `\nimport rateLimit from "express-rate-limit";`;
380
380
  }
381
381
  if (answers.helmet) {
382
- imports += `\nconst helmet = require("helmet");`;
382
+ imports += `\nimport helmet from "helmet";`;
383
383
  }
384
- imports += `\nconst logger = require("./middleware/logger");`;
384
+ imports += `\nimport logger from "./middleware/logger.js";`;
385
385
 
386
386
  // Rate limiting block
387
387
  const rateLimitBlock = answers.rateLimiting
@@ -396,9 +396,7 @@ const { Redis } = require("ioredis");`;
396
396
  const helmetBlock = answers.helmet ? `app.use(helmet());` : "";
397
397
 
398
398
  return `${imports}
399
-
400
- // Load environment variables
401
- require("dotenv").config();
399
+ import "dotenv/config";
402
400
 
403
401
  // Initialize database adapter
404
402
  init("${answers.database}");
@@ -428,7 +426,7 @@ app.listen(PORT, () => {
428
426
  console.log(\`Server running on port \${PORT}\`);
429
427
  });
430
428
 
431
- module.exports = app;
429
+ export default app;
432
430
  `;
433
431
  }
434
432
 
@@ -551,14 +549,15 @@ function generateMigrateScript(answers) {
551
549
 
552
550
  if (isNoSql) {
553
551
  return `#!/usr/bin/env node
554
- "use strict";
552
+ import fs from "fs";
553
+ import path from "path";
554
+ import crypto from "crypto";
555
+ import { fileURLToPath } from "url";
556
+ import "dotenv/config";
555
557
 
556
- const fs = require("fs");
557
- const path = require("path");
558
- const crypto = require("crypto");
559
- require("dotenv").config();
558
+ import { init, db } from "db-model-router";
560
559
 
561
- const { init, db } = require("db-model-router");
560
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
562
561
 
563
562
  init("${answers.database}");
564
563
 
@@ -598,7 +597,7 @@ async function migrate() {
598
597
  const content = fs.readFileSync(filePath, "utf8");
599
598
  const checksum = crypto.createHash("md5").update(content).digest("hex");
600
599
 
601
- const migration = require(filePath);
600
+ const migration = await import(filePath);
602
601
  console.log(\` Running migration: \${file}\`);
603
602
  await migration.up(db);
604
603
  await recordMigration(file, checksum);
@@ -622,14 +621,15 @@ migrate().catch(err => {
622
621
  }
623
622
 
624
623
  return `#!/usr/bin/env node
625
- "use strict";
624
+ import fs from "fs";
625
+ import path from "path";
626
+ import crypto from "crypto";
627
+ import { fileURLToPath } from "url";
628
+ import "dotenv/config";
626
629
 
627
- const fs = require("fs");
628
- const path = require("path");
629
- const crypto = require("crypto");
630
- require("dotenv").config();
630
+ import { init, db } from "db-model-router";
631
631
 
632
- const { init, db } = require("db-model-router");
632
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
633
633
 
634
634
  init("${answers.database}");
635
635
 
@@ -700,14 +700,15 @@ function generateAddMigrationScript(answers) {
700
700
  const isNoSql = NOSQL_DATABASES.includes(answers.database);
701
701
  const ext = isNoSql ? "js" : "sql";
702
702
  const template = isNoSql
703
- ? `"use strict";\\n\\nmodule.exports = {\\n async up(db) {\\n // Write your migration here\\n },\\n\\n async down(db) {\\n // Write your rollback here\\n },\\n};\\n`
703
+ ? `export async function up(db) {\\n // Write your migration here\\n}\\n\\nexport async function down(db) {\\n // Write your rollback here\\n}\\n`
704
704
  : `-- Write your migration SQL here\\n`;
705
705
 
706
706
  return `#!/usr/bin/env node
707
- "use strict";
707
+ import fs from "fs";
708
+ import path from "path";
709
+ import { fileURLToPath } from "url";
708
710
 
709
- const fs = require("fs");
710
- const path = require("path");
711
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
711
712
 
712
713
  const migrationsDir = path.join(__dirname, "migrations");
713
714
 
@@ -1422,6 +1423,8 @@ if (isMain) {
1422
1423
  const pkg = await import("db-model-router");
1423
1424
  const mod = pkg.default || pkg;
1424
1425
  mod.init("${answers.database}");
1426
+ mod.db.connect({
1427
+ ${dbConnectArgs(answers.database)}});
1425
1428
  const migrationsDir = path.join(__dirname, "${migrationsRel}");
1426
1429
  runMigrations(mod.db, migrationsDir)
1427
1430
  .then(() => process.exit(0))
@@ -1490,6 +1493,8 @@ if (isMain) {
1490
1493
  const pkg = await import("db-model-router");
1491
1494
  const mod = pkg.default || pkg;
1492
1495
  mod.init("${answers.database}");
1496
+ mod.db.connect({
1497
+ ${dbConnectArgs(answers.database)}});
1493
1498
  const migrationsDir = path.join(__dirname, "${migrationsRel}");
1494
1499
  runMigrations(mod.db, migrationsDir)
1495
1500
  .then(() => process.exit(0))
@@ -1726,7 +1731,8 @@ import configureSession from "${commonsPrefix}/session.js";
1726
1731
  import applySecurity from "${commonsPrefix}/security.js";
1727
1732
  import logger from "${middlewarePrefix}/logger.js";
1728
1733
  import route from "${routePrefix}/index.js";
1729
-
1734
+ import { fileURLToPath } from 'node:url';
1735
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
1730
1736
  const app = express();
1731
1737
  const PORT = process.env.PORT || 3000;
1732
1738
 
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");
@@ -64,7 +64,7 @@ const COMMAND_FLAGS = {
64
64
  ["--routes", "Generate only route files"],
65
65
  ["--openapi", "Generate only OpenAPI spec"],
66
66
  ["--tests", "Generate only test files"],
67
- ["--llm-docs", "Generate only LLM documentation"],
67
+ ["--db-manager", "Generate DB Manager UI (SQL adapters only)"],
68
68
  ],
69
69
  doctor: [["--from <path>", "Schema file (default: dbmr.schema.json)"]],
70
70
  diff: [["--from <path>", "Schema file (default: dbmr.schema.json)"]],
package/src/sqlite3/db.js CHANGED
@@ -16,6 +16,17 @@ function connect(config) {
16
16
  }
17
17
 
18
18
  function query(sql, parameter = []) {
19
+ // Use exec() for multi-statement SQL (e.g., migration files)
20
+ if (
21
+ parameter.length === 0 &&
22
+ sql
23
+ .replace(/--[^\n]*/g, "")
24
+ .split(";")
25
+ .filter((s) => s.trim()).length > 1
26
+ ) {
27
+ db.exec(sql);
28
+ return { changes: 0 };
29
+ }
19
30
  const stmt = db.prepare(sql);
20
31
  if (sql.trimStart().match(/^(SELECT|PRAGMA|WITH\s)/i)) {
21
32
  return stmt.all(...parameter);
@@ -1,7 +0,0 @@
1
- node_modules
2
- npm-debug.log
3
- .env
4
- .env.example
5
- .git
6
- .gitignore
7
- data
package/demo/.env.example DELETED
@@ -1,13 +0,0 @@
1
- # Server
2
- PORT=3000
3
-
4
- # Database
5
- DB_NAME=./data/data.db
6
-
7
- # Session
8
- SESSION_SECRET=your_session_secret
9
-
10
- # Logging
11
- APP_NAME=your_app_name
12
- LOG_LEVEL=info
13
- LOKI_HOST=http://your-loki-host:3100
package/demo/Dockerfile DELETED
@@ -1,20 +0,0 @@
1
- FROM node:alpine
2
-
3
- WORKDIR /app
4
-
5
- # Install dependencies
6
- COPY package*.json ./
7
- RUN npm ci --omit=dev
8
-
9
- # Copy application files
10
- COPY app.js ./
11
- COPY commons/ ./commons/
12
- COPY middleware/ ./middleware/
13
- COPY route/ ./route/
14
- COPY migrations/ ./migrations/
15
-
16
- # Expose port
17
- EXPOSE 3000
18
-
19
- # Start the application
20
- CMD ["node", "app.js"]
package/demo/app.js DELETED
@@ -1,37 +0,0 @@
1
- import express from "express";
2
- import "./commons/db.js";
3
- import configureSession from "./commons/session.js";
4
- import applySecurity from "./commons/security.js";
5
- import logger from "./middleware/logger.js";
6
- import route from "./routes/index.js";
7
-
8
- const app = express();
9
- const PORT = process.env.PORT || 3000;
10
-
11
- // Middleware
12
- app.use(express.json());
13
- app.use(express.urlencoded({ extended: true }));
14
-
15
- // Security (helmet, rate limiting, custom headers)
16
- applySecurity(app);
17
-
18
- // Session
19
- app.use(configureSession());
20
-
21
- // Logger
22
- app.use(logger);
23
-
24
- // Routes
25
- app.use(route);
26
-
27
- // Error handler
28
- app.use((err, req, res, next) => {
29
- console.error(err.stack);
30
- res.status(500).json({ type: "danger", message: "Internal Server Error" });
31
- });
32
-
33
- app.listen(PORT, () => {
34
- console.log(`Server running on port ${PORT}`);
35
- });
36
-
37
- export default app;
@@ -1,43 +0,0 @@
1
- #!/usr/bin/env node
2
- import fs from "fs";
3
- import path from "path";
4
- import { fileURLToPath } from "url";
5
-
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
-
8
- /**
9
- * Create a new timestamped migration file.
10
- * @param {string} migrationsDir - absolute path to migrations folder
11
- * @param {string} [name] - migration name (default: "migration")
12
- * @returns {string} the created filename
13
- */
14
- export default function addMigration(migrationsDir, name) {
15
- const migrationName = name || "migration";
16
- const now = new Date();
17
- const y = String(now.getFullYear()).padStart(4, "0");
18
- const mo = String(now.getMonth() + 1).padStart(2, "0");
19
- const d = String(now.getDate()).padStart(2, "0");
20
- const h = String(now.getHours()).padStart(2, "0");
21
- const mi = String(now.getMinutes()).padStart(2, "0");
22
- const s = String(now.getSeconds()).padStart(2, "0");
23
- const ts = `${y}${mo}${d}${h}${mi}${s}`;
24
-
25
- const filename = `${ts}_${migrationName}.sql`;
26
- const filePath = path.join(migrationsDir, filename);
27
-
28
- if (!fs.existsSync(migrationsDir)) {
29
- fs.mkdirSync(migrationsDir, { recursive: true });
30
- }
31
-
32
- fs.writeFileSync(filePath, "-- Write your migration SQL here\n");
33
- console.log(`Created migration: ${filename}`);
34
- return filename;
35
- }
36
-
37
- // Run as standalone script
38
- const isMain = process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
39
- if (isMain) {
40
- const migrationsDir = path.join(__dirname, "../migrations");
41
- const name = process.argv[2] || "migration";
42
- addMigration(migrationsDir, name);
43
- }
@@ -1,17 +0,0 @@
1
- import "dotenv/config";
2
- import dbModelRouter from "db-model-router";
3
-
4
- // Initialize database adapter
5
- dbModelRouter.init("sqlite3");
6
-
7
- // Connect to database
8
- dbModelRouter.db.connect({
9
- database: process.env.DB_NAME || "./data/data.db",
10
- });
11
-
12
- // Make db available globally across the application
13
- const db = dbModelRouter.db;
14
- global.db = db;
15
-
16
- export { db };
17
- export default db;
@@ -1,65 +0,0 @@
1
- #!/usr/bin/env node
2
- import fs from "fs";
3
- import path from "path";
4
- import crypto from "crypto";
5
- import { fileURLToPath } from "url";
6
-
7
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
-
9
- /**
10
- * Run all pending SQL migrations from the migrations directory.
11
- * @param {object} db - db-model-router db instance
12
- * @param {string} migrationsDir - absolute path to migrations folder
13
- */
14
- export default async function runMigrations(db, migrationsDir) {
15
- const files = fs.readdirSync(migrationsDir)
16
- .filter(f => f.endsWith(".sql"))
17
- .sort();
18
-
19
- let executed;
20
- try {
21
- const result = await db.query("SELECT filename FROM _migrations");
22
- executed = new Set((result || []).map(r => r.filename));
23
- } catch (e) {
24
- executed = new Set();
25
- }
26
-
27
- let ran = 0;
28
- for (const file of files) {
29
- if (executed.has(file)) {
30
- console.log(` Skipping (already executed): ${file}`);
31
- continue;
32
- }
33
- const filePath = path.join(migrationsDir, file);
34
- const content = fs.readFileSync(filePath, "utf8");
35
- const checksum = crypto.createHash("md5").update(content).digest("hex");
36
-
37
- console.log(` Running migration: ${file}`);
38
- await db.query(content);
39
- await db.query(
40
- "INSERT INTO _migrations (filename, checksum) VALUES (?, ?)",
41
- [file, checksum]
42
- );
43
- console.log(` Completed: ${file}`);
44
- ran++;
45
- }
46
-
47
- if (ran === 0) {
48
- console.log("No pending migrations.");
49
- } else {
50
- console.log(`\n${ran} migration(s) complete.`);
51
- }
52
- }
53
-
54
- // Run as standalone script
55
- const isMain = process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
56
- if (isMain) {
57
- await import("dotenv/config");
58
- const pkg = await import("db-model-router");
59
- const mod = pkg.default || pkg;
60
- mod.init("sqlite3");
61
- const migrationsDir = path.join(__dirname, "../migrations");
62
- runMigrations(mod.db, migrationsDir)
63
- .then(() => process.exit(0))
64
- .catch(err => { console.error("Migration failed:", err); process.exit(1); });
65
- }
@@ -1,30 +0,0 @@
1
- import helmet from "helmet";
2
- import rateLimit from "express-rate-limit";
3
-
4
- /**
5
- * Apply security middleware to the Express app.
6
- * Includes: Helmet, rate limiting, custom security headers.
7
- * @param {import("express").Application} app
8
- */
9
- export default function applySecurity(app) {
10
- // Helmet — sets various HTTP headers for security
11
- app.use(helmet());
12
-
13
- // Rate limiting
14
- app.use(rateLimit({
15
- windowMs: 15 * 60 * 1000,
16
- max: 100,
17
- standardHeaders: true,
18
- legacyHeaders: false,
19
- }));
20
-
21
- // Custom security headers (override or extend as needed)
22
- app.use((req, res, next) => {
23
- res.setHeader("X-Content-Type-Options", "nosniff");
24
- res.setHeader("X-Frame-Options", "DENY");
25
- res.setHeader("X-XSS-Protection", "1; mode=block");
26
- res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
27
- res.removeHeader("X-Powered-By");
28
- next();
29
- });
30
- }
@@ -1,13 +0,0 @@
1
- import session from "express-session";
2
-
3
- /**
4
- * Configure and return session middleware.
5
- * Session store: memory
6
- */
7
- export default function configureSession() {
8
- return session({
9
- secret: process.env.SESSION_SECRET || "change-me",
10
- resave: false,
11
- saveUninitialized: false,
12
- });
13
- }