huxy-node-server 1.0.0-beta.3 → 1.0.0-beta.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.
Files changed (3) hide show
  1. package/README.md +3 -1
  2. package/package.json +1 -1
  3. package/src/index.js +130 -133
package/README.md CHANGED
@@ -88,6 +88,7 @@ const server = await startStatic({
88
88
  | `port` | number | 3000 | 服务器端口 |
89
89
  | `host` | string | '0.0.0.0' | 服务器主机 |
90
90
  | `basepath` | string | '/' | 基础路径前缀 |
91
+ | `apiPrefix` | string | '/api' | 基础路径前缀 |
91
92
  | `nodeEnv` | string | 'development' | 运行环境 |
92
93
  | `appName` | string | 'HuxyServer' | 应用名称 |
93
94
 
@@ -151,7 +152,8 @@ const server = await startStatic({
151
152
  NODE_ENV=production
152
153
  PORT=3000
153
154
  HOST=0.0.0.0
154
- BASEPATH=/api
155
+ BASEPATH=/
156
+ API_PREFIX=/api
155
157
  CORS_ORIGIN=http://example.com,http://localhost:3000
156
158
  RATE_LIMIT_WINDOW_MS=900000
157
159
  RATE_LIMIT_MAX_REQUESTS=100
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huxy-node-server",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.4",
4
4
  "description": "一个精炼、高性能的 Express.js 服务器模板,为现代 Node.js 应用程序设计,提供灵活的功能和最佳实践。",
5
5
  "type": "module",
6
6
  "module": "./src/index.js",
package/src/index.js CHANGED
@@ -1,32 +1,32 @@
1
1
  import E from 'express';
2
- import K from 'helmet';
2
+ import V from 'helmet';
3
3
  import W from 'cors';
4
- import {rateLimit as z, ipKeyGenerator as X} from 'express-rate-limit';
5
- import B from 'compression';
6
- import Q from 'pino-http';
7
- import {createServer as J} from 'node:http';
8
- import w from 'pino';
4
+ import {rateLimit as K, ipKeyGenerator as z} from 'express-rate-limit';
5
+ import X from 'compression';
6
+ import B from 'pino-http';
7
+ import {createServer as Q} from 'node:http';
8
+ import A from 'pino';
9
9
  import M from 'node:os';
10
- import k from 'node:net';
10
+ import C from 'node:net';
11
11
  var m = (e = new Date()) => e.toLocaleString('zh-CN', {timeZone: 'Asia/Shanghai', hour12: !1}),
12
12
  u = e => {
13
- let o = e ? 'https' : 'http',
13
+ let r = e ? 'https' : 'http',
14
14
  t = M.networkInterfaces(),
15
- r = [];
16
- return (Object.keys(t).map(n => r.push(...t[n])), r.filter(n => n.family === 'IPv4').map(n => `${o}://${n.address}`));
15
+ o = [];
16
+ return (Object.keys(t).map(n => o.push(...t[n])), o.filter(n => n.family === 'IPv4').map(n => `${r}://${n.address}`));
17
17
  },
18
18
  x = e => {
19
- let o = e ?? process.argv.slice(2) ?? [],
19
+ let r = e ?? process.argv.slice(2) ?? [],
20
20
  t = {};
21
21
  return (
22
- o.map(r => {
23
- let [s, i] = r.split('=');
22
+ r.map(o => {
23
+ let [s, i] = o.split('=');
24
24
  t[s] = i;
25
25
  }),
26
26
  t
27
27
  );
28
28
  },
29
- U = {
29
+ k = {
30
30
  NODE_ENV: 'nodeEnv',
31
31
  PORT: 'port',
32
32
  STATIC_PORT: 'staticPort',
@@ -40,35 +40,35 @@ var m = (e = new Date()) => e.toLocaleString('zh-CN', {timeZone: 'Asia/Shanghai'
40
40
  JWT_SECRET: 'secret',
41
41
  AUTH_TOKEN: 'authToken',
42
42
  },
43
- j = (e, o, t) => {
44
- let [r, s] = e.split('.');
45
- r && s ? (t[r] || (t[r] = {}), (t[r][s] = o)) : (t[r] = o);
43
+ U = (e, r, t) => {
44
+ let [o, s] = e.split('.');
45
+ o && s ? (t[o] || (t[o] = {}), (t[o][s] = r)) : (t[o] = r);
46
46
  },
47
- d = (e = {}, o = U) => {
47
+ d = (e = {}, r = k) => {
48
48
  let {env: t} = process;
49
- Object.keys(o).map(s => {
49
+ Object.keys(r).map(s => {
50
50
  let i = t[s] ?? e[s];
51
- i && j(o[s], i, e);
51
+ i && U(r[s], i, e);
52
52
  });
53
- let r = {...e, ...x()};
54
- return ((r.port = r.staticPort || r.port), (r.isDev = r.NODE_ENV === 'development'), r);
53
+ let o = {...e, ...x()};
54
+ return ((o.port = o.staticPort || o.port), (o.isDev = o.NODE_ENV === 'development'), o);
55
55
  },
56
- f = (e, o = '127.0.0.1') =>
56
+ f = (e, r = '127.0.0.1') =>
57
57
  new Promise(t => {
58
- let r = k.createServer();
59
- (r.once('error', s => {
60
- (r.close(), t((s.code === 'EADDRINUSE', !1)));
58
+ let o = C.createServer();
59
+ (o.once('error', s => {
60
+ (o.close(), t((s.code === 'EADDRINUSE', !1)));
61
61
  }),
62
- r.once('listening', () => {
63
- (r.close(), t(!0));
62
+ o.once('listening', () => {
63
+ (o.close(), t(!0));
64
64
  }),
65
- r.listen(Number(e), o));
65
+ o.listen(Number(e), r));
66
66
  }),
67
- I = (e, o = {}) => {
68
- let t = r => {
69
- (a.warn(`\u6536\u5230 ${r} \u4FE1\u53F7, \u{1F6D1} \u6B63\u5728\u5173\u95ED\u670D\u52A1\u5668...`),
67
+ I = (e, r = {}) => {
68
+ let t = o => {
69
+ (a.warn(`\u6536\u5230 ${o} \u4FE1\u53F7, \u{1F6D1} \u6B63\u5728\u5173\u95ED\u670D\u52A1\u5668...`),
70
70
  e.close(async () => {
71
- (a.info('\u{1F44B} \u670D\u52A1\u5668\u5DF2\u5173\u95ED'), await o.shutdown?.(), process.exit(0));
71
+ (a.info('\u{1F44B} \u670D\u52A1\u5668\u5DF2\u5173\u95ED'), await r.shutdown?.(), process.exit(0));
72
72
  }),
73
73
  setTimeout(() => {
74
74
  (a.error('\u274C \u5F3A\u5236\u5173\u95ED\u670D\u52A1\u5668'), process.exit(1));
@@ -76,25 +76,25 @@ var m = (e = new Date()) => e.toLocaleString('zh-CN', {timeZone: 'Asia/Shanghai'
76
76
  };
77
77
  (process.on('SIGTERM', () => t('SIGTERM')),
78
78
  process.on('SIGINT', () => t('SIGINT')),
79
- process.on('uncaughtException', r => {
80
- (a.fatal(r, `\u{1F4A5} \u672A\u6355\u83B7\u7684\u5F02\u5E38: ${r.message}`), process.exit(1));
79
+ process.on('uncaughtException', o => {
80
+ (a.fatal(o, `\u{1F4A5} \u672A\u6355\u83B7\u7684\u5F02\u5E38: ${o.message}`), process.exit(1));
81
81
  }),
82
- process.on('unhandledRejection', (r, s) => {
83
- (a.fatal({reason: r, promise: s}, '\u26A0\uFE0F \u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD'), process.exit(1));
82
+ process.on('unhandledRejection', (o, s) => {
83
+ (a.fatal({reason: o, promise: s}, '\u26A0\uFE0F \u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD'), process.exit(1));
84
84
  }));
85
85
  },
86
- P = (e, {port: o, host: t = '0.0.0.0'} = {}) =>
87
- new Promise((r, s) => {
86
+ y = (e, {port: r, host: t = '0.0.0.0'} = {}) =>
87
+ new Promise((o, s) => {
88
88
  let i = l => {
89
89
  (c(), s(l));
90
90
  },
91
91
  n = () => {
92
- (c(), r(e));
92
+ (c(), o(e));
93
93
  },
94
94
  c = () => {
95
95
  (e.off('error', i), e.off('listening', n));
96
96
  };
97
- (e.once('error', i), e.once('listening', n), e.listen(o, t));
97
+ (e.once('error', i), e.once('listening', n), e.listen(r, t));
98
98
  });
99
99
  import 'dotenv';
100
100
  var G = {
@@ -103,11 +103,12 @@ var G = {
103
103
  port: parseInt(process.env.PORT || '3000', 10),
104
104
  host: process.env.HOST || '0.0.0.0',
105
105
  basepath: process.env.BASEPATH || '/',
106
+ apiPrefix: '/api',
106
107
  cors: {origin: process.env.CORS_ORIGIN?.split(',') || '*', credentials: !0},
107
108
  rateLimit: {
108
- windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '900000', 10),
109
+ windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '300000', 10),
109
110
  limit: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS || '100', 10),
110
- message: {error: '\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5'},
111
+ message: {message: '\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5'},
111
112
  },
112
113
  helmet: {
113
114
  contentSecurityPolicy: {directives: {defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'", "'unsafe-eval'"], imgSrc: ["'self'", 'data:', 'https:']}},
@@ -116,133 +117,129 @@ var G = {
116
117
  logLevel: process.env.LOG_LEVEL || 30,
117
118
  },
118
119
  g = G;
119
- var p = (e, o) =>
120
- w({
121
- name: e,
122
- level: g.logLevel,
123
- formatters: {level: t => ({level: t})},
124
- timestamp: w.stdTimeFunctions.isoTime,
125
- base: null,
126
- transport: {target: 'pino-pretty', options: {colorize: !0, sync: !0, levelFirst: !0}, ignore: 'pid,hostname,level,time', translateTime: 'SYS:yyyy-mm-dd HH:MM:ss'},
127
- ...o,
128
- }),
129
- L = () => {
130
- let e = p('http-request');
131
- return (o, t, r) => {
132
- let s = Date.now();
133
- (t.on('finish', () => {
134
- let i = Date.now() - s,
135
- n = {method: o.method, url: o.originalUrl, status: t.statusCode, duration: `${i}ms`, ip: o.ip, userAgent: o.get('User-Agent'), timestamp: m()};
136
- t.statusCode >= 500 ? e.error(n, 'HTTP\u8BF7\u6C42\u9519\u8BEF') : t.statusCode >= 400 && e.warn(n, 'HTTP\u5BA2\u6237\u7AEF\u9519\u8BEF');
137
- }),
138
- r());
139
- };
120
+ var p = (e, r) =>
121
+ A({
122
+ name: e,
123
+ level: g.logLevel,
124
+ formatters: {level: t => ({level: t})},
125
+ timestamp: A.stdTimeFunctions.isoTime,
126
+ base: null,
127
+ transport: {target: 'pino-pretty', options: {colorize: !0, sync: !0, levelFirst: !0}, ignore: 'pid,hostname,level,time', translateTime: 'SYS:yyyy-mm-dd HH:MM:ss'},
128
+ ...r,
129
+ });
130
+ var a = p('huxy');
131
+ var w = p('error-handler'),
132
+ L = e => (r, t, o) => {
133
+ (w.error({message: 'Not Found', timestamp: m(), url: r.originalUrl, method: r.method, ip: r.ip, userAgent: r.get('User-Agent')}, '\u627E\u4E0D\u5230\u8DEF\u5F84'),
134
+ t.status(404).json({success: !1, timestamp: m(), status: 404, message: `\u8DEF\u7531 ${r.method} ${r.originalUrl} \u4E0D\u5B58\u5728`, url: r.originalUrl}));
140
135
  },
141
- a = p('huxy');
142
- var b = p('error-handler'),
143
- A = e => (o, t, r) => {
144
- (b.error({message: 'Not Found', timestamp: m(), url: o.originalUrl, method: o.method, ip: o.ip, userAgent: o.get('User-Agent')}, '\u627E\u4E0D\u5230\u8DEF\u5F84'),
145
- t.status(404).json({success: !1, timestamp: m(), status: 404, message: `\u8DEF\u7531 ${o.method} ${o.originalUrl} \u4E0D\u5B58\u5728`, url: o.originalUrl}));
146
- },
147
- N = e => (o, t, r, s) => {
148
- let i = o.status || 500,
149
- n = o.message;
150
- (b.error({message: n, timestamp: m(), stack: o.stack, url: t.originalUrl, method: t.method, ip: t.ip, userAgent: t.get('User-Agent')}, '\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF'),
151
- r.status(i).json({success: !1, timestamp: m(), message: e.isDev ? n : '\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF', stack: e.isDev ? o.stack : void 0}));
136
+ b = e => (r, t, o, s) => {
137
+ let i = r.status || 500,
138
+ n = r.message;
139
+ (w.error({message: n, timestamp: m(), stack: r.stack, url: t.originalUrl, method: t.method, ip: t.ip, userAgent: t.get('User-Agent')}, '\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF'),
140
+ o.status(i).json({success: !1, timestamp: m(), message: e.isDev ? n : '\u670D\u52A1\u5668\u5185\u90E8\u9519\u8BEF', stack: e.isDev ? r.stack : void 0}));
152
141
  };
153
- var R = e => (o, t, r) => {
154
- (o.path.match(/\.(js|css|png|jpe?g|ico|webp|svg|mpeg|webm|m4a)$/) ? t.set('Cache-Control', 'public, max-age=31536000, immutable') : t.set('Cache-Control', 'no-cache'), r());
142
+ var R = e => (r, t, o) => {
143
+ (t.set('Cache-Control', `public, max-age=${e.cacheMaxAge ?? 300}, stale-while-revalidate=60`), o());
155
144
  };
156
- import {Router as F} from 'express';
157
- var V = e => {
158
- let o = F();
145
+ import {Router as j} from 'express';
146
+ var F = e => {
147
+ let r = j();
159
148
  return (
160
- o.use('/health', (t, r) => {
161
- r.status(200).json({status: 'OK', timestamp: m(), environment: e.nodeEnv, uptime: process.uptime(), memoryUsage: process.memoryUsage(), pid: process.pid});
149
+ r.use('/health', (t, o) => {
150
+ o.status(200).json({status: 'OK', timestamp: m(), environment: e.nodeEnv, uptime: process.uptime(), memoryUsage: process.memoryUsage(), pid: process.pid});
162
151
  }),
163
- o.get('/status', (t, r) => {
164
- r.status(200).json({message: 'Node.js \u670D\u52A1\u5668\u8FD0\u884C\u4E2D', timestamp: m(), environment: e.nodeEnv});
152
+ r.get('/status', (t, o) => {
153
+ o.status(200).json({message: 'Node.js \u670D\u52A1\u5668\u8FD0\u884C\u4E2D', timestamp: m(), environment: e.nodeEnv});
165
154
  }),
166
- o
155
+ r
167
156
  );
168
157
  },
169
- _ = V;
170
- var Y = (e, o = {}) => {
158
+ N = F;
159
+ var J = (e, r = {}) => {
171
160
  (e.disable('x-powered-by'),
172
161
  e.set('trust proxy', 1),
173
- e.use(K(o.helmet)),
174
- e.use(W(o.cors)),
175
- e.use(z({keyGenerator: t => X(t.ip) || t.headers['x-huxy-auth'] || t.headers['x-api-key'] || t.headers.authorization, ...o.rateLimit})),
176
- e.use(B()),
162
+ e.use(B({logger: a, quietReqLogger: !0, autoLogging: !1, genReqId: !1})),
163
+ e.use(X()),
164
+ e.use(V(r.helmet)),
165
+ e.use(W(r.cors)),
166
+ e.use(r.apiPrefix, K({keyGenerator: t => z(t.ip) || t.headers['x-huxy-auth'] || t.headers['x-api-key'] || t.headers.authorization, ...r.rateLimit})),
177
167
  e.use(E.json({limit: '20mb'})),
178
168
  e.use(E.urlencoded({extended: !0, limit: '20mb'})),
179
- e.use(Q({logger: a, quietReqLogger: !0, autoLogging: !1})),
180
- o.isDev && e.use(L()),
181
- e.use(R(o)));
169
+ e.use(r.apiPrefix, R(r)));
182
170
  },
183
- Z = (e, o = {}) => {
184
- (e.use(_(o)), e.use(A(o)), e.use(N(o)));
171
+ Y = (e, r = {}) => {
172
+ (e.use(N(r)), e.use(L(r)), e.use(b(r)));
185
173
  },
186
- q = async (e, o) => {
174
+ Z = async (e, r) => {
187
175
  let t = d(e),
188
- {port: r} = t;
189
- (await f(r, t.host)) || ((t.port = Number(r) + 1), a.warn(`\u7AEF\u53E3 ${r} \u5DF2\u88AB\u5360\u7528\uFF0C\u73B0\u5728\u4F7F\u7528\u7AEF\u53E3 ${t.port}`));
176
+ {port: o} = t;
177
+ (await f(o, t.host)) || ((t.port = Number(o) + 1), a.warn(`\u7AEF\u53E3 ${o} \u5DF2\u88AB\u5360\u7528\uFF0C\u73B0\u5728\u4F7F\u7528\u7AEF\u53E3 ${t.port}`));
190
178
  let i = E();
191
- Y(i, t);
192
- let n = J(i);
179
+ J(i, t);
180
+ let n = Q(i);
193
181
  I(n, t);
194
182
  try {
195
- await P(n, t);
183
+ await y(n, t);
196
184
  } catch {
197
185
  (a.error('\u26A0\uFE0F \u670D\u52A1\u5668\u542F\u52A8\u5931\u8D25'), process.exit(1));
198
186
  }
199
- return (await o?.(t, i, n), Z(i, t), {app: i, httpServer: n, config: t});
187
+ return (await r?.(t, i, n), Y(i, t), {app: i, httpServer: n, config: t});
200
188
  },
201
- $ = q;
202
- var ee = (e, o = 56) => {
189
+ _ = Z;
190
+ var q = (e, r = 56) => {
203
191
  let t = e.length,
204
- r = o - t,
205
- s = ~~(r / 2);
206
- return `${'-'.repeat(s)}${e}${'-'.repeat(r - s)}`;
192
+ o = r - t,
193
+ s = ~~(o / 2);
194
+ return `${'-'.repeat(s)}${e}${'-'.repeat(o - s)}`;
207
195
  },
208
- te = (e, o, t) =>
209
- $({...g, ...e}, async (r, s, i) => {
210
- let {port: n, host: c, nodeEnv: l, basepath: y, appName: C = 'HuxyServer'} = r;
196
+ ee = (e, r, t) =>
197
+ _({...g, ...e}, async (o, s, i) => {
198
+ let {port: n, host: c, nodeEnv: l, basepath: P, appName: D = 'HuxyServer'} = o;
211
199
  if (!t) {
212
200
  let H = u()
213
201
  .filter(v => v !== `http://${c}`)
214
- .map(v => `http://${v}:${n}${y}`);
215
- (a.info(ee(C)),
216
- a.info(`\u{1F680} \u670D\u52A1\u8FD0\u884C\u5728\u3010${l}\u3011\u73AF\u5883: http://${c}:${n}${y}`),
202
+ .map(v => `http://${v}:${n}${P}`);
203
+ (a.info(q(D)),
204
+ a.info(`\u{1F680} \u670D\u52A1\u8FD0\u884C\u5728\u3010${l}\u3011\u73AF\u5883: http://${c}:${n}${P}`),
217
205
  a.info(`-----------------[${m()}]------------------`),
218
206
  a.info({ips: H}, '\u672C\u5730\u5730\u5740\uFF1A'));
219
207
  }
220
- await o?.(r, s, i);
208
+ await r?.(o, s, i);
221
209
  }),
222
- h = te;
223
- import ne from 'express';
224
- import {fileURLToPath as oe} from 'node:url';
225
- import {dirname as re, resolve as se} from 'node:path';
226
- var T = (e = import.meta.url) => re(oe(e)),
227
- S = e => se(T(), e),
228
- O = S;
229
- var ie = {port: 9e3, host: 'localhost', basepath: '/', buildPath: './build'},
230
- ae = (e, o) =>
231
- h({...ie, ...e}, async (t, r, s) => {
210
+ h = ee;
211
+ import se from 'express';
212
+ import {fileURLToPath as te} from 'node:url';
213
+ import {dirname as oe, resolve as re} from 'node:path';
214
+ var T = (e = import.meta.url) => oe(te(e)),
215
+ S = e => re(T(), e),
216
+ $ = S;
217
+ var ne = {port: 9e3, host: 'localhost', basepath: '/', buildPath: './build'},
218
+ ie = (e, r) =>
219
+ h({...ne, ...e}, async (t, o, s) => {
232
220
  let {basepath: i, buildPath: n} = t;
233
- (r.use(i, ne.static(n, {maxAge: '1y', immutable: !0})),
234
- r.get(`${i}/{*splat}`.replace('//', '/'), (c, l) => {
235
- l.sendFile(O(n, 'index.html'));
221
+ (o.use(
222
+ i,
223
+ se.static(n, {
224
+ maxAge: '1y',
225
+ immutable: !0,
226
+ setHeaders: (c, l) => {
227
+ l.endsWith('.html') && c.setHeader('Cache-Control', 'no-cache, must-revalidate');
228
+ },
229
+ }),
230
+ ),
231
+ o.get(`${i}/{*splat}`.replace('//', '/'), (c, l) => {
232
+ l.sendFile($(n, 'index.html'));
236
233
  }),
237
- await o?.(t, r, s));
234
+ await r?.(t, o, s));
238
235
  }),
239
- D = ae;
240
- var ot = {startServer: h, startStatic: D, logger: a, createLogger: p, dateTime: m, localIPs: u, nodeArgs: x, getEnvConfig: d, checkPort: f, getDirName: T, resolvePath: S};
236
+ O = ie;
237
+ var tt = {startServer: h, startStatic: O, logger: a, createLogger: p, dateTime: m, localIPs: u, nodeArgs: x, getEnvConfig: d, checkPort: f, getDirName: T, resolvePath: S};
241
238
  export {
242
239
  f as checkPort,
243
240
  p as createLogger,
244
241
  m as dateTime,
245
- ot as default,
242
+ tt as default,
246
243
  T as getDirName,
247
244
  d as getEnvConfig,
248
245
  u as localIPs,
@@ -250,5 +247,5 @@ export {
250
247
  x as nodeArgs,
251
248
  S as resolvePath,
252
249
  h as startServer,
253
- D as startStatic,
250
+ O as startStatic,
254
251
  };