maxserver 0.8.3 → 1.0.1

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 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
- /** Casts a string ID to a MongoDB ObjectId using the global helper [cite: 2026-02-15]. */
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 [cite: 2026-02-15]. */
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 [cite: 2026-02-15].
23
+ * Creates an Error object with an attached HTTP status code.
19
24
  * Fastify catches this to return a structured JSON response.
20
- * * @param code The HTTP status code (e.g., 400, 401, 403, 404).
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maxserver",
3
- "version": "0.8.3",
3
+ "version": "1.0.1",
4
4
  "description": "Node server setup based fastify",
5
5
  "author": "Max Matinpalo",
6
6
  "type": "module",
package/src/devSounds.js CHANGED
@@ -1,31 +1,29 @@
1
- // Development sounds to make api development even more fun 😃
1
+ // Development sounds to make api development even more fun 😃
2
2
 
3
-
4
- import { exec } from 'child_process';
3
+ import { exec } from "child_process";
5
4
 
6
5
  export async function setupDevSounds(app) {
7
6
  if (!app.maxserver.sounds) return;
8
- if (app.maxserver.env === 'production' || process.env.CI) return;
9
-
10
- // Using here some builtin macos sounds see file paths
11
- // For windows we should add alternative
12
- if (process.platform !== 'darwin') return;
13
- const ok = '/System/Library/Sounds/Glass.aiff';
14
- const err = '/System/Library/Sounds/Submarine.aiff';
15
-
16
-
7
+ if (app.maxserver.env !== "development" || process.env.CI) return;
8
+ if (process.platform !== "darwin") return;
17
9
 
10
+ const ok = "/System/Library/Sounds/Glass.aiff";
11
+ const err = "/System/Library/Sounds/Submarine.aiff";
18
12
 
19
- const play = file => exec(`afplay "${file}" >/dev/null 2>&1 &`);
13
+ let lastPlayed = 0;
20
14
 
21
- app.addHook('onResponse', async (req, reply) => {
15
+ const play = file => {
16
+ const now = Date.now();
17
+ if (now - lastPlayed < 1000) return;
18
+ lastPlayed = now;
19
+ exec(`afplay "${file}" >/dev/null 2>&1 &`);
20
+ };
22
21
 
23
- // Dev sounds only for api requests - not for static served files 😃
24
- const ct = String(reply.getHeader('content-type') || '').toLowerCase();
22
+ app.addHook("onResponse", async (req, reply) => {
23
+ const ct = String(reply.getHeader("content-type") || "").toLowerCase();
25
24
  const disabled = req.routeOptions?.config?.devSound === false;
26
- let trigger = ct && ct.includes('application/json') && !disabled;
25
+ const trigger = ct && ct.includes("application/json") && !disabled;
27
26
 
28
- // Though this is route is auto registered by scalar, and we can't set sounds false on it...
29
27
  if (req.url === "/docs/openapi.json") return;
30
28
 
31
29
  if (trigger) {
@@ -34,4 +32,4 @@ export async function setupDevSounds(app) {
34
32
  else play(err);
35
33
  }
36
34
  });
37
- }
35
+ }
package/src/index.js CHANGED
@@ -7,56 +7,52 @@ 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", // should be removed, define via env only
27
+ env = process.env.NODE_ENV || "development",
33
28
  routesDir = process.env.ROUTESDIR || "src",
34
29
  scalar = {},
35
30
  openapiInfo,
36
- sounds,
31
+ sounds = true,
37
32
  static: isStatic = process.env.STATIC,
38
33
  public: isPublic = process.env.PUBLIC === "true",
39
34
 
40
- // everything else goes straight to Fastify
41
35
  ...fastifyOpts
42
-
43
36
  } = config;
44
37
 
38
+ globalThis.ENV = {
39
+ ...process.env,
40
+ development: process.env.NODE_ENV !== "production",
41
+ production: process.env.NODE_ENV === "production"
42
+ };
43
+
45
44
  const maxserverConfig = {
46
45
  port, secret, mongodb, docs, cors, env, openapiInfo, routesDir, scalar, sounds,
47
46
  static: isStatic,
48
47
  public: isPublic
49
48
  };
50
49
 
51
- if (!secret)
52
- throw new Error("secret is must have");
53
-
50
+ if (!secret) throw new Error("secret is must have");
54
51
 
55
52
  let app;
56
53
  try {
57
54
  app = Fastify({
58
55
  trustProxy: true,
59
- // Required to allow adding doc fields on schema
60
56
  ajv: { customOptions: { strictSchema: false } },
61
57
  ...fastifyOpts
62
58
  });
@@ -65,19 +61,19 @@ export default async function maxserver(config = {}) {
65
61
  throw err;
66
62
  }
67
63
 
68
-
69
64
  app.decorate("maxserver", maxserverConfig);
70
65
 
71
66
  app.decorate("start", async function () {
72
67
  const port = this.maxserver.port ?? 3000;
73
- const host = this.maxserver.public ? '0.0.0.0' : '127.0.0.1';
68
+ const host = this.maxserver.public ? "0.0.0.0" : "127.0.0.1";
74
69
  await this.listen({ port, host });
75
- console.log('🟢 ', getAddress(this));
70
+ console.log("🟢 ", getAddress(this));
76
71
  });
77
72
 
78
73
  app.register(fastifyWebsocket);
79
74
 
80
75
  await setupDevSounds(app);
76
+ await setupErrorLogger(app);
81
77
  await setupCookie(app);
82
78
  await setupHelmet(app);
83
79
  await setupCors(app);
@@ -87,7 +83,6 @@ export default async function maxserver(config = {}) {
87
83
  await setupDocs(app);
88
84
  await setupRoutes(app);
89
85
 
90
-
91
86
  global.createError = function (code, message) {
92
87
  const err = new Error(message);
93
88
  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; // Fastify-cors treats 'true' as "reflect the request origin"
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: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']
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.env !== "development") return;
112
93
 
94
+ app.addHook("onError", async (req, res, error) => {
95
+ console.log("\n\x1b[1;31mERROR\x1b[0m");
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
+ }
package/src/setupDocs.js CHANGED
@@ -48,7 +48,7 @@ export async function setupDocs(app) {
48
48
  // operationsSorter: "alpha",
49
49
  orderSchemaPropertiesBy: "preserve",
50
50
  metaData: {
51
- title: "API Docs 👨‍💻",
51
+ title: "Server",
52
52
  },
53
53
  authentication: {
54
54
  preferredSecurityScheme: 'bearerAuth',
@@ -4,19 +4,11 @@
4
4
  "module": "ES2020",
5
5
  "target": "ES2020",
6
6
  "moduleResolution": "bundler",
7
- "jsx": "react-jsx",
8
7
  "strict": false,
9
8
  "noUnusedParameters": false,
10
9
  "noUnusedLocals": false,
11
10
  "noImplicitAny": false,
12
- // new attempt to reduce noise
13
11
  "skipLibCheck": true,
14
- //"baseUrl": ".",
15
- "paths": {
16
- "*": [
17
- "./src/*"
18
- ],
19
- },
20
12
  "types": [
21
13
  "node",
22
14
  "maxserver"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "__NAME__",
3
- "version": "1.0.0",
3
+ "version": "0.1.0",
4
4
  "description": "",
5
5
  "main": "server.js",
6
6
  "scripts": {
@@ -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
+ };
@@ -0,0 +1,4 @@
1
+ @node
2
+ @js
3
+ @outjson
4
+ ../
package/templates/ai/BASE DELETED
@@ -1,2 +0,0 @@
1
- @node
2
- @js