@dangao/bun-server 2.0.2 → 2.0.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.
Files changed (57) hide show
  1. package/README.md +21 -8
  2. package/dist/config/config-module.d.ts +3 -0
  3. package/dist/config/config-module.d.ts.map +1 -1
  4. package/dist/core/context.d.ts +10 -0
  5. package/dist/core/context.d.ts.map +1 -1
  6. package/dist/database/service.d.ts +4 -4
  7. package/dist/database/service.d.ts.map +1 -1
  8. package/dist/index.js +236 -94
  9. package/dist/queue/queue-module.d.ts.map +1 -1
  10. package/dist/validation/decorators.d.ts.map +1 -1
  11. package/package.json +5 -4
  12. package/src/ai/providers/anthropic-provider.ts +1 -1
  13. package/src/ai/providers/google-provider.ts +1 -1
  14. package/src/ai/providers/ollama-provider.ts +1 -1
  15. package/src/ai/providers/openai-provider.ts +2 -2
  16. package/src/auth/jwt.ts +1 -1
  17. package/src/cache/interceptors.ts +3 -3
  18. package/src/cache/types.ts +10 -10
  19. package/src/client/runtime.ts +1 -1
  20. package/src/config/config-module.ts +46 -14
  21. package/src/config/service.ts +2 -2
  22. package/src/controller/param-binder.ts +1 -1
  23. package/src/conversation/service.ts +1 -1
  24. package/src/core/application.ts +1 -1
  25. package/src/core/cluster.ts +4 -4
  26. package/src/core/context.ts +71 -0
  27. package/src/dashboard/controller.ts +2 -2
  28. package/src/database/connection-manager.ts +4 -4
  29. package/src/database/service.ts +25 -28
  30. package/src/debug/middleware.ts +2 -2
  31. package/src/di/module-registry.ts +1 -1
  32. package/src/error/handler.ts +3 -3
  33. package/src/events/event-module.ts +4 -4
  34. package/src/files/static-middleware.ts +2 -2
  35. package/src/files/storage.ts +1 -1
  36. package/src/interceptor/builtin/log-interceptor.ts +1 -1
  37. package/src/mcp/server.ts +1 -1
  38. package/src/middleware/builtin/error-handler.ts +2 -2
  39. package/src/middleware/builtin/file-upload.ts +1 -1
  40. package/src/middleware/builtin/rate-limit.ts +1 -1
  41. package/src/middleware/builtin/static-file.ts +2 -2
  42. package/src/prompt/stores/file-store.ts +4 -4
  43. package/src/queue/queue-module.ts +4 -1
  44. package/src/request/body-parser.ts +3 -3
  45. package/src/security/filter.ts +1 -1
  46. package/src/security/guards/guard-registry.ts +1 -1
  47. package/src/session/middleware.ts +1 -1
  48. package/src/session/types.ts +5 -5
  49. package/src/testing/test-client.ts +1 -1
  50. package/src/validation/decorators.ts +70 -2
  51. package/src/validation/rules/common.ts +2 -2
  52. package/tests/config/config-module-extended.test.ts +24 -0
  53. package/tests/core/context.test.ts +52 -0
  54. package/tests/database/database-module.test.ts +87 -0
  55. package/tests/error/error-handler.test.ts +24 -0
  56. package/tests/queue/queue-module.test.ts +27 -0
  57. package/tests/validation/validation.test.ts +18 -0
