sonamu 0.7.4 → 0.7.6
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 +7 -4
- 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.d.ts.map +1 -1
- package/dist/template/implementations/model.template.js +20 -15
- package/dist/template/implementations/model_test.template.js +4 -4
- package/dist/template/implementations/service.template.d.ts.map +1 -1
- package/dist/template/implementations/service.template.js +2 -2
- 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 +6 -3
- 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 +25 -15
- package/src/template/implementations/model_test.template.ts +3 -3
- package/src/template/implementations/service.template.ts +5 -1
- 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
package/src/types/types.ts
CHANGED
|
@@ -15,12 +15,18 @@ export type DistributiveOmit<T, K extends keyof any> = T extends any ? Omit<T, K
|
|
|
15
15
|
/*
|
|
16
16
|
Model-Defintion
|
|
17
17
|
*/
|
|
18
|
+
export type GeneratedColumnType = "STORED" | "VIRTUAL";
|
|
19
|
+
export type GeneratedColumn = {
|
|
20
|
+
type: GeneratedColumnType;
|
|
21
|
+
expression: string;
|
|
22
|
+
};
|
|
18
23
|
export type CommonProp = {
|
|
19
24
|
name: string;
|
|
20
25
|
nullable?: boolean;
|
|
21
26
|
toFilter?: true;
|
|
22
27
|
desc?: string;
|
|
23
28
|
dbDefault?: string;
|
|
29
|
+
generated?: GeneratedColumn;
|
|
24
30
|
};
|
|
25
31
|
export type IntegerProp = CommonProp & {
|
|
26
32
|
type: "integer";
|
|
@@ -99,7 +105,14 @@ export type VirtualProp = CommonProp & {
|
|
|
99
105
|
type: "virtual";
|
|
100
106
|
id: string;
|
|
101
107
|
}; // PG: none / TS: any(id) / JSON: any
|
|
102
|
-
|
|
108
|
+
export type VectorProp = CommonProp & {
|
|
109
|
+
type: "vector";
|
|
110
|
+
dimensions: number;
|
|
111
|
+
};
|
|
112
|
+
export type VectorArrayProp = CommonProp & {
|
|
113
|
+
type: "vector[]";
|
|
114
|
+
dimensions: number;
|
|
115
|
+
};
|
|
103
116
|
export type RelationType = "HasMany" | "BelongsToOne" | "ManyToMany" | "OneToOne";
|
|
104
117
|
export type RelationOn = "CASCADE" | "SET NULL" | "NO ACTION" | "SET DEFAULT" | "RESTRICT";
|
|
105
118
|
type _RelationProp = {
|
|
@@ -170,13 +183,82 @@ export type EntityProp =
|
|
|
170
183
|
| UuidArrayProp
|
|
171
184
|
| JsonProp
|
|
172
185
|
| VirtualProp
|
|
186
|
+
| VectorProp
|
|
187
|
+
| VectorArrayProp
|
|
173
188
|
| RelationProp;
|
|
174
189
|
|
|
190
|
+
/**
|
|
191
|
+
* pgvector 거리 연산자 클래스
|
|
192
|
+
*
|
|
193
|
+
* @description
|
|
194
|
+
* - `vector_cosine_ops`: 코사인 거리 (Cosine Distance) - 권장
|
|
195
|
+
* - SQL 연산자: `<=>`
|
|
196
|
+
* - 벡터의 방향만 비교 (크기 무시), 1 - cosine_similarity
|
|
197
|
+
* - 텍스트 임베딩, 시맨틱 검색에 가장 일반적으로 사용
|
|
198
|
+
* - 사용 예: OpenAI, Voyage 등 대부분의 임베딩 모델에 권장
|
|
199
|
+
*
|
|
200
|
+
* - `vector_ip_ops`: 내적 (Inner Product)
|
|
201
|
+
* - SQL 연산자: `<#>`
|
|
202
|
+
* - 두 벡터의 내적을 계산 (sum(a[i] * b[i]))
|
|
203
|
+
* - 정규화된 벡터에서 코사인 유사도와 동일한 결과
|
|
204
|
+
* - 값이 클수록 유사 (음수 연산자이므로 ORDER BY에서 주의)
|
|
205
|
+
* - 사용 예: 이미 정규화된 임베딩에서 가장 빠른 성능
|
|
206
|
+
*
|
|
207
|
+
* - `vector_l2_ops`: 유클리드 거리 (L2 Distance)
|
|
208
|
+
* - SQL 연산자: `<->`
|
|
209
|
+
* - 두 벡터 간의 직선 거리를 계산 (sqrt(sum((a[i] - b[i])^2)))
|
|
210
|
+
* - 벡터의 크기(magnitude)가 중요할 때 사용
|
|
211
|
+
* - 사용 예: 이미지 유사도, 절대적 거리 측정이 필요한 경우
|
|
212
|
+
*/
|
|
213
|
+
export type VectorOps = "vector_cosine_ops" | "vector_ip_ops" | "vector_l2_ops";
|
|
214
|
+
|
|
215
|
+
type EntityIndexColumn = {
|
|
216
|
+
name: string;
|
|
217
|
+
nullsFirst?: boolean;
|
|
218
|
+
sortOrder?: "ASC" | "DESC";
|
|
219
|
+
/** pgvector 인덱스에서 사용할 거리 연산자 (vector 컬럼에만 적용) */
|
|
220
|
+
vectorOps?: VectorOps;
|
|
221
|
+
};
|
|
175
222
|
export type EntityIndex = {
|
|
176
|
-
type: "index" | "unique" | "fulltext";
|
|
177
|
-
columns:
|
|
223
|
+
type: "index" | "unique" | "fulltext" | "hnsw" | "ivfflat";
|
|
224
|
+
columns: EntityIndexColumn[];
|
|
178
225
|
name: string;
|
|
179
226
|
parser?: "built-in" | "ngram";
|
|
227
|
+
nullsNotDistinct?: boolean; // unique index only
|
|
228
|
+
/**
|
|
229
|
+
* HNSW (Hierarchical Navigable Small World) 인덱스: 각 노드의 최대 연결 수
|
|
230
|
+
*
|
|
231
|
+
* @description
|
|
232
|
+
* 그래프에서 각 노드가 가질 수 있는 최대 연결 수입니다.
|
|
233
|
+
* HNSW는 빠른 검색 속도와 높은 정확도를 제공하므로 권장됩니다.
|
|
234
|
+
* - 기본값: 16
|
|
235
|
+
* - 범위: 2 ~ 100
|
|
236
|
+
* - 높을수록: 정확도↑, 빌드 시간↑, 메모리↑
|
|
237
|
+
* - 권장: 빠른 빌드(8), 균형(16), 높은 정확도(32), 최고 정확도(64)
|
|
238
|
+
*/
|
|
239
|
+
m?: number;
|
|
240
|
+
/**
|
|
241
|
+
* HNSW (Hierarchical Navigable Small World) 인덱스: 구성 시 탐색 범위
|
|
242
|
+
*
|
|
243
|
+
* @description
|
|
244
|
+
* 인덱스 구성 시 각 노드에서 탐색할 범위입니다.
|
|
245
|
+
* - 기본값: 64
|
|
246
|
+
* - 범위: 4 ~ 1000
|
|
247
|
+
* - 높을수록: 정확도↑, 빌드 시간↑
|
|
248
|
+
* - 권장: 빠른 빌드(32), 균형(64), 높은 정확도(128), 최고 정확도(256)
|
|
249
|
+
*/
|
|
250
|
+
efConstruction?: number;
|
|
251
|
+
/**
|
|
252
|
+
* IVFFlat (Inverted File with Flat Compression) 인덱스: 클러스터링 리스트 수
|
|
253
|
+
*
|
|
254
|
+
* @description
|
|
255
|
+
* 벡터를 클러스터링할 버킷 수를 지정합니다.
|
|
256
|
+
* IVFFlat은 빠른 빌드와 낮은 메모리 사용이 필요할 때 사용합니다.
|
|
257
|
+
* - 권장값: sqrt(row_count) ~ row_count / 1000
|
|
258
|
+
* - 예시: 10,000행 → 100, 100,000행 → 300, 1,000,000행 → 1000
|
|
259
|
+
* - 많을수록 정확도↑, 검색 속도↓
|
|
260
|
+
*/
|
|
261
|
+
lists?: number;
|
|
180
262
|
};
|
|
181
263
|
export type EntityJson = {
|
|
182
264
|
id: string;
|
|
@@ -327,6 +409,15 @@ export function isJsonProp(p: unknown): p is JsonProp {
|
|
|
327
409
|
export function isVirtualProp(p: unknown): p is VirtualProp {
|
|
328
410
|
return (p as VirtualProp)?.type === "virtual";
|
|
329
411
|
}
|
|
412
|
+
export function isVectorSingleProp(p: unknown): p is VectorProp {
|
|
413
|
+
return (p as VectorProp)?.type === "vector";
|
|
414
|
+
}
|
|
415
|
+
export function isVectorArrayProp(p: unknown): p is VectorArrayProp {
|
|
416
|
+
return (p as VectorArrayProp)?.type === "vector[]";
|
|
417
|
+
}
|
|
418
|
+
export function isVectorProp(p: unknown): p is VectorProp | VectorArrayProp {
|
|
419
|
+
return isVectorSingleProp(p) || isVectorArrayProp(p);
|
|
420
|
+
}
|
|
330
421
|
export function isRelationProp(p: unknown): p is RelationProp {
|
|
331
422
|
return (p as RelationProp)?.type === "relation";
|
|
332
423
|
}
|
|
@@ -439,7 +530,10 @@ export type MigrationColumnType =
|
|
|
439
530
|
| "date[]"
|
|
440
531
|
| "uuid"
|
|
441
532
|
| "uuid[]"
|
|
442
|
-
| "json"
|
|
533
|
+
| "json"
|
|
534
|
+
| "vector"
|
|
535
|
+
| "vector[]"
|
|
536
|
+
| "tsvector";
|
|
443
537
|
export type MigrationColumn = {
|
|
444
538
|
name: string;
|
|
445
539
|
type: MigrationColumnType;
|
|
@@ -449,12 +543,21 @@ export type MigrationColumn = {
|
|
|
449
543
|
defaultTo?: string;
|
|
450
544
|
precision?: number;
|
|
451
545
|
scale?: number;
|
|
546
|
+
dimensions?: number;
|
|
547
|
+
generated?: GeneratedColumn;
|
|
452
548
|
};
|
|
453
549
|
export type MigrationIndex = {
|
|
550
|
+
type: "unique" | "index" | "fulltext" | "hnsw" | "ivfflat";
|
|
551
|
+
columns: EntityIndexColumn[];
|
|
454
552
|
name: string;
|
|
455
|
-
columns: string[];
|
|
456
|
-
type: "unique" | "index" | "fulltext";
|
|
457
553
|
parser?: "built-in" | "ngram";
|
|
554
|
+
nullsNotDistinct?: boolean;
|
|
555
|
+
/** HNSW (Hierarchical Navigable Small World): 각 노드의 최대 연결 수 */
|
|
556
|
+
m?: number;
|
|
557
|
+
/** HNSW (Hierarchical Navigable Small World): 구성 시 탐색 범위 */
|
|
558
|
+
efConstruction?: number;
|
|
559
|
+
/** IVFFlat (Inverted File with Flat Compression): 클러스터링 리스트 수 */
|
|
560
|
+
lists?: number;
|
|
458
561
|
};
|
|
459
562
|
export type MigrationForeign = {
|
|
460
563
|
columns: string[];
|
|
@@ -698,7 +801,8 @@ export type RenderingNode = {
|
|
|
698
801
|
| "array-images"
|
|
699
802
|
| "object"
|
|
700
803
|
| "object-pick"
|
|
701
|
-
| "record"
|
|
804
|
+
| "record"
|
|
805
|
+
| "vector";
|
|
702
806
|
zodType: z.ZodTypeAny;
|
|
703
807
|
element?: RenderingNode;
|
|
704
808
|
children?: RenderingNode[];
|
|
@@ -709,12 +813,18 @@ export type RenderingNode = {
|
|
|
709
813
|
nullable?: boolean;
|
|
710
814
|
};
|
|
711
815
|
|
|
816
|
+
const GeneratedColumnSchema = z.object({
|
|
817
|
+
type: z.enum(["STORED", "VIRTUAL"]),
|
|
818
|
+
expression: z.string(),
|
|
819
|
+
});
|
|
820
|
+
|
|
712
821
|
const BasePropFields = {
|
|
713
822
|
name: z.string(),
|
|
714
823
|
desc: z.string().optional(),
|
|
715
824
|
nullable: z.boolean().optional(),
|
|
716
|
-
toFilter: z.
|
|
825
|
+
toFilter: z.boolean().default(false).optional(),
|
|
717
826
|
dbDefault: z.union([z.string(), z.number(), z.boolean()]).optional(),
|
|
827
|
+
generated: GeneratedColumnSchema.optional(),
|
|
718
828
|
};
|
|
719
829
|
|
|
720
830
|
// 부가 필드가 필요없는 prop
|
|
@@ -764,6 +874,7 @@ const EnumPropSchema = z
|
|
|
764
874
|
...BasePropFields,
|
|
765
875
|
type: z.literal("enum"),
|
|
766
876
|
id: z.string(),
|
|
877
|
+
length: z.number().optional(),
|
|
767
878
|
})
|
|
768
879
|
.strict();
|
|
769
880
|
const EnumArrayPropSchema = z
|
|
@@ -822,6 +933,21 @@ const VirtualPropSchema = z
|
|
|
822
933
|
})
|
|
823
934
|
.strict();
|
|
824
935
|
|
|
936
|
+
const VectorPropSchema = z
|
|
937
|
+
.object({
|
|
938
|
+
...BasePropFields,
|
|
939
|
+
type: z.literal("vector"),
|
|
940
|
+
dimensions: z.number(),
|
|
941
|
+
})
|
|
942
|
+
.strict();
|
|
943
|
+
const VectorArrayPropSchema = z
|
|
944
|
+
.object({
|
|
945
|
+
...BasePropFields,
|
|
946
|
+
type: z.literal("vector[]"),
|
|
947
|
+
dimensions: z.number(),
|
|
948
|
+
})
|
|
949
|
+
.strict();
|
|
950
|
+
|
|
825
951
|
// Relation 타입은 relationType에 따라 세분화
|
|
826
952
|
const BaseRelationFields = {
|
|
827
953
|
...BasePropFields,
|
|
@@ -910,27 +1036,84 @@ const NormalPropTypes = [
|
|
|
910
1036
|
"uuid[]",
|
|
911
1037
|
"json",
|
|
912
1038
|
"virtual",
|
|
1039
|
+
"vector",
|
|
1040
|
+
"vector[]",
|
|
1041
|
+
] as const;
|
|
1042
|
+
|
|
1043
|
+
// VIRTUAL Generated Column에서 사용 불가능한 타입들
|
|
1044
|
+
const VirtualGeneratedDisallowedTypes = [
|
|
1045
|
+
"json",
|
|
1046
|
+
"vector",
|
|
1047
|
+
"vector[]",
|
|
1048
|
+
"string[]",
|
|
1049
|
+
"integer[]",
|
|
1050
|
+
"bigInteger[]",
|
|
1051
|
+
"boolean[]",
|
|
1052
|
+
"date[]",
|
|
1053
|
+
"uuid[]",
|
|
1054
|
+
"number[]",
|
|
1055
|
+
"numeric[]",
|
|
1056
|
+
"enum[]",
|
|
913
1057
|
] as const;
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1058
|
+
|
|
1059
|
+
export const NormalPropSchema = z
|
|
1060
|
+
.discriminatedUnion(
|
|
1061
|
+
"type",
|
|
1062
|
+
[
|
|
1063
|
+
BasePropFieldsWithoutAdditional,
|
|
1064
|
+
StringPropSchema,
|
|
1065
|
+
StringArrayPropSchema,
|
|
1066
|
+
EnumPropSchema,
|
|
1067
|
+
EnumArrayPropSchema,
|
|
1068
|
+
NumberPropSchema,
|
|
1069
|
+
NumberArrayPropSchema,
|
|
1070
|
+
NumericPropSchema,
|
|
1071
|
+
NumericArrayPropSchema,
|
|
1072
|
+
JsonPropSchema,
|
|
1073
|
+
VirtualPropSchema,
|
|
1074
|
+
VectorPropSchema,
|
|
1075
|
+
VectorArrayPropSchema,
|
|
1076
|
+
],
|
|
1077
|
+
{
|
|
1078
|
+
error: (iss) =>
|
|
1079
|
+
`type은 ${NormalPropTypes.map((t) => `'${t}'`).join(", ")} 중 하나여야 합니다. 입력값: "${(iss.input as Record<string, unknown>)?.type}"`,
|
|
1080
|
+
},
|
|
1081
|
+
)
|
|
1082
|
+
.superRefine((data, ctx) => {
|
|
1083
|
+
if (!data.generated) {
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// dbDefault와 generated 동시 사용 불가
|
|
1088
|
+
if (data.dbDefault !== undefined) {
|
|
1089
|
+
ctx.addIssue({
|
|
1090
|
+
code: "custom",
|
|
1091
|
+
message: "dbDefault와 generated는 함께 사용할 수 없습니다",
|
|
1092
|
+
path: ["generated"],
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// virtual 타입은 generated 불가
|
|
1097
|
+
if (data.type === "virtual") {
|
|
1098
|
+
ctx.addIssue({
|
|
1099
|
+
code: "custom",
|
|
1100
|
+
message: "virtual 타입은 generated column을 지원하지 않습니다",
|
|
1101
|
+
path: ["generated"],
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// VIRTUAL Generated Column 타입 제한 검증
|
|
1106
|
+
if (data.generated.type === "VIRTUAL") {
|
|
1107
|
+
if ((VirtualGeneratedDisallowedTypes as readonly string[]).includes(data.type)) {
|
|
1108
|
+
ctx.addIssue({
|
|
1109
|
+
code: "custom",
|
|
1110
|
+
message: `VIRTUAL generated column은 ${data.type} 타입을 지원하지 않습니다. STORED를 사용하세요.`,
|
|
1111
|
+
path: ["generated", "type"],
|
|
1112
|
+
fatal: true,
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
934
1117
|
|
|
935
1118
|
const AllPropTypes = [...NormalPropTypes, "relation"] as const;
|
|
936
1119
|
const EntityPropSchema = z.discriminatedUnion("type", [NormalPropSchema, RelationPropSchema], {
|
|
@@ -938,13 +1121,24 @@ const EntityPropSchema = z.discriminatedUnion("type", [NormalPropSchema, Relatio
|
|
|
938
1121
|
`type은 ${AllPropTypes.map((t) => `'${t}'`).join(", ")} 중 하나여야 합니다. 입력값: "${(iss.input as Record<string, unknown>)?.type}"`,
|
|
939
1122
|
});
|
|
940
1123
|
|
|
1124
|
+
const EntityIndexColumnSchema = z.object({
|
|
1125
|
+
name: z.string(),
|
|
1126
|
+
nullsFirst: z.boolean().optional(),
|
|
1127
|
+
sortOrder: z.enum(["ASC", "DESC"]).optional(),
|
|
1128
|
+
vectorOps: z.enum(["vector_cosine_ops", "vector_ip_ops", "vector_l2_ops"]).optional(),
|
|
1129
|
+
});
|
|
1130
|
+
|
|
941
1131
|
// EntityIndex 스키마 정의
|
|
942
1132
|
const EntityIndexSchema = z
|
|
943
1133
|
.object({
|
|
944
|
-
type: z.enum(["index", "unique", "fulltext"]),
|
|
945
|
-
columns: z.array(
|
|
1134
|
+
type: z.enum(["index", "unique", "fulltext", "hnsw", "ivfflat"]),
|
|
1135
|
+
columns: z.array(EntityIndexColumnSchema),
|
|
946
1136
|
name: z.string().min(1).max(63),
|
|
947
1137
|
parser: z.enum(["built-in", "ngram"]).optional(),
|
|
1138
|
+
nullsNotDistinct: z.boolean().optional(),
|
|
1139
|
+
m: z.number().optional(),
|
|
1140
|
+
efConstruction: z.number().optional(),
|
|
1141
|
+
lists: z.number().optional(),
|
|
948
1142
|
})
|
|
949
1143
|
.strict();
|
|
950
1144
|
|
|
@@ -1121,6 +1315,8 @@ export type RelationNode = {
|
|
|
1121
1315
|
|
|
1122
1316
|
// biome-ignore lint/suspicious/noEmptyInterface: sonamu.generated.sso 에서 확장을 위해 준비된 빈 인터페이스
|
|
1123
1317
|
export interface DatabaseSchemaExtend {}
|
|
1318
|
+
// biome-ignore lint/suspicious/noEmptyInterface: sonamu.generated.sso 에서 확장을 위해 준비된 빈 인터페이스
|
|
1319
|
+
export interface DatabaseForeignKeys {}
|
|
1124
1320
|
export type ManyToManyBaseSchema<FromIdKey extends string, ToIdKey extends string> = {
|
|
1125
1321
|
id: number;
|
|
1126
1322
|
} & {
|
|
@@ -1149,22 +1345,4 @@ export type SonamuFastifyConfig = {
|
|
|
1149
1345
|
options: ApiDecoratorOptions;
|
|
1150
1346
|
},
|
|
1151
1347
|
) => void;
|
|
1152
|
-
cache?: {
|
|
1153
|
-
get: (key: string) => Promise<unknown | null>;
|
|
1154
|
-
put: (key: string, value: unknown, ttl?: number) => Promise<void>;
|
|
1155
|
-
resolveKey: (
|
|
1156
|
-
path: string,
|
|
1157
|
-
reqBody: {
|
|
1158
|
-
[key: string]: unknown;
|
|
1159
|
-
},
|
|
1160
|
-
) =>
|
|
1161
|
-
| {
|
|
1162
|
-
cache: false;
|
|
1163
|
-
}
|
|
1164
|
-
| {
|
|
1165
|
-
cache: true;
|
|
1166
|
-
key: string;
|
|
1167
|
-
ttl?: number;
|
|
1168
|
-
};
|
|
1169
|
-
};
|
|
1170
1348
|
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { DEFAULT_VECTOR_CONFIG } from "./config";
|
|
2
|
+
import type { Chunk, ChunkingConfig } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 텍스트 청킹
|
|
6
|
+
* - 현재 가이드에서는 400토큰 이하만 저장하므로 기본적으로 사용하지 않음
|
|
7
|
+
* - 추후 긴 문서 처리 시 사용
|
|
8
|
+
*/
|
|
9
|
+
export class Chunking {
|
|
10
|
+
private config: ChunkingConfig;
|
|
11
|
+
|
|
12
|
+
constructor(config: Partial<ChunkingConfig> = {}) {
|
|
13
|
+
this.config = { ...DEFAULT_VECTOR_CONFIG.chunking, ...config };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 텍스트를 청크로 분할
|
|
18
|
+
*/
|
|
19
|
+
chunk(text: string): Chunk[] {
|
|
20
|
+
if (text.length < this.config.skipThreshold) {
|
|
21
|
+
return [
|
|
22
|
+
{
|
|
23
|
+
index: 0,
|
|
24
|
+
text: text.trim(),
|
|
25
|
+
startOffset: 0,
|
|
26
|
+
endOffset: text.length,
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const chunks: Chunk[] = [];
|
|
32
|
+
let currentPosition = 0;
|
|
33
|
+
|
|
34
|
+
while (currentPosition < text.length) {
|
|
35
|
+
const remainingText = text.slice(currentPosition);
|
|
36
|
+
const { chunk, length } = this.extractChunk(remainingText);
|
|
37
|
+
|
|
38
|
+
if (chunk.trim().length >= this.config.minChunkSize) {
|
|
39
|
+
chunks.push({
|
|
40
|
+
index: chunks.length,
|
|
41
|
+
text: chunk.trim(),
|
|
42
|
+
startOffset: currentPosition,
|
|
43
|
+
endOffset: currentPosition + length,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const advance = Math.max(length - this.config.chunkOverlap, this.config.minChunkSize);
|
|
48
|
+
currentPosition += advance;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return chunks;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 청킹이 필요한지 확인
|
|
56
|
+
*/
|
|
57
|
+
needsChunking(text: string): boolean {
|
|
58
|
+
return text.length > this.config.chunkSize;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 예상 청크 수 계산
|
|
63
|
+
*/
|
|
64
|
+
estimateChunkCount(text: string): number {
|
|
65
|
+
if (text.length <= this.config.chunkSize) {
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
const effectiveChunkSize = this.config.chunkSize - this.config.chunkOverlap;
|
|
69
|
+
return Math.ceil(text.length / effectiveChunkSize);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private extractChunk(text: string): { chunk: string; length: number } {
|
|
73
|
+
if (text.length <= this.config.chunkSize) {
|
|
74
|
+
return { chunk: text, length: text.length };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const separator of this.config.separators) {
|
|
78
|
+
const result = this.splitBySeparator(text, separator);
|
|
79
|
+
if (result) return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
chunk: text.slice(0, this.config.chunkSize),
|
|
84
|
+
length: this.config.chunkSize,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private splitBySeparator(
|
|
89
|
+
text: string,
|
|
90
|
+
separator: string,
|
|
91
|
+
): { chunk: string; length: number } | null {
|
|
92
|
+
const searchRange = text.slice(0, this.config.chunkSize + 100);
|
|
93
|
+
|
|
94
|
+
let lastIndex = -1;
|
|
95
|
+
let index = 0;
|
|
96
|
+
|
|
97
|
+
while (true) {
|
|
98
|
+
index = searchRange.indexOf(separator, index);
|
|
99
|
+
if (index === -1) break;
|
|
100
|
+
if (index <= this.config.chunkSize) {
|
|
101
|
+
lastIndex = index + separator.length;
|
|
102
|
+
}
|
|
103
|
+
index++;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (lastIndex > this.config.minChunkSize) {
|
|
107
|
+
return {
|
|
108
|
+
chunk: text.slice(0, lastIndex),
|
|
109
|
+
length: lastIndex,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { VectorConfig } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 벡터 검색 기본 설정
|
|
5
|
+
* 사용자는 이 설정을 override하여 커스터마이즈할 수 있음
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULT_VECTOR_CONFIG: VectorConfig = {
|
|
8
|
+
// Voyage AI 설정
|
|
9
|
+
// apiKey는 Sonamu.secrets에서 로드되므로 여기서는 빈 문자열
|
|
10
|
+
voyage: {
|
|
11
|
+
apiKey: "",
|
|
12
|
+
baseUrl: "https://api.voyageai.com/v1/embeddings",
|
|
13
|
+
model: "voyage-3",
|
|
14
|
+
dimensions: 1024,
|
|
15
|
+
maxTokens: 32000,
|
|
16
|
+
batchSize: 100,
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
// OpenAI 설정
|
|
20
|
+
// apiKey는 Sonamu.secrets에서 로드되므로 여기서는 빈 문자열
|
|
21
|
+
openai: {
|
|
22
|
+
apiKey: "",
|
|
23
|
+
baseUrl: "https://api.openai.com/v1/embeddings",
|
|
24
|
+
model: "text-embedding-3-small",
|
|
25
|
+
dimensions: 1536,
|
|
26
|
+
maxTokens: 8191,
|
|
27
|
+
batchSize: 100,
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
// 청킹 설정 (필요시 사용)
|
|
31
|
+
chunking: {
|
|
32
|
+
chunkSize: 500,
|
|
33
|
+
chunkOverlap: 50,
|
|
34
|
+
minChunkSize: 50,
|
|
35
|
+
skipThreshold: 200,
|
|
36
|
+
separators: ["\n\n", "\n", "。", ". ", "! ", "? ", ", ", " "],
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// 검색 설정
|
|
40
|
+
search: {
|
|
41
|
+
defaultLimit: 10,
|
|
42
|
+
similarityThreshold: 0.5,
|
|
43
|
+
vectorWeight: 0.7,
|
|
44
|
+
ftsWeight: 0.3,
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// pgvector 설정
|
|
48
|
+
pgvector: {
|
|
49
|
+
iterativeScan: true,
|
|
50
|
+
efSearch: 100,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 설정 생성 헬퍼 함수
|
|
56
|
+
* 부분 설정만 제공하면 나머지는 기본값 사용
|
|
57
|
+
*/
|
|
58
|
+
export function createVectorConfig(
|
|
59
|
+
overrides: Partial<VectorConfig> = {}
|
|
60
|
+
): VectorConfig {
|
|
61
|
+
return {
|
|
62
|
+
voyage: { ...DEFAULT_VECTOR_CONFIG.voyage, ...overrides.voyage },
|
|
63
|
+
openai: { ...DEFAULT_VECTOR_CONFIG.openai, ...overrides.openai },
|
|
64
|
+
chunking: { ...DEFAULT_VECTOR_CONFIG.chunking, ...overrides.chunking },
|
|
65
|
+
search: { ...DEFAULT_VECTOR_CONFIG.search, ...overrides.search },
|
|
66
|
+
pgvector: { ...DEFAULT_VECTOR_CONFIG.pgvector, ...overrides.pgvector },
|
|
67
|
+
};
|
|
68
|
+
}
|