maxserver 0.0.17 โ†’ 0.1.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/README.md CHANGED
@@ -64,7 +64,10 @@ Any fastify options can be passed to maxserver() too.
64
64
  | `mongodb` | *-* | MongoDB URI, if set auto-connects db |
65
65
  | `public` | `false` | Set `true` to expose the server publicly (binds to `0.0.0.0`) |
66
66
  | `static` | *-* | If set, serves this directory statically |
67
- <br><br>
67
+ | `routesDir` | *src* | Directory to auto collect routes |
68
+ ---
69
+
70
+ <br>
68
71
 
69
72
  ## ๐Ÿ—‚๏ธ Project Structure
70
73
  Our golden rule: **1 route = 1 handler file + 1 schema file**
@@ -82,21 +85,16 @@ src/
82
85
  ```
83
86
  <br>
84
87
 
85
- ## ๐Ÿ›ฃ๏ธ Handlers
86
-
87
- #### 1) Define method + path
88
- Start each route file with a comment to define the path.
89
- That comment is what the route loader uses to auto-register the route.
90
-
91
- ```js
92
- // GET /teams/:id
93
- ```
94
-
95
- #### 2) Export default handler
88
+ ## ๐Ÿค– Auto Routing
96
89
 
90
+ To auto-register routes, simple add a comment of the form:
91
+ **// METHOD /path**
92
+ **// GET /user**
93
+ **// POST /feedback/something**
94
+ ...
97
95
 
98
96
  ```js
99
- // GET /teams/:id
97
+ // GET /example/:id
100
98
 