@@ -1 +1 @@
1
- {"version":3,"file":"queue-module.d.ts","sourceRoot":"","sources":["../../src/queue/queue-module.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,kBAAkB,EAExB,MAAM,SAAS,CAAC;AAEjB,qBAGa,WAAW;IACtB;;;OAGG;WACW,OAAO,CACnB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,WAAW;CA0CtB"}
1
+ {"version":3,"file":"queue-module.d.ts","sourceRoot":"","sources":["../../src/queue/queue-module.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,kBAAkB,EAExB,MAAM,SAAS,CAAC;AAEjB,qBAGa,WAAW;IACtB;;;OAGG;WACW,OAAO,CACnB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,WAAW;CA6CtB"}
@@ -1 +1 @@
1
- {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../../src/validation/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAgB5E;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,KAAK,EAAE,wBAAwB,EAAE,GAAG,kBAAkB,CAgBjF;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,QAAQ,CAAC,OAAO,GAAE,UAAe,GAAG,wBAAwB,CAM3E;AAED,wBAAgB,QAAQ,CAAC,OAAO,GAAE,UAAe,GAAG,wBAAwB,CAM3E;AAED,wBAAgB,OAAO,CAAC,OAAO,GAAE,UAAe,GAAG,wBAAwB,CAO1E;AAED,wBAAgB,UAAU,IAAI,wBAAwB,CAOrD;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,GAAG,wBAAwB,CAMzF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,kBAAkB,EAAE,CAIxG"}
1
+ {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../../src/validation/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAgB5E;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,KAAK,EAAE,wBAAwB,EAAE,GAAG,kBAAkB,CAgBjF;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,QAAQ,CAAC,OAAO,GAAE,UAAe,GAAG,wBAAwB,CAM3E;AAED,wBAAgB,QAAQ,CAAC,OAAO,GAAE,UAAe,GAAG,wBAAwB,CAM3E;AAED,wBAAgB,OAAO,CAAC,OAAO,GAAE,UAAe,GAAG,wBAAwB,CA2E1E;AAED,wBAAgB,UAAU,IAAI,wBAAwB,CAOrD;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,GAAG,wBAAwB,CAMzF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,kBAAkB,EAAE,CAIxG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dangao/bun-server",
3
- "version": "2.0.2",
3
+ "version": "2.0.8",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -49,11 +49,12 @@
49
49
  "dts": "tsc -p tsconfig.build.json",
50
50
  "build": "bun run clean && bun run bundle && bun run dts",
51
51
  "prepublishOnly": "bun run build",
52
- "publish:package": "cp -r ../../README.md ../../LICENSE ../../docs . && bun publish --access public && rm -rf ./README.md ./LICENSE ./docs"
52
+ "publish:package": "cp -r ../../README.md ../../LICENSE ../../docs . && bun publish --access public && rm -rf ./README.md ./LICENSE ./docs",
53
+ "publish:package:github": "cp -r ../../README.md ../../LICENSE ../../docs . && bun pm pack --access public && npm publish *.tgz --provenance --access public && rm -rf ./README.md ./LICENSE ./docs"
53
54
  },
54
55
  "devDependencies": {
55
- "typescript": "5.9.3",
56
- "@types/bun": "latest"
56
+ "typescript": "^5.9.3",
57
+ "@types/bun": "^1.3.10"
57
58
  },
58
59
  "dependencies": {
59
60
  "reflect-metadata": "^0.2.2",
@@ -151,7 +151,7 @@ export class AnthropicProvider implements LlmProvider {
151
151
  } else if (parsed.type === 'message_stop') {
152
152
  controller.enqueue(encoder.encode(`data: ${JSON.stringify({ done: true })}\n\n`));
153
153
  }
154
- } catch {
154
+ } catch (_error) {
155
155
  // skip
156
156
  }
157
157
  }
@@ -139,7 +139,7 @@ export class GoogleProvider implements LlmProvider {
139
139
  const parts = (candidate?.['content'] as Record<string, unknown>)?.['parts'] as Array<Record<string, unknown>> ?? [];
140
140
  const text = parts.map((p) => p['text'] ?? '').join('');
141
141
  controller.enqueue(encoder.encode(`data: ${JSON.stringify({ content: text, done: false })}\n\n`));
142
- } catch {
142
+ } catch (_error) {
143
143
  // skip
144
144
  }
145
145
  }
@@ -106,7 +106,7 @@ export class OllamaProvider implements LlmProvider {
106
106
  controller.enqueue(encoder.encode(
107
107
  `data: ${JSON.stringify({ content: msgContent, done: isDone })}\n\n`,
108
108
  ));
109
- } catch {
109
+ } catch (_error) {
110
110
  // skip
111
111
  }
112
112
  }
