@lark-apaas/nestjs-common 0.1.4-alpha.9 → 0.1.5

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
@@ -127,6 +127,8 @@ interface RequestContextState {
127
127
  ip?: string;
128
128
  /** x-tt-env header,用于环境标识透传(如灰度/测试环境) */
129
129
  ttEnv?: string;
130
+ /** 是否为系统账号 */
131
+ isSystemAccount?: boolean;
130
132
  [key: string]: unknown;
131
133
  }
132
134
  ```
package/dist/index.cjs CHANGED
@@ -24,6 +24,7 @@ __export(index_exports, {
24
24
  AUTO_TRACE: () => AUTO_TRACE,
25
25
  AutoTrace: () => AutoTrace,
26
26
  CommonModule: () => CommonModule,
27
+ HTTP_CLIENT_FACTORY: () => HTTP_CLIENT_FACTORY,
27
28
  OBSERVABLE_SERVICE: () => OBSERVABLE_SERVICE,
28
29
  PLATFORM_HTTP_CLIENT: () => PLATFORM_HTTP_CLIENT,
29
30
  RequestContextService: () => RequestContextService,
@@ -77,6 +78,7 @@ var import_common2 = require("@nestjs/common");
77
78
  var OBSERVABLE_SERVICE = /* @__PURE__ */ Symbol("OBSERVABLE_SERVICE");
78
79
  var PLATFORM_HTTP_CLIENT = /* @__PURE__ */ Symbol("PLATFORM_HTTP_CLIENT");
79
80
  var AUTO_TRACE = /* @__PURE__ */ Symbol("AUTO_TRACE");
81
+ var HTTP_CLIENT_FACTORY = /* @__PURE__ */ Symbol("HTTP_CLIENT_FACTORY");
80
82
 
81
83
  // src/decorators/auto-trace.decorator.ts
82
84
  var AutoTrace = /* @__PURE__ */ __name((options) => {
@@ -131,6 +133,7 @@ __name(stripBasePath, "stripBasePath");
131
133
  AUTO_TRACE,
132
134
  AutoTrace,
133
135
  CommonModule,
136
+ HTTP_CLIENT_FACTORY,
134
137
  OBSERVABLE_SERVICE,
135
138
  PLATFORM_HTTP_CLIENT,
136
139
  RequestContextService,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/services/request-context.service.ts","../src/decorators/auto-trace.decorator.ts","../src/tokens.ts","../src/module.ts","../src/utils/strip-base-path.ts"],"sourcesContent":["export { RequestContextService, type RequestContextState } from './services/request-context.service';\nexport { AutoTrace } from './decorators/auto-trace.decorator';\nexport { CommonModule } from './module';\nexport { stripBasePath } from './utils/strip-base-path';\nexport * from './tokens';\nexport * from './type';\n","import { Injectable } from '@nestjs/common';\nimport { AsyncLocalStorage } from 'async_hooks';\n\nexport interface RequestContextState {\n requestId?: string;\n path?: string;\n method?: string;\n userId?: string;\n appId?: string;\n tenantId?: string;\n ip?: string;\n requestRootSpan?: unknown;\n /** x-tt-env header,用于环境标识透传(如灰度/测试环境) */\n ttEnv?: string;\n /** 请求来源页面路由 (X-Page-Route header) */\n pageRoute?: string;\n [key: string]: unknown;\n}\n\n@Injectable()\nexport class RequestContextService {\n private readonly storage = new AsyncLocalStorage<RequestContextState>();\n\n run<T>(context: RequestContextState, callback: () => T): T {\n const store = { ...context };\n return this.storage.run(store, callback);\n }\n\n setContext(partial: Partial<RequestContextState>): void {\n const store = this.storage.getStore();\n if (!store) {\n return;\n }\n Object.assign(store, partial);\n }\n\n getContext(): RequestContextState | undefined {\n return this.storage.getStore();\n }\n\n get<K extends keyof RequestContextState>(key: K): RequestContextState[K] | undefined {\n return this.storage.getStore()?.[key];\n }\n}\n","import { SetMetadata } from '@nestjs/common';\nimport { AUTO_TRACE } from '../tokens';\n\nexport const AutoTrace = (options: string) => {\n return SetMetadata(AUTO_TRACE, options);\n};","/**\n * DI token for ObservableService\n *\n * 注入此 token 获得 ObservableService 实例\n */\nexport const OBSERVABLE_SERVICE = Symbol('OBSERVABLE_SERVICE');\n\n/**\n * DI Token for Platform HttpClient\n *\n * 这些 token 供 PlatformModule 和内部依赖(如 AuthNPaasModule)共享使用,通过创建独立的 nestjs-common 包,避免循环依赖\n *\n * @example\n * ```typescript\n * // nestjs-core 中注册 provider\n * import { PLATFORM_HTTP_CLIENT } from '@lark-apaas/nestjs-common';\n *\n * {\n * provide: PLATFORM_HTTP_CLIENT,\n * useFactory: (svc: PlatformHttpClientService) => svc.instance,\n * inject: [PlatformHttpClientService],\n * }\n * ```\n *\n * @example\n * ```typescript\n * // nestjs-authnpaas 中注入使用\n * import { Injectable, Inject } from '@nestjs/common';\n * import { PLATFORM_HTTP_CLIENT, type SafeHttpClient } from '@lark-apaas/nestjs-common';\n *\n * @Injectable()\n * export class AuthNService {\n * constructor(\n * @Inject(PLATFORM_HTTP_CLIENT) private http: SafeHttpClient\n * ) {}\n * }\n * ```\n */\n\n/**\n * DI token for PlatformHttpClient instance\n *\n * 注入此 token 获得 SafeHttpClient 实例(不暴露 interceptors)\n */\nexport const PLATFORM_HTTP_CLIENT = Symbol('PLATFORM_HTTP_CLIENT');\n\n/**\n * DI token for AutoTraceInterceptor\n *\n * 此 token 用于元数据存取 AUTO_TRACE 装饰器的参数\n */\nexport const AUTO_TRACE = Symbol('AUTO_TRACE');\n","import { Global, Module } from '@nestjs/common';\nimport { RequestContextService } from './services/request-context.service';\n\n@Global()\n@Module({\n providers: [RequestContextService],\n exports: [RequestContextService],\n})\nexport class CommonModule {}\n","/**\n * 从完整路径中去除 basePath 前缀,获取相对页面路由\n *\n * @param fullPath - 完整路径,如 \"/af/p/app_xxx/dashboard\"\n * @param basePath - 基础路径前缀,如 \"/af/p/app_xxx\",通常来自 CLIENT_BASE_PATH 环境变量\n * @returns 去除前缀后的相对路径,如 \"/dashboard\";如果无法处理则返回原值\n *\n * @example\n * stripBasePath('/af/p/app_xxx/dashboard', '/af/p/app_xxx') // => '/dashboard'\n * stripBasePath('/af/p/app_xxx', '/af/p/app_xxx') // => '/'\n * stripBasePath('/other/path', '/af/p/app_xxx') // => '/other/path'\n * stripBasePath(undefined, '/af/p/app_xxx') // => undefined\n * stripBasePath('/dashboard', '') // => '/dashboard'\n */\nexport function stripBasePath(\n fullPath: string | undefined | null,\n basePath: string | undefined | null,\n): string | undefined | null {\n // 如果 fullPath 为空,直接返回\n if (!fullPath) {\n return fullPath;\n }\n\n // 如果 basePath 为空,直接返回原路径\n if (!basePath) {\n return fullPath;\n }\n\n // 如果 fullPath 以 basePath 开头,去除前缀\n // 需要确保是完整的路径段匹配,避免部分匹配(如 /app_xxx 匹配到 /app_xxx_extended)\n if (fullPath.startsWith(basePath)) {\n const stripped = fullPath.slice(basePath.length);\n // 确保 stripped 为空(完全匹配)或以 '/' 开头(路径段边界)\n if (stripped === '' || stripped.startsWith('/')) {\n return stripped || '/';\n }\n }\n\n // 不匹配则返回原路径\n return fullPath;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;;ACAA,oBAA2B;AAC3B,yBAAkC;;;;;;;;AAmB3B,IAAMA,wBAAN,MAAMA;SAAAA;;;EACMC,UAAU,IAAIC,qCAAAA;EAE/BC,IAAOC,SAA8BC,UAAsB;AACzD,UAAMC,QAAQ;MAAE,GAAGF;IAAQ;AAC3B,WAAO,KAAKH,QAAQE,IAAIG,OAAOD,QAAAA;EACjC;EAEAE,WAAWC,SAA6C;AACtD,UAAMF,QAAQ,KAAKL,QAAQQ,SAAQ;AACnC,QAAI,CAACH,OAAO;AACV;IACF;AACAI,WAAOC,OAAOL,OAAOE,OAAAA;EACvB;EAEAI,aAA8C;AAC5C,WAAO,KAAKX,QAAQQ,SAAQ;EAC9B;EAEAI,IAAyCC,KAA4C;AACnF,WAAO,KAAKb,QAAQQ,SAAQ,IAAKK,GAAAA;EACnC;AACF;;;;;;AC3CA,IAAAC,iBAA4B;;;ACKrB,IAAMC,qBAAqBC,uBAAO,oBAAA;AAuClC,IAAMC,uBAAuBD,uBAAO,sBAAA;AAOpC,IAAME,aAAaF,uBAAO,YAAA;;;ADhD1B,IAAMG,YAAY,wBAACC,YAAAA;AACxB,aAAOC,4BAAYC,YAAYF,OAAAA;AACjC,GAFyB;;;AEHzB,IAAAG,iBAA+B;;;;;;;;AAQxB,IAAMC,eAAN,MAAMA;SAAAA;;;AAAc;;;;IAHzBC,WAAW;MAACC;;IACZC,SAAS;MAACD;;;;;;ACQL,SAASE,cACdC,UACAC,UAAmC;AAGnC,MAAI,CAACD,UAAU;AACb,WAAOA;EACT;AAGA,MAAI,CAACC,UAAU;AACb,WAAOD;EACT;AAIA,MAAIA,SAASE,WAAWD,QAAAA,GAAW;AACjC,UAAME,WAAWH,SAASI,MAAMH,SAASI,MAAM;AAE/C,QAAIF,aAAa,MAAMA,SAASD,WAAW,GAAA,GAAM;AAC/C,aAAOC,YAAY;IACrB;EACF;AAGA,SAAOH;AACT;AA1BgBD;","names":["RequestContextService","storage","AsyncLocalStorage","run","context","callback","store","setContext","partial","getStore","Object","assign","getContext","get","key","import_common","OBSERVABLE_SERVICE","Symbol","PLATFORM_HTTP_CLIENT","AUTO_TRACE","AutoTrace","options","SetMetadata","AUTO_TRACE","import_common","CommonModule","providers","RequestContextService","exports","stripBasePath","fullPath","basePath","startsWith","stripped","slice","length"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/services/request-context.service.ts","../src/decorators/auto-trace.decorator.ts","../src/tokens.ts","../src/module.ts","../src/utils/strip-base-path.ts"],"sourcesContent":["export { RequestContextService, type RequestContextState } from './services/request-context.service';\nexport { AutoTrace } from './decorators/auto-trace.decorator';\nexport { CommonModule } from './module';\nexport { stripBasePath } from './utils/strip-base-path';\nexport * from './tokens';\nexport * from './type';\n","import { Injectable } from '@nestjs/common';\nimport { AsyncLocalStorage } from 'async_hooks';\n\nexport interface RequestContextState {\n requestId?: string;\n path?: string;\n method?: string;\n userId?: string;\n appId?: string;\n tenantId?: string;\n ip?: string;\n requestRootSpan?: unknown;\n /** x-tt-env header,用于环境标识透传(如灰度/测试环境) */\n ttEnv?: string;\n /** 请求来源页面路由 (X-Page-Route header) */\n pageRoute?: string;\n /** 是否为系统账号 */\n isSystemAccount?: boolean;\n [key: string]: unknown;\n}\n\n@Injectable()\nexport class RequestContextService {\n private readonly storage = new AsyncLocalStorage<RequestContextState>();\n\n run<T>(context: RequestContextState, callback: () => T): T {\n const store = { ...context };\n return this.storage.run(store, callback);\n }\n\n setContext(partial: Partial<RequestContextState>): void {\n const store = this.storage.getStore();\n if (!store) {\n return;\n }\n Object.assign(store, partial);\n }\n\n getContext(): RequestContextState | undefined {\n return this.storage.getStore();\n }\n\n get<K extends keyof RequestContextState>(key: K): RequestContextState[K] | undefined {\n return this.storage.getStore()?.[key];\n }\n}\n","import { SetMetadata } from '@nestjs/common';\nimport { AUTO_TRACE } from '../tokens';\n\nexport const AutoTrace = (options: string) => {\n return SetMetadata(AUTO_TRACE, options);\n};","/**\n * DI token for ObservableService\n *\n * 注入此 token 获得 ObservableService 实例\n */\nexport const OBSERVABLE_SERVICE = Symbol('OBSERVABLE_SERVICE');\n\n/**\n * DI Token for Platform HttpClient\n *\n * 这些 token 供 PlatformModule 和内部依赖(如 AuthNPaasModule)共享使用,通过创建独立的 nestjs-common 包,避免循环依赖\n *\n * @example\n * ```typescript\n * // nestjs-core 中注册 provider\n * import { PLATFORM_HTTP_CLIENT } from '@lark-apaas/nestjs-common';\n *\n * {\n * provide: PLATFORM_HTTP_CLIENT,\n * useFactory: (svc: PlatformHttpClientService) => svc.instance,\n * inject: [PlatformHttpClientService],\n * }\n * ```\n *\n * @example\n * ```typescript\n * // nestjs-authnpaas 中注入使用\n * import { Injectable, Inject } from '@nestjs/common';\n * import { PLATFORM_HTTP_CLIENT, type SafeHttpClient } from '@lark-apaas/nestjs-common';\n *\n * @Injectable()\n * export class AuthNService {\n * constructor(\n * @Inject(PLATFORM_HTTP_CLIENT) private http: SafeHttpClient\n * ) {}\n * }\n * ```\n */\n\n/**\n * DI token for PlatformHttpClient instance\n *\n * 注入此 token 获得 SafeHttpClient 实例(不暴露 interceptors)\n */\nexport const PLATFORM_HTTP_CLIENT = Symbol('PLATFORM_HTTP_CLIENT');\n\n/**\n * DI token for AutoTraceInterceptor\n *\n * 此 token 用于元数据存取 AUTO_TRACE 装饰器的参数\n */\nexport const AUTO_TRACE = Symbol('AUTO_TRACE');\n\n/**\n * DI token for HttpClientFactory\n *\n * 用于创建独立的 HttpClient 实例,支持自定义拦截器\n *\n * @example\n * ```typescript\n * // 注入并使用\n * @Injectable()\n * export class PluginService {\n * constructor(\n * @Inject(HTTP_CLIENT_FACTORY) private factory: HttpClientFactory\n * ) {}\n *\n * createClientForPlugin(pluginKey: string) {\n * const client = this.factory.create();\n * client.interceptors.request.use((config) => {\n * config.headers['x-plugin-key'] = pluginKey;\n * return config;\n * });\n * return client;\n * }\n * }\n * ```\n */\nexport const HTTP_CLIENT_FACTORY = Symbol('HTTP_CLIENT_FACTORY');\n","import { Global, Module } from '@nestjs/common';\nimport { RequestContextService } from './services/request-context.service';\n\n@Global()\n@Module({\n providers: [RequestContextService],\n exports: [RequestContextService],\n})\nexport class CommonModule {}\n","/**\n * 从完整路径中去除 basePath 前缀,获取相对页面路由\n *\n * @param fullPath - 完整路径,如 \"/af/p/app_xxx/dashboard\"\n * @param basePath - 基础路径前缀,如 \"/af/p/app_xxx\",通常来自 CLIENT_BASE_PATH 环境变量\n * @returns 去除前缀后的相对路径,如 \"/dashboard\";如果无法处理则返回原值\n *\n * @example\n * stripBasePath('/af/p/app_xxx/dashboard', '/af/p/app_xxx') // => '/dashboard'\n * stripBasePath('/af/p/app_xxx', '/af/p/app_xxx') // => '/'\n * stripBasePath('/other/path', '/af/p/app_xxx') // => '/other/path'\n * stripBasePath(undefined, '/af/p/app_xxx') // => undefined\n * stripBasePath('/dashboard', '') // => '/dashboard'\n */\nexport function stripBasePath(\n fullPath: string | undefined | null,\n basePath: string | undefined | null,\n): string | undefined | null {\n // 如果 fullPath 为空,直接返回\n if (!fullPath) {\n return fullPath;\n }\n\n // 如果 basePath 为空,直接返回原路径\n if (!basePath) {\n return fullPath;\n }\n\n // 如果 fullPath 以 basePath 开头,去除前缀\n // 需要确保是完整的路径段匹配,避免部分匹配(如 /app_xxx 匹配到 /app_xxx_extended)\n if (fullPath.startsWith(basePath)) {\n const stripped = fullPath.slice(basePath.length);\n // 确保 stripped 为空(完全匹配)或以 '/' 开头(路径段边界)\n if (stripped === '' || stripped.startsWith('/')) {\n return stripped || '/';\n }\n }\n\n // 不匹配则返回原路径\n return fullPath;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;;;ACAA,oBAA2B;AAC3B,yBAAkC;;;;;;;;AAqB3B,IAAMA,wBAAN,MAAMA;SAAAA;;;EACMC,UAAU,IAAIC,qCAAAA;EAE/BC,IAAOC,SAA8BC,UAAsB;AACzD,UAAMC,QAAQ;MAAE,GAAGF;IAAQ;AAC3B,WAAO,KAAKH,QAAQE,IAAIG,OAAOD,QAAAA;EACjC;EAEAE,WAAWC,SAA6C;AACtD,UAAMF,QAAQ,KAAKL,QAAQQ,SAAQ;AACnC,QAAI,CAACH,OAAO;AACV;IACF;AACAI,WAAOC,OAAOL,OAAOE,OAAAA;EACvB;EAEAI,aAA8C;AAC5C,WAAO,KAAKX,QAAQQ,SAAQ;EAC9B;EAEAI,IAAyCC,KAA4C;AACnF,WAAO,KAAKb,QAAQQ,SAAQ,IAAKK,GAAAA;EACnC;AACF;;;;;;AC7CA,IAAAC,iBAA4B;;;ACKrB,IAAMC,qBAAqBC,uBAAO,oBAAA;AAuClC,IAAMC,uBAAuBD,uBAAO,sBAAA;AAOpC,IAAME,aAAaF,uBAAO,YAAA;AA2B1B,IAAMG,sBAAsBH,uBAAO,qBAAA;;;AD3EnC,IAAMI,YAAY,wBAACC,YAAAA;AACxB,aAAOC,4BAAYC,YAAYF,OAAAA;AACjC,GAFyB;;;AEHzB,IAAAG,iBAA+B;;;;;;;;AAQxB,IAAMC,eAAN,MAAMA;SAAAA;;;AAAc;;;;IAHzBC,WAAW;MAACC;;IACZC,SAAS;MAACD;;;;;;ACQL,SAASE,cACdC,UACAC,UAAmC;AAGnC,MAAI,CAACD,UAAU;AACb,WAAOA;EACT;AAGA,MAAI,CAACC,UAAU;AACb,WAAOD;EACT;AAIA,MAAIA,SAASE,WAAWD,QAAAA,GAAW;AACjC,UAAME,WAAWH,SAASI,MAAMH,SAASI,MAAM;AAE/C,QAAIF,aAAa,MAAMA,SAASD,WAAW,GAAA,GAAM;AAC/C,aAAOC,YAAY;IACrB;EACF;AAGA,SAAOH;AACT;AA1BgBD;","names":["RequestContextService","storage","AsyncLocalStorage","run","context","callback","store","setContext","partial","getStore","Object","assign","getContext","get","key","import_common","OBSERVABLE_SERVICE","Symbol","PLATFORM_HTTP_CLIENT","AUTO_TRACE","HTTP_CLIENT_FACTORY","AutoTrace","options","SetMetadata","AUTO_TRACE","import_common","CommonModule","providers","RequestContextService","exports","stripBasePath","fullPath","basePath","startsWith","stripped","slice","length"]}
