sonamu 0.7.11 → 0.7.12

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 (118) hide show
  1. package/dist/api/config.d.ts +10 -3
  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 +36 -2
  7. package/dist/bin/cli.js +121 -117
  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.js +5 -1
  45. package/dist/syncer/file-patterns.d.ts +1 -1
  46. package/dist/syncer/file-patterns.d.ts.map +1 -1
  47. package/dist/syncer/file-patterns.js +6 -5
  48. package/dist/syncer/module-loader.d.ts +5 -0
  49. package/dist/syncer/module-loader.d.ts.map +1 -1
  50. package/dist/syncer/module-loader.js +17 -1
  51. package/dist/syncer/syncer.d.ts +3 -0
  52. package/dist/syncer/syncer.d.ts.map +1 -1
  53. package/dist/syncer/syncer.js +12 -2
  54. package/dist/tasks/decorator.d.ts +26 -0
  55. package/dist/tasks/decorator.d.ts.map +1 -0
  56. package/dist/tasks/decorator.js +28 -0
  57. package/dist/tasks/step-wrapper.d.ts +18 -0
  58. package/dist/tasks/step-wrapper.d.ts.map +1 -0
  59. package/dist/tasks/step-wrapper.js +38 -0
  60. package/dist/tasks/workflow-manager.d.ts +40 -0
  61. package/dist/tasks/workflow-manager.d.ts.map +1 -0
  62. package/dist/tasks/workflow-manager.js +193 -0
  63. package/dist/template/implementations/generated.template.d.ts.map +1 -1
  64. package/dist/template/implementations/generated.template.js +7 -3
  65. package/dist/types/types.d.ts +27 -11
  66. package/dist/types/types.d.ts.map +1 -1
  67. package/dist/types/types.js +15 -2
  68. package/dist/utils/formatter.d.ts.map +1 -1
  69. package/dist/utils/formatter.js +10 -2
  70. package/dist/utils/model.d.ts +9 -2
  71. package/dist/utils/model.d.ts.map +1 -1
  72. package/dist/utils/model.js +16 -1
  73. package/dist/utils/type-utils.d.ts.map +1 -1
  74. package/dist/utils/type-utils.js +3 -1
  75. package/dist/vector/embedding.d.ts +2 -5
  76. package/dist/vector/embedding.d.ts.map +1 -1
  77. package/dist/vector/embedding.js +3 -7
  78. package/dist/vector/types.d.ts.map +1 -1
  79. package/dist/vector/types.js +1 -1
  80. package/package.json +5 -3
  81. package/src/api/config.ts +15 -8
  82. package/src/api/sonamu.ts +43 -2
  83. package/src/bin/cli.ts +58 -54
  84. package/src/database/base-model.ts +21 -128
  85. package/src/database/base-model.types.ts +3 -4
  86. package/src/database/db.ts +28 -18
  87. package/src/database/puri-subset.test-d.ts +1 -0
  88. package/src/database/puri-subset.types.ts +2 -0
  89. package/src/database/puri.ts +238 -27
  90. package/src/database/puri.types.test-d.ts +1 -1
  91. package/src/database/puri.types.ts +49 -6
  92. package/src/entity/entity-manager.ts +9 -0
  93. package/src/index.ts +1 -1
  94. package/src/migration/code-generation.ts +40 -1
  95. package/src/migration/postgresql-schema-reader.ts +53 -22
  96. package/src/naite/messaging-types.ts +43 -44
  97. package/src/naite/naite.ts +1 -1
  98. package/src/shared/app.shared.ts.txt +13 -0
  99. package/src/shared/web.shared.ts.txt +13 -0
  100. package/src/stream/sse.ts +15 -3
  101. package/src/syncer/api-parser.ts +4 -0
  102. package/src/syncer/file-patterns.ts +11 -9
  103. package/src/syncer/module-loader.ts +35 -0
  104. package/src/syncer/syncer.ts +14 -0
  105. package/src/tasks/decorator.ts +71 -0
  106. package/src/tasks/step-wrapper.ts +84 -0
  107. package/src/tasks/workflow-manager.ts +330 -0
  108. package/src/template/implementations/generated.template.ts +19 -6
  109. package/src/types/types.ts +20 -4
  110. package/src/utils/formatter.ts +8 -1
  111. package/src/utils/model.ts +26 -2
  112. package/src/utils/type-utils.ts +2 -0
  113. package/src/vector/embedding.ts +2 -8
  114. package/src/vector/types.ts +1 -2
  115. package/dist/vector/vector-search.d.ts +0 -47
  116. package/dist/vector/vector-search.d.ts.map +0 -1
  117. package/dist/vector/vector-search.js +0 -176
  118. package/src/vector/vector-search.ts +0 -261
