@nest-omni/core 4.1.3-23 → 4.1.3-25

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.
@@ -116,17 +116,36 @@ export interface ResponseTransformerConfig {
116
116
  }
117
117
  /**
118
118
  * API客户端配置
119
+ * 用于创建多个具有不同配置的 HttpClient 实例
119
120
  */
120
121
  export interface ApiClientConfig {
121
122
  /** API名称(用于标识和日志) */
122
123
  name: string;
123
124
  /** 基础URL */
124
125
  baseURL: string;
125
- /** HTTP客户端基础配置 */
126
- httpConfig?: HttpClientConfig;
127
- /** 鉴权配置 */
126
+ /**
127
+ * HttpClient 配置
128
+ * 包含所有 HttpClientConfig 的配置项(timeout, retry, logging, axiosConfig 等)
129
+ */
130
+ httpConfig?: Partial<HttpClientConfig>;
131
+ /**
132
+ * 重试配置(快捷方式,会合并到 httpConfig.retry)
133
+ * @deprecated 使用 httpConfig.retry 代替
134
+ */
135
+ retry?: {
136
+ enabled?: boolean;
137
+ retries?: number;
138
+ retryCondition?: (error: any) => boolean;
139
+ };
140
+ /**
141
+ * 鉴权配置
142
+ * API客户端特有的鉴权功能
143
+ */
128
144
  auth?: AuthConfig;
129
- /** 响应转换配置 */
145
+ /**
146
+ * 响应转换配置
147
+ * API客户端特有的响应处理
148
+ */
130
149
  responseTransformer?: ResponseTransformerConfig;
131
150
  /** 默认请求头 */
132
151
  defaultHeaders?: Record<string, string>;
@@ -138,20 +157,12 @@ export interface ApiClientConfig {
138
157
  response?: Array<(response: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>>;
139
158
  error?: Array<(error: any) => any>;
140
159
  };
141
- /** 重试配置(覆盖全局配置) */
142
- retry?: {
143
- enabled?: boolean;
144
- retries?: number;
145
- retryCondition?: (error: any) => boolean;
146
- };
147
160
  /** 是否启用响应验证 */
148
161
  enableValidation?: boolean;
149
162
  /** 响应时间警告阈值(毫秒) */
150
163
  responseTimeWarningThreshold?: number;
151
164
  /** 自定义标签(用于分类和查询) */
152
165
  tags?: string[];
153
- /** 是否启用详细日志 */
154
- enableDetailedLogging?: boolean;
155
166
  }
156
167
  /**
157
168
  * API客户端实例配置
@@ -19,6 +19,11 @@ export declare class ApiClientRegistryService {
19
19
  * 使用 HttpClientService.createApiClient 静态方法创建
20
20
  */
21
21
  createClient<T = any>(config: ApiClientInstanceConfig): T;
22
+ /**
23
+ * 深度合并配置(已废弃,不再需要)
24
+ * @deprecated 配置结构简化后不再需要此方法
25
+ */
26
+ private deepMergeConfigs;
22
27
  /**
23
28
  * 获取已注册的客户端
24
29
  */
@@ -62,7 +62,6 @@ let ApiClientRegistryService = ApiClientRegistryService_1 = class ApiClientRegis
62
62
  * 使用 HttpClientService.createApiClient 静态方法创建
63
63
  */
64
64
  createClient(config) {
65
- var _a, _b, _c, _d, _e, _f, _g;
66
65
  const clientName = config.name;
67
66
  const existingClient = this.clients.get(clientName);
68
67
  if (existingClient && !config.override) {
@@ -71,6 +70,15 @@ let ApiClientRegistryService = ApiClientRegistryService_1 = class ApiClientRegis
71
70
  }
72
71
  // 合并环境特定配置
73
72
  const finalConfig = this.mergeEnvironmentConfig(config);
73
+ // 构建 HttpClient 配置
74
+ // 1. 从全局默认配置开始
75
+ // 2. 合并 ApiClient 的 httpConfig
76
+ // 3. 应用 ApiClient 特有的 retry 配置(兼容旧代码)
77
+ let mergedHttpConfig = Object.assign(Object.assign({}, this.globalDefaults.globalDefaults), finalConfig.httpConfig);
78
+ // 兼容:如果 ApiClientConfig 顶层有 retry 配置,合并到 httpConfig.retry
79
+ if (finalConfig.retry) {
80
+ mergedHttpConfig.retry = Object.assign(Object.assign({}, mergedHttpConfig.retry), finalConfig.retry);
81
+ }
74
82
  // 使用静态工厂方法创建 HttpClientService 实例
75
83
  const httpClient = http_client_service_1.HttpClientService.createApiClient({
76
84
  circuitBreakerService: this.circuitBreakerService,
@@ -78,30 +86,7 @@ let ApiClientRegistryService = ApiClientRegistryService_1 = class ApiClientRegis
78
86
  }, {
79
87
  name: clientName,
80
88
  baseURL: finalConfig.baseURL,
81
- timeout: ((_a = finalConfig.httpConfig) === null || _a === void 0 ? void 0 : _a.timeout) || 30000,
82
- httpConfig: Object.assign({ retry: {
83
- enabled: ((_b = finalConfig.retry) === null || _b === void 0 ? void 0 : _b.enabled) !== false &&
84
- this.globalDefaults.enableGlobalRetry,
85
- retries: ((_c = finalConfig.retry) === null || _c === void 0 ? void 0 : _c.retries) ||
86
- ((_e = (_d = this.globalDefaults.globalDefaults) === null || _d === void 0 ? void 0 : _d.retry) === null || _e === void 0 ? void 0 : _e.retries) ||
87
- 3,
88
- retryDelay: (retryCount) => Math.pow(2, retryCount) * 1000,
89
- retryCondition: ((_f = finalConfig.retry) === null || _f === void 0 ? void 0 : _f.retryCondition) ||
90
- ((error) => {
91
- if (!error.response)
92
- return true;
93
- const status = error.response.status;
94
- return status >= 500 || status === 429;
95
- }),
96
- shouldResetTimeout: true,
97
- }, logging: Object.assign({ enabled: true, logRequests: true, logResponses: true, logErrors: true, logHeaders: true, logBody: true, maxBodyLength: 10000, sanitizeHeaders: ['authorization', 'api-key'], logLevel: 'info' }, (_g = this.globalDefaults.globalDefaults) === null || _g === void 0 ? void 0 : _g.logging), circuitBreaker: {
98
- enabled: this.globalDefaults.enableCircuitBreaker,
99
- failureThreshold: 5,
100
- recoveryTimeoutMs: 60000,
101
- monitoringPeriodMs: 10000,
102
- minimumThroughputThreshold: 10,
103
- countHalfOpenCalls: true,
104
- } }, finalConfig.httpConfig),
89
+ httpConfig: mergedHttpConfig,
105
90
  auth: finalConfig.auth,
106
91
  });
107
92
  // 存储客户端和配置
@@ -110,6 +95,13 @@ let ApiClientRegistryService = ApiClientRegistryService_1 = class ApiClientRegis
110
95
  this.logger.log(`Created API client: ${clientName}`);
111
96
  return httpClient;
112
97
  }
98
+ /**
99
+ * 深度合并配置(已废弃,不再需要)
100
+ * @deprecated 配置结构简化后不再需要此方法
101
+ */
102
+ deepMergeConfigs(defaults, userConfig) {
103
+ return Object.assign(Object.assign({}, defaults), userConfig);
104
+ }
113
105
  /**
114
106
  * 获取已注册的客户端
115
107
  */
@@ -135,6 +135,7 @@ export declare class HttpClientService {
135
135
  private calculateRetryDelay;
136
136
  /**
137
137
  * 应用装饰器配置
138
+ * 只在装饰器配置值有效时才覆盖请求配置
138
139
  */
139
140
  private applyDecoratorConfig;
140
141
  /**
@@ -147,6 +148,7 @@ export declare class HttpClientService {
147
148
  private mergeWithDefaults;
148
149
  /**
149
150
  * 深度合并对象
151
+ * 只在源值有效(非 undefined)时才覆盖目标值
150
152
  */
151
153
  private deepMerge;
152
154
  /**
@@ -79,14 +79,23 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
79
79
  * @returns HttpClientService 实例
80
80
  */
81
81
  static createApiClient(dependencies, config) {
82
- return new HttpClientService_1(dependencies.circuitBreakerService, dependencies.loggingService, dependencies.redisLockService, Object.assign({ baseURL: config.baseURL, timeout: config.timeout || 30000 }, config.httpConfig), config.name, config.auth);
82
+ // 构建配置,只在值有效时才覆盖
83
+ const mergedConfig = Object.assign({}, config.httpConfig);
84
+ // 只在显式提供且值有效时才覆盖
85
+ if (config.baseURL !== undefined) {
86
+ mergedConfig.baseURL = config.baseURL;
87
+ }
88
+ if (config.timeout !== undefined) {
89
+ mergedConfig.timeout = config.timeout;
90
+ }
91
+ return new HttpClientService_1(dependencies.circuitBreakerService, dependencies.loggingService, dependencies.redisLockService, mergedConfig, config.name, config.auth);
83
92
  }
84
93
  /**
85
94
  * 执行HTTP请求
86
95
  */
87
96
  request(config, decoratorContext, clientName) {
88
97
  return __awaiter(this, void 0, void 0, function* () {
89
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
98
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
90
99
  // Use the instance's clientName as fallback if not provided
91
100
  const effectiveClientName = clientName || this.clientName;
92
101
  // If no decorator context provided, try to get it from CallStackExtractor
@@ -94,22 +103,36 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
94
103
  const effectiveDecoratorContext = decoratorContext || call_stack_extractor_util_1.CallStackExtractor.getDecoratorContext();
95
104
  // ========== 安全验证开始 ==========
96
105
  // 构造完整URL进行验证
97
- const fullURL = config.url || '';
106
+ const requestURL = config.url || '';
98
107
  const baseURL = config.baseURL || this.defaultConfig.baseURL || '';
99
- // 执行URL安全验证
100
- const sanitizeResult = security_validator_util_1.SecurityValidator.sanitizeURL(fullURL, {
101
- urlConfig: (_a = this.defaultConfig.security) === null || _a === void 0 ? void 0 : _a.urlValidation,
102
- ssrfConfig: (_b = this.defaultConfig.security) === null || _b === void 0 ? void 0 : _b.ssrfProtection,
103
- });
104
- if (!sanitizeResult.valid) {
105
- const error = new Error(`URL validation failed: ${sanitizeResult.error}`);
106
- this.logger.error(`URL validation failed for ${fullURL}: ${sanitizeResult.error}`);
107
- throw error;
108
+ // 构建完整的URL用于验证
109
+ let fullURL = requestURL;
110
+ if (requestURL && !requestURL.startsWith('http://') && !requestURL.startsWith('https://') && baseURL) {
111
+ // 处理相对URL
112
+ try {
113
+ fullURL = new URL(requestURL, baseURL).href;
114
+ }
115
+ catch (_m) {
116
+ // 如果URL构建失败,使用原始URL
117
+ fullURL = requestURL;
118
+ }
108
119
  }
109
- // 如果URL被修改,更新配置
110
- if (sanitizeResult.url !== fullURL) {
111
- config.url = sanitizeResult.url;
112
- this.logger.debug(`URL sanitized from "${fullURL}" to "${sanitizeResult.url}"`);
120
+ // 执行URL安全验证(只对完整URL进行验证)
121
+ if (fullURL && (fullURL.startsWith('http://') || fullURL.startsWith('https://'))) {
122
+ const sanitizeResult = security_validator_util_1.SecurityValidator.sanitizeURL(fullURL, {
123
+ urlConfig: (_a = this.defaultConfig.security) === null || _a === void 0 ? void 0 : _a.urlValidation,
124
+ ssrfConfig: (_b = this.defaultConfig.security) === null || _b === void 0 ? void 0 : _b.ssrfProtection,
125
+ });
126
+ if (!sanitizeResult.valid) {
127
+ const error = new Error(`URL validation failed: ${sanitizeResult.error}`);
128
+ this.logger.error(`URL validation failed for ${fullURL}: ${sanitizeResult.error}`);
129
+ throw error;
130
+ }
131
+ // 如果URL被修改,更新配置
132
+ if (sanitizeResult.url !== fullURL) {
133
+ // 仅更新完整URL,保留原始的相对路径
134
+ this.logger.debug(`URL sanitized from "${fullURL}" to "${sanitizeResult.url}"`);
135
+ }
113
136
  }
114
137
  // ========== 安全验证结束 ==========
115
138
  // Capture the calling context information
@@ -167,7 +190,7 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
167
190
  this.loggingService.logRequestSuccess(response, startTime, requestId, loggingOptions, databaseLogging, retryRecorder.getRecords(), circuitBreakerState, decoratorContext, effectiveClientName, callingContext);
168
191
  }
169
192
  // 更新统计信息
170
- this.updateRequestStats(true, Date.now() - startTime, response.config.method, response.status);
193
+ this.updateRequestStats(true, Date.now() - startTime, ((_f = response.config) === null || _f === void 0 ? void 0 : _f.method) || 'GET', response.status);
171
194
  return response.data;
172
195
  }
173
196
  catch (error) {
@@ -175,16 +198,16 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
175
198
  const decoratorLogging = effectiveDecoratorContext
176
199
  ? decorators_1.HttpDecoratorUtils.getLoggingOptions(effectiveDecoratorContext.target, effectiveDecoratorContext.propertyKey)
177
200
  : {};
178
- const loggingOptions = Object.assign(Object.assign(Object.assign({}, this.defaultConfig.logging), decoratorLogging), { databaseLogging: (_f = this.defaultConfig.logging) === null || _f === void 0 ? void 0 : _f.databaseLogging });
201
+ const loggingOptions = Object.assign(Object.assign(Object.assign({}, this.defaultConfig.logging), decoratorLogging), { databaseLogging: (_g = this.defaultConfig.logging) === null || _g === void 0 ? void 0 : _g.databaseLogging });
179
202
  if (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.enabled) {
180
203
  const databaseLogging = (loggingOptions === null || loggingOptions === void 0 ? void 0 : loggingOptions.databaseLog) ||
181
- ((_h = (_g = this.defaultConfig.logging) === null || _g === void 0 ? void 0 : _g.databaseLogging) === null || _h === void 0 ? void 0 : _h.enabled);
204
+ ((_j = (_h = this.defaultConfig.logging) === null || _h === void 0 ? void 0 : _h.databaseLogging) === null || _j === void 0 ? void 0 : _j.enabled);
182
205
  const records = retryRecorder.getRecords();
183
206
  this.loggingService.logRequestError(error, startTime, requestId, (records === null || records === void 0 ? void 0 : records.length) ? Math.max(...records.map((r) => r.attempt)) + 1 : 1, loggingOptions, databaseLogging, records, circuitBreakerState, effectiveDecoratorContext, effectiveClientName, callingContext);
184
207
  }
185
208
  // 更新统计信息
186
- const errorMethod = (_j = error.config) === null || _j === void 0 ? void 0 : _j.method;
187
- const errorStatus = (_k = error.response) === null || _k === void 0 ? void 0 : _k.status;
209
+ const errorMethod = (_k = error.config) === null || _k === void 0 ? void 0 : _k.method;
210
+ const errorStatus = (_l = error.response) === null || _l === void 0 ? void 0 : _l.status;
188
211
  this.updateRequestStats(false, Date.now() - startTime, errorMethod, errorStatus);
189
212
  throw error;
190
213
  }
@@ -478,9 +501,14 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
478
501
  httpsAgent: httpsAgent,
479
502
  };
480
503
  // 透传所有其他 axiosConfig 配置(排除已特殊处理的字段)
504
+ // 只在值有效时才覆盖
481
505
  if (this.defaultConfig.axiosConfig) {
482
506
  const _d = this.defaultConfig.axiosConfig, { ssl, httpsAgent: _ } = _d, restAxiosConfig = __rest(_d, ["ssl", "httpsAgent"]);
483
- Object.assign(axiosCreateConfig, restAxiosConfig);
507
+ for (const key in restAxiosConfig) {
508
+ if (restAxiosConfig[key] !== undefined) {
509
+ axiosCreateConfig[key] = restAxiosConfig[key];
510
+ }
511
+ }
484
512
  }
485
513
  const instance = require('axios').create(axiosCreateConfig);
486
514
  // 配置axios-retry
@@ -587,12 +615,20 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
587
615
  }
588
616
  /**
589
617
  * 应用装饰器配置
618
+ * 只在装饰器配置值有效时才覆盖请求配置
590
619
  */
591
620
  applyDecoratorConfig(config, decoratorContext) {
592
621
  if (!decoratorContext)
593
622
  return Object.assign({}, config);
594
623
  const httpClientConfig = decorators_1.HttpDecoratorUtils.getHttpClientOptions(decoratorContext.target);
595
- return Object.assign(Object.assign({}, config), httpClientConfig);
624
+ // 只在值有效时才合并
625
+ const result = Object.assign({}, config);
626
+ for (const key in httpClientConfig) {
627
+ if (httpClientConfig[key] !== undefined) {
628
+ result[key] = httpClientConfig[key];
629
+ }
630
+ }
631
+ return result;
596
632
  }
597
633
  // Note: applyDecoratorConfig is not currently being called in executeWithFeatures
598
634
  // The decorator config is already being applied via effectiveDecoratorContext
@@ -663,10 +699,15 @@ let HttpClientService = HttpClientService_1 = class HttpClientService {
663
699
  }
664
700
  /**
665
701
  * 深度合并对象
702
+ * 只在源值有效(非 undefined)时才覆盖目标值
666
703
  */
667
704
  deepMerge(target, source) {
668
705
  const result = Object.assign({}, target);
669
706
  for (const key in source) {
707
+ // 跳过 undefined 值,不覆盖目标值
708
+ if (source[key] === undefined) {
709
+ continue;
710
+ }
670
711
  if (source[key] &&
671
712
  typeof source[key] === 'object' &&
672
713
  !Array.isArray(source[key])) {
@@ -154,7 +154,6 @@ let HttpLoggingService = HttpLoggingService_1 = class HttpLoggingService {
154
154
  ? callingContext
155
155
  : this.extractCallInfo(decoratorContext, response.config);
156
156
  this.saveToDatabase({
157
- id: requestId,
158
157
  requestId,
159
158
  userId: context.userId,
160
159
  method: ((_c = response.config.method) === null || _c === void 0 ? void 0 : _c.toUpperCase()) || 'UNKNOWN',
@@ -232,7 +231,6 @@ let HttpLoggingService = HttpLoggingService_1 = class HttpLoggingService {
232
231
  ? callingContext
233
232
  : this.extractCallInfo(decoratorContext, error.config);
234
233
  this.saveToDatabase({
235
- id: requestId,
236
234
  requestId,
237
235
  userId: context.userId,
238
236
  method: ((_d = (_c = error.config) === null || _c === void 0 ? void 0 : _c.method) === null || _d === void 0 ? void 0 : _d.toUpperCase()) || 'UNKNOWN',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nest-omni/core",
3
- "version": "4.1.3-23",
3
+ "version": "4.1.3-25",
4
4
  "description": "A comprehensive NestJS framework for building enterprise-grade applications with best practices",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",