@lark-apaas/nestjs-common 0.1.7 → 0.1.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.
package/dist/index.cjs CHANGED
@@ -28,6 +28,7 @@ __export(index_exports, {
28
28
  OBSERVABLE_SERVICE: () => OBSERVABLE_SERVICE,
29
29
  PLATFORM_HTTP_CLIENT: () => PLATFORM_HTTP_CLIENT,
30
30
  RequestContextService: () => RequestContextService,
31
+ createTimer: () => createTimer,
31
32
  stripBasePath: () => stripBasePath
32
33
  });
33
34
  module.exports = __toCommonJS(index_exports);
@@ -128,6 +129,16 @@ function stripBasePath(fullPath, basePath) {
128
129
  return fullPath;
129
130
  }
130
131
  __name(stripBasePath, "stripBasePath");
132
+
133
+ // src/utils/create-timer.ts
134
+ function createTimer() {
135
+ const start = process.hrtime();
136
+ return () => {
137
+ const diff = process.hrtime(start);
138
+ return Math.round((diff[0] * 1e3 + diff[1] / 1e6) * 100) / 100;
139
+ };
140
+ }
141
+ __name(createTimer, "createTimer");
131
142
  // Annotate the CommonJS export names for ESM import in node:
132
143
  0 && (module.exports = {
133
144
  AUTO_TRACE,
@@ -137,6 +148,7 @@ __name(stripBasePath, "stripBasePath");
137
148
  OBSERVABLE_SERVICE,
138
149
  PLATFORM_HTTP_CLIENT,
139
150
  RequestContextService,
151
+ createTimer,
140
152
  stripBasePath
141
153
  });
142
154
  //# sourceMappingURL=index.cjs.map
