@lark-apaas/nestjs-common 0.0.1-alpha.1

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 ADDED
@@ -0,0 +1,161 @@
1
+ # @lark-apaas/nestjs-common
2
+
3
+ 提供可复用的 NestJS 通用能力与类型定义,当前内置请求级上下文服务 `RequestContextService`,用于在一次 HTTP 请求的生命周期内安全地读写上下文数据(如请求 ID、路径、方法、用户/租户/应用信息、IP 等)。
4
+
5
+ ## 特性
6
+
7
+ - 请求级上下文(AsyncLocalStorage)
8
+ - 在同一请求链路中安全传递上下文数据
9
+ - 支持在中间件、拦截器、服务、控制器中读写
10
+ - 纯粹的 NestJS 通用包
11
+ - 不依赖具体业务模块(例如日志、可观测),便于复用
12
+
13
+ ## 安装
14
+
15
+ ```bash
16
+ yarn add @lark-apaas/nestjs-common
17
+ ```
18
+
19
+ 或使用 npm:
20
+
21
+ ```bash
22
+ npm install @lark-apaas/nestjs-common
23
+ ```
24
+
25
+ ## 环境要求
26
+
27
+ - Node.js >= 18.0.0
28
+ - NestJS >= 10.4.20
29
+
30
+ ## 快速开始
31
+
32
+ ### 1. 在顶层模块中注册为单例 Provider(推荐)
33
+
34
+ ```ts
35
+ // app.module.ts
36
+ import { Module } from '@nestjs/common';
37
+ import { RequestContextService } from '@lark-apaas/nestjs-common';
38
+
39
+ @Module({
40
+ providers: [RequestContextService],
41
+ exports: [RequestContextService],
42
+ })
43
+ export class AppModule {}
44
+ ```
45
+
46
+ > 建议在应用的根模块或全局模块中注册为单例,以确保整个应用共享同一个 `AsyncLocalStorage` 实例。
47
+
48
+ ### 2. 在中间件入口写入请求上下文
49
+
50
+ ```ts
51
+ // observable-context.middleware.ts(示例)
52
+ import { Injectable, NestMiddleware } from '@nestjs/common';
53
+ import type { Request, Response, NextFunction } from 'express';
54
+ import { randomUUID } from 'crypto';
55
+ import { RequestContextService } from '@lark-apaas/nestjs-common';
56
+
57
+ @Injectable()
58
+ export class ObservableContextMiddleware implements NestMiddleware {
59
+ constructor(private readonly reqCtx: RequestContextService) {}
60
+
61
+ use(req: Request & { userContext?: any }, res: Response, next: NextFunction) {
62
+ const requestId = (req.headers['x-tt-log-id'] as string) || (req.headers['x-request-id'] as string) || randomUUID();
63
+ const path = req.originalUrl ?? req.url;
64
+ const uc = req.userContext ?? {};
65
+
66
+ res.setHeader('x-log-trace-id', requestId);
67
+
68
+ this.reqCtx.run(
69
+ {
70
+ requestId,
71
+ path,
72
+ method: req.method,
73
+ userId: uc.userId,
74
+ tenantId: uc.tenantId,
75
+ appId: uc.appId,
76
+ ip: (req.ip as string) ?? undefined,
77
+ },
78
+ () => next(),
79
+ );
80
+ }
81
+ }
82
+ ```
83
+
84
+ ### 3. 在任意位置读取上下文
85
+
86
+ ```ts
87
+ import { Injectable } from '@nestjs/common';
88
+ import { RequestContextService } from '@lark-apaas/nestjs-common';
89
+
90
+ @Injectable()
91
+ export class DemoService {
92
+ constructor(private readonly reqCtx: RequestContextService) {}
93
+
94
+ doSomething() {
95
+ const ctx = this.reqCtx.getContext();
96
+ // 或者精确获取某个键
97
+ const requestId = this.reqCtx.get('requestId');
98
+ // ... 使用上下文执行你的逻辑
99
+ return { requestId, path: ctx?.path, method: ctx?.method };
100
+ }
101
+ }
102
+ ```
103
+
104
+ ## API 概览
105
+
106
+ `RequestContextService` 源码位置:packages/server/nestjs-common/src/request-context.service.ts
107
+
108
+ - `run(context, callback)`:在 `AsyncLocalStorage` 中启动一次上下文存储并执行回调。
109
+ - 参考:packages/server/nestjs-common/src/request-context.service.ts:15-20
110
+ - `setContext(partial)`:在当前上下文中合并写入部分字段。
111
+ - 参考:packages/server/nestjs-common/src/request-context.service.ts:22-30
112
+ - `getContext()`:读取当前完整上下文。
113
+ - 参考:packages/server/nestjs-common/src/request-context.service.ts:32-34
114
+ - `get(key)`:读取当前上下文中的特定键。
115
+ - 参考:packages/server/nestjs-common/src/request-context.service.ts:36-38
116
+
117
+ `RequestContextState` 默认字段:
118
+
119
+ ```ts
120
+ interface RequestContextState {
121
+ requestId: string;
122
+ path?: string;
123
+ method?: string;
124
+ userId?: string;
125
+ appId?: string;
126
+ tenantId?: string;
127
+ ip?: string;
128
+ [key: string]: unknown;
129
+ }
130
+ ```
131
+
132
+ ## 与其他模块的协作建议
133
+
134
+ - 与日志/可观测模块协作
135
+ - 在 HTTP 入口(中间件)调用 `run(...)` 写入上下文;
136
+ - 在日志或可观测的记录点读取上下文并合并到结构化日志或埋点数据中。
137
+ - 仅白名单传递敏感信息
138
+ - 避免直接把所有请求头写入上下文,建议只传递必要字段(如 trace-id、user-id、tenant-id、app-id)。
139
+
140
+ ## TypeScript 支持
141
+
142
+ 本包使用 TypeScript 编写并导出完整类型:
143
+
144
+ ```ts
145
+ export { RequestContextService, type RequestContextState } from '@lark-apaas/nestjs-common';
146
+ ```
147
+
148
+ 如需在 `Express.Request` 上扩展 `userContext` 等业务字段,请在你自己的项目中通过 `declare global` 进行类型声明扩展。
149
+
150
+ ## 许可证
151
+
152
+ MIT
153
+
154
+ ## 关键字
155
+
156
+ - nestjs
157
+ - common
158
+ - context
159
+ - als
160
+ - server
161
+ - typescript
package/dist/index.cjs ADDED
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ CommonModule: () => CommonModule,
25
+ RequestContextService: () => RequestContextService
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/request-context.service.ts
30
+ var import_common = require("@nestjs/common");
31
+ var import_async_hooks = require("async_hooks");
32
+ function _ts_decorate(decorators, target, key, desc) {
33
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
34
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
35
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
36
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
37
+ }
38
+ __name(_ts_decorate, "_ts_decorate");
39
+ var RequestContextService = class {
40
+ static {
41
+ __name(this, "RequestContextService");
42
+ }
43
+ storage = new import_async_hooks.AsyncLocalStorage();
44
+ run(context, callback) {
45
+ const store = {
46
+ ...context
47
+ };
48
+ return this.storage.run(store, callback);
49
+ }
50
+ setContext(partial) {
51
+ const store = this.storage.getStore();
52
+ if (!store) {
53
+ return;
54
+ }
55
+ Object.assign(store, partial);
56
+ }
57
+ getContext() {
58
+ return this.storage.getStore();
59
+ }
60
+ get(key) {
61
+ return this.storage.getStore()?.[key];
62
+ }
63
+ };
64
+ RequestContextService = _ts_decorate([
65
+ (0, import_common.Injectable)()
66
+ ], RequestContextService);
67
+
68
+ // src/module.ts
69
+ var import_common2 = require("@nestjs/common");
70
+ function _ts_decorate2(decorators, target, key, desc) {
71
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
72
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
73
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
74
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
75
+ }
76
+ __name(_ts_decorate2, "_ts_decorate");
77
+ var CommonModule = class {
78
+ static {
79
+ __name(this, "CommonModule");
80
+ }
81
+ };
82
+ CommonModule = _ts_decorate2([
83
+ (0, import_common2.Global)(),
84
+ (0, import_common2.Module)({
85
+ providers: [
86
+ RequestContextService
87
+ ],
88
+ exports: [
89
+ RequestContextService
90
+ ]
91
+ })
92
+ ], CommonModule);
93
+ // Annotate the CommonJS export names for ESM import in node:
94
+ 0 && (module.exports = {
95
+ CommonModule,
96
+ RequestContextService
97
+ });
98
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/request-context.service.ts","../src/module.ts"],"sourcesContent":["export { RequestContextService, type RequestContextState } from './request-context.service';\nexport { CommonModule } from './module';\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 [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 { Global, Module } from '@nestjs/common';\nimport { RequestContextService } from './request-context.service';\n\n@Global()\n@Module({\n providers: [RequestContextService],\n exports: [RequestContextService],\n})\nexport class CommonModule {}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;ACAA,oBAA2B;AAC3B,yBAAkC;;;;;;;;AAc3B,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;;;;;;ACtCA,IAAAC,iBAA+B;;;;;;;;AAQxB,IAAMC,eAAN,MAAMA;SAAAA;;;AAAc;;;;IAHzBC,WAAW;MAACC;;IACZC,SAAS;MAACD;;;;","names":["RequestContextService","storage","AsyncLocalStorage","run","context","callback","store","setContext","partial","getStore","Object","assign","getContext","get","key","import_common","CommonModule","providers","RequestContextService","exports"]}
@@ -0,0 +1,22 @@
1
+ interface RequestContextState {
2
+ requestId: string;
3
+ path?: string;
4
+ method?: string;
5
+ userId?: string;
6
+ appId?: string;
7
+ tenantId?: string;
8
+ ip?: string;
9
+ [key: string]: unknown;
10
+ }
11
+ declare class RequestContextService {
12
+ private readonly storage;
13
+ run<T>(context: RequestContextState, callback: () => T): T;
14
+ setContext(partial: Partial<RequestContextState>): void;
15
+ getContext(): RequestContextState | undefined;
16
+ get<K extends keyof RequestContextState>(key: K): RequestContextState[K] | undefined;
17
+ }
18
+
19
+ declare class CommonModule {
20
+ }
21
+
22
+ export { CommonModule, RequestContextService, type RequestContextState };
@@ -0,0 +1,22 @@
1
+ interface RequestContextState {
2
+ requestId: string;
3
+ path?: string;
4
+ method?: string;
5
+ userId?: string;
6
+ appId?: string;
7
+ tenantId?: string;
8
+ ip?: string;
9
+ [key: string]: unknown;
10
+ }
11
+ declare class RequestContextService {
12
+ private readonly storage;
13
+ run<T>(context: RequestContextState, callback: () => T): T;
14
+ setContext(partial: Partial<RequestContextState>): void;
15
+ getContext(): RequestContextState | undefined;
16
+ get<K extends keyof RequestContextState>(key: K): RequestContextState[K] | undefined;
17
+ }
18
+
19
+ declare class CommonModule {
20
+ }
21
+
22
+ export { CommonModule, RequestContextService, type RequestContextState };
package/dist/index.js ADDED
@@ -0,0 +1,72 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/request-context.service.ts
5
+ import { Injectable } from "@nestjs/common";
6
+ import { AsyncLocalStorage } from "async_hooks";
7
+ function _ts_decorate(decorators, target, key, desc) {
8
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
9
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
10
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
11
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
12
+ }
13
+ __name(_ts_decorate, "_ts_decorate");
14
+ var RequestContextService = class {
15
+ static {
16
+ __name(this, "RequestContextService");
17
+ }
18
+ storage = new AsyncLocalStorage();
19
+ run(context, callback) {
20
+ const store = {
21
+ ...context
22
+ };
23
+ return this.storage.run(store, callback);
24
+ }
25
+ setContext(partial) {
26
+ const store = this.storage.getStore();
27
+ if (!store) {
28
+ return;
29
+ }
30
+ Object.assign(store, partial);
31
+ }
32
+ getContext() {
33
+ return this.storage.getStore();
34
+ }
35
+ get(key) {
36
+ return this.storage.getStore()?.[key];
37
+ }
38
+ };
39
+ RequestContextService = _ts_decorate([
40
+ Injectable()
41
+ ], RequestContextService);
42
+
43
+ // src/module.ts
44
+ import { Global, Module } from "@nestjs/common";
45
+ function _ts_decorate2(decorators, target, key, desc) {
46
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
47
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
48
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
49
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
50
+ }
51
+ __name(_ts_decorate2, "_ts_decorate");
52
+ var CommonModule = class {
53
+ static {
54
+ __name(this, "CommonModule");
55
+ }
56
+ };
57
+ CommonModule = _ts_decorate2([
58
+ Global(),
59
+ Module({
60
+ providers: [
61
+ RequestContextService
62
+ ],
63
+ exports: [
64
+ RequestContextService
65
+ ]
66
+ })
67
+ ], CommonModule);
68
+ export {
69
+ CommonModule,
70
+ RequestContextService
71
+ };
72
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/request-context.service.ts","../src/module.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 [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 { Global, Module } from '@nestjs/common';\nimport { RequestContextService } from './request-context.service';\n\n@Global()\n@Module({\n providers: [RequestContextService],\n exports: [RequestContextService],\n})\nexport class CommonModule {}\n"],"mappings":";;;;AAAA,SAASA,kBAAkB;AAC3B,SAASC,yBAAyB;;;;;;;;AAc3B,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;;;;;;ACtCA,SAASC,QAAQC,cAAc;;;;;;;;AAQxB,IAAMC,eAAN,MAAMA;SAAAA;;;AAAc;;;;IAHzBC,WAAW;MAACC;;IACZC,SAAS;MAACD;;;;","names":["Injectable","AsyncLocalStorage","RequestContextService","storage","AsyncLocalStorage","run","context","callback","store","setContext","partial","getStore","Object","assign","getContext","get","key","Global","Module","CommonModule","providers","RequestContextService","exports"]}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@lark-apaas/nestjs-common",
3
+ "version": "0.0.1-alpha.1",
4
+ "description": "Common NestJS utilities",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "license": "MIT",
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "keywords": [
13
+ "nestjs",
14
+ "common",
15
+ "context",
16
+ "als"
17
+ ],
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "exports": {
22
+ ".": {
23
+ "import": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "require": "./dist/index.cjs"
26
+ }
27
+ },
28
+ "scripts": {
29
+ "build": "tsup",
30
+ "dev": "tsup --watch",
31
+ "typecheck": "tsc --noEmit",
32
+ "test": "vitest run",
33
+ "test:watch": "vitest",
34
+ "lint": "echo 'ESLint skipped for TypeScript files'"
35
+ },
36
+ "peerDependencies": {
37
+ "@nestjs/common": "^10.4.20"
38
+ },
39
+ "devDependencies": {
40
+ "@nestjs/common": "^10.4.20",
41
+ "@types/node": "^18.19.0",
42
+ "tsup": "^8.0.0",
43
+ "typescript": "^5.2.0"
44
+ }
45
+ }