maxserver 0.1.6 → 0.1.8
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 +2 -5
- package/package.json +1 -1
- package/src/index.js +1 -1
- package/src/setup.js +1 -38
- package/src/setupDocs.js +78 -0
- package/templates/src/Tests/hello.js +14 -0
- package/templates/src/Tests/hello.schema.js +27 -0
- package/templates/src/{welcome.js → Tests/welcome.js} +1 -1
- package/templates/src/Tests/welcome.schema.js +17 -0
- package/templates/vscode/tasks.json +14 -7
- package/archive/setupRoutesOld.js +0 -132
- package/templates/src/hello.js +0 -14
- package/templates/src/hello.schema.js +0 -36
package/README.md
CHANGED
|
@@ -229,11 +229,8 @@ Rule of thumb: make the message something you would want to see at 03:00 in logs
|
|
|
229
229
|
## 🛠️ Tips & Tools
|
|
230
230
|
|
|
231
231
|
### 🔌 Scalar API Client (Live Testing)
|
|
232
|
-
-
|
|
233
|
-
|
|
234
|
-
- Add Item → Import from OpenAPI
|
|
235
|
-
- Paste: `http://localhost:3000/openapi.json`
|
|
236
|
-
- Enable watch mode for live updates
|
|
232
|
+
- Open: `http://localhost:3000/docs`
|
|
233
|
+
|
|
237
234
|
|
|
238
235
|
### ⚡ VS Code Auto-Start
|
|
239
236
|
In `.vscode/tasks.json`, enable the task with:
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -6,12 +6,12 @@ import {
|
|
|
6
6
|
setupJwt,
|
|
7
7
|
setupMongo,
|
|
8
8
|
setupStatic,
|
|
9
|
-
setupDocs,
|
|
10
9
|
setupCookie,
|
|
11
10
|
} from "./setup.js";
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
import { getAddress } from "./getAddress.js";
|
|
14
|
+
import { setupDocs } from "./setupDocs.js";
|
|
15
15
|
import { setupRoutes } from "./setupRoutes.js";
|
|
16
16
|
|
|
17
17
|
|
package/src/setup.js
CHANGED
|
@@ -7,8 +7,7 @@ import cookie from "@fastify/cookie";
|
|
|
7
7
|
import mongodb from "@fastify/mongodb";
|
|
8
8
|
import fastifyStatic from "@fastify/static";
|
|
9
9
|
import helmet from "@fastify/helmet";
|
|
10
|
-
|
|
11
|
-
import apiReference from "@scalar/fastify-api-reference";
|
|
10
|
+
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
export async function setupHelmet(app) {
|
|
@@ -78,42 +77,6 @@ export async function setupJwt(app) {
|
|
|
78
77
|
|
|
79
78
|
|
|
80
79
|
|
|
81
|
-
export async function setupDocs(app) {
|
|
82
|
-
|
|
83
|
-
const info = app.maxserver.openapiInfo || {
|
|
84
|
-
title: "API",
|
|
85
|
-
version: "1.0.0",
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
await app.register(swagger, {
|
|
89
|
-
openapi: {
|
|
90
|
-
info,
|
|
91
|
-
// OpenAPI 3.x: securitySchemes must be defined globally here not per route
|
|
92
|
-
// Routes only add `security: [...]` that references these scheme names
|
|
93
|
-
components: {
|
|
94
|
-
securitySchemes: {
|
|
95
|
-
bearerAuth: { type: "http", scheme: "bearer" },
|
|
96
|
-
cookieAuth: { type: "apiKey", in: "cookie", name: "token" },
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
app.get("/openapi.json", {}, () => app.swagger());
|
|
104
|
-
|
|
105
|
-
if (app.maxserver.docs !== false)
|
|
106
|
-
await app.register(apiReference, { routePrefix: "/docs", openapi: true });
|
|
107
|
-
|
|
108
|
-
app.addHook("onRoute", (route) => {
|
|
109
|
-
const auth = route.config?.auth;
|
|
110
|
-
if (!auth) return;
|
|
111
|
-
route.schema ||= {};
|
|
112
|
-
route.schema.security ||= [{ bearerAuth: [] }, { cookieAuth: [] }];
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
80
|
|
|
118
81
|
export async function setupStatic(app) {
|
|
119
82
|
const dir = app.maxserver.static;
|
package/src/setupDocs.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import swagger from "@fastify/swagger";
|
|
2
|
+
import apiReference from "@scalar/fastify-api-reference";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const schema = {
|
|
7
|
+
summary: "OpenAPI Specification",
|
|
8
|
+
description: "Returns the full OpenAPI 3.0 specification for this API in JSON format.",
|
|
9
|
+
tags: ["Docs"],
|
|
10
|
+
response: {
|
|
11
|
+
200: {
|
|
12
|
+
type: "object",
|
|
13
|
+
additionalProperties: true,
|
|
14
|
+
properties: {
|
|
15
|
+
openapi: {
|
|
16
|
+
type: "string",
|
|
17
|
+
example: "3.0.3"
|
|
18
|
+
},
|
|
19
|
+
info: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
title: { type: "string", example: "maxserver API" },
|
|
23
|
+
version: { type: "string", example: "1.0.0" }
|
|
24
|
+
},
|
|
25
|
+
required: ["title", "version"]
|
|
26
|
+
},
|
|
27
|
+
paths: {
|
|
28
|
+
type: "object",
|
|
29
|
+
example: {}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
required: ["openapi", "info", "paths"]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
export async function setupDocs(app) {
|
|
42
|
+
|
|
43
|
+
const info = app.maxserver.openapiInfo || {
|
|
44
|
+
title: "API",
|
|
45
|
+
version: "1.0.0",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
await app.register(swagger, {
|
|
49
|
+
openapi: {
|
|
50
|
+
info,
|
|
51
|
+
// OpenAPI 3.x: securitySchemes must be defined globally here not per route
|
|
52
|
+
// Routes only add `security: [...]` that references these scheme names
|
|
53
|
+
components: {
|
|
54
|
+
securitySchemes: {
|
|
55
|
+
bearerAuth: { type: "http", scheme: "bearer" },
|
|
56
|
+
cookieAuth: { type: "apiKey", in: "cookie", name: "token" },
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
app.get("/openapi.json", { schema }, () => app.swagger());
|
|
64
|
+
|
|
65
|
+
if (app.maxserver.docs !== false)
|
|
66
|
+
await app.register(apiReference, { routePrefix: "/docs", openapi: true });
|
|
67
|
+
|
|
68
|
+
app.addHook("onRoute", (route) => {
|
|
69
|
+
const auth = route.config?.auth;
|
|
70
|
+
if (!auth) return;
|
|
71
|
+
route.schema ||= {};
|
|
72
|
+
route.schema.security ||= [{ bearerAuth: [] }, { cookieAuth: [] }];
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
summary: "Test post hello",
|
|
3
|
+
description: "Receives a name in the body and returns a personalized hello message.",
|
|
4
|
+
tags: ["Tests"],
|
|
5
|
+
body: {
|
|
6
|
+
type: "object",
|
|
7
|
+
properties: {
|
|
8
|
+
name: {
|
|
9
|
+
type: "string",
|
|
10
|
+
example: "John Doe"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
required: ["name"]
|
|
14
|
+
},
|
|
15
|
+
response: {
|
|
16
|
+
200: {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
message: {
|
|
20
|
+
type: "string",
|
|
21
|
+
example: "Hello John Doe again 🙋♂️"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
required: ["message"]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
summary: "Test get welcome",
|
|
3
|
+
description: "Returns a friendly welcome message to verify the server is operational.",
|
|
4
|
+
tags: ["Tests"],
|
|
5
|
+
response: {
|
|
6
|
+
200: {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: {
|
|
9
|
+
message: {
|
|
10
|
+
type: "string",
|
|
11
|
+
example: "Weclome to maxserver 😉 - Updated"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
required: ["message"]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
};
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
// Comment task in to autostart server on dir open
|
|
2
|
-
/*
|
|
3
|
-
|
|
4
|
-
|
|
5
2
|
{
|
|
6
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
7
4
|
"tasks": [
|
|
8
5
|
{
|
|
9
6
|
"label": "devserver 📟",
|
|
@@ -16,8 +13,18 @@
|
|
|
16
13
|
"runOptions": {
|
|
17
14
|
"runOn": "folderOpen"
|
|
18
15
|
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"label": "openBrowser",
|
|
19
|
+
"type": "shell",
|
|
20
|
+
"command": "sleep 1; open http://localhost:3000/docs",
|
|
21
|
+
"presentation": {
|
|
22
|
+
"reveal": "never",
|
|
23
|
+
"panel": "shared"
|
|
24
|
+
},
|
|
25
|
+
"runOptions": {
|
|
26
|
+
"runOn": "folderOpen"
|
|
27
|
+
}
|
|
19
28
|
}
|
|
20
29
|
]
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
*/
|
|
30
|
+
}
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* 🚀 AUTO-LOADER
|
|
4
|
-
* Scans src/ for files with "// METHOD /url" comments.
|
|
5
|
-
* Automatically registers them as Fastify routes.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import fs from "fs";
|
|
10
|
-
import path from "path";
|
|
11
|
-
import { pathToFileURL } from "url";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// Matches lines like: // GET /api/v1/users
|
|
15
|
-
const ROUTE_REGEX = /^\/\/\s*(GET|POST|PUT|PATCH|DELETE)\s+(.+)$/gm;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Recursively finds all .js files in a directory.
|
|
20
|
-
* Skips node_modules and dotfiles.
|
|
21
|
-
*/
|
|
22
|
-
function walk(dir, out = []) {
|
|
23
|
-
if (!fs.existsSync(dir)) return out;
|
|
24
|
-
|
|
25
|
-
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
26
|
-
if (e.name === "node_modules") continue;
|
|
27
|
-
if (e.name.startsWith(".")) continue;
|
|
28
|
-
|
|
29
|
-
const full = path.join(dir, e.name);
|
|
30
|
-
if (e.isDirectory()) {
|
|
31
|
-
walk(full, out);
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (!e.name.endsWith(".js")) continue;
|
|
36
|
-
out.push(full);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return out;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Extracts method and URL from the file's "magic comment".
|
|
45
|
-
* Enforces strict "One Route Per File" policy.
|
|
46
|
-
*/
|
|
47
|
-
function getRoute(file) {
|
|
48
|
-
const text = fs.readFileSync(file, "utf8");
|
|
49
|
-
const matches = [...text.matchAll(ROUTE_REGEX)];
|
|
50
|
-
|
|
51
|
-
if (matches.length === 0) return null;
|
|
52
|
-
|
|
53
|
-
// Warn if user accidentally defines multiple routes in one file
|
|
54
|
-
if (matches.length > 1) {
|
|
55
|
-
console.warn(
|
|
56
|
-
`⚠️ Ignored "${file}": Found ${matches.length} route comments. ` +
|
|
57
|
-
`Only 1 allowed per file.`
|
|
58
|
-
);
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const m = matches[0];
|
|
63
|
-
const method = m[1].toLowerCase();
|
|
64
|
-
// Normalize URL: Remove all leading slashes, then add exactly one
|
|
65
|
-
const url = "/" + m[2].trim().replace(/^\/+/, "");
|
|
66
|
-
|
|
67
|
-
return { method, url };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Safe dynamic import that handles Windows paths correctly.
|
|
73
|
-
*/
|
|
74
|
-
async function importDefault(file) {
|
|
75
|
-
return (await import(pathToFileURL(file).href)).default;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
export async function setupRoutes(app) {
|
|
80
|
-
const seen = new Map();
|
|
81
|
-
const root = path.resolve(app.maxserver.routesDir || "src");
|
|
82
|
-
|
|
83
|
-
for (const file of walk(root)) {
|
|
84
|
-
// Skip schema files; they are loaded alongside their route file
|
|
85
|
-
if (file.endsWith(".schema.js")) continue;
|
|
86
|
-
|
|
87
|
-
const info = getRoute(file);
|
|
88
|
-
if (!info) continue;
|
|
89
|
-
|
|
90
|
-
// 🛡️ Collision Detection: Ensure no two files claim the same route
|
|
91
|
-
const key = info.method + " " + info.url;
|
|
92
|
-
if (seen.has(key)) {
|
|
93
|
-
throw new Error(
|
|
94
|
-
`Duplicate route "${key}" detected:\n` +
|
|
95
|
-
`1. ${seen.get(key)}\n` +
|
|
96
|
-
`2. ${file}`
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
seen.set(key, file);
|
|
100
|
-
|
|
101
|
-
// Import the route handler
|
|
102
|
-
const handler = await importDefault(file);
|
|
103
|
-
if (typeof handler !== "function") {
|
|
104
|
-
throw new Error(
|
|
105
|
-
`Route "${key}" in "${file}" must export a default function.`
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// 🤝 Schema Loading: Look for sibling .schema.js file
|
|
110
|
-
const schemaFile = file.replace(/\.js$/, ".schema.js");
|
|
111
|
-
let raw = {};
|
|
112
|
-
|
|
113
|
-
if (fs.existsSync(schemaFile)) {
|
|
114
|
-
const loaded = await importDefault(schemaFile);
|
|
115
|
-
// 🛡️ Guard: Ensure export is a valid object before using it
|
|
116
|
-
if (loaded && typeof loaded === "object") raw = loaded;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// ✨ Magic: Extract 'auth' and 'routeOptions' specifically
|
|
120
|
-
let { auth, routeOptions = {}, ...schema } = raw;
|
|
121
|
-
|
|
122
|
-
// Inject 'auth' into config if present (Syntactic Sugar)
|
|
123
|
-
if (auth !== undefined) {
|
|
124
|
-
routeOptions = {
|
|
125
|
-
...routeOptions,
|
|
126
|
-
config: { ...(routeOptions.config || {}), auth: !!auth },
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
app[info.method](info.url, { ...routeOptions, schema }, handler);
|
|
131
|
-
}
|
|
132
|
-
}
|
package/templates/src/hello.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
|
|
3
|
-
// These 3 fields are for the documentation
|
|
4
|
-
// Not must have, but your auto generated documentation will be great
|
|
5
|
-
|
|
6
|
-
tags: ["Test"],
|
|
7
|
-
summary: "Post hello",
|
|
8
|
-
description: "Accepts a name and returns a greeting.",
|
|
9
|
-
|
|
10
|
-
body: {
|
|
11
|
-
type: "object",
|
|
12
|
-
required: ["name"],
|
|
13
|
-
properties: {
|
|
14
|
-
name: {
|
|
15
|
-
type: "string",
|
|
16
|
-
example: "Max",
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
|
|
21
|
-
response: {
|
|
22
|
-
200: {
|
|
23
|
-
type: "object",
|
|
24
|
-
properties: {
|
|
25
|
-
message: {
|
|
26
|
-
type: "string",
|
|
27
|
-
example: "Hello Max",
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// Hint - You don't need to write these ourself
|
|
35
|
-
// Just ask chat gpt or gemini to generate them
|
|
36
|
-
// In docs you will find little instruction for it
|