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,487 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.TenantSubscriber = void 0;
|
|
13
|
+
exports.unpatch = unpatch;
|
|
14
|
+
const typeorm_1 = require("typeorm");
|
|
15
|
+
const get_current_tenant_id_1 = require("../context/get-current-tenant-id");
|
|
16
|
+
const tenant_context_storage_1 = require("../context/tenant-context.storage");
|
|
17
|
+
const missing_tenant_context_error_1 = require("../errors/missing-tenant-context.error");
|
|
18
|
+
const cross_tenant_access_error_1 = require("../errors/cross-tenant-access.error");
|
|
19
|
+
/**
|
|
20
|
+
* ─────────────────────────────────────────────────────────────
|
|
21
|
+
* TenantSubscriber — v0.1의 가장 중요한 컴포넌트.
|
|
22
|
+
*
|
|
23
|
+
* TypeORM의 EventSubscriber는 모든 SELECT/INSERT/UPDATE/DELETE
|
|
24
|
+
* 동작에 끼어들 수 있는 hook입니다. 이 hook을 활용해서 사용자가
|
|
25
|
+
* 코드에서 WHERE를 빠뜨려도, 라이브러리가 자동으로 안전망을 깔아줍니다.
|
|
26
|
+
*
|
|
27
|
+
* 적용되는 보호 3가지:
|
|
28
|
+
* 1) beforeQuery (또는 QueryBuilder 가로채기): SELECT/UPDATE/DELETE에 자동 WHERE 주입
|
|
29
|
+
* 2) beforeInsert: INSERT 시 현재 tenant ID를 자동으로 컬럼에 주입
|
|
30
|
+
* 3) afterLoad: 어떤 escape hatch로든 다른 tenant의 entity가 반환되면 즉시 throw
|
|
31
|
+
*
|
|
32
|
+
* ⚠️ Raw SQL (repository.query()) 은 이 hook을 통과하지 않습니다.
|
|
33
|
+
* 이 경우 사용자가 withTenantWhere() 헬퍼로 수동 보호해야 합니다.
|
|
34
|
+
* ─────────────────────────────────────────────────────────────
|
|
35
|
+
*/
|
|
36
|
+
let TenantSubscriber = class TenantSubscriber {
|
|
37
|
+
constructor(options) {
|
|
38
|
+
this.options = options;
|
|
39
|
+
// v0.1: QueryBuilder 레벨에서 auto WHERE 주입 패치 적용
|
|
40
|
+
patchQueryBuildersOnce(options);
|
|
41
|
+
// v0.1: Repository 레벨에서 auto WHERE/INSERT 보강
|
|
42
|
+
patchRepositoriesOnce(options);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* INSERT 직전 hook.
|
|
46
|
+
* tenant_id 컬럼이 비어 있으면 현재 컨텍스트의 tenantId를 자동으로 채워 넣습니다.
|
|
47
|
+
*
|
|
48
|
+
* 사용자가 "tenantId를 명시적으로 안 적어도 알아서 들어가는" 마법은 여기서 발생.
|
|
49
|
+
*/
|
|
50
|
+
beforeInsert(event) {
|
|
51
|
+
const entity = event.entity;
|
|
52
|
+
if (!entity)
|
|
53
|
+
return;
|
|
54
|
+
// tenant 보호 대상 entity인지 확인 (옵션에 entities 명시된 경우).
|
|
55
|
+
if (!this.isTenantAwareEntity(event.metadata.target))
|
|
56
|
+
return;
|
|
57
|
+
const tenantField = this.options.tenantIdField;
|
|
58
|
+
const tenantId = (0, get_current_tenant_id_1.getCurrentTenantId)();
|
|
59
|
+
const isSystemAction = tenant_context_storage_1.tenantContextStorage.getStore()?.isSystemAction === true;
|
|
60
|
+
// 1) 이미 사용자가 직접 채워둔 경우 — 검증만 (cross-tenant insert 차단).
|
|
61
|
+
const existingValue = entity[tenantField];
|
|
62
|
+
if (existingValue) {
|
|
63
|
+
if (tenantId && existingValue !== tenantId) {
|
|
64
|
+
throw new cross_tenant_access_error_1.CrossTenantAccessError(`INSERT 시 ${tenantField}=${existingValue}는 현재 tenant(${tenantId})와 다릅니다.`, tenantId, String(existingValue), event.metadata.name);
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// 2) 비어 있으면 자동 주입.
|
|
69
|
+
if (tenantId) {
|
|
70
|
+
entity[tenantField] = tenantId;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// 3) 컨텍스트도 없고 시스템 작업도 아니면 strict mode에서 throw.
|
|
74
|
+
if (!isSystemAction && this.options.strictMode !== false) {
|
|
75
|
+
throw new missing_tenant_context_error_1.MissingTenantContextError(`INSERT 시 tenant 컨텍스트가 없습니다. entity=${event.metadata.name}`, 'typeorm-insert');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Entity가 DB에서 로드된 직후 hook.
|
|
80
|
+
*
|
|
81
|
+
* 어떤 경로로든 (raw SQL, escape hatch, 우회 코드) 다른 tenant의 데이터가
|
|
82
|
+
* 메모리에 올라왔다면 여기서 마지막으로 검출해 throw.
|
|
83
|
+
*
|
|
84
|
+
* "다층 방어"의 최종 안전망입니다.
|
|
85
|
+
*/
|
|
86
|
+
afterLoad(entity, event) {
|
|
87
|
+
if (!entity || !event)
|
|
88
|
+
return;
|
|
89
|
+
if (!this.isTenantAwareEntity(event.metadata.target))
|
|
90
|
+
return;
|
|
91
|
+
const tenantField = this.options.tenantIdField;
|
|
92
|
+
const tenantId = (0, get_current_tenant_id_1.getCurrentTenantId)();
|
|
93
|
+
const isSystemAction = tenant_context_storage_1.tenantContextStorage.getStore()?.isSystemAction === true;
|
|
94
|
+
// 시스템 작업이면 cross-tenant 검사 스킵 (모든 tenant 데이터 정상 접근).
|
|
95
|
+
if (isSystemAction)
|
|
96
|
+
return;
|
|
97
|
+
const entityTenant = entity[tenantField];
|
|
98
|
+
// 현재 컨텍스트가 있는데 다른 tenant 데이터가 올라왔다면 즉시 차단.
|
|
99
|
+
if (tenantId && entityTenant && entityTenant !== tenantId) {
|
|
100
|
+
throw new cross_tenant_access_error_1.CrossTenantAccessError(`[SECURITY] ${event.metadata.name} 로드 결과의 ${tenantField}(${entityTenant})가 현재 tenant(${tenantId})와 다릅니다. 코드의 escape hatch를 점검하세요.`, tenantId, String(entityTenant), event.metadata.name);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* QueryBuilder의 SELECT/UPDATE/DELETE에 자동 WHERE 주입.
|
|
105
|
+
*
|
|
106
|
+
* TypeORM 0.3+에서는 beforeQuery 같은 hook이 제한적이라,
|
|
107
|
+
* 실제 구현은 EntityManager wrap 또는 QueryBuilder 가로채기를 통해 이뤄집니다.
|
|
108
|
+
*
|
|
109
|
+
* v0.1에서는 QueryBuilder 프로토타입을 monkey-patch하여
|
|
110
|
+
* getMany/getOne/getCount/execute 전에 자동 조건을 주입합니다.
|
|
111
|
+
*/
|
|
112
|
+
// beforeQuery(event: QueryEvent): void { ... } // v0.0.3 추가 예정
|
|
113
|
+
/**
|
|
114
|
+
* 주어진 entity 클래스가 tenant 보호 대상인지 판정.
|
|
115
|
+
*
|
|
116
|
+
* 사용자가 forRoot에 entities를 명시했으면 그 화이트리스트로만 확인.
|
|
117
|
+
* 미명시 시 모든 entity를 검사 (기본 안전 모드).
|
|
118
|
+
*/
|
|
119
|
+
isTenantAwareEntity(target) {
|
|
120
|
+
if (!this.options.entities || this.options.entities.length === 0) {
|
|
121
|
+
// 화이트리스트가 없으면 모든 entity가 잠재적 대상.
|
|
122
|
+
// 실제 검사 단계에서 tenantIdField가 entity에 없으면 자연스럽게 skip됨.
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
return this.options.entities.includes(target);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
exports.TenantSubscriber = TenantSubscriber;
|
|
129
|
+
exports.TenantSubscriber = TenantSubscriber = __decorate([
|
|
130
|
+
(0, typeorm_1.EventSubscriber)(),
|
|
131
|
+
__metadata("design:paramtypes", [Object])
|
|
132
|
+
], TenantSubscriber);
|
|
133
|
+
const TENANT_WHERE_APPLIED_FLAG = '__tenantShieldWhereApplied';
|
|
134
|
+
let isQueryBuilderPatched = false;
|
|
135
|
+
let patchedTenantField = null;
|
|
136
|
+
let isRepositoryPatched = false;
|
|
137
|
+
let patchedOptions = null;
|
|
138
|
+
let _origGetMany = null;
|
|
139
|
+
let _origGetOne = null;
|
|
140
|
+
let _origGetManyAndCount = null;
|
|
141
|
+
let _origGetCount = null;
|
|
142
|
+
let _origGetRawMany = null;
|
|
143
|
+
let _origGetRawOne = null;
|
|
144
|
+
let _origUpdateExecute = null;
|
|
145
|
+
let _origDeleteExecute = null;
|
|
146
|
+
let _origFind = null;
|
|
147
|
+
let _origFindOne = null;
|
|
148
|
+
let _origFindBy = null;
|
|
149
|
+
let _origFindOneBy = null;
|
|
150
|
+
let _origCount = null;
|
|
151
|
+
let _origSave = null;
|
|
152
|
+
function patchQueryBuildersOnce(options) {
|
|
153
|
+
if (isQueryBuilderPatched) {
|
|
154
|
+
if (patchedTenantField && patchedTenantField !== options.tenantIdField) {
|
|
155
|
+
throw new Error(`TenantSubscriber: 이미 tenantIdField='${patchedTenantField}'로 패치됨. ` +
|
|
156
|
+
`다른 tenantIdField='${options.tenantIdField}'는 지원하지 않습니다.`);
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
isQueryBuilderPatched = true;
|
|
161
|
+
patchedTenantField = options.tenantIdField;
|
|
162
|
+
_origGetMany = typeorm_1.SelectQueryBuilder.prototype.getMany;
|
|
163
|
+
typeorm_1.SelectQueryBuilder.prototype.getMany = function (...args) {
|
|
164
|
+
applyTenantWhereIfNeeded(this, options);
|
|
165
|
+
return _origGetMany.apply(this, args);
|
|
166
|
+
};
|
|
167
|
+
_origGetOne = typeorm_1.SelectQueryBuilder.prototype.getOne;
|
|
168
|
+
typeorm_1.SelectQueryBuilder.prototype.getOne = function (...args) {
|
|
169
|
+
applyTenantWhereIfNeeded(this, options);
|
|
170
|
+
return _origGetOne.apply(this, args);
|
|
171
|
+
};
|
|
172
|
+
_origGetManyAndCount = typeorm_1.SelectQueryBuilder.prototype.getManyAndCount;
|
|
173
|
+
typeorm_1.SelectQueryBuilder.prototype.getManyAndCount = function (...args) {
|
|
174
|
+
applyTenantWhereIfNeeded(this, options);
|
|
175
|
+
return _origGetManyAndCount.apply(this, args);
|
|
176
|
+
};
|
|
177
|
+
_origGetCount = typeorm_1.SelectQueryBuilder.prototype.getCount;
|
|
178
|
+
typeorm_1.SelectQueryBuilder.prototype.getCount = function (...args) {
|
|
179
|
+
applyTenantWhereIfNeeded(this, options);
|
|
180
|
+
return _origGetCount.apply(this, args);
|
|
181
|
+
};
|
|
182
|
+
_origGetRawMany = typeorm_1.SelectQueryBuilder.prototype.getRawMany;
|
|
183
|
+
typeorm_1.SelectQueryBuilder.prototype.getRawMany = function (...args) {
|
|
184
|
+
applyTenantWhereIfNeeded(this, options);
|
|
185
|
+
return _origGetRawMany.apply(this, args);
|
|
186
|
+
};
|
|
187
|
+
_origGetRawOne = typeorm_1.SelectQueryBuilder.prototype.getRawOne;
|
|
188
|
+
typeorm_1.SelectQueryBuilder.prototype.getRawOne = function (...args) {
|
|
189
|
+
applyTenantWhereIfNeeded(this, options);
|
|
190
|
+
return _origGetRawOne.apply(this, args);
|
|
191
|
+
};
|
|
192
|
+
_origUpdateExecute = typeorm_1.UpdateQueryBuilder.prototype.execute;
|
|
193
|
+
typeorm_1.UpdateQueryBuilder.prototype.execute = function (...args) {
|
|
194
|
+
applyTenantWhereIfNeeded(this, options);
|
|
195
|
+
return _origUpdateExecute.apply(this, args);
|
|
196
|
+
};
|
|
197
|
+
_origDeleteExecute = typeorm_1.DeleteQueryBuilder.prototype.execute;
|
|
198
|
+
typeorm_1.DeleteQueryBuilder.prototype.execute = function (...args) {
|
|
199
|
+
applyTenantWhereIfNeeded(this, options);
|
|
200
|
+
return _origDeleteExecute.apply(this, args);
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function applyTenantWhereIfNeeded(queryBuilder, options) {
|
|
204
|
+
const qb = queryBuilder;
|
|
205
|
+
if (qb[TENANT_WHERE_APPLIED_FLAG])
|
|
206
|
+
return;
|
|
207
|
+
const tenantId = (0, get_current_tenant_id_1.getCurrentTenantId)();
|
|
208
|
+
const isSystemAction = tenant_context_storage_1.tenantContextStorage.getStore()?.isSystemAction === true;
|
|
209
|
+
if (!tenantId) {
|
|
210
|
+
if (!isSystemAction && options.strictMode !== false) {
|
|
211
|
+
throw new missing_tenant_context_error_1.MissingTenantContextError('QueryBuilder 실행 시 tenant 컨텍스트가 없습니다. 요청/테스트 컨텍스트를 확인하세요.', 'typeorm-query');
|
|
212
|
+
}
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (isSystemAction)
|
|
216
|
+
return;
|
|
217
|
+
const mainAlias = qb.expressionMap?.mainAlias;
|
|
218
|
+
const metadata = mainAlias?.metadata;
|
|
219
|
+
if (!metadata)
|
|
220
|
+
return;
|
|
221
|
+
if (!isTenantAwareMetadata(metadata, options))
|
|
222
|
+
return;
|
|
223
|
+
const tenantField = options.tenantIdField;
|
|
224
|
+
const aliasName = mainAlias.name;
|
|
225
|
+
const paramKey = '__tenant_id';
|
|
226
|
+
const wheres = qb.expressionMap?.wheres ?? [];
|
|
227
|
+
const hasTenantWhere = wheres.some((where) => {
|
|
228
|
+
const condition = where?.condition;
|
|
229
|
+
if (!condition)
|
|
230
|
+
return false;
|
|
231
|
+
if (typeof condition === 'string')
|
|
232
|
+
return condition.includes(tenantField);
|
|
233
|
+
try {
|
|
234
|
+
return JSON.stringify(condition).includes(tenantField);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
if (!hasTenantWhere) {
|
|
241
|
+
qb.andWhere(`${aliasName}.${tenantField} = :${paramKey}`, {
|
|
242
|
+
[paramKey]: tenantId,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
qb[TENANT_WHERE_APPLIED_FLAG] = true;
|
|
246
|
+
}
|
|
247
|
+
function isTenantAwareMetadata(metadata, options) {
|
|
248
|
+
if (options.entities && options.entities.length > 0) {
|
|
249
|
+
if (!options.entities.includes(metadata.target))
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
const tenantField = options.tenantIdField;
|
|
253
|
+
return metadata.columns.some((column) => column.propertyName === tenantField || column.databaseName === tenantField);
|
|
254
|
+
}
|
|
255
|
+
function patchRepositoriesOnce(options) {
|
|
256
|
+
if (isRepositoryPatched) {
|
|
257
|
+
if (patchedTenantField && patchedTenantField !== options.tenantIdField) {
|
|
258
|
+
throw new Error(`TenantSubscriber: 이미 tenantIdField='${patchedTenantField}'로 패치됨. ` +
|
|
259
|
+
`다른 tenantIdField='${options.tenantIdField}'는 지원하지 않습니다.`);
|
|
260
|
+
}
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
isRepositoryPatched = true;
|
|
264
|
+
patchedTenantField = options.tenantIdField;
|
|
265
|
+
patchedOptions = options;
|
|
266
|
+
// 각 patch에 try/catch + Promise.reject — 컨텍스트 누락 throw가 NestJS의
|
|
267
|
+
// async 흐름에서 자연스럽게 catch되도록 sync throw를 async rejection으로 통일.
|
|
268
|
+
_origFind = typeorm_1.Repository.prototype.find;
|
|
269
|
+
typeorm_1.Repository.prototype.find = function (options) {
|
|
270
|
+
try {
|
|
271
|
+
const patched = applyTenantToFindOptions(this, options, options ? options : undefined);
|
|
272
|
+
return _origFind.call(this, patched);
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
return Promise.reject(err);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
_origFindOne = typeorm_1.Repository.prototype.findOne;
|
|
279
|
+
typeorm_1.Repository.prototype.findOne = function (options) {
|
|
280
|
+
try {
|
|
281
|
+
const patched = applyTenantToFindOptions(this, options, options ? options : undefined);
|
|
282
|
+
return _origFindOne.call(this, patched);
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
return Promise.reject(err);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
_origFindBy = typeorm_1.Repository.prototype.findBy;
|
|
289
|
+
typeorm_1.Repository.prototype.findBy = function (where) {
|
|
290
|
+
try {
|
|
291
|
+
const patched = applyTenantToWhere(this, where);
|
|
292
|
+
return _origFindBy.call(this, patched);
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
return Promise.reject(err);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
_origFindOneBy = typeorm_1.Repository.prototype.findOneBy;
|
|
299
|
+
typeorm_1.Repository.prototype.findOneBy = function (where) {
|
|
300
|
+
try {
|
|
301
|
+
const patched = applyTenantToWhere(this, where);
|
|
302
|
+
return _origFindOneBy.call(this, patched);
|
|
303
|
+
}
|
|
304
|
+
catch (err) {
|
|
305
|
+
return Promise.reject(err);
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
_origCount = typeorm_1.Repository.prototype.count;
|
|
309
|
+
typeorm_1.Repository.prototype.count = function (options) {
|
|
310
|
+
try {
|
|
311
|
+
const patched = applyTenantToFindOptions(this, options, options ? options : undefined);
|
|
312
|
+
return _origCount.call(this, patched);
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
return Promise.reject(err);
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
_origSave = typeorm_1.Repository.prototype.save;
|
|
319
|
+
typeorm_1.Repository.prototype.save = function (entity, options) {
|
|
320
|
+
try {
|
|
321
|
+
const patchedEntity = applyTenantToSaveEntity(this, entity, options);
|
|
322
|
+
return _origSave.call(this, patchedEntity, options);
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
return Promise.reject(err);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function applyTenantToFindOptions(repo, rawOptions, options) {
|
|
330
|
+
const tenantId = (0, get_current_tenant_id_1.getCurrentTenantId)();
|
|
331
|
+
const isSystemAction = tenant_context_storage_1.tenantContextStorage.getStore()?.isSystemAction === true;
|
|
332
|
+
const strict = patchedOptions?.strictMode !== false;
|
|
333
|
+
// tenant가 없는 모든 케이스를 한 곳에서 분기.
|
|
334
|
+
// - 시스템 작업이면: 머지 안 하고 원본 옵션 그대로 → 모든 tenant 데이터 접근
|
|
335
|
+
// - strict면 : MissingTenantContextError throw
|
|
336
|
+
// - 그 외 : 머지 안 하고 원본 옵션 그대로 (경고 정책은 데코레이터에서)
|
|
337
|
+
if (!tenantId) {
|
|
338
|
+
if (!isSystemAction && strict) {
|
|
339
|
+
throw new missing_tenant_context_error_1.MissingTenantContextError('Repository 실행 시 tenant 컨텍스트가 없습니다. 요청/테스트 컨텍스트를 확인하세요.', 'typeorm-repository');
|
|
340
|
+
}
|
|
341
|
+
return rawOptions;
|
|
342
|
+
}
|
|
343
|
+
if (!isTenantAwareRepository(repo, options))
|
|
344
|
+
return rawOptions;
|
|
345
|
+
const tenantField = patchedTenantField;
|
|
346
|
+
const baseWhere = options?.where ?? undefined;
|
|
347
|
+
const mergedWhere = mergeTenantWhere(baseWhere, tenantField, tenantId);
|
|
348
|
+
return {
|
|
349
|
+
...(rawOptions ?? {}),
|
|
350
|
+
where: mergedWhere,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function applyTenantToWhere(repo, where) {
|
|
354
|
+
const tenantId = (0, get_current_tenant_id_1.getCurrentTenantId)();
|
|
355
|
+
const isSystemAction = tenant_context_storage_1.tenantContextStorage.getStore()?.isSystemAction === true;
|
|
356
|
+
const strict = patchedOptions?.strictMode !== false;
|
|
357
|
+
if (!tenantId) {
|
|
358
|
+
if (!isSystemAction) {
|
|
359
|
+
if (strict) {
|
|
360
|
+
throw new missing_tenant_context_error_1.MissingTenantContextError('Repository 실행 시 tenant 컨텍스트가 없습니다. 요청/테스트 컨텍스트를 확인하세요.', 'typeorm-repository');
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return where;
|
|
364
|
+
}
|
|
365
|
+
if (!isTenantAwareRepository(repo, where))
|
|
366
|
+
return where;
|
|
367
|
+
const tenantField = patchedTenantField;
|
|
368
|
+
const resolvedTenantId = tenantId;
|
|
369
|
+
return mergeTenantWhere(where, tenantField, resolvedTenantId);
|
|
370
|
+
}
|
|
371
|
+
function applyTenantToSaveEntity(repo, entity, _options) {
|
|
372
|
+
const tenantId = (0, get_current_tenant_id_1.getCurrentTenantId)();
|
|
373
|
+
const isSystemAction = tenant_context_storage_1.tenantContextStorage.getStore()?.isSystemAction === true;
|
|
374
|
+
const strict = patchedOptions?.strictMode !== false;
|
|
375
|
+
if (!tenantId) {
|
|
376
|
+
if (!isSystemAction) {
|
|
377
|
+
if (strict) {
|
|
378
|
+
throw new missing_tenant_context_error_1.MissingTenantContextError('Repository.save 시 tenant 컨텍스트가 없습니다. 요청/테스트 컨텍스트를 확인하세요.', 'typeorm-save');
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return entity;
|
|
382
|
+
}
|
|
383
|
+
if (!isTenantAwareRepository(repo, entity))
|
|
384
|
+
return entity;
|
|
385
|
+
const tenantField = patchedTenantField;
|
|
386
|
+
const resolvedTenantId = tenantId;
|
|
387
|
+
const attach = (item) => {
|
|
388
|
+
const existing = item?.[tenantField];
|
|
389
|
+
if (existing && existing !== resolvedTenantId) {
|
|
390
|
+
throw new cross_tenant_access_error_1.CrossTenantAccessError(`INSERT 시 ${tenantField}=${existing}는 현재 tenant(${tenantId})와 다릅니다.`, resolvedTenantId, String(existing), repo.metadata?.name ?? 'UnknownEntity');
|
|
391
|
+
}
|
|
392
|
+
if (!existing) {
|
|
393
|
+
item[tenantField] = resolvedTenantId;
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
if (Array.isArray(entity)) {
|
|
397
|
+
entity.forEach(attach);
|
|
398
|
+
return entity;
|
|
399
|
+
}
|
|
400
|
+
attach(entity);
|
|
401
|
+
return entity;
|
|
402
|
+
}
|
|
403
|
+
function mergeTenantWhere(where, tenantField, tenantId) {
|
|
404
|
+
if (!where) {
|
|
405
|
+
return { [tenantField]: tenantId };
|
|
406
|
+
}
|
|
407
|
+
if (Array.isArray(where)) {
|
|
408
|
+
return where.map((item) => ({ ...item, [tenantField]: tenantId }));
|
|
409
|
+
}
|
|
410
|
+
return { ...where, [tenantField]: tenantId };
|
|
411
|
+
}
|
|
412
|
+
function isTenantAwareRepository(repo, _input) {
|
|
413
|
+
const metadata = repo.metadata;
|
|
414
|
+
if (!metadata)
|
|
415
|
+
return false;
|
|
416
|
+
if (patchedTenantField == null)
|
|
417
|
+
return false;
|
|
418
|
+
const tenantField = patchedTenantField;
|
|
419
|
+
return metadata.columns.some((column) => column.propertyName === tenantField || column.databaseName === tenantField);
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* 모든 prototype 패치를 원복하고 모듈 상태를 초기화한다.
|
|
423
|
+
* e2e 테스트의 afterAll에서 호출해 테스트 간 오염을 방지한다.
|
|
424
|
+
*/
|
|
425
|
+
function unpatch() {
|
|
426
|
+
if (_origGetMany) {
|
|
427
|
+
typeorm_1.SelectQueryBuilder.prototype.getMany = _origGetMany;
|
|
428
|
+
_origGetMany = null;
|
|
429
|
+
}
|
|
430
|
+
if (_origGetOne) {
|
|
431
|
+
typeorm_1.SelectQueryBuilder.prototype.getOne = _origGetOne;
|
|
432
|
+
_origGetOne = null;
|
|
433
|
+
}
|
|
434
|
+
if (_origGetManyAndCount) {
|
|
435
|
+
typeorm_1.SelectQueryBuilder.prototype.getManyAndCount = _origGetManyAndCount;
|
|
436
|
+
_origGetManyAndCount = null;
|
|
437
|
+
}
|
|
438
|
+
if (_origGetCount) {
|
|
439
|
+
typeorm_1.SelectQueryBuilder.prototype.getCount = _origGetCount;
|
|
440
|
+
_origGetCount = null;
|
|
441
|
+
}
|
|
442
|
+
if (_origGetRawMany) {
|
|
443
|
+
typeorm_1.SelectQueryBuilder.prototype.getRawMany = _origGetRawMany;
|
|
444
|
+
_origGetRawMany = null;
|
|
445
|
+
}
|
|
446
|
+
if (_origGetRawOne) {
|
|
447
|
+
typeorm_1.SelectQueryBuilder.prototype.getRawOne = _origGetRawOne;
|
|
448
|
+
_origGetRawOne = null;
|
|
449
|
+
}
|
|
450
|
+
if (_origUpdateExecute) {
|
|
451
|
+
typeorm_1.UpdateQueryBuilder.prototype.execute = _origUpdateExecute;
|
|
452
|
+
_origUpdateExecute = null;
|
|
453
|
+
}
|
|
454
|
+
if (_origDeleteExecute) {
|
|
455
|
+
typeorm_1.DeleteQueryBuilder.prototype.execute = _origDeleteExecute;
|
|
456
|
+
_origDeleteExecute = null;
|
|
457
|
+
}
|
|
458
|
+
if (_origFind) {
|
|
459
|
+
typeorm_1.Repository.prototype.find = _origFind;
|
|
460
|
+
_origFind = null;
|
|
461
|
+
}
|
|
462
|
+
if (_origFindOne) {
|
|
463
|
+
typeorm_1.Repository.prototype.findOne = _origFindOne;
|
|
464
|
+
_origFindOne = null;
|
|
465
|
+
}
|
|
466
|
+
if (_origFindBy) {
|
|
467
|
+
typeorm_1.Repository.prototype.findBy = _origFindBy;
|
|
468
|
+
_origFindBy = null;
|
|
469
|
+
}
|
|
470
|
+
if (_origFindOneBy) {
|
|
471
|
+
typeorm_1.Repository.prototype.findOneBy = _origFindOneBy;
|
|
472
|
+
_origFindOneBy = null;
|
|
473
|
+
}
|
|
474
|
+
if (_origCount) {
|
|
475
|
+
typeorm_1.Repository.prototype.count = _origCount;
|
|
476
|
+
_origCount = null;
|
|
477
|
+
}
|
|
478
|
+
if (_origSave) {
|
|
479
|
+
typeorm_1.Repository.prototype.save = _origSave;
|
|
480
|
+
_origSave = null;
|
|
481
|
+
}
|
|
482
|
+
isQueryBuilderPatched = false;
|
|
483
|
+
isRepositoryPatched = false;
|
|
484
|
+
patchedTenantField = null;
|
|
485
|
+
patchedOptions = null;
|
|
486
|
+
}
|
|
487
|
+
//# sourceMappingURL=tenant.subscriber.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant.subscriber.js","sourceRoot":"","sources":["../../src/typeorm/tenant.subscriber.ts"],"names":[],"mappings":";;;;;;;;;;;;AAsfA,0BAoBC;AA1gBD,qCASiB;AACjB,4EAAsE;AACtE,8EAAyE;AAEzE,yFAAmF;AACnF,mFAA6E;AAE7E;;;;;;;;;;;;;;;;GAgBG;AAEI,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAC3B,YAA6B,OAA4B;QAA5B,YAAO,GAAP,OAAO,CAAqB;QACvD,8CAA8C;QAC9C,sBAAsB,CAAC,OAAO,CAAC,CAAC;QAChC,6CAA6C;QAC7C,qBAAqB,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IAED;;;;;OAKG;IACH,YAAY,CAAC,KAAuB;QAClC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAC5B,IAAI,CAAC,MAAM;YAAE,OAAO;QAEpB,kDAAkD;QAClD,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAkB,CAAC;YAAE,OAAO;QAEzE,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC;QAC/C,MAAM,QAAQ,GAAG,IAAA,0CAAkB,GAAE,CAAC;QACtC,MAAM,cAAc,GAAG,6CAAoB,CAAC,QAAQ,EAAE,EAAE,cAAc,KAAK,IAAI,CAAC;QAEhF,uDAAuD;QACvD,MAAM,aAAa,GAAI,MAAkC,CAAC,WAAW,CAAC,CAAC;QACvE,IAAI,aAAa,EAAE,CAAC;YAClB,IAAI,QAAQ,IAAI,aAAa,KAAK,QAAQ,EAAE,CAAC;gBAC3C,MAAM,IAAI,kDAAsB,CAC9B,YAAY,WAAW,IAAI,aAAa,eAAe,QAAQ,UAAU,EACzE,QAAQ,EACR,MAAM,CAAC,aAAa,CAAC,EACrB,KAAK,CAAC,QAAQ,CAAC,IAAI,CACpB,CAAC;YACJ,CAAC;YACD,OAAO;QACT,CAAC;QAED,mBAAmB;QACnB,IAAI,QAAQ,EAAE,CAAC;YACZ,MAAkC,CAAC,WAAW,CAAC,GAAG,QAAQ,CAAC;YAC5D,OAAO;QACT,CAAC;QAED,+CAA+C;QAC/C,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,OAAO,CAAC,UAAU,KAAK,KAAK,EAAE,CAAC;YACzD,MAAM,IAAI,wDAAyB,CACjC,sCAAsC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,EAC3D,gBAAgB,CACjB,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACH,SAAS,CAAC,MAAW,EAAE,KAAsB;QAC3C,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK;YAAE,OAAO;QAC9B,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAkB,CAAC;YAAE,OAAO;QAEzE,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC;QAC/C,MAAM,QAAQ,GAAG,IAAA,0CAAkB,GAAE,CAAC;QACtC,MAAM,cAAc,GAAG,6CAAoB,CAAC,QAAQ,EAAE,EAAE,cAAc,KAAK,IAAI,CAAC;QAEhF,qDAAqD;QACrD,IAAI,cAAc;YAAE,OAAO;QAE3B,MAAM,YAAY,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC;QAEzC,2CAA2C;QAC3C,IAAI,QAAQ,IAAI,YAAY,IAAI,YAAY,KAAK,QAAQ,EAAE,CAAC;YAC1D,MAAM,IAAI,kDAAsB,CAC9B,cAAc,KAAK,CAAC,QAAQ,CAAC,IAAI,WAAW,WAAW,IAAI,YAAY,gBAAgB,QAAQ,mCAAmC,EAClI,QAAQ,EACR,MAAM,CAAC,YAAY,CAAC,EACpB,KAAK,CAAC,QAAQ,CAAC,IAAI,CACpB,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;;;;;;;OAQG;IACH,gEAAgE;IAEhE;;;;;OAKG;IACK,mBAAmB,CAAC,MAAgB;QAC1C,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjE,iCAAiC;YACjC,qDAAqD;YACrD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChD,CAAC;CACF,CAAA;AA/GY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,yBAAe,GAAE;;GACL,gBAAgB,CA+G5B;AAED,MAAM,yBAAyB,GAAG,4BAA4B,CAAC;AAC/D,IAAI,qBAAqB,GAAG,KAAK,CAAC;AAClC,IAAI,kBAAkB,GAAkB,IAAI,CAAC;AAC7C,IAAI,mBAAmB,GAAG,KAAK,CAAC;AAChC,IAAI,cAAc,GAA+B,IAAI,CAAC;AAEtD,IAAI,YAAY,GAAuD,IAAI,CAAC;AAC5E,IAAI,WAAW,GAAsD,IAAI,CAAC;AAC1E,IAAI,oBAAoB,GAA+D,IAAI,CAAC;AAC5F,IAAI,aAAa,GAAwD,IAAI,CAAC;AAC9E,IAAI,eAAe,GAA0D,IAAI,CAAC;AAClF,IAAI,cAAc,GAAyD,IAAI,CAAC;AAChF,IAAI,kBAAkB,GAAuD,IAAI,CAAC;AAClF,IAAI,kBAAkB,GAAuD,IAAI,CAAC;AAClF,IAAI,SAAS,GAA4C,IAAI,CAAC;AAC9D,IAAI,YAAY,GAA+C,IAAI,CAAC;AACpE,IAAI,WAAW,GAA8C,IAAI,CAAC;AAClE,IAAI,cAAc,GAAiD,IAAI,CAAC;AACxE,IAAI,UAAU,GAA6C,IAAI,CAAC;AAChE,IAAI,SAAS,GAA4C,IAAI,CAAC;AAE9D,SAAS,sBAAsB,CAAC,OAA4B;IAC1D,IAAI,qBAAqB,EAAE,CAAC;QAC1B,IAAI,kBAAkB,IAAI,kBAAkB,KAAK,OAAO,CAAC,aAAa,EAAE,CAAC;YACvE,MAAM,IAAI,KAAK,CACb,uCAAuC,kBAAkB,UAAU;gBACjE,qBAAqB,OAAO,CAAC,aAAa,eAAe,CAC5D,CAAC;QACJ,CAAC;QACD,OAAO;IACT,CAAC;IAED,qBAAqB,GAAG,IAAI,CAAC;IAC7B,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC;IAE3C,YAAY,GAAG,4BAAkB,CAAC,SAAS,CAAC,OAAO,CAAC;IACpD,4BAAkB,CAAC,SAAS,CAAC,OAAO,GAAG,UAAyC,GAAG,IAAW;QAC5F,wBAAwB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxC,OAAO,YAAa,CAAC,KAAK,CAAC,IAAI,EAAE,IAAW,CAAC,CAAC;IAChD,CAAQ,CAAC;IAET,WAAW,GAAG,4BAAkB,CAAC,SAAS,CAAC,MAAM,CAAC;IAClD,4BAAkB,CAAC,SAAS,CAAC,MAAM,GAAG,UAAyC,GAAG,IAAW;QAC3F,wBAAwB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxC,OAAO,WAAY,CAAC,KAAK,CAAC,IAAI,EAAE,IAAW,CAAC,CAAC;IAC/C,CAAQ,CAAC;IAET,oBAAoB,GAAG,4BAAkB,CAAC,SAAS,CAAC,eAAe,CAAC;IACpE,4BAAkB,CAAC,SAAS,CAAC,eAAe,GAAG,UAAyC,GAAG,IAAW;QACpG,wBAAwB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxC,OAAO,oBAAqB,CAAC,KAAK,CAAC,IAAI,EAAE,IAAW,CAAC,CAAC;IACxD,CAAQ,CAAC;IAET,aAAa,GAAG,4BAAkB,CAAC,SAAS,CAAC,QAAQ,CAAC;IACtD,4BAAkB,CAAC,SAAS,CAAC,QAAQ,GAAG,UAAyC,GAAG,IAAW;QAC7F,wBAAwB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxC,OAAO,aAAc,CAAC,KAAK,CAAC,IAAI,EAAE,IAAW,CAAC,CAAC;IACjD,CAAQ,CAAC;IAET,eAAe,GAAG,4BAAkB,CAAC,SAAS,CAAC,UAAU,CAAC;IAC1D,4BAAkB,CAAC,SAAS,CAAC,UAAU,GAAG,UAAyC,GAAG,IAAW;QAC/F,wBAAwB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxC,OAAO,eAAgB,CAAC,KAAK,CAAC,IAAI,EAAE,IAAW,CAAC,CAAC;IACnD,CAAQ,CAAC;IAET,cAAc,GAAG,4BAAkB,CAAC,SAAS,CAAC,SAAS,CAAC;IACxD,4BAAkB,CAAC,SAAS,CAAC,SAAS,GAAG,UAAyC,GAAG,IAAW;QAC9F,wBAAwB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxC,OAAO,cAAe,CAAC,KAAK,CAAC,IAAI,EAAE,IAAW,CAAC,CAAC;IAClD,CAAQ,CAAC;IAET,kBAAkB,GAAG,4BAAkB,CAAC,SAAS,CAAC,OAAO,CAAC;IAC1D,4BAAkB,CAAC,SAAS,CAAC,OAAO,GAAG,UAAyC,GAAG,IAAW;QAC5F,wBAAwB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxC,OAAO,kBAAmB,CAAC,KAAK,CAAC,IAAI,EAAE,IAAW,CAAC,CAAC;IACtD,CAAQ,CAAC;IAET,kBAAkB,GAAG,4BAAkB,CAAC,SAAS,CAAC,OAAO,CAAC;IAC1D,4BAAkB,CAAC,SAAS,CAAC,OAAO,GAAG,UAAyC,GAAG,IAAW;QAC5F,wBAAwB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxC,OAAO,kBAAmB,CAAC,KAAK,CAAC,IAAI,EAAE,IAAW,CAAC,CAAC;IACtD,CAAQ,CAAC;AACX,CAAC;AAED,SAAS,wBAAwB,CAC/B,YAAyF,EACzF,OAA4B;IAE5B,MAAM,EAAE,GAAG,YAAmB,CAAC;IAC/B,IAAI,EAAE,CAAC,yBAAyB,CAAC;QAAE,OAAO;IAE1C,MAAM,QAAQ,GAAG,IAAA,0CAAkB,GAAE,CAAC;IACtC,MAAM,cAAc,GAAG,6CAAoB,CAAC,QAAQ,EAAE,EAAE,cAAc,KAAK,IAAI,CAAC;IAEhF,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,IAAI,CAAC,cAAc,IAAI,OAAO,CAAC,UAAU,KAAK,KAAK,EAAE,CAAC;YACpD,MAAM,IAAI,wDAAyB,CACjC,0DAA0D,EAC1D,eAAe,CAChB,CAAC;QACJ,CAAC;QACD,OAAO;IACT,CAAC;IAED,IAAI,cAAc;QAAE,OAAO;IAE3B,MAAM,SAAS,GAAG,EAAE,CAAC,aAAa,EAAE,SAAS,CAAC;IAC9C,MAAM,QAAQ,GAAG,SAAS,EAAE,QAAQ,CAAC;IACrC,IAAI,CAAC,QAAQ;QAAE,OAAO;IAEtB,IAAI,CAAC,qBAAqB,CAAC,QAAQ,EAAE,OAAO,CAAC;QAAE,OAAO;IAEtD,MAAM,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC;IAC1C,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC;IACjC,MAAM,QAAQ,GAAG,aAAa,CAAC;IAE/B,MAAM,MAAM,GAAG,EAAE,CAAC,aAAa,EAAE,MAAM,IAAI,EAAE,CAAC;IAC9C,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,KAAU,EAAE,EAAE;QAChD,MAAM,SAAS,GAAG,KAAK,EAAE,SAAS,CAAC;QACnC,IAAI,CAAC,SAAS;YAAE,OAAO,KAAK,CAAC;QAC7B,IAAI,OAAO,SAAS,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAC1E,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,EAAE,CAAC,QAAQ,CAAC,GAAG,SAAS,IAAI,WAAW,OAAO,QAAQ,EAAE,EAAE;YACxD,CAAC,QAAQ,CAAC,EAAE,QAAQ;SACrB,CAAC,CAAC;IACL,CAAC;IAED,EAAE,CAAC,yBAAyB,CAAC,GAAG,IAAI,CAAC;AACvC,CAAC;AAED,SAAS,qBAAqB,CAAC,QAAa,EAAE,OAA4B;IACxE,IAAI,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpD,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,KAAK,CAAC;IAChE,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC;IAC1C,OAAO,QAAQ,CAAC,OAAO,CAAC,IAAI,CAC1B,CAAC,MAAW,EAAE,EAAE,CAAC,MAAM,CAAC,YAAY,KAAK,WAAW,IAAI,MAAM,CAAC,YAAY,KAAK,WAAW,CAC5F,CAAC;AACJ,CAAC;AAED,SAAS,qBAAqB,CAAC,OAA4B;IACzD,IAAI,mBAAmB,EAAE,CAAC;QACxB,IAAI,kBAAkB,IAAI,kBAAkB,KAAK,OAAO,CAAC,aAAa,EAAE,CAAC;YACvE,MAAM,IAAI,KAAK,CACb,uCAAuC,kBAAkB,UAAU;gBACjE,qBAAqB,OAAO,CAAC,aAAa,eAAe,CAC5D,CAAC;QACJ,CAAC;QACD,OAAO;IACT,CAAC;IAED,mBAAmB,GAAG,IAAI,CAAC;IAC3B,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC;IAC3C,cAAc,GAAG,OAAO,CAAC;IAEzB,+DAA+D;IAC/D,8DAA8D;IAC9D,SAAS,GAAG,oBAAU,CAAC,SAAS,CAAC,IAAI,CAAC;IACtC,oBAAU,CAAC,SAAS,CAAC,IAAI,GAAG,UAAiC,OAAa;QACxE,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,wBAAwB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YACvF,OAAO,SAAU,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;IACH,CAAQ,CAAC;IAET,YAAY,GAAG,oBAAU,CAAC,SAAS,CAAC,OAAO,CAAC;IAC5C,oBAAU,CAAC,SAAS,CAAC,OAAO,GAAG,UAAiC,OAAa;QAC3E,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,wBAAwB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YACvF,OAAO,YAAa,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;IACH,CAAQ,CAAC;IAET,WAAW,GAAG,oBAAU,CAAC,SAAS,CAAC,MAAM,CAAC;IAC1C,oBAAU,CAAC,SAAS,CAAC,MAAM,GAAG,UAAiC,KAAU;QACvE,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAChD,OAAO,WAAY,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC1C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;IACH,CAAQ,CAAC;IAET,cAAc,GAAG,oBAAU,CAAC,SAAS,CAAC,SAAS,CAAC;IAChD,oBAAU,CAAC,SAAS,CAAC,SAAS,GAAG,UAAiC,KAAU;QAC1E,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAChD,OAAO,cAAe,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;IACH,CAAQ,CAAC;IAET,UAAU,GAAG,oBAAU,CAAC,SAAS,CAAC,KAAK,CAAC;IACxC,oBAAU,CAAC,SAAS,CAAC,KAAK,GAAG,UAAiC,OAAa;QACzE,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,wBAAwB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YACvF,OAAO,UAAW,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;IACH,CAAQ,CAAC;IAET,SAAS,GAAG,oBAAU,CAAC,SAAS,CAAC,IAAI,CAAC;IACtC,oBAAU,CAAC,SAAS,CAAC,IAAI,GAAG,UAAiC,MAAW,EAAE,OAAa;QACrF,IAAI,CAAC;YACH,MAAM,aAAa,GAAG,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;YACrE,OAAO,SAAU,CAAC,IAAI,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC;QACvD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;IACH,CAAQ,CAAC;AACX,CAAC;AAED,SAAS,wBAAwB,CAAC,IAAqB,EAAE,UAAe,EAAE,OAAY;IACpF,MAAM,QAAQ,GAAG,IAAA,0CAAkB,GAAE,CAAC;IACtC,MAAM,cAAc,GAAG,6CAAoB,CAAC,QAAQ,EAAE,EAAE,cAAc,KAAK,IAAI,CAAC;IAChF,MAAM,MAAM,GAAG,cAAc,EAAE,UAAU,KAAK,KAAK,CAAC;IAEpD,+BAA+B;IAC/B,oDAAoD;IACpD,oDAAoD;IACpD,sDAAsD;IACtD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,IAAI,CAAC,cAAc,IAAI,MAAM,EAAE,CAAC;YAC9B,MAAM,IAAI,wDAAyB,CACjC,wDAAwD,EACxD,oBAAoB,CACrB,CAAC;QACJ,CAAC;QACD,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,IAAI,CAAC,uBAAuB,CAAC,IAAI,EAAE,OAAO,CAAC;QAAE,OAAO,UAAU,CAAC;IAE/D,MAAM,WAAW,GAAG,kBAA4B,CAAC;IACjD,MAAM,SAAS,GAAG,OAAO,EAAE,KAAK,IAAI,SAAS,CAAC;IAC9C,MAAM,WAAW,GAAG,gBAAgB,CAAC,SAAS,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;IAEvE,OAAO;QACL,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC;QACrB,KAAK,EAAE,WAAW;KACnB,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAqB,EAAE,KAAU;IAC3D,MAAM,QAAQ,GAAG,IAAA,0CAAkB,GAAE,CAAC;IACtC,MAAM,cAAc,GAAG,6CAAoB,CAAC,QAAQ,EAAE,EAAE,cAAc,KAAK,IAAI,CAAC;IAChF,MAAM,MAAM,GAAG,cAAc,EAAE,UAAU,KAAK,KAAK,CAAC;IAEpD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,IAAI,wDAAyB,CACjC,wDAAwD,EACxD,oBAAoB,CACrB,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,CAAC,uBAAuB,CAAC,IAAI,EAAE,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAExD,MAAM,WAAW,GAAG,kBAA4B,CAAC;IACjD,MAAM,gBAAgB,GAAG,QAAkB,CAAC;IAC5C,OAAO,gBAAgB,CAAC,KAAK,EAAE,WAAW,EAAE,gBAAgB,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,uBAAuB,CAAC,IAAqB,EAAE,MAAW,EAAE,QAAa;IAChF,MAAM,QAAQ,GAAG,IAAA,0CAAkB,GAAE,CAAC;IACtC,MAAM,cAAc,GAAG,6CAAoB,CAAC,QAAQ,EAAE,EAAE,cAAc,KAAK,IAAI,CAAC;IAChF,MAAM,MAAM,GAAG,cAAc,EAAE,UAAU,KAAK,KAAK,CAAC;IAEpD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,IAAI,wDAAyB,CACjC,0DAA0D,EAC1D,cAAc,CACf,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,IAAI,CAAC,uBAAuB,CAAC,IAAI,EAAE,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IAE1D,MAAM,WAAW,GAAG,kBAA4B,CAAC;IACjD,MAAM,gBAAgB,GAAG,QAAkB,CAAC;IAE5C,MAAM,MAAM,GAAG,CAAC,IAAS,EAAE,EAAE;QAC3B,MAAM,QAAQ,GAAG,IAAI,EAAE,CAAC,WAAW,CAAC,CAAC;QACrC,IAAI,QAAQ,IAAI,QAAQ,KAAK,gBAAgB,EAAE,CAAC;YAC9C,MAAM,IAAI,kDAAsB,CAC9B,YAAY,WAAW,IAAI,QAAQ,eAAe,QAAQ,UAAU,EACpE,gBAAgB,EAChB,MAAM,CAAC,QAAQ,CAAC,EAChB,IAAI,CAAC,QAAQ,EAAE,IAAI,IAAI,eAAe,CACvC,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,IAAI,CAAC,WAAW,CAAC,GAAG,gBAAgB,CAAC;QACvC,CAAC;IACH,CAAC,CAAC;IAEF,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACvB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,CAAC,MAAM,CAAC,CAAC;IACf,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAU,EAAE,WAAmB,EAAE,QAAgB;IACzE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC;IACrC,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC;AAC/C,CAAC;AAED,SAAS,uBAAuB,CAAC,IAAqB,EAAE,MAAW;IACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC/B,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5B,IAAI,kBAAkB,IAAI,IAAI;QAAE,OAAO,KAAK,CAAC;IAE7C,MAAM,WAAW,GAAG,kBAAkB,CAAC;IAEvC,OAAO,QAAQ,CAAC,OAAO,CAAC,IAAI,CAC1B,CAAC,MAAW,EAAE,EAAE,CAAC,MAAM,CAAC,YAAY,KAAK,WAAW,IAAI,MAAM,CAAC,YAAY,KAAK,WAAW,CAC5F,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAgB,OAAO;IACrB,IAAI,YAAY,EAAE,CAAC;QAAC,4BAAkB,CAAC,SAAS,CAAC,OAAO,GAAG,YAAY,CAAC;QAAC,YAAY,GAAG,IAAI,CAAC;IAAC,CAAC;IAC/F,IAAI,WAAW,EAAE,CAAC;QAAC,4BAAkB,CAAC,SAAS,CAAC,MAAM,GAAG,WAAW,CAAC;QAAC,WAAW,GAAG,IAAI,CAAC;IAAC,CAAC;IAC3F,IAAI,oBAAoB,EAAE,CAAC;QAAC,4BAAkB,CAAC,SAAS,CAAC,eAAe,GAAG,oBAAoB,CAAC;QAAC,oBAAoB,GAAG,IAAI,CAAC;IAAC,CAAC;IAC/H,IAAI,aAAa,EAAE,CAAC;QAAC,4BAAkB,CAAC,SAAS,CAAC,QAAQ,GAAG,aAAa,CAAC;QAAC,aAAa,GAAG,IAAI,CAAC;IAAC,CAAC;IACnG,IAAI,eAAe,EAAE,CAAC;QAAC,4BAAkB,CAAC,SAAS,CAAC,UAAU,GAAG,eAAe,CAAC;QAAC,eAAe,GAAG,IAAI,CAAC;IAAC,CAAC;IAC3G,IAAI,cAAc,EAAE,CAAC;QAAC,4BAAkB,CAAC,SAAS,CAAC,SAAS,GAAG,cAAc,CAAC;QAAC,cAAc,GAAG,IAAI,CAAC;IAAC,CAAC;IACvG,IAAI,kBAAkB,EAAE,CAAC;QAAC,4BAAkB,CAAC,SAAS,CAAC,OAAO,GAAG,kBAAkB,CAAC;QAAC,kBAAkB,GAAG,IAAI,CAAC;IAAC,CAAC;IACjH,IAAI,kBAAkB,EAAE,CAAC;QAAC,4BAAkB,CAAC,SAAS,CAAC,OAAO,GAAG,kBAAkB,CAAC;QAAC,kBAAkB,GAAG,IAAI,CAAC;IAAC,CAAC;IACjH,IAAI,SAAS,EAAE,CAAC;QAAC,oBAAU,CAAC,SAAS,CAAC,IAAI,GAAG,SAAS,CAAC;QAAC,SAAS,GAAG,IAAI,CAAC;IAAC,CAAC;IAC3E,IAAI,YAAY,EAAE,CAAC;QAAC,oBAAU,CAAC,SAAS,CAAC,OAAO,GAAG,YAAY,CAAC;QAAC,YAAY,GAAG,IAAI,CAAC;IAAC,CAAC;IACvF,IAAI,WAAW,EAAE,CAAC;QAAC,oBAAU,CAAC,SAAS,CAAC,MAAM,GAAG,WAAW,CAAC;QAAC,WAAW,GAAG,IAAI,CAAC;IAAC,CAAC;IACnF,IAAI,cAAc,EAAE,CAAC;QAAC,oBAAU,CAAC,SAAS,CAAC,SAAS,GAAG,cAAc,CAAC;QAAC,cAAc,GAAG,IAAI,CAAC;IAAC,CAAC;IAC/F,IAAI,UAAU,EAAE,CAAC;QAAC,oBAAU,CAAC,SAAS,CAAC,KAAK,GAAG,UAAU,CAAC;QAAC,UAAU,GAAG,IAAI,CAAC;IAAC,CAAC;IAC/E,IAAI,SAAS,EAAE,CAAC;QAAC,oBAAU,CAAC,SAAS,CAAC,IAAI,GAAG,SAAS,CAAC;QAAC,SAAS,GAAG,IAAI,CAAC;IAAC,CAAC;IAE3E,qBAAqB,GAAG,KAAK,CAAC;IAC9B,mBAAmB,GAAG,KAAK,CAAC;IAC5B,kBAAkB,GAAG,IAAI,CAAC;IAC1B,cAAc,GAAG,IAAI,CAAC;AACxB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
{
|
|
2
|
+
"//": "nestjs-tenant-shield 라이브러리의 npm 패키지 메타데이터입니다.",
|
|
3
|
+
"//1": "사용자가 'pnpm add nestjs-tenant-shield'를 실행하면 npm 레지스트리에서 이 정보를 보고 다운로드합니다.",
|
|
4
|
+
"name": "nestjs-tenant-shield",
|
|
5
|
+
"//2": "Semantic Versioning(semver) 규칙: MAJOR.MINOR.PATCH. v0.x 단계에서는 API 변경 가능, v1.0부터 엄격 적용.",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"description": "NestJS B2B SaaS의 멀티테넌시 데이터 격리를 데코레이터 한 줄로 자동화하는 안전망 라이브러리",
|
|
8
|
+
"//3": "라이브러리 사용자가 'import { ... } from \"nestjs-tenant-shield\"' 할 때 진입점이 되는 파일.",
|
|
9
|
+
"//4": "빌드 결과물은 dist/ 폴더에 들어가므로 배포 시에는 dist/index.js를 가리킵니다.",
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./testing": {
|
|
18
|
+
"types": "./dist/testing/index.d.ts",
|
|
19
|
+
"default": "./dist/testing/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"//5": "npm publish 시 실제로 패키지에 포함될 파일 화이트리스트입니다. src/ 같은 원본 코드는 제외하여 패키지 크기를 줄입니다.",
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"//build": "TypeScript 소스를 dist/ 폴더에 JavaScript로 컴파일합니다. npm publish 전 필수.",
|
|
31
|
+
"build": "tsc -p tsconfig.build.json",
|
|
32
|
+
"//test": "Jest로 모든 단위 테스트 실행. CI에서도 동일하게 동작.",
|
|
33
|
+
"test": "jest",
|
|
34
|
+
"//test:watch": "파일 저장할 때마다 관련 테스트만 다시 실행. 개발 중 유용.",
|
|
35
|
+
"test:watch": "jest --watch",
|
|
36
|
+
"//test:cov": "테스트 커버리지 리포트 생성. v1.0 전에 80%+ 목표.",
|
|
37
|
+
"test:cov": "jest --coverage",
|
|
38
|
+
"//lint": "코드 스타일과 잠재적 버그를 정적 분석으로 잡습니다.",
|
|
39
|
+
"lint": "eslint \"src/**/*.ts\" --fix",
|
|
40
|
+
"//format": "Prettier로 일관된 포매팅 적용.",
|
|
41
|
+
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
42
|
+
"//prepublishOnly": "npm publish 직전에 자동 실행되는 안전 장치. 빌드 실패 시 배포 차단.",
|
|
43
|
+
"prepublishOnly": "npm run build && npm test",
|
|
44
|
+
"//demo": "academy-saas 예제 데모 서버 실행.",
|
|
45
|
+
"demo": "ts-node -r tsconfig-paths/register --project tsconfig.demo.json examples/academy-saas/src/main.ts"
|
|
46
|
+
},
|
|
47
|
+
"//peerDeps-comment": "라이브러리 사용자가 직접 설치해야 하는 의존성. nestjs-tenant-shield 자체는 이들을 번들링하지 않고 사용자 앱의 버전을 그대로 씁니다 (버전 충돌 방지).",
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
|
50
|
+
"@nestjs/core": "^10.0.0 || ^11.0.0",
|
|
51
|
+
"reflect-metadata": "^0.1.13 || ^0.2.0",
|
|
52
|
+
"rxjs": "^7.0.0",
|
|
53
|
+
"typeorm": "^0.3.0"
|
|
54
|
+
},
|
|
55
|
+
"//peerDepsMeta-comment": "v0.1에서는 TypeORM만 지원하지만, ORM을 안 쓰는 사용자도 있을 수 있으므로 optional 표시.",
|
|
56
|
+
"peerDependenciesMeta": {
|
|
57
|
+
"typeorm": {
|
|
58
|
+
"optional": true
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"//devDeps-comment": "개발/빌드/테스트에만 필요한 패키지. 배포 패키지에는 포함되지 않습니다.",
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@nestjs/common": "^10.0.0",
|
|
64
|
+
"@nestjs/core": "^10.0.0",
|
|
65
|
+
"@nestjs/platform-express": "^11.1.23",
|
|
66
|
+
"@nestjs/testing": "^10.0.0",
|
|
67
|
+
"@nestjs/typeorm": "^11.0.1",
|
|
68
|
+
"@testcontainers/postgresql": "^12.0.0",
|
|
69
|
+
"@types/jest": "^29.5.0",
|
|
70
|
+
"@types/node": "^20.19.41",
|
|
71
|
+
"@types/pg": "^8.20.0",
|
|
72
|
+
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
73
|
+
"@typescript-eslint/parser": "^6.0.0",
|
|
74
|
+
"eslint": "^8.0.0",
|
|
75
|
+
"jest": "^29.5.0",
|
|
76
|
+
"pg": "^8.21.0",
|
|
77
|
+
"prettier": "^3.0.0",
|
|
78
|
+
"reflect-metadata": "^0.2.0",
|
|
79
|
+
"rxjs": "^7.8.0",
|
|
80
|
+
"sqlite3": "^5.1.7",
|
|
81
|
+
"ts-jest": "^29.1.0",
|
|
82
|
+
"ts-node": "^10.9.2",
|
|
83
|
+
"tsconfig-paths": "^4.2.0",
|
|
84
|
+
"typeorm": "^0.3.17",
|
|
85
|
+
"typescript": "^5.0.0"
|
|
86
|
+
},
|
|
87
|
+
"keywords": [
|
|
88
|
+
"nestjs",
|
|
89
|
+
"multitenancy",
|
|
90
|
+
"multi-tenant",
|
|
91
|
+
"saas",
|
|
92
|
+
"b2b",
|
|
93
|
+
"tenant",
|
|
94
|
+
"isolation",
|
|
95
|
+
"data-isolation",
|
|
96
|
+
"typeorm",
|
|
97
|
+
"async-local-storage"
|
|
98
|
+
],
|
|
99
|
+
"author": "Jinyeong Jung",
|
|
100
|
+
"license": "MIT",
|
|
101
|
+
"repository": {
|
|
102
|
+
"type": "git",
|
|
103
|
+
"url": "git+https://github.com/jinyeongjung/nestjs-tenant-shield.git"
|
|
104
|
+
},
|
|
105
|
+
"//engines-comment": "Node.js 18+ 필요. AsyncLocalStorage가 18부터 안정화되었기 때문.",
|
|
106
|
+
"engines": {
|
|
107
|
+
"node": ">=18.0.0"
|
|
108
|
+
}
|
|
109
|
+
}
|