@@ -171,7 +171,7 @@ export class OpenAIProvider implements LlmProvider {
171
171
  const delta = parsed.choices?.[0]?.delta;
172
172
  const chunk = { content: delta?.content ?? '', done: false };
173
173
  controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`));
174
- } catch {
174
+ } catch (_error) {
175
175
  // skip malformed lines
176
176
  }
177
177
  }
@@ -229,7 +229,7 @@ export class OpenAIProvider implements LlmProvider {
229
229
  return parsed as Record<string, unknown>;
230
230
  }
231
231
  return {};
232
- } catch {
232
+ } catch (_error) {
233
233
  return {};
234
234
  }
235
235
  }
package/src/auth/jwt.ts CHANGED
@@ -76,7 +76,7 @@ export class JWTUtil {
76
76
  }
77
77
 
78
78
  return payload;
79
- } catch {
79
+ } catch (_error) {
80
80
  return null;
81
81
  }
82
82
  }
@@ -120,7 +120,7 @@ export class CacheableInterceptor extends BaseInterceptor {
120
120
  let cacheService: CacheService;
121
121
  try {
122
122
  cacheService = container.resolve<CacheService>(CACHE_SERVICE_TOKEN);
123
- } catch {
123
+ } catch (_error) {
124
124
  // 缓存服务未注册,直接执行原方法
125
125
  console.warn('[CacheableInterceptor] CacheService not registered, skipping cache');
126
126
  return await Promise.resolve(originalMethod.apply(target, args));
@@ -191,7 +191,7 @@ export class CacheEvictInterceptor extends BaseInterceptor {
191
191
  let cacheService: CacheService;
192
192
  try {
193
193
  cacheService = container.resolve<CacheService>(CACHE_SERVICE_TOKEN);
194
- } catch {
194
+ } catch (_error) {
195
195
  // 缓存服务未注册,直接执行原方法
196
196
  console.warn('[CacheEvictInterceptor] CacheService not registered, skipping cache eviction');
197
197
  return await Promise.resolve(originalMethod.apply(target, args));
@@ -265,7 +265,7 @@ export class CachePutInterceptor extends BaseInterceptor {
265
265
  let cacheService: CacheService;
266
266
  try {
267
267
  cacheService = container.resolve<CacheService>(CACHE_SERVICE_TOKEN);
268
- } catch {
268
+ } catch (_error) {
269
269
  // 缓存服务未注册,直接执行原方法
270
270
  console.warn('[CachePutInterceptor] CacheService not registered, skipping cache update');
271
271
  return await Promise.resolve(originalMethod.apply(target, args));
@@ -246,7 +246,7 @@ export class RedisCacheStore implements CacheStore {
246
246
  }
247
247
  try {
248
248
  return JSON.parse(value) as T;
249
- } catch {
249
+ } catch (_error) {
250
250
  return undefined;
251
251
  }
252
252
  }
@@ -264,7 +264,7 @@ export class RedisCacheStore implements CacheStore {
264
264
  await this.client.set(this.getKey(key), serialized);
265
265
  }
266
266
  return true;
267
- } catch {
267
+ } catch (_error) {
268
268
  return false;
269
269
  }
270
270
  }
@@ -273,7 +273,7 @@ export class RedisCacheStore implements CacheStore {
273
273
  try {
274
274
  await this.client.del(this.getKey(key));
275
275
  return true;
276
- } catch {
276
+ } catch (_error) {
277
277
  return false;
278
278
  }
279
279
  }
@@ -282,7 +282,7 @@ export class RedisCacheStore implements CacheStore {
282
282
  try {
283
283
  const result = await this.client.exists(this.getKey(key));
284
284
  return result === 1;
285
- } catch {
285
+ } catch (_error) {
286
286
  return false;
287
287
  }
288
288
  }
@@ -291,7 +291,7 @@ export class RedisCacheStore implements CacheStore {
291
291
  try {
292
292
  await this.client.flushdb();
293
293
  return true;
294
- } catch {
294
+ } catch (_error) {
295
295
  return false;
296
296
  }
297
297
  }
@@ -313,12 +313,12 @@ export class RedisCacheStore implements CacheStore {
313
313
  if (value !== null) {
314
314
  try {
315
315
  result.set(keys[i], JSON.parse(value) as T);
316
- } catch {
316
+ } catch (_error) {
317
317
  // 忽略解析错误
318
318
  }
319
319
  }
320
320
  }
321
- } catch {
321
+ } catch (_error) {
322
322
  // 忽略错误,返回空 Map
323
323
  }
324
324
 
@@ -352,7 +352,7 @@ export class RedisCacheStore implements CacheStore {
352
352
  }
353
353
 
354
354
  return true;
355
- } catch {
355
+ } catch (_error) {
356
356
  return false;
357
357
  }
358
358
  }
@@ -372,13 +372,13 @@ export class RedisCacheStore implements CacheStore {
372
372
  try {
373
373
  await this.client.del(key);
374
374
  deleted.push(key.replace(this.keyPrefix, ''));
375
- } catch {
375
+ } catch (_error) {
376
376
  // 忽略单个删除错误
377
377
  }
378
378
  }
379
379
 
380
380
  return deleted;
381
- } catch {
381
+ } catch (_error) {
382
382
  return [];
383
383
  }
384
384
  }
@@ -88,7 +88,7 @@ export function createClient(
88
88
 
89
89
  try {
90
90
  return JSON.parse(text);
91
- } catch {
91
+ } catch (_error) {
92
92
  return text;
93
93
  }
94
94
  };
@@ -11,6 +11,23 @@ import { CONFIG_SERVICE_TOKEN, type ConfigModuleOptions } from './types';
11
11
  providers: [],
12
12
  })
13
13
  export class ConfigModule {
14
+ private static readonly DANGEROUS_PATH_SEGMENTS = new Set([
15
+ '__proto__',
16
+ 'prototype',
17
+ 'constructor',
18
+ ]);
19
+
20
+ private static isPlainObject(value: unknown): value is Record<string, unknown> {
21
+ if (typeof value !== 'object' || value === null) {
22
+ return false;
23
+ }
24
+ const proto = Object.getPrototypeOf(value);
25
+ return proto === null || proto === Object.prototype;
26
+ }
27
+
28
+ private static createSafeContainer(): Record<string, unknown> {
29
+ return Object.create(null) as Record<string, unknown>;
30
+ }
14
31
  /**
15
32
  * 创建配置模块
16
33
  * @param options - 模块配置
@@ -213,7 +230,7 @@ export class ConfigModule {
213
230
  try {
214
231
  const parsed = ConfigService.parseConfigContent(result.content);
215
232
  ConfigModule.setValueByPath(configMap, configPath, parsed);
216
- } catch {
233
+ } catch (_error) {
217
234
  ConfigModule.setValueByPath(configMap, configPath, result.content);
218
235
  }
219
236
  })
@@ -261,7 +278,7 @@ export class ConfigModule {
261
278
  let parsedValue: unknown;
262
279
  try {
263
280
  parsedValue = ConfigService.parseConfigContent(result.content);
264
- } catch {
281
+ } catch (_error) {
265
282
  parsedValue = result.content;
266
283
  }
267
284
 
@@ -302,24 +319,39 @@ export class ConfigModule {
302
319
  path: string,
303
320
  value: unknown,
304
321
  ): void {
305
- const segments = path.split('.');
322
+ const normalizedSegments = path.split('.').map((segment) => segment.trim());
323
+ if (
324
+ normalizedSegments.length === 0 ||
325
+ normalizedSegments.some((segment) => !segment)
326
+ ) {
327
+ throw new Error(`[ConfigModule] Invalid config path: "${path}"`);
328
+ }
329
+
330
+ for (const segment of normalizedSegments) {
331
+ if (ConfigModule.DANGEROUS_PATH_SEGMENTS.has(segment)) {
332
+ throw new Error(`[ConfigModule] Unsafe config path segment: "${segment}"`);
333
+ }
334
+ }
335
+
306
336
  let current: Record<string, unknown> = obj;
307
337
 
308
- for (let i = 0; i < segments.length - 1; i++) {
309
- const segment = segments[i]!;
310
- // 检查是否需要创建嵌套对象
311
- // 注意:typeof null === 'object',所以需要明确排除 null
312
- if (
313
- !(segment in current) ||
314
- current[segment] == null ||
315
- typeof current[segment] !== 'object'
316
- ) {
317
- current[segment] = {};
338
+ for (let i = 0; i < normalizedSegments.length - 1; i++) {
339
+ const segment = normalizedSegments[i]!;
340
+ const hasOwn = Object.prototype.hasOwnProperty.call(current, segment);
341
+ const existing = hasOwn ? current[segment] : undefined;
342
+
343
+ if (!ConfigModule.isPlainObject(existing)) {
344
+ current[segment] = ConfigModule.createSafeContainer();
318
345
  }
346
+
319
347
  current = current[segment] as Record<string, unknown>;
320
348
  }
321
349
 
322
- current[segments[segments.length - 1]!] = value;
350
+ const lastSegment = normalizedSegments[normalizedSegments.length - 1]!;
351
+ if (ConfigModule.DANGEROUS_PATH_SEGMENTS.has(lastSegment)) {
352
+ throw new Error(`[ConfigModule] Unsafe config path segment: "${lastSegment}"`);
353
+ }
354
+ current[lastSegment] = value;
323
355
  }
324
356
  }
325
357
 
@@ -24,10 +24,10 @@ export class ConfigService<TConfig extends Record<string, unknown> = Record<stri
24
24
 
25
25
  try {
26
26
  return JSON.parse(content);
27
- } catch {
27
+ } catch (_error) {
28
28
  try {
29
29
  return Bun.JSONC.parse(content);
30
- } catch {
30
+ } catch (_innerError) {
31
31
  return Bun.JSON5.parse(content);
32
32
  }
33
33
  }
@@ -105,7 +105,7 @@ export class ParamBinder {
105
105
  (context as unknown as { sessionId: string }).sessionId = newSession.id;
106
106
  return newSession;
107
107
  }
108
- } catch {
108
+ } catch (_error) {
109
109
  // SessionService 未注册,返回 undefined
110
110
  }
111
111
  return undefined;
@@ -125,7 +125,7 @@ export class ConversationService {
125
125
  for (const msg of newMessages) {
126
126
  await this.store.appendMessage(id, msg);
127
127
  }
128
- } catch {
128
+ } catch (_error) {
129
129
  // If summarization fails, fall back to simple trim
130
130
  await this.store.trim(id, this.maxMessages);
131
131
  }
@@ -343,7 +343,7 @@ export class Application {
343
343
  path: context.path,
344
344
  });
345
345
  context.setStatus(404);
346
- return context.createResponse({ error: 'Not Found' });
346
+ return context.createErrorResponse({ error: 'Not Found' });
347
347
  });
348
348
  });
349
349
  }
@@ -117,7 +117,7 @@ export class ClusterManager {
117
117
  if (this.socketDir) {
118
118
  try {
119
119
  rmSync(this.socketDir, { recursive: true, force: true });
120
- } catch {
120
+ } catch (_error) {
121
121
  // ignore cleanup errors
122
122
  }
123
123
  this.socketDir = undefined;
@@ -186,7 +186,7 @@ export class ClusterManager {
186
186
 
187
187
  this.socketDir = join(tmpdir(), `bun-cluster-${process.pid}`);
188
188
  // Clean up stale directory from a previous run with the same PID
189
- try { rmSync(this.socketDir, { recursive: true, force: true }); } catch { /* ignore */ }
189
+ try { rmSync(this.socketDir, { recursive: true, force: true }); } catch (_error) { /* ignore */ }
190
190
  mkdirSync(this.socketDir, { recursive: true });
191
191
 
192
192
  this.socketPaths = [];
@@ -271,7 +271,7 @@ export class ClusterManager {
271
271
  redirect: 'manual',
272
272
  unix: socketPath,
273
273
  });
274
- } catch {
274
+ } catch (_error) {
275
275
  return new Response('Bad Gateway', { status: 502 });
276
276
  }
277
277
  },
@@ -289,7 +289,7 @@ export class ClusterManager {
289
289
  );
290
290
 
291
291
  const socketPath = this.socketPaths[index]!;
292
- try { rmSync(socketPath); } catch { /* ignore */ }
292
+ try { rmSync(socketPath); } catch (_error) { /* ignore */ }
293
293
 
294
294
  this.workers[index] = this.spawnProxyWorker(index, socketPath);
295
295
 
@@ -8,6 +8,7 @@ import { type URLSearchParams, URL } from 'url';
8
8
  * 封装 Request 和 Response,提供便捷的访问方法
9
9
  */
10
10
  export class Context {
11
+ private static readonly ERROR_REDACTED_KEYS = new Set(['stack', 'trace', 'cause']);
11
12
  /**
12
13
  * 原始请求对象
13
14
  */
@@ -225,5 +226,75 @@ export class Context {
225
226
  ...init,
226
227
  });
227
228
  }
229
+
230
+ /**
231
+ * 创建错误响应(自动过滤敏感字段)
232
+ * @param body - 错误响应体
233
+ * @param init - 响应初始化选项
234
+ * @returns Response 对象
235
+ */
236
+ public createErrorResponse(body?: unknown, init?: ResponseInit): Response {
237
+ const status = init?.status ?? this.statusCode;
238
+ const errorStatus = status >= 400 ? status : 500;
239
+ const sanitizedBody = this.sanitizeErrorPayload(body);
240
+
241
+ return this.createResponse(sanitizedBody, {
242
+ ...init,
243
+ status: errorStatus,
244
+ });
245
+ }
246
+
247
+ private sanitizeErrorPayload(body: unknown): unknown {
248
+ if (body === undefined || body === null) {
249
+ return body;
250
+ }
251
+
252
+ if (body instanceof Error) {
253
+ return {
254
+ error: body.message || 'Internal Server Error',
255
+ };
256
+ }
257
+
258
+ const seen = new WeakSet<object>();
259
+ return this.sanitizeValue(body, seen);
260
+ }
261
+
262
+ private sanitizeValue(value: unknown, seen: WeakSet<object>): unknown {
263
+ if (value === null || value === undefined) {
264
+ return value;
265
+ }
266
+
267
+ if (typeof value !== 'object') {
268
+ return value;
269
+ }
270
+
271
+ if (value instanceof Date || value instanceof ArrayBuffer || value instanceof Blob) {
272
+ return value;
273
+ }
274
+
275
+ if (seen.has(value)) {
276
+ return '[Circular]';
277
+ }
278
+ seen.add(value);
279
+
280
+ if (Array.isArray(value)) {
281
+ return value.map((item) => this.sanitizeValue(item, seen));
282
+ }
283
+
284
+ const prototype = Object.getPrototypeOf(value);
285
+ if (prototype !== Object.prototype && prototype !== null) {
286
+ return value;
287
+ }
288
+
289
+ const sanitized: Record<string, unknown> = {};
290
+ for (const [key, nestedValue] of Object.entries(value as Record<string, unknown>)) {
291
+ if (Context.ERROR_REDACTED_KEYS.has(key)) {
292
+ continue;
293
+ }
294
+ sanitized[key] = this.sanitizeValue(nestedValue, seen);
295
+ }
296
+
297
+ return sanitized;
298
+ }
228
299
  }
229
300
 
@@ -75,7 +75,7 @@ export class DashboardService {
75
75
  return (
76
76
  username === this.auth.username && password === this.auth.password
77
77
  );
78
- } catch {
78
+ } catch (_error) {
79
79
  return false;
80
80
  }
81
81
  }
@@ -214,7 +214,7 @@ export class DashboardService {
214
214
  return new Response(JSON.stringify(data), {
215
215
  headers: { 'Content-Type': 'application/json; charset=utf-8' },
216
216
  });
217
- } catch {
217
+ } catch (_error) {
218
218
  const data = {
219
219
  status: 'up',
220
220
  timestamp: Date.now(),
@@ -104,7 +104,7 @@ export class DatabaseConnectionManager {
104
104
  return await this.healthCheckMysql(this.currentConnection);
105
105
  }
106
106
  return false;
107
- } catch {
107
+ } catch (_error) {
108
108
  return false;
109
109
  }
110
110
  }
@@ -176,7 +176,7 @@ export class DatabaseConnectionManager {
176
176
  return true;
177
177
  }
178
178
  return false;
179
- } catch {
179
+ } catch (_error) {
180
180
  return false;
181
181
  }
182
182
  }
@@ -204,7 +204,7 @@ export class DatabaseConnectionManager {
204
204
  return true;
205
205
  }
206
206
  return false;
207
- } catch {
207
+ } catch (_error) {
208
208
  return false;
209
209
  }
210
210
  }
@@ -232,7 +232,7 @@ export class DatabaseConnectionManager {
232
232
  return true;
233
233
  }
234
234
  return false;
235
- } catch {
235
+ } catch (_error) {
236
236
  return false;
237
237
  }
238
238
  }
@@ -145,7 +145,7 @@ export class DatabaseService {
145
145
 
146
146
  /**
147
147
  * Bun.SQL 查询实现(PostgreSQL/MySQL)
148
- * 注意:Bun.SQL 主要使用模板字符串,但为了兼容性,我们尝试支持参数化查询
148
+ * 通过模板字符串调用 Bun.SQL,确保参数走 Bun.SQL 转义逻辑
149
149
  */
150
150
  private async queryBunSQL<T = unknown>(
151
151
  connection: unknown,
@@ -153,25 +153,23 @@ export class DatabaseService {
153
153
  params?: unknown[],
154
154
  ): Promise<T[]> {
155
155
  // Bun.SQL 对象可以作为函数调用(模板字符串)
156
- // 但我们需要支持参数化查询
157
156
  if (connection && typeof connection === 'function') {
158
- // Bun.SQL 模板字符串查询
159
- // 注意:Bun.SQL 使用模板字符串,参数会自动转义
160
- // 这里我们需要将 SQL 和参数组合成模板字符串调用
161
- // 但由于 TypeScript 限制,我们使用动态调用
162
157
  try {
163
- // 尝试使用模板字符串方式
164
- // 注意:这需要将参数插入到 SQL 中,但 Bun.SQL 会自动处理 SQL 注入防护
165
- const sqlWithParams = this.interpolateParams(sql, params);
158
+ const { strings, values } = this.buildTemplateFromSql(sql, params);
159
+ const template = Object.assign(strings, {
160
+ raw: strings,
161
+ }) as unknown as TemplateStringsArray;
166
162
  const result = await (connection as (
167
163
  template: TemplateStringsArray,
168
164
  ...values: unknown[]
169
- ) => Promise<Array<Record<string, unknown>>>)`${sqlWithParams}`;
165
+ ) => Promise<Array<Record<string, unknown>>>)(template, ...values);
170
166
  return result as T[];
171
- } catch {
172
- // 如果模板字符串方式失败,尝试其他方式
167
+ } catch (error) {
168
+ const errorMessage =
169
+ error instanceof Error ? error.message : String(error);
170
+ // 如果模板字符串方式失败,保留原始错误,便于排查参数/SQL 构造问题
173
171
  throw new Error(
174
- 'Bun.SQL parameterized queries are not fully supported. Consider using template string queries.',
172
+ `Bun.SQL parameterized queries are not fully supported. Consider using template string queries. Original error: ${errorMessage}`,
175
173
  );
176
174
  }
177
175
  }
@@ -197,25 +195,24 @@ export class DatabaseService {
197
195
  }
198
196
 
199
197
  /**
200
- * 将参数插入到 SQL 中(用于 Bun.SQL 模板字符串)
201
- * 注意:这只是临时方案,Bun.SQL 的模板字符串会自动处理 SQL 注入防护
198
+ * SQL ? 占位符参数转换为模板字符串片段
199
+ * 让参数通过 Bun.SQL values 通道注入,避免手工拼接 SQL
202
200
  */
203
- private interpolateParams(sql: string, params?: unknown[]): string {
201
+ private buildTemplateFromSql(
202
+ sql: string,
203
+ params?: unknown[],
204
+ ): { strings: string[]; values: unknown[] } {
204
205
  if (!params || params.length === 0) {
205
- return sql;
206
+ return { strings: [sql], values: [] };
206
207
  }
207
208
 
208
- // 简单的参数替换(实际应该使用 Bun.SQL 的模板字符串)
209
- // 这里只是占位实现,实际使用时应该使用模板字符串
210
- let result = sql;
211
- for (let i = 0; i < params.length; i++) {
212
- const param = params[i];
213
- const value =
214
- typeof param === 'string'
215
- ? `'${param.replace(/'/g, "''")}'`
216
- : String(param);
217
- result = result.replace('?', value);
209
+ const strings = sql.split('?');
210
+ if (strings.length !== params.length + 1) {
211
+ throw new Error(
212
+ 'SQL placeholders count does not match parameters count',
213
+ );
218
214
  }
