@dangao/bun-server 1.2.0 → 1.3.0

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.
@@ -8,7 +8,9 @@ export declare enum ParamType {
8
8
  PARAM = "param",
9
9
  HEADER = "header",
10
10
  SESSION = "session",
11
- CONTEXT = "context"
11
+ CONTEXT = "context",
12
+ QUERY_MAP = "query_map",
13
+ HEADER_MAP = "header_map"
12
14
  }
13
15
  /**
14
16
  * 参数元数据
@@ -17,6 +19,7 @@ export interface ParamMetadata {
17
19
  type: ParamType;
18
20
  key?: string;
19
21
  index: number;
22
+ options?: unknown;
20
23
  }
21
24
  /**
22
25
  * 参数装饰器工厂
@@ -45,6 +48,32 @@ export declare function Param(key: string): (target: any, propertyKey: string |
45
48
  * @param key - 请求头键
46
49
  */
47
50
  export declare function Header(key: string): (target: any, propertyKey: string | symbol | undefined, parameterIndex: number) => void;
51
+ /**
52
+ * QueryMap 注解选项
53
+ */
54
+ export interface QueryMapOptions<T = unknown> {
55
+ transform?: (input: Record<string, string | string[]>) => T | Promise<T>;
56
+ validate?: (dto: T) => void | Promise<void>;
57
+ }
58
+ /**
59
+ * HeaderMap 注解选项
60
+ */
61
+ export interface HeaderMapOptions<T = unknown> {
62
+ normalize?: boolean;
63
+ pick?: string[];
64
+ transform?: (input: Record<string, string | string[]>) => T | Promise<T>;
65
+ validate?: (dto: T) => void | Promise<void>;
66
+ }
67
+ /**
68
+ * QueryMap 参数装饰器
69
+ * 一次性注入完整查询对象
70
+ */
71
+ export declare function QueryMap<T = Record<string, string | string[]>>(options?: QueryMapOptions<T> | ((input: Record<string, string | string[]>) => T | Promise<T>)): (target: any, propertyKey: string | symbol | undefined, parameterIndex: number) => void;
72
+ /**
73
+ * HeaderMap 参数装饰器
74
+ * 一次性注入完整 headers 对象
75
+ */
76
+ export declare function HeaderMap<T = Record<string, string | string[]>>(options?: HeaderMapOptions<T> | ((input: Record<string, string | string[]>) => T | Promise<T>)): (target: any, propertyKey: string | symbol | undefined, parameterIndex: number) => void;
48
77
  /**
49
78
  * Context 参数装饰器
50
79
  * 用于在控制器方法中注入当前请求的 Context 对象
@@ -1 +1 @@
1
- {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../../src/controller/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAO1B;;GAEG;AACH,oBAAY,SAAS;IACnB,IAAI,SAAS;IACb,KAAK,UAAU;IACf,KAAK,UAAU;IACf,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,OAAO,YAAY;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,SAAS,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,MAAM,IAC/C,QAAQ,GAAG,EAAE,aAAa,MAAM,GAAG,MAAM,GAAG,SAAS,EAAE,gBAAgB,MAAM,UAM/F;AAED;;;GAGG;AACH,wBAAgB,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,YAZN,GAAG,eAAe,MAAM,GAAG,MAAM,GAAG,SAAS,kBAAkB,MAAM,UAc/F;AAED;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,YApBN,GAAG,eAAe,MAAM,GAAG,MAAM,GAAG,SAAS,kBAAkB,MAAM,UAsB/F;AAED;;;GAGG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,YA5BN,GAAG,eAAe,MAAM,GAAG,MAAM,GAAG,SAAS,kBAAkB,MAAM,UA8B/F;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,YApCP,GAAG,eAAe,MAAM,GAAG,MAAM,GAAG,SAAS,kBAAkB,MAAM,UAsC/F;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,aAtDI,GAAG,eAAe,MAAM,GAAG,MAAM,GAAG,SAAS,kBAAkB,MAAM,UAwD/F;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,GAAG,aAAa,EAAE,CAElF"}
1
+ {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../../src/controller/decorators.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- export { Body, Query, Param, Header, Context, getParamMetadata, ParamType } from './decorators';
2
- export type { ParamMetadata } from './decorators';
1
+ export { Body, Query, QueryMap, Param, Header, HeaderMap, Context, getParamMetadata, ParamType, } from './decorators';
2
+ export type { ParamMetadata, QueryMapOptions, HeaderMapOptions, } from './decorators';
3
3
  export { ParamBinder } from './param-binder';
4
4
  export { Controller, ControllerRegistry } from './controller';
5
5
  export type { ControllerMetadata } from './controller';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/controller/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAChG,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC9D,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/controller/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,KAAK,EACL,MAAM,EACN,SAAS,EACT,OAAO,EACP,gBAAgB,EAChB,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,aAAa,EACb,eAAe,EACf,gBAAgB,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC9D,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
@@ -50,5 +50,17 @@ export declare class ParamBinder {
50
50
  * @returns Header 值
51
51
  */