package/dist/index.d.cts CHANGED
@@ -13,6 +13,8 @@ interface RequestContextState {
13
13
  ttEnv?: string;
14
14
  /** 请求来源页面路由 (X-Page-Route header) */
15
15
  pageRoute?: string;
16
+ /** 是否为系统账号 */
17
+ isSystemAccount?: boolean;
16
18
  [key: string]: unknown;
17
19
  }
18
20
  declare class RequestContextService {
@@ -72,6 +74,32 @@ declare const PLATFORM_HTTP_CLIENT: unique symbol;
72
74
  * 此 token 用于元数据存取 AUTO_TRACE 装饰器的参数
73
75
  */
74
76
  declare const AUTO_TRACE: unique symbol;
77
+ /**
78
+ * DI token for HttpClientFactory
79
+ *
80
+ * 用于创建独立的 HttpClient 实例,支持自定义拦截器
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * // 注入并使用
85
+ * @Injectable()
86
+ * export class PluginService {
87
+ * constructor(
88
+ * @Inject(HTTP_CLIENT_FACTORY) private factory: HttpClientFactory
89
+ * ) {}
90
+ *
91
+ * createClientForPlugin(pluginKey: string) {
92
+ * const client = this.factory.create();
93
+ * client.interceptors.request.use((config) => {
94
+ * config.headers['x-plugin-key'] = pluginKey;
95
+ * return config;
96
+ * });
97
+ * return client;
98
+ * }
99
+ * }
100
+ * ```
101
+ */
102
+ declare const HTTP_CLIENT_FACTORY: unique symbol;
75
103
 
76
104
  declare const AutoTrace: (options: string) => _nestjs_common.CustomDecorator<typeof AUTO_TRACE>;
77
105
 
@@ -171,5 +199,51 @@ interface PlatformHttpClient {
171
199
  */
172
200
  delete(url: string, config?: any): Promise<Response>;
173
201
  }
202
+ /**
203
+ * 带拦截器的 HttpClient 接口
204
+ *
205
+ * 继承 PlatformHttpClient,额外暴露 interceptors 属性
206
+ * 用于需要自定义拦截器的高级场景
207
+ */
208
+ interface HttpClientWithInterceptors extends PlatformHttpClient {
209
+ interceptors: {
210
+ request: {
211
+ use(onFulfilled: (config: any) => any, onRejected?: (error: any) => any): number;
212
+ };
213
+ response: {
214
+ use(onFulfilled: (response: any) => any, onRejected?: (error: any) => any): number;
215
+ };
216
+ };
217
+ }
218
+ /**
219
+ * HttpClient 工厂接口
220
+ *
221
+ * 用于创建独立的 HttpClient 实例,支持自定义拦截器
222
+ *
223
+ * @example
224
+ * ```typescript
225
+ * @Injectable()
226
+ * export class MyService {
227
+ * constructor(
228
+ * @Inject(HTTP_CLIENT_FACTORY) private factory: HttpClientFactory
229
+ * ) {
230
+ * const client = this.factory.create();
231
+ * client.interceptors.request.use((config) => {
232
+ * config.headers['X-Custom'] = 'value';
233
+ * return config;
234
+ * });
235
+ * }
236
+ * }
237
+ * ```
238
+ */
239
+ interface HttpClientFactory {
240
+ /**
241
+ * 创建新的独立 HttpClient 实例
242
+ *
243
+ * @param options - 可选配置
244
+ * @returns 带拦截器的 HttpClient 实例
245
+ */
246
+ create(options?: unknown): HttpClientWithInterceptors;
247
+ }
174
248
 
175
- export { AUTO_TRACE, AutoTrace, CommonModule, OBSERVABLE_SERVICE, type ObservableService, PLATFORM_HTTP_CLIENT, type PlatformHttpClient, RequestContextService, type RequestContextState, stripBasePath };
249
+ export { AUTO_TRACE, AutoTrace, CommonModule, HTTP_CLIENT_FACTORY, type HttpClientFactory, type HttpClientWithInterceptors, OBSERVABLE_SERVICE, type ObservableService, PLATFORM_HTTP_CLIENT, type PlatformHttpClient, RequestContextService, type RequestContextState, stripBasePath };
package/dist/index.d.ts CHANGED
@@ -13,6 +13,8 @@ interface RequestContextState {
13
13
  ttEnv?: string;
14
14
  /** 请求来源页面路由 (X-Page-Route header) */
15
15
  pageRoute?: string;
16
+ /** 是否为系统账号 */
17
+ isSystemAccount?: boolean;
16
18
  [key: string]: unknown;
17
19
  }
18
20
  declare class RequestContextService {
@@ -72,6 +74,32 @@ declare const PLATFORM_HTTP_CLIENT: unique symbol;
72
74
  * 此 token 用于元数据存取 AUTO_TRACE 装饰器的参数
73
75
  */
74
76
  declare const AUTO_TRACE: unique symbol;
77
+ /**
78
+ * DI token for HttpClientFactory
79
+ *
80
+ * 用于创建独立的 HttpClient 实例,支持自定义拦截器
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * // 注入并使用
85
+ * @Injectable()
86
+ * export class PluginService {
87
+ * constructor(
88
+ * @Inject(HTTP_CLIENT_FACTORY) private factory: HttpClientFactory
89
+ * ) {}
90
+ *
91
+ * createClientForPlugin(pluginKey: string) {
92
+ * const client = this.factory.create();
93
+ * client.interceptors.request.use((config) => {
94
+ * config.headers['x-plugin-key'] = pluginKey;
95
+ * return config;
96
+ * });
97
+ * return client;
98
+ * }
99
+ * }
100
+ * ```
101
+ */
102
+ declare const HTTP_CLIENT_FACTORY: unique symbol;
75
103
 
76
104
  declare const AutoTrace: (options: string) => _nestjs_common.CustomDecorator<typeof AUTO_TRACE>;
77
105
 
@@ -171,5 +199,51 @@ interface PlatformHttpClient {
171
199
  */
172
200
  delete(url: string, config?: any): Promise<Response>;
173
201
  }
202
+ /**
203
+ * 带拦截器的 HttpClient 接口
204
+ *
205
+ * 继承 PlatformHttpClient,额外暴露 interceptors 属性
206
+ * 用于需要自定义拦截器的高级场景
207
+ */
208
+ interface HttpClientWithInterceptors extends PlatformHttpClient {
209
+ interceptors: {
210
+ request: {
211
+ use(onFulfilled: (config: any) => any, onRejected?: (error: any) => any): number;
212
+ };
213
+ response: {
214
+ use(onFulfilled: (response: any) => any, onRejected?: (error: any) => any): number;
215
+ };
216
+ };
217
+ }
218
+ /**
219
+ * HttpClient 工厂接口
220
+ *
221
+ * 用于创建独立的 HttpClient 实例,支持自定义拦截器
222
+ *
223
+ * @example
224
+ * ```typescript
225
+ * @Injectable()
226
+ * export class MyService {
227
+ * constructor(
228
+ * @Inject(HTTP_CLIENT_FACTORY) private factory: HttpClientFactory
229
+ * ) {
230
+ * const client = this.factory.create();
231
+ * client.interceptors.request.use((config) => {
232
+ * config.headers['X-Custom'] = 'value';
233
+ * return config;
234
+ * });
235
+ * }
236
+ * }
237
+ * ```
238
+ */
239
+ interface HttpClientFactory {
240
+ /**
241
+ * 创建新的独立 HttpClient 实例
242
+ *
243
+ * @param options - 可选配置
244
+ * @returns 带拦截器的 HttpClient 实例
245
+ */
246
+ create(options?: unknown): HttpClientWithInterceptors;
247
+ }
174
248
 
175
- export { AUTO_TRACE, AutoTrace, CommonModule, OBSERVABLE_SERVICE, type ObservableService, PLATFORM_HTTP_CLIENT, type PlatformHttpClient, RequestContextService, type RequestContextState, stripBasePath };
249
+ export { AUTO_TRACE, AutoTrace, CommonModule, HTTP_CLIENT_FACTORY, type HttpClientFactory, type HttpClientWithInterceptors, OBSERVABLE_SERVICE, type ObservableService, PLATFORM_HTTP_CLIENT, type PlatformHttpClient, RequestContextService, type RequestContextState, stripBasePath };
package/dist/index.js CHANGED
@@ -47,6 +47,7 @@ import { SetMetadata } from "@nestjs/common";
47
47
  var OBSERVABLE_SERVICE = /* @__PURE__ */ Symbol("OBSERVABLE_SERVICE");
48
48
  var PLATFORM_HTTP_CLIENT = /* @__PURE__ */ Symbol("PLATFORM_HTTP_CLIENT");
49
49
  var AUTO_TRACE = /* @__PURE__ */ Symbol("AUTO_TRACE");
50
+ var HTTP_CLIENT_FACTORY = /* @__PURE__ */ Symbol("HTTP_CLIENT_FACTORY");
50
51
 
51
52
  // src/decorators/auto-trace.decorator.ts
52
53
  var AutoTrace = /* @__PURE__ */ __name((options) => {
@@ -100,6 +101,7 @@ export {
100
101
  AUTO_TRACE,
101
102
  AutoTrace,
102
103
  CommonModule,
104
+ HTTP_CLIENT_FACTORY,
103
105
  OBSERVABLE_SERVICE,
104
106
  PLATFORM_HTTP_CLIENT,
105
107
  RequestContextService,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/services/request-context.service.ts","../src/decorators/auto-trace.decorator.ts","../src/tokens.ts","../src/module.ts","../src/utils/strip-base-path.ts"],"sourcesContent":["import { Injectable } from '@nestjs/common';\nimport { AsyncLocalStorage } from 'async_hooks';\n\nexport interface RequestContextState {\n requestId?: string;\n path?: string;\n method?: string;\n userId?: string;\n appId?: string;\n tenantId?: string;\n ip?: string;\n requestRootSpan?: unknown;\n /** x-tt-env header,用于环境标识透传(如灰度/测试环境) */\n ttEnv?: string;\n /** 请求来源页面路由 (X-Page-Route header) */\n pageRoute?: string;\n [key: string]: unknown;\n}\n\n@Injectable()\nexport class RequestContextService {\n private readonly storage = new AsyncLocalStorage<RequestContextState>();\n\n run<T>(context: RequestContextState, callback: () => T): T {\n const store = { ...context };\n return this.storage.run(store, callback);\n }\n\n setContext(partial: Partial<RequestContextState>): void {\n const store = this.storage.getStore();\n if (!store) {\n return;\n }\n Object.assign(store, partial);\n }\n\n getContext(): RequestContextState | undefined {\n return this.storage.getStore();\n }\n\n get<K extends keyof RequestContextState>(key: K): RequestContextState[K] | undefined {\n return this.storage.getStore()?.[key];\n }\n}\n","import { SetMetadata } from '@nestjs/common';\nimport { AUTO_TRACE } from '../tokens';\n\nexport const AutoTrace = (options: string) => {\n return SetMetadata(AUTO_TRACE, options);\n};","/**\n * DI token for ObservableService\n *\n * 注入此 token 获得 ObservableService 实例\n */\nexport const OBSERVABLE_SERVICE = Symbol('OBSERVABLE_SERVICE');\n\n/**\n * DI Token for Platform HttpClient\n *\n * 这些 token 供 PlatformModule 和内部依赖(如 AuthNPaasModule)共享使用,通过创建独立的 nestjs-common 包,避免循环依赖\n *\n * @example\n * ```typescript\n * // nestjs-core 中注册 provider\n * import { PLATFORM_HTTP_CLIENT } from '@lark-apaas/nestjs-common';\n *\n * {\n * provide: PLATFORM_HTTP_CLIENT,\n * useFactory: (svc: PlatformHttpClientService) => svc.instance,\n * inject: [PlatformHttpClientService],\n * }\n * ```\n *\n * @example\n * ```typescript\n * // nestjs-authnpaas 中注入使用\n * import { Injectable, Inject } from '@nestjs/common';\n * import { PLATFORM_HTTP_CLIENT, type SafeHttpClient } from '@lark-apaas/nestjs-common';\n *\n * @Injectable()\n * export class AuthNService {\n * constructor(\n * @Inject(PLATFORM_HTTP_CLIENT) private http: SafeHttpClient\n * ) {}\n * }\n * ```\n */\n\n/**\n * DI token for PlatformHttpClient instance\n *\n * 注入此 token 获得 SafeHttpClient 实例(不暴露 interceptors)\n */\nexport const PLATFORM_HTTP_CLIENT = Symbol('PLATFORM_HTTP_CLIENT');\n\n/**\n * DI token for AutoTraceInterceptor\n *\n * 此 token 用于元数据存取 AUTO_TRACE 装饰器的参数\n */\nexport const AUTO_TRACE = Symbol('AUTO_TRACE');\n","import { Global, Module } from '@nestjs/common';\nimport { RequestContextService } from './services/request-context.service';\n\n@Global()\n@Module({\n providers: [RequestContextService],\n exports: [RequestContextService],\n})\nexport class CommonModule {}\n","/**\n * 从完整路径中去除 basePath 前缀,获取相对页面路由\n *\n * @param fullPath - 完整路径,如 \"/af/p/app_xxx/dashboard\"\n * @param basePath - 基础路径前缀,如 \"/af/p/app_xxx\",通常来自 CLIENT_BASE_PATH 环境变量\n * @returns 去除前缀后的相对路径,如 \"/dashboard\";如果无法处理则返回原值\n *\n * @example\n * stripBasePath('/af/p/app_xxx/dashboard', '/af/p/app_xxx') // => '/dashboard'\n * stripBasePath('/af/p/app_xxx', '/af/p/app_xxx') // => '/'\n * stripBasePath('/other/path', '/af/p/app_xxx') // => '/other/path'\n * stripBasePath(undefined, '/af/p/app_xxx') // => undefined\n * stripBasePath('/dashboard', '') // => '/dashboard'\n */\nexport function stripBasePath(\n fullPath: string | undefined | null,\n basePath: string | undefined | null,\n): string | undefined | null {\n // 如果 fullPath 为空,直接返回\n if (!fullPath) {\n return fullPath;\n }\n\n // 如果 basePath 为空,直接返回原路径\n if (!basePath) {\n return fullPath;\n }\n\n // 如果 fullPath 以 basePath 开头,去除前缀\n // 需要确保是完整的路径段匹配,避免部分匹配(如 /app_xxx 匹配到 /app_xxx_extended)\n if (fullPath.startsWith(basePath)) {\n const stripped = fullPath.slice(basePath.length);\n // 确保 stripped 为空(完全匹配)或以 '/' 开头(路径段边界)\n if (stripped === '' || stripped.startsWith('/')) {\n return stripped || '/';\n }\n }\n\n // 不匹配则返回原路径\n return fullPath;\n}\n"],"mappings":";;;;AAAA,SAASA,kBAAkB;AAC3B,SAASC,yBAAyB;;;;;;;;AAmB3B,IAAMC,wBAAN,MAAMA;SAAAA;;;EACMC,UAAU,IAAIC,kBAAAA;EAE/BC,IAAOC,SAA8BC,UAAsB;AACzD,UAAMC,QAAQ;MAAE,GAAGF;IAAQ;AAC3B,WAAO,KAAKH,QAAQE,IAAIG,OAAOD,QAAAA;EACjC;EAEAE,WAAWC,SAA6C;AACtD,UAAMF,QAAQ,KAAKL,QAAQQ,SAAQ;AACnC,QAAI,CAACH,OAAO;AACV;IACF;AACAI,WAAOC,OAAOL,OAAOE,OAAAA;EACvB;EAEAI,aAA8C;AAC5C,WAAO,KAAKX,QAAQQ,SAAQ;EAC9B;EAEAI,IAAyCC,KAA4C;AACnF,WAAO,KAAKb,QAAQQ,SAAQ,IAAKK,GAAAA;EACnC;AACF;;;;;;AC3CA,SAASC,mBAAmB;;;ACKrB,IAAMC,qBAAqBC,uBAAO,oBAAA;AAuClC,IAAMC,uBAAuBD,uBAAO,sBAAA;AAOpC,IAAME,aAAaF,uBAAO,YAAA;;;ADhD1B,IAAMG,YAAY,wBAACC,YAAAA;AACxB,SAAOC,YAAYC,YAAYF,OAAAA;AACjC,GAFyB;;;AEHzB,SAASG,QAAQC,cAAc;;;;;;;;AAQxB,IAAMC,eAAN,MAAMA;SAAAA;;;AAAc;;;;IAHzBC,WAAW;MAACC;;IACZC,SAAS;MAACD;;;;;;ACQL,SAASE,cACdC,UACAC,UAAmC;AAGnC,MAAI,CAACD,UAAU;AACb,WAAOA;EACT;AAGA,MAAI,CAACC,UAAU;AACb,WAAOD;EACT;AAIA,MAAIA,SAASE,WAAWD,QAAAA,GAAW;AACjC,UAAME,WAAWH,SAASI,MAAMH,SAASI,MAAM;AAE/C,QAAIF,aAAa,MAAMA,SAASD,WAAW,GAAA,GAAM;AAC/C,aAAOC,YAAY;IACrB;EACF;AAGA,SAAOH;AACT;AA1BgBD;","names":["Injectable","AsyncLocalStorage","RequestContextService","storage","AsyncLocalStorage","run","context","callback","store","setContext","partial","getStore","Object","assign","getContext","get","key","SetMetadata","OBSERVABLE_SERVICE","Symbol","PLATFORM_HTTP_CLIENT","AUTO_TRACE","AutoTrace","options","SetMetadata","AUTO_TRACE","Global","Module","CommonModule","providers","RequestContextService","exports","stripBasePath","fullPath","basePath","startsWith","stripped","slice","length"]}
1
+ {"version":3,"sources":["../src/services/request-context.service.ts","../src/decorators/auto-trace.decorator.ts","../src/tokens.ts","../src/module.ts","../src/utils/strip-base-path.ts"],"sourcesContent":["import { Injectable } from '@nestjs/common';\nimport { AsyncLocalStorage } from 'async_hooks';\n\nexport interface RequestContextState {\n requestId?: string;\n path?: string;\n method?: string;\n userId?: string;\n appId?: string;\n tenantId?: string;\n ip?: string;\n requestRootSpan?: unknown;\n /** x-tt-env header,用于环境标识透传(如灰度/测试环境) */\n ttEnv?: string;\n /** 请求来源页面路由 (X-Page-Route header) */\n pageRoute?: string;\n /** 是否为系统账号 */\n isSystemAccount?: boolean;\n [key: string]: unknown;\n}\n\n@Injectable()\nexport class RequestContextService {\n private readonly storage = new AsyncLocalStorage<RequestContextState>();\n\n run<T>(context: RequestContextState, callback: () => T): T {\n const store = { ...context };\n return this.storage.run(store, callback);\n }\n\n setContext(partial: Partial<RequestContextState>): void {\n const store = this.storage.getStore();\n if (!store) {\n return;\n }\n Object.assign(store, partial);\n }\n\n getContext(): RequestContextState | undefined {\n return this.storage.getStore();\n }\n\n get<K extends keyof RequestContextState>(key: K): RequestContextState[K] | undefined {\n return this.storage.getStore()?.[key];\n }\n}\n","import { SetMetadata } from '@nestjs/common';\nimport { AUTO_TRACE } from '../tokens';\n\nexport const AutoTrace = (options: string) => {\n return SetMetadata(AUTO_TRACE, options);\n};","/**\n * DI token for ObservableService\n *\n * 注入此 token 获得 ObservableService 实例\n */\nexport const OBSERVABLE_SERVICE = Symbol('OBSERVABLE_SERVICE');\n\n/**\n * DI Token for Platform HttpClient\n *\n * 这些 token 供 PlatformModule 和内部依赖(如 AuthNPaasModule)共享使用,通过创建独立的 nestjs-common 包,避免循环依赖\n *\n * @example\n * ```typescript\n * // nestjs-core 中注册 provider\n * import { PLATFORM_HTTP_CLIENT } from '@lark-apaas/nestjs-common';\n *\n * {\n * provide: PLATFORM_HTTP_CLIENT,\n * useFactory: (svc: PlatformHttpClientService) => svc.instance,\n * inject: [PlatformHttpClientService],\n * }\n * ```\n *\n * @example\n * ```typescript\n * // nestjs-authnpaas 中注入使用\n * import { Injectable, Inject } from '@nestjs/common';\n * import { PLATFORM_HTTP_CLIENT, type SafeHttpClient } from '@lark-apaas/nestjs-common';\n *\n * @Injectable()\n * export class AuthNService {\n * constructor(\n * @Inject(PLATFORM_HTTP_CLIENT) private http: SafeHttpClient\n * ) {}\n * }\n * ```\n */\n\n/**\n * DI token for PlatformHttpClient instance\n *\n * 注入此 token 获得 SafeHttpClient 实例(不暴露 interceptors)\n */\nexport const PLATFORM_HTTP_CLIENT = Symbol('PLATFORM_HTTP_CLIENT');\n\n/**\n * DI token for AutoTraceInterceptor\n *\n * 此 token 用于元数据存取 AUTO_TRACE 装饰器的参数\n */\nexport const AUTO_TRACE = Symbol('AUTO_TRACE');\n\n/**\n * DI token for HttpClientFactory\n *\n * 用于创建独立的 HttpClient 实例,支持自定义拦截器\n *\n * @example\n * ```typescript\n * // 注入并使用\n * @Injectable()\n * export class PluginService {\n * constructor(\n * @Inject(HTTP_CLIENT_FACTORY) private factory: HttpClientFactory\n * ) {}\n *\n * createClientForPlugin(pluginKey: string) {\n * const client = this.factory.create();\n * client.interceptors.request.use((config) => {\n * config.headers['x-plugin-key'] = pluginKey;\n * return config;\n * });\n * return client;\n * }\n * }\n * ```\n */\nexport const HTTP_CLIENT_FACTORY = Symbol('HTTP_CLIENT_FACTORY');\n","import { Global, Module } from '@nestjs/common';\nimport { RequestContextService } from './services/request-context.service';\n\n@Global()\n@Module({\n providers: [RequestContextService],\n exports: [RequestContextService],\n})\nexport class CommonModule {}\n","/**\n * 从完整路径中去除 basePath 前缀,获取相对页面路由\n *\n * @param fullPath - 完整路径,如 \"/af/p/app_xxx/dashboard\"\n * @param basePath - 基础路径前缀,如 \"/af/p/app_xxx\",通常来自 CLIENT_BASE_PATH 环境变量\n * @returns 去除前缀后的相对路径,如 \"/dashboard\";如果无法处理则返回原值\n *\n * @example\n * stripBasePath('/af/p/app_xxx/dashboard', '/af/p/app_xxx') // => '/dashboard'\n * stripBasePath('/af/p/app_xxx', '/af/p/app_xxx') // => '/'\n * stripBasePath('/other/path', '/af/p/app_xxx') // => '/other/path'\n * stripBasePath(undefined, '/af/p/app_xxx') // => undefined\n * stripBasePath('/dashboard', '') // => '/dashboard'\n */\nexport function stripBasePath(\n fullPath: string | undefined | null,\n basePath: string | undefined | null,\n): string | undefined | null {\n // 如果 fullPath 为空,直接返回\n if (!fullPath) {\n return fullPath;\n }\n\n // 如果 basePath 为空,直接返回原路径\n if (!basePath) {\n return fullPath;\n }\n\n // 如果 fullPath 以 basePath 开头,去除前缀\n // 需要确保是完整的路径段匹配,避免部分匹配(如 /app_xxx 匹配到 /app_xxx_extended)\n if (fullPath.startsWith(basePath)) {\n const stripped = fullPath.slice(basePath.length);\n // 确保 stripped 为空(完全匹配)或以 '/' 开头(路径段边界)\n if (stripped === '' || stripped.startsWith('/')) {\n return stripped || '/';\n }\n }\n\n // 不匹配则返回原路径\n return fullPath;\n}\n"],"mappings":";;;;AAAA,SAASA,kBAAkB;AAC3B,SAASC,yBAAyB;;;;;;;;AAqB3B,IAAMC,wBAAN,MAAMA;SAAAA;;;EACMC,UAAU,IAAIC,kBAAAA;EAE/BC,IAAOC,SAA8BC,UAAsB;AACzD,UAAMC,QAAQ;MAAE,GAAGF;IAAQ;AAC3B,WAAO,KAAKH,QAAQE,IAAIG,OAAOD,QAAAA;EACjC;EAEAE,WAAWC,SAA6C;AACtD,UAAMF,QAAQ,KAAKL,QAAQQ,SAAQ;AACnC,QAAI,CAACH,OAAO;AACV;IACF;AACAI,WAAOC,OAAOL,OAAOE,OAAAA;EACvB;EAEAI,aAA8C;AAC5C,WAAO,KAAKX,QAAQQ,SAAQ;EAC9B;EAEAI,IAAyCC,KAA4C;AACnF,WAAO,KAAKb,QAAQQ,SAAQ,IAAKK,GAAAA;EACnC;AACF;;;;;;AC7CA,SAASC,mBAAmB;;;ACKrB,IAAMC,qBAAqBC,uBAAO,oBAAA;AAuClC,IAAMC,uBAAuBD,uBAAO,sBAAA;AAOpC,IAAME,aAAaF,uBAAO,YAAA;AA2B1B,IAAMG,sBAAsBH,uBAAO,qBAAA;;;AD3EnC,IAAMI,YAAY,wBAACC,YAAAA;AACxB,SAAOC,YAAYC,YAAYF,OAAAA;AACjC,GAFyB;;;AEHzB,SAASG,QAAQC,cAAc;;;;;;;;AAQxB,IAAMC,eAAN,MAAMA;SAAAA;;;AAAc;;;;IAHzBC,WAAW;MAACC;;IACZC,SAAS;MAACD;;;;;;ACQL,SAASE,cACdC,UACAC,UAAmC;AAGnC,MAAI,CAACD,UAAU;AACb,WAAOA;EACT;AAGA,MAAI,CAACC,UAAU;AACb,WAAOD;EACT;AAIA,MAAIA,SAASE,WAAWD,QAAAA,GAAW;AACjC,UAAME,WAAWH,SAASI,MAAMH,SAASI,MAAM;AAE/C,QAAIF,aAAa,MAAMA,SAASD,WAAW,GAAA,GAAM;AAC/C,aAAOC,YAAY;IACrB;EACF;AAGA,SAAOH;AACT;AA1BgBD;","names":["Injectable","AsyncLocalStorage","RequestContextService","storage","AsyncLocalStorage","run","context","callback","store","setContext","partial","getStore","Object","assign","getContext","get","key","SetMetadata","OBSERVABLE_SERVICE","Symbol","PLATFORM_HTTP_CLIENT","AUTO_TRACE","HTTP_CLIENT_FACTORY","AutoTrace","options","SetMetadata","AUTO_TRACE","Global","Module","CommonModule","providers","RequestContextService","exports","stripBasePath","fullPath","basePath","startsWith","stripped","slice","length"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/nestjs-common",
3
- "version": "0.1.4-alpha.9",
3
+ "version": "0.1.5",
4
4
  "description": "Common NestJS utilities",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",