sonamu 0.7.11 → 0.7.13

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.
Files changed (153) hide show
  1. package/dist/api/config.d.ts +10 -6
  2. package/dist/api/config.d.ts.map +1 -1
  3. package/dist/api/config.js +2 -1
  4. package/dist/api/sonamu.d.ts +4 -0
  5. package/dist/api/sonamu.d.ts.map +1 -1
  6. package/dist/api/sonamu.js +49 -5
  7. package/dist/bin/cli.js +118 -170
  8. package/dist/database/base-model.d.ts +10 -50
  9. package/dist/database/base-model.d.ts.map +1 -1
  10. package/dist/database/base-model.js +19 -84
  11. package/dist/database/base-model.types.d.ts +4 -4
  12. package/dist/database/base-model.types.d.ts.map +1 -1
  13. package/dist/database/base-model.types.js +1 -1
  14. package/dist/database/db.d.ts +1 -0
  15. package/dist/database/db.d.ts.map +1 -1
  16. package/dist/database/db.js +24 -13
  17. package/dist/database/puri-subset.test-d.js +1 -1
  18. package/dist/database/puri-subset.types.d.ts +1 -0
  19. package/dist/database/puri-subset.types.d.ts.map +1 -1
  20. package/dist/database/puri-subset.types.js +2 -2
  21. package/dist/database/puri.d.ts +82 -3
  22. package/dist/database/puri.d.ts.map +1 -1
  23. package/dist/database/puri.js +180 -14
  24. package/dist/database/puri.types.d.ts +33 -6
  25. package/dist/database/puri.types.d.ts.map +1 -1
  26. package/dist/database/puri.types.js +1 -1
  27. package/dist/database/puri.types.test-d.js +1 -1
  28. package/dist/entity/entity-manager.d.ts +5 -4
  29. package/dist/entity/entity-manager.d.ts.map +1 -1
  30. package/dist/entity/entity-manager.js +8 -1
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +3 -3
  34. package/dist/migration/code-generation.d.ts.map +1 -1
  35. package/dist/migration/code-generation.js +33 -2
  36. package/dist/migration/postgresql-schema-reader.d.ts.map +1 -1
  37. package/dist/migration/postgresql-schema-reader.js +53 -22
  38. package/dist/naite/messaging-types.d.ts.map +1 -1
  39. package/dist/naite/messaging-types.js +1 -1
  40. package/dist/naite/naite.js +2 -2
  41. package/dist/stream/sse.d.ts +2 -6
  42. package/dist/stream/sse.d.ts.map +1 -1
  43. package/dist/stream/sse.js +9 -3
  44. package/dist/syncer/api-parser.d.ts.map +1 -1
  45. package/dist/syncer/api-parser.js +7 -2
  46. package/dist/syncer/file-patterns.d.ts +1 -1
  47. package/dist/syncer/file-patterns.d.ts.map +1 -1
  48. package/dist/syncer/file-patterns.js +6 -5
  49. package/dist/syncer/module-loader.d.ts +5 -0
  50. package/dist/syncer/module-loader.d.ts.map +1 -1
  51. package/dist/syncer/module-loader.js +17 -1
  52. package/dist/syncer/syncer.d.ts +5 -1
  53. package/dist/syncer/syncer.d.ts.map +1 -1
  54. package/dist/syncer/syncer.js +28 -19
  55. package/dist/tasks/decorator.d.ts +26 -0
  56. package/dist/tasks/decorator.d.ts.map +1 -0
  57. package/dist/tasks/decorator.js +28 -0
  58. package/dist/tasks/step-wrapper.d.ts +18 -0
  59. package/dist/tasks/step-wrapper.d.ts.map +1 -0
  60. package/dist/tasks/step-wrapper.js +38 -0
  61. package/dist/tasks/workflow-manager.d.ts +40 -0
  62. package/dist/tasks/workflow-manager.d.ts.map +1 -0
  63. package/dist/tasks/workflow-manager.js +193 -0
  64. package/dist/template/implementations/generated.template.d.ts.map +1 -1
  65. package/dist/template/implementations/generated.template.js +7 -3
  66. package/dist/types/types.d.ts +26 -10
  67. package/dist/types/types.d.ts.map +1 -1
  68. package/dist/types/types.js +15 -2
  69. package/dist/ui/ai-api.d.ts +1 -0
  70. package/dist/ui/ai-api.d.ts.map +1 -0
  71. package/dist/ui/ai-api.js +50 -0
  72. package/dist/ui/ai-client.d.ts +1 -0
  73. package/dist/ui/ai-client.d.ts.map +1 -0
  74. package/dist/ui/ai-client.js +438 -0
  75. package/dist/ui/api.d.ts +3 -0
  76. package/dist/ui/api.d.ts.map +1 -0
  77. package/dist/ui/api.js +680 -0
  78. package/dist/ui-web/assets/brand-icons-Cu_C0hZ4.svg +1008 -0
  79. package/dist/ui-web/assets/brand-icons-F3SPCeH1.woff +0 -0
  80. package/dist/ui-web/assets/brand-icons-XL9sxUpA.woff2 +0 -0
  81. package/dist/ui-web/assets/brand-icons-sqJ2Pg7a.eot +0 -0
  82. package/dist/ui-web/assets/brand-icons-ubhWoxly.ttf +0 -0
  83. package/dist/ui-web/assets/flags-DOLqOU7Y.png +0 -0
  84. package/dist/ui-web/assets/icons-BOCtAERH.woff +0 -0
  85. package/dist/ui-web/assets/icons-CHzK1VD9.eot +0 -0
  86. package/dist/ui-web/assets/icons-D29ZQHHw.ttf +0 -0
  87. package/dist/ui-web/assets/icons-Du6TOHnR.woff2 +0 -0
  88. package/dist/ui-web/assets/icons-RwhydX30.svg +1518 -0
  89. package/dist/ui-web/assets/index-CpaB9P6g.css +1 -0
  90. package/dist/ui-web/assets/index-J9MCfjCd.js +95 -0
  91. package/dist/ui-web/assets/outline-icons-BfdLr8tr.svg +366 -0
  92. package/dist/ui-web/assets/outline-icons-DD8jm0uy.ttf +0 -0
  93. package/dist/ui-web/assets/outline-icons-DInHoiqI.woff2 +0 -0
  94. package/dist/ui-web/assets/outline-icons-LX8adJ4n.eot +0 -0
  95. package/dist/ui-web/assets/outline-icons-aQ88nltS.woff +0 -0
  96. package/dist/ui-web/assets/provider-utils_false-BKJD46kk.js +1 -0
  97. package/dist/ui-web/assets/provider-utils_false-Bu5lmX18.js +1 -0
  98. package/dist/ui-web/index.html +13 -0
  99. package/dist/ui-web/vite.svg +1 -0
  100. package/dist/utils/formatter.d.ts.map +1 -1
  101. package/dist/utils/formatter.js +10 -2
  102. package/dist/utils/model.d.ts +9 -2
  103. package/dist/utils/model.d.ts.map +1 -1
  104. package/dist/utils/model.js +16 -1
  105. package/dist/utils/type-utils.d.ts.map +1 -1
  106. package/dist/utils/type-utils.js +3 -1
  107. package/dist/vector/embedding.d.ts +2 -5
  108. package/dist/vector/embedding.d.ts.map +1 -1
  109. package/dist/vector/embedding.js +9 -13
  110. package/dist/vector/types.d.ts.map +1 -1
  111. package/dist/vector/types.js +1 -1
  112. package/package.json +9 -5
  113. package/src/api/config.ts +15 -11
  114. package/src/api/sonamu.ts +60 -6
  115. package/src/bin/cli.ts +57 -119
  116. package/src/database/base-model.ts +21 -128
  117. package/src/database/base-model.types.ts +3 -4
  118. package/src/database/db.ts +28 -18
  119. package/src/database/puri-subset.test-d.ts +1 -0
  120. package/src/database/puri-subset.types.ts +2 -0
  121. package/src/database/puri.ts +238 -27
  122. package/src/database/puri.types.test-d.ts +1 -1
  123. package/src/database/puri.types.ts +49 -6
  124. package/src/entity/entity-manager.ts +9 -0
  125. package/src/index.ts +1 -1
  126. package/src/migration/code-generation.ts +40 -1
  127. package/src/migration/postgresql-schema-reader.ts +53 -22
  128. package/src/naite/messaging-types.ts +43 -44
  129. package/src/naite/naite.ts +1 -1
  130. package/src/shared/app.shared.ts.txt +13 -0
  131. package/src/shared/web.shared.ts.txt +13 -0
  132. package/src/stream/sse.ts +15 -3
  133. package/src/syncer/api-parser.ts +6 -1
  134. package/src/syncer/file-patterns.ts +11 -9
  135. package/src/syncer/module-loader.ts +35 -0
  136. package/src/syncer/syncer.ts +34 -21
  137. package/src/tasks/decorator.ts +71 -0
  138. package/src/tasks/step-wrapper.ts +84 -0
  139. package/src/tasks/workflow-manager.ts +330 -0
  140. package/src/template/implementations/generated.template.ts +19 -6
  141. package/src/types/types.ts +20 -4
  142. package/src/ui/ai-api.ts +60 -0
  143. package/src/ui/ai-client.ts +499 -0
  144. package/src/ui/api.ts +786 -0
  145. package/src/utils/formatter.ts +8 -1
  146. package/src/utils/model.ts +26 -2
  147. package/src/utils/type-utils.ts +2 -0
  148. package/src/vector/embedding.ts +10 -14
  149. package/src/vector/types.ts +1 -2
  150. package/dist/vector/vector-search.d.ts +0 -47
  151. package/dist/vector/vector-search.d.ts.map +0 -1
  152. package/dist/vector/vector-search.js +0 -176
  153. package/src/vector/vector-search.ts +0 -261