219
- return result;
215
+
216
+ return { strings, values: params };
220
217
  }
221
218
  }
@@ -67,7 +67,7 @@ export function createDebugMiddleware(
67
67
  if (recordBody && ['POST', 'PUT', 'PATCH'].includes(context.method)) {
68
68
  try {
69
69
  requestBody = await context.getBody();
70
- } catch {
70
+ } catch (_error) {
71
71
  requestBody = undefined;
72
72
  }
73
73
  }
@@ -92,7 +92,7 @@ export function createDebugMiddleware(
92
92
  if (route) {
93
93
  matchedRoute = route.path;
94
94
  }
95
- } catch {
95
+ } catch (_error) {
96
96
  matchedRoute = undefined;
97
97
  }
98
98
 
@@ -242,7 +242,7 @@ export class ModuleRegistry {
242
242
  } else if ('useFactory' in provider) {
243
243
  instances.push(ref.container.resolve(provider.provide as Constructor<unknown>));
244
244
  }
245
- } catch {
245
+ } catch (_error) {
246
246
  // skip providers that can't be resolved (e.g. pending async providers)
247
247
  }
248
248
  }
@@ -53,12 +53,12 @@ export async function handleError(error: unknown, context: Context): Promise<Res
53
53
  responseBody.details = error.details;
54
54
  }
55
55
 
56
- return context.createResponse(responseBody);
56
+ return context.createErrorResponse(responseBody);
57
57
  }
58
58
 
59
59
  if (error instanceof ValidationError) {
60
60
  context.setStatus(400);
61
- return context.createResponse({
61
+ return context.createErrorResponse({
62
62
  error: error.message,
63
63
  code: 'VALIDATION_FAILED',
64
64
  issues: error.issues,
@@ -74,7 +74,7 @@ export async function handleError(error: unknown, context: Context): Promise<Res
74
74
  message,
75
75
  stack,
76
76
  });
77
- return context.createResponse({
77
+ return context.createErrorResponse({
78
78
  error: 'Internal Server Error',
79
79
  details: process.env.NODE_ENV === 'production' ? undefined : message,
80
80
  });