sonamu 0.7.10 → 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.
- package/dist/api/config.d.ts +10 -3
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +2 -1
- package/dist/api/sonamu.d.ts +4 -0
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +36 -2
- package/dist/bin/cli.js +121 -117
- package/dist/database/base-model.d.ts +10 -50
- package/dist/database/base-model.d.ts.map +1 -1
- package/dist/database/base-model.js +19 -84
- package/dist/database/base-model.types.d.ts +4 -4
- package/dist/database/base-model.types.d.ts.map +1 -1
- package/dist/database/base-model.types.js +1 -1
- package/dist/database/db.d.ts +1 -0
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +24 -13
- package/dist/database/puri-subset.test-d.js +1 -1
- package/dist/database/puri-subset.types.d.ts +1 -0
- package/dist/database/puri-subset.types.d.ts.map +1 -1
- package/dist/database/puri-subset.types.js +2 -2
- package/dist/database/puri.d.ts +96 -1
- package/dist/database/puri.d.ts.map +1 -1
- package/dist/database/puri.js +214 -2
- package/dist/database/puri.types.d.ts +60 -5
- package/dist/database/puri.types.d.ts.map +1 -1
- package/dist/database/puri.types.js +2 -3
- package/dist/database/puri.types.test-d.js +1 -1
- package/dist/database/upsert-builder.d.ts +3 -1
- package/dist/database/upsert-builder.d.ts.map +1 -1
- package/dist/database/upsert-builder.js +19 -4
- package/dist/entity/entity-manager.d.ts +5 -4
- package/dist/entity/entity-manager.d.ts.map +1 -1
- package/dist/entity/entity-manager.js +8 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/migration/code-generation.d.ts.map +1 -1
- package/dist/migration/code-generation.js +33 -2
- package/dist/migration/postgresql-schema-reader.d.ts.map +1 -1
- package/dist/migration/postgresql-schema-reader.js +53 -22
- package/dist/naite/messaging-types.d.ts.map +1 -1
- package/dist/naite/messaging-types.js +1 -1
- package/dist/naite/naite-reporter.d.ts +7 -4
- package/dist/naite/naite-reporter.d.ts.map +1 -1
- package/dist/naite/naite-reporter.js +45 -21
- package/dist/naite/naite.js +2 -2
- package/dist/stream/sse.d.ts +2 -6
- package/dist/stream/sse.d.ts.map +1 -1
- package/dist/stream/sse.js +9 -3
- package/dist/syncer/api-parser.js +5 -1
- package/dist/syncer/file-patterns.d.ts +1 -1
- package/dist/syncer/file-patterns.d.ts.map +1 -1
- package/dist/syncer/file-patterns.js +6 -5
- package/dist/syncer/module-loader.d.ts +5 -0
- package/dist/syncer/module-loader.d.ts.map +1 -1
- package/dist/syncer/module-loader.js +17 -1
- package/dist/syncer/syncer.d.ts +3 -0
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +12 -2
- package/dist/tasks/decorator.d.ts +26 -0
- package/dist/tasks/decorator.d.ts.map +1 -0
- package/dist/tasks/decorator.js +28 -0
- package/dist/tasks/step-wrapper.d.ts +18 -0
- package/dist/tasks/step-wrapper.d.ts.map +1 -0
- package/dist/tasks/step-wrapper.js +38 -0
- package/dist/tasks/workflow-manager.d.ts +40 -0
- package/dist/tasks/workflow-manager.d.ts.map +1 -0
- package/dist/tasks/workflow-manager.js +193 -0
- package/dist/template/implementations/generated.template.d.ts.map +1 -1
- package/dist/template/implementations/generated.template.js +7 -3
- package/dist/template/implementations/model.template.js +2 -2
- package/dist/template/zod-converter.d.ts.map +1 -1
- package/dist/template/zod-converter.js +4 -2
- package/dist/types/types.d.ts +28 -11
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +18 -2
- package/dist/utils/console-util.js +2 -2
- package/dist/utils/formatter.d.ts.map +1 -1
- package/dist/utils/formatter.js +10 -2
- package/dist/utils/model.d.ts +9 -2
- package/dist/utils/model.d.ts.map +1 -1
- package/dist/utils/model.js +16 -1
- package/dist/utils/type-utils.d.ts.map +1 -1
- package/dist/utils/type-utils.js +3 -1
- package/dist/vector/embedding.d.ts +2 -5
- package/dist/vector/embedding.d.ts.map +1 -1
- package/dist/vector/embedding.js +3 -7
- package/dist/vector/types.d.ts.map +1 -1
- package/dist/vector/types.js +1 -1
- package/package.json +4 -2
- package/src/api/config.ts +15 -8
- package/src/api/sonamu.ts +43 -2
- package/src/bin/cli.ts +58 -54
- package/src/database/base-model.ts +21 -128
- package/src/database/base-model.types.ts +3 -4
- package/src/database/db.ts +28 -18
- package/src/database/puri-subset.test-d.ts +1 -0
- package/src/database/puri-subset.types.ts +2 -0
- package/src/database/puri.ts +292 -1
- package/src/database/puri.types.test-d.ts +1 -1
- package/src/database/puri.types.ts +81 -7
- package/src/database/upsert-builder.ts +27 -9
- package/src/entity/entity-manager.ts +9 -0
- package/src/index.ts +1 -1
- package/src/migration/code-generation.ts +40 -1
- package/src/migration/postgresql-schema-reader.ts +53 -22
- package/src/naite/messaging-types.ts +43 -44
- package/src/naite/naite-reporter.ts +51 -20
- package/src/naite/naite.ts +1 -1
- package/src/shared/app.shared.ts.txt +13 -0
- package/src/shared/web.shared.ts.txt +13 -0
- package/src/stream/sse.ts +15 -3
- package/src/syncer/api-parser.ts +4 -0
- package/src/syncer/file-patterns.ts +11 -9
- package/src/syncer/module-loader.ts +35 -0
- package/src/syncer/syncer.ts +14 -0
- package/src/tasks/decorator.ts +71 -0
- package/src/tasks/step-wrapper.ts +84 -0
- package/src/tasks/workflow-manager.ts +330 -0
- package/src/template/implementations/generated.template.ts +19 -6
- package/src/template/implementations/model.template.ts +1 -1
- package/src/template/zod-converter.ts +3 -0
- package/src/types/types.ts +23 -4
- package/src/utils/console-util.ts +1 -1
- package/src/utils/formatter.ts +8 -1
- package/src/utils/model.ts +26 -2
- package/src/utils/type-utils.ts +2 -0
- package/src/vector/embedding.ts +2 -8
- package/src/vector/types.ts +1 -2
- package/dist/vector/vector-search.d.ts +0 -47
- package/dist/vector/vector-search.d.ts.map +0 -1
- package/dist/vector/vector-search.js +0 -176
- package/src/vector/vector-search.ts +0 -261
|
@@ -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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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
|
-
|
|
248
|
-
ORDER BY i.relname,
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
+
}
|
|
@@ -2,22 +2,46 @@
|
|
|
2
2
|
* NaiteReporter
|
|
3
3
|
*
|
|
4
4
|
* 테스트 결과와 Trace 정보를 Unix Socket으로 VS Code extension에 전달합니다.
|
|
5
|
-
* extension이 ~/.sonamu/naite.sock에 소켓 서버를 열어둡니다.
|
|
5
|
+
* extension이 ~/.sonamu/naite-{hash}.sock에 소켓 서버를 열어둡니다.
|
|
6
|
+
*
|
|
7
|
+
* 프로젝트별로 고유한 소켓을 사용하기 위해 sonamu.config.ts 경로의 해시를 사용합니다.
|
|
6
8
|
*
|
|
7
9
|
* fs mock 충돌을 피하기 위해 net 모듈만 사용합니다.
|
|
8
10
|
*/
|
|
9
11
|
/** biome-ignore-all lint/suspicious/noExplicitAny: Naite는 expect와 호응하도록 any를 허용함 */
|
|
10
12
|
|
|
13
|
+
import { createHash } from "crypto";
|
|
11
14
|
import { connect, type Socket } from "net";
|
|
12
15
|
import { homedir } from "os";
|
|
13
16
|
import { join } from "path";
|
|
17
|
+
import { findApiRootPath } from "../utils/utils";
|
|
14
18
|
import type { NaiteMessagingTypes } from "./messaging-types";
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
/**
|
|
21
|
+
* sonamu.config.ts 경로를 받아서 프로젝트별 고유 해시를 생성합니다.
|
|
22
|
+
* 익스텐션과 동일한 방식으로 계산해야 합니다.
|
|
23
|
+
*/
|
|
24
|
+
function getProjectHash(configPath: string): string {
|
|
25
|
+
return createHash("md5").update(configPath).digest("hex").slice(0, 8);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 현재 프로젝트의 소켓 경로를 계산합니다.
|
|
30
|
+
* Sonamu.apiRootPath가 설정된 후에 호출해야 합니다.
|
|
31
|
+
*/
|
|
32
|
+
function getSocketPath(): string {
|
|
33
|
+
// sonamu.config.ts의 절대 경로 계산
|
|
34
|
+
// apiRootPath는 /project/api 형태이고, config는 /project/api/src/sonamu.config.ts에 있음
|
|
35
|
+
const configPath = join(findApiRootPath(), "src", "sonamu.config.ts");
|
|
36
|
+
const hash = getProjectHash(configPath);
|
|
37
|
+
|
|
38
|
+
return process.platform === "win32"
|
|
39
|
+
? `\\\\.\\pipe\\naite-${hash}`
|
|
40
|
+
: join(homedir(), ".sonamu", `naite-${hash}.sock`);
|
|
41
|
+
}
|
|
19
42
|
|
|
20
43
|
class NaiteReporterClass {
|
|
44
|
+
private socketPath: string | null = null;
|
|
21
45
|
private socket: Socket | null = null;
|
|
22
46
|
private connected = false;
|
|
23
47
|
private buffer: string[] = [];
|
|
@@ -25,11 +49,16 @@ class NaiteReporterClass {
|
|
|
25
49
|
/**
|
|
26
50
|
* 소켓 연결 시도
|
|
27
51
|
*/
|
|
28
|
-
private ensureConnection(): void {
|
|
29
|
-
if (this.connected || this.socket)
|
|
52
|
+
private async ensureConnection(): Promise<void> {
|
|
53
|
+
if (this.connected || this.socket) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
30
56
|
|
|
31
|
-
|
|
32
|
-
this.
|
|
57
|
+
return new Promise((res, rej) => {
|
|
58
|
+
if (!this.socketPath) {
|
|
59
|
+
this.socketPath = getSocketPath();
|
|
60
|
+
}
|
|
61
|
+
this.socket = connect(this.socketPath);
|
|
33
62
|
|
|
34
63
|
this.socket.on("connect", () => {
|
|
35
64
|
this.connected = true;
|
|
@@ -38,30 +67,30 @@ class NaiteReporterClass {
|
|
|
38
67
|
this.socket?.write(msg);
|
|
39
68
|
}
|
|
40
69
|
this.buffer = [];
|
|
70
|
+
res();
|
|
41
71
|
});
|
|
42
72
|
|
|
43
|
-
this.socket.on("error", () => {
|
|
73
|
+
this.socket.on("error", (e) => {
|
|
44
74
|
// 연결 실패 무시 (extension이 꺼져있을 수 있음)
|
|
45
75
|
this.connected = false;
|
|
46
76
|
this.socket = null;
|
|
77
|
+
rej(e);
|
|
47
78
|
});
|
|
48
79
|
|
|
49
80
|
this.socket.on("close", () => {
|
|
50
81
|
this.connected = false;
|
|
51
82
|
this.socket = null;
|
|
52
83
|
});
|
|
53
|
-
}
|
|
54
|
-
// 연결 실패 무시
|
|
55
|
-
}
|
|
84
|
+
});
|
|
56
85
|
}
|
|
57
86
|
|
|
58
87
|
/**
|
|
59
88
|
* 메시지 전송 (줄바꿈으로 구분)
|
|
60
89
|
*/
|
|
61
|
-
private send(data: NaiteMessagingTypes.NaiteMessage): void {
|
|
90
|
+
private async send(data: NaiteMessagingTypes.NaiteMessage): Promise<void> {
|
|
62
91
|
const msg = `${JSON.stringify(data)}\n`;
|
|
63
92
|
|
|
64
|
-
this.ensureConnection();
|
|
93
|
+
await this.ensureConnection().catch((_) => {});
|
|
65
94
|
|
|
66
95
|
if (this.connected && this.socket) {
|
|
67
96
|
this.socket.write(msg);
|
|
@@ -75,12 +104,12 @@ class NaiteReporterClass {
|
|
|
75
104
|
* beforeAll에서 호출합니다.
|
|
76
105
|
* 테스트 run 시작을 알립니다 (데이터 클리어 신호).
|
|
77
106
|
*/
|
|
78
|
-
startTestRun(): void {
|
|
107
|
+
async startTestRun(): Promise<void> {
|
|
79
108
|
if (process.env.CI) {
|
|
80
109
|
return;
|
|
81
110
|
}
|
|
82
111
|
|
|
83
|
-
this.send({
|
|
112
|
+
await this.send({
|
|
84
113
|
type: "run/start",
|
|
85
114
|
startedAt: new Date().toISOString(),
|
|
86
115
|
});
|
|
@@ -90,12 +119,14 @@ class NaiteReporterClass {
|
|
|
90
119
|
* afterEach에서 호출합니다.
|
|
91
120
|
* 테스트 케이스 결과를 traces와 함께 전송합니다.
|
|
92
121
|
*/
|
|
93
|
-
reportTestResult(
|
|
122
|
+
async reportTestResult(
|
|
123
|
+
result: Omit<NaiteMessagingTypes.TestResult, "receivedAt">,
|
|
124
|
+
): Promise<void> {
|
|
94
125
|
if (process.env.CI) {
|
|
95
126
|
return;
|
|
96
127
|
}
|
|
97
128
|
|
|
98
|
-
this.send({
|
|
129
|
+
await this.send({
|
|
99
130
|
type: "test/result",
|
|
100
131
|
receivedAt: new Date().toISOString(),
|
|
101
132
|
...result,
|
|
@@ -106,12 +137,12 @@ class NaiteReporterClass {
|
|
|
106
137
|
* afterAll에서 호출합니다.
|
|
107
138
|
* 테스트 run 종료를 알립니다.
|
|
108
139
|
*/
|
|
109
|
-
endTestRun(): void {
|
|
140
|
+
async endTestRun(): Promise<void> {
|
|
110
141
|
if (process.env.CI) {
|
|
111
142
|
return;
|
|
112
143
|
}
|
|
113
144
|
|
|
114
|
-
this.send({
|
|
145
|
+
await this.send({
|
|
115
146
|
type: "run/end",
|
|
116
147
|
endedAt: new Date().toISOString(),
|
|
117
148
|
});
|
package/src/naite/naite.ts
CHANGED
|
@@ -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
|
|
9
|
+
): SSEConnection<T> {
|
|
10
|
+
return new SSEConnectionImpl<T>(socket, reply);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
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(
|
package/src/syncer/api-parser.ts
CHANGED
|
@@ -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
|
-
| "
|
|
7
|
-
| "types"
|
|
8
|
-
| "functions"
|
|
9
|
-
| "generated"
|
|
6
|
+
| "config"
|
|
10
7
|
| "entity"
|
|
11
8
|
| "frame"
|
|
12
|
-
| "
|
|
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
|
-
|
|
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
|
+
}
|
package/src/syncer/syncer.ts
CHANGED
|
@@ -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
|
};
|