@@ -105,5 +105,12 @@ export function formatCode(code: string, parser: "typescript" | "json", filePath
105
105
  }
106
106
  Naite.t("formatCode:linted", linted);
107
107
 
108
- return linted.content;
108
+ // 포맷팅 한 번 더 (import 구문에 type 키워드 추가되는 경우 maxWidth 초과로 인한 에러 발생)
109
+ const formattedAgain = biome.formatContent(projectKey, linted.content, { filePath });
110
+ if (formattedAgain.diagnostics.filter((d) => d.severity === "error").length > 0) {
111
+ console.error(formattedAgain.diagnostics);
112
+ throw new Error("Biome format error");
113
+ }
114
+
115
+ return formattedAgain.content;
109
116
  }
@@ -1,13 +1,18 @@
1
1
  import type { SonamuQueryMode } from "..";
2
2
 
3
+ // semanticQuery가 있으면 similarity를 추가하는 조건부 타입
4
+ type WithSimilarity<LP, T> = LP extends { semanticQuery: Record<string, unknown> }
5
+ ? T & { similarity: number }
6
+ : T;
7
+
3
8
  export type ListResult<
4
9
  LP extends { queryMode?: SonamuQueryMode },
5
10
  T,
6
11
  > = LP["queryMode"] extends "list"
7
- ? { rows: T[] }
12
+ ? { rows: WithSimilarity<LP, T>[] }
8
13
  : LP["queryMode"] extends "count"
9
14
  ? { total: number }
10
- : { rows: T[]; total: number };
15
+ : { rows: WithSimilarity<LP, T>[]; total: number };
11
16
 
12
17
  export type ArrayOr<T> = T | T[];
13
18
 
@@ -34,4 +39,23 @@ export interface BaseListParams {
34
39
  page?: number;
35
40
  keyword?: string;
36
41
  queryMode?: "list" | "count" | "both";
42
+ semanticQuery?: Record<string, unknown>;
37
43
  }
