nestjs-tenant-shield 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +612 -0
- package/dist/cache/cache.registry.d.ts +19 -0
- package/dist/cache/cache.registry.d.ts.map +1 -0
- package/dist/cache/cache.registry.js +51 -0
- package/dist/cache/cache.registry.js.map +1 -0
- package/dist/cache/cache.service.d.ts +44 -0
- package/dist/cache/cache.service.d.ts.map +1 -0
- package/dist/cache/cache.service.js +64 -0
- package/dist/cache/cache.service.js.map +1 -0
- package/dist/cache/index.d.ts +3 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +19 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/constants/index.d.ts +64 -0
- package/dist/constants/index.d.ts.map +1 -0
- package/dist/constants/index.js +67 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/context/get-current-tenant-id.d.ts +30 -0
- package/dist/context/get-current-tenant-id.d.ts.map +1 -0
- package/dist/context/get-current-tenant-id.js +40 -0
- package/dist/context/get-current-tenant-id.js.map +1 -0
- package/dist/context/index.d.ts +7 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +23 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/run-with-tenant.d.ts +84 -0
- package/dist/context/run-with-tenant.d.ts.map +1 -0
- package/dist/context/run-with-tenant.js +95 -0
- package/dist/context/run-with-tenant.js.map +1 -0
- package/dist/context/tenant-context.storage.d.ts +43 -0
- package/dist/context/tenant-context.storage.d.ts.map +1 -0
- package/dist/context/tenant-context.storage.js +45 -0
- package/dist/context/tenant-context.storage.js.map +1 -0
- package/dist/decorators/cacheable.decorator.d.ts +27 -0
- package/dist/decorators/cacheable.decorator.d.ts.map +1 -0
- package/dist/decorators/cacheable.decorator.js +108 -0
- package/dist/decorators/cacheable.decorator.js.map +1 -0
- package/dist/decorators/index.d.ts +13 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +29 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/decorators/require-tenant.decorator.d.ts +41 -0
- package/dist/decorators/require-tenant.decorator.d.ts.map +1 -0
- package/dist/decorators/require-tenant.decorator.js +125 -0
- package/dist/decorators/require-tenant.decorator.js.map +1 -0
- package/dist/decorators/system-action.decorator.d.ts +39 -0
- package/dist/decorators/system-action.decorator.d.ts.map +1 -0
- package/dist/decorators/system-action.decorator.js +50 -0
- package/dist/decorators/system-action.decorator.js.map +1 -0
- package/dist/decorators/tenant-context.decorator.d.ts +33 -0
- package/dist/decorators/tenant-context.decorator.d.ts.map +1 -0
- package/dist/decorators/tenant-context.decorator.js +54 -0
- package/dist/decorators/tenant-context.decorator.js.map +1 -0
- package/dist/errors/cross-tenant-access.error.d.ts +29 -0
- package/dist/errors/cross-tenant-access.error.d.ts.map +1 -0
- package/dist/errors/cross-tenant-access.error.js +37 -0
- package/dist/errors/cross-tenant-access.error.js.map +1 -0
- package/dist/errors/index.d.ts +10 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +26 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/invalid-tenant-source.error.d.ts +20 -0
- package/dist/errors/invalid-tenant-source.error.d.ts.map +1 -0
- package/dist/errors/invalid-tenant-source.error.js +28 -0
- package/dist/errors/invalid-tenant-source.error.js.map +1 -0
- package/dist/errors/missing-tenant-context.error.d.ts +22 -0
- package/dist/errors/missing-tenant-context.error.d.ts.map +1 -0
- package/dist/errors/missing-tenant-context.error.js +32 -0
- package/dist/errors/missing-tenant-context.error.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/cacheable-options.interface.d.ts +37 -0
- package/dist/interfaces/cacheable-options.interface.d.ts.map +1 -0
- package/dist/interfaces/cacheable-options.interface.js +3 -0
- package/dist/interfaces/cacheable-options.interface.js.map +1 -0
- package/dist/interfaces/index.d.ts +13 -0
- package/dist/interfaces/index.d.ts.map +1 -0
- package/dist/interfaces/index.js +30 -0
- package/dist/interfaces/index.js.map +1 -0
- package/dist/interfaces/require-tenant-options.interface.d.ts +23 -0
- package/dist/interfaces/require-tenant-options.interface.d.ts.map +1 -0
- package/dist/interfaces/require-tenant-options.interface.js +3 -0
- package/dist/interfaces/require-tenant-options.interface.js.map +1 -0
- package/dist/interfaces/tenant-context-options.interface.d.ts +19 -0
- package/dist/interfaces/tenant-context-options.interface.d.ts.map +1 -0
- package/dist/interfaces/tenant-context-options.interface.js +3 -0
- package/dist/interfaces/tenant-context-options.interface.js.map +1 -0
- package/dist/interfaces/tenant-context.interface.d.ts +26 -0
- package/dist/interfaces/tenant-context.interface.d.ts.map +1 -0
- package/dist/interfaces/tenant-context.interface.js +3 -0
- package/dist/interfaces/tenant-context.interface.js.map +1 -0
- package/dist/interfaces/tenant-shield-options.interface.d.ts +141 -0
- package/dist/interfaces/tenant-shield-options.interface.d.ts.map +1 -0
- package/dist/interfaces/tenant-shield-options.interface.js +11 -0
- package/dist/interfaces/tenant-shield-options.interface.js.map +1 -0
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +18 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/tenant-context.middleware.d.ts +30 -0
- package/dist/middleware/tenant-context.middleware.d.ts.map +1 -0
- package/dist/middleware/tenant-context.middleware.js +68 -0
- package/dist/middleware/tenant-context.middleware.js.map +1 -0
- package/dist/options/index.d.ts +2 -0
- package/dist/options/index.d.ts.map +1 -0
- package/dist/options/index.js +18 -0
- package/dist/options/index.js.map +1 -0
- package/dist/options/options.registry.d.ts +8 -0
- package/dist/options/options.registry.d.ts.map +1 -0
- package/dist/options/options.registry.js +36 -0
- package/dist/options/options.registry.js.map +1 -0
- package/dist/resolvers/custom.resolver.d.ts +29 -0
- package/dist/resolvers/custom.resolver.d.ts.map +1 -0
- package/dist/resolvers/custom.resolver.js +47 -0
- package/dist/resolvers/custom.resolver.js.map +1 -0
- package/dist/resolvers/header.resolver.d.ts +22 -0
- package/dist/resolvers/header.resolver.d.ts.map +1 -0
- package/dist/resolvers/header.resolver.js +39 -0
- package/dist/resolvers/header.resolver.js.map +1 -0
- package/dist/resolvers/index.d.ts +13 -0
- package/dist/resolvers/index.d.ts.map +1 -0
- package/dist/resolvers/index.js +29 -0
- package/dist/resolvers/index.js.map +1 -0
- package/dist/resolvers/jwt.resolver.d.ts +35 -0
- package/dist/resolvers/jwt.resolver.d.ts.map +1 -0
- package/dist/resolvers/jwt.resolver.js +51 -0
- package/dist/resolvers/jwt.resolver.js.map +1 -0
- package/dist/resolvers/resolver.factory.d.ts +12 -0
- package/dist/resolvers/resolver.factory.d.ts.map +1 -0
- package/dist/resolvers/resolver.factory.js +43 -0
- package/dist/resolvers/resolver.factory.js.map +1 -0
- package/dist/resolvers/subdomain.resolver.d.ts +37 -0
- package/dist/resolvers/subdomain.resolver.d.ts.map +1 -0
- package/dist/resolvers/subdomain.resolver.js +57 -0
- package/dist/resolvers/subdomain.resolver.js.map +1 -0
- package/dist/resolvers/tenant-resolver.interface.d.ts +22 -0
- package/dist/resolvers/tenant-resolver.interface.d.ts.map +1 -0
- package/dist/resolvers/tenant-resolver.interface.js +3 -0
- package/dist/resolvers/tenant-resolver.interface.js.map +1 -0
- package/dist/tenant-shield.module.d.ts +88 -0
- package/dist/tenant-shield.module.d.ts.map +1 -0
- package/dist/tenant-shield.module.js +263 -0
- package/dist/tenant-shield.module.js.map +1 -0
- package/dist/testing/index.d.ts +12 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +28 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/test-helpers.d.ts +52 -0
- package/dist/testing/test-helpers.d.ts.map +1 -0
- package/dist/testing/test-helpers.js +72 -0
- package/dist/testing/test-helpers.js.map +1 -0
- package/dist/typeorm/index.d.ts +10 -0
- package/dist/typeorm/index.d.ts.map +1 -0
- package/dist/typeorm/index.js +26 -0
- package/dist/typeorm/index.js.map +1 -0
- package/dist/typeorm/raw-sql.helper.d.ts +35 -0
- package/dist/typeorm/raw-sql.helper.d.ts.map +1 -0
- package/dist/typeorm/raw-sql.helper.js +24 -0
- package/dist/typeorm/raw-sql.helper.js.map +1 -0
- package/dist/typeorm/tenant.subscriber.d.ts +61 -0
- package/dist/typeorm/tenant.subscriber.d.ts.map +1 -0
- package/dist/typeorm/tenant.subscriber.js +487 -0
- package/dist/typeorm/tenant.subscriber.js.map +1 -0
- package/package.json +109 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-with-tenant.js","sourceRoot":"","sources":["../../src/context/run-with-tenant.ts"],"names":[],"mappings":";;AA6BA,sCAOC;AAwDD,4CAEC;AA9FD,qEAAgE;AAEhE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,SAAgB,aAAa,CAC3B,QAAgB,EAChB,EAAoB;IAEpB,gDAAgD;IAChD,iDAAiD;IACjD,OAAO,6CAAoB,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;AAC3E,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,SAAgB,gBAAgB,CAAI,EAAoB;IACtD,OAAO,6CAAoB,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;AAChF,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { TenantContext } from '../interfaces/tenant-context.interface';
|
|
3
|
+
/**
|
|
4
|
+
* ─────────────────────────────────────────────────────────────
|
|
5
|
+
* 이 라이브러리의 심장.
|
|
6
|
+
*
|
|
7
|
+
* Node.js의 AsyncLocalStorage를 활용해서, "지금 처리 중인 요청이
|
|
8
|
+
* 어느 tenant 것인지"를 비동기 호출 체인 전체에 걸쳐 안전하게
|
|
9
|
+
* 추적합니다.
|
|
10
|
+
*
|
|
11
|
+
* ─────────────────────────────────────────────────────────────
|
|
12
|
+
*
|
|
13
|
+
* [비유]
|
|
14
|
+
* 사무실에 동시에 여러 손님이 방문해도, 각자 손에 출입 카드를
|
|
15
|
+
* 들고 있어서 어느 부서로 안내해야 하는지 직원이 헷갈리지 않습니다.
|
|
16
|
+
* AsyncLocalStorage는 그 "출입 카드"의 역할입니다.
|
|
17
|
+
*
|
|
18
|
+
* Node.js는 동시 요청을 단일 스레드에서 비동기로 처리하기 때문에,
|
|
19
|
+
* 단순히 전역 변수에 tenantId를 넣으면 A요청 처리 중에 B요청이
|
|
20
|
+
* 끼어들어 값이 섞입니다. AsyncLocalStorage는 각 비동기 흐름에
|
|
21
|
+
* 별도의 "상자"를 부여해 이 문제를 원천 차단합니다.
|
|
22
|
+
*
|
|
23
|
+
* ─────────────────────────────────────────────────────────────
|
|
24
|
+
*
|
|
25
|
+
* [사용 패턴]
|
|
26
|
+
* tenantContextStorage.run({ tenantId: 'A' }, async () => {
|
|
27
|
+
* // 이 콜백 안에서는, 그리고 그 안에서 호출되는 모든 비동기
|
|
28
|
+
* // 함수에서는 getStore()가 { tenantId: 'A' }를 반환합니다.
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* ─────────────────────────────────────────────────────────────
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* 라이브러리 전체에서 공유되는 단 하나의 AsyncLocalStorage 인스턴스.
|
|
35
|
+
*
|
|
36
|
+
* 이걸 여러 개 만들면 컨텍스트가 분리되어버려 의도와 다르게 동작합니다.
|
|
37
|
+
* 따라서 module-level singleton으로 export 합니다.
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ 외부 사용자는 직접 접근하지 말고 runWithTenant() 같은 헬퍼를 쓰세요.
|
|
40
|
+
* 이 export는 라이브러리 내부 모듈들 간의 협업과 고급 디버깅용입니다.
|
|
41
|
+
*/
|
|
42
|
+
export declare const tenantContextStorage: AsyncLocalStorage<TenantContext>;
|
|
43
|
+
//# sourceMappingURL=tenant-context.storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-context.storage.d.ts","sourceRoot":"","sources":["../../src/context/tenant-context.storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,kCAAyC,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tenantContextStorage = void 0;
|
|
4
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
5
|
+
/**
|
|
6
|
+
* ─────────────────────────────────────────────────────────────
|
|
7
|
+
* 이 라이브러리의 심장.
|
|
8
|
+
*
|
|
9
|
+
* Node.js의 AsyncLocalStorage를 활용해서, "지금 처리 중인 요청이
|
|
10
|
+
* 어느 tenant 것인지"를 비동기 호출 체인 전체에 걸쳐 안전하게
|
|
11
|
+
* 추적합니다.
|
|
12
|
+
*
|
|
13
|
+
* ─────────────────────────────────────────────────────────────
|
|
14
|
+
*
|
|
15
|
+
* [비유]
|
|
16
|
+
* 사무실에 동시에 여러 손님이 방문해도, 각자 손에 출입 카드를
|
|
17
|
+
* 들고 있어서 어느 부서로 안내해야 하는지 직원이 헷갈리지 않습니다.
|
|
18
|
+
* AsyncLocalStorage는 그 "출입 카드"의 역할입니다.
|
|
19
|
+
*
|
|
20
|
+
* Node.js는 동시 요청을 단일 스레드에서 비동기로 처리하기 때문에,
|
|
21
|
+
* 단순히 전역 변수에 tenantId를 넣으면 A요청 처리 중에 B요청이
|
|
22
|
+
* 끼어들어 값이 섞입니다. AsyncLocalStorage는 각 비동기 흐름에
|
|
23
|
+
* 별도의 "상자"를 부여해 이 문제를 원천 차단합니다.
|
|
24
|
+
*
|
|
25
|
+
* ─────────────────────────────────────────────────────────────
|
|
26
|
+
*
|
|
27
|
+
* [사용 패턴]
|
|
28
|
+
* tenantContextStorage.run({ tenantId: 'A' }, async () => {
|
|
29
|
+
* // 이 콜백 안에서는, 그리고 그 안에서 호출되는 모든 비동기
|
|
30
|
+
* // 함수에서는 getStore()가 { tenantId: 'A' }를 반환합니다.
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* ─────────────────────────────────────────────────────────────
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* 라이브러리 전체에서 공유되는 단 하나의 AsyncLocalStorage 인스턴스.
|
|
37
|
+
*
|
|
38
|
+
* 이걸 여러 개 만들면 컨텍스트가 분리되어버려 의도와 다르게 동작합니다.
|
|
39
|
+
* 따라서 module-level singleton으로 export 합니다.
|
|
40
|
+
*
|
|
41
|
+
* ⚠️ 외부 사용자는 직접 접근하지 말고 runWithTenant() 같은 헬퍼를 쓰세요.
|
|
42
|
+
* 이 export는 라이브러리 내부 모듈들 간의 협업과 고급 디버깅용입니다.
|
|
43
|
+
*/
|
|
44
|
+
exports.tenantContextStorage = new node_async_hooks_1.AsyncLocalStorage();
|
|
45
|
+
//# sourceMappingURL=tenant-context.storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-context.storage.js","sourceRoot":"","sources":["../../src/context/tenant-context.storage.ts"],"names":[],"mappings":";;;AAAA,uDAAqD;AAGrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH;;;;;;;;GAQG;AACU,QAAA,oBAAoB,GAAG,IAAI,oCAAiB,EAAiB,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { CacheableOptions } from '../interfaces/cacheable-options.interface';
|
|
2
|
+
/**
|
|
3
|
+
* ─────────────────────────────────────────────────────────────
|
|
4
|
+
* @Cacheable() — 메서드 결과를 캐시 + tenant 단위 자동 분리.
|
|
5
|
+
*
|
|
6
|
+
* 일반 캐시 데코레이터의 가장 큰 함정은 "캐시 키가 tenant 간 공유돼서
|
|
7
|
+
* A학원이 요청한 결과를 B학원이 받는" 사고입니다.
|
|
8
|
+
* 이 데코레이터는 tenantScoped: true 시 키에 tenant ID prefix를
|
|
9
|
+
* 자동으로 붙여 그 위험을 차단합니다.
|
|
10
|
+
*
|
|
11
|
+
* ─────────────────────────────────────────────────────────────
|
|
12
|
+
*
|
|
13
|
+
* [예시]
|
|
14
|
+
*
|
|
15
|
+
* @Cacheable({ ttl: 300, tenantScoped: true })
|
|
16
|
+
* async getStudentRoster() {
|
|
17
|
+
* return this.repo.find();
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* // 캐시 키 예: "academy-A:StudentsService.getStudentRoster:[]"
|
|
21
|
+
* // "academy-B:StudentsService.getStudentRoster:[]"
|
|
22
|
+
* // → 서로 절대 섞이지 않음.
|
|
23
|
+
*
|
|
24
|
+
* ─────────────────────────────────────────────────────────────
|
|
25
|
+
*/
|
|
26
|
+
export declare function Cacheable(options: CacheableOptions): MethodDecorator;
|
|
27
|
+
//# sourceMappingURL=cacheable.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cacheable.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/cacheable.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAK7E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CA2DpE"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Cacheable = Cacheable;
|
|
4
|
+
const get_current_tenant_id_1 = require("../context/get-current-tenant-id");
|
|
5
|
+
const cache_registry_1 = require("../cache/cache.registry");
|
|
6
|
+
/**
|
|
7
|
+
* ─────────────────────────────────────────────────────────────
|
|
8
|
+
* @Cacheable() — 메서드 결과를 캐시 + tenant 단위 자동 분리.
|
|
9
|
+
*
|
|
10
|
+
* 일반 캐시 데코레이터의 가장 큰 함정은 "캐시 키가 tenant 간 공유돼서
|
|
11
|
+
* A학원이 요청한 결과를 B학원이 받는" 사고입니다.
|
|
12
|
+
* 이 데코레이터는 tenantScoped: true 시 키에 tenant ID prefix를
|
|
13
|
+
* 자동으로 붙여 그 위험을 차단합니다.
|
|
14
|
+
*
|
|
15
|
+
* ─────────────────────────────────────────────────────────────
|
|
16
|
+
*
|
|
17
|
+
* [예시]
|
|
18
|
+
*
|
|
19
|
+
* @Cacheable({ ttl: 300, tenantScoped: true })
|
|
20
|
+
* async getStudentRoster() {
|
|
21
|
+
* return this.repo.find();
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* // 캐시 키 예: "academy-A:StudentsService.getStudentRoster:[]"
|
|
25
|
+
* // "academy-B:StudentsService.getStudentRoster:[]"
|
|
26
|
+
* // → 서로 절대 섞이지 않음.
|
|
27
|
+
*
|
|
28
|
+
* ─────────────────────────────────────────────────────────────
|
|
29
|
+
*/
|
|
30
|
+
function Cacheable(options) {
|
|
31
|
+
return function (target, propertyKey, descriptor) {
|
|
32
|
+
const originalMethod = descriptor.value;
|
|
33
|
+
const className = target.constructor?.name ?? 'UnknownClass';
|
|
34
|
+
// dev 환경에서 fallback 사용 시 1회만 안내 — 디버깅 부담 완화.
|
|
35
|
+
let fallbackWarned = false;
|
|
36
|
+
descriptor.value = async function (...args) {
|
|
37
|
+
// 1) 캐시 서비스 인스턴스 lookup — 두 경로 시도:
|
|
38
|
+
// (a) 사용자 클래스에 명시적으로 주입된 this.cacheService (우선)
|
|
39
|
+
// (b) TenantShieldModule이 bootstrap 시 등록한 글로벌 registry (fallback)
|
|
40
|
+
//
|
|
41
|
+
// (b)가 동작하려면 AppModule이 TenantShieldModule.forRoot()를
|
|
42
|
+
// import 했고, 앱이 onApplicationBootstrap 라이프사이클을 지난 상태여야 함.
|
|
43
|
+
const explicit = this.cacheService;
|
|
44
|
+
const cacheService = explicit ?? (0, cache_registry_1.getGlobalCache)();
|
|
45
|
+
if (!cacheService) {
|
|
46
|
+
// 둘 다 없으면 캐시를 그냥 우회 — 기능적으로는 안전(원본 결과 반환).
|
|
47
|
+
// 다만 사용자가 @Cacheable을 명시했다는 건 캐시를 원했다는 뜻이므로
|
|
48
|
+
// 개발 환경에서 한 번만 경고 로그를 남겨 디버깅을 도와줍니다.
|
|
49
|
+
if (!fallbackWarned && process.env.NODE_ENV !== 'production') {
|
|
50
|
+
fallbackWarned = true;
|
|
51
|
+
// eslint-disable-next-line no-console
|
|
52
|
+
console.warn(`[nestjs-tenant-shield] @Cacheable이 적용된 ${className}.${String(propertyKey)}가 ` +
|
|
53
|
+
`캐시 인스턴스를 찾지 못해 원본 호출로 fallback 합니다. ` +
|
|
54
|
+
`TenantShieldModule.forRoot() import 여부 또는 cacheService 주입을 확인하세요.`);
|
|
55
|
+
}
|
|
56
|
+
return originalMethod.apply(this, args);
|
|
57
|
+
}
|
|
58
|
+
// 2) 캐시 키 생성.
|
|
59
|
+
const key = buildCacheKey({
|
|
60
|
+
className,
|
|
61
|
+
methodName: String(propertyKey),
|
|
62
|
+
args,
|
|
63
|
+
options,
|
|
64
|
+
});
|
|
65
|
+
// 3) 캐시 hit → 즉시 반환
|
|
66
|
+
const cached = await cacheService.get(key);
|
|
67
|
+
if (cached !== undefined)
|
|
68
|
+
return cached;
|
|
69
|
+
// 4) Miss → 원본 실행 → 결과 저장 → 반환
|
|
70
|
+
const result = await originalMethod.apply(this, args);
|
|
71
|
+
await cacheService.set(key, result, options.ttl);
|
|
72
|
+
return result;
|
|
73
|
+
};
|
|
74
|
+
return descriptor;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 캐시 키를 만드는 헬퍼.
|
|
79
|
+
*
|
|
80
|
+
* 기본 키 포맷:
|
|
81
|
+
* `[${tenantId}:]${ClassName}.${methodName}:${JSON.stringify(args)}`
|
|
82
|
+
*
|
|
83
|
+
* keyGenerator가 옵션에 있으면 args 부분은 그것을 그대로 사용.
|
|
84
|
+
*/
|
|
85
|
+
function buildCacheKey(input) {
|
|
86
|
+
const { className, methodName, args, options } = input;
|
|
87
|
+
// 1) tenant prefix
|
|
88
|
+
const tenantPrefix = options.tenantScoped ? `${(0, get_current_tenant_id_1.getCurrentTenantId)() ?? 'no-tenant'}:` : '';
|
|
89
|
+
// 2) args 직렬화
|
|
90
|
+
// 커스텀 keyGenerator가 있으면 우선 사용 — 사용자가 키 충돌/길이 제어 가능.
|
|
91
|
+
const argsKey = options.keyGenerator
|
|
92
|
+
? options.keyGenerator(args)
|
|
93
|
+
: safeStringify(args);
|
|
94
|
+
return `${tenantPrefix}${className}.${methodName}:${argsKey}`;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 인자가 순환 참조이거나 Date 등 직렬화가 까다로운 경우에도 죽지 않게.
|
|
98
|
+
* 일단 try/catch로 감싸고, 실패 시 인자 타입 정보만 활용.
|
|
99
|
+
*/
|
|
100
|
+
function safeStringify(args) {
|
|
101
|
+
try {
|
|
102
|
+
return JSON.stringify(args);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return `[unserializable:${args.length}args]`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=cacheable.decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cacheable.decorator.js","sourceRoot":"","sources":["../../src/decorators/cacheable.decorator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 라이브러리가 제공하는 모든 데코레이터.
|
|
3
|
+
*
|
|
4
|
+
* - @RequireTenant : tenant 컨텍스트 강제
|
|
5
|
+
* - @SystemAction : 의도적 tenant 없는 작업
|
|
6
|
+
* - @Cacheable : tenant 분리 캐시
|
|
7
|
+
* - @TenantContext : 큐 작업 컨텍스트 복원 (v0.2)
|
|
8
|
+
*/
|
|
9
|
+
export * from './require-tenant.decorator';
|
|
10
|
+
export * from './system-action.decorator';
|
|
11
|
+
export * from './cacheable.decorator';
|
|
12
|
+
export * from './tenant-context.decorator';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,4BAA4B,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
/**
|
|
18
|
+
* 라이브러리가 제공하는 모든 데코레이터.
|
|
19
|
+
*
|
|
20
|
+
* - @RequireTenant : tenant 컨텍스트 강제
|
|
21
|
+
* - @SystemAction : 의도적 tenant 없는 작업
|
|
22
|
+
* - @Cacheable : tenant 분리 캐시
|
|
23
|
+
* - @TenantContext : 큐 작업 컨텍스트 복원 (v0.2)
|
|
24
|
+
*/
|
|
25
|
+
__exportStar(require("./require-tenant.decorator"), exports);
|
|
26
|
+
__exportStar(require("./system-action.decorator"), exports);
|
|
27
|
+
__exportStar(require("./cacheable.decorator"), exports);
|
|
28
|
+
__exportStar(require("./tenant-context.decorator"), exports);
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA;;;;;;;GAOG;AACH,6DAA2C;AAC3C,4DAA0C;AAC1C,wDAAsC;AACtC,6DAA2C"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { RequireTenantOptions } from '../interfaces/require-tenant-options.interface';
|
|
2
|
+
/**
|
|
3
|
+
* ─────────────────────────────────────────────────────────────
|
|
4
|
+
* @RequireTenant() — 이 라이브러리의 얼굴 마담 데코레이터.
|
|
5
|
+
*
|
|
6
|
+
* 클래스 또는 메서드에 붙이면, 해당 메서드를 호출할 때마다
|
|
7
|
+
* AsyncLocalStorage에 tenant 컨텍스트가 있는지 자동으로 확인합니다.
|
|
8
|
+
* 없으면 MissingTenantContextError를 throw하여 데이터 누출을 사전 차단.
|
|
9
|
+
*
|
|
10
|
+
* ─────────────────────────────────────────────────────────────
|
|
11
|
+
*
|
|
12
|
+
* [사용 패턴 1 — 클래스 레벨]
|
|
13
|
+
*
|
|
14
|
+
* @Injectable()
|
|
15
|
+
* @RequireTenant() // 이 서비스의 모든 public 메서드 보호
|
|
16
|
+
* export class StudentsService {
|
|
17
|
+
* async findAll() { ... } // 자동 보호
|
|
18
|
+
* async findOne(id) { ... } // 자동 보호
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* [사용 패턴 2 — 메서드 레벨]
|
|
22
|
+
*
|
|
23
|
+
* @Injectable()
|
|
24
|
+
* export class MixedService {
|
|
25
|
+
* @RequireTenant()
|
|
26
|
+
* async findStudents() { ... } // 보호
|
|
27
|
+
*
|
|
28
|
+
* async getPublicStats() { ... } // 보호 안 됨 (공개 통계 등)
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* ─────────────────────────────────────────────────────────────
|
|
32
|
+
*
|
|
33
|
+
* [구현 메모]
|
|
34
|
+
* - reflect-metadata로 메타데이터를 박아두면 Subscriber가 "이 호출이
|
|
35
|
+
* 보호 대상인지" 알 수 있습니다.
|
|
36
|
+
* - 메서드 디스크립터를 감싸서 실제 호출 직전 컨텍스트를 검사.
|
|
37
|
+
* - 클래스 데코레이터로 쓰면 prototype의 모든 메서드를 자동 순회.
|
|
38
|
+
* ─────────────────────────────────────────────────────────────
|
|
39
|
+
*/
|
|
40
|
+
export declare function RequireTenant(options?: RequireTenantOptions): MethodDecorator & ClassDecorator;
|
|
41
|
+
//# sourceMappingURL=require-tenant.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-tenant.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/require-tenant.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,gDAAgD,CAAC;AAOtF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,aAAa,CAC3B,OAAO,GAAE,oBAAyB,GACjC,eAAe,GAAG,cAAc,CA0ClC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RequireTenant = RequireTenant;
|
|
4
|
+
const get_current_tenant_id_1 = require("../context/get-current-tenant-id");
|
|
5
|
+
const tenant_context_storage_1 = require("../context/tenant-context.storage");
|
|
6
|
+
const missing_tenant_context_error_1 = require("../errors/missing-tenant-context.error");
|
|
7
|
+
const constants_1 = require("../constants");
|
|
8
|
+
const options_registry_1 = require("../options/options.registry");
|
|
9
|
+
/**
|
|
10
|
+
* ─────────────────────────────────────────────────────────────
|
|
11
|
+
* @RequireTenant() — 이 라이브러리의 얼굴 마담 데코레이터.
|
|
12
|
+
*
|
|
13
|
+
* 클래스 또는 메서드에 붙이면, 해당 메서드를 호출할 때마다
|
|
14
|
+
* AsyncLocalStorage에 tenant 컨텍스트가 있는지 자동으로 확인합니다.
|
|
15
|
+
* 없으면 MissingTenantContextError를 throw하여 데이터 누출을 사전 차단.
|
|
16
|
+
*
|
|
17
|
+
* ─────────────────────────────────────────────────────────────
|
|
18
|
+
*
|
|
19
|
+
* [사용 패턴 1 — 클래스 레벨]
|
|
20
|
+
*
|
|
21
|
+
* @Injectable()
|
|
22
|
+
* @RequireTenant() // 이 서비스의 모든 public 메서드 보호
|
|
23
|
+
* export class StudentsService {
|
|
24
|
+
* async findAll() { ... } // 자동 보호
|
|
25
|
+
* async findOne(id) { ... } // 자동 보호
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* [사용 패턴 2 — 메서드 레벨]
|
|
29
|
+
*
|
|
30
|
+
* @Injectable()
|
|
31
|
+
* export class MixedService {
|
|
32
|
+
* @RequireTenant()
|
|
33
|
+
* async findStudents() { ... } // 보호
|
|
34
|
+
*
|
|
35
|
+
* async getPublicStats() { ... } // 보호 안 됨 (공개 통계 등)
|
|
36
|
+
* }
|
|
37
|
+
*
|
|
38
|
+
* ─────────────────────────────────────────────────────────────
|
|
39
|
+
*
|
|
40
|
+
* [구현 메모]
|
|
41
|
+
* - reflect-metadata로 메타데이터를 박아두면 Subscriber가 "이 호출이
|
|
42
|
+
* 보호 대상인지" 알 수 있습니다.
|
|
43
|
+
* - 메서드 디스크립터를 감싸서 실제 호출 직전 컨텍스트를 검사.
|
|
44
|
+
* - 클래스 데코레이터로 쓰면 prototype의 모든 메서드를 자동 순회.
|
|
45
|
+
* ─────────────────────────────────────────────────────────────
|
|
46
|
+
*/
|
|
47
|
+
function RequireTenant(options = {}) {
|
|
48
|
+
return function (target, propertyKey, descriptor) {
|
|
49
|
+
// ─────────────────────────────────────────────
|
|
50
|
+
// CASE 1: 메서드 데코레이터로 사용된 경우
|
|
51
|
+
// propertyKey와 descriptor가 모두 존재함.
|
|
52
|
+
// ─────────────────────────────────────────────
|
|
53
|
+
if (propertyKey && descriptor) {
|
|
54
|
+
wrapMethod(propertyKey, descriptor, options);
|
|
55
|
+
return descriptor;
|
|
56
|
+
}
|
|
57
|
+
// ─────────────────────────────────────────────
|
|
58
|
+
// CASE 2: 클래스 데코레이터로 사용된 경우
|
|
59
|
+
// propertyKey가 undefined이고, target이 클래스 생성자.
|
|
60
|
+
// 클래스의 prototype에 있는 모든 메서드를 자동으로 감쌉니다.
|
|
61
|
+
// ─────────────────────────────────────────────
|
|
62
|
+
const proto = target.prototype;
|
|
63
|
+
for (const key of Object.getOwnPropertyNames(proto)) {
|
|
64
|
+
// constructor는 건너뛰기.
|
|
65
|
+
if (key === 'constructor')
|
|
66
|
+
continue;
|
|
67
|
+
const desc = Object.getOwnPropertyDescriptor(proto, key);
|
|
68
|
+
// 함수가 아닌 속성(getter 등)은 건너뜀.
|
|
69
|
+
if (!desc || typeof desc.value !== 'function')
|
|
70
|
+
continue;
|
|
71
|
+
// @SystemAction()이 붙은 메서드는 의도적으로 보호 제외.
|
|
72
|
+
const isSystemAction = Reflect.getMetadata(constants_1.SYSTEM_ACTION_METADATA, desc.value);
|
|
73
|
+
if (isSystemAction)
|
|
74
|
+
continue;
|
|
75
|
+
wrapMethod(key, desc, options);
|
|
76
|
+
// 변경된 descriptor 적용.
|
|
77
|
+
Object.defineProperty(proto, key, desc);
|
|
78
|
+
}
|
|
79
|
+
return target;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 메서드 디스크립터를 감싸 호출 전 tenant 컨텍스트를 검사하도록 변환합니다.
|
|
84
|
+
*
|
|
85
|
+
* 이 함수가 실질적인 보호 로직의 코어입니다.
|
|
86
|
+
*/
|
|
87
|
+
function wrapMethod(propertyKey, descriptor, options) {
|
|
88
|
+
// 원본 메서드 백업.
|
|
89
|
+
const originalMethod = descriptor.value;
|
|
90
|
+
// 이 메서드가 @RequireTenant로 보호된다는 메타데이터 기록.
|
|
91
|
+
// Subscriber나 디버깅 도구가 참조할 수 있습니다.
|
|
92
|
+
Reflect.defineMetadata(constants_1.REQUIRE_TENANT_METADATA, options, originalMethod);
|
|
93
|
+
// 메서드를 감싸기 — 화살표 함수가 아니라 일반 함수로 작성하여
|
|
94
|
+
// `this`가 호출 시점의 인스턴스로 정상 바인딩되도록 합니다.
|
|
95
|
+
descriptor.value = async function (...args) {
|
|
96
|
+
const store = tenant_context_storage_1.tenantContextStorage.getStore();
|
|
97
|
+
const tenantId = (0, get_current_tenant_id_1.getCurrentTenantId)();
|
|
98
|
+
// 런타임 컨텍스트 플래그: runWithoutTenant()로 진입한 경우.
|
|
99
|
+
const isSystemActionRuntime = store?.isSystemAction === true;
|
|
100
|
+
// 데코레이터 메타데이터 플래그: @SystemAction()이 originalMethod에 붙은 경우.
|
|
101
|
+
// 올바른 적용 순서(@RequireTenant 위, @SystemAction 아래)일 때만 유효하다.
|
|
102
|
+
const isSystemActionDecorated = Reflect.getMetadata(constants_1.SYSTEM_ACTION_METADATA, originalMethod) === true;
|
|
103
|
+
// forRoot.allowSystemActions: true일 때만 데코레이터 우회 허용.
|
|
104
|
+
const globalAllowSystemActions = (0, options_registry_1.getGlobalOptions)()?.allowSystemActions ?? false;
|
|
105
|
+
const shouldBypass = isSystemActionRuntime || (isSystemActionDecorated && globalAllowSystemActions);
|
|
106
|
+
if (!tenantId && !options.allowSystem && !shouldBypass) {
|
|
107
|
+
// strictMode가 명시적으로 false면 경고만 남기고 통과,
|
|
108
|
+
// 그 외에는 (기본 true) 즉시 throw.
|
|
109
|
+
const strict = options.strictMode !== false;
|
|
110
|
+
if (strict) {
|
|
111
|
+
throw new missing_tenant_context_error_1.MissingTenantContextError(`@RequireTenant: tenant 컨텍스트 없음 — "${String(propertyKey)}" 호출 전 확인:\n` +
|
|
112
|
+
` 1) TenantContextMiddleware가 이 라우트에 적용됐는지\n` +
|
|
113
|
+
` 2) 테스트라면 runWithTenant()로 감쌌는지\n` +
|
|
114
|
+
` 3) 시스템 작업이라면 @SystemAction + forRoot({ allowSystemActions: true }) 조합인지`, 'decorator');
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
// TODO: forRoot에서 받은 logger 인스턴스로 경고 출력.
|
|
118
|
+
// (의도적으로 console 직접 호출은 피함 — 로거 인스턴스 주입 예정)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// 원본 메서드 실행 결과 반환.
|
|
122
|
+
return originalMethod.apply(this, args);
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=require-tenant.decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-tenant.decorator.js","sourceRoot":"","sources":["../../src/decorators/require-tenant.decorator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ─────────────────────────────────────────────────────────────
|
|
3
|
+
* @SystemAction() — tenant 없이 실행되어야 하는 시스템 작업 표시 데코레이터.
|
|
4
|
+
*
|
|
5
|
+
* 일반적으로 @RequireTenant()가 클래스 레벨에 붙어 있는 서비스라도,
|
|
6
|
+
* cron / 마이그레이션 / 모니터링 같은 메서드는 모든 tenant를 가로질러
|
|
7
|
+
* 동작해야 합니다. 그때 이 데코레이터로 "의도적으로 보호 제외"임을
|
|
8
|
+
* 명시하는 것이 안전합니다.
|
|
9
|
+
*
|
|
10
|
+
* 동작 전제:
|
|
11
|
+
* forRoot.allowSystemActions: true 여야 실제로 작동.
|
|
12
|
+
* (켜지 않으면 라이브러리가 보안 우회 가능성을 차단)
|
|
13
|
+
*
|
|
14
|
+
* ─────────────────────────────────────────────────────────────
|
|
15
|
+
*
|
|
16
|
+
* [사용 예시]
|
|
17
|
+
*
|
|
18
|
+
* @Injectable()
|
|
19
|
+
* @RequireTenant()
|
|
20
|
+
* export class MaintenanceService {
|
|
21
|
+
* // 일반 메서드 — tenant 컨텍스트 필요
|
|
22
|
+
* async cleanupForCurrentTenant() { ... }
|
|
23
|
+
*
|
|
24
|
+
* // 시스템 메서드 — tenant 컨텍스트 없이 실행 가능
|
|
25
|
+
* @SystemAction()
|
|
26
|
+
* async runDailyCleanupForAllTenants() {
|
|
27
|
+
* const tenants = await tenantRegistry.findAll();
|
|
28
|
+
* for (const t of tenants) {
|
|
29
|
+
* await runWithTenant(t.id, async () => {
|
|
30
|
+
* await this.cleanupForCurrentTenant();
|
|
31
|
+
* });
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
*
|
|
36
|
+
* ─────────────────────────────────────────────────────────────
|
|
37
|
+
*/
|
|
38
|
+
export declare function SystemAction(): MethodDecorator;
|
|
39
|
+
//# sourceMappingURL=system-action.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-action.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/system-action.decorator.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,YAAY,IAAI,eAAe,CAW9C"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SystemAction = SystemAction;
|
|
4
|
+
const constants_1 = require("../constants");
|
|
5
|
+
/**
|
|
6
|
+
* ─────────────────────────────────────────────────────────────
|
|
7
|
+
* @SystemAction() — tenant 없이 실행되어야 하는 시스템 작업 표시 데코레이터.
|
|
8
|
+
*
|
|
9
|
+
* 일반적으로 @RequireTenant()가 클래스 레벨에 붙어 있는 서비스라도,
|
|
10
|
+
* cron / 마이그레이션 / 모니터링 같은 메서드는 모든 tenant를 가로질러
|
|
11
|
+
* 동작해야 합니다. 그때 이 데코레이터로 "의도적으로 보호 제외"임을
|
|
12
|
+
* 명시하는 것이 안전합니다.
|
|
13
|
+
*
|
|
14
|
+
* 동작 전제:
|
|
15
|
+
* forRoot.allowSystemActions: true 여야 실제로 작동.
|
|
16
|
+
* (켜지 않으면 라이브러리가 보안 우회 가능성을 차단)
|
|
17
|
+
*
|
|
18
|
+
* ─────────────────────────────────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* [사용 예시]
|
|
21
|
+
*
|
|
22
|
+
* @Injectable()
|
|
23
|
+
* @RequireTenant()
|
|
24
|
+
* export class MaintenanceService {
|
|
25
|
+
* // 일반 메서드 — tenant 컨텍스트 필요
|
|
26
|
+
* async cleanupForCurrentTenant() { ... }
|
|
27
|
+
*
|
|
28
|
+
* // 시스템 메서드 — tenant 컨텍스트 없이 실행 가능
|
|
29
|
+
* @SystemAction()
|
|
30
|
+
* async runDailyCleanupForAllTenants() {
|
|
31
|
+
* const tenants = await tenantRegistry.findAll();
|
|
32
|
+
* for (const t of tenants) {
|
|
33
|
+
* await runWithTenant(t.id, async () => {
|
|
34
|
+
* await this.cleanupForCurrentTenant();
|
|
35
|
+
* });
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
* }
|
|
39
|
+
*
|
|
40
|
+
* ─────────────────────────────────────────────────────────────
|
|
41
|
+
*/
|
|
42
|
+
function SystemAction() {
|
|
43
|
+
return function (_target, _propertyKey, descriptor) {
|
|
44
|
+
// 메서드 자체에 메타데이터 표식만 남깁니다.
|
|
45
|
+
// 실제 우회 로직은 @RequireTenant() 쪽에서 이 표식을 보고 처리.
|
|
46
|
+
Reflect.defineMetadata(constants_1.SYSTEM_ACTION_METADATA, true, descriptor.value);
|
|
47
|
+
return descriptor;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=system-action.decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"system-action.decorator.js","sourceRoot":"","sources":["../../src/decorators/system-action.decorator.ts"],"names":[],"mappings":";;AAuCA,oCAWC;AAlDD,4CAAsD;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,SAAgB,YAAY;IAC1B,OAAO,UACL,OAAY,EACZ,YAA6B,EAC7B,UAA8B;QAE9B,0BAA0B;QAC1B,8CAA8C;QAC9C,OAAO,CAAC,cAAc,CAAC,kCAAsB,EAAE,IAAI,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;QACvE,OAAO,UAAU,CAAC;IACpB,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { TenantContextOptions } from '../interfaces/tenant-context-options.interface';
|
|
2
|
+
/**
|
|
3
|
+
* ─────────────────────────────────────────────────────────────
|
|
4
|
+
* @TenantContext() — 백그라운드 큐 작업용 tenant 컨텍스트 복원 데코레이터.
|
|
5
|
+
*
|
|
6
|
+
* ⚠️ v0.2 정식 출시 예정. v0.1 스켈레톤만 존재.
|
|
7
|
+
*
|
|
8
|
+
* 동작:
|
|
9
|
+
* BullMQ/Bull의 @Process 핸들러에 붙이면, job payload에서 tenant ID를
|
|
10
|
+
* 꺼내 AsyncLocalStorage.run()으로 컨텍스트를 만든 채 핸들러를 실행.
|
|
11
|
+
*
|
|
12
|
+
* 결과적으로 HTTP 요청과 동일한 보호망(@RequireTenant, Subscriber 등)이
|
|
13
|
+
* 백그라운드 작업에서도 그대로 동작합니다.
|
|
14
|
+
*
|
|
15
|
+
* ─────────────────────────────────────────────────────────────
|
|
16
|
+
*
|
|
17
|
+
* [예시]
|
|
18
|
+
*
|
|
19
|
+
* @Processor('reports')
|
|
20
|
+
* export class ReportProcessor {
|
|
21
|
+
* @Process('monthly')
|
|
22
|
+
* @TenantContext() // job.data.tenantId 자동 추출
|
|
23
|
+
* async generateMonthlyReport(job: Job) { ... }
|
|
24
|
+
*
|
|
25
|
+
* @Process('quarterly')
|
|
26
|
+
* @TenantContext({ extractFrom: j => j.data.orgId }) // 커스텀 추출
|
|
27
|
+
* async generateQuarterly(job: Job) { ... }
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* ─────────────────────────────────────────────────────────────
|
|
31
|
+
*/
|
|
32
|
+
export declare function TenantContext(options?: TenantContextOptions): MethodDecorator;
|
|
33
|
+
//# sourceMappingURL=tenant-context.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-context.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/tenant-context.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,gDAAgD,CAAC;AAGtF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,eAAe,CAgCjF"}
|