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.
Files changed (167) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +612 -0
  3. package/dist/cache/cache.registry.d.ts +19 -0
  4. package/dist/cache/cache.registry.d.ts.map +1 -0
  5. package/dist/cache/cache.registry.js +51 -0
  6. package/dist/cache/cache.registry.js.map +1 -0
  7. package/dist/cache/cache.service.d.ts +44 -0
  8. package/dist/cache/cache.service.d.ts.map +1 -0
  9. package/dist/cache/cache.service.js +64 -0
  10. package/dist/cache/cache.service.js.map +1 -0
  11. package/dist/cache/index.d.ts +3 -0
  12. package/dist/cache/index.d.ts.map +1 -0
  13. package/dist/cache/index.js +19 -0
  14. package/dist/cache/index.js.map +1 -0
  15. package/dist/constants/index.d.ts +64 -0
  16. package/dist/constants/index.d.ts.map +1 -0
  17. package/dist/constants/index.js +67 -0
  18. package/dist/constants/index.js.map +1 -0
  19. package/dist/context/get-current-tenant-id.d.ts +30 -0
  20. package/dist/context/get-current-tenant-id.d.ts.map +1 -0
  21. package/dist/context/get-current-tenant-id.js +40 -0
  22. package/dist/context/get-current-tenant-id.js.map +1 -0
  23. package/dist/context/index.d.ts +7 -0
  24. package/dist/context/index.d.ts.map +1 -0
  25. package/dist/context/index.js +23 -0
  26. package/dist/context/index.js.map +1 -0
  27. package/dist/context/run-with-tenant.d.ts +84 -0
  28. package/dist/context/run-with-tenant.d.ts.map +1 -0
  29. package/dist/context/run-with-tenant.js +95 -0
  30. package/dist/context/run-with-tenant.js.map +1 -0
  31. package/dist/context/tenant-context.storage.d.ts +43 -0
  32. package/dist/context/tenant-context.storage.d.ts.map +1 -0
  33. package/dist/context/tenant-context.storage.js +45 -0
  34. package/dist/context/tenant-context.storage.js.map +1 -0
  35. package/dist/decorators/cacheable.decorator.d.ts +27 -0
  36. package/dist/decorators/cacheable.decorator.d.ts.map +1 -0
  37. package/dist/decorators/cacheable.decorator.js +108 -0
  38. package/dist/decorators/cacheable.decorator.js.map +1 -0
  39. package/dist/decorators/index.d.ts +13 -0
  40. package/dist/decorators/index.d.ts.map +1 -0
  41. package/dist/decorators/index.js +29 -0
  42. package/dist/decorators/index.js.map +1 -0
  43. package/dist/decorators/require-tenant.decorator.d.ts +41 -0
  44. package/dist/decorators/require-tenant.decorator.d.ts.map +1 -0
  45. package/dist/decorators/require-tenant.decorator.js +125 -0
  46. package/dist/decorators/require-tenant.decorator.js.map +1 -0
  47. package/dist/decorators/system-action.decorator.d.ts +39 -0
  48. package/dist/decorators/system-action.decorator.d.ts.map +1 -0
  49. package/dist/decorators/system-action.decorator.js +50 -0
  50. package/dist/decorators/system-action.decorator.js.map +1 -0
  51. package/dist/decorators/tenant-context.decorator.d.ts +33 -0
  52. package/dist/decorators/tenant-context.decorator.d.ts.map +1 -0
  53. package/dist/decorators/tenant-context.decorator.js +54 -0
  54. package/dist/decorators/tenant-context.decorator.js.map +1 -0
  55. package/dist/errors/cross-tenant-access.error.d.ts +29 -0
  56. package/dist/errors/cross-tenant-access.error.d.ts.map +1 -0
  57. package/dist/errors/cross-tenant-access.error.js +37 -0
  58. package/dist/errors/cross-tenant-access.error.js.map +1 -0
  59. package/dist/errors/index.d.ts +10 -0
  60. package/dist/errors/index.d.ts.map +1 -0
  61. package/dist/errors/index.js +26 -0
  62. package/dist/errors/index.js.map +1 -0
  63. package/dist/errors/invalid-tenant-source.error.d.ts +20 -0
  64. package/dist/errors/invalid-tenant-source.error.d.ts.map +1 -0
  65. package/dist/errors/invalid-tenant-source.error.js +28 -0
  66. package/dist/errors/invalid-tenant-source.error.js.map +1 -0
  67. package/dist/errors/missing-tenant-context.error.d.ts +22 -0
  68. package/dist/errors/missing-tenant-context.error.d.ts.map +1 -0
  69. package/dist/errors/missing-tenant-context.error.js +32 -0
  70. package/dist/errors/missing-tenant-context.error.js.map +1 -0
  71. package/dist/index.d.ts +36 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +56 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/interfaces/cacheable-options.interface.d.ts +37 -0
  76. package/dist/interfaces/cacheable-options.interface.d.ts.map +1 -0
  77. package/dist/interfaces/cacheable-options.interface.js +3 -0
  78. package/dist/interfaces/cacheable-options.interface.js.map +1 -0
  79. package/dist/interfaces/index.d.ts +13 -0
  80. package/dist/interfaces/index.d.ts.map +1 -0
  81. package/dist/interfaces/index.js +30 -0
  82. package/dist/interfaces/index.js.map +1 -0
  83. package/dist/interfaces/require-tenant-options.interface.d.ts +23 -0
  84. package/dist/interfaces/require-tenant-options.interface.d.ts.map +1 -0
  85. package/dist/interfaces/require-tenant-options.interface.js +3 -0
  86. package/dist/interfaces/require-tenant-options.interface.js.map +1 -0
  87. package/dist/interfaces/tenant-context-options.interface.d.ts +19 -0
  88. package/dist/interfaces/tenant-context-options.interface.d.ts.map +1 -0
  89. package/dist/interfaces/tenant-context-options.interface.js +3 -0
  90. package/dist/interfaces/tenant-context-options.interface.js.map +1 -0
  91. package/dist/interfaces/tenant-context.interface.d.ts +26 -0
  92. package/dist/interfaces/tenant-context.interface.d.ts.map +1 -0
  93. package/dist/interfaces/tenant-context.interface.js +3 -0
  94. package/dist/interfaces/tenant-context.interface.js.map +1 -0
  95. package/dist/interfaces/tenant-shield-options.interface.d.ts +141 -0
  96. package/dist/interfaces/tenant-shield-options.interface.d.ts.map +1 -0
  97. package/dist/interfaces/tenant-shield-options.interface.js +11 -0
  98. package/dist/interfaces/tenant-shield-options.interface.js.map +1 -0
  99. package/dist/middleware/index.d.ts +2 -0
  100. package/dist/middleware/index.d.ts.map +1 -0
  101. package/dist/middleware/index.js +18 -0
  102. package/dist/middleware/index.js.map +1 -0
  103. package/dist/middleware/tenant-context.middleware.d.ts +30 -0
  104. package/dist/middleware/tenant-context.middleware.d.ts.map +1 -0
  105. package/dist/middleware/tenant-context.middleware.js +68 -0
  106. package/dist/middleware/tenant-context.middleware.js.map +1 -0
  107. package/dist/options/index.d.ts +2 -0
  108. package/dist/options/index.d.ts.map +1 -0
  109. package/dist/options/index.js +18 -0
  110. package/dist/options/index.js.map +1 -0
  111. package/dist/options/options.registry.d.ts +8 -0
  112. package/dist/options/options.registry.d.ts.map +1 -0
  113. package/dist/options/options.registry.js +36 -0
  114. package/dist/options/options.registry.js.map +1 -0
  115. package/dist/resolvers/custom.resolver.d.ts +29 -0
  116. package/dist/resolvers/custom.resolver.d.ts.map +1 -0
  117. package/dist/resolvers/custom.resolver.js +47 -0
  118. package/dist/resolvers/custom.resolver.js.map +1 -0
  119. package/dist/resolvers/header.resolver.d.ts +22 -0
  120. package/dist/resolvers/header.resolver.d.ts.map +1 -0
  121. package/dist/resolvers/header.resolver.js +39 -0
  122. package/dist/resolvers/header.resolver.js.map +1 -0
  123. package/dist/resolvers/index.d.ts +13 -0
  124. package/dist/resolvers/index.d.ts.map +1 -0
  125. package/dist/resolvers/index.js +29 -0
  126. package/dist/resolvers/index.js.map +1 -0
  127. package/dist/resolvers/jwt.resolver.d.ts +35 -0
  128. package/dist/resolvers/jwt.resolver.d.ts.map +1 -0
  129. package/dist/resolvers/jwt.resolver.js +51 -0
  130. package/dist/resolvers/jwt.resolver.js.map +1 -0
  131. package/dist/resolvers/resolver.factory.d.ts +12 -0
  132. package/dist/resolvers/resolver.factory.d.ts.map +1 -0
  133. package/dist/resolvers/resolver.factory.js +43 -0
  134. package/dist/resolvers/resolver.factory.js.map +1 -0
  135. package/dist/resolvers/subdomain.resolver.d.ts +37 -0
  136. package/dist/resolvers/subdomain.resolver.d.ts.map +1 -0
  137. package/dist/resolvers/subdomain.resolver.js +57 -0
  138. package/dist/resolvers/subdomain.resolver.js.map +1 -0
  139. package/dist/resolvers/tenant-resolver.interface.d.ts +22 -0
  140. package/dist/resolvers/tenant-resolver.interface.d.ts.map +1 -0
  141. package/dist/resolvers/tenant-resolver.interface.js +3 -0
  142. package/dist/resolvers/tenant-resolver.interface.js.map +1 -0
  143. package/dist/tenant-shield.module.d.ts +88 -0
  144. package/dist/tenant-shield.module.d.ts.map +1 -0
  145. package/dist/tenant-shield.module.js +263 -0
  146. package/dist/tenant-shield.module.js.map +1 -0
  147. package/dist/testing/index.d.ts +12 -0
  148. package/dist/testing/index.d.ts.map +1 -0
  149. package/dist/testing/index.js +28 -0
  150. package/dist/testing/index.js.map +1 -0
  151. package/dist/testing/test-helpers.d.ts +52 -0
  152. package/dist/testing/test-helpers.d.ts.map +1 -0
  153. package/dist/testing/test-helpers.js +72 -0
  154. package/dist/testing/test-helpers.js.map +1 -0
  155. package/dist/typeorm/index.d.ts +10 -0
  156. package/dist/typeorm/index.d.ts.map +1 -0
  157. package/dist/typeorm/index.js +26 -0
  158. package/dist/typeorm/index.js.map +1 -0
  159. package/dist/typeorm/raw-sql.helper.d.ts +35 -0
  160. package/dist/typeorm/raw-sql.helper.d.ts.map +1 -0
  161. package/dist/typeorm/raw-sql.helper.js +24 -0
  162. package/dist/typeorm/raw-sql.helper.js.map +1 -0
  163. package/dist/typeorm/tenant.subscriber.d.ts +61 -0
  164. package/dist/typeorm/tenant.subscriber.d.ts.map +1 -0
  165. package/dist/typeorm/tenant.subscriber.js +487 -0
  166. package/dist/typeorm/tenant.subscriber.js.map +1 -0
  167. package/package.json +109 -0
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setGlobalCache = setGlobalCache;
4
+ exports.getGlobalCache = getGlobalCache;
5
+ exports.resetGlobalCache = resetGlobalCache;
6
+ /**
7
+ * ─────────────────────────────────────────────────────────────
8
+ * 글로벌 캐시 registry — @Cacheable 데코레이터의 DI 우회로.
9
+ *
10
+ * 배경:
11
+ * @Cacheable은 메서드 디스크립터를 직접 wrap 하기 때문에 NestJS DI
12
+ * 컨테이너에 접근할 수 없습니다. 그렇다고 사용자에게 매번 클래스에
13
+ * `cacheService` 프로퍼티를 inject 하라고 강제하면 불편합니다.
14
+ *
15
+ * 해결: 모듈이 onApplicationBootstrap 시점에 캐시 인스턴스를 이 작은
16
+ * 글로벌 registry에 등록 → 데코레이터는 registry에서 가져옴.
17
+ *
18
+ * 사용자가 클래스 인스턴스에 `cacheService`를 명시적으로 두면 그게
19
+ * 우선 사용되고, 없으면 글로벌 fallback으로 동작합니다.
20
+ *
21
+ * ⚠️ 이 패턴의 한계:
22
+ * - 한 프로세스에 하나의 캐시만 가능 (대부분 SaaS에서는 OK)
23
+ * - 테스트 격리를 위해 reset이 필요 → resetGlobalCache 제공
24
+ *
25
+ * ─────────────────────────────────────────────────────────────
26
+ */
27
+ let globalCache;
28
+ /**
29
+ * 모듈 bootstrap 단계에서 1회 호출.
30
+ * 라이브러리 외부에서 직접 호출할 일은 없습니다.
31
+ */
32
+ function setGlobalCache(cache) {
33
+ globalCache = cache;
34
+ }
35
+ /**
36
+ * @Cacheable 데코레이터가 fallback으로 사용하는 lookup 함수.
37
+ *
38
+ * 반환값:
39
+ * - TenantAwareCacheService 인스턴스 (모듈 bootstrap 후 정상 상태)
40
+ * - undefined (모듈이 아직 init되지 않았거나, 라이브러리 미사용 환경)
41
+ */
42
+ function getGlobalCache() {
43
+ return globalCache;
44
+ }
45
+ /**
46
+ * 테스트 격리용. 각 테스트 케이스 후 호출해서 다음 테스트에 영향을 안 미치게.
47
+ */
48
+ function resetGlobalCache() {
49
+ globalCache = undefined;
50
+ }
51
+ //# sourceMappingURL=cache.registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.registry.js","sourceRoot":"","sources":["../../src/cache/cache.registry.ts"],"names":[],"mappings":";;AA8BA,wCAEC;AASD,wCAEC;AAKD,4CAEC;AAhDD;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,IAAI,WAAgD,CAAC;AAErD;;;GAGG;AACH,SAAgB,cAAc,CAAC,KAA8B;IAC3D,WAAW,GAAG,KAAK,CAAC;AACtB,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,cAAc;IAC5B,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,SAAgB,gBAAgB;IAC9B,WAAW,GAAG,SAAS,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * ─────────────────────────────────────────────────────────────
3
+ * 캐시 백엔드 추상화.
4
+ *
5
+ * v0.1에서는 가장 단순한 in-memory Map 구현체를 기본 제공합니다.
6
+ * 프로덕션에서는 사용자가 Redis 등 외부 캐시로 교체할 수 있도록
7
+ * 인터페이스를 분리해 둡니다.
8
+ *
9
+ * 추후 nestjs-tenant-shield-redis 같은 어댑터 패키지로 분리 검토.
10
+ * ─────────────────────────────────────────────────────────────
11
+ */
12
+ /**
13
+ * 모든 캐시 구현체가 충족해야 하는 계약.
14
+ */
15
+ export interface TenantAwareCacheService {
16
+ get(key: string): Promise<unknown | undefined>;
17
+ set(key: string, value: unknown, ttlSeconds: number): Promise<void>;
18
+ del(key: string): Promise<void>;
19
+ }
20
+ /**
21
+ * ─────────────────────────────────────────────────────────────
22
+ * In-Memory 캐시 구현체 (기본 제공).
23
+ *
24
+ * 단일 프로세스 + 개발/테스트 환경에 적합합니다.
25
+ * 멀티 인스턴스 배포(여러 컨테이너)에서는 캐시가 공유되지 않으므로
26
+ * Redis 어댑터로 교체 권장.
27
+ * ─────────────────────────────────────────────────────────────
28
+ */
29
+ export declare class InMemoryTenantAwareCacheService implements TenantAwareCacheService {
30
+ /**
31
+ * 캐시 본체.
32
+ * value 옆에 expiresAt(ms epoch)을 저장해 lazy expiration 처리.
33
+ */
34
+ private readonly store;
35
+ get(key: string): Promise<unknown | undefined>;
36
+ set(key: string, value: unknown, ttlSeconds: number): Promise<void>;
37
+ del(key: string): Promise<void>;
38
+ /**
39
+ * 테스트나 tenant 데이터 삭제(GDPR purge) 시 활용.
40
+ * 특정 prefix(예: 'academy-A:')로 시작하는 모든 캐시 키 제거.
41
+ */
42
+ deleteByPrefix(prefix: string): Promise<void>;
43
+ }
44
+ //# sourceMappingURL=cache.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.service.d.ts","sourceRoot":"","sources":["../../src/cache/cache.service.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AAEH;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IAC/C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,qBACa,+BAAgC,YAAW,uBAAuB;IAC7E;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4D;IAE5E,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAY9C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOnE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrC;;;OAGG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAOpD"}
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ 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;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.InMemoryTenantAwareCacheService = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ /**
12
+ * ─────────────────────────────────────────────────────────────
13
+ * In-Memory 캐시 구현체 (기본 제공).
14
+ *
15
+ * 단일 프로세스 + 개발/테스트 환경에 적합합니다.
16
+ * 멀티 인스턴스 배포(여러 컨테이너)에서는 캐시가 공유되지 않으므로
17
+ * Redis 어댑터로 교체 권장.
18
+ * ─────────────────────────────────────────────────────────────
19
+ */
20
+ let InMemoryTenantAwareCacheService = class InMemoryTenantAwareCacheService {
21
+ constructor() {
22
+ /**
23
+ * 캐시 본체.
24
+ * value 옆에 expiresAt(ms epoch)을 저장해 lazy expiration 처리.
25
+ */
26
+ this.store = new Map();
27
+ }
28
+ async get(key) {
29
+ const entry = this.store.get(key);
30
+ if (!entry)
31
+ return undefined;
32
+ // TTL 만료 검사. 만료됐으면 삭제 후 미스로 처리.
33
+ if (entry.expiresAt < Date.now()) {
34
+ this.store.delete(key);
35
+ return undefined;
36
+ }
37
+ return entry.value;
38
+ }
39
+ async set(key, value, ttlSeconds) {
40
+ this.store.set(key, {
41
+ value,
42
+ expiresAt: Date.now() + ttlSeconds * 1000,
43
+ });
44
+ }
45
+ async del(key) {
46
+ this.store.delete(key);
47
+ }
48
+ /**
49
+ * 테스트나 tenant 데이터 삭제(GDPR purge) 시 활용.
50
+ * 특정 prefix(예: 'academy-A:')로 시작하는 모든 캐시 키 제거.
51
+ */
52
+ async deleteByPrefix(prefix) {
53
+ for (const key of this.store.keys()) {
54
+ if (key.startsWith(prefix)) {
55
+ this.store.delete(key);
56
+ }
57
+ }
58
+ }
59
+ };
60
+ exports.InMemoryTenantAwareCacheService = InMemoryTenantAwareCacheService;
61
+ exports.InMemoryTenantAwareCacheService = InMemoryTenantAwareCacheService = __decorate([
62
+ (0, common_1.Injectable)()
63
+ ], InMemoryTenantAwareCacheService);
64
+ //# sourceMappingURL=cache.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.service.js","sourceRoot":"","sources":["../../src/cache/cache.service.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4C;AAuB5C;;;;;;;;GAQG;AAEI,IAAM,+BAA+B,GAArC,MAAM,+BAA+B;IAArC;QACL;;;WAGG;QACc,UAAK,GAAG,IAAI,GAAG,EAAiD,CAAC;IAoCpF,CAAC;IAlCC,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAE7B,gCAAgC;QAChC,IAAI,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YACjC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACvB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAc,EAAE,UAAkB;QACvD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;YAClB,KAAK;YACL,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,GAAG,IAAI;SAC1C,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,cAAc,CAAC,MAAc;QACjC,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;YACpC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC3B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;IACH,CAAC;CACF,CAAA;AAzCY,0EAA+B;0CAA/B,+BAA+B;IAD3C,IAAA,mBAAU,GAAE;GACA,+BAA+B,CAyC3C"}
@@ -0,0 +1,3 @@
1
+ export * from './cache.service';
2
+ export * from './cache.registry';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC"}
@@ -0,0 +1,19 @@
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
+ __exportStar(require("./cache.service"), exports);
18
+ __exportStar(require("./cache.registry"), exports);
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,kDAAgC;AAChC,mDAAiC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * ─────────────────────────────────────────────────────────────
3
+ * 라이브러리 전역에서 공유하는 상수 모음.
4
+ *
5
+ * 두 종류가 들어 있습니다:
6
+ *
7
+ * 1) DI 토큰 — NestJS 의존성 주입 시 옵션 객체를 식별하는 키.
8
+ * 2) 메타데이터 키 — reflect-metadata로 메서드/클래스에 표식을 남길 때
9
+ * 사용하는 문자열 키. 다른 곳에서 같은 키를 쓰면 충돌이
10
+ * 나므로 라이브러리 prefix를 붙여 고유성을 확보합니다.
11
+ *
12
+ * 모든 키는 'nestjs-tenant-shield:' prefix를 사용 — 외부와 충돌 방지.
13
+ * ─────────────────────────────────────────────────────────────
14
+ */
15
+ /**
16
+ * forRoot()에서 받은 옵션 객체(TenantShieldOptions)를 DI 컨테이너에
17
+ * 등록할 때 사용하는 토큰.
18
+ *
19
+ * 미들웨어/Subscriber 등이 `@Inject(TENANT_SHIELD_OPTIONS)`로
20
+ * 같은 인스턴스를 받아 옵니다.
21
+ */
22
+ export declare const TENANT_SHIELD_OPTIONS: unique symbol;
23
+ /**
24
+ * 캐시 서비스(TenantAwareCacheService 구현체)를 DI에 등록할 때 쓰는 토큰.
25
+ *
26
+ * 기본값은 InMemoryTenantAwareCacheService(모듈에서 자동 등록).
27
+ * 사용자가 Redis 등으로 교체하려면:
28
+ *
29
+ * @Module({
30
+ * providers: [
31
+ * { provide: TENANT_SHIELD_CACHE, useClass: RedisCacheAdapter },
32
+ * ],
33
+ * })
34
+ */
35
+ export declare const TENANT_SHIELD_CACHE: unique symbol;
36
+ /**
37
+ * @RequireTenant()가 메서드에 남기는 메타데이터 키.
38
+ * Subscriber 또는 디버깅 도구가 "이 메서드가 보호 대상인지" 확인할 때 사용.
39
+ *
40
+ * 값은 RequireTenantOptions 객체.
41
+ */
42
+ export declare const REQUIRE_TENANT_METADATA = "nestjs-tenant-shield:require-tenant";
43
+ /**
44
+ * @SystemAction()이 메서드에 남기는 메타데이터 키.
45
+ * @RequireTenant()의 클래스 레벨 적용 시 이 표식이 있는 메서드는
46
+ * 자동 wrapping에서 건너뜁니다.
47
+ *
48
+ * 값은 boolean (true).
49
+ */
50
+ export declare const SYSTEM_ACTION_METADATA = "nestjs-tenant-shield:system-action";
51
+ /**
52
+ * @Cacheable()이 메서드에 남기는 메타데이터 키.
53
+ * (현재는 데코레이터가 디스크립터를 직접 감싸므로 동작에는 필수 아님.
54
+ * 향후 introspection / 디버깅 / 캐시 무효화 도구에서 사용 예정.)
55
+ */
56
+ export declare const CACHEABLE_METADATA = "nestjs-tenant-shield:cacheable";
57
+ /**
58
+ * 캐시 키의 tenant prefix 구분자.
59
+ * 'academy-A' + ':' + 'StudentsService.findAll' 처럼 조합.
60
+ *
61
+ * 변경 시 캐시 호환성이 깨지므로 메이저 버전 업데이트에서만 변경.
62
+ */
63
+ export declare const CACHE_KEY_DELIMITER = ":";
64
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/constants/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,eAAyC,CAAC;AAE5E;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,eAAuC,CAAC;AAExE;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,wCAAwC,CAAC;AAE7E;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,uCAAuC,CAAC;AAE3E;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,mCAAmC,CAAC;AAEnE;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,MAAM,CAAC"}
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ /**
3
+ * ─────────────────────────────────────────────────────────────
4
+ * 라이브러리 전역에서 공유하는 상수 모음.
5
+ *
6
+ * 두 종류가 들어 있습니다:
7
+ *
8
+ * 1) DI 토큰 — NestJS 의존성 주입 시 옵션 객체를 식별하는 키.
9
+ * 2) 메타데이터 키 — reflect-metadata로 메서드/클래스에 표식을 남길 때
10
+ * 사용하는 문자열 키. 다른 곳에서 같은 키를 쓰면 충돌이
11
+ * 나므로 라이브러리 prefix를 붙여 고유성을 확보합니다.
12
+ *
13
+ * 모든 키는 'nestjs-tenant-shield:' prefix를 사용 — 외부와 충돌 방지.
14
+ * ─────────────────────────────────────────────────────────────
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.CACHE_KEY_DELIMITER = exports.CACHEABLE_METADATA = exports.SYSTEM_ACTION_METADATA = exports.REQUIRE_TENANT_METADATA = exports.TENANT_SHIELD_CACHE = exports.TENANT_SHIELD_OPTIONS = void 0;
18
+ /**
19
+ * forRoot()에서 받은 옵션 객체(TenantShieldOptions)를 DI 컨테이너에
20
+ * 등록할 때 사용하는 토큰.
21
+ *
22
+ * 미들웨어/Subscriber 등이 `@Inject(TENANT_SHIELD_OPTIONS)`로
23
+ * 같은 인스턴스를 받아 옵니다.
24
+ */
25
+ exports.TENANT_SHIELD_OPTIONS = Symbol('nestjs-tenant-shield:options');
26
+ /**
27
+ * 캐시 서비스(TenantAwareCacheService 구현체)를 DI에 등록할 때 쓰는 토큰.
28
+ *
29
+ * 기본값은 InMemoryTenantAwareCacheService(모듈에서 자동 등록).
30
+ * 사용자가 Redis 등으로 교체하려면:
31
+ *
32
+ * @Module({
33
+ * providers: [
34
+ * { provide: TENANT_SHIELD_CACHE, useClass: RedisCacheAdapter },
35
+ * ],
36
+ * })
37
+ */
38
+ exports.TENANT_SHIELD_CACHE = Symbol('nestjs-tenant-shield:cache');
39
+ /**
40
+ * @RequireTenant()가 메서드에 남기는 메타데이터 키.
41
+ * Subscriber 또는 디버깅 도구가 "이 메서드가 보호 대상인지" 확인할 때 사용.
42
+ *
43
+ * 값은 RequireTenantOptions 객체.
44
+ */
45
+ exports.REQUIRE_TENANT_METADATA = 'nestjs-tenant-shield:require-tenant';
46
+ /**
47
+ * @SystemAction()이 메서드에 남기는 메타데이터 키.
48
+ * @RequireTenant()의 클래스 레벨 적용 시 이 표식이 있는 메서드는
49
+ * 자동 wrapping에서 건너뜁니다.
50
+ *
51
+ * 값은 boolean (true).
52
+ */
53
+ exports.SYSTEM_ACTION_METADATA = 'nestjs-tenant-shield:system-action';
54
+ /**
55
+ * @Cacheable()이 메서드에 남기는 메타데이터 키.
56
+ * (현재는 데코레이터가 디스크립터를 직접 감싸므로 동작에는 필수 아님.
57
+ * 향후 introspection / 디버깅 / 캐시 무효화 도구에서 사용 예정.)
58
+ */
59
+ exports.CACHEABLE_METADATA = 'nestjs-tenant-shield:cacheable';
60
+ /**
61
+ * 캐시 키의 tenant prefix 구분자.
62
+ * 'academy-A' + ':' + 'StudentsService.findAll' 처럼 조합.
63
+ *
64
+ * 변경 시 캐시 호환성이 깨지므로 메이저 버전 업데이트에서만 변경.
65
+ */
66
+ exports.CACHE_KEY_DELIMITER = ':';
67
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/constants/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;;AAEH;;;;;;GAMG;AACU,QAAA,qBAAqB,GAAG,MAAM,CAAC,8BAA8B,CAAC,CAAC;AAE5E;;;;;;;;;;;GAWG;AACU,QAAA,mBAAmB,GAAG,MAAM,CAAC,4BAA4B,CAAC,CAAC;AAExE;;;;;GAKG;AACU,QAAA,uBAAuB,GAAG,qCAAqC,CAAC;AAE7E;;;;;;GAMG;AACU,QAAA,sBAAsB,GAAG,oCAAoC,CAAC;AAE3E;;;;GAIG;AACU,QAAA,kBAAkB,GAAG,gCAAgC,CAAC;AAEnE;;;;;GAKG;AACU,QAAA,mBAAmB,GAAG,GAAG,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 현재 요청의 tenant ID를 조회합니다.
3
+ *
4
+ * 비즈니스 로직 안에서 "지금 누구의 데이터를 다루고 있는지" 알고 싶을 때 사용.
5
+ *
6
+ * 반환값:
7
+ * - string : 컨텍스트가 정상 설정된 경우 tenant ID
8
+ * - null : 컨텍스트가 없거나 시스템 작업인 경우
9
+ *
10
+ * ─────────────────────────────────────────────────────────────
11
+ *
12
+ * [사용 예시]
13
+ *
14
+ * @Injectable()
15
+ * export class AuditService {
16
+ * async log(action: string) {
17
+ * const tenantId = getCurrentTenantId();
18
+ * await this.repo.save({ action, tenantId, occurredAt: new Date() });
19
+ * }
20
+ * }
21
+ *
22
+ * ─────────────────────────────────────────────────────────────
23
+ *
24
+ * [주의]
25
+ * 이 함수는 절대 throw하지 않습니다. tenant가 없으면 그냥 null을 반환합니다.
26
+ * "없으면 거부"하는 보호 로직은 @RequireTenant() 데코레이터의 역할입니다.
27
+ * → 일반 비즈니스 코드에서 throw 책임이 분산되면 추적이 어려워지기 때문.
28
+ */
29
+ export declare function getCurrentTenantId(): string | null;
30
+ //# sourceMappingURL=get-current-tenant-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-current-tenant-id.d.ts","sourceRoot":"","sources":["../../src/context/get-current-tenant-id.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,IAAI,CAOlD"}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCurrentTenantId = getCurrentTenantId;
4
+ const tenant_context_storage_1 = require("./tenant-context.storage");
5
+ /**
6
+ * 현재 요청의 tenant ID를 조회합니다.
7
+ *
8
+ * 비즈니스 로직 안에서 "지금 누구의 데이터를 다루고 있는지" 알고 싶을 때 사용.
9
+ *
10
+ * 반환값:
11
+ * - string : 컨텍스트가 정상 설정된 경우 tenant ID
12
+ * - null : 컨텍스트가 없거나 시스템 작업인 경우
13
+ *
14
+ * ─────────────────────────────────────────────────────────────
15
+ *
16
+ * [사용 예시]
17
+ *
18
+ * @Injectable()
19
+ * export class AuditService {
20
+ * async log(action: string) {
21
+ * const tenantId = getCurrentTenantId();
22
+ * await this.repo.save({ action, tenantId, occurredAt: new Date() });
23
+ * }
24
+ * }
25
+ *
26
+ * ─────────────────────────────────────────────────────────────
27
+ *
28
+ * [주의]
29
+ * 이 함수는 절대 throw하지 않습니다. tenant가 없으면 그냥 null을 반환합니다.
30
+ * "없으면 거부"하는 보호 로직은 @RequireTenant() 데코레이터의 역할입니다.
31
+ * → 일반 비즈니스 코드에서 throw 책임이 분산되면 추적이 어려워지기 때문.
32
+ */
33
+ function getCurrentTenantId() {
34
+ // AsyncLocalStorage에서 현재 비동기 컨텍스트에 묶인 store를 가져옵니다.
35
+ // store가 undefined면 한 번도 storage.run()으로 감싸지지 않은 코드.
36
+ const store = tenant_context_storage_1.tenantContextStorage.getStore();
37
+ // 옵셔널 체이닝(?.)으로 안전하게 접근. 없으면 자연스럽게 null.
38
+ return store?.tenantId ?? null;
39
+ }
40
+ //# sourceMappingURL=get-current-tenant-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-current-tenant-id.js","sourceRoot":"","sources":["../../src/context/get-current-tenant-id.ts"],"names":[],"mappings":";;AA8BA,gDAOC;AArCD,qEAAgE;AAEhE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,SAAgB,kBAAkB;IAChC,oDAAoD;IACpD,qDAAqD;IACrD,MAAM,KAAK,GAAG,6CAAoB,CAAC,QAAQ,EAAE,CAAC;IAE9C,yCAAyC;IACzC,OAAO,KAAK,EAAE,QAAQ,IAAI,IAAI,CAAC;AACjC,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 컨텍스트 관련 public API 배럴 export.
3
+ */
4
+ export * from './tenant-context.storage';
5
+ export * from './get-current-tenant-id';
6
+ export * from './run-with-tenant';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,23 @@
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
+ * 컨텍스트 관련 public API 배럴 export.
19
+ */
20
+ __exportStar(require("./tenant-context.storage"), exports);
21
+ __exportStar(require("./get-current-tenant-id"), exports);
22
+ __exportStar(require("./run-with-tenant"), exports);
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA;;GAEG;AACH,2DAAyC;AACzC,0DAAwC;AACxC,oDAAkC"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * 명시적으로 tenant 컨텍스트를 설정한 채 비동기 함수를 실행합니다.
3
+ *
4
+ * 주 용도:
5
+ * 1) 테스트: 매 테스트 케이스마다 어떤 tenant인지 명시
6
+ * 2) 시스템 작업: 모든 tenant를 순회하는 cron 등
7
+ * 3) 마이그레이션 스크립트: 특정 tenant에 대해 직접 작업
8
+ *
9
+ * ─────────────────────────────────────────────────────────────
10
+ *
11
+ * [예시 1 — 테스트]
12
+ *
13
+ * await runWithTenant('academy-A', async () => {
14
+ * const students = await service.findAll();
15
+ * expect(students.every(s => s.tenantId === 'academy-A')).toBe(true);
16
+ * });
17
+ *
18
+ * [예시 2 — 모든 tenant 순회]
19
+ *
20
+ * for (const t of await tenantRegistry.findAll()) {
21
+ * await runWithTenant(t.id, async () => {
22
+ * await this.dailyCleanupService.run();
23
+ * });
24
+ * }
25
+ *
26
+ * ─────────────────────────────────────────────────────────────
27
+ */
28
+ export declare function runWithTenant<T>(tenantId: string, fn: () => Promise<T>): Promise<T>;
29
+ /**
30
+ * tenant 없이 시스템 작업을 실행합니다.
31
+ *
32
+ * forRoot.allowSystemActions가 true여야 의미가 있습니다.
33
+ * 컨텍스트에 isSystemAction: true 플래그를 박아 두어
34
+ * Subscriber/데코레이터가 "이건 의도된 무-tenant 실행"임을 알 수 있게 함.
35
+ *
36
+ * ─────────────────────────────────────────────────────────────
37
+ *
38
+ * 🚨 보안 경고
39
+ *
40
+ * 이 함수는 라이브러리의 모든 자동 격리 보호를 의도적으로 해제합니다.
41
+ * 잘못 쓰면 단 한 번의 호출로 전 tenant 데이터가 한 결과 셋에 섞여 나올 수
42
+ * 있고, 그 결과가 사용자에게 노출되면 회사가 멈춥니다.
43
+ *
44
+ * ✅ 쓰는 게 정당한 경우
45
+ * - 시스템 테이블(tenant_id 컬럼이 없는 메타 테이블) 접근
46
+ * - "모든 tenant 한 번에 집계"가 진짜 비즈니스 요구인 운영 통계
47
+ * - 마이그레이션/데이터 보정 스크립트 (운영자 직접 실행)
48
+ *
49
+ * ❌ 절대 쓰면 안 되는 경우
50
+ * - HTTP 요청 핸들러 (사용자 요청 안에서 호출되면 그 사용자에게 다른
51
+ * tenant 데이터가 흘러갈 가능성이 즉시 생김)
52
+ * - "테스트가 빨리 지나가게 하려고" 임시 우회
53
+ * - tenant ID 추출이 귀찮아서 우회 — 99%는 runWithTenant 루프가 정답
54
+ *
55
+ * 📋 운영 권장 사항
56
+ * - 호출 위치마다 보안 로그를 남길 것 (누가/언제/왜 실행했는지)
57
+ * - 가능하면 runWithTenant(t.id, ...) 루프로 대체할 수 있는지 먼저 검토
58
+ * - 코드 리뷰에서 이 함수 호출은 반드시 검토 대상으로 표시
59
+ *
60
+ * ─────────────────────────────────────────────────────────────
61
+ *
62
+ * [올바른 예시 — 시스템 테이블 접근]
63
+ *
64
+ * await runWithoutTenant(async () => {
65
+ * // tenant_id 컬럼이 없는 메타 테이블. 정당한 무-tenant 접근.
66
+ * return this.featureFlagsService.loadAll();
67
+ * });
68
+ *
69
+ * [잘못된 패턴 — 게으른 우회]
70
+ *
71
+ * // ❌ 이렇게 쓰지 마세요.
72
+ * await runWithoutTenant(async () => {
73
+ * return this.studentsService.findAll(); // 전 학원 학생 다 나옴
74
+ * });
75
+ *
76
+ * // ✅ 올바른 패턴 — 명시적 tenant 루프
77
+ * for (const t of await tenantRegistry.findAll()) {
78
+ * await runWithTenant(t.id, () => this.studentsService.cleanup());
79
+ * }
80
+ *
81
+ * ─────────────────────────────────────────────────────────────
82
+ */
83
+ export declare function runWithoutTenant<T>(fn: () => Promise<T>): Promise<T>;
84
+ //# sourceMappingURL=run-with-tenant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-with-tenant.d.ts","sourceRoot":"","sources":["../../src/context/run-with-tenant.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAC7B,QAAQ,EAAE,MAAM,EAChB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAIZ;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAEpE"}
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runWithTenant = runWithTenant;
4
+ exports.runWithoutTenant = runWithoutTenant;
5
+ const tenant_context_storage_1 = require("./tenant-context.storage");
6
+ /**
7
+ * 명시적으로 tenant 컨텍스트를 설정한 채 비동기 함수를 실행합니다.
8
+ *
9
+ * 주 용도:
10
+ * 1) 테스트: 매 테스트 케이스마다 어떤 tenant인지 명시
11
+ * 2) 시스템 작업: 모든 tenant를 순회하는 cron 등
12
+ * 3) 마이그레이션 스크립트: 특정 tenant에 대해 직접 작업
13
+ *
14
+ * ─────────────────────────────────────────────────────────────
15
+ *
16
+ * [예시 1 — 테스트]
17
+ *
18
+ * await runWithTenant('academy-A', async () => {
19
+ * const students = await service.findAll();
20
+ * expect(students.every(s => s.tenantId === 'academy-A')).toBe(true);
21
+ * });
22
+ *
23
+ * [예시 2 — 모든 tenant 순회]
24
+ *
25
+ * for (const t of await tenantRegistry.findAll()) {
26
+ * await runWithTenant(t.id, async () => {
27
+ * await this.dailyCleanupService.run();
28
+ * });
29
+ * }
30
+ *
31
+ * ─────────────────────────────────────────────────────────────
32
+ */
33
+ function runWithTenant(tenantId, fn) {
34
+ // storage.run()의 첫 번째 인자가 이 비동기 흐름의 store가 됩니다.
35
+ // 콜백 안에서 호출되는 모든 await/Promise 체인은 같은 store를 공유.
36
+ return tenant_context_storage_1.tenantContextStorage.run({ tenantId, isSystemAction: false }, fn);
37
+ }
38
+ /**
39
+ * tenant 없이 시스템 작업을 실행합니다.
40
+ *
41
+ * forRoot.allowSystemActions가 true여야 의미가 있습니다.
42
+ * 컨텍스트에 isSystemAction: true 플래그를 박아 두어
43
+ * Subscriber/데코레이터가 "이건 의도된 무-tenant 실행"임을 알 수 있게 함.
44
+ *
45
+ * ─────────────────────────────────────────────────────────────
46
+ *
47
+ * 🚨 보안 경고
48
+ *
49
+ * 이 함수는 라이브러리의 모든 자동 격리 보호를 의도적으로 해제합니다.
50
+ * 잘못 쓰면 단 한 번의 호출로 전 tenant 데이터가 한 결과 셋에 섞여 나올 수
51
+ * 있고, 그 결과가 사용자에게 노출되면 회사가 멈춥니다.
52
+ *
53
+ * ✅ 쓰는 게 정당한 경우
54
+ * - 시스템 테이블(tenant_id 컬럼이 없는 메타 테이블) 접근
55
+ * - "모든 tenant 한 번에 집계"가 진짜 비즈니스 요구인 운영 통계
56
+ * - 마이그레이션/데이터 보정 스크립트 (운영자 직접 실행)
57
+ *
58
+ * ❌ 절대 쓰면 안 되는 경우
59
+ * - HTTP 요청 핸들러 (사용자 요청 안에서 호출되면 그 사용자에게 다른
60
+ * tenant 데이터가 흘러갈 가능성이 즉시 생김)
61
+ * - "테스트가 빨리 지나가게 하려고" 임시 우회
62
+ * - tenant ID 추출이 귀찮아서 우회 — 99%는 runWithTenant 루프가 정답
63
+ *
64
+ * 📋 운영 권장 사항
65
+ * - 호출 위치마다 보안 로그를 남길 것 (누가/언제/왜 실행했는지)
66
+ * - 가능하면 runWithTenant(t.id, ...) 루프로 대체할 수 있는지 먼저 검토
67
+ * - 코드 리뷰에서 이 함수 호출은 반드시 검토 대상으로 표시
68
+ *
69
+ * ─────────────────────────────────────────────────────────────
70
+ *
71
+ * [올바른 예시 — 시스템 테이블 접근]
72
+ *
73
+ * await runWithoutTenant(async () => {
74
+ * // tenant_id 컬럼이 없는 메타 테이블. 정당한 무-tenant 접근.
75
+ * return this.featureFlagsService.loadAll();
76
+ * });
77
+ *
78
+ * [잘못된 패턴 — 게으른 우회]
79
+ *
80
+ * // ❌ 이렇게 쓰지 마세요.
81
+ * await runWithoutTenant(async () => {
82
+ * return this.studentsService.findAll(); // 전 학원 학생 다 나옴
83
+ * });
84
+ *
85
+ * // ✅ 올바른 패턴 — 명시적 tenant 루프
86
+ * for (const t of await tenantRegistry.findAll()) {
87
+ * await runWithTenant(t.id, () => this.studentsService.cleanup());
88
+ * }
89
+ *
90
+ * ─────────────────────────────────────────────────────────────
91
+ */
92
+ function runWithoutTenant(fn) {
93
+ return tenant_context_storage_1.tenantContextStorage.run({ tenantId: null, isSystemAction: true }, fn);
94
+ }
95
+ //# sourceMappingURL=run-with-tenant.js.map