44
+
45
+ // const a: ListResult<{ queryMode: "list"; semanticQuery: {} }, { id: number; name: string }> = {
46
+ // rows: [{ id: 1, name: "test", similarity: 0.5 }],
47
+ // };
48
+ // a.rows[0].similarity;
49
+
50
+ // // const b: ListResult<{ queryMode: "count" }, { id: number; name: string }> = {
51
+ // // total: 1,
52
+ // // };
53
+
54
+ // const c: ListResult<
55
+ // { queryMode: "both"; semanticQuery: { embedding: number[] } },
56
+ // { id: number; name: string }
57
+ // > = {
58
+ // rows: [{ id: 1, name: "test", similarity: 0.5 }],
59
+ // total: 1,
60
+ // };
61
+ // c.rows[0].similarity;
@@ -31,6 +31,7 @@ export function withProp<T extends object, P extends string, V>(
31
31
  if (keys.length === 0) throw new Error("Path cannot be empty");
32
32
  const result = structuredClone(obj);
33
33
 
34
+ // biome-ignore lint/suspicious/noExplicitAny: 범용 배열 요소 타입
34
35
  const setDeep = (current: any, keys: string[], value: V): void => {
35
36
  if (keys.length === 0) return;
36
37
  const [key, ...rest] = keys;
@@ -48,6 +49,7 @@ export function withProp<T extends object, P extends string, V>(
48
49
  current[key] = {};
49
50
  }
50
51
  if (Array.isArray(current[key])) {
52
+ // biome-ignore lint/suspicious/noExplicitAny: 범용 배열 요소 타입
51
53
  current[key].forEach((item: any) => {
52
54
  setDeep(item, rest, value);
53
55
  });
@@ -1,6 +1,6 @@
1
- import { createOpenAI, type OpenAIProvider } from "@ai-sdk/openai";
1
+ import type { OpenAIProvider } from "@ai-sdk/openai";
2
2
  import { type EmbeddingModel, embedMany } from "ai";
3
- import { VoyageAIClient } from "voyageai";
3
+ import type { VoyageAIClient } from "voyageai";
4
4
  import { Sonamu } from "../api/sonamu";
5
5
  import { DEFAULT_VECTOR_CONFIG } from "./config";
6
6
  import type {
@@ -15,7 +15,7 @@ import type {
15
15
  * 임베딩 클라이언트
16
16
  * Voyage AI와 OpenAI 임베딩을 SDK 방식으로 통합 지원
17
17
  */
18
- export class Embedding {
18
+ export class EmbeddingClass {
19
19
  private config: VectorConfig;
20
20
 
21
21
  constructor(config: Partial<VectorConfig> = {}) {
@@ -31,7 +31,8 @@ export class Embedding {
31
31
  /**
32
32
  * Voyage AI 클라이언트 초기화
33
33
  */
34
- private getVoyageClient(): VoyageAIClient {
34
+ private async getVoyageClient(): Promise<VoyageAIClient> {
35
+ const { VoyageAIClient } = await import("voyageai");
35
36
  const apiKey = Sonamu.secrets?.voyage_api_key ?? process.env.VOYAGE_API_KEY;
36
37
  if (!apiKey) {
37
38
  throw new Error("VOYAGE_API_KEY가 설정되지 않았습니다. 환경변수를 확인하세요.");
@@ -42,7 +43,8 @@ export class Embedding {
42
43
  /**
43
44
  * OpenAI provider 생성
44
45
  */
45
- private getOpenAIProvider(): OpenAIProvider {
46
+ private async getOpenAIProvider(): Promise<OpenAIProvider> {
47
+ const { createOpenAI } = await import("@ai-sdk/openai");
46
48
  const apiKey = Sonamu.secrets?.openai_api_key ?? process.env.OPENAI_API_KEY;
47
49
  if (!apiKey) {
48
50
  throw new Error("OPENAI_API_KEY가 설정되지 않았습니다. 환경변수를 확인하세요.");
@@ -107,7 +109,7 @@ export class Embedding {
107
109
  texts: string[],
108
110
  inputType: VectorInputType,
109
111
  ): Promise<EmbeddingResult[]> {
110
- const client = this.getVoyageClient();
112
+ const client = await this.getVoyageClient();
111
113
  const voyageConfig = this.config.voyage;
112
114
 
113
115
  const response = await client.embed({
@@ -130,7 +132,7 @@ export class Embedding {
130
132
  * OpenAI 임베딩
131
133
  */
132
134
  private async embedOpenAI(texts: string[]): Promise<EmbeddingResult[]> {
133
- const openai = this.getOpenAIProvider();
135
+ const openai = await this.getOpenAIProvider();
134
136
  const openaiConfig = this.config.openai;
135
137
  const model = openai.embeddingModel(openaiConfig.model);
136
138
 
@@ -146,13 +148,6 @@ export class Embedding {
146
148
  }));
147
149
  }
148
150
 
149
- /**
150
- * 벡터를 PostgreSQL vector 타입 문자열로 변환
151
- */
152
- static toVectorString(embedding: number[]): string {
153
- return `[${embedding.join(",")}]`;
154
- }
155
-
156
151
  /**
157
152
  * 임베딩 provider의 차원 수 반환
158
153
  */
@@ -160,3 +155,4 @@ export class Embedding {
160
155
  return provider === "voyage" ? this.config.voyage.dimensions : this.config.openai.dimensions;
161
156
  }
162
157
  }
158
+ export const Embedding = new EmbeddingClass();
@@ -31,8 +31,7 @@ export interface VectorSearchResult<T = Record<string, unknown>> {
31
31
  }
32
32
 
33
33
  /** 하이브리드 검색 결과 (Vector + FTS) */
34
- export interface HybridSearchResult<T = Record<string, unknown>>
35
- extends VectorSearchResult<T> {
34
+ export interface HybridSearchResult<T = Record<string, unknown>> extends VectorSearchResult<T> {
36
35
  vectorScore?: number;
37
36
  ftsScore?: number;
38
37
  }
@@ -1,47 +0,0 @@
1
- import type { Knex } from "knex";
2
- import { Embedding } from "./embedding";
3
- import type { EmbeddingItem, EmbeddingProvider, HybridSearchOptions, HybridSearchResult, ProgressCallback, VectorConfig, VectorSearchOptions, VectorSearchResult } from "./types";
4
- /**
5
- * 벡터 검색
6
- * pgvector를 활용한 벡터 검색 및 하이브리드 검색 지원
7
- */
8
- export declare class VectorSearch<T = Record<string, unknown>> {
9
- private db;
10
- private config;
11
- private embedding;
12
- private tableName;
13
- constructor(db: Knex, tableName: string, config?: Partial<VectorConfig>);
14
- /**
15
- * 단일 항목에 임베딩 저장
16
- */
17
- saveEmbedding(id: number, text: string, provider: EmbeddingProvider, embeddingColumn?: string): Promise<void>;
18
- /**
19
- * 여러 항목에 임베딩 일괄 저장
20
- */
21
- saveEmbeddingsBatch(items: EmbeddingItem[], provider: EmbeddingProvider, embeddingColumn?: string, onProgress?: ProgressCallback): Promise<void>;
22
- /**
23
- * 벡터 검색 (코사인 유사도)
24
- */
25
- search(query: string, provider: EmbeddingProvider, options?: VectorSearchOptions): Promise<VectorSearchResult<T>[]>;
26
- /**
27
- * 하이브리드 검색 (Vector + FTS)
28
- */
29
- hybridSearch(query: string, provider: EmbeddingProvider, options?: HybridSearchOptions): Promise<HybridSearchResult<T>[]>;
30
- /**
31
- * 임베딩 현황 조회
32
- */
33
- getEmbeddingStatus(embeddingColumn?: string): Promise<{
34
- total: number;
35
- withEmbedding: number;
36
- withoutEmbedding: number;
37
- }>;
38
- /**
39
- * 임베딩이 없는 항목 ID 조회
40
- */
41
- getItemsWithoutEmbedding(embeddingColumn?: string, limit?: number): Promise<number[]>;
42
- /**
43
- * Embedding 인스턴스 반환 (고급 사용)
44
- */
45
- getEmbedding(): Embedding;
46
- }
47
- //# sourceMappingURL=vector-search.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"vector-search.d.ts","sourceRoot":"","sources":["../../src/vector/vector-search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAGjC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EACnB,MAAM,SAAS,CAAC;AAEjB;;;GAGG;AACH,qBAAa,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACnD,OAAO,CAAC,EAAE,CAAO;IACjB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,SAAS,CAAS;gBAEd,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,CAAC,YAAY,CAAM;IAa3E;;OAEG;IACG,aAAa,CACjB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,iBAAiB,EAC3B,eAAe,GAAE,MAA4B,GAC5C,OAAO,CAAC,IAAI,CAAC;IAUhB;;OAEG;IACG,mBAAmB,CACvB,KAAK,EAAE,aAAa,EAAE,EACtB,QAAQ,EAAE,iBAAiB,EAC3B,eAAe,GAAE,MAA4B,EAC7C,UAAU,CAAC,EAAE,gBAAgB,GAC5B,OAAO,CAAC,IAAI,CAAC;IAehB;;OAEG;IACG,MAAM,CACV,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC;IAyCnC;;OAEG;IACG,YAAY,CAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,iBAAiB,EAC3B,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,CAAC;IAiFnC;;OAEG;IACG,kBAAkB,CAAC,eAAe,GAAE,MAA4B,GAAG,OAAO,CAAC;QAC/E,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,CAAC;QACtB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;IAgBF;;OAEG;IACG,wBAAwB,CAC5B,eAAe,GAAE,MAA4B,EAC7C,KAAK,GAAE,MAAY,GAClB,OAAO,CAAC,MAAM,EAAE,CAAC;IAUpB;;OAEG;IACH,YAAY,IAAI,SAAS;CAG1B"}
@@ -1,176 +0,0 @@
1
- import pgvector from "pgvector/knex";
2
- import { DEFAULT_VECTOR_CONFIG } from "./config.js";
3
- import { Embedding } from "./embedding.js";
4
- /**
5
- * 벡터 검색
6
- * pgvector를 활용한 벡터 검색 및 하이브리드 검색 지원
7
- */ export class VectorSearch {
8
- db;
9
- config;
10
- embedding;
11
- tableName;
12
- constructor(db, tableName, config = {}){
13
- this.db = db;
14
- this.tableName = tableName;
15
- this.config = {
16
- voyage: {
17
- ...DEFAULT_VECTOR_CONFIG.voyage,
18
- ...config.voyage
19
- },
20
- openai: {
21
- ...DEFAULT_VECTOR_CONFIG.openai,
22
- ...config.openai
23
- },
24
- chunking: {
25
- ...DEFAULT_VECTOR_CONFIG.chunking,
26
- ...config.chunking
27
- },
28
- search: {
29
- ...DEFAULT_VECTOR_CONFIG.search,
30
- ...config.search
31
- },
32
- pgvector: {
33
- ...DEFAULT_VECTOR_CONFIG.pgvector,
34
- ...config.pgvector
35
- }
36
- };
37
- this.embedding = new Embedding(config);
38
- }
39
- /**
40
- * 단일 항목에 임베딩 저장
41
- */ async saveEmbedding(id, text, provider, embeddingColumn = "content_embedding") {
42
- const { embedding } = await this.embedding.embedOne(text, provider, "document");
43
- await this.db(this.tableName).where("id", id).update({
44
- [embeddingColumn]: pgvector.toSql(embedding)
45
- });
46
- }
47
- /**
48
- * 여러 항목에 임베딩 일괄 저장
49
- */ async saveEmbeddingsBatch(items, provider, embeddingColumn = "content_embedding", onProgress) {
50
- const texts = items.map((item)=>item.text);
51
- const embeddings = await this.embedding.embed(texts, provider, "document", onProgress);
52
- await this.db.transaction(async (trx)=>{
53
- for(let i = 0; i < items.length; i++){
54
- await trx(this.tableName).where("id", items[i].id).update({
55
- [embeddingColumn]: pgvector.toSql(embeddings[i].embedding)
56
- });
57
- }
58
- });
59
- }
60
- /**
61
- * 벡터 검색 (코사인 유사도)
62
- */ async search(query, provider, options = {}) {
63
- const { embeddingColumn = "content_embedding", limit = this.config.search.defaultLimit, threshold = this.config.search.similarityThreshold, where } = options;
64
- // 쿼리 임베딩 (input_type: 'query' 중요!)
65
- const { embedding } = await this.embedding.embedOne(query, provider, "query");
66
- // pgvector 세션 설정
67
- if (this.config.pgvector.iterativeScan) {
68
- await this.db.raw("SET hnsw.iterative_scan = relaxed_order");
69
- }
70
- await this.db.raw(`SET hnsw.ef_search = ${this.config.pgvector.efSearch}`);
71
- // 코사인 유사도 = 1 - 코사인 거리
72
- const vectorStr = pgvector.toSql(embedding);
73
- let queryBuilder = this.db(this.tableName).select("*").select(this.db.raw(`1 - (${embeddingColumn} <=> ?::vector) AS similarity`, [
74
- vectorStr
75
- ])).whereNotNull(embeddingColumn).orderByRaw(`${embeddingColumn} <=> ?::vector`, [
76
- vectorStr
77
- ]).limit(limit);
78
- if (where) {
79
- queryBuilder = queryBuilder.whereRaw(where);
80
- }
81
- const rows = await queryBuilder;
82
- return rows.filter((row)=>row.similarity >= threshold).map((row)=>({
83
- id: row.id,
84
- similarity: parseFloat(String(row.similarity)),
85
- data: row
86
- }));
87
- }
88
- /**
89
- * 하이브리드 검색 (Vector + FTS)
90
- */ async hybridSearch(query, provider, options = {}) {
91
- const { embeddingColumn = "content_embedding", ftsColumn = "content_tsv", limit = this.config.search.defaultLimit, vectorWeight = this.config.search.vectorWeight, ftsWeight = this.config.search.ftsWeight } = options;
92
- const { embedding } = await this.embedding.embedOne(query, provider, "query");
93
- const vectorStr = pgvector.toSql(embedding);
94
- // pgvector 세션 설정
95
- if (this.config.pgvector.iterativeScan) {
96
- await this.db.raw("SET hnsw.iterative_scan = relaxed_order");
97
- }
98
- await this.db.raw(`SET hnsw.ef_search = ${this.config.pgvector.efSearch}`);
99
- const sql = `
100
- WITH vector_search AS (
101
- SELECT
102
- id,
103
- ROW_NUMBER() OVER (ORDER BY ${embeddingColumn} <=> ?::vector) AS rank
104
- FROM ${this.tableName}
105
- WHERE ${embeddingColumn} IS NOT NULL
106
- ORDER BY ${embeddingColumn} <=> ?::vector
107
- LIMIT 50
108
- ),
109
- fts_search AS (
110
- SELECT
111
- id,
112
- ROW_NUMBER() OVER (ORDER BY ts_rank(${ftsColumn}, query) DESC) AS rank
113
- FROM ${this.tableName}, plainto_tsquery('simple', ?) query
114
- WHERE ${ftsColumn} @@ query
115
- LIMIT 50
116
- ),
117
- combined AS (
118
- SELECT
119
- COALESCE(v.id, f.id) AS id,
120
- COALESCE(1.0 / (60 + v.rank), 0) AS vector_score,
121
- COALESCE(1.0 / (60 + f.rank), 0) AS fts_score
122
- FROM vector_search v
123
- FULL OUTER JOIN fts_search f ON v.id = f.id
124
- )
125
- SELECT
126
- t.*,
127
- c.vector_score,
128
- c.fts_score,
129
- (c.vector_score * ? + c.fts_score * ?) AS similarity
130
- FROM combined c
131
- JOIN ${this.tableName} t ON c.id = t.id
132
- ORDER BY similarity DESC
133
- LIMIT ?
134
- `;
135
- const { rows } = await this.db.raw(sql, [
136
- vectorStr,
137
- vectorStr,
138
- query,
139
- vectorWeight,
140
- ftsWeight,
141
- limit
142
- ]);
143
- return rows.map((row)=>({
144
- id: row.id,
145
- similarity: parseFloat(String(row.similarity)),
146
- vectorScore: parseFloat(String(row.vector_score)),
147
- ftsScore: parseFloat(String(row.fts_score)),
148
- data: row
149
- }));
150
- }
151
- /**
152
- * 임베딩 현황 조회
153
- */ async getEmbeddingStatus(embeddingColumn = "content_embedding") {
154
- const result = await this.db(this.tableName).count("* as total").count(`${embeddingColumn} as with_embedding`).first();
155
- const total = parseInt(String(result?.total ?? 0), 10);
156
- const withEmbedding = parseInt(String(result?.with_embedding ?? 0), 10);
157
- return {
158
- total,
159
- withEmbedding,
160
- withoutEmbedding: total - withEmbedding
161
- };
162
- }
163
- /**
164
- * 임베딩이 없는 항목 ID 조회
165
- */ async getItemsWithoutEmbedding(embeddingColumn = "content_embedding", limit = 100) {
166
- const rows = await this.db(this.tableName).select("id").whereNull(embeddingColumn).orderBy("id").limit(limit);
167
- return rows.map((row)=>row.id);
168
- }
169
- /**
170
- * Embedding 인스턴스 반환 (고급 사용)
171
- */ getEmbedding() {
172
- return this.embedding;
173
- }
174
- }
175
-
176
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy92ZWN0b3IvdmVjdG9yLXNlYXJjaC50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgdHlwZSB7IEtuZXggfSBmcm9tIFwia25leFwiO1xuaW1wb3J0IHBndmVjdG9yIGZyb20gXCJwZ3ZlY3Rvci9rbmV4XCI7XG5pbXBvcnQgeyBERUZBVUxUX1ZFQ1RPUl9DT05GSUcgfSBmcm9tIFwiLi9jb25maWdcIjtcbmltcG9ydCB7IEVtYmVkZGluZyB9IGZyb20gXCIuL2VtYmVkZGluZ1wiO1xuaW1wb3J0IHR5cGUge1xuICBFbWJlZGRpbmdJdGVtLFxuICBFbWJlZGRpbmdQcm92aWRlcixcbiAgSHlicmlkU2VhcmNoT3B0aW9ucyxcbiAgSHlicmlkU2VhcmNoUmVzdWx0LFxuICBQcm9ncmVzc0NhbGxiYWNrLFxuICBWZWN0b3JDb25maWcsXG4gIFZlY3RvclNlYXJjaE9wdGlvbnMsXG4gIFZlY3RvclNlYXJjaFJlc3VsdCxcbn0gZnJvbSBcIi4vdHlwZXNcIjtcblxuLyoqXG4gKiDrsqHthLAg6rKA7IOJXG4gKiBwZ3ZlY3RvcuulvCDtmZzsmqntlZwg67Kh7YSwIOqygOyDiSDrsI8g7ZWY7J2067iM66as65OcIOqygOyDiSDsp4Dsm5BcbiAqL1xuZXhwb3J0IGNsYXNzIFZlY3RvclNlYXJjaDxUID0gUmVjb3JkPHN0cmluZywgdW5rbm93bj4+IHtcbiAgcHJpdmF0ZSBkYjogS25leDtcbiAgcHJpdmF0ZSBjb25maWc6IFZlY3RvckNvbmZpZztcbiAgcHJpdmF0ZSBlbWJlZGRpbmc6IEVtYmVkZGluZztcbiAgcHJpdmF0ZSB0YWJsZU5hbWU6IHN0cmluZztcblxuICBjb25zdHJ1Y3RvcihkYjogS25leCwgdGFibGVOYW1lOiBzdHJpbmcsIGNvbmZpZzogUGFydGlhbDxWZWN0b3JDb25maWc+ID0ge30pIHtcbiAgICB0aGlzLmRiID0gZGI7XG4gICAgdGhpcy50YWJsZU5hbWUgPSB0YWJsZU5hbWU7XG4gICAgdGhpcy5jb25maWcgPSB7XG4gICAgICB2b3lhZ2U6IHsgLi4uREVGQVVMVF9WRUNUT1JfQ09ORklHLnZveWFnZSwgLi4uY29uZmlnLnZveWFnZSB9LFxuICAgICAgb3BlbmFpOiB7IC4uLkRFRkFVTFRfVkVDVE9SX0NPTkZJRy5vcGVuYWksIC4uLmNvbmZpZy5vcGVuYWkgfSxcbiAgICAgIGNodW5raW5nOiB7IC4uLkRFRkFVTFRfVkVDVE9SX0NPTkZJRy5jaHVua2luZywgLi4uY29uZmlnLmNodW5raW5nIH0sXG4gICAgICBzZWFyY2g6IHsgLi4uREVGQVVMVF9WRUNUT1JfQ09ORklHLnNlYXJjaCwgLi4uY29uZmlnLnNlYXJjaCB9LFxuICAgICAgcGd2ZWN0b3I6IHsgLi4uREVGQVVMVF9WRUNUT1JfQ09ORklHLnBndmVjdG9yLCAuLi5jb25maWcucGd2ZWN0b3IgfSxcbiAgICB9O1xuICAgIHRoaXMuZW1iZWRkaW5nID0gbmV3IEVtYmVkZGluZyhjb25maWcpO1xuICB9XG5cbiAgLyoqXG4gICAqIOuLqOydvCDtla3rqqnsl5Ag7J6E67Kg65SpIOyggOyepVxuICAgKi9cbiAgYXN5bmMgc2F2ZUVtYmVkZGluZyhcbiAgICBpZDogbnVtYmVyLFxuICAgIHRleHQ6IHN0cmluZyxcbiAgICBwcm92aWRlcjogRW1iZWRkaW5nUHJvdmlkZXIsXG4gICAgZW1iZWRkaW5nQ29sdW1uOiBzdHJpbmcgPSBcImNvbnRlbnRfZW1iZWRkaW5nXCIsXG4gICk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IHsgZW1iZWRkaW5nIH0gPSBhd2FpdCB0aGlzLmVtYmVkZGluZy5lbWJlZE9uZSh0ZXh0LCBwcm92aWRlciwgXCJkb2N1bWVudFwiKTtcblxuICAgIGF3YWl0IHRoaXMuZGIodGhpcy50YWJsZU5hbWUpXG4gICAgICAud2hlcmUoXCJpZFwiLCBpZClcbiAgICAgIC51cGRhdGUoe1xuICAgICAgICBbZW1iZWRkaW5nQ29sdW1uXTogcGd2ZWN0b3IudG9TcWwoZW1iZWRkaW5nKSxcbiAgICAgIH0pO1xuICB9XG5cbiAgLyoqXG4gICAqIOyXrOufrCDtla3rqqnsl5Ag7J6E67Kg65SpIOydvOq0hCDsoIDsnqVcbiAgICovXG4gIGFzeW5jIHNhdmVFbWJlZGRpbmdzQmF0Y2goXG4gICAgaXRlbXM6IEVtYmVkZGluZ0l0ZW1bXSxcbiAgICBwcm92aWRlcjogRW1iZWRkaW5nUHJvdmlkZXIsXG4gICAgZW1iZWRkaW5nQ29sdW1uOiBzdHJpbmcgPSBcImNvbnRlbnRfZW1iZWRkaW5nXCIsXG4gICAgb25Qcm9ncmVzcz86IFByb2dyZXNzQ2FsbGJhY2ssXG4gICk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IHRleHRzID0gaXRlbXMubWFwKChpdGVtKSA9PiBpdGVtLnRleHQpO1xuICAgIGNvbnN0IGVtYmVkZGluZ3MgPSBhd2FpdCB0aGlzLmVtYmVkZGluZy5lbWJlZCh0ZXh0cywgcHJvdmlkZXIsIFwiZG9jdW1lbnRcIiwgb25Qcm9ncmVzcyk7XG5cbiAgICBhd2FpdCB0aGlzLmRiLnRyYW5zYWN0aW9uKGFzeW5jICh0cngpID0+IHtcbiAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgaXRlbXMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgYXdhaXQgdHJ4KHRoaXMudGFibGVOYW1lKVxuICAgICAgICAgIC53aGVyZShcImlkXCIsIGl0ZW1zW2ldLmlkKVxuICAgICAgICAgIC51cGRhdGUoe1xuICAgICAgICAgICAgW2VtYmVkZGluZ0NvbHVtbl06IHBndmVjdG9yLnRvU3FsKGVtYmVkZGluZ3NbaV0uZW1iZWRkaW5nKSxcbiAgICAgICAgICB9KTtcbiAgICAgIH1cbiAgICB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiDrsqHthLAg6rKA7IOJICjsvZTsgqzsnbgg7Jyg7IKs64+EKVxuICAgKi9cbiAgYXN5bmMgc2VhcmNoKFxuICAgIHF1ZXJ5OiBzdHJpbmcsXG4gICAgcHJvdmlkZXI6IEVtYmVkZGluZ1Byb3ZpZGVyLFxuICAgIG9wdGlvbnM6IFZlY3RvclNlYXJjaE9wdGlvbnMgPSB7fSxcbiAgKTogUHJvbWlzZTxWZWN0b3JTZWFyY2hSZXN1bHQ8VD5bXT4ge1xuICAgIGNvbnN0IHtcbiAgICAgIGVtYmVkZGluZ0NvbHVtbiA9IFwiY29udGVudF9lbWJlZGRpbmdcIixcbiAgICAgIGxpbWl0ID0gdGhpcy5jb25maWcuc2VhcmNoLmRlZmF1bHRMaW1pdCxcbiAgICAgIHRocmVzaG9sZCA9IHRoaXMuY29uZmlnLnNlYXJjaC5zaW1pbGFyaXR5VGhyZXNob2xkLFxuICAgICAgd2hlcmUsXG4gICAgfSA9IG9wdGlvbnM7XG5cbiAgICAvLyDsv7zrpqwg7J6E67Kg65SpIChpbnB1dF90eXBlOiAncXVlcnknIOykkeyalCEpXG4gICAgY29uc3QgeyBlbWJlZGRpbmcgfSA9IGF3YWl0IHRoaXMuZW1iZWRkaW5nLmVtYmVkT25lKHF1ZXJ5LCBwcm92aWRlciwgXCJxdWVyeVwiKTtcblxuICAgIC8vIHBndmVjdG9yIOyEuOyFmCDshKTsoJVcbiAgICBpZiAodGhpcy5jb25maWcucGd2ZWN0b3IuaXRlcmF0aXZlU2Nhbikge1xuICAgICAgYXdhaXQgdGhpcy5kYi5yYXcoXCJTRVQgaG5zdy5pdGVyYXRpdmVfc2NhbiA9IHJlbGF4ZWRfb3JkZXJcIik7XG4gICAgfVxuICAgIGF3YWl0IHRoaXMuZGIucmF3KGBTRVQgaG5zdy5lZl9zZWFyY2ggPSAke3RoaXMuY29uZmlnLnBndmVjdG9yLmVmU2VhcmNofWApO1xuXG4gICAgLy8g7L2U7IKs7J24IOycoOyCrOuPhCA9IDEgLSDsvZTsgqzsnbgg6rGw66asXG4gICAgY29uc3QgdmVjdG9yU3RyID0gcGd2ZWN0b3IudG9TcWwoZW1iZWRkaW5nKTtcbiAgICBsZXQgcXVlcnlCdWlsZGVyID0gdGhpcy5kYih0aGlzLnRhYmxlTmFtZSlcbiAgICAgIC5zZWxlY3QoXCIqXCIpXG4gICAgICAuc2VsZWN0KHRoaXMuZGIucmF3KGAxIC0gKCR7ZW1iZWRkaW5nQ29sdW1ufSA8PT4gPzo6dmVjdG9yKSBBUyBzaW1pbGFyaXR5YCwgW3ZlY3RvclN0cl0pKVxuICAgICAgLndoZXJlTm90TnVsbChlbWJlZGRpbmdDb2x1bW4pXG4gICAgICAub3JkZXJCeVJhdyhgJHtlbWJlZGRpbmdDb2x1bW59IDw9PiA/Ojp2ZWN0b3JgLCBbdmVjdG9yU3RyXSlcbiAgICAgIC5saW1pdChsaW1pdCk7XG5cbiAgICBpZiAod2hlcmUpIHtcbiAgICAgIHF1ZXJ5QnVpbGRlciA9IHF1ZXJ5QnVpbGRlci53aGVyZVJhdyh3aGVyZSk7XG4gICAgfVxuXG4gICAgY29uc3Qgcm93cyA9IGF3YWl0IHF1ZXJ5QnVpbGRlcjtcblxuICAgIHJldHVybiByb3dzXG4gICAgICAuZmlsdGVyKChyb3c6IHsgc2ltaWxhcml0eTogbnVtYmVyIH0pID0+IHJvdy5zaW1pbGFyaXR5ID49IHRocmVzaG9sZClcbiAgICAgIC5tYXAoKHJvdzogVCAmIHsgc2ltaWxhcml0eTogbnVtYmVyIH0pID0+ICh7XG4gICAgICAgIGlkOiAocm93IGFzIHVua25vd24gYXMgeyBpZDogbnVtYmVyIH0pLmlkLFxuICAgICAgICBzaW1pbGFyaXR5OiBwYXJzZUZsb2F0KFN0cmluZyhyb3cuc2ltaWxhcml0eSkpLFxuICAgICAgICBkYXRhOiByb3cgYXMgVCxcbiAgICAgIH0pKTtcbiAgfVxuXG4gIC8qKlxuICAgKiDtlZjsnbTruIzrpqzrk5wg6rKA7IOJIChWZWN0b3IgKyBGVFMpXG4gICAqL1xuICBhc3luYyBoeWJyaWRTZWFyY2goXG4gICAgcXVlcnk6IHN0cmluZyxcbiAgICBwcm92aWRlcjogRW1iZWRkaW5nUHJvdmlkZXIsXG4gICAgb3B0aW9uczogSHlicmlkU2VhcmNoT3B0aW9ucyA9IHt9LFxuICApOiBQcm9taXNlPEh5YnJpZFNlYXJjaFJlc3VsdDxUPltdPiB7XG4gICAgY29uc3Qge1xuICAgICAgZW1iZWRkaW5nQ29sdW1uID0gXCJjb250ZW50X2VtYmVkZGluZ1wiLFxuICAgICAgZnRzQ29sdW1uID0gXCJjb250ZW50X3RzdlwiLFxuICAgICAgbGltaXQgPSB0aGlzLmNvbmZpZy5zZWFyY2guZGVmYXVsdExpbWl0LFxuICAgICAgdmVjdG9yV2VpZ2h0ID0gdGhpcy5jb25maWcuc2VhcmNoLnZlY3RvcldlaWdodCxcbiAgICAgIGZ0c1dlaWdodCA9IHRoaXMuY29uZmlnLnNlYXJjaC5mdHNXZWlnaHQsXG4gICAgfSA9IG9wdGlvbnM7XG5cbiAgICBjb25zdCB7IGVtYmVkZGluZyB9ID0gYXdhaXQgdGhpcy5lbWJlZGRpbmcuZW1iZWRPbmUocXVlcnksIHByb3ZpZGVyLCBcInF1ZXJ5XCIpO1xuICAgIGNvbnN0IHZlY3RvclN0ciA9IHBndmVjdG9yLnRvU3FsKGVtYmVkZGluZyk7XG5cbiAgICAvLyBwZ3ZlY3RvciDshLjshZgg7ISk7KCVXG4gICAgaWYgKHRoaXMuY29uZmlnLnBndmVjdG9yLml0ZXJhdGl2ZVNjYW4pIHtcbiAgICAgIGF3YWl0IHRoaXMuZGIucmF3KFwiU0VUIGhuc3cuaXRlcmF0aXZlX3NjYW4gPSByZWxheGVkX29yZGVyXCIpO1xuICAgIH1cbiAgICBhd2FpdCB0aGlzLmRiLnJhdyhgU0VUIGhuc3cuZWZfc2VhcmNoID0gJHt0aGlzLmNvbmZpZy5wZ3ZlY3Rvci5lZlNlYXJjaH1gKTtcblxuICAgIGNvbnN0IHNxbCA9IGBcbiAgICAgIFdJVEggdmVjdG9yX3NlYXJjaCBBUyAoXG4gICAgICAgIFNFTEVDVFxuICAgICAgICAgIGlkLFxuICAgICAgICAgIFJPV19OVU1CRVIoKSBPVkVSIChPUkRFUiBCWSAke2VtYmVkZGluZ0NvbHVtbn0gPD0+ID86OnZlY3RvcikgQVMgcmFua1xuICAgICAgICBGUk9NICR7dGhpcy50YWJsZU5hbWV9XG4gICAgICAgIFdIRVJFICR7ZW1iZWRkaW5nQ29sdW1ufSBJUyBOT1QgTlVMTFxuICAgICAgICBPUkRFUiBCWSAke2VtYmVkZGluZ0NvbHVtbn0gPD0+ID86OnZlY3RvclxuICAgICAgICBMSU1JVCA1MFxuICAgICAgKSxcbiAgICAgIGZ0c19zZWFyY2ggQVMgKFxuICAgICAgICBTRUxFQ1RcbiAgICAgICAgICBpZCxcbiAgICAgICAgICBST1dfTlVNQkVSKCkgT1ZFUiAoT1JERVIgQlkgdHNfcmFuaygke2Z0c0NvbHVtbn0sIHF1ZXJ5KSBERVNDKSBBUyByYW5rXG4gICAgICAgIEZST00gJHt0aGlzLnRhYmxlTmFtZX0sIHBsYWludG9fdHNxdWVyeSgnc2ltcGxlJywgPykgcXVlcnlcbiAgICAgICAgV0hFUkUgJHtmdHNDb2x1bW59IEBAIHF1ZXJ5XG4gICAgICAgIExJTUlUIDUwXG4gICAgICApLFxuICAgICAgY29tYmluZWQgQVMgKFxuICAgICAgICBTRUxFQ1RcbiAgICAgICAgICBDT0FMRVNDRSh2LmlkLCBmLmlkKSBBUyBpZCxcbiAgICAgICAgICBDT0FMRVNDRSgxLjAgLyAoNjAgKyB2LnJhbmspLCAwKSBBUyB2ZWN0b3Jfc2NvcmUsXG4gICAgICAgICAgQ09BTEVTQ0UoMS4wIC8gKDYwICsgZi5yYW5rKSwgMCkgQVMgZnRzX3Njb3JlXG4gICAgICAgIEZST00gdmVjdG9yX3NlYXJjaCB2XG4gICAgICAgIEZVTEwgT1VURVIgSk9JTiBmdHNfc2VhcmNoIGYgT04gdi5pZCA9IGYuaWRcbiAgICAgIClcbiAgICAgIFNFTEVDVFxuICAgICAgICB0LiosXG4gICAgICAgIGMudmVjdG9yX3Njb3JlLFxuICAgICAgICBjLmZ0c19zY29yZSxcbiAgICAgICAgKGMudmVjdG9yX3Njb3JlICogPyArIGMuZnRzX3Njb3JlICogPykgQVMgc2ltaWxhcml0eVxuICAgICAgRlJPTSBjb21iaW5lZCBjXG4gICAgICBKT0lOICR7dGhpcy50YWJsZU5hbWV9IHQgT04gYy5pZCA9IHQuaWRcbiAgICAgIE9SREVSIEJZIHNpbWlsYXJpdHkgREVTQ1xuICAgICAgTElNSVQgP1xuICAgIGA7XG5cbiAgICBjb25zdCB7IHJvd3MgfSA9IGF3YWl0IHRoaXMuZGIucmF3KHNxbCwgW1xuICAgICAgdmVjdG9yU3RyLFxuICAgICAgdmVjdG9yU3RyLFxuICAgICAgcXVlcnksXG4gICAgICB2ZWN0b3JXZWlnaHQsXG4gICAgICBmdHNXZWlnaHQsXG4gICAgICBsaW1pdCxcbiAgICBdKTtcblxuICAgIHJldHVybiByb3dzLm1hcChcbiAgICAgIChcbiAgICAgICAgcm93OiBUICYge1xuICAgICAgICAgIHNpbWlsYXJpdHk6IG51bWJlcjtcbiAgICAgICAgICB2ZWN0b3Jfc2NvcmU6IG51bWJlcjtcbiAgICAgICAgICBmdHNfc2NvcmU6IG51bWJlcjtcbiAgICAgICAgfSxcbiAgICAgICkgPT4gKHtcbiAgICAgICAgaWQ6IChyb3cgYXMgdW5rbm93biBhcyB7IGlkOiBudW1iZXIgfSkuaWQsXG4gICAgICAgIHNpbWlsYXJpdHk6IHBhcnNlRmxvYXQoU3RyaW5nKHJvdy5zaW1pbGFyaXR5KSksXG4gICAgICAgIHZlY3RvclNjb3JlOiBwYXJzZUZsb2F0KFN0cmluZyhyb3cudmVjdG9yX3Njb3JlKSksXG4gICAgICAgIGZ0c1Njb3JlOiBwYXJzZUZsb2F0KFN0cmluZyhyb3cuZnRzX3Njb3JlKSksXG4gICAgICAgIGRhdGE6IHJvdyBhcyBULFxuICAgICAgfSksXG4gICAgKTtcbiAgfVxuXG4gIC8qKlxuICAgKiDsnoTrsqDrlKkg7ZiE7ZmpIOyhsO2ajFxuICAgKi9cbiAgYXN5bmMgZ2V0RW1iZWRkaW5nU3RhdHVzKGVtYmVkZGluZ0NvbHVtbjogc3RyaW5nID0gXCJjb250ZW50X2VtYmVkZGluZ1wiKTogUHJvbWlzZTx7XG4gICAgdG90YWw6IG51bWJlcjtcbiAgICB3aXRoRW1iZWRkaW5nOiBudW1iZXI7XG4gICAgd2l0aG91dEVtYmVkZGluZzogbnVtYmVyO1xuICB9PiB7XG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdGhpcy5kYih0aGlzLnRhYmxlTmFtZSlcbiAgICAgIC5jb3VudChcIiogYXMgdG90YWxcIilcbiAgICAgIC5jb3VudChgJHtlbWJlZGRpbmdDb2x1bW59IGFzIHdpdGhfZW1iZWRkaW5nYClcbiAgICAgIC5maXJzdCgpO1xuXG4gICAgY29uc3QgdG90YWwgPSBwYXJzZUludChTdHJpbmcocmVzdWx0Py50b3RhbCA/PyAwKSwgMTApO1xuICAgIGNvbnN0IHdpdGhFbWJlZGRpbmcgPSBwYXJzZUludChTdHJpbmcocmVzdWx0Py53aXRoX2VtYmVkZGluZyA/PyAwKSwgMTApO1xuXG4gICAgcmV0dXJuIHtcbiAgICAgIHRvdGFsLFxuICAgICAgd2l0aEVtYmVkZGluZyxcbiAgICAgIHdpdGhvdXRFbWJlZGRpbmc6IHRvdGFsIC0gd2l0aEVtYmVkZGluZyxcbiAgICB9O1xuICB9XG5cbiAgLyoqXG4gICAqIOyehOuyoOuUqeydtCDsl4bripQg7ZWt66qpIElEIOyhsO2ajFxuICAgKi9cbiAgYXN5bmMgZ2V0SXRlbXNXaXRob3V0RW1iZWRkaW5nKFxuICAgIGVtYmVkZGluZ0NvbHVtbjogc3RyaW5nID0gXCJjb250ZW50X2VtYmVkZGluZ1wiLFxuICAgIGxpbWl0OiBudW1iZXIgPSAxMDAsXG4gICk6IFByb21pc2U8bnVtYmVyW10+IHtcbiAgICBjb25zdCByb3dzID0gYXdhaXQgdGhpcy5kYih0aGlzLnRhYmxlTmFtZSlcbiAgICAgIC5zZWxlY3QoXCJpZFwiKVxuICAgICAgLndoZXJlTnVsbChlbWJlZGRpbmdDb2x1bW4pXG4gICAgICAub3JkZXJCeShcImlkXCIpXG4gICAgICAubGltaXQobGltaXQpO1xuXG4gICAgcmV0dXJuIHJvd3MubWFwKChyb3c6IHsgaWQ6IG51bWJlciB9KSA9PiByb3cuaWQpO1xuICB9XG5cbiAgLyoqXG4gICAqIEVtYmVkZGluZyDsnbjsiqTthLTsiqQg67CY7ZmYICjqs6DquIkg7IKs7JqpKVxuICAgKi9cbiAgZ2V0RW1iZWRkaW5nKCk6IEVtYmVkZGluZyB7XG4gICAgcmV0dXJuIHRoaXMuZW1iZWRkaW5nO1xuICB9XG59XG4iXSwibmFtZXMiOlsicGd2ZWN0b3IiLCJERUZBVUxUX1ZFQ1RPUl9DT05GSUciLCJFbWJlZGRpbmciLCJWZWN0b3JTZWFyY2giLCJkYiIsImNvbmZpZyIsImVtYmVkZGluZyIsInRhYmxlTmFtZSIsInZveWFnZSIsIm9wZW5haSIsImNodW5raW5nIiwic2VhcmNoIiwic2F2ZUVtYmVkZGluZyIsImlkIiwidGV4dCIsInByb3ZpZGVyIiwiZW1iZWRkaW5nQ29sdW1uIiwiZW1iZWRPbmUiLCJ3aGVyZSIsInVwZGF0ZSIsInRvU3FsIiwic2F2ZUVtYmVkZGluZ3NCYXRjaCIsIml0ZW1zIiwib25Qcm9ncmVzcyIsInRleHRzIiwibWFwIiwiaXRlbSIsImVtYmVkZGluZ3MiLCJlbWJlZCIsInRyYW5zYWN0aW9uIiwidHJ4IiwiaSIsImxlbmd0aCIsInF1ZXJ5Iiwib3B0aW9ucyIsImxpbWl0IiwiZGVmYXVsdExpbWl0IiwidGhyZXNob2xkIiwic2ltaWxhcml0eVRocmVzaG9sZCIsIml0ZXJhdGl2ZVNjYW4iLCJyYXciLCJlZlNlYXJjaCIsInZlY3RvclN0ciIsInF1ZXJ5QnVpbGRlciIsInNlbGVjdCIsIndoZXJlTm90TnVsbCIsIm9yZGVyQnlSYXciLCJ3aGVyZVJhdyIsInJvd3MiLCJmaWx0ZXIiLCJyb3ciLCJzaW1pbGFyaXR5IiwicGFyc2VGbG9hdCIsIlN0cmluZyIsImRhdGEiLCJoeWJyaWRTZWFyY2giLCJmdHNDb2x1bW4iLCJ2ZWN0b3JXZWlnaHQiLCJmdHNXZWlnaHQiLCJzcWwiLCJ2ZWN0b3JTY29yZSIsInZlY3Rvcl9zY29yZSIsImZ0c1Njb3JlIiwiZnRzX3Njb3JlIiwiZ2V0RW1iZWRkaW5nU3RhdHVzIiwicmVzdWx0IiwiY291bnQiLCJmaXJzdCIsInRvdGFsIiwicGFyc2VJbnQiLCJ3aXRoRW1iZWRkaW5nIiwid2l0aF9lbWJlZGRpbmciLCJ3aXRob3V0RW1iZWRkaW5nIiwiZ2V0SXRlbXNXaXRob3V0RW1iZWRkaW5nIiwid2hlcmVOdWxsIiwib3JkZXJCeSIsImdldEVtYmVkZGluZyJdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBT0EsY0FBYyxnQkFBZ0I7QUFDckMsU0FBU0MscUJBQXFCLFFBQVEsY0FBVztBQUNqRCxTQUFTQyxTQUFTLFFBQVEsaUJBQWM7QUFZeEM7OztDQUdDLEdBQ0QsT0FBTyxNQUFNQztJQUNIQyxHQUFTO0lBQ1RDLE9BQXFCO0lBQ3JCQyxVQUFxQjtJQUNyQkMsVUFBa0I7SUFFMUIsWUFBWUgsRUFBUSxFQUFFRyxTQUFpQixFQUFFRixT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