@@ -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 /** 是否为系统账号 */\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"]}
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","../src/utils/create-timer.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 { createTimer } from './utils/create-timer';\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 /** 前端参数化页面路由 (Rpc-Persist-Apaas-Observability-Referer-Path) */\n refererPath?: string;\n /** 前端参数化 API 标识 (Rpc-Persist-Apaas-Observability-Api) */\n observabilityApi?: 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","/**\n * 创建高精度计时器\n * 使用 process.hrtime 正确计算秒 + 纳秒,支持亚毫秒测量\n * 返回一个函数,调用后返回经过的毫秒数(保留两位小数)\n */\nexport function createTimer(): () => number {\n const start = process.hrtime();\n return () => {\n const diff = process.hrtime(start);\n return Math.round((diff[0] * 1000 + diff[1] / 1e6) * 100) / 100;\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;;;;ACAA,oBAA2B;AAC3B,yBAAkC;;;;;;;;AAyB3B,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;;;;;;ACjDA,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;;;ACTT,SAASO,cAAAA;AACd,QAAMC,QAAQC,QAAQC,OAAM;AAC5B,SAAO,MAAA;AACL,UAAMC,OAAOF,QAAQC,OAAOF,KAAAA;AAC5B,WAAOI,KAAKC,OAAOF,KAAK,CAAA,IAAK,MAAOA,KAAK,CAAA,IAAK,OAAO,GAAA,IAAO;EAC9D;AACF;AANgBJ;","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","createTimer","start","process","hrtime","diff","Math","round"]}
package/dist/index.d.cts CHANGED
@@ -13,6 +13,10 @@ interface RequestContextState {
13
13
  ttEnv?: string;
14
14
  /** 请求来源页面路由 (X-Page-Route header) */
15
15
  pageRoute?: string;
16
+ /** 前端参数化页面路由 (Rpc-Persist-Apaas-Observability-Referer-Path) */
17
+ refererPath?: string;
18
+ /** 前端参数化 API 标识 (Rpc-Persist-Apaas-Observability-Api) */
19
+ observabilityApi?: string;
16
20
  /** 是否为系统账号 */
17
21
  isSystemAccount?: boolean;
18
22
  [key: string]: unknown;
@@ -122,6 +126,13 @@ declare class CommonModule {
122
126
  */
123
127
  declare function stripBasePath(fullPath: string | undefined | null, basePath: string | undefined | null): string | undefined | null;
124
128
 
129
+ /**
130
+ * 创建高精度计时器
131
+ * 使用 process.hrtime 正确计算秒 + 纳秒,支持亚毫秒测量
132
+ * 返回一个函数,调用后返回经过的毫秒数(保留两位小数)
133
+ */
134
+ declare function createTimer(): () => number;
135
+
125
136
  interface ObservableService {
126
137
  log(level: string, message: string, attributes: Record<string, unknown>, parentSpan?: {
127
138
  traceId: string;
@@ -246,4 +257,4 @@ interface HttpClientFactory {
246
257
  create(options?: unknown): HttpClientWithInterceptors;
247
258
  }
248
259
 
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 };
260
+ export { AUTO_TRACE, AutoTrace, CommonModule, HTTP_CLIENT_FACTORY, type HttpClientFactory, type HttpClientWithInterceptors, OBSERVABLE_SERVICE, type ObservableService, PLATFORM_HTTP_CLIENT, type PlatformHttpClient, RequestContextService, type RequestContextState, createTimer, stripBasePath };
package/dist/index.d.ts CHANGED
@@ -13,6 +13,10 @@ interface RequestContextState {
13
13
  ttEnv?: string;
14
14
  /** 请求来源页面路由 (X-Page-Route header) */
15
15
  pageRoute?: string;
16
+ /** 前端参数化页面路由 (Rpc-Persist-Apaas-Observability-Referer-Path) */
17
+ refererPath?: string;
18
+ /** 前端参数化 API 标识 (Rpc-Persist-Apaas-Observability-Api) */
19
+ observabilityApi?: string;
16
20
  /** 是否为系统账号 */
17
21
  isSystemAccount?: boolean;
18
22
  [key: string]: unknown;
@@ -122,6 +126,13 @@ declare class CommonModule {
122
126
  */
123
127
  declare function stripBasePath(fullPath: string | undefined | null, basePath: string | undefined | null): string | undefined | null;
124
128
 
129
+ /**
130
+ * 创建高精度计时器
131
+ * 使用 process.hrtime 正确计算秒 + 纳秒,支持亚毫秒测量
132
+ * 返回一个函数,调用后返回经过的毫秒数(保留两位小数)
133
+ */
134
+ declare function createTimer(): () => number;
135
+
125
136
  interface ObservableService {
126
137
  log(level: string, message: string, attributes: Record<string, unknown>, parentSpan?: {
127
138
  traceId: string;
@@ -246,4 +257,4 @@ interface HttpClientFactory {
246
257
  create(options?: unknown): HttpClientWithInterceptors;
247
258
  }
248
259
 
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 };
260
+ export { AUTO_TRACE, AutoTrace, CommonModule, HTTP_CLIENT_FACTORY, type HttpClientFactory, type HttpClientWithInterceptors, OBSERVABLE_SERVICE, type ObservableService, PLATFORM_HTTP_CLIENT, type PlatformHttpClient, RequestContextService, type RequestContextState, createTimer, stripBasePath };
package/dist/index.js CHANGED
@@ -97,6 +97,16 @@ function stripBasePath(fullPath, basePath) {
97
97
  return fullPath;
98
98
  }
99
99
  __name(stripBasePath, "stripBasePath");
100
+
101
+ // src/utils/create-timer.ts
102
+ function createTimer() {
103
+ const start = process.hrtime();
104
+ return () => {
105
+ const diff = process.hrtime(start);
106
+ return Math.round((diff[0] * 1e3 + diff[1] / 1e6) * 100) / 100;
107
+ };
108
+ }
109
+ __name(createTimer, "createTimer");
100
110
  export {
101
111
  AUTO_TRACE,
102
112
  AutoTrace,
@@ -105,6 +115,7 @@ export {
105
115
  OBSERVABLE_SERVICE,
106
116
  PLATFORM_HTTP_CLIENT,
107
117
  RequestContextService,
118
+ createTimer,
108
119
  stripBasePath
109
120
  };
110
121
  //# sourceMappingURL=index.js.map
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 /** 是否为系统账号 */\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"]}
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","../src/utils/create-timer.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 /** 前端参数化页面路由 (Rpc-Persist-Apaas-Observability-Referer-Path) */\n refererPath?: string;\n /** 前端参数化 API 标识 (Rpc-Persist-Apaas-Observability-Api) */\n observabilityApi?: 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","/**\n * 创建高精度计时器\n * 使用 process.hrtime 正确计算秒 + 纳秒,支持亚毫秒测量\n * 返回一个函数,调用后返回经过的毫秒数(保留两位小数)\n */\nexport function createTimer(): () => number {\n const start = process.hrtime();\n return () => {\n const diff = process.hrtime(start);\n return Math.round((diff[0] * 1000 + diff[1] / 1e6) * 100) / 100;\n };\n}\n"],"mappings":";;;;AAAA,SAASA,kBAAkB;AAC3B,SAASC,yBAAyB;;;;;;;;AAyB3B,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;;;;;;ACjDA,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;;;ACTT,SAASO,cAAAA;AACd,QAAMC,QAAQC,QAAQC,OAAM;AAC5B,SAAO,MAAA;AACL,UAAMC,OAAOF,QAAQC,OAAOF,KAAAA;AAC5B,WAAOI,KAAKC,OAAOF,KAAK,CAAA,IAAK,MAAOA,KAAK,CAAA,IAAK,OAAO,GAAA,IAAO;EAC9D;AACF;AANgBJ;","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","createTimer","start","process","hrtime","diff","Math","round"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/nestjs-common",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Common NestJS utilities",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",