db-model-router 1.0.11 → 1.0.13

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.
@@ -131,16 +131,16 @@ function autoIncrementType(adapter) {
131
131
  switch (adapter) {
132
132
  case "postgres":
133
133
  case "cockroachdb":
134
- return "SERIAL";
134
+ return "BIGSERIAL";
135
135
  case "mssql":
136
- return "INT IDENTITY(1,1)";
136
+ return "BIGINT IDENTITY(1,1)";
137
137
  case "oracle":
138
- return "NUMBER GENERATED BY DEFAULT AS IDENTITY";
138
+ return "NUMBER(19) GENERATED BY DEFAULT AS IDENTITY";
139
139
  case "sqlite3":
140
140
  return "INTEGER";
141
141
  default:
142
142
  // mysql, mariadb
143
- return "INT AUTO_INCREMENT";
143
+ return "BIGINT AUTO_INCREMENT";
144
144
  }
145
145
  }
146
146
 
@@ -354,9 +354,17 @@ function generateCreateTableSQL(tableName, tableDef, adapter) {
354
354
 
355
355
  // Unique constraints (excluding PK which is already PRIMARY KEY)
356
356
  if (tableDef.unique && tableDef.unique.length > 0) {
357
- const uniqueCols = tableDef.unique.filter((c) => c !== pk);
358
- for (const col of uniqueCols) {
359
- lines.push(` UNIQUE (${quoteIdent(col, adapter)})`);
357
+ // Support both flat arrays (wrapped as one composite group) and array-of-arrays
358
+ const uniqueGroups = Array.isArray(tableDef.unique[0])
359
+ ? tableDef.unique
360
+ : [tableDef.unique];
361
+ for (const group of uniqueGroups) {
362
+ const cols = group.filter((c) => c !== pk);
363
+ if (cols.length > 0) {
364
+ lines.push(
365
+ ` UNIQUE (${cols.map((c) => quoteIdent(c, adapter)).join(", ")})`,
366
+ );
367
+ }
360
368
  }
361
369
  }
362
370
 
@@ -412,12 +420,16 @@ function defaultBoolean(adapter) {
412
420
  * Generate a MongoDB migration JS file for a single table (collection).
413
421
  */
414
422
  function generateMongoDBMigration(tableName, tableDef) {
415
- const uniqueCols = (tableDef.unique || []).filter((c) => c !== tableDef.pk);
416
- const indexLines = uniqueCols
417
- .map(
418
- (col) =>
419
- ` await db.collection("${tableName}").createIndex({ ${col}: 1 }, { unique: true });`,
420
- )
423
+ const uniqueGroups = Array.isArray((tableDef.unique || [])[0])
424
+ ? tableDef.unique || []
425
+ : [tableDef.unique || []];
426
+ const indexLines = uniqueGroups
427
+ .flatMap((group) => {
428
+ const cols = group.filter((c) => c !== tableDef.pk);
429
+ if (cols.length === 0) return [];
430
+ const indexObj = cols.map((c) => `${c}: 1`).join(", ");
431
+ return ` await db.collection("${tableName}").createIndex({ ${indexObj} }, { unique: true });`;
432
+ })
421
433
  .join("\n");
422
434
 
423
435
  return `"use strict";
@@ -42,11 +42,15 @@ export default router;
42
42
  * Generate a parent route file that includes its own CRUD and mounts child routes.
43
43
  * e.g., routes/orders/index.js mounts order_items under /:order_id/items
44
44
  *
45
+ * When the parent is itself a child (intermediate node), its own CRUD is scoped
46
+ * by the parentForeignKey so it filters on params from the ancestor router.
47
+ *
45
48
  * @param {string} tableName - Parent table name
46
49
  * @param {Array<{child, foreignKey}>} children - Child relationships for this parent
50
+ * @param {string} [parentForeignKey] - If set, own CRUD is scoped by this FK from an ancestor
47
51
  * @returns {string}
48
52
  */
49
- function generateParentRouteFile(tableName, children) {
53
+ function generateParentRouteFile(tableName, children, parentForeignKey) {
50
54
  const varName = safeVarName(tableName);
51
55
  let code = `import dbModelRouter from "db-model-router";
52
56
  import express from "express";
@@ -71,9 +75,12 @@ const { route } = dbModelRouter;
71
75
  code += `router.use("/:${child.foreignKey}/${child.child}", ${childVar}Route);\n`;
72
76
  }
73
77
 
78
+ const scope = parentForeignKey
79
+ ? `, { ${parentForeignKey}: "params.${parentForeignKey}" }`
80
+ : "";
74
81
  code += `
75
82
  // CRUD routes for ${tableName}
76
- router.use("/", route(${varName}));
83
+ router.use("/", route(${varName}${scope}));
77
84
 
78
85
  export default router;
79
86
  `;
@@ -372,7 +379,7 @@ function columnToFaker(col, rule) {
372
379
  /**
373
380
  * Generate a child route test file that tests the nested parent/:fk/child endpoints.
374
381
  */
375
- function generateChildTestFile(childTable, parentTable, fkColumn, pk) {
382
+ function generateChildTestFile(childTable, parentTable, fkColumn, pk, modelsRelPath = "../models/") {
376
383
  const childVar = safeVarName(childTable);
377
384
  return `import assert from "assert";
378
385
  import express from "express";
@@ -381,7 +388,7 @@ import dbModelRouter from "db-model-router";
381
388
 
382
389
  const { route } = dbModelRouter;
383
390
 
384
- import ${childVar} from "../models/${childTable}.js";
391
+ import ${childVar} from "${modelsRelPath}${childTable}.js";
385
392
 
386
393
  function createApp() {
387
394
  const app = express();
@@ -900,108 +900,6 @@ const DOCKER_DB_MAP = {
900
900
  },
901
901
  };
902
902
 
903
- /**
904
- * CloudBeaver JDBC driver IDs and URL templates per database.
905
- */
906
- const CLOUDBEAVER_DB_MAP = {
907
- mysql: {
908
- provider: "mysql",
909
- driver: "mysql8",
910
- urlTemplate: (host, port, dbName) =>
911
- `jdbc:mysql://${host}:${port}/${dbName}`,
912
- },
913
- mariadb: {
914
- provider: "mysql",
915
- driver: "mariaDB",
916
- urlTemplate: (host, port, dbName) =>
917
- `jdbc:mariadb://${host}:${port}/${dbName}`,
918
- },
919
- postgres: {
920
- provider: "postgresql",
921
- driver: "postgres-jdbc",
922
- urlTemplate: (host, port, dbName) =>
923
- `jdbc:postgresql://${host}:${port}/${dbName}`,
924
- },
925
- cockroachdb: {
926
- provider: "postgresql",
927
- driver: "postgres-jdbc",
928
- urlTemplate: (host, port, dbName) =>
929
- `jdbc:postgresql://${host}:${port}/${dbName}`,
930
- },
931
- mssql: {
932
- provider: "sqlserver",
933
- driver: "mssql_jdbc_ms_new",
934
- urlTemplate: (host, port, dbName) =>
935
- `jdbc:sqlserver://${host}:${port};databaseName=${dbName};trustServerCertificate=true`,
936
- },
937
- oracle: {
938
- provider: "oracle",
939
- driver: "oracle_thin",
940
- urlTemplate: (host, port, dbName) =>
941
- `jdbc:oracle:thin:@${host}:${port}/${dbName}`,
942
- },
943
- mongodb: {
944
- provider: "mongodb",
945
- driver: "mongodb",
946
- urlTemplate: (host, port, dbName) => `mongodb://${host}:${port}/${dbName}`,
947
- },
948
- };
949
-
950
- /**
951
- * Generate CloudBeaver data-sources.json for auto-connecting to the project database.
952
- * @param {import('./types').InitAnswers} answers
953
- * @param {object} secrets
954
- * @returns {string|null}
955
- */
956
- function generateCloudBeaverDataSources(answers, secrets) {
957
- const cbDb = CLOUDBEAVER_DB_MAP[answers.database];
958
- if (!cbDb) return null;
959
-
960
- const dbConfig = DOCKER_DB_MAP[answers.database];
961
- if (!dbConfig) return null;
962
-
963
- const host = answers.database; // service name in docker-compose
964
- const port = dbConfig.port.split(":")[1];
965
- const dbName = "my_app";
966
-
967
- // Determine user/pass based on adapter
968
- let user = "root";
969
- let pass = secrets.dbPass;
970
- if (answers.database === "postgres" || answers.database === "cockroachdb")
971
- user = "postgres";
972
- if (answers.database === "mssql") user = "sa";
973
- if (answers.database === "oracle") user = "system";
974
- if (answers.database === "mongodb") user = "root";
975
-
976
- const connId = `${answers.database}-project-db`;
977
- const url = cbDb.urlTemplate(host, port, dbName);
978
-
979
- const config = {
980
- folders: {},
981
- connections: {
982
- [connId]: {
983
- provider: cbDb.provider,
984
- driver: cbDb.driver,
985
- name: `${answers.database} - my_app`,
986
- "save-password": true,
987
- configuration: {
988
- host: host,
989
- port: port,
990
- database: dbName,
991
- url: url,
992
- configurationType: "MANUAL",
993
- type: "dev",
994
- auth: "native",
995
- userName: user,
996
- userPassword: pass,
997
- },
998
- },
999
- },
1000
- };
1001
-
1002
- return JSON.stringify(config, null, 2) + "\n";
1003
- }
1004
-
1005
903
  /**
1006
904
  * Generate docker-compose.yml content.
1007
905
  * @param {import('./types').InitAnswers} answers
@@ -1057,27 +955,6 @@ function generateDockerCompose(answers, secrets) {
1057
955
  services["redis"] = redisService;
1058
956
  }
1059
957
 
1060
- // --- CloudBeaver service (for SQL/MongoDB databases) ---
1061
- const hasCbSupport = !!CLOUDBEAVER_DB_MAP[answers.database];
1062
- if (hasCbSupport) {
1063
- services["cloudbeaver"] = {
1064
- container_name: "cloudbeaver",
1065
- image: "dbeaver/cloudbeaver:latest",
1066
- ports: ["8978:8978"],
1067
- restart: "unless-stopped",
1068
- environment: {
1069
- CB_SERVER_NAME: "CloudBeaver",
1070
- CB_ADMIN_NAME: "cbadmin",
1071
- CB_ADMIN_PASSWORD: secrets.dbPass,
1072
- },
1073
- volumes: [
1074
- "./data/cloudbeaver:/opt/cloudbeaver/workspace",
1075
- "./.cloudbeaver/data-sources.json:/opt/cloudbeaver/workspace/GlobalConfiguration/.dbeaver/data-sources.json:ro",
1076
- ],
1077
- depends_on: [answers.database],
1078
- };
1079
- }
1080
-
1081
958
  // --- Loki + Grafana (when logger + loki are enabled) ---
1082
959
  if (answers.loki) {
1083
960
  services["loki"] = {
@@ -1225,7 +1102,6 @@ function generateGitignore() {
1225
1102
  .env
1226
1103
  *.db
1227
1104
  data/
1228
- .cloudbeaver/
1229
1105
  `;
1230
1106
  }
1231
1107
 
@@ -1713,7 +1589,6 @@ module.exports = {
1713
1589
  generateDockerignore,
1714
1590
  generateGrafanaDatasources,
1715
1591
  generateDockerCompose,
1716
- generateCloudBeaverDataSources,
1717
1592
  generateSessionJs,
1718
1593
  generateMigrateModule,
1719
1594
  generateAddMigrationModule,
package/src/cli/init.js CHANGED
@@ -17,7 +17,6 @@ const {
17
17
  generateDockerignore,
18
18
  generateGrafanaDatasources,
19
19
  generateDockerCompose,
20
- generateCloudBeaverDataSources,
21
20
  generateSessionJs,
22
21
  generateMigrateModule,
23
22
  generateAddMigrationModule,
@@ -124,18 +123,6 @@ function generateFiles(answers, outputDir) {
124
123
  files.push("docker-compose.yml");
125
124
  }
126
125
 
127
- // CloudBeaver data-sources.json (auto-connect config)
128
- const cbDataSources = generateCloudBeaverDataSources(answers, secrets);
129
- if (cbDataSources !== null) {
130
- const cbDir = ".cloudbeaver";
131
- if (!fs.existsSync(cbDir)) {
132
- fs.mkdirSync(cbDir, { recursive: true });
133
- }
134
- const cbPath = path.join(cbDir, "data-sources.json");
135
- if (safeWriteFile(cbPath, cbDataSources))
136
- files.push(".cloudbeaver/data-sources.json");
137
- }
138
-
139
126
  // Grafana datasource provisioning (when loki is enabled)
140
127
  if (answers.loki) {
141
128
  const grafanaDir = ".grafana";
@@ -165,18 +165,35 @@ function validateTables(tables, errors) {
165
165
  message: `unique must be an array in table "${tableName}"`,
166
166
  });
167
167
  } else {
168
- for (let i = 0; i < tableDef.unique.length; i++) {
169
- const entry = tableDef.unique[i];
170
- if (typeof entry !== "string") {
168
+ // Support both flat arrays and array-of-arrays
169
+ const groups = Array.isArray(tableDef.unique[0])
170
+ ? tableDef.unique
171
+ : [tableDef.unique];
172
+ for (let g = 0; g < groups.length; g++) {
173
+ const group = groups[g];
174
+ const groupPath = Array.isArray(tableDef.unique[0])
175
+ ? `${basePath}.unique[${g}]`
176
+ : `${basePath}.unique`;
177
+ if (!Array.isArray(group)) {
171
178
  errors.push({
172
- path: `${basePath}.unique[${i}]`,
173
- message: `unique entry must be a string in table "${tableName}"`,
174
- });
175
- } else if (entry !== pk && !columnNames.has(entry)) {
176
- errors.push({
177
- path: `${basePath}.unique[${i}]`,
178
- message: `unique entry "${entry}" does not match any column or the primary key "${pk}" in table "${tableName}"`,
179
+ path: groupPath,
180
+ message: `unique constraint must be an array of column names in table "${tableName}"`,
179
181
  });
182
+ continue;
183
+ }
184
+ for (let i = 0; i < group.length; i++) {
185
+ const entry = group[i];
186
+ if (typeof entry !== "string") {
187
+ errors.push({
188
+ path: `${groupPath}[${i}]`,
189
+ message: `unique entry must be a string in table "${tableName}"`,
190
+ });
191
+ } else if (entry !== pk && !columnNames.has(entry)) {
192
+ errors.push({
193
+ path: `${groupPath}[${i}]`,
194
+ message: `unique entry "${entry}" does not match any column or the primary key "${pk}" in table "${tableName}"`,
195
+ });
196
+ }
180
197
  }
181
198
  }
182
199
  }