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
package/src/cli/main.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { parseFlags, OutputContext } = require("./flags");
|
|
5
|
+
|
|
6
|
+
const initCmd = require("./commands/init");
|
|
7
|
+
const inspectCmd = require("./commands/inspect");
|
|
8
|
+
const generateCmd = require("./commands/generate");
|
|
9
|
+
const doctorCmd = require("./commands/doctor");
|
|
10
|
+
const diffCmd = require("./commands/diff");
|
|
11
|
+
const helpCmd = require("./commands/help");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Map of subcommand names to their handler functions.
|
|
15
|
+
*/
|
|
16
|
+
const COMMANDS = {
|
|
17
|
+
init: initCmd,
|
|
18
|
+
inspect: inspectCmd,
|
|
19
|
+
generate: generateCmd,
|
|
20
|
+
doctor: doctorCmd,
|
|
21
|
+
diff: diffCmd,
|
|
22
|
+
help: helpCmd,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Descriptions for each subcommand, used in help output.
|
|
27
|
+
*/
|
|
28
|
+
const COMMAND_DESCRIPTIONS = {
|
|
29
|
+
init: "Scaffold a new project from a schema file or interactively",
|
|
30
|
+
inspect: "Introspect a live database and produce a schema file",
|
|
31
|
+
generate: "Generate models, routes, tests, and OpenAPI spec from a schema",
|
|
32
|
+
doctor: "Validate schema, check dependencies, and verify file sync",
|
|
33
|
+
diff: "Preview changes between current files and what the schema would produce",
|
|
34
|
+
help: "Show help for a command",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Per-command flag summaries shown in the general help overview.
|
|
39
|
+
*/
|
|
40
|
+
const COMMAND_FLAGS = {
|
|
41
|
+
init: [
|
|
42
|
+
["--from <path>", "Read config from a schema file"],
|
|
43
|
+
["--framework <name>", "express, ultimate-express"],
|
|
44
|
+
[
|
|
45
|
+
"--database <name>",
|
|
46
|
+
"mysql, mariadb, postgres, sqlite3, mongodb, mssql, cockroachdb, oracle, redis, dynamodb",
|
|
47
|
+
],
|
|
48
|
+
["--db <name>", "Alias for --database"],
|
|
49
|
+
["--session <type>", "memory, redis, database"],
|
|
50
|
+
["--output <dir>", "Directory for backend source files"],
|
|
51
|
+
["--rateLimiting", "Enable rate limiting (default: yes)"],
|
|
52
|
+
["--helmet", "Enable Helmet security headers (default: yes)"],
|
|
53
|
+
["--logger", "Enable Winston + Loki logger (default: yes)"],
|
|
54
|
+
],
|
|
55
|
+
inspect: [
|
|
56
|
+
["--type <adapter>", "Database adapter (required)"],
|
|
57
|
+
["--env <path>", "Path to .env file"],
|
|
58
|
+
["--out <path>", "Output file (default: dbmr.schema.json)"],
|
|
59
|
+
["--tables <list>", "Comma-separated table filter"],
|
|
60
|
+
],
|
|
61
|
+
generate: [
|
|
62
|
+
["--from <path>", "Schema file (default: dbmr.schema.json)"],
|
|
63
|
+
["--models", "Generate only model files"],
|
|
64
|
+
["--routes", "Generate only route files"],
|
|
65
|
+
["--openapi", "Generate only OpenAPI spec"],
|
|
66
|
+
["--tests", "Generate only test files"],
|
|
67
|
+
["--llm-docs", "Generate only LLM documentation"],
|
|
68
|
+
],
|
|
69
|
+
doctor: [["--from <path>", "Schema file (default: dbmr.schema.json)"]],
|
|
70
|
+
diff: [["--from <path>", "Schema file (default: dbmr.schema.json)"]],
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Print help message listing all available subcommands with their flags.
|
|
75
|
+
*/
|
|
76
|
+
function printHelp() {
|
|
77
|
+
console.log("Usage: db-model-router <command> [options]\n");
|
|
78
|
+
|
|
79
|
+
console.log("Commands:\n");
|
|
80
|
+
for (const [name, desc] of Object.entries(COMMAND_DESCRIPTIONS)) {
|
|
81
|
+
if (name === "help") {
|
|
82
|
+
console.log(` ${name.padEnd(12)} ${desc}`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
console.log(` ${name.padEnd(12)} ${desc}`);
|
|
86
|
+
const flags = COMMAND_FLAGS[name];
|
|
87
|
+
if (flags) {
|
|
88
|
+
for (const [flag, info] of flags) {
|
|
89
|
+
console.log(` ${flag.padEnd(22)} ${info}`);
|
|
90
|
+
}
|
|
91
|
+
console.log("");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log("Global flags (all commands):");
|
|
96
|
+
console.log(" --yes Accept all defaults without prompting");
|
|
97
|
+
console.log(" --json Output machine-readable JSON");
|
|
98
|
+
console.log(" --dry-run Preview actions without side effects");
|
|
99
|
+
console.log(" --no-install Skip npm install step");
|
|
100
|
+
console.log(" --help Show help for a command");
|
|
101
|
+
console.log('\nRun "db-model-router help <command>" for detailed usage.');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Print error for unknown subcommand and list valid subcommands.
|
|
106
|
+
* @param {string} cmd - The unknown subcommand
|
|
107
|
+
*/
|
|
108
|
+
function printUnknown(cmd) {
|
|
109
|
+
const valid = Object.keys(COMMANDS).join(", ");
|
|
110
|
+
console.error(`Unknown command: ${cmd}`);
|
|
111
|
+
console.error(`Valid commands: ${valid}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Main CLI entry point.
|
|
116
|
+
* @param {string[]} argv - process.argv.slice(2) style array
|
|
117
|
+
*/
|
|
118
|
+
async function main(argv) {
|
|
119
|
+
const { subcommand, flags, args } = parseFlags(argv);
|
|
120
|
+
|
|
121
|
+
if (!subcommand) {
|
|
122
|
+
printHelp();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// `help <command>` — extract the topic from the second positional arg
|
|
127
|
+
if (subcommand === "help") {
|
|
128
|
+
// Re-parse to grab the second positional word as the help topic.
|
|
129
|
+
// parseFlags puts the first positional into subcommand; the second
|
|
130
|
+
// positional ends up as a key in args (if it looks like a flag value)
|
|
131
|
+
// or is lost. So we grab it directly from argv.
|
|
132
|
+
const topic =
|
|
133
|
+
argv.find((a, i) => i > 0 && !a.startsWith("-") && argv[0] === "help") ||
|
|
134
|
+
argv.find(
|
|
135
|
+
(a, i) => i > 0 && !a.startsWith("-") && argv.indexOf("help") < i,
|
|
136
|
+
);
|
|
137
|
+
args._command = topic || null;
|
|
138
|
+
const ctx = new OutputContext(flags);
|
|
139
|
+
await helpCmd(args, flags, ctx, { printHelp });
|
|
140
|
+
ctx.flush();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// `<command> --help` — show detailed help for that command
|
|
145
|
+
if (flags.help) {
|
|
146
|
+
args._command = subcommand;
|
|
147
|
+
const ctx = new OutputContext(flags);
|
|
148
|
+
await helpCmd(args, flags, ctx, { printHelp });
|
|
149
|
+
ctx.flush();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!COMMANDS[subcommand]) {
|
|
154
|
+
printUnknown(subcommand);
|
|
155
|
+
process.exitCode = 1;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const ctx = new OutputContext(flags);
|
|
160
|
+
await COMMANDS[subcommand](args, flags, ctx);
|
|
161
|
+
ctx.flush();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// When run directly as a script
|
|
165
|
+
if (require.main === module) {
|
|
166
|
+
main(process.argv.slice(2)).catch((err) => {
|
|
167
|
+
console.error(err.message || err);
|
|
168
|
+
process.exitCode = 1;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = main;
|
|
173
|
+
module.exports.COMMANDS = COMMANDS;
|
|
174
|
+
module.exports.printHelp = printHelp;
|
|
175
|
+
module.exports.printUnknown = printUnknown;
|
package/src/commons/model.js
CHANGED
|
@@ -280,6 +280,7 @@ module.exports = function model(
|
|
|
280
280
|
data,
|
|
281
281
|
getPayloadValidator("CREATE", modelStructure, primary_key, false),
|
|
282
282
|
);
|
|
283
|
+
const originalData = { ...data };
|
|
283
284
|
data = RemoveUnknownData(modelStructure, [data]);
|
|
284
285
|
data = jsonStringify(data);
|
|
285
286
|
updateResult = await db.upsert(table, data, unique);
|
|
@@ -288,12 +289,10 @@ module.exports = function model(
|
|
|
288
289
|
[[primary_key, "=", updateResult.id]],
|
|
289
290
|
]);
|
|
290
291
|
return getResult.count > 0 ? getResult["data"][0] : null;
|
|
291
|
-
} else if (
|
|
292
|
-
const result = await db.get(
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
option.safeDelete,
|
|
296
|
-
);
|
|
292
|
+
} else if (originalData.hasOwnProperty(primary_key)) {
|
|
293
|
+
const result = await db.get(table, [
|
|
294
|
+
[[primary_key, "=", originalData[primary_key]]],
|
|
295
|
+
]);
|
|
297
296
|
if (result.count > 0) return result["data"][0];
|
|
298
297
|
else return null;
|
|
299
298
|
}
|
package/src/commons/route.js
CHANGED
|
@@ -156,6 +156,14 @@ module.exports = function route(model, override = {}) {
|
|
|
156
156
|
});
|
|
157
157
|
})
|
|
158
158
|
.post("/", (req, res) => {
|
|
159
|
+
if (!req.body || !Array.isArray(req.body.data)) {
|
|
160
|
+
return res
|
|
161
|
+
.status(400)
|
|
162
|
+
.send({
|
|
163
|
+
type: "danger",
|
|
164
|
+
message: "Request body must contain a 'data' array",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
159
167
|
let payload = payloadOverride(req.body.data, req, override);
|
|
160
168
|
model
|
|
161
169
|
.insert({ data: payload })
|
|
@@ -167,6 +175,14 @@ module.exports = function route(model, override = {}) {
|
|
|
167
175
|
});
|
|
168
176
|
})
|
|
169
177
|
.put("/", (req, res) => {
|
|
178
|
+
if (!req.body || !Array.isArray(req.body.data)) {
|
|
179
|
+
return res
|
|
180
|
+
.status(400)
|
|
181
|
+
.send({
|
|
182
|
+
type: "danger",
|
|
183
|
+
message: "Request body must contain a 'data' array",
|
|
184
|
+
});
|
|
185
|
+
}
|
|
170
186
|
let payload = payloadOverride(req.body.data, req, override);
|
|
171
187
|
model
|
|
172
188
|
.update({ data: payload })
|
|
@@ -178,6 +194,14 @@ module.exports = function route(model, override = {}) {
|
|
|
178
194
|
});
|
|
179
195
|
})
|
|
180
196
|
.delete("/", (req, res) => {
|
|
197
|
+
if (!req.body || !Array.isArray(req.body.data)) {
|
|
198
|
+
return res
|
|
199
|
+
.status(400)
|
|
200
|
+
.send({
|
|
201
|
+
type: "danger",
|
|
202
|
+
message: "Request body must contain a 'data' array",
|
|
203
|
+
});
|
|
204
|
+
}
|
|
181
205
|
let payload = payloadOverride(req.body.data, req, override);
|
|
182
206
|
model
|
|
183
207
|
.remove(payload)
|
package/src/index.js
CHANGED
|
@@ -2,6 +2,7 @@ const model = require("./commons/model.js");
|
|
|
2
2
|
const route = require("./commons/route.js");
|
|
3
3
|
const routers = {
|
|
4
4
|
mysql: "./mysql/db.js",
|
|
5
|
+
mariadb: "./mysql/db.js",
|
|
5
6
|
postgresql: "./postgres/db.js",
|
|
6
7
|
postgres: "./postgres/db.js",
|
|
7
8
|
oracle: "./oracle/db.js",
|
|
@@ -29,6 +30,7 @@ function init(DB_TYPE) {
|
|
|
29
30
|
if (err.code === "MODULE_NOT_FOUND") {
|
|
30
31
|
const driverMap = {
|
|
31
32
|
mysql: "mysql2",
|
|
33
|
+
mariadb: "mysql2",
|
|
32
34
|
postgresql: "pg",
|
|
33
35
|
postgres: "pg",
|
|
34
36
|
oracle: "oracledb",
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { validateSchema, SchemaValidationError } = require("./schema-validator");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parse a schema from a JSON string or plain object.
|
|
7
|
+
*
|
|
8
|
+
* - If `input` is a string, JSON.parse it (wrapping parse errors).
|
|
9
|
+
* - Validate via validateSchema(); throw SchemaValidationError if invalid.
|
|
10
|
+
* - Normalize each table with defaults for pk, unique, timestamps, softDelete.
|
|
11
|
+
* - Return the internal { adapter, framework, tables, relationships, options } representation.
|
|
12
|
+
*
|
|
13
|
+
* @param {string|object} input — raw JSON string or parsed object
|
|
14
|
+
* @returns {{ adapter: string, framework: string, tables: object, relationships: Array, options: object }}
|
|
15
|
+
* @throws {SchemaValidationError}
|
|
16
|
+
*/
|
|
17
|
+
function parseSchema(input) {
|
|
18
|
+
let raw = input;
|
|
19
|
+
|
|
20
|
+
// If string, attempt JSON.parse; wrap errors
|
|
21
|
+
if (typeof raw === "string") {
|
|
22
|
+
try {
|
|
23
|
+
raw = JSON.parse(raw);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
throw new SchemaValidationError([
|
|
26
|
+
{
|
|
27
|
+
path: "",
|
|
28
|
+
message: `Invalid JSON: ${err.message}`,
|
|
29
|
+
},
|
|
30
|
+
]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Validate
|
|
35
|
+
const result = validateSchema(raw);
|
|
36
|
+
if (!result.valid) {
|
|
37
|
+
throw new SchemaValidationError(result.errors);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Normalize tables
|
|
41
|
+
const tables = {};
|
|
42
|
+
for (const [tableName, tableDef] of Object.entries(raw.tables)) {
|
|
43
|
+
const pk = tableDef.pk || "id";
|
|
44
|
+
const unique = tableDef.unique !== undefined ? [...tableDef.unique] : [pk];
|
|
45
|
+
const timestamps =
|
|
46
|
+
tableDef.timestamps !== undefined
|
|
47
|
+
? { ...tableDef.timestamps }
|
|
48
|
+
: { created_at: null, modified_at: null };
|
|
49
|
+
// Ensure timestamps always has both keys
|
|
50
|
+
if (!("created_at" in timestamps)) {
|
|
51
|
+
timestamps.created_at = null;
|
|
52
|
+
}
|
|
53
|
+
if (!("modified_at" in timestamps)) {
|
|
54
|
+
timestamps.modified_at = null;
|
|
55
|
+
}
|
|
56
|
+
const softDelete =
|
|
57
|
+
tableDef.softDelete !== undefined ? tableDef.softDelete : null;
|
|
58
|
+
|
|
59
|
+
tables[tableName] = {
|
|
60
|
+
name: tableName,
|
|
61
|
+
columns: { ...tableDef.columns },
|
|
62
|
+
pk,
|
|
63
|
+
unique,
|
|
64
|
+
softDelete,
|
|
65
|
+
timestamps,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
adapter: raw.adapter,
|
|
71
|
+
framework: raw.framework,
|
|
72
|
+
tables,
|
|
73
|
+
relationships: raw.relationships ? [...raw.relationships] : [],
|
|
74
|
+
options: raw.options ? { ...raw.options } : {},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { parseSchema };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Serialize a parsed schema (internal representation from parseSchema())
|
|
5
|
+
* back into a JSON string.
|
|
6
|
+
*
|
|
7
|
+
* - Tables are sorted alphabetically by name.
|
|
8
|
+
* - Relationships are sorted by [parent, child].
|
|
9
|
+
* - Output uses 2-space indentation with a trailing newline.
|
|
10
|
+
* - Optional fields (options, unique, softDelete, relationships) are preserved.
|
|
11
|
+
*
|
|
12
|
+
* @param {object} schema — internal representation from parseSchema()
|
|
13
|
+
* @returns {string} — JSON with 2-space indent + trailing newline
|
|
14
|
+
*/
|
|
15
|
+
function printSchema(schema) {
|
|
16
|
+
const output = {};
|
|
17
|
+
|
|
18
|
+
output.adapter = schema.adapter;
|
|
19
|
+
output.framework = schema.framework;
|
|
20
|
+
|
|
21
|
+
// Preserve options if present and non-empty
|
|
22
|
+
if (schema.options && Object.keys(schema.options).length > 0) {
|
|
23
|
+
output.options = schema.options;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Sort tables alphabetically by name
|
|
27
|
+
const sortedTableNames = Object.keys(schema.tables).sort();
|
|
28
|
+
const tables = {};
|
|
29
|
+
for (const name of sortedTableNames) {
|
|
30
|
+
const table = schema.tables[name];
|
|
31
|
+
const tableDef = {
|
|
32
|
+
columns: table.columns,
|
|
33
|
+
pk: table.pk || "id",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Preserve unique if not the default [pk]
|
|
37
|
+
const defaultUnique = [table.pk || "id"];
|
|
38
|
+
const hasCustomUnique =
|
|
39
|
+
table.unique &&
|
|
40
|
+
(table.unique.length !== defaultUnique.length ||
|
|
41
|
+
table.unique.some((v, i) => v !== defaultUnique[i]));
|
|
42
|
+
if (hasCustomUnique) {
|
|
43
|
+
tableDef.unique = table.unique;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Preserve softDelete if set
|
|
47
|
+
if (table.softDelete !== null && table.softDelete !== undefined) {
|
|
48
|
+
tableDef.softDelete = table.softDelete;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Preserve timestamps if not the default { created_at: null, modified_at: null }
|
|
52
|
+
if (table.timestamps) {
|
|
53
|
+
const hasCustomTimestamps =
|
|
54
|
+
table.timestamps.created_at !== null ||
|
|
55
|
+
table.timestamps.modified_at !== null;
|
|
56
|
+
if (hasCustomTimestamps) {
|
|
57
|
+
tableDef.timestamps = table.timestamps;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
tables[name] = tableDef;
|
|
62
|
+
}
|
|
63
|
+
output.tables = tables;
|
|
64
|
+
|
|
65
|
+
// Sort relationships by [parent, child] and include if non-empty
|
|
66
|
+
if (schema.relationships && schema.relationships.length > 0) {
|
|
67
|
+
output.relationships = [...schema.relationships].sort((a, b) => {
|
|
68
|
+
const cmp = a.parent.localeCompare(b.parent);
|
|
69
|
+
if (cmp !== 0) return cmp;
|
|
70
|
+
return a.child.localeCompare(b.child);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return JSON.stringify(output, null, 2) + "\n";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { printSchema };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Convert a parsed schema into the model metadata array used by
|
|
5
|
+
* generateModelFile(), generateRouteFile(), generateOpenAPISpec().
|
|
6
|
+
*
|
|
7
|
+
* Each ModelMeta matches the shape returned by the existing introspection
|
|
8
|
+
* functions:
|
|
9
|
+
* { table, structure, primary_key, unique, option }
|
|
10
|
+
*
|
|
11
|
+
* - The primary key column is excluded from `structure`.
|
|
12
|
+
* - Timestamp columns (created_at, modified_at values) are excluded from `structure`.
|
|
13
|
+
* - The softDelete column is excluded from `structure`.
|
|
14
|
+
* - Output is sorted alphabetically by table name.
|
|
15
|
+
*
|
|
16
|
+
* @param {{ adapter: string, framework: string, tables: object, relationships: Array, options: object }} schema
|
|
17
|
+
* @returns {Array<{ table: string, structure: object, primary_key: string, unique: string[], option: { safeDelete: string|null, created_at: string|null, modified_at: string|null } }>}
|
|
18
|
+
*/
|
|
19
|
+
function schemaToModelMeta(schema) {
|
|
20
|
+
const tableNames = Object.keys(schema.tables).sort();
|
|
21
|
+
return tableNames.map((tableName) => {
|
|
22
|
+
const tableDef = schema.tables[tableName];
|
|
23
|
+
|
|
24
|
+
// Build the set of columns to exclude from structure
|
|
25
|
+
const excludeSet = new Set();
|
|
26
|
+
|
|
27
|
+
// Exclude primary key
|
|
28
|
+
excludeSet.add(tableDef.pk);
|
|
29
|
+
|
|
30
|
+
// Exclude timestamp columns
|
|
31
|
+
if (tableDef.timestamps) {
|
|
32
|
+
if (tableDef.timestamps.created_at) {
|
|
33
|
+
excludeSet.add(tableDef.timestamps.created_at);
|
|
34
|
+
}
|
|
35
|
+
if (tableDef.timestamps.modified_at) {
|
|
36
|
+
excludeSet.add(tableDef.timestamps.modified_at);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Exclude softDelete column
|
|
41
|
+
if (tableDef.softDelete) {
|
|
42
|
+
excludeSet.add(tableDef.softDelete);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Build structure, excluding the above columns
|
|
46
|
+
const structure = {};
|
|
47
|
+
for (const [colName, rule] of Object.entries(tableDef.columns)) {
|
|
48
|
+
if (!excludeSet.has(colName)) {
|
|
49
|
+
// Strip auto_increment and datetime columns from model structure
|
|
50
|
+
// (DB handles these automatically)
|
|
51
|
+
const baseType = rule.replace(/^required\|/, "");
|
|
52
|
+
if (baseType === "auto_increment") continue;
|
|
53
|
+
structure[colName] = rule;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Map option fields
|
|
58
|
+
const option = {
|
|
59
|
+
safeDelete: tableDef.softDelete || null,
|
|
60
|
+
created_at: tableDef.timestamps
|
|
61
|
+
? tableDef.timestamps.created_at || null
|
|
62
|
+
: null,
|
|
63
|
+
modified_at: tableDef.timestamps
|
|
64
|
+
? tableDef.timestamps.modified_at || null
|
|
65
|
+
: null,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
table: tableName,
|
|
70
|
+
structure,
|
|
71
|
+
primary_key: tableDef.pk,
|
|
72
|
+
unique: [...tableDef.unique],
|
|
73
|
+
option,
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { schemaToModelMeta };
|