@@ -13,13 +13,31 @@ type VirtualKey = "__virtual__";
13
13
  type LeftJoinedKey = "__leftJoined__";
14
14
  type HasDefault = "__hasDefault__";
15
15
  type GeneratedKey = "__generated__";
16
+ type VectorKey = "__vector__";
16
17
 
17
- type InternalTypeKeys = FulltextKey | VirtualKey | LeftJoinedKey | HasDefault | GeneratedKey;
18
+ type InternalTypeKeys =
19
+ | FulltextKey
20
+ | VirtualKey
21
+ | LeftJoinedKey
22
+ | HasDefault
23
+ | GeneratedKey
24
+ | VectorKey;
18
25
 
19
26
  // ============================================
20
27
  // 타입 유틸리티
21
28
  // ============================================
22
29
 
30
+ // __vector__ 메타데이터에서 벡터 컬럼 추출
31
+ type VectorColumnKeys<T> = T extends { [K in VectorKey]: readonly (infer V)[] }
32
+ ? V & string
33
+ : never;
34
+
35
+ export type VectorColumns<TTables extends Record<string, any>> =
36
+ | {
37
+ [TAlias in keyof TTables]: `${TAlias & string}.${VectorColumnKeys<TTables[TAlias]>}`;
38
+ }[keyof TTables]
39
+ | (IsSingleKey<TTables> extends true ? VectorColumnKeys<TTables[keyof TTables]> : never);
40
+
23
41
  // 테이블명 타입
24
42
  export type TableName<TSchema> = keyof TSchema & string;
25
43
 
@@ -78,7 +96,7 @@ export type ResultAvailableColumns<TTables extends Record<string, any>, TResult
78
96
  // Select 값 타입 확장 (단일 컬럼 또는 SQL 표현식)
79
97
  export type SelectValue<TTables extends Record<string, any>> =
80
98
  | AvailableColumns<TTables>
81
- | SqlExpression<"string" | "number" | "boolean" | "date">;
99
+ | SqlExpression<"string" | "number" | "boolean" | "date" | "string[]">;
82
100
 
83
101
  // 중첩 Select 객체 타입 (재귀적)
84
102
  // 예: { parent: { id: "parent.id", name: "parent.name" } }
@@ -178,7 +196,9 @@ type ParseSelectObjectWithPath<
178
196
  ? boolean
179
197
  : R extends "date"
180
198
  ? Date
181
- : never
199
+ : R extends "string[]"
200
+ ? string[]
201
+ : never
182
202
  : IsNestedObject<TSelect[K]> extends true
183
203
  ? TSelect[K] extends NestedSelectObject<TTables>
184
204
  ? IsNullableJoinedTable<TTables, JoinPath<Prefix, K & string>> extends true // 주어진 테이블이 FK nullable에 leftJoin되었는지 여부에 따라 select 결과 객체의 타입이 달라집니다.
@@ -205,7 +225,9 @@ type ParseSelectObjectInner<
205
225
  ? boolean
206
226
  : R extends "date"
207
227
  ? Date
208
- : never
228
+ : R extends "string[]"
229
+ ? string[]
230
+ : never
209
231
  : IsNestedObject<TSelect[K]> extends true
210
232
  ? TSelect[K] extends NestedSelectObject<TTables>
