db-model-router 1.0.17 → 1.0.19
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/.serena/project.yml +57 -30
- package/README.md +117 -104
- package/dbmr.postgres.schema.json +5 -1
- package/dbmr.postgres.test.schema.json +5 -1
- package/dbmr.schema.json +5 -1
- package/docs/dbmr-schema-spec.md +28 -4
- package/package.json +1 -1
- package/scripts/demo-create.js +56 -87
- package/skill/SKILL.md +77 -46
- package/src/cli/commands/generate.js +32 -115
- package/src/cli/commands/help.js +55 -62
- package/src/cli/commands/init.js +95 -72
- package/src/cli/flags.js +4 -1
- package/src/cli/init/generators.js +2 -2
- package/src/cli/init.js +13 -77
- package/src/cli/main.js +4 -25
- package/src/cli/saas/generate-saas-migrations.js +2 -1
- package/src/schema/schema-parser.js +18 -1
- package/src/schema/schema-validator.js +75 -0
- package/src/cli/init/prompt.js +0 -191
|
@@ -104,12 +104,87 @@ function validateSchema(raw) {
|
|
|
104
104
|
Array.isArray(raw.options)
|
|
105
105
|
) {
|
|
106
106
|
errors.push({ path: "options", message: "options must be an object" });
|
|
107
|
+
} else {
|
|
108
|
+
validateOptions(raw.options, errors);
|
|
107
109
|
}
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
return { valid: errors.length === 0, errors };
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Validate the buildout-config keys inside `options`.
|
|
117
|
+
* Only the known keys are type-checked; unknown keys are passed through silently.
|
|
118
|
+
* - output: string (relative or absolute path)
|
|
119
|
+
* - saasStructure: boolean
|
|
120
|
+
* - apiBasePath: string starting with "/"
|
|
121
|
+
* - port: positive integer
|
|
122
|
+
* - session: "memory" | "redis" | "database"
|
|
123
|
+
* - rateLimiting / helmet / logger / loki: boolean
|
|
124
|
+
*/
|
|
125
|
+
function validateOptions(options, errors) {
|
|
126
|
+
if (options.output !== undefined && options.output !== null) {
|
|
127
|
+
if (typeof options.output !== "string" || options.output.length === 0) {
|
|
128
|
+
errors.push({
|
|
129
|
+
path: "options.output",
|
|
130
|
+
message: "options.output must be a non-empty string path or null",
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (options.saasStructure !== undefined) {
|
|
136
|
+
if (typeof options.saasStructure !== "boolean") {
|
|
137
|
+
errors.push({
|
|
138
|
+
path: "options.saasStructure",
|
|
139
|
+
message: "options.saasStructure must be a boolean",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (options.apiBasePath !== undefined) {
|
|
145
|
+
if (
|
|
146
|
+
typeof options.apiBasePath !== "string" ||
|
|
147
|
+
!options.apiBasePath.startsWith("/")
|
|
148
|
+
) {
|
|
149
|
+
errors.push({
|
|
150
|
+
path: "options.apiBasePath",
|
|
151
|
+
message: 'options.apiBasePath must be a string starting with "/" (e.g. "/api")',
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (options.port !== undefined) {
|
|
157
|
+
if (
|
|
158
|
+
typeof options.port !== "number" ||
|
|
159
|
+
!Number.isInteger(options.port) ||
|
|
160
|
+
options.port <= 0
|
|
161
|
+
) {
|
|
162
|
+
errors.push({
|
|
163
|
+
path: "options.port",
|
|
164
|
+
message: "options.port must be a positive integer",
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (options.session !== undefined) {
|
|
170
|
+
if (!["memory", "redis", "database"].includes(options.session)) {
|
|
171
|
+
errors.push({
|
|
172
|
+
path: "options.session",
|
|
173
|
+
message: 'options.session must be one of: memory, redis, database',
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
for (const key of ["rateLimiting", "helmet", "logger", "loki"]) {
|
|
179
|
+
if (options[key] !== undefined && typeof options[key] !== "boolean") {
|
|
180
|
+
errors.push({
|
|
181
|
+
path: `options.${key}`,
|
|
182
|
+
message: `options.${key} must be a boolean`,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
113
188
|
/**
|
|
114
189
|
* Validate all table entries.
|
|
115
190
|
*/
|
package/src/cli/init/prompt.js
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const inquirer = require("inquirer");
|
|
4
|
-
|
|
5
|
-
const VALID_FRAMEWORKS = ["ultimate-express", "express"];
|
|
6
|
-
const VALID_DATABASES = [
|
|
7
|
-
"mysql",
|
|
8
|
-
"mariadb",
|
|
9
|
-
"postgres",
|
|
10
|
-
"sqlite3",
|
|
11
|
-
"mongodb",
|
|
12
|
-
"mssql",
|
|
13
|
-
"cockroachdb",
|
|
14
|
-
"oracle",
|
|
15
|
-
"redis",
|
|
16
|
-
"dynamodb",
|
|
17
|
-
];
|
|
18
|
-
const VALID_SESSIONS = ["memory", "redis", "database"];
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Parse CLI arguments into a partial answers object.
|
|
22
|
-
* Supports: --framework, --database, --session, --rateLimiting, --helmet, --logger
|
|
23
|
-
* Boolean flags accept: true/false, yes/no, 1/0, or just --flag (implies true)
|
|
24
|
-
* @param {string[]} argv
|
|
25
|
-
* @returns {Partial<import('./types').InitAnswers>}
|
|
26
|
-
*/
|
|
27
|
-
function parseInitArgs(argv) {
|
|
28
|
-
const args = {};
|
|
29
|
-
for (let i = 0; i < argv.length; i++) {
|
|
30
|
-
const arg = argv[i];
|
|
31
|
-
if (arg.startsWith("--")) {
|
|
32
|
-
const key = arg.slice(2);
|
|
33
|
-
const next = argv[i + 1];
|
|
34
|
-
if (next && !next.startsWith("--")) {
|
|
35
|
-
args[key] = next;
|
|
36
|
-
i++;
|
|
37
|
-
} else {
|
|
38
|
-
args[key] = "true";
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const partial = {};
|
|
44
|
-
|
|
45
|
-
if (args.framework && VALID_FRAMEWORKS.includes(args.framework)) {
|
|
46
|
-
partial.framework = args.framework;
|
|
47
|
-
}
|
|
48
|
-
if (args.database && VALID_DATABASES.includes(args.database)) {
|
|
49
|
-
partial.database = args.database;
|
|
50
|
-
}
|
|
51
|
-
// Accept --db as alias for --database
|
|
52
|
-
if (!partial.database && args.db && VALID_DATABASES.includes(args.db)) {
|
|
53
|
-
partial.database = args.db;
|
|
54
|
-
}
|
|
55
|
-
if (args.session && VALID_SESSIONS.includes(args.session)) {
|
|
56
|
-
partial.session = args.session;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Boolean flags
|
|
60
|
-
for (const flag of ["rateLimiting", "helmet", "logger", "loki"]) {
|
|
61
|
-
if (args[flag] !== undefined) {
|
|
62
|
-
partial[flag] = parseBool(args[flag]);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Output directory
|
|
67
|
-
if (args.output !== undefined && args.output !== "true") {
|
|
68
|
-
partial.output = args.output;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return partial;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Parse a string as a boolean.
|
|
76
|
-
* @param {string} val
|
|
77
|
-
* @returns {boolean}
|
|
78
|
-
*/
|
|
79
|
-
function parseBool(val) {
|
|
80
|
-
return ["true", "yes", "1"].includes(String(val).toLowerCase());
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Run the interactive prompt flow to collect user preferences.
|
|
85
|
-
* Any values already present in `prefilledAnswers` are skipped (not prompted).
|
|
86
|
-
* When all 6 values are provided, no prompts are shown at all.
|
|
87
|
-
* @param {Partial<import('./types').InitAnswers>} [prefilledAnswers={}]
|
|
88
|
-
* @returns {Promise<import('./types').InitAnswers>}
|
|
89
|
-
*/
|
|
90
|
-
async function promptUser(prefilledAnswers) {
|
|
91
|
-
const prefilled = prefilledAnswers || {};
|
|
92
|
-
|
|
93
|
-
const questions = [];
|
|
94
|
-
|
|
95
|
-
if (prefilled.framework === undefined) {
|
|
96
|
-
questions.push({
|
|
97
|
-
type: "list",
|
|
98
|
-
name: "framework",
|
|
99
|
-
message: "Select your Express framework:",
|
|
100
|
-
choices: VALID_FRAMEWORKS,
|
|
101
|
-
default: "ultimate-express",
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (prefilled.database === undefined) {
|
|
106
|
-
questions.push({
|
|
107
|
-
type: "list",
|
|
108
|
-
name: "database",
|
|
109
|
-
message: "Select your database:",
|
|
110
|
-
choices: VALID_DATABASES,
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (prefilled.session === undefined) {
|
|
115
|
-
questions.push({
|
|
116
|
-
type: "list",
|
|
117
|
-
name: "session",
|
|
118
|
-
message: "Select your session store:",
|
|
119
|
-
choices: VALID_SESSIONS,
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (prefilled.output === undefined) {
|
|
124
|
-
questions.push({
|
|
125
|
-
type: "input",
|
|
126
|
-
name: "output",
|
|
127
|
-
message:
|
|
128
|
-
"Output directory for backend source files (leave empty for root):",
|
|
129
|
-
default: "",
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (prefilled.rateLimiting === undefined) {
|
|
134
|
-
questions.push({
|
|
135
|
-
type: "confirm",
|
|
136
|
-
name: "rateLimiting",
|
|
137
|
-
message: "Enable rate limiting?",
|
|
138
|
-
default: true,
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (prefilled.helmet === undefined) {
|
|
143
|
-
questions.push({
|
|
144
|
-
type: "confirm",
|
|
145
|
-
name: "helmet",
|
|
146
|
-
message: "Enable Helmet security headers?",
|
|
147
|
-
default: true,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (prefilled.logger === undefined) {
|
|
152
|
-
questions.push({
|
|
153
|
-
type: "confirm",
|
|
154
|
-
name: "logger",
|
|
155
|
-
message: "Enable request/response logger (Winston)?",
|
|
156
|
-
default: true,
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// If all values are prefilled, skip prompts entirely
|
|
161
|
-
if (questions.length === 0) {
|
|
162
|
-
return /** @type {import('./types').InitAnswers} */ (prefilled);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const prompted = await inquirer.prompt(questions);
|
|
166
|
-
|
|
167
|
-
// Follow-up: if logger is enabled, ask about Loki
|
|
168
|
-
if (prompted.logger && prefilled.loki === undefined) {
|
|
169
|
-
const lokiAnswer = await inquirer.prompt([
|
|
170
|
-
{
|
|
171
|
-
type: "confirm",
|
|
172
|
-
name: "loki",
|
|
173
|
-
message: "Send logs to Grafana Loki?",
|
|
174
|
-
default: false,
|
|
175
|
-
},
|
|
176
|
-
]);
|
|
177
|
-
prompted.loki = lokiAnswer.loki;
|
|
178
|
-
} else if (!prompted.logger && prefilled.logger === undefined) {
|
|
179
|
-
prompted.loki = false;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return Object.assign({}, prefilled, prompted);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
module.exports = {
|
|
186
|
-
promptUser,
|
|
187
|
-
parseInitArgs,
|
|
188
|
-
VALID_FRAMEWORKS,
|
|
189
|
-
VALID_DATABASES,
|
|
190
|
-
VALID_SESSIONS,
|
|
191
|
-
};
|