sonamu 0.7.4 → 0.7.5
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/dist/api/config.d.ts +1 -4
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +1 -1
- package/dist/api/sonamu.d.ts +2 -0
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +19 -47
- package/dist/bin/cli.js +6 -6
- package/dist/database/base-model.d.ts +1 -1
- package/dist/database/base-model.d.ts.map +1 -1
- package/dist/database/base-model.js +15 -4
- package/dist/database/code-generator.d.ts.map +1 -1
- package/dist/database/code-generator.js +3 -3
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +1 -1
- package/dist/database/puri-wrapper.d.ts +11 -11
- package/dist/database/puri-wrapper.d.ts.map +1 -1
- package/dist/database/puri-wrapper.js +7 -11
- package/dist/database/puri.d.ts +36 -17
- package/dist/database/puri.d.ts.map +1 -1
- package/dist/database/puri.js +54 -7
- package/dist/database/puri.types.d.ts +54 -17
- package/dist/database/puri.types.d.ts.map +1 -1
- package/dist/database/puri.types.js +2 -4
- package/dist/database/puri.types.test-d.js +129 -0
- package/dist/database/upsert-builder.d.ts +16 -10
- package/dist/database/upsert-builder.d.ts.map +1 -1
- package/dist/database/upsert-builder.js +10 -19
- package/dist/entity/entity-manager.d.ts +113 -22
- package/dist/entity/entity-manager.d.ts.map +1 -1
- package/dist/entity/entity-manager.js +1 -1
- package/dist/entity/entity.d.ts +34 -0
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +110 -37
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -2
- package/dist/migration/code-generation.d.ts.map +1 -1
- package/dist/migration/code-generation.js +341 -149
- package/dist/migration/migration-set.d.ts.map +1 -1
- package/dist/migration/migration-set.js +21 -5
- package/dist/migration/migrator.d.ts.map +1 -1
- package/dist/migration/migrator.js +7 -1
- package/dist/migration/postgresql-schema-reader.d.ts +11 -1
- package/dist/migration/postgresql-schema-reader.d.ts.map +1 -1
- package/dist/migration/postgresql-schema-reader.js +111 -10
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +4 -3
- package/dist/template/implementations/generated.template.d.ts.map +1 -1
- package/dist/template/implementations/generated.template.js +12 -2
- package/dist/template/implementations/generated_sso.template.d.ts +3 -3
- package/dist/template/implementations/generated_sso.template.d.ts.map +1 -1
- package/dist/template/implementations/generated_sso.template.js +50 -2
- package/dist/template/implementations/model.template.js +6 -6
- package/dist/template/implementations/model_test.template.js +4 -4
- package/dist/template/implementations/view_enums_dropdown.template.js +2 -2
- package/dist/template/implementations/view_enums_select.template.js +2 -2
- package/dist/template/implementations/view_form.template.d.ts.map +1 -1
- package/dist/template/implementations/view_form.template.js +12 -9
- package/dist/template/implementations/view_id_async_select.template.js +4 -4
- package/dist/template/implementations/view_list.template.d.ts.map +1 -1
- package/dist/template/implementations/view_list.template.js +12 -9
- package/dist/template/implementations/view_search_input.template.js +2 -2
- package/dist/template/template.js +2 -2
- package/dist/template/zod-converter.d.ts.map +1 -1
- package/dist/template/zod-converter.js +17 -2
- package/dist/testing/fixture-manager.d.ts +2 -1
- package/dist/testing/fixture-manager.d.ts.map +1 -1
- package/dist/testing/fixture-manager.js +29 -29
- package/dist/types/types.d.ts +593 -68
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +113 -9
- package/dist/vector/chunking.d.ts +25 -0
- package/dist/vector/chunking.d.ts.map +1 -0
- package/dist/vector/chunking.js +97 -0
- package/dist/vector/config.d.ts +12 -0
- package/dist/vector/config.d.ts.map +1 -0
- package/dist/vector/config.js +83 -0
- package/dist/vector/embedding.d.ts +42 -0
- package/dist/vector/embedding.d.ts.map +1 -0
- package/dist/vector/embedding.js +147 -0
- package/dist/vector/types.d.ts +105 -0
- package/dist/vector/types.d.ts.map +1 -0
- package/dist/vector/types.js +5 -0
- package/dist/vector/vector-search.d.ts +47 -0
- package/dist/vector/vector-search.d.ts.map +1 -0
- package/dist/vector/vector-search.js +176 -0
- package/package.json +9 -8
- package/src/api/config.ts +0 -4
- package/src/api/sonamu.ts +21 -36
- package/src/bin/cli.ts +5 -5
- package/src/database/base-model.ts +20 -11
- package/src/database/code-generator.ts +6 -2
- package/src/database/db.ts +1 -0
- package/src/database/puri-wrapper.ts +22 -16
- package/src/database/puri.ts +150 -27
- package/src/database/puri.types.test-d.ts +457 -0
- package/src/database/puri.types.ts +231 -33
- package/src/database/upsert-builder.ts +43 -34
- package/src/entity/entity-manager.ts +2 -2
- package/src/entity/entity.ts +134 -44
- package/src/index.ts +6 -0
- package/src/migration/code-generation.ts +377 -174
- package/src/migration/migration-set.ts +22 -3
- package/src/migration/migrator.ts +6 -0
- package/src/migration/postgresql-schema-reader.ts +121 -21
- package/src/syncer/syncer.ts +3 -2
- package/src/template/implementations/generated.template.ts +51 -9
- package/src/template/implementations/generated_sso.template.ts +71 -2
- package/src/template/implementations/model.template.ts +5 -5
- package/src/template/implementations/model_test.template.ts +3 -3
- package/src/template/implementations/view_enums_dropdown.template.ts +1 -1
- package/src/template/implementations/view_enums_select.template.ts +1 -1
- package/src/template/implementations/view_form.template.ts +11 -8
- package/src/template/implementations/view_id_async_select.template.ts +3 -3
- package/src/template/implementations/view_list.template.ts +11 -8
- package/src/template/implementations/view_search_input.template.ts +1 -1
- package/src/template/template.ts +1 -1
- package/src/template/zod-converter.ts +20 -0
- package/src/testing/fixture-manager.ts +31 -30
- package/src/types/types.ts +226 -48
- package/src/vector/chunking.ts +115 -0
- package/src/vector/config.ts +68 -0
- package/src/vector/embedding.ts +193 -0
- package/src/vector/types.ts +122 -0
- package/src/vector/vector-search.ts +261 -0
- package/dist/template/implementations/view_enums_buttonset.template.d.ts +0 -17
- package/dist/template/implementations/view_enums_buttonset.template.d.ts.map +0 -1
- package/dist/template/implementations/view_enums_buttonset.template.js +0 -31
- package/dist/template/implementations/view_list_columns.template.d.ts +0 -17
- package/dist/template/implementations/view_list_columns.template.d.ts.map +0 -1
- package/dist/template/implementations/view_list_columns.template.js +0 -49
- package/src/template/implementations/view_enums_buttonset.template.ts +0 -34
- package/src/template/implementations/view_list_columns.template.ts +0 -53
|
@@ -1,27 +1,50 @@
|
|
|
1
1
|
/** biome-ignore-all lint/suspicious/noExplicitAny: Puri.types.ts는 다양한 타입을 사용하고 있습니다. */
|
|
2
2
|
|
|
3
3
|
import type { QueryResult } from "pg";
|
|
4
|
-
import type { DatabaseSchemaExtend } from "../types/types";
|
|
4
|
+
import type { DatabaseForeignKeys, DatabaseSchemaExtend } from "../types/types";
|
|
5
5
|
import type { Puri } from "./puri";
|
|
6
6
|
import type { PuriWrapper } from "./puri-wrapper";
|
|
7
7
|
|
|
8
|
-
//
|
|
9
|
-
|
|
8
|
+
// ============================================
|
|
9
|
+
// 내부 타입 키 (메타데이터)
|
|
10
|
+
// ============================================
|
|
11
|
+
type FulltextKey = "__fulltext__";
|
|
12
|
+
type VirtualKey = "__virtual__";
|
|
13
|
+
type LeftJoinedKey = "__leftJoined__";
|
|
14
|
+
type HasDefault = "__hasDefault__";
|
|
15
|
+
type GeneratedKey = "__generated__";
|
|
16
|
+
|
|
17
|
+
type InternalTypeKeys = FulltextKey | VirtualKey | LeftJoinedKey | HasDefault | GeneratedKey;
|
|
18
|
+
|
|
19
|
+
// ============================================
|
|
20
|
+
// 타입 유틸리티
|
|
21
|
+
// ============================================
|
|
22
|
+
|
|
23
|
+
// 테이블명 타입
|
|
24
|
+
export type TableName<TSchema> = keyof TSchema & string;
|
|
10
25
|
|
|
11
26
|
// virtual 컬럼 타입 추출
|
|
12
|
-
type VirtualKeys<T> = T extends {
|
|
27
|
+
type VirtualKeys<T> = T extends { [K in VirtualKey]: readonly (infer V)[] } ? V & string : never;
|
|
13
28
|
|
|
14
29
|
// virtual 컬럼 제거
|
|
15
30
|
type StripVirtual<T> = Omit<T, VirtualKeys<T>>;
|
|
16
31
|
|
|
32
|
+
// LEFT JOIN 마커 - nullable FK로 조인된 테이블
|
|
33
|
+
// 이 마커는 nullable FK + leftJoin 조합에서만 붙습니다.
|
|
34
|
+
// join + FK nullable -> 안 붙음
|
|
35
|
+
// join + FK non-nullable -> 안 붙음
|
|
36
|
+
// leftJoin + FK non-nullable -> 안 붙음
|
|
37
|
+
// leftJoin + FK nullable -> 붙음!
|
|
38
|
+
export type LeftJoinedMarker = { [K in LeftJoinedKey]: true };
|
|
39
|
+
|
|
17
40
|
// 메타데이터 필드 제외한 실제 엔티티 컬럼
|
|
18
|
-
export type ColumnKeys<T> = Exclude<keyof StripVirtual<T>,
|
|
41
|
+
export type ColumnKeys<T> = Exclude<keyof StripVirtual<T>, InternalTypeKeys> & string;
|
|
19
42
|
|
|
20
|
-
// virtual 컬럼 제거 후 __fulltext__
|
|
21
|
-
export type PuriTable<T> = Omit<StripVirtual<T>,
|
|
43
|
+
// virtual 컬럼 제거 후 __fulltext__ 유지
|
|
44
|
+
export type PuriTable<T> = Omit<StripVirtual<T>, VirtualKey>;
|
|
22
45
|
|
|
23
|
-
//
|
|
24
|
-
export type
|
|
46
|
+
// 내부 타입 키 제외 (실제 컬럼만 남김)
|
|
47
|
+
export type OmitInternalTypeKeys<T> = Omit<T, InternalTypeKeys>;
|
|
25
48
|
|
|
26
49
|
// TTables의 모든 테이블에서 사용 가능한 컬럼 경로
|
|
27
50
|
export type AvailableColumns<TTables extends Record<string, any>> =
|
|
@@ -32,27 +55,120 @@ export type AvailableColumns<TTables extends Record<string, any>> =
|
|
|
32
55
|
? ColumnKeys<TTables[keyof TTables]> // 단일 테이블이면 컬럼명만도 허용
|
|
33
56
|
: never);
|
|
34
57
|
|
|
58
|
+
// 숫자 타입 컬럼만 추출하는 유틸리티 타입
|
|
59
|
+
type NumericColumnKeys<T> = {
|
|
60
|
+
[K in keyof T]: T[K] extends number | bigint | null | undefined ? K : never;
|
|
61
|
+
}[keyof T] &
|
|
62
|
+
string;
|
|
63
|
+
|
|
64
|
+
// TTables의 모든 테이블에서 숫자 타입 컬럼만 추출
|
|
65
|
+
export type NumericColumns<TTables extends Record<string, any>> =
|
|
66
|
+
| {
|
|
67
|
+
[TAlias in keyof TTables]: `${TAlias & string}.${NumericColumnKeys<TTables[TAlias]>}`;
|
|
68
|
+
}[keyof TTables]
|
|
69
|
+
| (IsSingleKey<TTables> extends true
|
|
70
|
+
? NumericColumnKeys<TTables[keyof TTables]> // 단일 테이블이면 컬럼명만도 허용
|
|
71
|
+
: never);
|
|
72
|
+
|
|
35
73
|
// Group By, Order By, Having 등에서 선택 가능한 컬럼
|
|
36
74
|
export type ResultAvailableColumns<TTables extends Record<string, any>, TResult = any> =
|
|
37
75
|
| AvailableColumns<TTables>
|
|
38
76
|
| `${keyof TResult & string}`;
|
|
39
77
|
|
|
40
|
-
// Select 값 타입 확장
|
|
78
|
+
// Select 값 타입 확장 (단일 컬럼 또는 SQL 표현식)
|
|
41
79
|
export type SelectValue<TTables extends Record<string, any>> =
|
|
42
80
|
| AvailableColumns<TTables>
|
|
43
81
|
| SqlExpression<"string" | "number" | "boolean" | "date">;
|
|
44
82
|
|
|
45
|
-
// Select 객체 타입 (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
SelectValue<TTables>
|
|
49
|
-
|
|
83
|
+
// 중첩 Select 객체 타입 (재귀적)
|
|
84
|
+
// 예: { parent: { id: "parent.id", name: "parent.name" } }
|
|
85
|
+
export type NestedSelectObject<TTables extends Record<string, any>> = {
|
|
86
|
+
[key: string]: SelectValue<TTables> | NestedSelectObject<TTables>;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Select 객체 타입 (flat 또는 중첩 허용)
|
|
90
|
+
export type SelectObject<TTables extends Record<string, any>> = NestedSelectObject<TTables>;
|
|
91
|
+
|
|
92
|
+
// 값이 중첩 객체인지 판별하는 헬퍼 타입
|
|
93
|
+
type IsNestedObject<T> = T extends string
|
|
94
|
+
? false
|
|
95
|
+
: T extends SqlExpression<any>
|
|
96
|
+
? false
|
|
97
|
+
: T extends Record<string, any>
|
|
98
|
+
? true
|
|
99
|
+
: false;
|
|
100
|
+
|
|
101
|
+
// 컬럼이 nullable인지 확인 (스키마에서 직접 추출)
|
|
102
|
+
// 예: IsNullableColumn<TTables, "employees.department_id"> → department_id가 number | null이면 true
|
|
103
|
+
export type IsNullableColumn<
|
|
104
|
+
TTables,
|
|
105
|
+
Path extends string,
|
|
106
|
+
> = Path extends `${infer TAlias}.${infer TColumn}`
|
|
107
|
+
? TAlias extends keyof TTables
|
|
108
|
+
? TColumn extends keyof TTables[TAlias]
|
|
109
|
+
? null extends TTables[TAlias][TColumn]
|
|
110
|
+
? true
|
|
111
|
+
: false
|
|
112
|
+
: false
|
|
113
|
+
: false
|
|
114
|
+
: false;
|
|
115
|
+
|
|
116
|
+
// FK nullable 여부에 따른 마커 타입 결정
|
|
117
|
+
// nullable FK로 leftJoin → LeftJoinedMarker (객체 자체가 null일 수 있음)
|
|
118
|
+
// non-null FK로 leftJoin → 마커 없음 (부모가 있으면 자식도 반드시 있음)
|
|
119
|
+
export type LeftJoinMarkerFor<TTables, Path extends string> = IsNullableColumn<
|
|
120
|
+
TTables,
|
|
121
|
+
Path
|
|
122
|
+
> extends true
|
|
123
|
+
? LeftJoinedMarker
|
|
124
|
+
: {};
|
|
125
|
+
|
|
126
|
+
// 주어진 테이블이 FK nullable로 leftJoin 된 테이블인지 확인합니다.
|
|
127
|
+
// 사실 LeftJoinMarker가 붙었는지 확인하는게 다입니다.
|
|
128
|
+
// 이 마커는 FK nullable + leftJoin 조합에서만 붙습니다.
|
|
129
|
+
type IsNullableJoinedTable<TTables, TableKey> = TableKey extends keyof TTables
|
|
130
|
+
? TTables[TableKey] extends LeftJoinedMarker
|
|
131
|
+
? true // LeftJoinedMarker가 있으면 nullable
|
|
132
|
+
: false
|
|
133
|
+
: false;
|
|
50
134
|
|
|
51
|
-
//
|
|
135
|
+
// 경로 조합 헬퍼 (prefix가 없으면 key만, 있으면 prefix__key)
|
|
136
|
+
type JoinPath<Prefix extends string, Key extends string> = Prefix extends ""
|
|
137
|
+
? Key
|
|
138
|
+
: `${Prefix}__${Key}`;
|
|
139
|
+
|
|
140
|
+
// Select 결과 타입을 추론해주는 친구입니다.
|
|
141
|
+
// 이 타입은 Puri의 select, appendSelect에서 TResult로 사용됩니다.
|
|
142
|
+
//
|
|
143
|
+
// Schema를 읽어서 FK의 nullability에 따라 join된 객체의 타입을 추론해주는 기능이 있습니다.
|
|
144
|
+
// 이게 무슨 소리냐? FK가 nullable인데 leftJoin되었다면, 해당 객체는 nullable 해야 함을 타입 추론으로 반영해준다는 것입니다.
|
|
145
|
+
// 반면 FK가 non-nullable이거나 그냥 join으로 이어졌다면 해당 객체는 non-nullable할 겁니다.
|
|
146
|
+
// 물론 객체 내부의 nullability는 또 별개로 추론됩니다.
|
|
147
|
+
//
|
|
148
|
+
// 아래에도 ParseSelectObjectWithPath를 비롯해 ExtractColumnType, ExtractColumnTypeRaw 등의 타입이 있습니다.
|
|
149
|
+
// 이들의 역할은 다음과 같습니다:
|
|
150
|
+
// - Parse*: 객체 레벨에서 중첩 구조를 순회하며 객체에 | null을 붙일지 결정합니다.
|
|
151
|
+
// - Extract*: 필드 레벨에서 "table.column" 경로로부터 실제 타입을 추출합니다.
|
|
152
|
+
//
|
|
153
|
+
// 예시:
|
|
154
|
+
// .select({
|
|
155
|
+
// id: "users.id", // ← ExtractColumnType의 결과는 number입니다.
|
|
156
|
+
// department: { // ← ParseSelectObjectInner에 의해 nullable 객체로 추론됩니다.
|
|
157
|
+
// id: "department.id", // ← ExtractColumnTypeRaw의 결과는 number입니다.
|
|
158
|
+
// name: "department.name" // ← ExtractColumnTypeRaw의 결과는 string입니다.
|
|
159
|
+
// }
|
|
160
|
+
// })
|
|
52
161
|
export type ParseSelectObject<
|
|
53
162
|
TTables extends Record<string, any>,
|
|
54
163
|
TSelect extends SelectObject<TTables>,
|
|
55
|
-
> =
|
|
164
|
+
> = ParseSelectObjectWithPath<TTables, TSelect, "">;
|
|
165
|
+
|
|
166
|
+
// 경로를 추적하면서 Select 결과 타입을 추론합니다.
|
|
167
|
+
type ParseSelectObjectWithPath<
|
|
168
|
+
TTables extends Record<string, any>,
|
|
169
|
+
TSelect extends SelectObject<TTables>,
|
|
170
|
+
Prefix extends string,
|
|
171
|
+
> = Expand<{
|
|
56
172
|
[K in keyof TSelect]: TSelect[K] extends SqlExpression<infer R>
|
|
57
173
|
? R extends "string"
|
|
58
174
|
? string
|
|
@@ -63,24 +179,78 @@ export type ParseSelectObject<
|
|
|
63
179
|
: R extends "date"
|
|
64
180
|
? Date
|
|
65
181
|
: never
|
|
66
|
-
:
|
|
67
|
-
|
|
182
|
+
: IsNestedObject<TSelect[K]> extends true
|
|
183
|
+
? TSelect[K] extends NestedSelectObject<TTables>
|
|
184
|
+
? IsNullableJoinedTable<TTables, JoinPath<Prefix, K & string>> extends true // 주어진 테이블이 FK nullable에 leftJoin되었는지 여부에 따라 select 결과 객체의 타입이 달라집니다.
|
|
185
|
+
? Expand<ParseSelectObjectInner<TTables, TSelect[K], JoinPath<Prefix, K & string>>> | null // 만약 해당한다면 해당 객체 자체는 nullable 하며,
|
|
186
|
+
: Expand<ParseSelectObjectInner<TTables, TSelect[K], JoinPath<Prefix, K & string>>> // 그렇지 않다면 non-nullable 합니다.
|
|
187
|
+
: never
|
|
188
|
+
: ExtractColumnType<TTables, TSelect[K] & string>;
|
|
189
|
+
}>;
|
|
190
|
+
|
|
191
|
+
// 중첩 객체 내부용 - leftJoin nullable을 객체 레벨에서 이미 처리했으므로 필드는 원본 타입을 사용합니다.
|
|
192
|
+
// ParseSelectObjectWithPath와 거의 동일하나, 마지막에 ExtractColumnType 대신 ExtractColumnTypeRaw를 사용하여
|
|
193
|
+
// 필드 레벨에서 중복으로 | null이 추가되는 것을 방지합니다.
|
|
194
|
+
type ParseSelectObjectInner<
|
|
195
|
+
TTables extends Record<string, any>,
|
|
196
|
+
TSelect extends SelectObject<TTables>,
|
|
197
|
+
Prefix extends string,
|
|
198
|
+
> = Expand<{
|
|
199
|
+
[K in keyof TSelect]: TSelect[K] extends SqlExpression<infer R>
|
|
200
|
+
? R extends "string"
|
|
201
|
+
? string
|
|
202
|
+
: R extends "number"
|
|
203
|
+
? number
|
|
204
|
+
: R extends "boolean"
|
|
205
|
+
? boolean
|
|
206
|
+
: R extends "date"
|
|
207
|
+
? Date
|
|
208
|
+
: never
|
|
209
|
+
: IsNestedObject<TSelect[K]> extends true
|
|
210
|
+
? TSelect[K] extends NestedSelectObject<TTables>
|
|
211
|
+
? IsNullableJoinedTable<TTables, JoinPath<Prefix, K & string>> extends true
|
|
212
|
+
? Expand<ParseSelectObjectInner<TTables, TSelect[K], JoinPath<Prefix, K & string>>> | null
|
|
213
|
+
: Expand<ParseSelectObjectInner<TTables, TSelect[K], JoinPath<Prefix, K & string>>>
|
|
214
|
+
: never
|
|
215
|
+
: ExtractColumnTypeRaw<TTables, TSelect[K] & string>; // leftJoin nullable 무시
|
|
216
|
+
}>;
|
|
68
217
|
|
|
69
|
-
// 컬럼 경로에서
|
|
218
|
+
// 컬럼 경로에서 타입을 추출합니다. LeftJoinedMarker가 있으면 | null을 추가합니다.
|
|
219
|
+
// 최상위 select 필드에서 사용됩니다.
|
|
70
220
|
export type ExtractColumnType<
|
|
71
221
|
TTables extends Record<string, any>,
|
|
72
222
|
Path extends string,
|
|
73
223
|
> = Path extends `${infer TAlias}.${infer TColumn}`
|
|
74
224
|
? TAlias extends keyof TTables
|
|
75
225
|
? TColumn extends keyof TTables[TAlias]
|
|
76
|
-
? TTables[TAlias]
|
|
226
|
+
? TTables[TAlias] extends LeftJoinedMarker
|
|
227
|
+
? TTables[TAlias][TColumn] | null // LEFT JOIN (nullable FK) → nullable
|
|
228
|
+
: TTables[TAlias][TColumn] // INNER JOIN 또는 non-null FK leftJoin → non-nullable
|
|
229
|
+
: never
|
|
230
|
+
: never
|
|
231
|
+
: IsSingleKey<TTables> extends true
|
|
232
|
+
? Path extends keyof TTables[keyof TTables]
|
|
233
|
+
? TTables[keyof TTables][Path]
|
|
234
|
+
: never
|
|
235
|
+
: never;
|
|
236
|
+
|
|
237
|
+
// 컬럼 경로에서 타입을 추출합니다. leftJoin 여부와 관계없이 원본 타입을 반환합니다.
|
|
238
|
+
// 중첩 객체 내부 필드에서 사용됩니다. (객체 레벨에서 이미 | null 처리가 완료되었으므로)
|
|
239
|
+
type ExtractColumnTypeRaw<
|
|
240
|
+
TTables extends Record<string, any>,
|
|
241
|
+
Path extends string,
|
|
242
|
+
> = Path extends `${infer TAlias}.${infer TColumn}`
|
|
243
|
+
? TAlias extends keyof TTables
|
|
244
|
+
? TColumn extends keyof TTables[TAlias]
|
|
245
|
+
? TTables[TAlias][TColumn] // leftJoin 여부와 관계없이 원본 타입
|
|
77
246
|
: never
|
|
78
247
|
: never
|
|
79
|
-
: IsSingleKey<TTables> extends true
|
|
248
|
+
: IsSingleKey<TTables> extends true
|
|
80
249
|
? Path extends keyof TTables[keyof TTables]
|
|
81
250
|
? TTables[keyof TTables][Path]
|
|
82
251
|
: never
|
|
83
252
|
: never;
|
|
253
|
+
|
|
84
254
|
// Where 조건 객체 타입
|
|
85
255
|
// 예: { "u.id": 1, "u.status": "active" }
|
|
86
256
|
export type WhereCondition<TTables extends Record<string, any>> = {
|
|
@@ -90,7 +260,7 @@ export type WhereCondition<TTables extends Record<string, any>> = {
|
|
|
90
260
|
// Fulltext index 컬럼 추출 타입
|
|
91
261
|
export type FulltextColumns<TTables extends Record<string, any>> = {
|
|
92
262
|
[TAlias in keyof TTables]: TTables[TAlias] extends {
|
|
93
|
-
|
|
263
|
+
[K in FulltextKey]: readonly (infer Col)[];
|
|
94
264
|
}
|
|
95
265
|
? Col extends string
|
|
96
266
|
? `${TAlias & string}.${Col}`
|
|
@@ -100,6 +270,8 @@ export type FulltextColumns<TTables extends Record<string, any>> = {
|
|
|
100
270
|
|
|
101
271
|
// 비교 연산자
|
|
102
272
|
export type ComparisonOperator = "=" | ">" | ">=" | "<" | "<=" | "<>" | "!=";
|
|
273
|
+
// 조건 연산자: 비교 연산자 + 패턴 매칭 연산자
|
|
274
|
+
export type WhereOperator = ComparisonOperator | "like" | "not like";
|
|
103
275
|
|
|
104
276
|
// SQL Expression 타입 정의
|
|
105
277
|
export type SqlExpression<T extends "string" | "number" | "boolean" | "date"> = {
|
|
@@ -126,17 +298,23 @@ type IsSingleKey<TTables extends Record<string, any>> = keyof TTables extends in
|
|
|
126
298
|
export type SingleTableValue<TTables extends Record<string, any>> =
|
|
127
299
|
IsSingleKey<TTables> extends true ? TTables[keyof TTables] : never;
|
|
128
300
|
|
|
129
|
-
//
|
|
130
|
-
type
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
[K in keyof T as T[K] extends null | undefined ? never : K]: T[K];
|
|
134
|
-
}>;
|
|
301
|
+
// __hasDefault__에 포함된 키들을 PuriTable<T>의 키로 제한
|
|
302
|
+
type HasDefaultKeys<T> = T extends { __hasDefault__: readonly (infer K)[] }
|
|
303
|
+
? Extract<K, keyof PuriTable<T>>
|
|
304
|
+
: never;
|
|
135
305
|
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
306
|
+
// __generated__에 포함된 키들 (INSERT 시 제외해야 함)
|
|
307
|
+
type GeneratedKeys<T> = T extends { __generated__: readonly (infer K)[] }
|
|
308
|
+
? Extract<K, keyof PuriTable<T>>
|
|
309
|
+
: never;
|
|
310
|
+
|
|
311
|
+
// Insert 타입: 메타데이터 제거 후, __hasDefault__ 컬럼들만 optional로 처리, __generated__ 컬럼은 완전히 제외
|
|
312
|
+
export type InsertData<T> = Omit<
|
|
313
|
+
PuriTable<T>,
|
|
314
|
+
InternalTypeKeys | HasDefaultKeys<T> | GeneratedKeys<T>
|
|
315
|
+
> & {
|
|
316
|
+
[K in HasDefaultKeys<T>]?: PuriTable<T>[K];
|
|
317
|
+
};
|
|
140
318
|
|
|
141
319
|
// Insert Result 타입
|
|
142
320
|
export type InsertResult = Pick<QueryResult<any>, "command" | "rowCount" | "rows" | "oid">;
|
|
@@ -170,3 +348,23 @@ export type OnConflictAction<TTables extends Record<string, unknown>> =
|
|
|
170
348
|
| AvailableColumns<TTables>[] // 배열 형태 - ["name", "email"]
|
|
171
349
|
| WhereCondition<TTables>; // 객체 형태 - { name: "John", count: Puri.rawNumber(...) }
|
|
172
350
|
};
|
|
351
|
+
|
|
352
|
+
// FK 컬럼명 추출 유틸리티 타입 - DatabaseForeignKeys 활용
|
|
353
|
+
export type ForeignKeyColumns<TTable extends TableName<DatabaseSchemaExtend>> =
|
|
354
|
+
TTable extends keyof DatabaseForeignKeys ? DatabaseForeignKeys[TTable] : never;
|
|
355
|
+
|
|
356
|
+
// Union을 Intersection으로 변환하는 유틸리티
|
|
357
|
+
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void
|
|
358
|
+
? I
|
|
359
|
+
: never;
|
|
360
|
+
|
|
361
|
+
// SelectAll 시 모든 조인된 테이블의 컬럼 포함
|
|
362
|
+
export type SelectAllResult<TTables extends Record<string, any>> = UnionToIntersection<
|
|
363
|
+
{
|
|
364
|
+
[K in keyof TTables]: TTables[K] extends infer T
|
|
365
|
+
? T extends LeftJoinedMarker
|
|
366
|
+
? Partial<OmitInternalTypeKeys<T>> // LEFT JOIN은 nullable, 메타데이터 제거
|
|
367
|
+
: OmitInternalTypeKeys<T> // INNER JOIN은 non-nullable, 메타데이터 제거
|
|
368
|
+
: never;
|
|
369
|
+
}[keyof TTables]
|
|
370
|
+
>;
|
|
@@ -3,24 +3,43 @@ import type { Knex } from "knex";
|
|
|
3
3
|
import { isArray, unique } from "radashi";
|
|
4
4
|
import { EntityManager } from "../entity/entity-manager";
|
|
5
5
|
import { Naite } from "../naite/naite";
|
|
6
|
+
import type { DatabaseForeignKeys, DatabaseSchemaExtend, EntityIndex } from "../types/types";
|
|
6
7
|
import { assertDefined, chunk, nonNullable } from "../utils/utils";
|
|
7
8
|
import { batchUpdate, type RowWithId } from "./_batch_update";
|
|
9
|
+
import type { ForeignKeyColumns, TableName } from "./puri.types";
|
|
8
10
|
|
|
11
|
+
/**
|
|
12
|
+
* FK 타입 추론을 위해 DatabaseForeignKeys export
|
|
13
|
+
* (module augmentation 자동 로드 보장)
|
|
14
|
+
*/
|
|
15
|
+
export type { DatabaseForeignKeys };
|
|
16
|
+
|
|
17
|
+
// 테이블 데이터 타입
|
|
9
18
|
type TableData = {
|
|
10
19
|
references: Set<string>;
|
|
11
20
|
rows: Record<string, unknown>[];
|
|
12
|
-
uniqueIndexes:
|
|
21
|
+
uniqueIndexes: EntityIndex[];
|
|
13
22
|
uniquesMap: Map<string, string>;
|
|
14
23
|
};
|
|
24
|
+
|
|
25
|
+
// 참조 필드 타입
|
|
15
26
|
export type UBRef = {
|
|
16
27
|
uuid: string;
|
|
17
28
|
of: string;
|
|
18
29
|
use?: string;
|
|
19
30
|
};
|
|
20
|
-
|
|
31
|
+
|
|
32
|
+
// upsert 옵션
|
|
33
|
+
export type UpsertOptions<TTable extends TableName<DatabaseSchemaExtend>> = {
|
|
34
|
+
chunkSize?: number;
|
|
35
|
+
cleanOrphans?: ForeignKeyColumns<TTable> | ForeignKeyColumns<TTable>[];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// insertOnly 옵션
|
|
39
|
+
export type InsertOnlyOptions = {
|
|
21
40
|
chunkSize?: number;
|
|
22
|
-
cleanOrphans?: string | string[]; // FK 컬럼명(들)
|
|
23
41
|
};
|
|
42
|
+
|
|
24
43
|
export function isRefField(field: unknown): field is UBRef {
|
|
25
44
|
return (
|
|
26
45
|
field !== undefined &&
|
|
@@ -76,11 +95,11 @@ export class UpsertBuilder {
|
|
|
76
95
|
const uniqueKeys = table.uniqueIndexes
|
|
77
96
|
.map((unqIndex) => {
|
|
78
97
|
const uniqueKeyArray = unqIndex.columns.map((unqCol) => {
|
|
79
|
-
const val = row[unqCol as keyof typeof row];
|
|
98
|
+
const val = row[unqCol.name as keyof typeof row];
|
|
80
99
|
if (isRefField(val)) {
|
|
81
100
|
return val.uuid;
|
|
82
101
|
} else {
|
|
83
|
-
return row[unqCol as keyof typeof row] ?? randomUUID(); // nullable인 경우 uuid로 랜덤값 삽입
|
|
102
|
+
return row[unqCol.name as keyof typeof row] ?? randomUUID(); // nullable인 경우 uuid로 랜덤값 삽입
|
|
84
103
|
}
|
|
85
104
|
});
|
|
86
105
|
|
|
@@ -154,37 +173,27 @@ export class UpsertBuilder {
|
|
|
154
173
|
return result;
|
|
155
174
|
}
|
|
156
175
|
|
|
157
|
-
async upsert(
|
|
176
|
+
async upsert<TTable extends TableName<DatabaseSchemaExtend>>(
|
|
158
177
|
wdb: Knex,
|
|
159
|
-
tableName:
|
|
160
|
-
|
|
178
|
+
tableName: TTable,
|
|
179
|
+
options?: UpsertOptions<TTable>,
|
|
161
180
|
): Promise<number[]> {
|
|
162
|
-
// 숫자면 { chunkSize: n } 으로 변환
|
|
163
|
-
const options =
|
|
164
|
-
typeof optionsOrChunkSize === "number"
|
|
165
|
-
? { chunkSize: optionsOrChunkSize }
|
|
166
|
-
: optionsOrChunkSize;
|
|
167
|
-
|
|
168
181
|
return this.upsertOrInsert(wdb, tableName, "upsert", options);
|
|
169
182
|
}
|
|
170
|
-
|
|
183
|
+
|
|
184
|
+
async insertOnly<TTable extends TableName<DatabaseSchemaExtend>>(
|
|
171
185
|
wdb: Knex,
|
|
172
|
-
tableName:
|
|
173
|
-
|
|
186
|
+
tableName: TTable,
|
|
187
|
+
options?: InsertOnlyOptions,
|
|
174
188
|
): Promise<number[]> {
|
|
175
|
-
const options =
|
|
176
|
-
typeof optionsOrChunkSize === "number"
|
|
177
|
-
? { chunkSize: optionsOrChunkSize }
|
|
178
|
-
: optionsOrChunkSize;
|
|
179
|
-
|
|
180
189
|
return this.upsertOrInsert(wdb, tableName, "insert", options);
|
|
181
190
|
}
|
|
182
191
|
|
|
183
|
-
async upsertOrInsert(
|
|
192
|
+
async upsertOrInsert<TTable extends TableName<DatabaseSchemaExtend>>(
|
|
184
193
|
wdb: Knex,
|
|
185
|
-
tableName:
|
|
194
|
+
tableName: TTable,
|
|
186
195
|
mode: "upsert" | "insert",
|
|
187
|
-
options?: UpsertOptions
|
|
196
|
+
options?: UpsertOptions<TTable>,
|
|
188
197
|
): Promise<number[]> {
|
|
189
198
|
if (this.hasTable(tableName) === false) {
|
|
190
199
|
return [];
|
|
@@ -234,11 +243,6 @@ export class UpsertBuilder {
|
|
|
234
243
|
throw new Error(`${tableName}에 순환 자기 참조가 있습니다.`);
|
|
235
244
|
}
|
|
236
245
|
|
|
237
|
-
// upsert 모드일 때 유니크 인덱스가 없으면 에러
|
|
238
|
-
if (mode === "upsert" && table.uniqueIndexes.length === 0) {
|
|
239
|
-
throw new Error(`${tableName}에 unique index가 정의되지 않아 upsert를 할 수 없습니다.`);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
246
|
const uuidMap = new Map<string, unknown>();
|
|
243
247
|
const allIds: number[] = [];
|
|
244
248
|
|
|
@@ -284,8 +288,11 @@ export class UpsertBuilder {
|
|
|
284
288
|
// INSERT 모드 - RETURNING 사용
|
|
285
289
|
resultRows = await wdb.insert(dataForDb).into(tableName).returning(selectFields);
|
|
286
290
|
} else {
|
|
287
|
-
// UPSERT 모드 - onConflict 사용
|
|
288
|
-
const conflictColumns =
|
|
291
|
+
// UPSERT 모드 - onConflict 사용 (unique index 없으면 PK fallback)
|
|
292
|
+
const conflictColumns =
|
|
293
|
+
table.uniqueIndexes.length > 0
|
|
294
|
+
? table.uniqueIndexes[0].columns.map((c) => c.name)
|
|
295
|
+
: ["id"];
|
|
289
296
|
const updateColumns = Object.keys(dataForDb[0]).filter(
|
|
290
297
|
(col) => !conflictColumns.includes(col),
|
|
291
298
|
);
|
|
@@ -340,7 +347,9 @@ export class UpsertBuilder {
|
|
|
340
347
|
|
|
341
348
|
if (options?.cleanOrphans) {
|
|
342
349
|
const cleanOrphans = options.cleanOrphans;
|
|
343
|
-
const fkColumns = isArray(cleanOrphans)
|
|
350
|
+
const fkColumns = isArray(cleanOrphans)
|
|
351
|
+
? (cleanOrphans as ForeignKeyColumns<TTable>[])
|
|
352
|
+
: [cleanOrphans as ForeignKeyColumns<TTable>];
|
|
344
353
|
|
|
345
354
|
// 현재 register된 레코드들의 FK 값들 추출
|
|
346
355
|
const fkConditions = fkColumns.map((fkCol) => {
|
|
@@ -430,7 +439,7 @@ export class UpsertBuilder {
|
|
|
430
439
|
}
|
|
431
440
|
|
|
432
441
|
// ============================================================================
|
|
433
|
-
// Private
|
|
442
|
+
// Private Helper Methods
|
|
434
443
|
// ============================================================================
|
|
435
444
|
|
|
436
445
|
/**
|
|
@@ -5,7 +5,7 @@ import inflection from "inflection";
|
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { prettifyError } from "zod";
|
|
7
7
|
import { Sonamu } from "../api/sonamu";
|
|
8
|
-
import { type EntityJson, EntityJsonSchema } from "../types/types";
|
|
8
|
+
import { type EntityIndex, type EntityJson, EntityJsonSchema } from "../types/types";
|
|
9
9
|
import type { AbsolutePath } from "../utils/path-utils";
|
|
10
10
|
import { Entity } from "./entity";
|
|
11
11
|
|
|
@@ -15,7 +15,7 @@ export type EntityNamesRecord = Record<
|
|
|
15
15
|
>;
|
|
16
16
|
type TableSpec = {
|
|
17
17
|
name: string;
|
|
18
|
-
uniqueIndexes:
|
|
18
|
+
uniqueIndexes: EntityIndex[];
|
|
19
19
|
};
|
|
20
20
|
class EntityManagerClass {
|
|
21
21
|
private entities: Map<string, Entity> = new Map();
|