reltype 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/LICENSE +21 -0
- package/README.md +952 -0
- package/dist/configs/env.d.ts +14 -0
- package/dist/configs/env.d.ts.map +1 -0
- package/dist/configs/env.js +46 -0
- package/dist/configs/index.d.ts +2 -0
- package/dist/configs/index.d.ts.map +1 -0
- package/dist/configs/index.js +17 -0
- package/dist/features/connection/index.d.ts +3 -0
- package/dist/features/connection/index.d.ts.map +1 -0
- package/dist/features/connection/index.js +18 -0
- package/dist/features/connection/pool.d.ts +42 -0
- package/dist/features/connection/pool.d.ts.map +1 -0
- package/dist/features/connection/pool.js +131 -0
- package/dist/features/connection/tx.d.ts +10 -0
- package/dist/features/connection/tx.d.ts.map +1 -0
- package/dist/features/connection/tx.js +37 -0
- package/dist/features/query/builder.d.ts +253 -0
- package/dist/features/query/builder.d.ts.map +1 -0
- package/dist/features/query/builder.js +613 -0
- package/dist/features/query/bulkInsert.d.ts +10 -0
- package/dist/features/query/bulkInsert.d.ts.map +1 -0
- package/dist/features/query/bulkInsert.js +30 -0
- package/dist/features/query/delete.d.ts +8 -0
- package/dist/features/query/delete.d.ts.map +1 -0
- package/dist/features/query/delete.js +15 -0
- package/dist/features/query/index.d.ts +10 -0
- package/dist/features/query/index.d.ts.map +1 -0
- package/dist/features/query/index.js +25 -0
- package/dist/features/query/insert.d.ts +7 -0
- package/dist/features/query/insert.d.ts.map +1 -0
- package/dist/features/query/insert.js +18 -0
- package/dist/features/query/interfaces/Advanced.d.ts +177 -0
- package/dist/features/query/interfaces/Advanced.d.ts.map +1 -0
- package/dist/features/query/interfaces/Advanced.js +2 -0
- package/dist/features/query/interfaces/Order.d.ts +6 -0
- package/dist/features/query/interfaces/Order.d.ts.map +1 -0
- package/dist/features/query/interfaces/Order.js +2 -0
- package/dist/features/query/interfaces/Query.d.ts +6 -0
- package/dist/features/query/interfaces/Query.d.ts.map +1 -0
- package/dist/features/query/interfaces/Query.js +2 -0
- package/dist/features/query/interfaces/Where.d.ts +3 -0
- package/dist/features/query/interfaces/Where.d.ts.map +1 -0
- package/dist/features/query/interfaces/Where.js +2 -0
- package/dist/features/query/interfaces/index.d.ts +5 -0
- package/dist/features/query/interfaces/index.d.ts.map +1 -0
- package/dist/features/query/interfaces/index.js +20 -0
- package/dist/features/query/select.d.ts +15 -0
- package/dist/features/query/select.d.ts.map +1 -0
- package/dist/features/query/select.js +33 -0
- package/dist/features/query/update.d.ts +8 -0
- package/dist/features/query/update.d.ts.map +1 -0
- package/dist/features/query/update.js +28 -0
- package/dist/features/query/upsert.d.ts +11 -0
- package/dist/features/query/upsert.d.ts.map +1 -0
- package/dist/features/query/upsert.js +31 -0
- package/dist/features/query/where.d.ts +11 -0
- package/dist/features/query/where.d.ts.map +1 -0
- package/dist/features/query/where.js +30 -0
- package/dist/features/repository/base.d.ts +107 -0
- package/dist/features/repository/base.d.ts.map +1 -0
- package/dist/features/repository/base.js +243 -0
- package/dist/features/repository/create.d.ts +13 -0
- package/dist/features/repository/create.d.ts.map +1 -0
- package/dist/features/repository/create.js +16 -0
- package/dist/features/repository/index.d.ts +4 -0
- package/dist/features/repository/index.d.ts.map +1 -0
- package/dist/features/repository/index.js +19 -0
- package/dist/features/repository/interfaces/Find.d.ts +9 -0
- package/dist/features/repository/interfaces/Find.d.ts.map +1 -0
- package/dist/features/repository/interfaces/Find.js +2 -0
- package/dist/features/repository/interfaces/Repo.d.ts +29 -0
- package/dist/features/repository/interfaces/Repo.d.ts.map +1 -0
- package/dist/features/repository/interfaces/Repo.js +2 -0
- package/dist/features/repository/interfaces/index.d.ts +3 -0
- package/dist/features/repository/interfaces/index.d.ts.map +1 -0
- package/dist/features/repository/interfaces/index.js +18 -0
- package/dist/features/schema/column.d.ts +62 -0
- package/dist/features/schema/column.d.ts.map +1 -0
- package/dist/features/schema/column.js +74 -0
- package/dist/features/schema/index.d.ts +4 -0
- package/dist/features/schema/index.d.ts.map +1 -0
- package/dist/features/schema/index.js +19 -0
- package/dist/features/schema/interfaces/Column.d.ts +19 -0
- package/dist/features/schema/interfaces/Column.d.ts.map +1 -0
- package/dist/features/schema/interfaces/Column.js +2 -0
- package/dist/features/schema/interfaces/Infer.d.ts +20 -0
- package/dist/features/schema/interfaces/Infer.d.ts.map +1 -0
- package/dist/features/schema/interfaces/Infer.js +2 -0
- package/dist/features/schema/interfaces/Table.d.ts +7 -0
- package/dist/features/schema/interfaces/Table.d.ts.map +1 -0
- package/dist/features/schema/interfaces/Table.js +2 -0
- package/dist/features/schema/interfaces/index.d.ts +4 -0
- package/dist/features/schema/interfaces/index.d.ts.map +1 -0
- package/dist/features/schema/interfaces/index.js +19 -0
- package/dist/features/schema/table.d.ts +14 -0
- package/dist/features/schema/table.d.ts.map +1 -0
- package/dist/features/schema/table.js +17 -0
- package/dist/features/transform/case.d.ts +19 -0
- package/dist/features/transform/case.d.ts.map +1 -0
- package/dist/features/transform/case.js +32 -0
- package/dist/features/transform/index.d.ts +3 -0
- package/dist/features/transform/index.d.ts.map +1 -0
- package/dist/features/transform/index.js +18 -0
- package/dist/features/transform/mapper.d.ts +9 -0
- package/dist/features/transform/mapper.d.ts.map +1 -0
- package/dist/features/transform/mapper.js +17 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/interfaces/DatabaseConfig.d.ts +25 -0
- package/dist/interfaces/DatabaseConfig.d.ts.map +1 -0
- package/dist/interfaces/DatabaseConfig.js +2 -0
- package/dist/interfaces/EnvSource.d.ts +4 -0
- package/dist/interfaces/EnvSource.d.ts.map +1 -0
- package/dist/interfaces/EnvSource.js +2 -0
- package/dist/interfaces/PostgresDriverOptions.d.ts +20 -0
- package/dist/interfaces/PostgresDriverOptions.d.ts.map +1 -0
- package/dist/interfaces/PostgresDriverOptions.js +2 -0
- package/dist/interfaces/index.d.ts +3 -0
- package/dist/interfaces/index.d.ts.map +1 -0
- package/dist/interfaces/index.js +18 -0
- package/dist/utils/dbError.d.ts +61 -0
- package/dist/utils/dbError.d.ts.map +1 -0
- package/dist/utils/dbError.js +122 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +19 -0
- package/dist/utils/logger.d.ts +63 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +138 -0
- package/dist/utils/reader.d.ts +49 -0
- package/dist/utils/reader.d.ts.map +1 -0
- package/dist/utils/reader.js +159 -0
- package/package.json +73 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DatabaseConfig } from '../interfaces/DatabaseConfig';
|
|
2
|
+
/**
|
|
3
|
+
* 환경 변수에서 DatabaseConfig를 파싱합니다.
|
|
4
|
+
* Pool 관리는 features/connection/pool.ts 를 사용하세요.
|
|
5
|
+
*
|
|
6
|
+
* @note 라이브러리는 process.env를 읽기만 합니다.
|
|
7
|
+
* dotenv.config() 호출은 애플리케이션 진입점에서 직접 하세요:
|
|
8
|
+
* ```ts
|
|
9
|
+
* import 'dotenv/config'; // ESM
|
|
10
|
+
* require('dotenv').config(); // CJS
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare function getDatabaseConfig(): DatabaseConfig;
|
|
14
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/configs/env.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE9D;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,CAqClD"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDatabaseConfig = getDatabaseConfig;
|
|
4
|
+
const reader_1 = require("../utils/reader");
|
|
5
|
+
/**
|
|
6
|
+
* 환경 변수에서 DatabaseConfig를 파싱합니다.
|
|
7
|
+
* Pool 관리는 features/connection/pool.ts 를 사용하세요.
|
|
8
|
+
*
|
|
9
|
+
* @note 라이브러리는 process.env를 읽기만 합니다.
|
|
10
|
+
* dotenv.config() 호출은 애플리케이션 진입점에서 직접 하세요:
|
|
11
|
+
* ```ts
|
|
12
|
+
* import 'dotenv/config'; // ESM
|
|
13
|
+
* require('dotenv').config(); // CJS
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
function getDatabaseConfig() {
|
|
17
|
+
const env = process.env;
|
|
18
|
+
const connectionString = env.DB_CONNECTION_STRING;
|
|
19
|
+
const config = {
|
|
20
|
+
host: (0, reader_1.readEnv)(env, 'DB_HOST', '127.0.0.1'),
|
|
21
|
+
port: (0, reader_1.readEnv)(env, 'DB_PORT', 5432),
|
|
22
|
+
database: env.DB_NAME,
|
|
23
|
+
user: (0, reader_1.readEnv)(env, 'DB_USER', 'postgres'),
|
|
24
|
+
password: (0, reader_1.readEnv)(env, 'DB_PASSWORD', 'postgres'),
|
|
25
|
+
connectionString,
|
|
26
|
+
ssl: env.DB_SSL !== undefined
|
|
27
|
+
? (0, reader_1.readEnv)(env, 'DB_SSL', false)
|
|
28
|
+
: undefined,
|
|
29
|
+
max: env.DB_MAX ? (0, reader_1.readEnv)(env, 'DB_MAX', 10) : undefined,
|
|
30
|
+
idleTimeoutMillis: env.DB_IDLE_TIMEOUT ? (0, reader_1.readEnv)(env, 'DB_IDLE_TIMEOUT', 30000) : undefined,
|
|
31
|
+
connectionTimeoutMillis: env.DB_CONNECTION_TIMEOUT ? (0, reader_1.readEnv)(env, 'DB_CONNECTION_TIMEOUT', 2000) : undefined,
|
|
32
|
+
allowExitOnIdle: (0, reader_1.readEnv)(env, 'DB_ALLOW_EXIT_ON_IDLE', false) || undefined,
|
|
33
|
+
statement_timeout: env.DB_STATEMENT_TIMEOUT ? (0, reader_1.readEnv)(env, 'DB_STATEMENT_TIMEOUT', 0) : undefined,
|
|
34
|
+
query_timeout: env.DB_QUERY_TIMEOUT ? (0, reader_1.readEnv)(env, 'DB_QUERY_TIMEOUT', 0) : undefined,
|
|
35
|
+
application_name: env.DB_APPLICATION_NAME,
|
|
36
|
+
parseInputDatesAsUTC: (0, reader_1.readEnv)(env, 'DB_PARSE_INPUT_DATES_AS_UTC', false) || undefined,
|
|
37
|
+
keepAlive: (0, reader_1.readEnv)(env, 'DB_KEEP_ALIVE', true) || undefined,
|
|
38
|
+
keepAliveInitialDelayMillis: env.DB_KEEP_ALIVE_INITIAL_DELAY
|
|
39
|
+
? (0, reader_1.readEnv)(env, 'DB_KEEP_ALIVE_INITIAL_DELAY', 10000)
|
|
40
|
+
: undefined,
|
|
41
|
+
};
|
|
42
|
+
if (!config.connectionString && !config.database) {
|
|
43
|
+
throw new Error('DB config error: DB_CONNECTION_STRING 또는 DB_NAME 중 하나는 필수입니다.');
|
|
44
|
+
}
|
|
45
|
+
return config;
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/configs/index.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
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("./env"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/features/connection/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,MAAM,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
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("./pool"), exports);
|
|
18
|
+
__exportStar(require("./tx"), exports);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Pool, PoolClient, PoolConfig } from 'pg';
|
|
2
|
+
/** Pool 상태 스냅샷 */
|
|
3
|
+
export interface PoolStatus {
|
|
4
|
+
/** 총 생성된 연결 수 */
|
|
5
|
+
totalCount: number;
|
|
6
|
+
/** 현재 유휴 연결 수 */
|
|
7
|
+
idleCount: number;
|
|
8
|
+
/** 연결 대기 중인 요청 수 */
|
|
9
|
+
waitingCount: number;
|
|
10
|
+
/** Pool이 정상 상태인지 여부 */
|
|
11
|
+
isHealthy: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 싱글턴 Pool을 반환합니다.
|
|
15
|
+
*
|
|
16
|
+
* - 최초 호출 시 환경 변수 또는 config로 Pool을 생성합니다.
|
|
17
|
+
* - connectionTimeoutMillis 미설정 시 경고를 출력합니다.
|
|
18
|
+
* - Pool 에러 이벤트를 감지해 로깅합니다.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getPool(config?: PoolConfig): Pool;
|
|
21
|
+
/**
|
|
22
|
+
* Pool에서 client를 빌려 콜백을 실행한 뒤 자동으로 release합니다.
|
|
23
|
+
*
|
|
24
|
+
* - 연결 획득 실패(연결 수 초과, 타임아웃 등)는 DbError로 변환됩니다.
|
|
25
|
+
* - client는 성공/실패 여부와 무관하게 반드시 release됩니다.
|
|
26
|
+
*/
|
|
27
|
+
export declare function withClient<T>(fn: (client: PoolClient) => Promise<T>): Promise<T>;
|
|
28
|
+
/**
|
|
29
|
+
* 현재 Pool 상태를 반환합니다.
|
|
30
|
+
* Pool이 생성되지 않았을 경우 모든 값이 0입니다.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getPoolStatus(): PoolStatus;
|
|
33
|
+
/**
|
|
34
|
+
* Pool과 DB 서버 간의 연결이 살아있는지 확인합니다.
|
|
35
|
+
* `SELECT 1` 쿼리를 실행하고 결과를 반환합니다.
|
|
36
|
+
*/
|
|
37
|
+
export declare function checkPoolHealth(): Promise<boolean>;
|
|
38
|
+
/**
|
|
39
|
+
* 애플리케이션 종료 시 Pool을 안전하게 닫습니다.
|
|
40
|
+
*/
|
|
41
|
+
export declare function closePool(): Promise<void>;
|
|
42
|
+
//# sourceMappingURL=pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../../../src/features/connection/pool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAUlD,kBAAkB;AAClB,MAAM,WAAW,UAAU;IACzB,iBAAiB;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,uBAAuB;IACvB,SAAS,EAAE,OAAO,CAAC;CACpB;AAOD;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI,CAuDjD;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAChC,EAAE,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GACrC,OAAO,CAAC,CAAC,CAAC,CAgBZ;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,UAAU,CAK1C;AAED;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CASxD;AAED;;GAEG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAM/C"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getPool = getPool;
|
|
4
|
+
exports.withClient = withClient;
|
|
5
|
+
exports.getPoolStatus = getPoolStatus;
|
|
6
|
+
exports.checkPoolHealth = checkPoolHealth;
|
|
7
|
+
exports.closePool = closePool;
|
|
8
|
+
const pg_1 = require("pg");
|
|
9
|
+
const logger_1 = require("../../utils/logger");
|
|
10
|
+
const dbError_1 = require("../../utils/dbError");
|
|
11
|
+
const reader_1 = require("../../utils/reader");
|
|
12
|
+
const logger = logger_1.Logger.fromEnv(process.env, { prefix: '[Pool]', level: 'info' });
|
|
13
|
+
let _pool = null;
|
|
14
|
+
/** 설정된 최대 연결 수 (경고 임계값 계산용) */
|
|
15
|
+
let _maxConnections = 10;
|
|
16
|
+
/**
|
|
17
|
+
* 싱글턴 Pool을 반환합니다.
|
|
18
|
+
*
|
|
19
|
+
* - 최초 호출 시 환경 변수 또는 config로 Pool을 생성합니다.
|
|
20
|
+
* - connectionTimeoutMillis 미설정 시 경고를 출력합니다.
|
|
21
|
+
* - Pool 에러 이벤트를 감지해 로깅합니다.
|
|
22
|
+
*/
|
|
23
|
+
function getPool(config) {
|
|
24
|
+
if (_pool)
|
|
25
|
+
return _pool;
|
|
26
|
+
const cfg = config ??
|
|
27
|
+
reader_1.PostgresConfig.fromEnv(new reader_1.NodeEnvSource(process.env)).toDriverOptions();
|
|
28
|
+
_maxConnections = cfg.max ?? 10;
|
|
29
|
+
if (!cfg.connectionTimeoutMillis) {
|
|
30
|
+
logger.warn('connectionTimeoutMillis 미설정: 연결 획득 시 무한 대기가 발생할 수 있습니다. ' +
|
|
31
|
+
'DB_CONNECTION_TIMEOUT 환경 변수를 설정하세요.');
|
|
32
|
+
}
|
|
33
|
+
_pool = new pg_1.Pool(cfg);
|
|
34
|
+
_pool.on('error', (err, client) => {
|
|
35
|
+
const dbErr = dbError_1.DbError.from(err);
|
|
36
|
+
logger.error('유휴 클라이언트 오류', {
|
|
37
|
+
...dbErr.toLogContext(),
|
|
38
|
+
clientPid: client.processID,
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
_pool.on('connect', () => {
|
|
42
|
+
const status = readPoolStatus(_pool);
|
|
43
|
+
logger.debug('새 연결 생성', {
|
|
44
|
+
totalCount: status.totalCount,
|
|
45
|
+
idleCount: status.idleCount,
|
|
46
|
+
waitingCount: status.waitingCount,
|
|
47
|
+
});
|
|
48
|
+
if (status.totalCount >= _maxConnections) {
|
|
49
|
+
logger.warn('Pool이 최대 연결 수에 도달했습니다.', {
|
|
50
|
+
totalCount: status.totalCount,
|
|
51
|
+
max: _maxConnections,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
_pool.on('remove', () => {
|
|
56
|
+
logger.debug('연결 제거됨', { totalCount: _pool?.totalCount ?? 0 });
|
|
57
|
+
});
|
|
58
|
+
logger.info('Pool 생성 완료', {
|
|
59
|
+
max: _maxConnections,
|
|
60
|
+
connectionTimeoutMillis: cfg.connectionTimeoutMillis,
|
|
61
|
+
idleTimeoutMillis: cfg.idleTimeoutMillis,
|
|
62
|
+
});
|
|
63
|
+
return _pool;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Pool에서 client를 빌려 콜백을 실행한 뒤 자동으로 release합니다.
|
|
67
|
+
*
|
|
68
|
+
* - 연결 획득 실패(연결 수 초과, 타임아웃 등)는 DbError로 변환됩니다.
|
|
69
|
+
* - client는 성공/실패 여부와 무관하게 반드시 release됩니다.
|
|
70
|
+
*/
|
|
71
|
+
async function withClient(fn) {
|
|
72
|
+
let client;
|
|
73
|
+
try {
|
|
74
|
+
client = await getPool().connect();
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
const dbErr = dbError_1.DbError.from(err);
|
|
78
|
+
logger.error('클라이언트 획득 실패', dbErr.toLogContext());
|
|
79
|
+
throw dbErr;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
return await fn(client);
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
client.release();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 현재 Pool 상태를 반환합니다.
|
|
90
|
+
* Pool이 생성되지 않았을 경우 모든 값이 0입니다.
|
|
91
|
+
*/
|
|
92
|
+
function getPoolStatus() {
|
|
93
|
+
if (!_pool) {
|
|
94
|
+
return { totalCount: 0, idleCount: 0, waitingCount: 0, isHealthy: true };
|
|
95
|
+
}
|
|
96
|
+
return readPoolStatus(_pool);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Pool과 DB 서버 간의 연결이 살아있는지 확인합니다.
|
|
100
|
+
* `SELECT 1` 쿼리를 실행하고 결과를 반환합니다.
|
|
101
|
+
*/
|
|
102
|
+
async function checkPoolHealth() {
|
|
103
|
+
try {
|
|
104
|
+
await withClient(async (client) => {
|
|
105
|
+
await client.query('SELECT 1');
|
|
106
|
+
});
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 애플리케이션 종료 시 Pool을 안전하게 닫습니다.
|
|
115
|
+
*/
|
|
116
|
+
async function closePool() {
|
|
117
|
+
if (!_pool)
|
|
118
|
+
return;
|
|
119
|
+
const p = _pool;
|
|
120
|
+
_pool = null;
|
|
121
|
+
await p.end();
|
|
122
|
+
logger.info('Pool 종료 완료');
|
|
123
|
+
}
|
|
124
|
+
function readPoolStatus(pool) {
|
|
125
|
+
return {
|
|
126
|
+
totalCount: pool.totalCount,
|
|
127
|
+
idleCount: pool.idleCount,
|
|
128
|
+
waitingCount: pool.waitingCount,
|
|
129
|
+
isHealthy: pool.idleCount > 0 || pool.waitingCount === 0,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { PoolClient } from 'pg';
|
|
2
|
+
/**
|
|
3
|
+
* 트랜잭션 내에서 콜백을 실행합니다.
|
|
4
|
+
*
|
|
5
|
+
* - 성공 시 COMMIT, 실패 시 ROLLBACK됩니다.
|
|
6
|
+
* - ROLLBACK 실패 시에도 원래 에러가 전파됩니다.
|
|
7
|
+
* - 모든 에러는 DbError로 변환됩니다.
|
|
8
|
+
*/
|
|
9
|
+
export declare function runInTx<T>(fn: (client: PoolClient) => Promise<T>): Promise<T>;
|
|
10
|
+
//# sourceMappingURL=tx.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tx.d.ts","sourceRoot":"","sources":["../../../src/features/connection/tx.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAUhC;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAC7B,EAAE,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GACrC,OAAO,CAAC,CAAC,CAAC,CAuBZ"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runInTx = runInTx;
|
|
4
|
+
const logger_1 = require("../../utils/logger");
|
|
5
|
+
const dbError_1 = require("../../utils/dbError");
|
|
6
|
+
const pool_1 = require("./pool");
|
|
7
|
+
const logger = logger_1.Logger.fromEnv(process.env, { prefix: '[Tx]' });
|
|
8
|
+
/**
|
|
9
|
+
* 트랜잭션 내에서 콜백을 실행합니다.
|
|
10
|
+
*
|
|
11
|
+
* - 성공 시 COMMIT, 실패 시 ROLLBACK됩니다.
|
|
12
|
+
* - ROLLBACK 실패 시에도 원래 에러가 전파됩니다.
|
|
13
|
+
* - 모든 에러는 DbError로 변환됩니다.
|
|
14
|
+
*/
|
|
15
|
+
async function runInTx(fn) {
|
|
16
|
+
return (0, pool_1.withClient)(async (client) => {
|
|
17
|
+
await client.query('BEGIN');
|
|
18
|
+
logger.debug('트랜잭션 시작');
|
|
19
|
+
try {
|
|
20
|
+
const result = await fn(client);
|
|
21
|
+
await client.query('COMMIT');
|
|
22
|
+
logger.debug('트랜잭션 커밋');
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
const dbErr = dbError_1.DbError.from(err);
|
|
27
|
+
try {
|
|
28
|
+
await client.query('ROLLBACK');
|
|
29
|
+
logger.warn('트랜잭션 롤백', dbErr.toLogContext());
|
|
30
|
+
}
|
|
31
|
+
catch (rollbackErr) {
|
|
32
|
+
logger.error('롤백 실패', dbError_1.DbError.from(rollbackErr).toLogContext());
|
|
33
|
+
}
|
|
34
|
+
throw dbErr;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { AdvancedWhere, JoinClause, AggregateCalc, PaginateOpts, PageResult, OrderByClause, KeysetPaginateOpts, KeysetPageResult, StreamOpts, ExecHooks } from './interfaces/Advanced';
|
|
2
|
+
/**
|
|
3
|
+
* 플루언트 쿼리 빌더.
|
|
4
|
+
*
|
|
5
|
+
* ### 기본 사용
|
|
6
|
+
* ```ts
|
|
7
|
+
* const users = await repo.select({ isActive: true })
|
|
8
|
+
* .or({ email: { operator: 'ILIKE', value: '%@gmail.com' } })
|
|
9
|
+
* .orderBy([{ column: 'createdAt', direction: 'DESC' }])
|
|
10
|
+
* .limit(20);
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* ### 페이지네이션 (OFFSET 방식)
|
|
14
|
+
* ```ts
|
|
15
|
+
* const page = await repo.select().paginate({ page: 1, pageSize: 20 });
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* ### 커서 기반 페이지네이션 (대용량 최적화)
|
|
19
|
+
* ```ts
|
|
20
|
+
* const p1 = await repo.select().cursorPaginate({ pageSize: 20, cursorColumn: 'id' });
|
|
21
|
+
* const p2 = await repo.select().cursorPaginate({ pageSize: 20, cursorColumn: 'id', cursor: p1.nextCursor });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ### 스트리밍 (메모리 효율)
|
|
25
|
+
* ```ts
|
|
26
|
+
* for await (const user of repo.select().stream()) {
|
|
27
|
+
* await processRow(user);
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* ### 배치 처리
|
|
32
|
+
* ```ts
|
|
33
|
+
* await repo.select().forEach(async (batch) => {
|
|
34
|
+
* await processBatch(batch);
|
|
35
|
+
* }, { batchSize: 500 });
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* ### 훅
|
|
39
|
+
* ```ts
|
|
40
|
+
* repo.select()
|
|
41
|
+
* .hooks({ beforeExec: ({ sql }) => console.log(sql) })
|
|
42
|
+
* .exec();
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare class QueryBuilder<T extends Record<string, unknown>> {
|
|
46
|
+
private readonly _table;
|
|
47
|
+
private _andConds;
|
|
48
|
+
private _orConds;
|
|
49
|
+
private _orderByClauses;
|
|
50
|
+
private _limitVal?;
|
|
51
|
+
private _offsetVal?;
|
|
52
|
+
private _groupByCols;
|
|
53
|
+
private _joins;
|
|
54
|
+
private _cols;
|
|
55
|
+
private _execHooks?;
|
|
56
|
+
constructor(table: string, initWhere?: AdvancedWhere<T>);
|
|
57
|
+
/** AND 조건 추가 */
|
|
58
|
+
where(conditions: AdvancedWhere<T>): this;
|
|
59
|
+
/** OR 조건 추가 */
|
|
60
|
+
or(conditions: AdvancedWhere<T>): this;
|
|
61
|
+
/**
|
|
62
|
+
* ORDER BY 설정
|
|
63
|
+
* @example .orderBy([{ column: 'createdAt', direction: 'DESC' }])
|
|
64
|
+
*/
|
|
65
|
+
orderBy(clauses: OrderByClause<T>[]): this;
|
|
66
|
+
limit(n: number): this;
|
|
67
|
+
offset(n: number): this;
|
|
68
|
+
/**
|
|
69
|
+
* GROUP BY 설정
|
|
70
|
+
* @example .groupBy(['status', 'isActive'])
|
|
71
|
+
*/
|
|
72
|
+
groupBy(columns: Array<keyof T | string>): this;
|
|
73
|
+
/**
|
|
74
|
+
* JOIN 추가 (여러 번 호출 가능)
|
|
75
|
+
* @example
|
|
76
|
+
* .join({ table: 'orders', on: 'users.id = orders.user_id', type: 'LEFT' })
|
|
77
|
+
*/
|
|
78
|
+
join(j: JoinClause): this;
|
|
79
|
+
/**
|
|
80
|
+
* SELECT 할 컬럼 지정 (기본값: *)
|
|
81
|
+
* @example .columns(['id', 'email', 'firstName'])
|
|
82
|
+
*/
|
|
83
|
+
columns(cols: Array<keyof T | string>): this;
|
|
84
|
+
/**
|
|
85
|
+
* 쿼리 실행 라이프사이클 훅을 등록합니다.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* repo.select()
|
|
90
|
+
* .hooks({
|
|
91
|
+
* beforeExec: ({ sql }) => logger.debug('SQL:', sql),
|
|
92
|
+
* afterExec: ({ elapsed }) => metrics.record(elapsed),
|
|
93
|
+
* onError: ({ err }) => alerting.notify(err),
|
|
94
|
+
* })
|
|
95
|
+
* .exec();
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
hooks(h: ExecHooks<T>): this;
|
|
99
|
+
/**
|
|
100
|
+
* 쿼리를 실행하고 rows 배열을 반환합니다.
|
|
101
|
+
* `await builder` 와 동일합니다.
|
|
102
|
+
*/
|
|
103
|
+
exec(): Promise<T[]>;
|
|
104
|
+
/**
|
|
105
|
+
* 첫 번째 row 하나를 반환합니다. 없으면 null입니다.
|
|
106
|
+
*/
|
|
107
|
+
one(): Promise<T | null>;
|
|
108
|
+
/**
|
|
109
|
+
* 집계 함수를 실행합니다.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* const result = await repo.select({ isActive: true })
|
|
114
|
+
* .calculate([
|
|
115
|
+
* { fn: 'COUNT', alias: 'count' },
|
|
116
|
+
* { fn: 'AVG', column: 'price', alias: 'avgPrice' },
|
|
117
|
+
* ]);
|
|
118
|
+
* // → { count: '5', avgPrice: '12000.00' }
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
calculate(fns: AggregateCalc[]): Promise<Record<string, unknown>>;
|
|
122
|
+
/**
|
|
123
|
+
* OFFSET 기반 페이지네이션.
|
|
124
|
+
* COUNT + DATA 쿼리를 병렬로 실행합니다.
|
|
125
|
+
*
|
|
126
|
+
* > 수백만 건 이상의 테이블에서는 `cursorPaginate()`를 사용하세요.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* const result = await repo.select()
|
|
131
|
+
* .paginate({ page: 1, pageSize: 20 });
|
|
132
|
+
* // → { data, count, page, pageSize, nextAction, previousAction }
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
paginate(opts: PaginateOpts): Promise<PageResult<T>>;
|
|
136
|
+
/**
|
|
137
|
+
* 커서(Keyset) 기반 페이지네이션.
|
|
138
|
+
*
|
|
139
|
+
* OFFSET 스캔 없이 `WHERE cursor_col > last_value` 방식으로 동작하므로
|
|
140
|
+
* 수천만 건 규모에서도 일정한 응답 속도를 보장합니다.
|
|
141
|
+
*
|
|
142
|
+
* - `cursorColumn`에는 반드시 인덱스가 존재해야 합니다.
|
|
143
|
+
* - 결과는 항상 `cursorColumn` 기준으로 정렬됩니다.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```ts
|
|
147
|
+
* // 첫 페이지
|
|
148
|
+
* const p1 = await repo.select({ isActive: true })
|
|
149
|
+
* .cursorPaginate({ pageSize: 20, cursorColumn: 'id' });
|
|
150
|
+
*
|
|
151
|
+
* // 다음 페이지
|
|
152
|
+
* if (p1.hasNext) {
|
|
153
|
+
* const p2 = await repo.select({ isActive: true })
|
|
154
|
+
* .cursorPaginate({ pageSize: 20, cursorColumn: 'id', cursor: p1.nextCursor });
|
|
155
|
+
* }
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
cursorPaginate(opts: KeysetPaginateOpts): Promise<KeysetPageResult<T>>;
|
|
159
|
+
/**
|
|
160
|
+
* 결과를 배치 단위로 처리합니다. 전체 데이터를 메모리에 올리지 않습니다.
|
|
161
|
+
*
|
|
162
|
+
* 대용량 테이블의 일괄 처리(ETL, 이메일 발송, 마이그레이션 등)에 적합합니다.
|
|
163
|
+
*
|
|
164
|
+
* @param fn - 배치 배열을 받아 처리하는 비동기 함수
|
|
165
|
+
* @param opts.batchSize - 한 번에 처리할 row 수 (기본값: 500)
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```ts
|
|
169
|
+
* await repo.select({ isActive: true })
|
|
170
|
+
* .orderBy([{ column: 'id', direction: 'ASC' }])
|
|
171
|
+
* .forEach(async (batch) => {
|
|
172
|
+
* await sendEmailBatch(batch);
|
|
173
|
+
* }, { batchSize: 200 });
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
forEach(fn: (batch: T[]) => Promise<void>, opts?: StreamOpts): Promise<void>;
|
|
177
|
+
/**
|
|
178
|
+
* AsyncGenerator로 row를 하나씩 yield합니다.
|
|
179
|
+
* 내부적으로 배치 단위로 DB를 조회하여 메모리 효율을 유지합니다.
|
|
180
|
+
*
|
|
181
|
+
* @param opts.batchSize - 내부 배치 크기 (기본값: 500)
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* for await (const user of repo.select({ isActive: true }).stream()) {
|
|
186
|
+
* await processRow(user);
|
|
187
|
+
* }
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
stream(opts?: StreamOpts): AsyncGenerator<T, void, unknown>;
|
|
191
|
+
/**
|
|
192
|
+
* `for await...of` 직접 사용을 지원합니다. `.stream()` 과 동일합니다.
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* for await (const user of repo.select()) {
|
|
197
|
+
* // 각 row를 순서대로 처리
|
|
198
|
+
* }
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
[Symbol.asyncIterator](): AsyncGenerator<T, void, unknown>;
|
|
202
|
+
/**
|
|
203
|
+
* EXPLAIN (ANALYZE) 결과를 반환합니다.
|
|
204
|
+
* 쿼리 플랜 분석 및 인덱스 사용 여부 확인에 사용합니다.
|
|
205
|
+
*
|
|
206
|
+
* @param analyze - true이면 실제 실행 후 통계를 포함합니다 (기본값: false)
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```ts
|
|
210
|
+
* const plan = await repo.select({ isActive: true }).explain(true);
|
|
211
|
+
* console.log(plan);
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
explain(analyze?: boolean): Promise<string>;
|
|
215
|
+
/**
|
|
216
|
+
* 현재 builder 상태에서 최종 SQL과 params를 반환합니다.
|
|
217
|
+
* 디버깅 또는 로깅 목적으로 사용합니다.
|
|
218
|
+
*/
|
|
219
|
+
toSQL(): {
|
|
220
|
+
sql: string;
|
|
221
|
+
params: unknown[];
|
|
222
|
+
};
|
|
223
|
+
/**
|
|
224
|
+
* Raw SQL을 직접 실행합니다.
|
|
225
|
+
* DB 컬럼명(snake_case) → TypeScript(camelCase) 자동 변환이 적용됩니다.
|
|
226
|
+
*
|
|
227
|
+
* @example
|
|
228
|
+
* ```ts
|
|
229
|
+
* const rows = await QueryBuilder.raw<UserRow>(
|
|
230
|
+
* 'SELECT * FROM users WHERE first_name ILIKE $1',
|
|
231
|
+
* ['%john%'],
|
|
232
|
+
* );
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
static raw<R extends Record<string, unknown> = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<R[]>;
|
|
236
|
+
/**
|
|
237
|
+
* builder 상태를 독립적으로 복사합니다.
|
|
238
|
+
* stream/forEach 내부에서 LIMIT/OFFSET을 덮어쓸 때 원본을 보호하기 위해 사용합니다.
|
|
239
|
+
*/
|
|
240
|
+
clone(): QueryBuilder<T>;
|
|
241
|
+
then<R>(onfulfilled: ((value: T[]) => R | PromiseLike<R>) | null | undefined, onrejected?: ((reason: unknown) => R | PromiseLike<R>) | null | undefined): Promise<R>;
|
|
242
|
+
catch<R = never>(onrejected: ((reason: unknown) => R | PromiseLike<R>) | null | undefined): Promise<T[] | R>;
|
|
243
|
+
private buildWhereParts;
|
|
244
|
+
private buildJoinSQL;
|
|
245
|
+
private buildGroupBySQL;
|
|
246
|
+
private buildOrderBySQL;
|
|
247
|
+
private buildSelectSQL;
|
|
248
|
+
/**
|
|
249
|
+
* 공통 쿼리 실행 (훅 + 로깅 포함).
|
|
250
|
+
*/
|
|
251
|
+
private runQuery;
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../../../src/features/query/builder.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,aAAa,EAEb,UAAU,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,aAAa,EAEb,kBAAkB,EAClB,gBAAgB,EAChB,UAAU,EACV,SAAS,EACV,MAAM,uBAAuB,CAAC;AA4D/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,qBAAa,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,QAAQ,CAAmB;IACnC,OAAO,CAAC,eAAe,CAAmD;IAC1E,OAAO,CAAC,SAAS,CAAC,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAC,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAiB;IACrC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,KAAK,CAAO;IACpB,OAAO,CAAC,UAAU,CAAC,CAAe;gBAEtB,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC;IASvD,gBAAgB;IAChB,KAAK,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI;IAKzC,eAAe;IACf,EAAE,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI;IAKtC;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI;IAQ1C,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAKtB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAKvB;;;OAGG;IACH,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI;IAK/C;;;;OAIG;IACH,IAAI,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI;IAKzB;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI;IAK5C;;;;;;;;;;;;;OAaG;IACH,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI;IAO5B;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC;IAK1B;;OAEG;IACG,GAAG,IAAI,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAW9B;;;;;;;;;;;;OAYG;IACG,SAAS,CAAC,GAAG,EAAE,aAAa,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAoBvE;;;;;;;;;;;;OAYG;IACG,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAoE1D;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;IA2C5E;;;;;;;;;;;;;;;;OAgBG;IACG,OAAO,CACX,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,EACjC,IAAI,CAAC,EAAE,UAAU,GAChB,OAAO,CAAC,IAAI,CAAC;IA0BhB;;;;;;;;;;;;OAYG;IACI,MAAM,CAAC,IAAI,CAAC,EAAE,UAAU,GAAG,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC;IA2BlE;;;;;;;;;OASG;IACH,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC;IAI1D;;;;;;;;;;;OAWG;IACG,OAAO,CAAC,OAAO,UAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;IAS/C;;;OAGG;IACH,KAAK,IAAI;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,EAAE,CAAA;KAAE;IAI3C;;;;;;;;;;;OAWG;WACU,GAAG,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,OAAO,EAAE,GACjB,OAAO,CAAC,CAAC,EAAE,CAAC;IAYf;;;OAGG;IACH,KAAK,IAAI,YAAY,CAAC,CAAC,CAAC;IAgBxB,IAAI,CAAC,CAAC,EACJ,WAAW,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,EACpE,UAAU,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,GACxE,OAAO,CAAC,CAAC,CAAC;IAIb,KAAK,CAAC,CAAC,GAAG,KAAK,EACb,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,GACvE,OAAO,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAMnB,OAAO,CAAC,eAAe;IAgBvB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,cAAc;IAuBtB;;OAEG;YACW,QAAQ;CA0CvB"}
|