211
233
  ? IsNullableJoinedTable<TTables, JoinPath<Prefix, K & string>> extends true
@@ -274,7 +296,7 @@ export type ComparisonOperator = "=" | ">" | ">=" | "<" | "<=" | "<>" | "!=";
274
296
  export type WhereOperator = ComparisonOperator | "like" | "not like";
275
297
 
276
298
  // SQL Expression 타입 정의
277
- export type SqlExpression<T extends "string" | "number" | "boolean" | "date"> = {
299
+ export type SqlExpression<T extends "string" | "number" | "boolean" | "date" | "string[]"> = {
278
300
  _type: "sql_expression"; // 또는 "computed_value"
279
301
  _return: T;
280
302
  _sql: string;
@@ -377,7 +399,7 @@ export type TsQueryOptions = {
377
399
  config?: TsQueryConfig;
378
400
  };
379
401
 
380
- export type HighlightOptions = {
402
+ export type TsHighlightOptions = {
381
403
  /** 쿼리 변환 함수 (기본값: "websearch_to_tsquery") */
382
404
  parser?: TsQueryParser;
383
405
  /** 텍스트 검색 설정 (기본값: "simple") */
@@ -399,3 +421,24 @@ export type HighlightOptions = {
399
421
  /** 조각 구분자 (기본값: " ... ") */
400
422
  fragmentDelimiter?: string;
401
423
  };
424
+
425
+ export type TsRankOptions = {
426
+ parser?: TsQueryParser;
427
+ config?: TsQueryConfig;
428
+ /** 가중치 배열 [D, C, B, A] (기본값: [0.1, 0.2, 0.4, 1.0]) */
429
+ weights?: [number, number, number, number];
430
+ /**
431
+ * 정규화 옵션
432
+ * 0: 문서 길이 무시 (기본값)
433
+ * 1: 1 + log(문서 길이)로 나눔
434
+ * 2: 문서 길이로 나눔
435
+ * 4: 평균 조화 거리로 나눔 (ts_rank_cd만)
436
+ * 8: 고유 단어 수로 나눔
437
+ * 16: 1 + log(고유 단어 수)로 나눔
438
+ * 32: rank/(rank+1) -> 0~1 사이의 값으로 스케일링
439
+ *
440
+ * 비트마스크를 사용하여 옵션을 조합할 수 있음
441
+ * 예: 8 | 32 -> 고유 단어 수로 나누고 0~1 스케일링
442
+ */
443
+ normalization?: number;
444
+ };
@@ -85,6 +85,15 @@ class EntityManagerClass {
85
85
  return entity;
86
86
  }
87
87
 
88
+ getByTable(table: string): Entity {
89
+ const entity = Array.from(this.entities.values()).find((entity) => entity.table === table);
90
+ if (entity === undefined) {
91
+ throw new Error(`존재하지 않는 Entity 요청 ${table}`);
92
+ }
93
+
94
+ return entity;
95
+ }
96
+
88
97
  exists(entityId: string): boolean {
89
98
  const entity = this.entities.get(entityId);
90
99
  return entity !== undefined;
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ export * from "./migration/types";
22
22
  export * from "./naite/naite";
23
23
  export * from "./naite/naite-reporter";
24
24
  export * from "./stream/sse";
25
+ export * from "./tasks/decorator";
25
26
  export * from "./template/template";
26
27
  export * from "./template/template-manager";
27
28
  export * from "./testing/fixture-manager";
@@ -35,7 +36,6 @@ export * from "./vector/chunking";
35
36
  export * from "./vector/config";
36
37
  export * from "./vector/embedding";
37
38
  export * from "./vector/types";
38
- export * from "./vector/vector-search";
39
39
 
40
40
  // export * from "./api/code-converters";
41
41
  // export * from "./syncer/syncer";
@@ -1,7 +1,8 @@
1
1
  import equal from "fast-deep-equal";
2
2
  import { alphabetical, diff } from "radashi";
3
- import { Naite } from "..";
3
+ import { EntityManager, Naite } from "..";
4
4
  import type {
5
+ EntityProp,
5
6
  GenMigrationCode,
6
7
  MigrationColumn,
7
8
  MigrationForeign,
@@ -216,6 +217,10 @@ function genIndexDefinition(index: MigrationIndex, table: string): string {
216
217
  return genVectorIndexDefinition(index, table);
217
218
  }
218
219
 
220
+ if (index.using === "pgroonga") {
221
+ return genPgroongaIndexDefinition(index, table);
222
+ }
223
+
219
224
  const methodMap = {
220
225
  index: "INDEX",
221
226
  unique: "UNIQUE INDEX",
@@ -245,6 +250,40 @@ function genIndexDefinition(index: MigrationIndex, table: string): string {
245
250
  );`;
246
251
  }
247
252
 
253
+ function genPgroongaIndexDefinition(index: MigrationIndex, table: string) {
254
+ const entity = EntityManager.getByTable(table);
255
+
256
+ // 복합 인덱스인 경우 ARRAY 사용
257
+ const columnClause = (() => {
258
+ if (index.columns.length === 1) {
259
+ const column = entity.propsDict[index.columns[0].name];
260
+ const option = getPgroongaColumnOption(column);
261
+ return `${index.columns[0].name}${option ? ` ${option}` : ""}`;
262
+ }
263
+
264
+ return `(ARRAY[${index.columns.map((col) => `${col.name}::text`).join(",")}])`;
265
+ })();
266
+
267
+ return `await knex.raw(
268
+ \`CREATE INDEX ${index.name} ON ${table} USING pgroonga (${columnClause}) WITH (tokenizer='TokenMecab');\`
269
+ )`;
270
+ }
271
+
272
+ /**
273
+ * PGroonga 컬럼 옵션 추출
274
+ *
275
+ * FullText 오퍼레이터를 지원하는 경우 우선 설정, 나머지는 디폴트 이용
276
+ * @link https://pgroonga.github.io/reference
277
+ */
278
+ function getPgroongaColumnOption(column: EntityProp) {
279
+ if (column.type === "string" && column.length !== undefined) {
280
+ return "pgroonga_varchar_full_text_search_ops_v2";
281
+ } else if (column.type === "json") {
282
+ return "pgroonga_jsonb_full_text_search_ops_v2";
283
+ }
284
+ return null;
285
+ }
286
+
248
287
  /**
249
288
  * @description
250
289
  * - HNSW (Hierarchical Navigable Small World): 느린 빌드, 빠른 검색 속도, 높은 메모리 및 정확도
@@ -148,7 +148,7 @@ class PostgreSQLSchemaReaderClass {
148
148
  sortOrder: idx.sort_order,
149
149
  })),
150
150
  nullsNotDistinct: firstIndex.nulls_not_distinct,
151
- using: firstIndex.index_type as "btree" | "hash" | "gin" | "gist" | undefined,
151
+ using: firstIndex.index_type as "btree" | "hash" | "gin" | "gist" | "pgroonga" | undefined,
152
152
  };
153
153
  });
154
154
 
@@ -219,34 +219,65 @@ class PostgreSQLSchemaReaderClass {
219
219
  throw new Error(`Table not found: ${tableName}`);
220
220
  }
221
221
 
222
- // Indexes 조회
222
+ // Indexes 조회 (PGroonga 표현식 인덱스 포함)
223
223
  const indexesQuery = `
224
224
  SELECT
225
- i.relname as index_name,
226
- a.attname as column_name,
227
- ix.indisunique as is_unique,
228
- ix.indisprimary as is_primary,
229
- am.amname as index_type,
230
- -- NULLS FIRST/LAST 확인 (비트 연산)
231
- (opt & 2) = 2 AS nulls_first,
232
- -- ASC/DESC 확인
233
- CASE
234
- WHEN (opt & 1) = 1 THEN 'DESC'
235
- ELSE 'ASC'
236
- END AS sort_order,
237
- ix.indnullsnotdistinct AS nulls_not_distinct
225
+ i.relname AS index_name,
226
+ CASE
227
+ WHEN am.amname = 'pgroonga' AND u.attnum = 0 THEN
228
+ regexp_replace(
229
+ regexp_replace(
230
+ TRIM(pgroonga_col.column_expr),
231
+ '::text',
232
+ '',
233
+ 'g'
234
+ ),
235
+ '[()]',
236
+ '',
237
+ 'g'
238
+ )
239
+ ELSE a.attname
240
+ END AS column_name,
241
+ ix.indisunique AS is_unique,
242
+ ix.indisprimary AS is_primary,
243
+ am.amname AS index_type,
244
+ COALESCE((u.opt & 2) = 2, FALSE) AS nulls_first,
245
+ CASE
246
+ WHEN (u.opt & 1) = 1 THEN 'DESC'
247
+ ELSE 'ASC'
248
+ END AS sort_order,
249
+ ix.indnullsnotdistinct AS nulls_not_distinct
238
250
  FROM pg_class t
239
251
  JOIN pg_index ix ON t.oid = ix.indrelid
240
252
  JOIN pg_class i ON i.oid = ix.indexrelid
241
253
  JOIN pg_am am ON i.relam = am.oid
242
- JOIN LATERAL unnest(ix.indkey, ix.indoption)
243
- WITH ORDINALITY AS u(attnum, opt, ord) ON true
244
- -- unnest에서 나온 attnum으로 직접 조인
245
- JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = u.attnum
254
+ JOIN LATERAL unnest(ix.indkey, ix.indoption)
255
+ WITH ORDINALITY AS u(attnum, opt, ord) ON true
256
+ LEFT JOIN pg_attribute a ON a.attrelid = t.oid
257
+ AND a.attnum = u.attnum
258
+ AND u.attnum > 0
259
+ LEFT JOIN LATERAL (
260
+ SELECT
261
+ unnest(
262
+ CASE
263
+ WHEN pg_get_expr(ix.indexprs, ix.indrelid) ~ '^ARRAY\\[' THEN
264
+ string_to_array(
265
+ regexp_replace(
266
+ pg_get_expr(ix.indexprs, ix.indrelid),
267
+ '^ARRAY\\[(.*)\\]$',
268
+ '\\1'
269
+ ),
270
+ ', '
271
+ )
272
+ ELSE
273
+ ARRAY[pg_get_expr(ix.indexprs, ix.indrelid)]
274
+ END
275
+ ) as column_expr
276
+ ) pgroonga_col ON am.amname = 'pgroonga' AND u.attnum = 0
246
277
  WHERE t.relname = ?
247
- AND a.attnum = ANY(ix.indkey)
248
- ORDER BY i.relname, array_position(ix.indkey, a.attnum)
249
- `;
278
+ AND (u.attnum > 0 OR (am.amname = 'pgroonga' AND u.attnum = 0))
279
+ ORDER BY i.relname, u.ord;
280
+ `;
250
281
  const indexes = (await compareDB.raw(indexesQuery, [tableName])).rows;
251
282
 
252
283
  // Foreign Keys 조회
@@ -5,47 +5,46 @@
5
5
  * 이 파일은 cartanova-ai/sonamu와 cartanova-ai/vscode-sonamu에서 공통으로 사용됩니다.
6
6
  */
7
7
  export namespace NaiteMessagingTypes {
8
- export type NaiteRunStartMessage = {
9
- type: "run/start";
10
- startedAt: string;
11
- };
12
-
13
- export type NaiteTestResultMessage = {
14
- type: "test/result";
15
- receivedAt: string;
16
- } & TestResult;
17
-
18
- export type NaiteRunEndMessage = {
19
- type: "run/end";
20
- endedAt: string;
21
- };
22
-
23
- export type NaiteMessage = NaiteRunStartMessage | NaiteTestResultMessage | NaiteRunEndMessage;
24
-
25
- export type NaiteTrace = {
26
- key: string;
27
- value: any;
28
- filePath: string;
29
- lineNumber: number;
30
- at: string;
31
- };
32
-
33
- export type TestError = {
34
- message: string;
35
- stack?: string;
36
- };
37
-
38
- export type TestResult = {
39
- suiteName: string;
40
- suiteFilePath?: string;
41
- testName: string;
42
- testFilePath: string;
43
- testLine: number;
44
- status: string;
45
- duration: number;
46
- error?: TestError;
47
- traces: NaiteTrace[];
48
- receivedAt: string;
49
- };
50
- }
51
-
8
+ export type NaiteRunStartMessage = {
9
+ type: "run/start";
10
+ startedAt: string;
11
+ };
12
+
13
+ export type NaiteTestResultMessage = {
14
+ type: "test/result";
15
+ receivedAt: string;
16
+ } & TestResult;
17
+
18
+ export type NaiteRunEndMessage = {
19
+ type: "run/end";
20
+ endedAt: string;
21
+ };
22
+
23
+ export type NaiteMessage = NaiteRunStartMessage | NaiteTestResultMessage | NaiteRunEndMessage;
24
+
25
+ export type NaiteTrace = {
26
+ key: string;
27
+ value: any;
28
+ filePath: string;
29
+ lineNumber: number;
30
+ at: string;
31
+ };
32
+
33
+ export type TestError = {
34
+ message: string;
35
+ stack?: string;
36
+ };
37
+
38
+ export type TestResult = {
39
+ suiteName: string;
40
+ suiteFilePath?: string;
41
+ testName: string;
42
+ testFilePath: string;
43
+ testLine: number;
44
+ status: string;
45
+ duration: number;
46
+ error?: TestError;
47
+ traces: NaiteTrace[];
48
+ receivedAt: string;
49
+ };
50
+ }
@@ -349,7 +349,7 @@ export class NaiteClass {
349
349
  // 이로 인해 "직렬화 가능한 값들만 허용"하는 제약이 생깁니다.
350
350
  //
351
351
  // 이 제약을 의식하여 Naite.t에 직렬화 가능한 값만 넘기게 할 수도 있었지만, 그렇게 하면 불편해질 것 같아서 하지 않았습니다.
352
- // 따라서 현재 Naite.t는 모든 값을 받을 수 있게 되어 있습니다.
352
+ // 따라서 현재 Naite.t는 모든 값을 받을 수 있게 되어 있습니다.
353
353
  // 대신 이렇게(getAllTraces) 그 값들을 빼낼 때 JSON.stringify를 사용하여 강제로 직렬화 가능하게 만들었습니다,,
354
354
  for (const trace of traces) {
355
355
  const check = isSerializable(trace.data);
@@ -180,6 +180,19 @@ export type ListResult<LP extends { queryMode?: SonamuQueryMode }, T> = LP["quer
180
180
  export const SonamuQueryMode = z.enum(["both", "list", "count"]);
181
181
  export type SonamuQueryMode = z.infer<typeof SonamuQueryMode>;
182
182
 
183
+ /* Semantic Query */
184
+ export const SonamuSemanticParams = z
185
+ .object({
186
+ semanticQuery: z.object({
187
+ embedding: z.array(z.number()).min(1024).max(1024),
188
+ threshold: z.number().optional(),
189
+ method: z.enum(["cosine", "l2", "inner_product"]).optional(),
190
+ which: z.string(),
191
+ }),
192
+ })
193
+ .partial();
194
+ export type SonamuSemanticParams = z.infer<typeof SonamuSemanticParams>;
195
+
183
196
  /*
184
197
  SWR
185
198
  */
@@ -95,6 +95,19 @@ export type ListResult<LP extends { queryMode?: SonamuQueryMode }, T> = LP["quer
95
95
  export const SonamuQueryMode = z.enum(["both", "list", "count"]);
96
96
  export type SonamuQueryMode = z.infer<typeof SonamuQueryMode>;
97
97
 
98
+ /* Semantic Query */
99
+ export const SonamuSemanticParams = z
100
+ .object({
101
+ semanticQuery: z.object({
102
+ embedding: z.array(z.number()).min(1024).max(1024),
103
+ threshold: z.number().optional(),
104
+ method: z.enum(["cosine", "l2", "inner_product"]).optional(),
105
+ which: z.string(),
106
+ }),
107
+ })
108
+ .partial();
109
+ export type SonamuSemanticParams = z.infer<typeof SonamuSemanticParams>;
110
+
98
111
  /*
99
112
  SWR
100
113
  */
package/src/stream/sse.ts CHANGED
@@ -6,11 +6,23 @@ export function createSSEFactory<T extends z.ZodObject>(
6
6
  socket: FastifyRequest["socket"],
7
7
  reply: FastifyReply,
8
8
  _events: T,
9
- ) {
10
- return new SSEConnection<T>(socket, reply);
9
+ ): SSEConnection<T> {
10
+ return new SSEConnectionImpl<T>(socket, reply);
11
11
  }
12
12
 
13
- class SSEConnection<T extends z.ZodObject> {
13
+ export function createMockSSEFactory<T extends z.ZodObject>(_events: T): SSEConnection<T> {
14
+ return {
15
+ publish: (_event, _data) => {},
16
+ end: () => Promise.resolve(),
17
+ };
18
+ }
19
+
20
+ export interface SSEConnection<T extends z.ZodObject> {
21
+ publish<K extends keyof z.infer<T>>(event: K, data: z.infer<T>[K]): void;
22
+ end(): Promise<void>;
23
+ }
24
+
25
+ class SSEConnectionImpl<T extends z.ZodObject> implements SSEConnection<T> {
14
26
  private _closed = false;
15
27
 
16
28
  constructor(
@@ -229,6 +229,10 @@ function resolveTypeNode(typeNode: ts.TypeNode): ApiParamType {
229
229
  };
230
230
  }
231
231
  break;
232
+ case ts.SyntaxKind.ParenthesizedType:
233
+ // 괄호로 묶인 타입 (예: (A & B)[] 에서 (A & B))
234
+ // 내부 타입을 재귀적으로 resolve
235
+ return resolveTypeNode((typeNode as ts.ParenthesizedTypeNode).type);
232
236
  case undefined:
233
237
  throw new Error(`typeNode undefined`);
234
238
  }
@@ -3,13 +3,14 @@ import { Sonamu } from "../api/sonamu";
3
3
  import type { AbsolutePath, ApiRelativePath } from "../utils/path-utils";
4
4
 
5
5
  export type FileType =
6
- | "model"
7
- | "types"
8
- | "functions"
9
- | "generated"
6
+ | "config"
10
7
  | "entity"
11
8
  | "frame"
12
- | "config";
9
+ | "functions"
10
+ | "generated"
11
+ | "model"
12
+ | "types"
13
+ | "workflow";
13
14
 
14
15
  export type GlobPattern<T extends ApiRelativePath | AbsolutePath> = {
15
16
  [key in FileType]: T;
@@ -24,13 +25,14 @@ export type GlobPattern<T extends ApiRelativePath | AbsolutePath> = {
24
25
  * **사용**: getChecksumPatternGroupInAbsolutePath()로 절대 경로 변환 후 glob 사용
25
26
  */
26
27
  export const checksumPatternGroup: GlobPattern<ApiRelativePath> = {
28
+ config: "src/sonamu.config.ts",
27
29
  entity: "src/application/**/*.entity.json",
28
- types: "src/application/**/*.types.ts",
29
- generated: "src/application/sonamu.generated.ts",
30
- model: "src/application/**/*.model.ts",
31
30
  frame: "src/application/**/*.frame.ts",
32
31
  functions: "src/application/**/*.functions.ts",
33
- config: "src/sonamu.config.ts",
32
+ generated: "src/application/sonamu.generated.ts",
33
+ model: "src/application/**/*.model.ts",
34
+ types: "src/application/**/*.types.ts",
35
+ workflow: "src/application/**/*.workflow.ts",
34
36
  };
35
37
 
36
38
  /**
@@ -4,6 +4,7 @@ import type { BaseFrameClass } from "../api/base-frame";
4
4
  import type { ApiDecoratorOptions } from "../api/decorators";
5
5
  import { Sonamu } from "../api/sonamu";
6
6
  import type { BaseModelClass } from "../database/base-model";
7
+ import type { WorkflowMetadata } from "../tasks/decorator";
7
8
  import type { ApiParam, ApiParamType } from "../types/types";
8
9
  import { globAsync } from "../utils/async-utils";
9
10
  import { importMembers } from "../utils/esm-utils";
@@ -126,3 +127,37 @@ export async function loadTypes(): Promise<LoadedTypes> {
126
127
 
127
128
  return types;
128
129
  }
130
+
131
+ /**
132
+ * *.workflow.ts 파일들에서 Workflow 메타데이터를 로드합니다.
133
+ */
134
+ export async function loadWorkflows() {
135
+ const workflowPathsPattern = path.join(
136
+ Sonamu.apiRootPath,
137
+ runtimePath("src/application/**/*.workflow.ts"),
138
+ );
139
+ const workflowPaths = await globAsync(workflowPathsPattern);
140
+ const workflows: Map<string, WorkflowMetadata[]> = new Map();
141
+ for (const filePath of workflowPaths) {
142
+ const importedMembers = await importMembers(filePath);
143
+ workflows.set(
144
+ filePath,
145
+ importedMembers
146
+ .filter(({ value }) => {
147
+ return (
148
+ typeof value === "object" &&
149
+ value !== null &&
150
+ "type" in value &&
151
+ value.type === "workflow" &&
152
+ "fn" in value &&
153
+ typeof value.fn === "function"
154
+ );
155
+ })
156
+ .map(({ value }) => {
157
+ return value as WorkflowMetadata;
158
+ }),
159
+ );
160
+ }
161
+
162
+ return workflows;
163
+ }
@@ -7,6 +7,7 @@ import { minimatch } from "minimatch";
7
7
  import path, { dirname } from "path";
8
8
  import { group, unique } from "radashi";
9
9
  import type { z } from "zod";
10
+ import type { WorkflowMetadata } from "..";
10
11
  import { registeredApis } from "../api/decorators";
11
12
  import { Sonamu } from "../api/sonamu";
12
13
  import { EntityManager, type EntityNamesRecord } from "../entity/entity-manager";
@@ -31,6 +32,7 @@ import {
31
32
  loadApis,
32
33
  loadModels,
33
34
  loadTypes,
35
+ loadWorkflows,
34
36
  } from "./module-loader";
35
37
 
36
38
  type DiffGroups = {
@@ -41,6 +43,7 @@ export class Syncer {
41
43
  apis: LoadedApis = [];
42
44
  types: LoadedTypes = {};
43
45
  models: LoadedModels = {};
46
+ workflows: Map<string, WorkflowMetadata[]> = new Map();
44
47
  isSyncing: boolean = false;
45
48
 
46
49
  /**
@@ -127,6 +130,7 @@ export class Syncer {
127
130
  await this.autoloadTypes();
128
131
  await this.autoloadModels();
129
132
  await this.autoloadApis();
133
+ await this.autoloadWorkflows();
130
134
 
131
135
  this.syncUI();
132
136
  }
@@ -199,6 +203,11 @@ export class Syncer {
199
203
  this.apis = await loadApis();
200
204
  }
201
205
 
206
+ async autoloadWorkflows() {
207
+ this.workflows = await loadWorkflows();
208
+ await Sonamu.workflows.synchronize(this.workflows);
209
+ }
210
+
202
211
  /**
203
212
  * 실제 싱크를 수행하는 본체입니다.
204
213
  * 변경된 파일들을 타입별로 분류하고 각 타입에 맞는 액션을 실행합니다.
@@ -235,6 +244,11 @@ export class Syncer {
235
244
  await this.actionSyncConfig();
236
245
  }
237
246
 
247
+ // 트리거: workflow
248
+ if (diffTypes.includes("workflow")) {
249
+ await this.autoloadWorkflows();
250
+ }
251
+
238
252
  return {
239
253
  diffTypes,
240
254
  };