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.JwtTenantResolver = void 0;
4
+ /**
5
+ * JWT 페이로드의 claim에서 tenant ID를 추출하는 resolver.
6
+ *
7
+ * 동작 전제:
8
+ * 사용자의 인증 미들웨어(예: Passport, @nestjs/jwt)가 이미
9
+ * JWT를 검증하고 페이로드를 request.user 또는 request.jwt에
10
+ * 넣어 둔 상태여야 합니다.
11
+ *
12
+ * 이 resolver는 그 객체에서 특정 키만 읽어옵니다.
13
+ * 즉, JWT 자체 검증은 이 라이브러리의 책임이 아님.
14
+ *
15
+ * ─────────────────────────────────────────────────────────────
16
+ *
17
+ * [예시 JWT 페이로드]
18
+ *
19
+ * {
20
+ * "sub": "user-123",
21
+ * "tenant_id": "academy-A",
22
+ * "exp": 1700000000
23
+ * }
24
+ *
25
+ * → jwtClaim: 'tenant_id'로 설정하면 'academy-A'를 추출.
26
+ *
27
+ * ─────────────────────────────────────────────────────────────
28
+ */
29
+ class JwtTenantResolver {
30
+ /**
31
+ * @param claimName - JWT 페이로드에서 읽을 키 이름. 기본 'tenant_id'.
32
+ */
33
+ constructor(claimName = 'tenant_id') {
34
+ this.claimName = claimName;
35
+ }
36
+ resolve(request) {
37
+ // 인증 미들웨어가 request.user 또는 request.jwt에 페이로드를 넣어 둔다는 가정.
38
+ // 두 위치 모두 확인하여 호환성 확보.
39
+ const req = request;
40
+ const payload = req.user ?? req.jwt;
41
+ if (!payload)
42
+ return null;
43
+ const value = payload[this.claimName];
44
+ // tenant ID는 문자열로만 받음. 숫자/객체 등은 보안상 거부.
45
+ if (typeof value !== 'string' || value.length === 0)
46
+ return null;
47
+ return value;
48
+ }
49
+ }
50
+ exports.JwtTenantResolver = JwtTenantResolver;
51
+ //# sourceMappingURL=jwt.resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwt.resolver.js","sourceRoot":"","sources":["../../src/resolvers/jwt.resolver.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAa,iBAAiB;IAC5B;;OAEG;IACH,YAA6B,YAAoB,WAAW;QAA/B,cAAS,GAAT,SAAS,CAAsB;IAAG,CAAC;IAEhE,OAAO,CAAC,OAAgB;QACtB,yDAAyD;QACzD,uBAAuB;QACvB,MAAM,GAAG,GAAG,OAGX,CAAC;QAEF,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC;QACpC,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAE1B,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAEtC,wCAAwC;QACxC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAEjE,OAAO,KAAK,CAAC;IACf,CAAC;CACF;AAxBD,8CAwBC"}
@@ -0,0 +1,12 @@
1
+ import { TenantShieldOptions } from '../interfaces/tenant-shield-options.interface';
2
+ import { TenantResolver } from './tenant-resolver.interface';
3
+ /**
4
+ * forRoot 옵션을 보고 적절한 TenantResolver 구현체를 만들어주는 팩토리 함수.
5
+ *
6
+ * 미들웨어는 어떤 source 종류인지 알 필요 없이 이 함수가 만들어준
7
+ * resolver의 resolve()만 호출하면 됩니다.
8
+ *
9
+ * 설정 오류는 부팅 시점에 즉시 throw (InvalidTenantSourceError).
10
+ */
11
+ export declare function createTenantResolver(options: TenantShieldOptions): TenantResolver;
12
+ //# sourceMappingURL=resolver.factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.factory.d.ts","sourceRoot":"","sources":["../../src/resolvers/resolver.factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,+CAA+C,CAAC;AAEpF,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAM7D;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,cAAc,CAsCjF"}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTenantResolver = createTenantResolver;
4
+ const invalid_tenant_source_error_1 = require("../errors/invalid-tenant-source.error");
5
+ const header_resolver_1 = require("./header.resolver");
6
+ const jwt_resolver_1 = require("./jwt.resolver");
7
+ const subdomain_resolver_1 = require("./subdomain.resolver");
8
+ const custom_resolver_1 = require("./custom.resolver");
9
+ /**
10
+ * forRoot 옵션을 보고 적절한 TenantResolver 구현체를 만들어주는 팩토리 함수.
11
+ *
12
+ * 미들웨어는 어떤 source 종류인지 알 필요 없이 이 함수가 만들어준
13
+ * resolver의 resolve()만 호출하면 됩니다.
14
+ *
15
+ * 설정 오류는 부팅 시점에 즉시 throw (InvalidTenantSourceError).
16
+ */
17
+ function createTenantResolver(options) {
18
+ switch (options.tenantSource) {
19
+ case 'header':
20
+ // headerName은 옵션이고 없으면 기본 'x-tenant-id' 사용.
21
+ return new header_resolver_1.HeaderTenantResolver(options.headerName);
22
+ case 'jwt':
23
+ return new jwt_resolver_1.JwtTenantResolver(options.jwtClaim);
24
+ case 'subdomain':
25
+ // subdomain은 패턴이 반드시 있어야 의미가 있으므로 누락 시 즉시 throw.
26
+ if (!options.subdomainPattern) {
27
+ throw new invalid_tenant_source_error_1.InvalidTenantSourceError("tenantSource='subdomain'을 사용하려면 subdomainPattern을 지정해야 합니다. 예: '*.yourapp.com'", 'subdomain');
28
+ }
29
+ return new subdomain_resolver_1.SubdomainTenantResolver(options.subdomainPattern);
30
+ case 'custom':
31
+ // custom은 함수가 반드시 있어야 함.
32
+ if (!options.customResolver) {
33
+ throw new invalid_tenant_source_error_1.InvalidTenantSourceError("tenantSource='custom'을 사용하려면 customResolver 함수를 제공해야 합니다.", 'custom');
34
+ }
35
+ return new custom_resolver_1.CustomTenantResolver(options.customResolver);
36
+ default:
37
+ // TypeScript의 exhaustiveness check 패턴.
38
+ // 새 source 종류가 추가됐는데 위 switch에 안 추가하면 컴파일 에러.
39
+ const _exhaustive = options.tenantSource;
40
+ throw new invalid_tenant_source_error_1.InvalidTenantSourceError(`알 수 없는 tenantSource: ${_exhaustive}`, String(_exhaustive));
41
+ }
42
+ }
43
+ //# sourceMappingURL=resolver.factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.factory.js","sourceRoot":"","sources":["../../src/resolvers/resolver.factory.ts"],"names":[],"mappings":";;AAgBA,oDAsCC;AArDD,uFAAiF;AAEjF,uDAAyD;AACzD,iDAAmD;AACnD,6DAA+D;AAC/D,uDAAyD;AAEzD;;;;;;;GAOG;AACH,SAAgB,oBAAoB,CAAC,OAA4B;IAC/D,QAAQ,OAAO,CAAC,YAAY,EAAE,CAAC;QAC7B,KAAK,QAAQ;YACX,4CAA4C;YAC5C,OAAO,IAAI,sCAAoB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAEtD,KAAK,KAAK;YACR,OAAO,IAAI,gCAAiB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAEjD,KAAK,WAAW;YACd,iDAAiD;YACjD,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;gBAC9B,MAAM,IAAI,sDAAwB,CAChC,gFAAgF,EAChF,WAAW,CACZ,CAAC;YACJ,CAAC;YACD,OAAO,IAAI,4CAAuB,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAE/D,KAAK,QAAQ;YACX,yBAAyB;YACzB,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC;gBAC5B,MAAM,IAAI,sDAAwB,CAChC,2DAA2D,EAC3D,QAAQ,CACT,CAAC;YACJ,CAAC;YACD,OAAO,IAAI,sCAAoB,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAE1D;YACE,uCAAuC;YACvC,8CAA8C;YAC9C,MAAM,WAAW,GAAU,OAAO,CAAC,YAAY,CAAC;YAChD,MAAM,IAAI,sDAAwB,CAChC,wBAAwB,WAAW,EAAE,EACrC,MAAM,CAAC,WAAW,CAAC,CACpB,CAAC;IACN,CAAC;AACH,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { TenantResolver } from './tenant-resolver.interface';
2
+ /**
3
+ * 호스트 이름의 서브도메인 부분에서 tenant ID를 추출하는 resolver.
4
+ *
5
+ * 사용 예:
6
+ * pattern : '*.yourapp.com'
7
+ * 요청 host: 'academy-a.yourapp.com'
8
+ * → tenant : 'academy-a'
9
+ *
10
+ * ─────────────────────────────────────────────────────────────
11
+ *
12
+ * 장점:
13
+ * - URL만 봐도 어느 tenant인지 명확 (브랜딩 효과)
14
+ * - 클라이언트가 명시적으로 헤더를 보낼 필요 없음
15
+ *
16
+ * 단점/주의:
17
+ * - DNS 와일드카드 + TLS 인증서 와일드카드 설정 필요
18
+ * - 로컬 개발 환경에서 /etc/hosts 또는 *.localtest.me 같은 도메인 필요
19
+ * - x-forwarded-host 같은 프록시 헤더 신뢰 정책 사전 점검 필수
20
+ *
21
+ * ─────────────────────────────────────────────────────────────
22
+ */
23
+ export declare class SubdomainTenantResolver implements TenantResolver {
24
+ /**
25
+ * 사용자가 넘긴 pattern에서 와일드카드 자리를 찾아낸 정규식.
26
+ * 생성 시점에 한 번만 컴파일해 둡니다 (요청마다 새로 만들면 느림).
27
+ */
28
+ private readonly matcher;
29
+ /**
30
+ * @param pattern - 와일드카드 도메인 패턴.
31
+ * '*' 자리에 들어가는 부분이 tenant ID로 사용됩니다.
32
+ * 예: '*.yourapp.com', '*.staging.yourapp.com'
33
+ */
34
+ constructor(pattern: string);
35
+ resolve(request: unknown): string | null;
36
+ }
37
+ //# sourceMappingURL=subdomain.resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subdomain.resolver.d.ts","sourceRoot":"","sources":["../../src/resolvers/subdomain.resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,uBAAwB,YAAW,cAAc;IAC5D;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC;;;;OAIG;gBACS,OAAO,EAAE,MAAM;IAU3B,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI;CAoBzC"}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SubdomainTenantResolver = void 0;
4
+ /**
5
+ * 호스트 이름의 서브도메인 부분에서 tenant ID를 추출하는 resolver.
6
+ *
7
+ * 사용 예:
8
+ * pattern : '*.yourapp.com'
9
+ * 요청 host: 'academy-a.yourapp.com'
10
+ * → tenant : 'academy-a'
11
+ *
12
+ * ─────────────────────────────────────────────────────────────
13
+ *
14
+ * 장점:
15
+ * - URL만 봐도 어느 tenant인지 명확 (브랜딩 효과)
16
+ * - 클라이언트가 명시적으로 헤더를 보낼 필요 없음
17
+ *
18
+ * 단점/주의:
19
+ * - DNS 와일드카드 + TLS 인증서 와일드카드 설정 필요
20
+ * - 로컬 개발 환경에서 /etc/hosts 또는 *.localtest.me 같은 도메인 필요
21
+ * - x-forwarded-host 같은 프록시 헤더 신뢰 정책 사전 점검 필수
22
+ *
23
+ * ─────────────────────────────────────────────────────────────
24
+ */
25
+ class SubdomainTenantResolver {
26
+ /**
27
+ * @param pattern - 와일드카드 도메인 패턴.
28
+ * '*' 자리에 들어가는 부분이 tenant ID로 사용됩니다.
29
+ * 예: '*.yourapp.com', '*.staging.yourapp.com'
30
+ */
31
+ constructor(pattern) {
32
+ // '*.yourapp.com' → '^([^.]+)\.yourapp\.com$' 형태로 변환.
33
+ // - '*' → '([^.]+)' (도트가 아닌 한 글자 이상, 캡처 그룹)
34
+ // - '.' → '\.' (정규식에서 '.' 이스케이프)
35
+ const escaped = pattern
36
+ .replace(/\./g, '\\.')
37
+ .replace(/\*/g, '([^.]+)');
38
+ this.matcher = new RegExp(`^${escaped}$`);
39
+ }
40
+ resolve(request) {
41
+ // Express는 req.hostname, Fastify는 req.hostname 또는 req.headers.host를 사용.
42
+ // 둘 모두를 fallback으로 시도해 호환성 확보.
43
+ const req = request;
44
+ const host = req.hostname ?? req.headers?.host;
45
+ if (!host)
46
+ return null;
47
+ // 포트가 붙어 있으면 (예: localhost:3000) 제거.
48
+ const hostWithoutPort = host.split(':')[0];
49
+ const match = this.matcher.exec(hostWithoutPort);
50
+ if (!match)
51
+ return null;
52
+ // 첫 번째 캡처 그룹이 tenant 부분.
53
+ return match[1] ?? null;
54
+ }
55
+ }
56
+ exports.SubdomainTenantResolver = SubdomainTenantResolver;
57
+ //# sourceMappingURL=subdomain.resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subdomain.resolver.js","sourceRoot":"","sources":["../../src/resolvers/subdomain.resolver.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAa,uBAAuB;IAOlC;;;;OAIG;IACH,YAAY,OAAe;QACzB,sDAAsD;QACtD,6CAA6C;QAC7C,uCAAuC;QACvC,MAAM,OAAO,GAAG,OAAO;aACpB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;aACrB,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,IAAI,MAAM,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC;IAC5C,CAAC;IAED,OAAO,CAAC,OAAgB;QACtB,wEAAwE;QACxE,+BAA+B;QAC/B,MAAM,GAAG,GAAG,OAGX,CAAC;QAEF,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC;QAC/C,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QAEvB,qCAAqC;QACrC,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAE3C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QACjD,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAExB,yBAAyB;QACzB,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAC1B,CAAC;CACF;AA1CD,0DA0CC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Tenant ID를 요청에서 추출하는 모든 resolver가 구현해야 하는 공통 인터페이스.
3
+ *
4
+ * 전략 패턴(Strategy Pattern):
5
+ * - HeaderTenantResolver
6
+ * - JwtTenantResolver
7
+ * - SubdomainTenantResolver
8
+ * - CustomTenantResolver
9
+ *
10
+ * 모두 같은 resolve() 시그니처를 갖기 때문에, 미들웨어는 어떤 구현체인지
11
+ * 신경 쓰지 않고 단일 코드로 처리할 수 있습니다.
12
+ */
13
+ export interface TenantResolver {
14
+ /**
15
+ * 들어온 요청 객체에서 tenant ID를 추출합니다.
16
+ *
17
+ * @param request - Express/Fastify의 Request 객체. 라이브러리는 둘 다 지원해야 하므로 unknown.
18
+ * @returns tenant ID 문자열 또는 null (찾지 못한 경우)
19
+ */
20
+ resolve(request: unknown): string | null;
21
+ }
22
+ //# sourceMappingURL=tenant-resolver.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant-resolver.interface.d.ts","sourceRoot":"","sources":["../../src/resolvers/tenant-resolver.interface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAAC;CAC1C"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=tenant-resolver.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant-resolver.interface.js","sourceRoot":"","sources":["../../src/resolvers/tenant-resolver.interface.ts"],"names":[],"mappings":""}
@@ -0,0 +1,88 @@
1
+ import { DynamicModule, MiddlewareConsumer, NestModule, OnApplicationBootstrap } from '@nestjs/common';
2
+ import { ModuleRef } from '@nestjs/core';
3
+ import { TenantShieldAsyncOptions, TenantShieldOptions } from './interfaces/tenant-shield-options.interface';
4
+ import { TenantAwareCacheService } from './cache/cache.service';
5
+ import { TenantSubscriber } from './typeorm/tenant.subscriber';
6
+ /**
7
+ * ─────────────────────────────────────────────────────────────
8
+ * TenantShieldModule — 사용자가 AppModule에서 import 하는 메인 모듈.
9
+ *
10
+ * @Module({
11
+ * imports: [
12
+ * TenantShieldModule.forRoot({ ... }),
13
+ * ],
14
+ * })
15
+ * export class AppModule {}
16
+ *
17
+ * 역할:
18
+ * 1) forRoot 옵션을 DI 컨테이너에 등록
19
+ * 2) TenantContextMiddleware를 모든 라우트에 자동 적용
20
+ * 3) TenantSubscriber 인스턴스를 만들고, 부팅 시점에 TypeORM
21
+ * DataSource의 subscribers 배열에 "자동 등록"
22
+ * 4) 캐시 서비스 인스턴스 제공 (사용자 교체 가능)
23
+ *
24
+ * Subscriber 자동 등록의 중요성:
25
+ * TypeORM 0.3에서는 NestJS provider로 등록되었다고 해서 자동으로
26
+ * DataSource가 알아보지 못합니다. dataSource.subscribers 배열에
27
+ * 명시적으로 들어가야 hook이 실제로 호출됩니다.
28
+ *
29
+ * v0.0.2까지는 이 와이어링이 빠져 있어, 사용자가 TypeOrmModule.forRoot
30
+ * 설정에 subscribers를 직접 적어야 했습니다. v0.0.3에서 모듈이
31
+ * onApplicationBootstrap에서 자동으로 잡아 push 합니다.
32
+ *
33
+ * TypeORM이 없는(또는 다른 ORM을 쓰는) 사용자는 lookup이 실패하지만
34
+ * try/catch로 silently skip — 라이브러리 부팅 자체는 깨지지 않음.
35
+ * ─────────────────────────────────────────────────────────────
36
+ */
37
+ export declare class TenantShieldModule implements NestModule, OnApplicationBootstrap {
38
+ private readonly moduleRef;
39
+ private readonly subscriber;
40
+ private readonly options;
41
+ private readonly cache;
42
+ /**
43
+ * onApplicationBootstrap 등에서 동적 lookup용으로 사용할 ModuleRef.
44
+ * forRoot가 만든 DynamicModule의 providers에 명시적으로 추가될 필요는 없음
45
+ * — Nest가 기본으로 주입해 줍니다.
46
+ */
47
+ private static readonly logger;
48
+ constructor(moduleRef: ModuleRef, subscriber: TenantSubscriber, options: TenantShieldOptions, cache: TenantAwareCacheService);
49
+ /**
50
+ * 동기 옵션으로 모듈 초기화.
51
+ * 가장 흔한 사용 방식.
52
+ *
53
+ * 반환 객체에 global: true를 박아두어 어떤 모듈에서든 imports 없이
54
+ * TENANT_SHIELD_OPTIONS / TENANT_SHIELD_CACHE를 주입받을 수 있게 함.
55
+ */
56
+ static forRoot(options: TenantShieldOptions): DynamicModule;
57
+ /**
58
+ * 비동기 옵션 (v0.1.x 패치에서 활성화 예정).
59
+ * ConfigService 등 다른 모듈의 값을 사용해 옵션을 만들고 싶을 때.
60
+ */
61
+ static forRootAsync(options: TenantShieldAsyncOptions): DynamicModule;
62
+ /**
63
+ * NestModule.configure — 미들웨어를 라우트에 적용하는 위치.
64
+ *
65
+ * forRoutes('*')는 NestJS v10 + Express의 path-to-regexp v6에서
66
+ * 깨지는 케이스가 있어, 명시적으로 { path, method } 형태를 사용.
67
+ *
68
+ * 만약 특정 라우트(예: /health, /metrics)는 제외하고 싶다면,
69
+ * 사용자가 별도 옵션으로 exclude 패턴을 넘길 수 있게 v0.1.x에서 확장 예정.
70
+ */
71
+ configure(consumer: MiddlewareConsumer): void;
72
+ /**
73
+ * 모든 모듈이 init된 직후 호출되는 라이프사이클.
74
+ *
75
+ * 이 시점에는:
76
+ * - TypeOrmModule.forRoot가 만든 DataSource가 DI 컨테이너에 등록되어 있음
77
+ * - 우리가 만든 TenantSubscriber 인스턴스도 주입 가능
78
+ *
79
+ * 따라서 여기서 한 번만 DataSource.subscribers 배열에 push 하면
80
+ * 그 다음부터 일어나는 모든 entity 조작 hook(beforeInsert/afterLoad 등)이
81
+ * 자동으로 호출됩니다.
82
+ *
83
+ * 사용자가 직접 TypeOrmModule.forRoot({ subscribers: [TenantSubscriber] })를
84
+ * 적을 필요 없음 — 라이브러리가 알아서 등록.
85
+ */
86
+ onApplicationBootstrap(): Promise<void>;
87
+ }
88
+ //# sourceMappingURL=tenant-shield.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant-shield.module.d.ts","sourceRoot":"","sources":["../src/tenant-shield.module.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EAIb,kBAAkB,EAElB,UAAU,EACV,sBAAsB,EAGvB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EACL,wBAAwB,EAExB,mBAAmB,EACpB,MAAM,8CAA8C,CAAC;AAGtD,OAAO,EAAmC,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAGjG,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAEa,kBAAmB,YAAW,UAAU,EAAE,sBAAsB;IASzE,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAE3B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAExB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAbxB;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAoC;gBAG/C,SAAS,EAAE,SAAS,EACpB,UAAU,EAAE,gBAAgB,EAE5B,OAAO,EAAE,mBAAmB,EAE5B,KAAK,EAAE,uBAAuB;IAGjD;;;;;;OAMG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,mBAAmB,GAAG,aAAa;IA8B3D;;;OAGG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,wBAAwB,GAAG,aAAa;IA+BrE;;;;;;;;OAQG;IACH,SAAS,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI;IAM7C;;;;;;;;;;;;;OAaG;IACG,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;CAqC9C"}
@@ -0,0 +1,263 @@
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
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var TenantShieldModule_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.TenantShieldModule = void 0;
17
+ const common_1 = require("@nestjs/common");
18
+ const core_1 = require("@nestjs/core");
19
+ const typeorm_1 = require("typeorm");
20
+ const constants_1 = require("./constants");
21
+ const tenant_context_middleware_1 = require("./middleware/tenant-context.middleware");
22
+ const cache_service_1 = require("./cache/cache.service");
23
+ const cache_registry_1 = require("./cache/cache.registry");
24
+ const options_registry_1 = require("./options/options.registry");
25
+ const tenant_subscriber_1 = require("./typeorm/tenant.subscriber");
26
+ /**
27
+ * ─────────────────────────────────────────────────────────────
28
+ * TenantShieldModule — 사용자가 AppModule에서 import 하는 메인 모듈.
29
+ *
30
+ * @Module({
31
+ * imports: [
32
+ * TenantShieldModule.forRoot({ ... }),
33
+ * ],
34
+ * })
35
+ * export class AppModule {}
36
+ *
37
+ * 역할:
38
+ * 1) forRoot 옵션을 DI 컨테이너에 등록
39
+ * 2) TenantContextMiddleware를 모든 라우트에 자동 적용
40
+ * 3) TenantSubscriber 인스턴스를 만들고, 부팅 시점에 TypeORM
41
+ * DataSource의 subscribers 배열에 "자동 등록"
42
+ * 4) 캐시 서비스 인스턴스 제공 (사용자 교체 가능)
43
+ *
44
+ * Subscriber 자동 등록의 중요성:
45
+ * TypeORM 0.3에서는 NestJS provider로 등록되었다고 해서 자동으로
46
+ * DataSource가 알아보지 못합니다. dataSource.subscribers 배열에
47
+ * 명시적으로 들어가야 hook이 실제로 호출됩니다.
48
+ *
49
+ * v0.0.2까지는 이 와이어링이 빠져 있어, 사용자가 TypeOrmModule.forRoot
50
+ * 설정에 subscribers를 직접 적어야 했습니다. v0.0.3에서 모듈이
51
+ * onApplicationBootstrap에서 자동으로 잡아 push 합니다.
52
+ *
53
+ * TypeORM이 없는(또는 다른 ORM을 쓰는) 사용자는 lookup이 실패하지만
54
+ * try/catch로 silently skip — 라이브러리 부팅 자체는 깨지지 않음.
55
+ * ─────────────────────────────────────────────────────────────
56
+ */
57
+ let TenantShieldModule = TenantShieldModule_1 = class TenantShieldModule {
58
+ constructor(moduleRef, subscriber, options, cache) {
59
+ this.moduleRef = moduleRef;
60
+ this.subscriber = subscriber;
61
+ this.options = options;
62
+ this.cache = cache;
63
+ }
64
+ /**
65
+ * 동기 옵션으로 모듈 초기화.
66
+ * 가장 흔한 사용 방식.
67
+ *
68
+ * 반환 객체에 global: true를 박아두어 어떤 모듈에서든 imports 없이
69
+ * TENANT_SHIELD_OPTIONS / TENANT_SHIELD_CACHE를 주입받을 수 있게 함.
70
+ */
71
+ static forRoot(options) {
72
+ return {
73
+ module: TenantShieldModule_1,
74
+ global: true,
75
+ providers: [
76
+ // 옵션을 토큰으로 등록 → 다른 곳에서 @Inject로 가져갈 수 있게.
77
+ {
78
+ provide: constants_1.TENANT_SHIELD_OPTIONS,
79
+ useValue: options,
80
+ },
81
+ // TypeORM Subscriber를 옵션과 함께 인스턴스화.
82
+ {
83
+ provide: tenant_subscriber_1.TenantSubscriber,
84
+ useFactory: (opts) => new tenant_subscriber_1.TenantSubscriber(opts),
85
+ inject: [constants_1.TENANT_SHIELD_OPTIONS],
86
+ },
87
+ // 캐시 서비스 provider — 사용자 옵션을 보고 형태 결정.
88
+ // 미지정 시 InMemoryTenantAwareCacheService(개발/단일 프로세스용).
89
+ buildCacheProvider(options.cache),
90
+ // 미들웨어 자체도 NestJS provider로 등록해 DI 동작 확보.
91
+ tenant_context_middleware_1.TenantContextMiddleware,
92
+ ],
93
+ exports: [
94
+ constants_1.TENANT_SHIELD_OPTIONS,
95
+ constants_1.TENANT_SHIELD_CACHE,
96
+ tenant_subscriber_1.TenantSubscriber,
97
+ ],
98
+ };
99
+ }
100
+ /**
101
+ * 비동기 옵션 (v0.1.x 패치에서 활성화 예정).
102
+ * ConfigService 등 다른 모듈의 값을 사용해 옵션을 만들고 싶을 때.
103
+ */
104
+ static forRootAsync(options) {
105
+ const asyncProvider = {
106
+ provide: constants_1.TENANT_SHIELD_OPTIONS,
107
+ useFactory: options.useFactory,
108
+ inject: options.inject ?? [],
109
+ };
110
+ return {
111
+ module: TenantShieldModule_1,
112
+ global: true,
113
+ imports: options.imports ?? [],
114
+ providers: [
115
+ asyncProvider,
116
+ {
117
+ provide: tenant_subscriber_1.TenantSubscriber,
118
+ useFactory: (opts) => new tenant_subscriber_1.TenantSubscriber(opts),
119
+ inject: [constants_1.TENANT_SHIELD_OPTIONS],
120
+ },
121
+ // 비동기 옵션에서는 cache provider도 동적으로 풀어줘야 함.
122
+ // 옵션 객체가 비동기로 만들어지므로 factory에서 cache 슬롯을 읽어 적용.
123
+ {
124
+ provide: constants_1.TENANT_SHIELD_CACHE,
125
+ useFactory: (opts) => buildCacheInstance(opts.cache),
126
+ inject: [constants_1.TENANT_SHIELD_OPTIONS],
127
+ },
128
+ tenant_context_middleware_1.TenantContextMiddleware,
129
+ ],
130
+ exports: [constants_1.TENANT_SHIELD_OPTIONS, constants_1.TENANT_SHIELD_CACHE, tenant_subscriber_1.TenantSubscriber],
131
+ };
132
+ }
133
+ /**
134
+ * NestModule.configure — 미들웨어를 라우트에 적용하는 위치.
135
+ *
136
+ * forRoutes('*')는 NestJS v10 + Express의 path-to-regexp v6에서
137
+ * 깨지는 케이스가 있어, 명시적으로 { path, method } 형태를 사용.
138
+ *
139
+ * 만약 특정 라우트(예: /health, /metrics)는 제외하고 싶다면,
140
+ * 사용자가 별도 옵션으로 exclude 패턴을 넘길 수 있게 v0.1.x에서 확장 예정.
141
+ */
142
+ configure(consumer) {
143
+ consumer
144
+ .apply(tenant_context_middleware_1.TenantContextMiddleware)
145
+ .forRoutes({ path: '*', method: common_1.RequestMethod.ALL });
146
+ }
147
+ /**
148
+ * 모든 모듈이 init된 직후 호출되는 라이프사이클.
149
+ *
150
+ * 이 시점에는:
151
+ * - TypeOrmModule.forRoot가 만든 DataSource가 DI 컨테이너에 등록되어 있음
152
+ * - 우리가 만든 TenantSubscriber 인스턴스도 주입 가능
153
+ *
154
+ * 따라서 여기서 한 번만 DataSource.subscribers 배열에 push 하면
155
+ * 그 다음부터 일어나는 모든 entity 조작 hook(beforeInsert/afterLoad 등)이
156
+ * 자동으로 호출됩니다.
157
+ *
158
+ * 사용자가 직접 TypeOrmModule.forRoot({ subscribers: [TenantSubscriber] })를
159
+ * 적을 필요 없음 — 라이브러리가 알아서 등록.
160
+ */
161
+ async onApplicationBootstrap() {
162
+ // 1) DataSource 동적 lookup.
163
+ // strict: false 옵션은 "현재 모듈 스코프 밖이라도 찾아라"의 의미.
164
+ // TypeORM이 아예 없는 사용자는 여기서 throw → catch로 무시.
165
+ let dataSource = null;
166
+ try {
167
+ dataSource = this.moduleRef.get(typeorm_1.DataSource, { strict: false });
168
+ }
169
+ catch {
170
+ // 미사용자 케이스. 무시.
171
+ }
172
+ if (dataSource) {
173
+ // 이미 등록되어 있으면 중복 push 방지.
174
+ if (!dataSource.subscribers.includes(this.subscriber)) {
175
+ dataSource.subscribers.push(this.subscriber);
176
+ TenantShieldModule_1.logger.log('TenantSubscriber가 DataSource에 자동 등록되었습니다.');
177
+ }
178
+ }
179
+ else {
180
+ // TypeORM이 안 쓰이는 환경 — 다른 ORM(Prisma v0.2, Mongoose v0.3) 사용자.
181
+ // 디버그 로그만 남기고 종료.
182
+ TenantShieldModule_1.logger.debug('TypeORM DataSource를 찾지 못했습니다. Subscriber 자동 등록은 건너뜁니다. ' +
183
+ '(TypeORM을 사용하지 않는 경우 정상)');
184
+ }
185
+ // 2) 글로벌 옵션 registry에 forRoot 옵션 등록.
186
+ // @RequireTenant wrapMethod가 런타임에 allowSystemActions 등을 읽기 위해 사용.
187
+ (0, options_registry_1.setGlobalOptions)(this.options);
188
+ // 3) 캐시 인스턴스를 글로벌 registry에 등록.
189
+ // @Cacheable 데코레이터가 사용자 클래스에 cacheService 프로퍼티가 없을 때
190
+ // 이 registry에서 fallback 인스턴스를 가져옵니다.
191
+ (0, cache_registry_1.setGlobalCache)(this.cache);
192
+ }
193
+ };
194
+ exports.TenantShieldModule = TenantShieldModule;
195
+ /**
196
+ * onApplicationBootstrap 등에서 동적 lookup용으로 사용할 ModuleRef.
197
+ * forRoot가 만든 DynamicModule의 providers에 명시적으로 추가될 필요는 없음
198
+ * — Nest가 기본으로 주입해 줍니다.
199
+ */
200
+ TenantShieldModule.logger = new common_1.Logger('TenantShieldModule');
201
+ exports.TenantShieldModule = TenantShieldModule = TenantShieldModule_1 = __decorate([
202
+ (0, common_1.Global)(),
203
+ (0, common_1.Module)({}),
204
+ __param(2, (0, common_1.Inject)(constants_1.TENANT_SHIELD_OPTIONS)),
205
+ __param(3, (0, common_1.Inject)(constants_1.TENANT_SHIELD_CACHE)),
206
+ __metadata("design:paramtypes", [core_1.ModuleRef,
207
+ tenant_subscriber_1.TenantSubscriber, Object, Object])
208
+ ], TenantShieldModule);
209
+ /**
210
+ * forRoot에서 사용자 cache 옵션을 보고 적절한 NestJS Provider 객체를 만들어줍니다.
211
+ *
212
+ * 세 가지 형태(useFactory / useValue / useClass)를 모두 지원하고, 어느 것도
213
+ * 안 주면 InMemoryTenantAwareCacheService를 기본으로 사용.
214
+ *
215
+ * 우선순위: useFactory > useValue > useClass > (default InMemory)
216
+ */
217
+ function buildCacheProvider(opts) {
218
+ if (opts?.useFactory) {
219
+ return {
220
+ provide: constants_1.TENANT_SHIELD_CACHE,
221
+ useFactory: opts.useFactory,
222
+ inject: opts.inject ?? [],
223
+ };
224
+ }
225
+ if (opts?.useValue !== undefined) {
226
+ return {
227
+ provide: constants_1.TENANT_SHIELD_CACHE,
228
+ useValue: opts.useValue,
229
+ };
230
+ }
231
+ if (opts?.useClass) {
232
+ return {
233
+ provide: constants_1.TENANT_SHIELD_CACHE,
234
+ useClass: opts.useClass,
235
+ };
236
+ }
237
+ // 기본 — 단일 프로세스/개발 환경용 인메모리 구현체.
238
+ return {
239
+ provide: constants_1.TENANT_SHIELD_CACHE,
240
+ useClass: cache_service_1.InMemoryTenantAwareCacheService,
241
+ };
242
+ }
243
+ /**
244
+ * forRootAsync 경로용 — TENANT_SHIELD_OPTIONS가 비동기로 만들어진 뒤 캐시 인스턴스를
245
+ * 직접 만들어야 하는 케이스. NestJS Provider가 아닌 인스턴스를 반환합니다.
246
+ *
247
+ * useClass는 무인자 생성자 가정, useFactory의 inject는 forRootAsync 경로에서는
248
+ * 지원하지 않음 (필요하면 useValue로 외부에서 만들어 넘기길 권장).
249
+ */
250
+ function buildCacheInstance(opts) {
251
+ if (opts?.useFactory) {
252
+ return opts.useFactory();
253
+ }
254
+ if (opts?.useValue !== undefined) {
255
+ return opts.useValue;
256
+ }
257
+ if (opts?.useClass) {
258
+ const Ctor = opts.useClass;
259
+ return new Ctor();
260
+ }
261
+ return new cache_service_1.InMemoryTenantAwareCacheService();
262
+ }
263
+ //# sourceMappingURL=tenant-shield.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant-shield.module.js","sourceRoot":"","sources":["../src/tenant-shield.module.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * 테스트 전용 헬퍼 배럴.
3
+ *
4
+ * 외부에서:
5
+ * import { runWithTenant, expectCurrentTenant } from 'nestjs-tenant-shield/testing';
6
+ *
7
+ * 같은 형태로 가져다 쓸 수 있게 합니다.
8
+ *
9
+ * (subpath import 지원은 package.json "exports" 필드 설정 후 활성화 — v0.1.x 예정)
10
+ */
11
+ export * from './test-helpers';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,gBAAgB,CAAC"}