@simplysm/orm-common 14.0.16 → 14.0.18
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 +25 -19
- package/dist/db-context.d.ts +133 -0
- package/dist/db-context.d.ts.map +1 -0
- package/dist/db-context.js +325 -0
- package/dist/db-context.js.map +1 -0
- package/dist/ddl/initialize.d.ts +5 -3
- package/dist/ddl/initialize.d.ts.map +1 -1
- package/dist/ddl/initialize.js +39 -32
- package/dist/ddl/initialize.js.map +1 -1
- package/dist/exec/executable.js +1 -1
- package/dist/exec/executable.js.map +1 -1
- package/dist/exec/queryable.js +2 -2
- package/dist/exec/queryable.js.map +1 -1
- package/dist/exec/search-parser.d.ts.map +1 -1
- package/dist/exec/search-parser.js +2 -1
- package/dist/exec/search-parser.js.map +1 -1
- package/dist/expr/expr.d.ts +2 -2
- package/dist/expr/expr.js +2 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.js +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.js +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.js +1 -1
- package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts +7 -0
- package/dist/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-query-builder.js +86 -16
- package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -1
- package/dist/schema/factory/relation-builder.d.ts +44 -67
- package/dist/schema/factory/relation-builder.d.ts.map +1 -1
- package/dist/schema/factory/relation-builder.js +37 -78
- package/dist/schema/factory/relation-builder.js.map +1 -1
- package/dist/schema/view-builder.d.ts +1 -1
- package/dist/schema/view-builder.d.ts.map +1 -1
- package/dist/schema/view-builder.js +0 -2
- package/dist/schema/view-builder.js.map +1 -1
- package/dist/types/column.js +1 -1
- package/dist/types/column.js.map +1 -1
- package/dist/types/db-context-def.d.ts +2 -42
- package/dist/types/db-context-def.d.ts.map +1 -1
- package/dist/utils/result-parser.js +31 -23
- package/dist/utils/result-parser.js.map +1 -1
- package/docs/core.md +110 -50
- package/docs/schema-builders.md +13 -19
- package/docs/types.md +2 -41
- package/package.json +3 -3
- package/src/db-context.ts +455 -0
- package/src/ddl/initialize.ts +49 -37
- package/src/exec/executable.ts +1 -1
- package/src/exec/queryable.ts +2 -2
- package/src/exec/search-parser.ts +2 -1
- package/src/expr/expr.ts +2 -2
- package/src/index.ts +2 -3
- package/src/query-builder/mssql/mssql-expr-renderer.ts +1 -1
- package/src/query-builder/mysql/mysql-expr-renderer.ts +1 -1
- package/src/query-builder/mysql/mysql-query-builder.ts +1 -1
- package/src/query-builder/postgresql/postgresql-query-builder.ts +93 -14
- package/src/schema/factory/relation-builder.ts +56 -87
- package/src/schema/view-builder.ts +1 -3
- package/src/types/column.ts +1 -1
- package/src/types/db-context-def.ts +2 -60
- package/src/utils/result-parser.ts +29 -22
- package/dist/create-db-context.d.ts +0 -34
- package/dist/create-db-context.d.ts.map +0 -1
- package/dist/create-db-context.js +0 -329
- package/dist/create-db-context.js.map +0 -1
- package/dist/define-db-context.d.ts +0 -15
- package/dist/define-db-context.d.ts.map +0 -1
- package/dist/define-db-context.js +0 -12
- package/dist/define-db-context.js.map +0 -1
- package/src/create-db-context.ts +0 -409
- package/src/define-db-context.ts +0 -28
|
@@ -376,7 +376,7 @@ export class MysqlExprRenderer extends ExprRendererBase {
|
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
protected isoWeek(expr: ExprIsoWeek): string {
|
|
379
|
-
return `WEEK(${this.render(expr.arg)},
|
|
379
|
+
return `WEEK(${this.render(expr.arg)}, 3)`;
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
protected isoWeekStartDate(expr: ExprIsoWeekStartDate): string {
|
|
@@ -731,7 +731,7 @@ export class MysqlQueryBuilder extends QueryBuilderBase {
|
|
|
731
731
|
sql: `
|
|
732
732
|
SET FOREIGN_KEY_CHECKS = 0;
|
|
733
733
|
SET @tables = NULL;
|
|
734
|
-
SELECT GROUP_CONCAT(table_name) INTO @tables FROM information_schema.tables WHERE table_schema = '${dbName}';
|
|
734
|
+
SELECT GROUP_CONCAT(CONCAT('\`', REPLACE(table_name, '\`', '\`\`'), '\`')) INTO @tables FROM information_schema.tables WHERE table_schema = '${dbName}';
|
|
735
735
|
SET @drop_stmt = IF(@tables IS NULL, 'SELECT 1', CONCAT('DROP TABLE IF EXISTS ', @tables));
|
|
736
736
|
PREPARE stmt FROM @drop_stmt;
|
|
737
737
|
EXECUTE stmt;
|
|
@@ -240,13 +240,20 @@ export class PostgresqlQueryBuilder extends QueryBuilderBase {
|
|
|
240
240
|
protected update(def: UpdateQueryDef): QueryBuildResult {
|
|
241
241
|
const table = this.tableName(def.table);
|
|
242
242
|
const alias = this.expr.wrap(def.as);
|
|
243
|
+
const hasLimit = def.top != null || def.limit != null;
|
|
243
244
|
|
|
244
245
|
// SET
|
|
245
246
|
const setParts = Object.entries(def.record).map(
|
|
246
247
|
([col, e]) => `${this.expr.wrap(col)} = ${this.expr.render(e)}`,
|
|
247
248
|
);
|
|
248
249
|
|
|
249
|
-
|
|
250
|
+
// CTE: top/limit → ctid 기반 서브쿼리
|
|
251
|
+
let ctePart = "";
|
|
252
|
+
if (hasLimit) {
|
|
253
|
+
ctePart = this._buildLimitCte(def, table, alias);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let sql = `${ctePart}UPDATE ${table} AS ${alias} SET ${setParts.join(", ")}`;
|
|
250
257
|
|
|
251
258
|
// PostgreSQL: JOIN은 FROM 절로 처리
|
|
252
259
|
if (def.joins != null && def.joins.length > 0) {
|
|
@@ -256,21 +263,29 @@ export class PostgresqlQueryBuilder extends QueryBuilderBase {
|
|
|
256
263
|
});
|
|
257
264
|
sql += ` FROM ${joinTables.join(", ")}`;
|
|
258
265
|
|
|
259
|
-
// JOIN ON 조건을 WHERE에 추가
|
|
260
266
|
const joinConditions = def.joins
|
|
261
267
|
.filter((j) => j.where != null && j.where.length > 0)
|
|
262
268
|
.map((j) => this.expr.renderWhere(j.where!));
|
|
263
|
-
|
|
269
|
+
|
|
270
|
+
if (hasLimit) {
|
|
271
|
+
// CTE가 WHERE 조건을 포함하므로 ctid 필터 + JOIN 조건만
|
|
272
|
+
const conditions = [`${alias}.ctid IN (SELECT ctid FROM _limited)`, ...joinConditions];
|
|
273
|
+
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
274
|
+
} else {
|
|
264
275
|
const whereCondition =
|
|
265
276
|
def.where != null && def.where.length > 0 ? this.expr.renderWhere(def.where) : null;
|
|
266
277
|
const allConditions =
|
|
267
278
|
whereCondition != null ? [whereCondition, ...joinConditions] : joinConditions;
|
|
268
|
-
|
|
279
|
+
if (allConditions.length > 0) {
|
|
280
|
+
sql += ` WHERE ${allConditions.join(" AND ")}`;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
if (hasLimit) {
|
|
285
|
+
sql += ` WHERE ${alias}.ctid IN (SELECT ctid FROM _limited)`;
|
|
269
286
|
} else {
|
|
270
287
|
sql += this.renderWhere(def.where);
|
|
271
288
|
}
|
|
272
|
-
} else {
|
|
273
|
-
sql += this.renderWhere(def.where);
|
|
274
289
|
}
|
|
275
290
|
|
|
276
291
|
// RETURNING 절
|
|
@@ -289,8 +304,15 @@ export class PostgresqlQueryBuilder extends QueryBuilderBase {
|
|
|
289
304
|
protected delete(def: DeleteQueryDef): QueryBuildResult {
|
|
290
305
|
const table = this.tableName(def.table);
|
|
291
306
|
const alias = this.expr.wrap(def.as);
|
|
307
|
+
const hasLimit = def.top != null || def.limit != null;
|
|
292
308
|
|
|
293
|
-
|
|
309
|
+
// CTE: top/limit → ctid 기반 서브쿼리
|
|
310
|
+
let ctePart = "";
|
|
311
|
+
if (hasLimit) {
|
|
312
|
+
ctePart = this._buildLimitCte(def, table, alias);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
let sql = `${ctePart}DELETE FROM ${table} AS ${alias}`;
|
|
294
316
|
|
|
295
317
|
// PostgreSQL: JOIN은 USING 절로 처리
|
|
296
318
|
if (def.joins != null && def.joins.length > 0) {
|
|
@@ -300,21 +322,28 @@ export class PostgresqlQueryBuilder extends QueryBuilderBase {
|
|
|
300
322
|
});
|
|
301
323
|
sql += ` USING ${joinTables.join(", ")}`;
|
|
302
324
|
|
|
303
|
-
// JOIN ON 조건을 WHERE에 추가
|
|
304
325
|
const joinConditions = def.joins
|
|
305
326
|
.filter((j) => j.where != null && j.where.length > 0)
|
|
306
327
|
.map((j) => this.expr.renderWhere(j.where!));
|
|
307
|
-
|
|
328
|
+
|
|
329
|
+
if (hasLimit) {
|
|
330
|
+
const conditions = [`${alias}.ctid IN (SELECT ctid FROM _limited)`, ...joinConditions];
|
|
331
|
+
sql += ` WHERE ${conditions.join(" AND ")}`;
|
|
332
|
+
} else {
|
|
308
333
|
const whereCondition =
|
|
309
334
|
def.where != null && def.where.length > 0 ? this.expr.renderWhere(def.where) : null;
|
|
310
335
|
const allConditions =
|
|
311
336
|
whereCondition != null ? [whereCondition, ...joinConditions] : joinConditions;
|
|
312
|
-
|
|
337
|
+
if (allConditions.length > 0) {
|
|
338
|
+
sql += ` WHERE ${allConditions.join(" AND ")}`;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
if (hasLimit) {
|
|
343
|
+
sql += ` WHERE ${alias}.ctid IN (SELECT ctid FROM _limited)`;
|
|
313
344
|
} else {
|
|
314
345
|
sql += this.renderWhere(def.where);
|
|
315
346
|
}
|
|
316
|
-
} else {
|
|
317
|
-
sql += this.renderWhere(def.where);
|
|
318
347
|
}
|
|
319
348
|
|
|
320
349
|
// RETURNING (PostgreSQL: DELETE에서도 지원)
|
|
@@ -611,12 +640,62 @@ export class PostgresqlQueryBuilder extends QueryBuilderBase {
|
|
|
611
640
|
protected execProc(def: ExecProcQueryDef): QueryBuildResult {
|
|
612
641
|
const proc = this.tableName(def.procedure);
|
|
613
642
|
if (def.params == null || Object.keys(def.params).length === 0) {
|
|
614
|
-
return { sql: `SELECT ${proc}()` };
|
|
643
|
+
return { sql: `SELECT * FROM ${proc}()` };
|
|
615
644
|
}
|
|
616
645
|
const params = Object.values(def.params)
|
|
617
646
|
.map((p) => this.expr.render(p))
|
|
618
647
|
.join(", ");
|
|
619
|
-
return { sql: `SELECT ${proc}(${params})` };
|
|
648
|
+
return { sql: `SELECT * FROM ${proc}(${params})` };
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* UPDATE/DELETE의 top/limit를 CTE로 변환
|
|
653
|
+
*
|
|
654
|
+
* PostgreSQL은 UPDATE/DELETE에 LIMIT을 지원하지 않으므로
|
|
655
|
+
* ctid 기반 CTE로 변환한다.
|
|
656
|
+
*/
|
|
657
|
+
private _buildLimitCte(
|
|
658
|
+
def: { table: QueryDefObjectName; as: string; top?: number; limit?: [number, number]; where?: unknown[]; joins?: SelectQueryDefJoin[] },
|
|
659
|
+
table: string,
|
|
660
|
+
alias: string,
|
|
661
|
+
): string {
|
|
662
|
+
let sql = `WITH _limited AS (SELECT ${alias}.ctid FROM ${table} AS ${alias}`;
|
|
663
|
+
|
|
664
|
+
if (def.joins != null && def.joins.length > 0) {
|
|
665
|
+
const joinTables = def.joins.map((j) => {
|
|
666
|
+
const from = this.renderFrom(j.from);
|
|
667
|
+
return `${from} AS ${this.expr.wrap(j.as)}`;
|
|
668
|
+
});
|
|
669
|
+
sql += `, ${joinTables.join(", ")}`;
|
|
670
|
+
|
|
671
|
+
const joinConditions = def.joins
|
|
672
|
+
.filter((j) => j.where != null && j.where.length > 0)
|
|
673
|
+
.map((j) => this.expr.renderWhere(j.where!));
|
|
674
|
+
const whereCondition =
|
|
675
|
+
def.where != null && def.where.length > 0
|
|
676
|
+
? this.expr.renderWhere(def.where as any)
|
|
677
|
+
: null;
|
|
678
|
+
const allConditions = [
|
|
679
|
+
...(whereCondition != null ? [whereCondition] : []),
|
|
680
|
+
...joinConditions,
|
|
681
|
+
];
|
|
682
|
+
if (allConditions.length > 0) {
|
|
683
|
+
sql += ` WHERE ${allConditions.join(" AND ")}`;
|
|
684
|
+
}
|
|
685
|
+
} else if (def.where != null && def.where.length > 0) {
|
|
686
|
+
sql += ` WHERE ${this.expr.renderWhere(def.where as any)}`;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (def.limit != null) {
|
|
690
|
+
const [offset, count] = def.limit;
|
|
691
|
+
sql += ` LIMIT ${count}`;
|
|
692
|
+
if (offset > 0) sql += ` OFFSET ${offset}`;
|
|
693
|
+
} else if (def.top != null) {
|
|
694
|
+
sql += ` LIMIT ${def.top}`;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
sql += `) `;
|
|
698
|
+
return sql;
|
|
620
699
|
}
|
|
621
700
|
|
|
622
701
|
//#endregion
|
|
@@ -12,6 +12,9 @@ import type { ViewBuilder } from "../view-builder";
|
|
|
12
12
|
* 현재 Table에서 대상 Table로의 FK 관계를 정의
|
|
13
13
|
* DB에 실제 FK 제약조건을 생성
|
|
14
14
|
*
|
|
15
|
+
* description 설정은 factory 함수의 opts 파라미터로 전달한다.
|
|
16
|
+
* 메서드 체이닝(.description())은 TypeScript 순환 참조 시 TS7022를 유발하므로 제거됨.
|
|
17
|
+
*
|
|
15
18
|
* @template TOwner - 소유 Table builder 타입
|
|
16
19
|
* @template TTargetFn - 대상 Table builder factory 타입
|
|
17
20
|
*
|
|
@@ -25,7 +28,7 @@ import type { ViewBuilder } from "../view-builder";
|
|
|
25
28
|
* .primaryKey("id")
|
|
26
29
|
* .relations((r) => ({
|
|
27
30
|
* // N:1 relationship - Post → User
|
|
28
|
-
* author: r.foreignKey(["authorId"], () => User),
|
|
31
|
+
* author: r.foreignKey(["authorId"], () => User, { description: "작성자" }),
|
|
29
32
|
* }));
|
|
30
33
|
* ```
|
|
31
34
|
*
|
|
@@ -51,23 +54,16 @@ export class ForeignKeyBuilder<
|
|
|
51
54
|
description?: string;
|
|
52
55
|
},
|
|
53
56
|
) {}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* 관계 설명 설정
|
|
57
|
-
*
|
|
58
|
-
* @param desc - 관계 설명
|
|
59
|
-
* @returns 새 ForeignKeyBuilder 인스턴스
|
|
60
|
-
*/
|
|
61
|
-
description(desc: string): ForeignKeyBuilder<TOwner, TTargetFn> {
|
|
62
|
-
return new ForeignKeyBuilder({ ...this.meta, description: desc });
|
|
63
|
-
}
|
|
64
57
|
}
|
|
65
58
|
|
|
66
59
|
/**
|
|
67
60
|
* Foreign Key 역참조 builder (1:N)
|
|
68
61
|
*
|
|
69
62
|
* 다른 Table이 현재 Table을 참조하는 FK의 역참조를 정의
|
|
70
|
-
* include() 시 배열로 로드됨 (single
|
|
63
|
+
* include() 시 배열로 로드됨 (opts.single: true 시 단일 객체)
|
|
64
|
+
*
|
|
65
|
+
* description, single 설정은 factory 함수의 opts 파라미터로 전달한다.
|
|
66
|
+
* 메서드 체이닝(.description(), .single())은 TypeScript 순환 참조 시 TS7022를 유발하므로 제거됨.
|
|
71
67
|
*
|
|
72
68
|
* @template TTargetTableFn - 참조하는 Table builder factory 타입
|
|
73
69
|
* @template TIsSingle - 단일 객체 여부
|
|
@@ -85,7 +81,10 @@ export class ForeignKeyBuilder<
|
|
|
85
81
|
* posts: r.foreignKeyTarget(() => Post, "author"),
|
|
86
82
|
*
|
|
87
83
|
* // 1:1 relation (single object)
|
|
88
|
-
* profile: r.foreignKeyTarget(() => Profile, "user"
|
|
84
|
+
* profile: r.foreignKeyTarget(() => Profile, "user", { single: true }),
|
|
85
|
+
*
|
|
86
|
+
* // with description
|
|
87
|
+
* comments: r.foreignKeyTarget(() => Comment, "user", { description: "댓글목록" }),
|
|
89
88
|
* }));
|
|
90
89
|
* ```
|
|
91
90
|
*
|
|
@@ -110,32 +109,6 @@ export class ForeignKeyTargetBuilder<
|
|
|
110
109
|
isSingle?: TIsSingle;
|
|
111
110
|
},
|
|
112
111
|
) {}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* 관계 설명 설정
|
|
116
|
-
*
|
|
117
|
-
* @param desc - 관계 설명
|
|
118
|
-
* @returns 새 ForeignKeyTargetBuilder 인스턴스
|
|
119
|
-
*/
|
|
120
|
-
description(desc: string): ForeignKeyTargetBuilder<TTargetTableFn, TIsSingle> {
|
|
121
|
-
return new ForeignKeyTargetBuilder({ ...this.meta, description: desc });
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* 단일 객체 관계로 설정 (1:1)
|
|
126
|
-
*
|
|
127
|
-
* 기본값은 배열(1:N), single() 호출 시 단일 객체
|
|
128
|
-
*
|
|
129
|
-
* @returns 새 ForeignKeyTargetBuilder 인스턴스 (isSingle=true)
|
|
130
|
-
*
|
|
131
|
-
* @example
|
|
132
|
-
* ```typescript
|
|
133
|
-
* profile: r.foreignKeyTarget(() => Profile, "user").single()
|
|
134
|
-
* ```
|
|
135
|
-
*/
|
|
136
|
-
single(): ForeignKeyTargetBuilder<TTargetTableFn, true> {
|
|
137
|
-
return new ForeignKeyTargetBuilder({ ...this.meta, isSingle: true });
|
|
138
|
-
}
|
|
139
112
|
}
|
|
140
113
|
|
|
141
114
|
// ============================================
|
|
@@ -148,6 +121,8 @@ export class ForeignKeyTargetBuilder<
|
|
|
148
121
|
* ForeignKeyBuilder와 동일하지만 DB에 FK 제약조건을 생성하지 않음
|
|
149
122
|
* View에서도 사용 가능
|
|
150
123
|
*
|
|
124
|
+
* description 설정은 factory 함수의 opts 파라미터로 전달한다.
|
|
125
|
+
*
|
|
151
126
|
* @template TOwner - 소유 Table/View builder 타입
|
|
152
127
|
* @template TTargetFn - 대상 Table/View builder factory 타입
|
|
153
128
|
*
|
|
@@ -158,14 +133,7 @@ export class ForeignKeyTargetBuilder<
|
|
|
158
133
|
* .query((db: MyDb) => db.user().select(...))
|
|
159
134
|
* .relations((r) => ({
|
|
160
135
|
* // View → Table (FK 미생성)
|
|
161
|
-
* company: r.relationKey(["companyId"], () => Company),
|
|
162
|
-
* }));
|
|
163
|
-
*
|
|
164
|
-
* // FK 없는 Table 관계 정의
|
|
165
|
-
* const Report = Table("Report")
|
|
166
|
-
* .columns((c) => ({ userId: c.bigint() }))
|
|
167
|
-
* .relations((r) => ({
|
|
168
|
-
* user: r.relationKey(["userId"], () => User),
|
|
136
|
+
* company: r.relationKey(["companyId"], () => Company, { description: "소속회사" }),
|
|
169
137
|
* }));
|
|
170
138
|
* ```
|
|
171
139
|
*
|
|
@@ -190,16 +158,6 @@ export class RelationKeyBuilder<
|
|
|
190
158
|
description?: string;
|
|
191
159
|
},
|
|
192
160
|
) {}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* 관계 설명 설정
|
|
196
|
-
*
|
|
197
|
-
* @param desc - 관계 설명
|
|
198
|
-
* @returns 새 RelationKeyBuilder 인스턴스
|
|
199
|
-
*/
|
|
200
|
-
description(desc: string): RelationKeyBuilder<TOwner, TTargetFn> {
|
|
201
|
-
return new RelationKeyBuilder({ ...this.meta, description: desc });
|
|
202
|
-
}
|
|
203
161
|
}
|
|
204
162
|
|
|
205
163
|
/**
|
|
@@ -208,6 +166,8 @@ export class RelationKeyBuilder<
|
|
|
208
166
|
* ForeignKeyTargetBuilder와 동일하지만 DB에 FK 제약조건을 생성하지 않음
|
|
209
167
|
* View에서도 사용 가능
|
|
210
168
|
*
|
|
169
|
+
* description, single 설정은 factory 함수의 opts 파라미터로 전달한다.
|
|
170
|
+
*
|
|
211
171
|
* @template TTargetTableFn - 참조하는 Table/View builder factory 타입
|
|
212
172
|
* @template TIsSingle - 단일 객체 여부
|
|
213
173
|
*
|
|
@@ -218,6 +178,8 @@ export class RelationKeyBuilder<
|
|
|
218
178
|
* .relations((r) => ({
|
|
219
179
|
* // 역참조 (FK 미생성)
|
|
220
180
|
* employees: r.relationKeyTarget(() => UserSummary, "company"),
|
|
181
|
+
* // 단일 객체 + 설명
|
|
182
|
+
* ceo: r.relationKeyTarget(() => UserSummary, "company", { single: true, description: "대표" }),
|
|
221
183
|
* }));
|
|
222
184
|
* ```
|
|
223
185
|
*
|
|
@@ -242,27 +204,6 @@ export class RelationKeyTargetBuilder<
|
|
|
242
204
|
isSingle?: TIsSingle;
|
|
243
205
|
},
|
|
244
206
|
) {}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* 관계 설명 설정
|
|
248
|
-
*
|
|
249
|
-
* @param desc - 관계 설명
|
|
250
|
-
* @returns 새 RelationKeyTargetBuilder 인스턴스
|
|
251
|
-
*/
|
|
252
|
-
description(desc: string): RelationKeyTargetBuilder<TTargetTableFn, TIsSingle> {
|
|
253
|
-
return new RelationKeyTargetBuilder({ ...this.meta, description: desc });
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* 단일 객체 관계로 설정 (1:1)
|
|
258
|
-
*
|
|
259
|
-
* 기본값은 배열(1:N), single() 호출 시 단일 객체
|
|
260
|
-
*
|
|
261
|
-
* @returns 새 RelationKeyTargetBuilder 인스턴스 (isSingle=true)
|
|
262
|
-
*/
|
|
263
|
-
single(): RelationKeyTargetBuilder<TTargetTableFn, true> {
|
|
264
|
-
return new RelationKeyTargetBuilder({ ...this.meta, isSingle: true });
|
|
265
|
-
}
|
|
266
207
|
}
|
|
267
208
|
|
|
268
209
|
/**
|
|
@@ -276,11 +217,18 @@ type RelationFkFactory<TOwner extends TableBuilder<any, any>, TColumnKey extends
|
|
|
276
217
|
foreignKey<TTargetFn extends () => TableBuilder<any, any>>(
|
|
277
218
|
columns: TColumnKey[],
|
|
278
219
|
targetFn: TTargetFn,
|
|
220
|
+
opts?: { description?: string },
|
|
279
221
|
): ForeignKeyBuilder<TOwner, TTargetFn>;
|
|
280
|
-
/** 1:N FK 역참조 정의 */
|
|
222
|
+
/** 1:N FK 역참조 정의 (single: true → 단일 객체) */
|
|
223
|
+
foreignKeyTarget<TTargetTableFn extends () => TableBuilder<any, any>>(
|
|
224
|
+
targetTableFn: TTargetTableFn,
|
|
225
|
+
relationName: string,
|
|
226
|
+
opts: { single: true; description?: string },
|
|
227
|
+
): ForeignKeyTargetBuilder<TTargetTableFn, true>;
|
|
281
228
|
foreignKeyTarget<TTargetTableFn extends () => TableBuilder<any, any>>(
|
|
282
229
|
targetTableFn: TTargetTableFn,
|
|
283
230
|
relationName: string,
|
|
231
|
+
opts?: { single?: false; description?: string },
|
|
284
232
|
): ForeignKeyTargetBuilder<TTargetTableFn, false>;
|
|
285
233
|
};
|
|
286
234
|
|
|
@@ -298,13 +246,22 @@ type RelationRkFactory<
|
|
|
298
246
|
relationKey<TTargetFn extends () => TableBuilder<any, any> | ViewBuilder<any, any, any>>(
|
|
299
247
|
columns: TColumnKey[],
|
|
300
248
|
targetFn: TTargetFn,
|
|
249
|
+
opts?: { description?: string },
|
|
301
250
|
): RelationKeyBuilder<TOwner, TTargetFn>;
|
|
302
|
-
/** 1:N 논리적 역참조 정의 */
|
|
251
|
+
/** 1:N 논리적 역참조 정의 (single: true → 단일 객체) */
|
|
303
252
|
relationKeyTarget<
|
|
304
253
|
TTargetTableFn extends () => TableBuilder<any, any> | ViewBuilder<any, any, any>,
|
|
305
254
|
>(
|
|
306
255
|
targetTableFn: TTargetTableFn,
|
|
307
256
|
relationName: string,
|
|
257
|
+
opts: { single: true; description?: string },
|
|
258
|
+
): RelationKeyTargetBuilder<TTargetTableFn, true>;
|
|
259
|
+
relationKeyTarget<
|
|
260
|
+
TTargetTableFn extends () => TableBuilder<any, any> | ViewBuilder<any, any, any>,
|
|
261
|
+
>(
|
|
262
|
+
targetTableFn: TTargetTableFn,
|
|
263
|
+
relationName: string,
|
|
264
|
+
opts?: { single?: false; description?: string },
|
|
308
265
|
): RelationKeyTargetBuilder<TTargetTableFn, false>;
|
|
309
266
|
};
|
|
310
267
|
|
|
@@ -348,25 +305,37 @@ export function createRelationFactory<
|
|
|
348
305
|
? RelationFkFactory<TOwner, TColumnKey> & RelationRkFactory<TOwner, TColumnKey>
|
|
349
306
|
: RelationRkFactory<TOwner, TColumnKey> {
|
|
350
307
|
return {
|
|
351
|
-
foreignKey(columns, targetFn) {
|
|
308
|
+
foreignKey(columns, targetFn, opts?) {
|
|
352
309
|
return new ForeignKeyBuilder({
|
|
353
310
|
ownerFn: ownerFn as () => TableBuilder<any, any>,
|
|
354
311
|
columns,
|
|
355
312
|
targetFn,
|
|
313
|
+
description: opts?.description,
|
|
356
314
|
});
|
|
357
315
|
},
|
|
358
|
-
foreignKeyTarget(targetTableFn, relationName) {
|
|
359
|
-
return new ForeignKeyTargetBuilder({
|
|
316
|
+
foreignKeyTarget(targetTableFn, relationName, opts?) {
|
|
317
|
+
return new ForeignKeyTargetBuilder({
|
|
318
|
+
targetTableFn,
|
|
319
|
+
relationName,
|
|
320
|
+
description: opts?.description,
|
|
321
|
+
isSingle: opts?.single,
|
|
322
|
+
});
|
|
360
323
|
},
|
|
361
|
-
relationKey(columns, targetFn) {
|
|
324
|
+
relationKey(columns, targetFn, opts?) {
|
|
362
325
|
return new RelationKeyBuilder({
|
|
363
326
|
ownerFn: ownerFn,
|
|
364
327
|
columns,
|
|
365
328
|
targetFn,
|
|
329
|
+
description: opts?.description,
|
|
366
330
|
});
|
|
367
331
|
},
|
|
368
|
-
relationKeyTarget(targetTableFn, relationName) {
|
|
369
|
-
return new RelationKeyTargetBuilder({
|
|
332
|
+
relationKeyTarget(targetTableFn, relationName, opts?) {
|
|
333
|
+
return new RelationKeyTargetBuilder({
|
|
334
|
+
targetTableFn,
|
|
335
|
+
relationName,
|
|
336
|
+
description: opts?.description,
|
|
337
|
+
isSingle: opts?.single,
|
|
338
|
+
});
|
|
370
339
|
},
|
|
371
340
|
} as TOwner extends TableBuilder<any, any>
|
|
372
341
|
? RelationFkFactory<TOwner, TColumnKey> & RelationRkFactory<TOwner, TColumnKey>
|
|
@@ -414,7 +383,7 @@ export type ExtractRelationTarget<TRelation> = TRelation extends
|
|
|
414
383
|
/**
|
|
415
384
|
* FKTarget/RelationKeyTarget에서 대상 타입 추출 (배열 또는 단일 객체)
|
|
416
385
|
*
|
|
417
|
-
* 1:N 관계의 대상 타입 (single
|
|
386
|
+
* 1:N 관계의 대상 타입 (opts.single: true 시 단일 객체)
|
|
418
387
|
* TTargetTableFn: 순환 참조 방지를 위한 지연 평가용 () => Post 형태
|
|
419
388
|
*
|
|
420
389
|
* @template T - FKTarget 또는 RelationKeyTarget builder 타입
|
|
@@ -168,9 +168,7 @@ export class ViewBuilder<
|
|
|
168
168
|
*/
|
|
169
169
|
relations<T extends RelationBuilderRecord>(
|
|
170
170
|
fn: (r: ReturnType<typeof createRelationFactory<this, keyof TData & string>>) => T,
|
|
171
|
-
): ViewBuilder<TDbContext, TData & InferDeepRelations<T>,
|
|
172
|
-
// TypeScript 제네릭 타입 추론 한계로 인해 캐스팅이 불가피
|
|
173
|
-
// TRelations 타입 파라미터와 새로 생성된 관계 타입 T 간의 타입 불일치 해결
|
|
171
|
+
): ViewBuilder<TDbContext, TData & InferDeepRelations<T>, T> {
|
|
174
172
|
return new ViewBuilder({
|
|
175
173
|
...this.meta,
|
|
176
174
|
relations: fn(createRelationFactory<this, keyof TData & string>(() => this)),
|
package/src/types/column.ts
CHANGED
|
@@ -157,7 +157,7 @@ export function inferColumnPrimitiveStr(value: ColumnPrimitive): ColumnPrimitive
|
|
|
157
157
|
if (value instanceof Time) return "Time";
|
|
158
158
|
if (value instanceof Uuid) return "Uuid";
|
|
159
159
|
if (value instanceof Uint8Array) return "Bytes";
|
|
160
|
-
throw new Error(
|
|
160
|
+
throw new Error("NULL 값으로는 타입을 추론할 수 없습니다.");
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
// ============================================
|
|
@@ -4,15 +4,14 @@ import type { ProcedureBuilder } from "../schema/procedure-builder";
|
|
|
4
4
|
import type { ColumnBuilder } from "../schema/factory/column-builder";
|
|
5
5
|
import type { ForeignKeyBuilder } from "../schema/factory/relation-builder";
|
|
6
6
|
import type { IndexBuilder } from "../schema/factory/index-builder";
|
|
7
|
-
import type { DataRecord,
|
|
7
|
+
import type { DataRecord, ResultMeta } from "./db";
|
|
8
8
|
import type { QueryDef, QueryDefObjectName } from "./query-def";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* DbContext 핵심 인터페이스
|
|
12
12
|
*
|
|
13
13
|
* Queryable, Executable, ViewBuilder에서 사용하는 내부 인터페이스.
|
|
14
|
-
*
|
|
15
|
-
* 이 인터페이스를 만족함.
|
|
14
|
+
* DbContext class가 이 인터페이스를 구현한다.
|
|
16
15
|
*/
|
|
17
16
|
export interface DbContextBase {
|
|
18
17
|
status: DbContextStatus;
|
|
@@ -32,63 +31,6 @@ export interface DbContextBase {
|
|
|
32
31
|
|
|
33
32
|
export type DbContextStatus = "ready" | "connect" | "transact";
|
|
34
33
|
|
|
35
|
-
/**
|
|
36
|
-
* DbContext 정의 (blueprint)
|
|
37
|
-
*
|
|
38
|
-
* defineDbContext()로 생성됨. Schema 메타데이터만 포함하며 런타임 상태는 없음.
|
|
39
|
-
*/
|
|
40
|
-
export interface DbContextDef<
|
|
41
|
-
TTables extends Record<string, TableBuilder<any, any>>,
|
|
42
|
-
TViews extends Record<string, ViewBuilder<any, any, any>>,
|
|
43
|
-
TProcedures extends Record<string, ProcedureBuilder<any, any>> = {},
|
|
44
|
-
> {
|
|
45
|
-
readonly meta: {
|
|
46
|
-
readonly tables: TTables;
|
|
47
|
-
readonly views: TViews;
|
|
48
|
-
readonly procedures: TProcedures;
|
|
49
|
-
readonly migrations: Migration[];
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* 전체 DbContext 인스턴스 타입 (createDbContext로 생성)
|
|
55
|
-
*
|
|
56
|
-
* DbContextBase를 queryable 접근자, DDL 메서드,
|
|
57
|
-
* 연결/트랜잭션 관리로 확장.
|
|
58
|
-
*/
|
|
59
|
-
export type DbContextInstance<TDef extends DbContextDef<any, any, any>> = DbContextBase &
|
|
60
|
-
DbContextConnectionMethods &
|
|
61
|
-
DbContextDdlMethods & {
|
|
62
|
-
// 자동 매핑된 table queryable 접근자
|
|
63
|
-
[K in keyof TDef["meta"]["tables"]]: () => import("../exec/queryable").Queryable<
|
|
64
|
-
TDef["meta"]["tables"][K]["$inferSelect"],
|
|
65
|
-
TDef["meta"]["tables"][K]
|
|
66
|
-
>;
|
|
67
|
-
} & {
|
|
68
|
-
// 자동 매핑된 view queryable 접근자
|
|
69
|
-
[K in keyof TDef["meta"]["views"]]: () => import("../exec/queryable").Queryable<
|
|
70
|
-
TDef["meta"]["views"][K]["$inferSelect"],
|
|
71
|
-
never
|
|
72
|
-
>;
|
|
73
|
-
} & {
|
|
74
|
-
// 자동 매핑된 procedure executable 접근자
|
|
75
|
-
[K in keyof TDef["meta"]["procedures"]]: () => import("../exec/executable").Executable<
|
|
76
|
-
TDef["meta"]["procedures"][K]["$params"],
|
|
77
|
-
TDef["meta"]["procedures"][K]["$returns"]
|
|
78
|
-
>;
|
|
79
|
-
} & {
|
|
80
|
-
// 시스템 table
|
|
81
|
-
_migration: () => import("../exec/queryable").Queryable<{ code: string }, any>;
|
|
82
|
-
// 초기화
|
|
83
|
-
initialize(options?: { dbs?: string[]; force?: boolean }): Promise<void>;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
export interface DbContextConnectionMethods {
|
|
87
|
-
connect<TResult>(fn: () => Promise<TResult>, isolationLevel?: IsolationLevel): Promise<TResult>;
|
|
88
|
-
connectWithoutTransaction<TResult>(callback: () => Promise<TResult>): Promise<TResult>;
|
|
89
|
-
transaction<TResult>(fn: () => Promise<TResult>, isolationLevel?: IsolationLevel): Promise<TResult>;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
34
|
export interface DbContextDdlMethods {
|
|
93
35
|
createTable(table: TableBuilder<any, any>): Promise<void>;
|
|
94
36
|
dropTable(table: QueryDefObjectName): Promise<void>;
|