52
52
  private static getHeaderValue;
53
+ /**
54
+ * 获取 QueryMap 值
55
+ * @param options - 装饰器选项
56
+ * @param context - 请求上下文
57
+ */
58
+ private static getQueryMapValue;
59
+ /**
60
+ * 获取 HeaderMap 值
61
+ * @param options - 装饰器选项
62
+ * @param context - 请求上下文
63
+ */
64
+ private static getHeaderMapValue;
53
65
  }
54
66
  //# sourceMappingURL=param-binder.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"param-binder.d.ts","sourceRoot":"","sources":["../../src/controller/param-binder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE/C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;GAGG;AACH,qBAAa,WAAW;IACtB;;;;;;;OAOG;WACiB,IAAI,CACtB,MAAM,EAAE,GAAG,EACX,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,OAAO,EAChB,SAAS,CAAC,EAAE,SAAS,GACpB,OAAO,CAAC,OAAO,EAAE,CAAC;IAyBrB;;;;;;OAMG;mBACkB,QAAQ;IAwD7B;;;;;OAKG;mBACkB,YAAY;IAWjC;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAI5B;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAI5B;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;CAG9B"}
1
+ {"version":3,"file":"param-binder.d.ts","sourceRoot":"","sources":["../../src/controller/param-binder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAQ/C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C;;;GAGG;AACH,qBAAa,WAAW;IACtB;;;;;;;OAOG;WACiB,IAAI,CACtB,MAAM,EAAE,GAAG,EACX,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,OAAO,EAChB,SAAS,CAAC,EAAE,SAAS,GACpB,OAAO,CAAC,OAAO,EAAE,CAAC;IAyBrB;;;;;;OAMG;mBACkB,QAAQ;IA4D7B;;;;;OAKG;mBACkB,YAAY;IAWjC;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAI5B;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAI5B;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;IAI7B;;;;OAIG;mBACkB,gBAAgB;IA0BrC;;;;OAIG;mBACkB,iBAAiB;CAuCvC"}
package/dist/index.d.ts CHANGED
@@ -6,9 +6,9 @@ export { Route, Router, RouteRegistry } from './router';
6
6
  export { GET, POST, PUT, DELETE, PATCH } from './router/decorators';
7
7
  export type { HttpMethod, RouteHandler, RouteMatch } from './router/types';
8
8
  export { BodyParser, RequestWrapper, ResponseBuilder } from './request';
9
- export { Body, Query, Param, Header, ParamBinder, Controller, ControllerRegistry } from './controller';
9
+ export { Body, Query, QueryMap, Param, Header, HeaderMap, ParamBinder, Controller, ControllerRegistry, } from './controller';
10
10
  export { Context as ContextParam } from './controller';
11
- export type { ParamMetadata, ControllerMetadata } from './controller';
11
+ export type { ParamMetadata, QueryMapOptions, HeaderMapOptions, ControllerMetadata, } from './controller';
12
12
  export { Container } from './di/container';
13
13
  export { Injectable, Inject } from './di/decorators';
14
14
  export { Lifecycle, type ProviderConfig, type DependencyMetadata } from './di/types';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1478,6 +1478,22 @@ function Param(key) {
1478
1478
  function Header(key) {
1479
1479
  return createParamDecorator("header" /* HEADER */, key);
1480
1480
  }
1481
+ function QueryMap(options) {
1482
+ return function(target, propertyKey, parameterIndex) {
1483
+ const existingParams = Reflect.getMetadata(PARAM_METADATA_KEY, target, propertyKey) || [];
1484
+ const normalizedOptions = typeof options === "function" ? { transform: options } : options ?? {};
1485
+ existingParams.push({ type: "query_map" /* QUERY_MAP */, index: parameterIndex, options: normalizedOptions });
1486
+ Reflect.defineMetadata(PARAM_METADATA_KEY, existingParams, target, propertyKey);
1487
+ };
1488
+ }
1489
+ function HeaderMap(options) {
1490
+ return function(target, propertyKey, parameterIndex) {
1491
+ const existingParams = Reflect.getMetadata(PARAM_METADATA_KEY, target, propertyKey) || [];
1492
+ const normalizedOptions = typeof options === "function" ? { transform: options } : options ?? {};
1493
+ existingParams.push({ type: "header_map" /* HEADER_MAP */, index: parameterIndex, options: normalizedOptions });
1494
+ Reflect.defineMetadata(PARAM_METADATA_KEY, existingParams, target, propertyKey);
1495
+ };
1496
+ }
1481
1497
  function Context2() {
1482
1498
  return createParamDecorator("context" /* CONTEXT */);
1483
1499
  }
@@ -1690,6 +1706,10 @@ class ParamBinder {
1690
1706
  return;
1691
1707
  case "context" /* CONTEXT */:
1692
1708
  return contextStore.getStore() ?? context;
1709
+ case "query_map" /* QUERY_MAP */:
1710
+ return await this.getQueryMapValue(meta.options, context);
1711
+ case "header_map" /* HEADER_MAP */:
1712
+ return await this.getHeaderMapValue(meta.options, context);
1693
1713
  default:
1694
1714
  return;
1695
1715
  }
@@ -1713,6 +1733,52 @@ class ParamBinder {
1713
1733
  static getHeaderValue(key, context) {
1714
1734
  return context.getHeader(key);
1715
1735
  }
1736
+ static async getQueryMapValue(options, context) {
1737
+ const result = {};
1738
+ const searchParams = context.query;
1739
+ for (const key of searchParams.keys()) {
1740
+ const values = searchParams.getAll(key);
1741
+ if (values.length === 1) {
1742
+ result[key] = values[0];
1743
+ } else {
1744
+ result[key] = values;
1745
+ }
1746
+ }
1747
+ let output = result;
1748
+ if (options?.transform) {
1749
+ output = await options.transform(result);
1750
+ }
1751
+ if (options?.validate) {
1752
+ await options.validate(output);
1753
+ }
1754
+ return output;
1755
+ }
1756
+ static async getHeaderMapValue(options, context) {
1757
+ const normalize = options?.normalize ?? true;
1758
+ const pick = options?.pick?.map((key) => key.toLowerCase());
1759
+ const headers = context.headers;
1760
+ const result = {};
1761
+ headers.forEach((value, rawKey) => {
1762
+ const key = normalize ? rawKey.toLowerCase() : rawKey;
1763
+ if (pick && !pick.includes(key)) {
1764
+ return;
1765
+ }
1766
+ const parts = value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
1767
+ if (parts.length <= 1) {
1768
+ result[key] = parts[0] ?? "";
1769
+ } else {
1770
+ result[key] = parts;
1771
+ }
1772
+ });
1773
+ let output = result;
1774
+ if (options?.transform) {
1775
+ output = await options.transform(result);
1776
+ }
1777
+ if (options?.validate) {
1778
+ await options.validate(output);
1779
+ }
1780
+ return output;
1781
+ }
1716
1782
  }
1717
1783
 
1718
1784
  // src/controller/metadata.ts
@@ -6782,6 +6848,7 @@ export {
6782
6848
  QueueService,
6783
6849
  QueueModule,
6784
6850
  Queue,
6851
+ QueryMap,
6785
6852
  Query,
6786
6853
  QUEUE_SERVICE_TOKEN,
6787
6854
  QUEUE_OPTIONS_TOKEN,
@@ -6842,6 +6909,7 @@ export {
6842
6909
  INTERCEPTOR_REGISTRY_TOKEN,
6843
6910
  HttpException,
6844
6911
  HealthModule,
6912
+ HeaderMap,
6845
6913
  Header,
6846
6914
  HEALTH_OPTIONS_TOKEN,
6847
6915
  HEALTH_INDICATORS_TOKEN,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dangao/bun-server",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -15,6 +15,8 @@ export enum ParamType {
15
15
  HEADER = 'header',
16
16
  SESSION = 'session',
17
17
  CONTEXT = 'context',
18
+ QUERY_MAP = 'query_map',
19
+ HEADER_MAP = 'header_map',
18
20
  }
19
21
 
20
22
  /**
@@ -24,6 +26,7 @@ export interface ParamMetadata {
24
26
  type: ParamType;
25
27
  key?: string;
26
28
  index: number;
29
+ options?: unknown;
27
30
  }
28
31
 
29
32
  /**
@@ -73,6 +76,58 @@ export function Header(key: string) {
73
76
  return createParamDecorator(ParamType.HEADER, key);
74
77
  }
75
78
 
79
+ /**
80
+ * QueryMap 注解选项
81
+ */
82
+ export interface QueryMapOptions<T = unknown> {
83
+ transform?: (input: Record<string, string | string[]>) => T | Promise<T>;
84
+ validate?: (dto: T) => void | Promise<void>;
85
+ }
86
+
87
+ /**
88
+ * HeaderMap 注解选项
89
+ */
90
+ export interface HeaderMapOptions<T = unknown> {
91
+ normalize?: boolean;
92
+ pick?: string[];
93
+ transform?: (input: Record<string, string | string[]>) => T | Promise<T>;
94
+ validate?: (dto: T) => void | Promise<void>;
95
+ }
96
+
97
+ /**
98
+ * QueryMap 参数装饰器
99
+ * 一次性注入完整查询对象
100
+ */
101
+ export function QueryMap<T = Record<string, string | string[]>>(
102
+ options?: QueryMapOptions<T> | ((input: Record<string, string | string[]>) => T | Promise<T>),
103
+ ) {
104
+ return function (target: any, propertyKey: string | symbol | undefined, parameterIndex: number) {
105
+ const existingParams: ParamMetadata[] =
106
+ Reflect.getMetadata(PARAM_METADATA_KEY, target, propertyKey as string) || [];
107
+ const normalizedOptions: QueryMapOptions<T> =
108
+ typeof options === 'function' ? { transform: options } : options ?? {};
109
+ existingParams.push({ type: ParamType.QUERY_MAP, index: parameterIndex, options: normalizedOptions });
110
+ Reflect.defineMetadata(PARAM_METADATA_KEY, existingParams, target, propertyKey as string);
111
+ };
112
+ }
113
+
114
+ /**
115
+ * HeaderMap 参数装饰器
116
+ * 一次性注入完整 headers 对象
117
+ */
118
+ export function HeaderMap<T = Record<string, string | string[]>>(
119
+ options?: HeaderMapOptions<T> | ((input: Record<string, string | string[]>) => T | Promise<T>),
120
+ ) {
121
+ return function (target: any, propertyKey: string | symbol | undefined, parameterIndex: number) {
122
+ const existingParams: ParamMetadata[] =
123
+ Reflect.getMetadata(PARAM_METADATA_KEY, target, propertyKey as string) || [];
124
+ const normalizedOptions: HeaderMapOptions<T> =
125
+ typeof options === 'function' ? { transform: options } : options ?? {};
126
+ existingParams.push({ type: ParamType.HEADER_MAP, index: parameterIndex, options: normalizedOptions });
127
+ Reflect.defineMetadata(PARAM_METADATA_KEY, existingParams, target, propertyKey as string);
128
+ };
129
+ }
130
+
76
131
  /**
77
132
  * Context 参数装饰器
78
133
  * 用于在控制器方法中注入当前请求的 Context 对象
@@ -1,5 +1,19 @@
1
- export { Body, Query, Param, Header, Context, getParamMetadata, ParamType } from './decorators';
2
- export type { ParamMetadata } from './decorators';
1
+ export {
2
+ Body,
3
+ Query,
4
+ QueryMap,
5
+ Param,
6
+ Header,
7
+ HeaderMap,
8
+ Context,
9
+ getParamMetadata,
10
+ ParamType,
11
+ } from './decorators';
12
+ export type {
13
+ ParamMetadata,
14
+ QueryMapOptions,
15
+ HeaderMapOptions,
16
+ } from './decorators';
3
17
  export { ParamBinder } from './param-binder';
4
18
  export { Controller, ControllerRegistry } from './controller';
5
19
  export type { ControllerMetadata } from './controller';
@@ -1,5 +1,11 @@
1
1
  import type { Context } from '../core/context';
2
- import { getParamMetadata, ParamType, type ParamMetadata } from './decorators';
2
+ import {
3
+ getParamMetadata,
4
+ ParamType,
5
+ type ParamMetadata,
6
+ type QueryMapOptions,
7
+ type HeaderMapOptions,
8
+ } from './decorators';
3
9
  import { Container } from '../di/container';
4
10
  import { SessionService } from '../session/service';
5
11
  import { SESSION_SERVICE_TOKEN } from '../session/types';
@@ -106,6 +112,10 @@ export class ParamBinder {
106
112
  case ParamType.CONTEXT:
107
113
  // 从 AsyncLocalStorage 获取当前请求的 Context
108
114
  return contextStore.getStore() ?? context;
115
+ case ParamType.QUERY_MAP:
116
+ return await this.getQueryMapValue(meta.options as QueryMapOptions, context);
117
+ case ParamType.HEADER_MAP:
118
+ return await this.getHeaderMapValue(meta.options as HeaderMapOptions, context);
109
119
  default:
110
120
  return undefined;
111
121
  }
@@ -157,5 +167,81 @@ export class ParamBinder {
157
167
  private static getHeaderValue(key: string, context: Context): string | null {
158
168
  return context.getHeader(key);
159
169
  }
170
+
171
+ /**
172
+ * 获取 QueryMap 值
173
+ * @param options - 装饰器选项
174
+ * @param context - 请求上下文
175
+ */
176
+ private static async getQueryMapValue(
177
+ options: QueryMapOptions | undefined,
178
+ context: Context,
179
+ ): Promise<unknown> {
180
+ const result: Record<string, string | string[]> = {};
181
+ const searchParams = context.query;
182
+ // 收集所有键,处理重复 key -> string[]
183
+ for (const key of searchParams.keys()) {
184
+ const values = searchParams.getAll(key);
185
+ if (values.length === 1) {
186
+ result[key] = values[0];
187
+ } else {
188
+ result[key] = values;
189
+ }
190
+ }
191
+
192
+ let output: unknown = result;
193
+ if (options?.transform) {
194
+ output = await options.transform(result);
195
+ }
196
+ if (options?.validate) {
197
+ await options.validate(output as never);
198
+ }
199
+ return output;
200
+ }
201
+
202
+ /**
203
+ * 获取 HeaderMap 值
204
+ * @param options - 装饰器选项
205
+ * @param context - 请求上下文
206
+ */
207
+ private static async getHeaderMapValue(
208
+ options: HeaderMapOptions | undefined,
209
+ context: Context,
210
+ ): Promise<unknown> {
211
+ const normalize = options?.normalize ?? true;
212
+ // Headers API 总是将 header 名称规范化为小写,所以 pick 数组也应该总是小写化
213
+ const pick = options?.pick?.map((key) => key.toLowerCase());
214
+ const headers = context.headers;
215
+ const result: Record<string, string | string[]> = {};
216
+
217
+ headers.forEach((value, rawKey) => {
218
+ // Headers API 总是返回小写的 key,所以 rawKey 已经是小写
219
+ // normalize 选项决定结果中的 key 格式(虽然实际上总是小写)
220
+ const key = normalize ? rawKey.toLowerCase() : rawKey;
221
+ if (pick && !pick.includes(key)) {
222
+ return;
223
+ }
224
+ // 处理可能的多值(逗号分隔),统一 trim
225
+ const parts = value
226
+ .split(',')
227
+ .map((item) => item.trim())
228
+ .filter((item) => item.length > 0);
229
+ if (parts.length <= 1) {
230
+ // 单值也保持 trim 后的形态
231
+ result[key] = parts[0] ?? '';
232
+ } else {
233
+ result[key] = parts;
234
+ }
235
+ });
236
+
237
+ let output: unknown = result;
238
+ if (options?.transform) {
239
+ output = await options.transform(result);
240
+ }
241
+ if (options?.validate) {
242
+ await options.validate(output as never);
243
+ }
244
+ return output;
245
+ }
160
246
  }
161
247
 
package/src/index.ts CHANGED
@@ -6,9 +6,24 @@ export { Route, Router, RouteRegistry } from './router';
6
6
  export { GET, POST, PUT, DELETE, PATCH } from './router/decorators';
7
7
  export type { HttpMethod, RouteHandler, RouteMatch } from './router/types';
8
8
  export { BodyParser, RequestWrapper, ResponseBuilder } from './request';
9
- export { Body, Query, Param, Header, ParamBinder, Controller, ControllerRegistry } from './controller';
9
+ export {
10
+ Body,
11
+ Query,
12
+ QueryMap,
13
+ Param,
14
+ Header,
15
+ HeaderMap,
16
+ ParamBinder,
17
+ Controller,
18
+ ControllerRegistry,
19
+ } from './controller';
10
20
  export { Context as ContextParam } from './controller';
11
- export type { ParamMetadata, ControllerMetadata } from './controller';
21
+ export type {
22
+ ParamMetadata,
23
+ QueryMapOptions,
24
+ HeaderMapOptions,
25
+ ControllerMetadata,
26
+ } from './controller';
12
27
  export { Container } from './di/container';
13
28
  export { Injectable, Inject } from './di/decorators';
14
29
  export { Lifecycle, type ProviderConfig, type DependencyMetadata } from './di/types';
@@ -0,0 +1,237 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { ParamBinder } from '../../src/controller/param-binder';
4
+ import {
5
+ QueryMap,
6
+ HeaderMap,
7
+ type QueryMapOptions,
8
+ type HeaderMapOptions,
9
+ } from '../../src/controller/decorators';
10
+ import { Context } from '../../src/core/context';
11
+
12
+ class QueryMapController {
13
+ public constructor(public readonly store: unknown[] = []) {}
14
+
15
+ public async handle(@QueryMap() query: Record<string, string | string[]>) {
16
+ this.store.push(query);
17
+ return query;
18
+ }
19
+
20
+ public async handleTransformed(
21
+ @QueryMap<{ name: string; age: number }>((input) => ({
22
+ name: (input.name as string) ?? '',
23
+ age: Number((input.age as string) ?? '0'),
24
+ }))
25
+ query: { name: string; age: number },
26
+ ) {
27
+ this.store.push(query);
28
+ return query;
29
+ }
30
+
31
+ public async handleValidated(
32
+ @QueryMap<{ foo: string }>(
33
+ ((input) => ({ foo: input.foo as string })) as QueryMapOptions<{ foo: string }>['transform'],
34
+ )
35
+ query: { foo: string },
36
+ ) {
37
+ if (!query.foo) {
38
+ throw new Error('validation failed');
39
+ }
40
+ this.store.push(query);
41
+ return query;
42
+ }
43
+ }
44
+
45
+ class HeaderMapController {
46
+ public constructor(public readonly store: unknown[] = []) {}
47
+
48
+ public async handle(
49
+ @HeaderMap() headers: Record<string, string | string[]>,
50
+ ) {
51
+ this.store.push(headers);
52
+ return headers;
53
+ }
54
+
55
+ public async handlePicked(
56
+ @HeaderMap({
57
+ normalize: true,
58
+ pick: ['x-custom', 'x-list'],
59
+ })
60
+ headers: Record<string, string | string[]>,
61
+ ) {
62
+ this.store.push(headers);
63
+ return headers;
64
+ }
65
+
66
+ public async handlePickedWithNormalizeFalse(
67
+ @HeaderMap({
68
+ normalize: false,
69
+ pick: ['X-Custom', 'X-List'], // 混合大小写
70
+ })
71
+ headers: Record<string, string | string[]>,
72
+ ) {
73
+ this.store.push(headers);
74
+ return headers;
75
+ }
76
+
77
+ public async handleTransformed(
78
+ @HeaderMap<{ token: string }>((input) => ({
79
+ token: (input.authorization as string) ?? '',
80
+ }))
81
+ headers: { token: string },
82
+ ) {
83
+ this.store.push(headers);
84
+ return headers;
85
+ }
86
+ }
87
+
88
+ function createContext(url: string, init?: RequestInit) {
89
+ return new Context(new Request(url, init));
90
+ }
91
+
92
+ describe('ParamBinder QueryMap', () => {
93
+ test('should aggregate query params into map with arrays', async () => {
94
+ const controller = new QueryMapController();
95
+ const ctx = createContext('http://localhost/api?q=1&q=2&name=alice');
96
+ const params = await ParamBinder.bind(
97
+ controller,
98
+ 'handle',
99
+ ctx,
100
+ );
101
+ expect(params[0]).toEqual({ q: ['1', '2'], name: 'alice' });
102
+ });
103
+
104
+ test('should transform query map', async () => {
105
+ const controller = new QueryMapController();
106
+ const ctx = createContext('http://localhost/api?name=alice&age=20');
107
+ const params = await ParamBinder.bind(
108
+ controller,
109
+ 'handleTransformed',
110
+ ctx,
111
+ );
112
+ expect(params[0]).toEqual({ name: 'alice', age: 20 });
113
+ });
114
+
115
+ test('should validate query map via user code', async () => {
116
+ const controller = new QueryMapController();
117
+ const ctx = createContext('http://localhost/api?foo=bar');
118
+ const params = await ParamBinder.bind(
119
+ controller,
120
+ 'handleValidated',
121
+ ctx,
122
+ );
123
+ expect(params[0]).toEqual({ foo: 'bar' });
124
+ });
125
+ });
126
+
127
+ describe('ParamBinder HeaderMap', () => {
128
+ test('should aggregate headers into map with optional array', async () => {
129
+ const controller = new HeaderMapController();
130
+ const ctx = createContext('http://localhost/api', {
131
+ headers: {
132
+ 'X-Token': 'abc',
133
+ 'X-List': 'a, b',
134
+ },
135
+ });
136
+ const params = await ParamBinder.bind(
137
+ controller,
138
+ 'handle',
139
+ ctx,
140
+ );
141
+ expect(params[0]).toMatchObject({
142
+ 'x-token': 'abc',
143
+ 'x-list': ['a', 'b'],
144
+ });
145
+ });
146
+
147
+ test('should pick and normalize headers', async () => {
148
+ const controller = new HeaderMapController();
149
+ const ctx = createContext('http://localhost/api', {
150
+ headers: {
151
+ 'X-Custom': 'val',
152
+ 'X-List': 'a, b',
153
+ 'Other': 'ignore',
154
+ },
155
+ });
156
+ const params = await ParamBinder.bind(
157
+ controller,
158
+ 'handlePicked',
159
+ ctx,
160
+ );
161
+ expect(params[0]).toEqual({
162
+ 'x-custom': 'val',
163
+ 'x-list': ['a', 'b'],
164
+ });
165
+ });
166
+
167
+ test('should pick headers with case-insensitive pick when normalize=true (default)', async () => {
168
+ const controller = new HeaderMapController();
169
+ const ctx = createContext('http://localhost/api', {
170
+ headers: {
171
+ 'X-CUSTOM': 'val',
172
+ 'X-LIST': 'a, b',
173
+ 'Other': 'ignore',
174
+ },
175
+ });
176
+ const params = await ParamBinder.bind(
177
+ controller,
178
+ 'handlePicked',
179
+ ctx,
180
+ );
181
+ expect(params[0]).toEqual({
182
+ 'x-custom': 'val',
183
+ 'x-list': ['a', 'b'],
184
+ });
185
+ });
186
+
187
+ test('should transform headers', async () => {
188
+ const controller = new HeaderMapController();
189
+ const ctx = createContext('http://localhost/api', {
190
+ headers: {
191
+ Authorization: ' Bearer token123 ',
192
+ },
193
+ });
194
+ const params = await ParamBinder.bind(
195
+ controller,
196
+ 'handleTransformed',
197
+ ctx,
198
+ );
199
+ expect(params[0]).toEqual({ token: 'Bearer token123' });
200
+ });
201
+
202
+ test('should trim single header value when no comma', async () => {
203
+ const controller = new HeaderMapController();
204
+ const ctx = createContext('http://localhost/api', {
205
+ headers: {
206
+ 'X-Single': ' abc ',
207
+ },
208
+ });
209
+ const params = await ParamBinder.bind(controller, 'handle', ctx);
210
+ expect(params[0]).toMatchObject({
211
+ 'x-single': 'abc',
212
+ });
213
+ });
214
+
215
+ test('should pick headers with mixed-case pick keys when normalize=false', async () => {
216
+ const controller = new HeaderMapController();
217
+ const ctx = createContext('http://localhost/api', {
218
+ headers: {
219
+ 'X-Custom': 'val',
220
+ 'X-List': 'a, b',
221
+ 'Other': 'ignore',
222
+ },
223
+ });
224
+ const params = await ParamBinder.bind(
225
+ controller,
226
+ 'handlePickedWithNormalizeFalse',
227
+ ctx,
228
+ );
229
+ // Headers API 总是返回小写的 key,所以即使 normalize=false,结果中的 key 也是小写
230
+ expect(params[0]).toEqual({
231
+ 'x-custom': 'val',
232
+ 'x-list': ['a', 'b'],
233
+ });
234
+ });
235
+ });
236
+
237
+