101
99
  export default async function (req, res) {
102
100
 
@@ -105,8 +103,11 @@ export default async function (req, res) {
105
103
  return team;
106
104
  }
107
105
  ```
108
-
106
+ <br>
107
+
108
+ And remember to use default export for your handler.
109
109
  If you don't want to autoregister some routes, then simply don't add that magic comment ๐Ÿ˜ƒ
110
+ That's it.
110
111
 
111
112
 
112
113
  <br>
@@ -114,11 +115,11 @@ If you don't want to autoregister some routes, then simply don't add that magic
114
115
 
115
116
 
116
117
  ## ๐Ÿงพ Schemas
117
- Create a sibling file ending with **`.schema.js`**, so it will be auto registered.
118
- For example: **hello.js** and **hello.schema.js**
118
+ Create a sibling file ending with **`.schema.js`**, so it will be auto registered. For example: **hello.js** and **hello.schema.js**
119
+
120
+ Besides the basic validation fields we can set fields like summary and description, which will appear in the docs. Mostly you don't need to write schemas yourself, chat gpt and gemini do it excelently.
121
+
119
122
 
120
- Besides the basic validation fields we can set fields like summary and description,
121
- which will appear in the docs. Mostly you don't need to write schemas yourself, chat gpt and gemini do it excelently.
122
123
 
123
124
 
124
125
  ```js
@@ -142,7 +143,8 @@ export default {
142
143
  };
143
144
  ```
144
145
 
145
- **`โ€ผ๏ธ Important use export default`**
146
+ **โ€ผ๏ธ Important use export default
147
+ **
146
148
 
147
149
  <br>
148
150
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maxserver",
3
- "version": "0.0.17",
3
+ "version": "0.1.1",
4
4
  "description": "Node server setup based fastify",
5
5
  "author": "Max Matinpalo",
6
6
  "type": "module",
package/src/getAddress.js CHANGED
@@ -1,5 +1,13 @@
1
1
  import os from "node:os";
2
2
 
3
+
4
+ /**
5
+ * When listening on all interfaces (0.0.0.0), Node returns "0.0.0.0".
6
+ * This is a setting, not a valid URL for other devices.
7
+ * So we try hunt down the actual LAN IP to print the real address on which server can be accessed
8
+ */
9
+
10
+
3
11
  function getLanIp() {
4
12
  const nets = os.networkInterfaces();
5
13
 
package/src/index.js CHANGED
@@ -6,13 +6,13 @@ import {
6
6
  setupJwt,
7
7
  setupMongo,
8
8
  setupStatic,
9
- setupRoutes,
10
9
  setupDocs,
11
10
  setupCookie,
12
- getHttpsOptions,
13
11
  } from "./setup.js";
14
12
 
13
+
15
14
  import { getAddress } from "./getAddress.js";
15
+ import { setupRoutes } from "./setupRoutes.js";
16
16
 
17
17
 
18
18
  export default async function maxserver(config = {}) {
@@ -26,6 +26,7 @@ export default async function maxserver(config = {}) {
26
26
  docs = process.env.DOCS !== "false",
27
27
  cors = process.env.CORS || "*",
28
28
  env = process.env.NODE_ENV || "development",
29
+ routesDir = process.env.ROUTESDIR || "src",
29
30
  openapiInfo,
30
31
  static: isStatic = process.env.STATIC,
31
32
  public: isPublic = process.env.PUBLIC === "true",
@@ -36,18 +37,19 @@ export default async function maxserver(config = {}) {
36
37
  } = config;
37
38
 
38
39
  const maxserverConfig = {
39
- port, secret, mongodb, docs, cors,
40
+ port, secret, mongodb, docs, cors, env, openapiInfo, src,
40
41
  static: isStatic,
41
42
  public: isPublic
42
43
  };
43
44
 
45
+ if (!secret)
46
+ throw new Error("secret is must have");
47
+
44
48
 
45
49
  let app;
46
50
  try {
47
51
  app = Fastify({
48
- https: getHttpsOptions() || undefined,
49
52
  trustProxy: true,
50
-
51
53
  // Required to allow adding doc fields on schema
52
54
  ajv: { customOptions: { strictSchema: false } },
53
55
  ...fastifyOpts
package/src/setup.js CHANGED
@@ -10,12 +10,6 @@ import helmet from "@fastify/helmet";
10
10
  import swagger from "@fastify/swagger";
11
11
  import apiReference from "@scalar/fastify-api-reference";
12
12
 
13
- import { loadRoutes } from "./routeLoader.js";
14
-
15
- export async function setupRoutes(app) {
16
- await loadRoutes(app);
17
- }
18
-
19
13
 
20
14
  export async function setupHelmet(app) {
21
15
  await app.register(helmet, {
@@ -27,50 +21,41 @@ export async function setupHelmet(app) {
27
21
  }
28
22
 
29
23
 
30
-
31
-
32
24
  export async function setupCors(app) {
33
- const isProd = process.env.NODE_ENV === "production";
25
+ const isProd = app.maxserver.env === "production";
34
26
  const origin = app.maxserver.cors ?? "*";
35
-
36
27
  if (isProd && origin === "*") {
37
28
  app.log.warn("CORS: allowing all origins (*) in production");
38
29
  }
39
-
40
30
  await app.register(cors, { origin });
41
31
  }
42
32
 
43
33
 
44
-
45
- export function getHttpsOptions() {
46
- const { TLS_KEY, TLS_CERT } = process.env;
47
- if (!TLS_KEY || !TLS_CERT) return null;
48
-
49
- try {
50
- return {
51
- key: fs.readFileSync(TLS_KEY),
52
- cert: fs.readFileSync(TLS_CERT),
53
- };
54
- } catch (err) {
55
- throw new Error(`TLS read failed: ${err.message || err}`);
56
- }
34
+ export async function setupCookie(app) {
35
+ await app.register(cookie, {
36
+ secret: app.maxserver.secret,
37
+ hook: "onRequest"
38
+ });
57
39
  }
58
40
 
59
41
 
60
42
 
61
43
 
62
- export async function setupCookie(app) {
63
- await app.register(cookie, {
64
- secret: app.maxserver.secret || "supersecret",
65
- hook: "onRequest"
66
- });
44
+ export async function setupMongo(app) {
45
+ const url = app.maxserver.mongodb;
46
+ if (!url) return;
47
+ await app.register(mongodb, { url });
48
+
49
+ const { ObjectId, db } = app.mongo;
50
+ global.oid = id => new ObjectId(id);
51
+ global.db = db;
67
52
  }
68
53
 
69
54
 
70
- export async function setupJwt(app) {
71
55
 
56
+ export async function setupJwt(app) {
72
57
  await app.register(jwt, {
73
- secret: app.maxserver.secret || "supersecret",
58
+ secret: app.maxserver.secret,
74
59
  cookie: { cookieName: "token" }
75
60
  });
76
61
 
@@ -79,9 +64,7 @@ export async function setupJwt(app) {
79
64
  // Let preflight requests pass
80
65
  if (req.method === "OPTIONS") return;
81
66
 
82
- const auth =
83
- req.routeOptions?.config?.auth ??
84
- req.routeOptions?.schema?.auth;
67
+ const auth = req.routeOptions?.config?.auth;
85
68
 
86
69
  if (!auth) return;
87
70
 
@@ -93,15 +76,6 @@ export async function setupJwt(app) {
93
76
  }
94
77
 
95
78
 
96
- export async function setupMongo(app) {
97
- const url = app.maxserver.mongodb;
98
- if (!url) return;
99
- await app.register(mongodb, { url });
100
-
101
- const { ObjectId, db } = app.mongo;
102
- global.oid = id => new ObjectId(id ? String(id) : undefined);
103
- global.db = db;
104
- }
105
79
 
106
80
 
107
81
  export async function setupDocs(app) {
@@ -128,11 +102,11 @@ export async function setupDocs(app) {
128
102
 
129
103
  app.get("/openapi.json", {}, () => app.swagger());
130
104
 
131
- if (app.maxserver.docs != false)
105
+ if (app.maxserver.docs !== false)
132
106
  await app.register(apiReference, { routePrefix: "/docs", openapi: true });
133
107
 
134
108
  app.addHook("onRoute", (route) => {
135
- const auth = route.config?.auth ?? route.schema?.auth;
109
+ const auth = route.config?.auth;
136
110
  if (!auth) return;
137
111
  route.schema ||= {};
138
112
  route.schema.security ||= [{ bearerAuth: [] }, { cookieAuth: [] }];
@@ -145,9 +119,19 @@ export async function setupStatic(app) {
145
119
  const dir = app.maxserver.static;
146
120
  if (!dir) return;
147
121
 
148
- await app.register(fastifyStatic, {
149
- root: path.resolve(dir),
150
- prefix: "/static/",
151
- });
122
+ if (typeof dir !== "string") {
123
+ console.error("โŒ maxserver.static must be a string path");
124
+ return;
125
+ }
126
+
127
+ const abs = path.resolve(dir);
128
+ if (!fs.existsSync(abs)) {
129
+ console.error(`โŒ maxserver.static not found: ${abs}`);
130
+ return;
131
+ }
132
+
133
+ await app.register(fastifyStatic, { root: abs });
152
134
  }
153
135
 
136
+
137
+
@@ -74,9 +74,9 @@ async function importDefault(file) {
74
74
  }
75
75
 
76
76
 
77
- export async function loadRoutes(fastify) {
77
+ export async function setupRoutes(app) {
78
78
  const seen = new Map();
79
- const root = path.join(process.cwd(), "src");
79
+ const root = path.resolve(app.maxserver.routesDir || "src");
80
80
 
81
81
  for (const file of walk(root)) {
82
82
  // Skip schema files; they are loaded alongside their route file
@@ -125,6 +125,6 @@ export async function loadRoutes(fastify) {
125
125
  };
126
126
  }
127
127
 
128
- fastify[info.method](info.url, { ...routeOptions, schema }, handler);
128
+ app[info.method](info.url, { ...routeOptions, schema }, handler);
129
129
  }
130
130
  }