maxserver 0.0.16 → 0.0.17
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 +19 -4
- package/package.json +1 -1
- package/src/index.js +1 -0
- package/src/routeLoader.js +80 -72
package/README.md
CHANGED
|
@@ -106,6 +106,9 @@ export default async function (req, res) {
|
|
|
106
106
|
}
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
If you don't want to autoregister some routes, then simply don't add that magic comment 😃
|
|
110
|
+
|
|
111
|
+
|
|
109
112
|
<br>
|
|
110
113
|
<br>
|
|
111
114
|
|
|
@@ -143,10 +146,22 @@ export default {
|
|
|
143
146
|
|
|
144
147
|
<br>
|
|
145
148
|
|
|
146
|
-
## Route Options
|
|
147
|
-
Though we don't register routes manually, we don't set route options on the register call.
|
|
148
|
-
|
|
149
|
-
|
|
149
|
+
## 🛠️ Route Options
|
|
150
|
+
Though we don't mostly register routes manually, we don't set route options on the register call.
|
|
151
|
+
If needed, you can wether register that route manually or just set them on the schema.
|
|
152
|
+
|
|
153
|
+
```js
|
|
154
|
+
// Inside schema
|
|
155
|
+
|
|
156
|
+
export default {
|
|
157
|
+
routeOptions: {
|
|
158
|
+
config: {
|
|
159
|
+
preHandler: ...
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
...
|
|
163
|
+
```
|
|
164
|
+
|
|
150
165
|
|
|
151
166
|
<br>
|
|
152
167
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -25,6 +25,7 @@ export default async function maxserver(config = {}) {
|
|
|
25
25
|
mongodb = process.env.MONGODB,
|
|
26
26
|
docs = process.env.DOCS !== "false",
|
|
27
27
|
cors = process.env.CORS || "*",
|
|
28
|
+
env = process.env.NODE_ENV || "development",
|
|
28
29
|
openapiInfo,
|
|
29
30
|
static: isStatic = process.env.STATIC,
|
|
30
31
|
public: isPublic = process.env.PUBLIC === "true",
|
package/src/routeLoader.js
CHANGED
|
@@ -1,89 +1,91 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 🚀 AUTO-LOADER
|
|
3
|
+
* Scans src/ for files with "// METHOD /url" comments.
|
|
4
|
+
* Automatically registers them as Fastify routes.
|
|
5
|
+
*/
|
|
4
6
|
|
|
5
7
|
import fs from "fs";
|
|
6
8
|
import path from "path";
|
|
9
|
+
import { pathToFileURL } from "url";
|
|
7
10
|
|
|
8
|
-
const ROUTE_OPTION_KEYS = new Set([
|
|
9
|
-
"config",
|
|
10
|
-
"preHandler",
|
|
11
|
-
"onRequest",
|
|
12
|
-
"preValidation",
|
|
13
|
-
"preSerialization",
|
|
14
|
-
"errorHandler",
|
|
15
|
-
"logLevel",
|
|
16
|
-
"bodyLimit",
|
|
17
|
-
"attachValidation",
|
|
18
|
-
"exposeHeadRoute",
|
|
19
|
-
"constraints",
|
|
20
|
-
"timeout",
|
|
21
|
-
"websocket",
|
|
22
|
-
"prefixTrailingSlash",
|
|
23
|
-
]);
|
|
24
11
|
|
|
12
|
+
// Matches lines like: // GET /api/v1/users
|
|
13
|
+
const ROUTE_REGEX = /^\/\/\s*(GET|POST|PUT|PATCH|DELETE)\s+(.+)$/gm;
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Recursively finds all .js files in a directory.
|
|
18
|
+
* Skips node_modules and dotfiles.
|
|
19
|
+
*/
|
|
25
20
|
function walk(dir, out = []) {
|
|
26
21
|
if (!fs.existsSync(dir)) return out;
|
|
27
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
28
22
|
|
|
29
|
-
for (const
|
|
30
|
-
if (
|
|
31
|
-
if (
|
|
32
|
-
const full = path.join(dir, entry.name);
|
|
23
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
24
|
+
if (e.name === "node_modules") continue;
|
|
25
|
+
if (e.name.startsWith(".")) continue;
|
|
33
26
|
|
|
34
|
-
|
|
27
|
+
const full = path.join(dir, e.name);
|
|
28
|
+
if (e.isDirectory()) {
|
|
35
29
|
walk(full, out);
|
|
36
|
-
|
|
37
|
-
out.push(full);
|
|
30
|
+
continue;
|
|
38
31
|
}
|
|
32
|
+
|
|
33
|
+
if (!e.name.endsWith(".js")) continue;
|
|
34
|
+
out.push(full);
|
|
39
35
|
}
|
|
36
|
+
|
|
40
37
|
return out;
|
|
41
38
|
}
|
|
42
39
|
|
|
43
|
-
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Extracts method and URL from the file's "magic comment".
|
|
43
|
+
* Enforces strict "One Route Per File" policy.
|
|
44
|
+
*/
|
|
45
|
+
function getRoute(file) {
|
|
44
46
|
const text = fs.readFileSync(file, "utf8");
|
|
45
|
-
const
|
|
46
|
-
const firstContent = lines.find((line) => line.trim().length > 0);
|
|
47
|
+
const matches = [...text.matchAll(ROUTE_REGEX)];
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
}
|
|
49
|
+
if (matches.length === 0) return null;
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
// Warn if user accidentally defines multiple routes in one file
|
|
52
|
+
if (matches.length > 1) {
|
|
53
|
+
console.warn(
|
|
54
|
+
`⚠️ Ignored "${file}": Found ${matches.length} route comments. ` +
|
|
55
|
+
`Only 1 allowed per file.`
|
|
56
|
+
);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
52
59
|
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
const m = matches[0];
|
|
61
|
+
const method = m[1].toLowerCase();
|
|
62
|
+
// Normalize URL: Remove all leading slashes, then add exactly one
|
|
63
|
+
const url = "/" + m[2].trim().replace(/^\/+/, "");
|
|
57
64
|
|
|
58
|
-
return { method
|
|
65
|
+
return { method, url };
|
|
59
66
|
}
|
|
60
67
|
|
|
61
|
-
function splitSchemaExport(raw) {
|
|
62
|
-
const routeOptions = {};
|
|
63
|
-
const schema = {};
|
|
64
|
-
|
|
65
|
-
for (const [key, value] of Object.entries(raw || {})) {
|
|
66
|
-
if (ROUTE_OPTION_KEYS.has(key)) {
|
|
67
|
-
routeOptions[key] = value;
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
schema[key] = value;
|
|
71
|
-
}
|
|
72
68
|
|
|
73
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Safe dynamic import that handles Windows paths correctly.
|
|
71
|
+
*/
|
|
72
|
+
async function importDefault(file) {
|
|
73
|
+
return (await import(pathToFileURL(file).href)).default;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
|
|
76
77
|
export async function loadRoutes(fastify) {
|
|
77
|
-
const ROOT = path.join(process.cwd(), "src");
|
|
78
|
-
const files = walk(ROOT);
|
|
79
78
|
const seen = new Map();
|
|
79
|
+
const root = path.join(process.cwd(), "src");
|
|
80
80
|
|
|
81
|
-
for (const file of
|
|
81
|
+
for (const file of walk(root)) {
|
|
82
|
+
// Skip schema files; they are loaded alongside their route file
|
|
82
83
|
if (file.endsWith(".schema.js")) continue;
|
|
83
84
|
|
|
84
|
-
const info =
|
|
85
|
+
const info = getRoute(file);
|
|
85
86
|
if (!info) continue;
|
|
86
87
|
|
|
88
|
+
// 🛡️ Collision Detection: Ensure no two files claim the same route
|
|
87
89
|
const key = info.method + " " + info.url;
|
|
88
90
|
if (seen.has(key)) {
|
|
89
91
|
throw new Error(
|
|
@@ -94,29 +96,35 @@ export async function loadRoutes(fastify) {
|
|
|
94
96
|
}
|
|
95
97
|
seen.set(key, file);
|
|
96
98
|
|
|
97
|
-
|
|
98
|
-
const handler =
|
|
99
|
+
// Import the route handler
|
|
100
|
+
const handler = await importDefault(file);
|
|
101
|
+
if (typeof handler !== "function") {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`Route "${key}" in "${file}" must export a default function.`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
99
106
|
|
|
107
|
+
// 🤝 Schema Loading: Look for sibling .schema.js file
|
|
100
108
|
const schemaFile = file.replace(/\.js$/, ".schema.js");
|
|
101
|
-
let raw =
|
|
109
|
+
let raw = {};
|
|
102
110
|
|
|
103
111
|
if (fs.existsSync(schemaFile)) {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
fastify.log.warn(
|
|
108
|
-
`Route schema missing: ` +
|
|
109
|
-
`${info.method.toUpperCase()} ${info.url}`
|
|
110
|
-
);
|
|
111
|
-
raw = {};
|
|
112
|
+
const loaded = await importDefault(schemaFile);
|
|
113
|
+
// 🛡️ Guard: Ensure export is a valid object before using it
|
|
114
|
+
if (loaded && typeof loaded === "object") raw = loaded;
|
|
112
115
|
}
|
|
113
116
|
|
|
114
|
-
|
|
117
|
+
// ✨ Magic: Extract 'auth' and 'routeOptions' specifically
|
|
118
|
+
let { auth, routeOptions = {}, ...schema } = raw;
|
|
115
119
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
// Inject 'auth' into config if present (Syntactic Sugar)
|
|
121
|
+
if (auth !== undefined) {
|
|
122
|
+
routeOptions = {
|
|
123
|
+
...routeOptions,
|
|
124
|
+
config: { ...(routeOptions.config || {}), auth: !!auth },
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
fastify[info.method](info.url, { ...routeOptions, schema }, handler);
|
|
121
129
|
}
|
|
122
|
-
}
|
|
130
|
+
}
|