maxserver 0.8.3 → 0.8.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/.github/README.md +32 -7
- package/index.d.ts +10 -4
- package/package.json +1 -1
- package/src/index.js +15 -18
- package/src/setup.js +14 -23
- package/templates/src/Models/user.schema.js +13 -0
package/.github/README.md
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# maxserver
|
|
2
2
|
Node server setup based on **Fastify** to speed up backend development.
|
|
3
|
-
maxserver stands for **maximized simplicity** and **minimum boilerplate**.
|
|
4
3
|
|
|
5
4
|
- **Auto Routes**: auto imports and registers routes and schemas
|
|
6
5
|
- **Auto Docs**: auto generates docs based on schemas
|
|
@@ -123,9 +122,6 @@ export default {
|
|
|
123
122
|
};
|
|
124
123
|
```
|
|
125
124
|
|
|
126
|
-
**‼️ Important use export default**
|
|
127
|
-
Some examples in the template folder.
|
|
128
|
-
|
|
129
125
|
|
|
130
126
|
### MODELS
|
|
131
127
|
You can also auto register **models** (schemas which are shared between multiple routes).
|
|
@@ -134,6 +130,9 @@ schema or generic model, by looking if a sibling file exist or not 😉
|
|
|
134
130
|
|
|
135
131
|
<br>
|
|
136
132
|
|
|
133
|
+
**‼️ Important use export default**
|
|
134
|
+
Some examples in the template folder.
|
|
135
|
+
|
|
137
136
|
|
|
138
137
|
|
|
139
138
|
## 📚 API Docs
|
|
@@ -145,6 +144,20 @@ And you can also easily test any route.
|
|
|
145
144
|
|
|
146
145
|
|
|
147
146
|
|
|
147
|
+
## Global Named Exports
|
|
148
|
+
|
|
149
|
+
Every named export across your JavaScript files is automatically assigned to the Node.js `global` object on startup. This makes your utility functions, constants, or services instantly accessible anywhere in the application without manual `import` statements. The system safely ignores `default` exports and lifecycle hooks, and it will immediately halt with a clear console error if it detects duplicate variable names across different files.
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
148
161
|
|
|
149
162
|
|
|
150
163
|
## 🔐 Authentication
|
|
@@ -217,10 +230,22 @@ Rule of thumb: make the message something you would want to see at 03:00 in logs
|
|
|
217
230
|
<br>
|
|
218
231
|
|
|
219
232
|
|
|
233
|
+
## Autoregister Hooks
|
|
234
|
+
|
|
235
|
+
Exported functions starting with `autoregister_` automatically execute on startup and receive the Fastify `app` instance. This allows files to self-inject custom hooks, plugins, or configurations locally.
|
|
236
|
+
|
|
237
|
+
### Example
|
|
238
|
+
```javascript
|
|
239
|
+
// In any standard .js file
|
|
240
|
+
export async function autoregister_custom_auth(app) {
|
|
241
|
+
app.addHook("onRequest", async (req, reply) => {
|
|
242
|
+
// Local hook logic here
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
|
|
220
248
|
|
|
221
|
-
## Note
|
|
222
|
-
On loading routes - possible side effects execute.
|
|
223
|
-
Means you can eg declare globals.
|
|
224
249
|
|
|
225
250
|
## About
|
|
226
251
|
- Dependencies: original fastify packages + scalar/fastify-api-reference
|
package/index.d.ts
CHANGED
|
@@ -8,16 +8,21 @@ declare global {
|
|
|
8
8
|
|
|
9
9
|
var global: typeof globalThis;
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
var ENV: NodeJS.ProcessEnv & {
|
|
12
|
+
development: boolean;
|
|
13
|
+
production: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** Casts a string ID to a MongoDB ObjectId using the global helper. */
|
|
12
17
|
var oid: (id?: string) => import("mongodb").ObjectId;
|
|
13
18
|
|
|
14
|
-
/** Access to the global MongoDB database instance
|
|
19
|
+
/** Access to the global MongoDB database instance. */
|
|
15
20
|
var db: import("mongodb").Db;
|
|
16
21
|
|
|
17
22
|
/**
|
|
18
|
-
* Creates an Error object with an attached HTTP status code
|
|
23
|
+
* Creates an Error object with an attached HTTP status code.
|
|
19
24
|
* Fastify catches this to return a structured JSON response.
|
|
20
|
-
*
|
|
25
|
+
* @param code The HTTP status code (e.g., 400, 401, 403, 404).
|
|
21
26
|
* @param message The specific failure reason returned in the JSON body.
|
|
22
27
|
*/
|
|
23
28
|
var createError: (code: number, message: string) => Error;
|
|
@@ -31,6 +36,7 @@ declare module "fastify" {
|
|
|
31
36
|
secret?: string;
|
|
32
37
|
mongodb?: string;
|
|
33
38
|
static?: string;
|
|
39
|
+
errorLogger?: boolean;
|
|
34
40
|
};
|
|
35
41
|
}
|
|
36
42
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -7,56 +7,54 @@ import {
|
|
|
7
7
|
setupMongo,
|
|
8
8
|
setupStatic,
|
|
9
9
|
setupCookie,
|
|
10
|
+
setupErrorLogger,
|
|
10
11
|
} from "./setup.js";
|
|
11
12
|
|
|
12
|
-
|
|
13
13
|
import { getAddress } from "./getAddress.js";
|
|
14
14
|
import { setupDocs } from "./setupDocs.js";
|
|
15
15
|
import { setupRoutes } from "./setupRoutes.js";
|
|
16
16
|
import { setupDevSounds } from "./devSounds.js";
|
|
17
17
|
|
|
18
|
-
|
|
19
18
|
import fastifyWebsocket from "@fastify/websocket";
|
|
20
19
|
|
|
21
20
|
export default async function maxserver(config = {}) {
|
|
22
|
-
|
|
23
|
-
|
|
24
21
|
const {
|
|
25
|
-
|
|
26
|
-
// maxserver options
|
|
27
22
|
port = Number(process.env.PORT || 3000),
|
|
28
23
|
secret = process.env.SECRET,
|
|
29
24
|
mongodb = process.env.MONGODB,
|
|
30
25
|
docs = process.env.DOCS !== "false",
|
|
31
26
|
cors = process.env.CORS || "*",
|
|
32
|
-
env = process.env.NODE_ENV || "development",
|
|
27
|
+
env = process.env.NODE_ENV || "development",
|
|
33
28
|
routesDir = process.env.ROUTESDIR || "src",
|
|
34
29
|
scalar = {},
|
|
35
30
|
openapiInfo,
|
|
36
31
|
sounds,
|
|
37
32
|
static: isStatic = process.env.STATIC,
|
|
38
33
|
public: isPublic = process.env.PUBLIC === "true",
|
|
34
|
+
errorLogger = process.env.ERROR_LOGGER === "true",
|
|
39
35
|
|
|
40
|
-
// everything else goes straight to Fastify
|
|
41
36
|
...fastifyOpts
|
|
42
|
-
|
|
43
37
|
} = config;
|
|
44
38
|
|
|
39
|
+
globalThis.ENV = {
|
|
40
|
+
...process.env,
|
|
41
|
+
development: process.env.NODE_ENV !== "production",
|
|
42
|
+
production: process.env.NODE_ENV === "production"
|
|
43
|
+
};
|
|
44
|
+
|
|
45
45
|
const maxserverConfig = {
|
|
46
46
|
port, secret, mongodb, docs, cors, env, openapiInfo, routesDir, scalar, sounds,
|
|
47
47
|
static: isStatic,
|
|
48
|
-
public: isPublic
|
|
48
|
+
public: isPublic,
|
|
49
|
+
errorLogger
|
|
49
50
|
};
|
|
50
51
|
|
|
51
|
-
if (!secret)
|
|
52
|
-
throw new Error("secret is must have");
|
|
53
|
-
|
|
52
|
+
if (!secret) throw new Error("secret is must have");
|
|
54
53
|
|
|
55
54
|
let app;
|
|
56
55
|
try {
|
|
57
56
|
app = Fastify({
|
|
58
57
|
trustProxy: true,
|
|
59
|
-
// Required to allow adding doc fields on schema
|
|
60
58
|
ajv: { customOptions: { strictSchema: false } },
|
|
61
59
|
...fastifyOpts
|
|
62
60
|
});
|
|
@@ -65,19 +63,19 @@ export default async function maxserver(config = {}) {
|
|
|
65
63
|
throw err;
|
|
66
64
|
}
|
|
67
65
|
|
|
68
|
-
|
|
69
66
|
app.decorate("maxserver", maxserverConfig);
|
|
70
67
|
|
|
71
68
|
app.decorate("start", async function () {
|
|
72
69
|
const port = this.maxserver.port ?? 3000;
|
|
73
|
-
const host = this.maxserver.public ?
|
|
70
|
+
const host = this.maxserver.public ? "0.0.0.0" : "127.0.0.1";
|
|
74
71
|
await this.listen({ port, host });
|
|
75
|
-
console.log(
|
|
72
|
+
console.log("🟢 ", getAddress(this));
|
|
76
73
|
});
|
|
77
74
|
|
|
78
75
|
app.register(fastifyWebsocket);
|
|
79
76
|
|
|
80
77
|
await setupDevSounds(app);
|
|
78
|
+
await setupErrorLogger(app);
|
|
81
79
|
await setupCookie(app);
|
|
82
80
|
await setupHelmet(app);
|
|
83
81
|
await setupCors(app);
|
|
@@ -87,7 +85,6 @@ export default async function maxserver(config = {}) {
|
|
|
87
85
|
await setupDocs(app);
|
|
88
86
|
await setupRoutes(app);
|
|
89
87
|
|
|
90
|
-
|
|
91
88
|
global.createError = function (code, message) {
|
|
92
89
|
const err = new Error(message);
|
|
93
90
|
err.statusCode = code;
|
package/src/setup.js
CHANGED
|
@@ -8,8 +8,6 @@ import mongodb from "@fastify/mongodb";
|
|
|
8
8
|
import fastifyStatic from "@fastify/static";
|
|
9
9
|
import helmet from "@fastify/helmet";
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
11
|
export async function setupHelmet(app) {
|
|
14
12
|
await app.register(helmet, {
|
|
15
13
|
contentSecurityPolicy: false,
|
|
@@ -20,16 +18,12 @@ export async function setupHelmet(app) {
|
|
|
20
18
|
});
|
|
21
19
|
}
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
21
|
export async function setupCors(app) {
|
|
26
22
|
const isProd = app.maxserver.env === "production";
|
|
27
23
|
let origin = app.maxserver.cors ?? "*";
|
|
28
24
|
|
|
29
|
-
// Fix: Credentials + "*" = Browser Error
|
|
30
|
-
// If no origin is defined in dev, we should allow the specific requester
|
|
31
25
|
if (origin === "*" && !isProd)
|
|
32
|
-
origin = true;
|
|
26
|
+
origin = true;
|
|
33
27
|
|
|
34
28
|
if (isProd && (origin === "*" || origin === true))
|
|
35
29
|
app.log.warn("CORS: allowing all origins in production with credentials is risky");
|
|
@@ -37,12 +31,10 @@ export async function setupCors(app) {
|
|
|
37
31
|
await app.register(cors, {
|
|
38
32
|
origin,
|
|
39
33
|
credentials: true,
|
|
40
|
-
methods: [
|
|
34
|
+
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
|
|
41
35
|
});
|
|
42
36
|
}
|
|
43
37
|
|
|
44
|
-
|
|
45
|
-
|
|
46
38
|
export async function setupCookie(app) {
|
|
47
39
|
await app.register(cookie, {
|
|
48
40
|
secret: app.maxserver.secret,
|
|
@@ -50,9 +42,6 @@ export async function setupCookie(app) {
|
|
|
50
42
|
});
|
|
51
43
|
}
|
|
52
44
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
45
|
export async function setupMongo(app) {
|
|
57
46
|
const url = app.maxserver.mongodb;
|
|
58
47
|
if (!url) return;
|
|
@@ -63,8 +52,6 @@ export async function setupMongo(app) {
|
|
|
63
52
|
global.db = db;
|
|
64
53
|
}
|
|
65
54
|
|
|
66
|
-
|
|
67
|
-
|
|
68
55
|
export async function setupJwt(app) {
|
|
69
56
|
await app.register(jwt, {
|
|
70
57
|
secret: app.maxserver.secret,
|
|
@@ -72,25 +59,17 @@ export async function setupJwt(app) {
|
|
|
72
59
|
});
|
|
73
60
|
|
|
74
61
|
app.addHook("preHandler", async function (req) {
|
|
75
|
-
|
|
76
|
-
// Let preflight requests pass
|
|
77
62
|
if (req.method === "OPTIONS") return;
|
|
78
63
|
|
|
79
64
|
const auth = req.routeOptions?.config?.auth;
|
|
80
|
-
|
|
81
65
|
if (!auth) return;
|
|
82
66
|
|
|
83
67
|
await req.jwtVerify();
|
|
84
68
|
const u = req.user;
|
|
85
69
|
req.userId = u?.sub || u?.userId || u?.userid || u?.id || null;
|
|
86
70
|
});
|
|
87
|
-
|
|
88
71
|
}
|
|
89
72
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
73
|
export async function setupStatic(app) {
|
|
95
74
|
const dir = app.maxserver.static;
|
|
96
75
|
if (!dir) return;
|
|
@@ -109,5 +88,17 @@ export async function setupStatic(app) {
|
|
|
109
88
|
await app.register(fastifyStatic, { root: abs });
|
|
110
89
|
}
|
|
111
90
|
|
|
91
|
+
export async function setupErrorLogger(app) {
|
|
92
|
+
if (!app.maxserver.errorLogger) return;
|
|
112
93
|
|
|
94
|
+
app.addHook("onError", async (req, res, error) => {
|
|
95
|
+
console.log("\n‼️ ERROR ‼️");
|
|
113
96
|
|
|
97
|
+
const stackLine = error.stack.split("\n")[1];
|
|
98
|
+
const match = stackLine.match(/([^\s()]+):(\d+):\d+/);
|
|
99
|
+
const file = match?.[1].replace("file://" + process.cwd(), "");
|
|
100
|
+
|
|
101
|
+
if (file) console.log(`${file} -> line ${match[2]}`);
|
|
102
|
+
console.log(error.message);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
$id: "User",
|
|
3
|
+
summary: "User Profile",
|
|
4
|
+
description: "Internal user profile including team memberships.",
|
|
5
|
+
tags: ["User"],
|
|
6
|
+
auth: true,
|
|
7
|
+
type: "object",
|
|
8
|
+
additionalProperties: false,
|
|
9
|
+
properties: {
|
|
10
|
+
id: { type: "string" },
|
|
11
|
+
email: { type: "string" },
|
|
12
|
+
}
|
|
13
|
+
};
|