@simplysm/orm-common 14.0.49 → 14.0.51
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/README.md +39 -172
- package/dist/db-context.d.ts +6 -6
- package/dist/db-context.d.ts.map +1 -1
- package/dist/db-context.js.map +1 -1
- package/dist/ddl/initialize.js.map +1 -1
- package/dist/ddl/table-ddl.d.ts +3 -3
- package/dist/ddl/table-ddl.d.ts.map +1 -1
- package/dist/ddl/table-ddl.js.map +1 -1
- package/dist/exec/queryable.d.ts +9 -9
- package/dist/exec/queryable.d.ts.map +1 -1
- package/dist/exec/queryable.js.map +1 -1
- package/dist/models/system-migration.d.ts +1 -1
- package/dist/schema/factory/relation-builder.d.ts +17 -17
- package/dist/schema/factory/relation-builder.d.ts.map +1 -1
- package/dist/schema/factory/relation-builder.js.map +1 -1
- package/dist/schema/table-builder.d.ts +12 -12
- package/dist/schema/table-builder.d.ts.map +1 -1
- package/dist/schema/table-builder.js +1 -1
- package/dist/schema/table-builder.js.map +1 -1
- package/dist/types/db-context-def.d.ts +4 -4
- package/dist/types/db-context-def.d.ts.map +1 -1
- package/dist/utils/result-parser.js.map +1 -1
- package/docs/core/db-context.md +208 -0
- package/docs/core/db-transaction-error.md +64 -0
- package/docs/expression/expr-unit.md +62 -0
- package/docs/expression/expr.md +198 -0
- package/docs/models/migration.md +37 -0
- package/docs/query-builder/create-query-builder.md +80 -0
- package/docs/queryable-executable/executable.md +54 -0
- package/docs/queryable-executable/parse-search-query.md +75 -0
- package/docs/queryable-executable/queryable.md +238 -0
- package/docs/schema-builders/column-builder.md +63 -0
- package/docs/schema-builders/foreign-key-builder.md +137 -0
- package/docs/schema-builders/index-builder.md +54 -0
- package/docs/schema-builders/procedure.md +67 -0
- package/docs/schema-builders/table.md +94 -0
- package/docs/schema-builders/view.md +71 -0
- package/docs/types/data-type.md +146 -0
- package/docs/types/dialect.md +151 -0
- package/docs/types/expr.md +175 -0
- package/docs/types/parse-query-result.md +58 -0
- package/docs/types/query-def.md +224 -0
- package/package.json +2 -2
- package/src/db-context.ts +8 -8
- package/src/ddl/initialize.ts +4 -4
- package/src/ddl/table-ddl.ts +3 -3
- package/src/exec/queryable.ts +19 -19
- package/src/schema/factory/relation-builder.ts +45 -38
- package/src/schema/table-builder.ts +13 -12
- package/src/types/db-context-def.ts +4 -4
- package/src/utils/result-parser.ts +57 -57
- package/docs/core.md +0 -188
- package/docs/expression.md +0 -190
- package/docs/models.md +0 -17
- package/docs/query-builder.md +0 -97
- package/docs/queryable-executable.md +0 -311
- package/docs/schema-builders.md +0 -364
- package/docs/types.md +0 -537
|
@@ -36,8 +36,8 @@ import type { ViewBuilder } from "../view-builder";
|
|
|
36
36
|
* @see {@link RelationKeyBuilder} DB FK 없는 관계
|
|
37
37
|
*/
|
|
38
38
|
export class ForeignKeyBuilder<
|
|
39
|
-
TOwner extends TableBuilder<any, any>,
|
|
40
|
-
TTargetFn extends () => TableBuilder<any, any>,
|
|
39
|
+
TOwner extends TableBuilder<any, any, any>,
|
|
40
|
+
TTargetFn extends () => TableBuilder<any, any, any>,
|
|
41
41
|
> {
|
|
42
42
|
/**
|
|
43
43
|
* @param meta - FK 메타데이터
|
|
@@ -91,7 +91,7 @@ export class ForeignKeyBuilder<
|
|
|
91
91
|
* @see {@link ForeignKeyBuilder} FK builder
|
|
92
92
|
*/
|
|
93
93
|
export class ForeignKeyTargetBuilder<
|
|
94
|
-
TTargetTableFn extends () => TableBuilder<any, any>,
|
|
94
|
+
TTargetTableFn extends () => TableBuilder<any, any, any>,
|
|
95
95
|
TIsSingle extends boolean,
|
|
96
96
|
> {
|
|
97
97
|
/**
|
|
@@ -140,8 +140,8 @@ export class ForeignKeyTargetBuilder<
|
|
|
140
140
|
* @see {@link ForeignKeyBuilder} DB FK 생성 버전
|
|
141
141
|
*/
|
|
142
142
|
export class RelationKeyBuilder<
|
|
143
|
-
TOwner extends TableBuilder<any, any> | ViewBuilder<any, any, any>,
|
|
144
|
-
TTargetFn extends () => TableBuilder<any, any> | ViewBuilder<any, any, any>,
|
|
143
|
+
TOwner extends TableBuilder<any, any, any> | ViewBuilder<any, any, any>,
|
|
144
|
+
TTargetFn extends () => TableBuilder<any, any, any> | ViewBuilder<any, any, any>,
|
|
145
145
|
> {
|
|
146
146
|
/**
|
|
147
147
|
* @param meta - 관계 메타데이터
|
|
@@ -186,7 +186,7 @@ export class RelationKeyBuilder<
|
|
|
186
186
|
* @see {@link ForeignKeyTargetBuilder} DB FK 생성 버전
|
|
187
187
|
*/
|
|
188
188
|
export class RelationKeyTargetBuilder<
|
|
189
|
-
TTargetTableFn extends () => TableBuilder<any, any> | ViewBuilder<any, any, any>,
|
|
189
|
+
TTargetTableFn extends () => TableBuilder<any, any, any> | ViewBuilder<any, any, any>,
|
|
190
190
|
TIsSingle extends boolean,
|
|
191
191
|
> {
|
|
192
192
|
/**
|
|
@@ -212,20 +212,20 @@ export class RelationKeyTargetBuilder<
|
|
|
212
212
|
* @template TOwner - 소유 Table builder 타입
|
|
213
213
|
* @template TColumnKey - Column key 타입
|
|
214
214
|
*/
|
|
215
|
-
type RelationFkFactory<TOwner extends TableBuilder<any, any>, TColumnKey extends string> = {
|
|
215
|
+
type RelationFkFactory<TOwner extends TableBuilder<any, any, any>, TColumnKey extends string> = {
|
|
216
216
|
/** N:1 FK 관계 정의 (DB FK 생성) */
|
|
217
|
-
foreignKey<TTargetFn extends () => TableBuilder<any, any>>(
|
|
217
|
+
foreignKey<TTargetFn extends () => TableBuilder<any, any, any>>(
|
|
218
218
|
columns: TColumnKey[],
|
|
219
219
|
targetFn: TTargetFn,
|
|
220
220
|
opts?: { description?: string },
|
|
221
221
|
): ForeignKeyBuilder<TOwner, TTargetFn>;
|
|
222
222
|
/** 1:N FK 역참조 정의 (single: true → 단일 객체) */
|
|
223
|
-
foreignKeyTarget<TTargetTableFn extends () => TableBuilder<any, any>>(
|
|
223
|
+
foreignKeyTarget<TTargetTableFn extends () => TableBuilder<any, any, any>>(
|
|
224
224
|
targetTableFn: TTargetTableFn,
|
|
225
225
|
relationName: string,
|
|
226
226
|
opts: { single: true; description?: string },
|
|
227
227
|
): ForeignKeyTargetBuilder<TTargetTableFn, true>;
|
|
228
|
-
foreignKeyTarget<TTargetTableFn extends () => TableBuilder<any, any>>(
|
|
228
|
+
foreignKeyTarget<TTargetTableFn extends () => TableBuilder<any, any, any>>(
|
|
229
229
|
targetTableFn: TTargetTableFn,
|
|
230
230
|
relationName: string,
|
|
231
231
|
opts?: { single?: false; description?: string },
|
|
@@ -239,25 +239,25 @@ type RelationFkFactory<TOwner extends TableBuilder<any, any>, TColumnKey extends
|
|
|
239
239
|
* @template TColumnKey - Column key 타입
|
|
240
240
|
*/
|
|
241
241
|
type RelationRkFactory<
|
|
242
|
-
TOwner extends TableBuilder<any, any> | ViewBuilder<any, any, any>,
|
|
242
|
+
TOwner extends TableBuilder<any, any, any> | ViewBuilder<any, any, any>,
|
|
243
243
|
TColumnKey extends string,
|
|
244
244
|
> = {
|
|
245
245
|
/** N:1 논리적 관계 정의 (DB FK 미생성) */
|
|
246
|
-
relationKey<TTargetFn extends () => TableBuilder<any, any> | ViewBuilder<any, any, any>>(
|
|
246
|
+
relationKey<TTargetFn extends () => TableBuilder<any, any, any> | ViewBuilder<any, any, any>>(
|
|
247
247
|
columns: TColumnKey[],
|
|
248
248
|
targetFn: TTargetFn,
|
|
249
249
|
opts?: { description?: string },
|
|
250
250
|
): RelationKeyBuilder<TOwner, TTargetFn>;
|
|
251
251
|
/** 1:N 논리적 역참조 정의 (single: true → 단일 객체) */
|
|
252
252
|
relationKeyTarget<
|
|
253
|
-
TTargetTableFn extends () => TableBuilder<any, any> | ViewBuilder<any, any, any>,
|
|
253
|
+
TTargetTableFn extends () => TableBuilder<any, any, any> | ViewBuilder<any, any, any>,
|
|
254
254
|
>(
|
|
255
255
|
targetTableFn: TTargetTableFn,
|
|
256
256
|
relationName: string,
|
|
257
257
|
opts: { single: true; description?: string },
|
|
258
258
|
): RelationKeyTargetBuilder<TTargetTableFn, true>;
|
|
259
259
|
relationKeyTarget<
|
|
260
|
-
TTargetTableFn extends () => TableBuilder<any, any> | ViewBuilder<any, any, any>,
|
|
260
|
+
TTargetTableFn extends () => TableBuilder<any, any, any> | ViewBuilder<any, any, any>,
|
|
261
261
|
>(
|
|
262
262
|
targetTableFn: TTargetTableFn,
|
|
263
263
|
relationName: string,
|
|
@@ -297,17 +297,17 @@ type RelationRkFactory<
|
|
|
297
297
|
* ```
|
|
298
298
|
*/
|
|
299
299
|
export function createRelationFactory<
|
|
300
|
-
TOwner extends TableBuilder<any, any> | ViewBuilder<any, any, any>,
|
|
300
|
+
TOwner extends TableBuilder<any, any, any> | ViewBuilder<any, any, any>,
|
|
301
301
|
TColumnKey extends string,
|
|
302
302
|
>(
|
|
303
303
|
ownerFn: () => TOwner,
|
|
304
|
-
): TOwner extends TableBuilder<any, any>
|
|
304
|
+
): TOwner extends TableBuilder<any, any, any>
|
|
305
305
|
? RelationFkFactory<TOwner, TColumnKey> & RelationRkFactory<TOwner, TColumnKey>
|
|
306
306
|
: RelationRkFactory<TOwner, TColumnKey> {
|
|
307
307
|
return {
|
|
308
308
|
foreignKey(columns, targetFn, opts?) {
|
|
309
309
|
return new ForeignKeyBuilder({
|
|
310
|
-
ownerFn: ownerFn as () => TableBuilder<any, any>,
|
|
310
|
+
ownerFn: ownerFn as () => TableBuilder<any, any, any>,
|
|
311
311
|
columns,
|
|
312
312
|
targetFn,
|
|
313
313
|
description: opts?.description,
|
|
@@ -337,7 +337,7 @@ export function createRelationFactory<
|
|
|
337
337
|
isSingle: opts?.single,
|
|
338
338
|
});
|
|
339
339
|
},
|
|
340
|
-
} as TOwner extends TableBuilder<any, any>
|
|
340
|
+
} as TOwner extends TableBuilder<any, any, any>
|
|
341
341
|
? RelationFkFactory<TOwner, TColumnKey> & RelationRkFactory<TOwner, TColumnKey>
|
|
342
342
|
: RelationRkFactory<TOwner, TColumnKey>;
|
|
343
343
|
}
|
|
@@ -370,13 +370,15 @@ export type RelationBuilderRecord = Record<
|
|
|
370
370
|
*
|
|
371
371
|
* @template T - FK 또는 RelationKey builder 타입
|
|
372
372
|
*/
|
|
373
|
-
export type ExtractRelationTarget<TRelation> = TRelation extends
|
|
373
|
+
export type ExtractRelationTarget<TRelation, TVisited extends string = never> = TRelation extends
|
|
374
374
|
| ForeignKeyBuilder<any, infer TTargetFn>
|
|
375
375
|
| RelationKeyBuilder<any, infer TTargetFn>
|
|
376
|
-
? ReturnType<TTargetFn> extends TableBuilder<infer TCols, infer TRels>
|
|
377
|
-
?
|
|
376
|
+
? ReturnType<TTargetFn> extends TableBuilder<infer TName, infer TCols, infer TRels>
|
|
377
|
+
? TName extends TVisited
|
|
378
|
+
? InferColumns<TCols>
|
|
379
|
+
: InferColumns<TCols> & InferDeepRelations<TRels, TVisited | TName>
|
|
378
380
|
: ReturnType<TTargetFn> extends ViewBuilder<any, infer TData, infer TRels>
|
|
379
|
-
? TData & InferDeepRelations<TRels>
|
|
381
|
+
? TData & InferDeepRelations<TRels, TVisited>
|
|
380
382
|
: never
|
|
381
383
|
: never;
|
|
382
384
|
|
|
@@ -388,19 +390,24 @@ export type ExtractRelationTarget<TRelation> = TRelation extends
|
|
|
388
390
|
*
|
|
389
391
|
* @template T - FKTarget 또는 RelationKeyTarget builder 타입
|
|
390
392
|
*/
|
|
391
|
-
export type ExtractRelationTargetResult<TRelation
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
?
|
|
396
|
-
?
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
393
|
+
export type ExtractRelationTargetResult<TRelation, TVisited extends string = never> =
|
|
394
|
+
TRelation extends
|
|
395
|
+
| ForeignKeyTargetBuilder<infer TTargetTableFn, infer TIsSingle>
|
|
396
|
+
| RelationKeyTargetBuilder<infer TTargetTableFn, infer TIsSingle>
|
|
397
|
+
? ReturnType<TTargetTableFn> extends TableBuilder<infer TName, infer TCols, infer TRels>
|
|
398
|
+
? TName extends TVisited
|
|
399
|
+
? TIsSingle extends true
|
|
400
|
+
? InferColumns<TCols>
|
|
401
|
+
: InferColumns<TCols>[]
|
|
402
|
+
: TIsSingle extends true
|
|
403
|
+
? InferColumns<TCols> & InferDeepRelations<TRels, TVisited | TName>
|
|
404
|
+
: (InferColumns<TCols> & InferDeepRelations<TRels, TVisited | TName>)[]
|
|
405
|
+
: ReturnType<TTargetTableFn> extends ViewBuilder<any, infer TData, infer TRels>
|
|
406
|
+
? TIsSingle extends true
|
|
407
|
+
? TData & InferDeepRelations<TRels, TVisited>
|
|
408
|
+
: (TData & InferDeepRelations<TRels, TVisited>)[]
|
|
409
|
+
: never
|
|
410
|
+
: never;
|
|
404
411
|
|
|
405
412
|
/**
|
|
406
413
|
* 관계 정의에서 심층 관계 타입 추론
|
|
@@ -415,8 +422,8 @@ export type ExtractRelationTargetResult<TRelation> = TRelation extends
|
|
|
415
422
|
* // { posts?: Post[]; profile?: Profile; }
|
|
416
423
|
* ```
|
|
417
424
|
*/
|
|
418
|
-
export type InferDeepRelations<TRelations extends RelationBuilderRecord> = {
|
|
425
|
+
export type InferDeepRelations<TRelations extends RelationBuilderRecord, TVisited extends string = never> = {
|
|
419
426
|
[K in keyof TRelations]?:
|
|
420
|
-
| ExtractRelationTarget<TRelations[K]>
|
|
421
|
-
| ExtractRelationTargetResult<TRelations[K]>;
|
|
427
|
+
| ExtractRelationTarget<TRelations[K], TVisited>
|
|
428
|
+
| ExtractRelationTargetResult<TRelations[K], TVisited>;
|
|
422
429
|
};
|
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
* @see {@link queryable} Queryable 생성
|
|
50
50
|
*/
|
|
51
51
|
export class TableBuilder<
|
|
52
|
+
TName extends string,
|
|
52
53
|
TColumns extends ColumnBuilderRecord,
|
|
53
54
|
TRelations extends RelationBuilderRecord,
|
|
54
55
|
> {
|
|
@@ -57,7 +58,7 @@ export class TableBuilder<
|
|
|
57
58
|
/** 관계 정의 (타입 추론용) */
|
|
58
59
|
readonly $relations!: TRelations;
|
|
59
60
|
|
|
60
|
-
/** 전체 타입 추론 (column + 관계) */
|
|
61
|
+
/** 전체 타입 추론 (column + 관계) — 순환 감지: 같은 테이블 재방문 시 끊김 */
|
|
61
62
|
readonly $inferSelect!: InferColumns<TColumns> & InferDeepRelations<TRelations>;
|
|
62
63
|
/** Column 전용 타입 추론 */
|
|
63
64
|
readonly $inferColumns!: InferColumns<TColumns>;
|
|
@@ -79,7 +80,7 @@ export class TableBuilder<
|
|
|
79
80
|
*/
|
|
80
81
|
constructor(
|
|
81
82
|
readonly meta: {
|
|
82
|
-
name:
|
|
83
|
+
name: TName;
|
|
83
84
|
description?: string;
|
|
84
85
|
database?: string;
|
|
85
86
|
schema?: string;
|
|
@@ -97,7 +98,7 @@ export class TableBuilder<
|
|
|
97
98
|
* @param desc - Table 설명 (DDL Comment로 사용됨)
|
|
98
99
|
* @returns 새 TableBuilder 인스턴스
|
|
99
100
|
*/
|
|
100
|
-
description(desc: string) {
|
|
101
|
+
description(desc: string): TableBuilder<TName, TColumns, TRelations> {
|
|
101
102
|
return new TableBuilder({ ...this.meta, description: desc });
|
|
102
103
|
}
|
|
103
104
|
|
|
@@ -112,7 +113,7 @@ export class TableBuilder<
|
|
|
112
113
|
* const User = Table("User").database("mydb");
|
|
113
114
|
* ```
|
|
114
115
|
*/
|
|
115
|
-
database(db: string) {
|
|
116
|
+
database(db: string): TableBuilder<TName, TColumns, TRelations> {
|
|
116
117
|
return new TableBuilder({ ...this.meta, database: db });
|
|
117
118
|
}
|
|
118
119
|
|
|
@@ -131,7 +132,7 @@ export class TableBuilder<
|
|
|
131
132
|
* .schema("custom_schema");
|
|
132
133
|
* ```
|
|
133
134
|
*/
|
|
134
|
-
schema(schema: string) {
|
|
135
|
+
schema(schema: string): TableBuilder<TName, TColumns, TRelations> {
|
|
135
136
|
return new TableBuilder({ ...this.meta, schema });
|
|
136
137
|
}
|
|
137
138
|
|
|
@@ -157,8 +158,8 @@ export class TableBuilder<
|
|
|
157
158
|
*/
|
|
158
159
|
columns<TNewColumnDefs extends ColumnBuilderRecord>(
|
|
159
160
|
fn: (c: ReturnType<typeof createColumnFactory>) => TNewColumnDefs,
|
|
160
|
-
) {
|
|
161
|
-
return new TableBuilder<TNewColumnDefs, TRelations>({
|
|
161
|
+
): TableBuilder<TName, TNewColumnDefs, TRelations> {
|
|
162
|
+
return new TableBuilder<TName, TNewColumnDefs, TRelations>({
|
|
162
163
|
...this.meta,
|
|
163
164
|
columns: fn(createColumnFactory()),
|
|
164
165
|
});
|
|
@@ -186,7 +187,7 @@ export class TableBuilder<
|
|
|
186
187
|
* .primaryKey("userId", "roleId");
|
|
187
188
|
* ```
|
|
188
189
|
*/
|
|
189
|
-
primaryKey(...columns: (keyof TColumns & string)[]) {
|
|
190
|
+
primaryKey(...columns: (keyof TColumns & string)[]): TableBuilder<TName, TColumns, TRelations> {
|
|
190
191
|
return new TableBuilder({
|
|
191
192
|
...this.meta,
|
|
192
193
|
primaryKey: columns,
|
|
@@ -217,7 +218,7 @@ export class TableBuilder<
|
|
|
217
218
|
fn: (
|
|
218
219
|
i: ReturnType<typeof createIndexFactory<keyof TColumns & string>>,
|
|
219
220
|
) => IndexBuilder<string[]>[],
|
|
220
|
-
) {
|
|
221
|
+
): TableBuilder<TName, TColumns, TRelations> {
|
|
221
222
|
return new TableBuilder({
|
|
222
223
|
...this.meta,
|
|
223
224
|
indexes: fn(createIndexFactory<keyof TColumns & string>()),
|
|
@@ -264,7 +265,7 @@ export class TableBuilder<
|
|
|
264
265
|
*/
|
|
265
266
|
relations<T extends RelationBuilderRecord>(
|
|
266
267
|
fn: (r: ReturnType<typeof createRelationFactory<this, keyof TColumns & string>>) => T,
|
|
267
|
-
): TableBuilder<TColumns, T> {
|
|
268
|
+
): TableBuilder<TName, TColumns, T> {
|
|
268
269
|
return new TableBuilder({
|
|
269
270
|
...this.meta,
|
|
270
271
|
relations: fn(createRelationFactory<this, keyof TColumns & string>(() => this)),
|
|
@@ -314,6 +315,6 @@ export class TableBuilder<
|
|
|
314
315
|
*
|
|
315
316
|
* @see {@link TableBuilder} builder 클래스
|
|
316
317
|
*/
|
|
317
|
-
export function Table(name:
|
|
318
|
-
return new TableBuilder({ name });
|
|
318
|
+
export function Table<TName extends string>(name: TName) {
|
|
319
|
+
return new TableBuilder<TName, ColumnBuilderRecord, RelationBuilderRecord>({ name });
|
|
319
320
|
}
|
|
@@ -24,7 +24,7 @@ export interface DbContextBase {
|
|
|
24
24
|
resultMetas?: (ResultMeta | undefined)[],
|
|
25
25
|
): Promise<T[][]>;
|
|
26
26
|
getQueryDefObjectName(
|
|
27
|
-
tableOrView: TableBuilder<any, any> | ViewBuilder<any, any, any>,
|
|
27
|
+
tableOrView: TableBuilder<any, any, any> | ViewBuilder<any, any, any>,
|
|
28
28
|
): QueryDefObjectName;
|
|
29
29
|
switchFk(table: QueryDefObjectName, enabled: boolean): Promise<void>;
|
|
30
30
|
}
|
|
@@ -32,7 +32,7 @@ export interface DbContextBase {
|
|
|
32
32
|
export type DbContextStatus = "ready" | "connect" | "transact";
|
|
33
33
|
|
|
34
34
|
export interface DbContextDdlMethods {
|
|
35
|
-
createTable(table: TableBuilder<any, any>): Promise<void>;
|
|
35
|
+
createTable(table: TableBuilder<any, any, any>): Promise<void>;
|
|
36
36
|
dropTable(table: QueryDefObjectName): Promise<void>;
|
|
37
37
|
renameTable(table: QueryDefObjectName, newName: string): Promise<void>;
|
|
38
38
|
createView(view: ViewBuilder<any, any, any>): Promise<void>;
|
|
@@ -66,11 +66,11 @@ export interface DbContextDdlMethods {
|
|
|
66
66
|
truncate(table: QueryDefObjectName): Promise<void>;
|
|
67
67
|
switchFk(table: QueryDefObjectName, enabled: boolean): Promise<void>;
|
|
68
68
|
// QueryDef 생성기
|
|
69
|
-
getCreateTableQueryDef(table: TableBuilder<any, any>): QueryDef;
|
|
69
|
+
getCreateTableQueryDef(table: TableBuilder<any, any, any>): QueryDef;
|
|
70
70
|
getCreateViewQueryDef(view: ViewBuilder<any, any, any>): QueryDef;
|
|
71
71
|
getCreateProcQueryDef(procedure: ProcedureBuilder<any, any>): QueryDef;
|
|
72
72
|
getCreateObjectQueryDef(
|
|
73
|
-
builder: TableBuilder<any, any> | ViewBuilder<any, any, any> | ProcedureBuilder<any, any>,
|
|
73
|
+
builder: TableBuilder<any, any, any> | ViewBuilder<any, any, any> | ProcedureBuilder<any, any>,
|
|
74
74
|
): QueryDef;
|
|
75
75
|
getDropTableQueryDef(table: QueryDefObjectName): QueryDef;
|
|
76
76
|
getRenameTableQueryDef(table: QueryDefObjectName, newName: string): QueryDef;
|
|
@@ -16,11 +16,11 @@ declare function setImmediate(callback: () => void): void;
|
|
|
16
16
|
* @returns 파싱된 값
|
|
17
17
|
* @throws 파싱 실패 시 Error
|
|
18
18
|
*/
|
|
19
|
-
function parseValue(value: unknown, type: ColumnPrimitiveStr): unknown {
|
|
20
|
-
// undefined는 key 제거 대상으로 남기고, null은 SQL 결과값으로 보존한다.
|
|
21
|
-
if (value == null) {
|
|
22
|
-
return value;
|
|
23
|
-
}
|
|
19
|
+
function parseValue(value: unknown, type: ColumnPrimitiveStr): unknown {
|
|
20
|
+
// undefined는 key 제거 대상으로 남기고, null은 SQL 결과값으로 보존한다.
|
|
21
|
+
if (value == null) {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
24
|
|
|
25
25
|
switch (type) {
|
|
26
26
|
case "number": {
|
|
@@ -93,11 +93,11 @@ function flatToNested(
|
|
|
93
93
|
const result: Record<string, unknown> = {};
|
|
94
94
|
|
|
95
95
|
for (const { key, type, parts } of columnInfos) {
|
|
96
|
-
const rawValue = record[key];
|
|
97
|
-
if (typeof rawValue === "undefined") continue;
|
|
98
|
-
const parsedValue = parseValue(rawValue, type);
|
|
99
|
-
|
|
100
|
-
if (parts != null) {
|
|
96
|
+
const rawValue = record[key];
|
|
97
|
+
if (typeof rawValue === "undefined") continue;
|
|
98
|
+
const parsedValue = parseValue(rawValue, type);
|
|
99
|
+
|
|
100
|
+
if (parts != null) {
|
|
101
101
|
// 중첩 key: "posts.id" → { posts: { id: ... } }
|
|
102
102
|
let current = result;
|
|
103
103
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
@@ -120,45 +120,45 @@ function flatToNested(
|
|
|
120
120
|
/**
|
|
121
121
|
* 객체가 비어있는지 확인 (모든 값이 undefined)
|
|
122
122
|
*/
|
|
123
|
-
function isEmptyObject(record: Record<string, unknown>): boolean {
|
|
124
|
-
return Object.keys(record).length === 0;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* JOIN 결과 객체가 실질적으로 비어있는지 확인
|
|
129
|
-
*
|
|
130
|
-
* 모든 값이 null/undefined 이거나, 중첩 객체도 재귀적으로 비어있으면 비어있는 JOIN으로 간주한다.
|
|
131
|
-
*/
|
|
132
|
-
function isEmptyJoinObject(record: Record<string, unknown>): boolean {
|
|
133
|
-
const entries = Object.entries(record);
|
|
134
|
-
if (entries.length === 0) {
|
|
135
|
-
return true;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
for (const [, value] of entries) {
|
|
139
|
-
if (value == null) {
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (Array.isArray(value)) {
|
|
144
|
-
if (value.length > 0) {
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (typeof value === "object") {
|
|
151
|
-
if (!isEmptyJoinObject(value as Record<string, unknown>)) {
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
123
|
+
function isEmptyObject(record: Record<string, unknown>): boolean {
|
|
124
|
+
return Object.keys(record).length === 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* JOIN 결과 객체가 실질적으로 비어있는지 확인
|
|
129
|
+
*
|
|
130
|
+
* 모든 값이 null/undefined 이거나, 중첩 객체도 재귀적으로 비어있으면 비어있는 JOIN으로 간주한다.
|
|
131
|
+
*/
|
|
132
|
+
function isEmptyJoinObject(record: Record<string, unknown>): boolean {
|
|
133
|
+
const entries = Object.entries(record);
|
|
134
|
+
if (entries.length === 0) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (const [, value] of entries) {
|
|
139
|
+
if (value == null) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (Array.isArray(value)) {
|
|
144
|
+
if (value.length > 0) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (typeof value === "object") {
|
|
151
|
+
if (!isEmptyJoinObject(value as Record<string, unknown>)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
162
|
|
|
163
163
|
// ============================================
|
|
164
164
|
// 메인 함수
|
|
@@ -401,10 +401,10 @@ function groupRecordsRecursively(
|
|
|
401
401
|
const localKey = currentPath === "" ? joinKey : joinKey.slice(currentPath.length + 1);
|
|
402
402
|
const joinData = newGroup[localKey] as Record<string, unknown> | undefined;
|
|
403
403
|
|
|
404
|
-
if (joinData != null && !isEmptyJoinObject(joinData)) {
|
|
405
|
-
if (!joinsConfig[joinKey].isSingle) {
|
|
406
|
-
// 배열로 변환 (hashSet은 첫 merge 시 초기화)
|
|
407
|
-
newGroup[localKey] = [joinData];
|
|
404
|
+
if (joinData != null && !isEmptyJoinObject(joinData)) {
|
|
405
|
+
if (!joinsConfig[joinKey].isSingle) {
|
|
406
|
+
// 배열로 변환 (hashSet은 첫 merge 시 초기화)
|
|
407
|
+
newGroup[localKey] = [joinData];
|
|
408
408
|
}
|
|
409
409
|
} else {
|
|
410
410
|
// 데이터가 비어있으면 key 삭제
|
|
@@ -500,16 +500,16 @@ function mergeJoinData(
|
|
|
500
500
|
): void {
|
|
501
501
|
const newJoinData = newRecord[localKey] as Record<string, unknown> | undefined;
|
|
502
502
|
|
|
503
|
-
if (newJoinData == null || isEmptyJoinObject(newJoinData)) {
|
|
504
|
-
return; // 병합할 데이터 없음
|
|
505
|
-
}
|
|
503
|
+
if (newJoinData == null || isEmptyJoinObject(newJoinData)) {
|
|
504
|
+
return; // 병합할 데이터 없음
|
|
505
|
+
}
|
|
506
506
|
|
|
507
507
|
const existingJoinData = existingGroup[localKey];
|
|
508
508
|
|
|
509
509
|
if (isSingle) {
|
|
510
510
|
// isSingle: true - 데이터가 존재하고 값이 다르면 에러
|
|
511
511
|
if (existingJoinData != null) {
|
|
512
|
-
if (!obj.equal(existingJoinData
|
|
512
|
+
if (!obj.equal(existingJoinData, newJoinData)) {
|
|
513
513
|
throw new Error(`isSingle 관계 '${localKey}'에 여러 개의 다른 결과가 있습니다.`);
|
|
514
514
|
}
|
|
515
515
|
} else {
|
package/docs/core.md
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
# Core
|
|
2
|
-
|
|
3
|
-
## `DbContext`
|
|
4
|
-
|
|
5
|
-
DB 연결/트랜잭션/DDL/초기화를 제공하는 추상 클래스. `queryable()`/`executable()`로 테이블/프로시저를 등록한다. 각 프로퍼티가 독립 직렬화되어 40+ 테이블에서도 TS7056이 발생하지 않는다.
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
export abstract class DbContext implements DbContextBase {
|
|
9
|
-
status: DbContextStatus;
|
|
10
|
-
migrations: Migration[];
|
|
11
|
-
|
|
12
|
-
constructor(
|
|
13
|
-
executor: DbContextExecutor,
|
|
14
|
-
opt: { database: string; schema?: string },
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
// 등록 메서드
|
|
18
|
-
protected queryable<T extends TableBuilder | ViewBuilder>(builder: T): () => Queryable;
|
|
19
|
-
protected executable<T extends ProcedureBuilder>(builder: T): () => Executable;
|
|
20
|
-
|
|
21
|
-
// 연결 관리
|
|
22
|
-
connect<TResult>(fn: () => Promise<TResult>, isolationLevel?: IsolationLevel): Promise<TResult>;
|
|
23
|
-
connectWithoutTransaction<TResult>(callback: () => Promise<TResult>): Promise<TResult>;
|
|
24
|
-
transaction<TResult>(fn: () => Promise<TResult>, isolationLevel?: IsolationLevel): Promise<TResult>;
|
|
25
|
-
|
|
26
|
-
// DDL 실행 메서드
|
|
27
|
-
createTable(table: TableBuilder): Promise<void>;
|
|
28
|
-
dropTable(table: QueryDefObjectName): Promise<void>;
|
|
29
|
-
renameTable(table: QueryDefObjectName, newName: string): Promise<void>;
|
|
30
|
-
createView(view: ViewBuilder): Promise<void>;
|
|
31
|
-
dropView(view: QueryDefObjectName): Promise<void>;
|
|
32
|
-
createProc(procedure: ProcedureBuilder): Promise<void>;
|
|
33
|
-
dropProc(procedure: QueryDefObjectName): Promise<void>;
|
|
34
|
-
addColumn(table: QueryDefObjectName, columnName: string, column: ColumnBuilder): Promise<void>;
|
|
35
|
-
dropColumn(table: QueryDefObjectName, column: string): Promise<void>;
|
|
36
|
-
modifyColumn(table: QueryDefObjectName, columnName: string, column: ColumnBuilder): Promise<void>;
|
|
37
|
-
renameColumn(table: QueryDefObjectName, column: string, newName: string): Promise<void>;
|
|
38
|
-
addPrimaryKey(table: QueryDefObjectName, columns: string[]): Promise<void>;
|
|
39
|
-
dropPrimaryKey(table: QueryDefObjectName): Promise<void>;
|
|
40
|
-
addForeignKey(table: QueryDefObjectName, relationName: string, relationDef: ForeignKeyBuilder): Promise<void>;
|
|
41
|
-
addIndex(table: QueryDefObjectName, indexBuilder: IndexBuilder): Promise<void>;
|
|
42
|
-
dropForeignKey(table: QueryDefObjectName, relationName: string): Promise<void>;
|
|
43
|
-
dropIndex(table: QueryDefObjectName, columns: string[]): Promise<void>;
|
|
44
|
-
clearSchema(params: { database: string; schema?: string }): Promise<void>;
|
|
45
|
-
schemaExists(database: string, schema?: string): Promise<boolean>;
|
|
46
|
-
truncate(table: QueryDefObjectName): Promise<void>;
|
|
47
|
-
switchFk(table: QueryDefObjectName, enabled: boolean): Promise<void>;
|
|
48
|
-
|
|
49
|
-
// DDL QueryDef 생성기 (getCreateTableQueryDef, getDropTableQueryDef, ... 등 동일 시그니처)
|
|
50
|
-
getCreateTableQueryDef(table: TableBuilder): QueryDef;
|
|
51
|
-
getCreateViewQueryDef(view: ViewBuilder): QueryDef;
|
|
52
|
-
getCreateProcQueryDef(procedure: ProcedureBuilder): QueryDef;
|
|
53
|
-
getCreateObjectQueryDef(builder: TableBuilder | ViewBuilder | ProcedureBuilder): QueryDef;
|
|
54
|
-
getDropTableQueryDef(table: QueryDefObjectName): QueryDef;
|
|
55
|
-
getRenameTableQueryDef(table: QueryDefObjectName, newName: string): QueryDef;
|
|
56
|
-
getDropViewQueryDef(view: QueryDefObjectName): QueryDef;
|
|
57
|
-
getDropProcQueryDef(procedure: QueryDefObjectName): QueryDef;
|
|
58
|
-
getAddColumnQueryDef(table: QueryDefObjectName, columnName: string, column: ColumnBuilder): QueryDef;
|
|
59
|
-
getDropColumnQueryDef(table: QueryDefObjectName, column: string): QueryDef;
|
|
60
|
-
getModifyColumnQueryDef(table: QueryDefObjectName, columnName: string, column: ColumnBuilder): QueryDef;
|
|
61
|
-
getRenameColumnQueryDef(table: QueryDefObjectName, column: string, newName: string): QueryDef;
|
|
62
|
-
getAddPrimaryKeyQueryDef(table: QueryDefObjectName, columns: string[]): QueryDef;
|
|
63
|
-
getDropPrimaryKeyQueryDef(table: QueryDefObjectName): QueryDef;
|
|
64
|
-
getAddForeignKeyQueryDef(table: QueryDefObjectName, relationName: string, relationDef: ForeignKeyBuilder): QueryDef;
|
|
65
|
-
getAddIndexQueryDef(table: QueryDefObjectName, indexBuilder: IndexBuilder): QueryDef;
|
|
66
|
-
getDropForeignKeyQueryDef(table: QueryDefObjectName, relationName: string): QueryDef;
|
|
67
|
-
getDropIndexQueryDef(table: QueryDefObjectName, columns: string[]): QueryDef;
|
|
68
|
-
getClearSchemaQueryDef(params: { database: string; schema?: string }): QueryDef;
|
|
69
|
-
getSchemaExistsQueryDef(database: string, schema?: string): QueryDef;
|
|
70
|
-
getTruncateQueryDef(table: QueryDefObjectName): QueryDef;
|
|
71
|
-
getSwitchFkQueryDef(table: QueryDefObjectName, enabled: boolean): QueryDef;
|
|
72
|
-
|
|
73
|
-
// 초기화
|
|
74
|
-
initialize(options?: { dbs?: string[]; force?: boolean }): Promise<boolean>;
|
|
75
|
-
|
|
76
|
-
// DbContextBase 구현
|
|
77
|
-
get database(): string | undefined;
|
|
78
|
-
get schema(): string | undefined;
|
|
79
|
-
getNextAlias(): string;
|
|
80
|
-
resetAliasCounter(): void;
|
|
81
|
-
executeDefs<T>(defs: QueryDef[], resultMetas?: (ResultMeta | undefined)[]): Promise<T[][]>;
|
|
82
|
-
getQueryDefObjectName(tableOrView: TableBuilder | ViewBuilder): QueryDefObjectName;
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### 연결/트랜잭션 패턴
|
|
87
|
-
|
|
88
|
-
| 메서드 | 용도 |
|
|
89
|
-
|--------|------|
|
|
90
|
-
| `connect(fn)` | 연결 -> 트랜잭션 시작 -> fn -> 커밋 -> 종료. 일반 DML 작업에 사용 |
|
|
91
|
-
| `connectWithoutTransaction(fn)` | 연결 -> fn -> 종료. 트랜잭션 없는 DDL/읽기 전용 작업에 사용 |
|
|
92
|
-
| `transaction(fn)` | 이미 연결된 상태에서 부분 트랜잭션 시작. `connectWithoutTransaction` 내부에서 사용 |
|
|
93
|
-
|
|
94
|
-
- `connect()`/`connectWithoutTransaction()`은 status가 `"ready"`가 아니면 에러를 던진다 (재진입 방지).
|
|
95
|
-
- 트랜잭션 중 DDL 실행은 런타임 에러를 발생시킨다.
|
|
96
|
-
- 롤백 실패 시 원래 에러의 `cause`에 롤백 에러를 첨부하여 전파한다.
|
|
97
|
-
|
|
98
|
-
## `SD_BUILDER`
|
|
99
|
-
|
|
100
|
-
DbContext의 queryable/executable 프로퍼티에 부착된 builder 참조용 Symbol.
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
export const SD_BUILDER: unique symbol;
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## `DbContextBase`
|
|
107
|
-
|
|
108
|
-
Queryable, Executable, ViewBuilder에서 사용하는 DbContext 내부 인터페이스. DbContext class가 이 인터페이스를 구현한다.
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
export interface DbContextBase {
|
|
112
|
-
status: DbContextStatus;
|
|
113
|
-
readonly database: string | undefined;
|
|
114
|
-
readonly schema: string | undefined;
|
|
115
|
-
getNextAlias(): string;
|
|
116
|
-
resetAliasCounter(): void;
|
|
117
|
-
executeDefs<T = DataRecord>(defs: QueryDef[], resultMetas?: (ResultMeta | undefined)[]): Promise<T[][]>;
|
|
118
|
-
getQueryDefObjectName(tableOrView: TableBuilder | ViewBuilder): QueryDefObjectName;
|
|
119
|
-
switchFk(table: QueryDefObjectName, enabled: boolean): Promise<void>;
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
| Field | Type | Description |
|
|
124
|
-
|-------|------|-------------|
|
|
125
|
-
| `status` | `DbContextStatus` | 현재 연결 상태 |
|
|
126
|
-
| `database` | `string \| undefined` | 데이터베이스 이름 |
|
|
127
|
-
| `schema` | `string \| undefined` | 스키마 이름 |
|
|
128
|
-
|
|
129
|
-
## `DbContextStatus`
|
|
130
|
-
|
|
131
|
-
DbContext 상태를 나타내는 문자열 리터럴 유니온.
|
|
132
|
-
|
|
133
|
-
```typescript
|
|
134
|
-
export type DbContextStatus = "ready" | "connect" | "transact";
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
## `DbContextDdlMethods`
|
|
138
|
-
|
|
139
|
-
DDL 실행 메서드 인터페이스. DbContext에서 사용하는 모든 DDL 메서드 시그니처를 정의한다. DDL 실행 메서드와 DDL QueryDef 생성 메서드를 모두 포함한다.
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
export interface DbContextDdlMethods {
|
|
143
|
-
createTable(table: TableBuilder): Promise<void>;
|
|
144
|
-
dropTable(table: QueryDefObjectName): Promise<void>;
|
|
145
|
-
// ... (DbContext의 DDL 메서드와 동일한 시그니처)
|
|
146
|
-
}
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## `DbErrorCode`
|
|
150
|
-
|
|
151
|
-
트랜잭션 관련 에러 코드. DBMS별 네이티브 에러 코드를 추상화한다.
|
|
152
|
-
|
|
153
|
-
```typescript
|
|
154
|
-
export enum DbErrorCode {
|
|
155
|
-
NO_ACTIVE_TRANSACTION = "NO_ACTIVE_TRANSACTION",
|
|
156
|
-
TRANSACTION_ALREADY_STARTED = "TRANSACTION_ALREADY_STARTED",
|
|
157
|
-
DEADLOCK = "DEADLOCK",
|
|
158
|
-
LOCK_TIMEOUT = "LOCK_TIMEOUT",
|
|
159
|
-
}
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
| Value | Description |
|
|
163
|
-
|-------|-------------|
|
|
164
|
-
| `NO_ACTIVE_TRANSACTION` | 활성 트랜잭션 없음 (ROLLBACK 시) |
|
|
165
|
-
| `TRANSACTION_ALREADY_STARTED` | 트랜잭션 이미 시작됨 |
|
|
166
|
-
| `DEADLOCK` | 데드락 발생 |
|
|
167
|
-
| `LOCK_TIMEOUT` | 잠금 타임아웃 |
|
|
168
|
-
|
|
169
|
-
## `DbTransactionError`
|
|
170
|
-
|
|
171
|
-
DBMS별 네이티브 에러를 표준화된 에러 코드로 래핑하는 에러 클래스.
|
|
172
|
-
|
|
173
|
-
```typescript
|
|
174
|
-
export class DbTransactionError extends Error {
|
|
175
|
-
override readonly name = "DbTransactionError";
|
|
176
|
-
|
|
177
|
-
constructor(
|
|
178
|
-
public readonly code: DbErrorCode,
|
|
179
|
-
message: string,
|
|
180
|
-
public readonly originalError?: unknown,
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
| Field | Type | Description |
|
|
186
|
-
|-------|------|-------------|
|
|
187
|
-
| `code` | `DbErrorCode` | 표준화된 에러 코드 |
|
|
188
|
-
| `originalError` | `unknown \| undefined` | 원본 DBMS 에러 (디버깅용) |
|