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
@@ -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(
@@ -98,7 +98,8 @@ export async function readApisFromFile(filePath: AbsolutePath): Promise<Extended
98
98
  // const p = path.join(tmpdir(), "sonamu-syncer-error.json");
99
99
  // writeFileSync(p, JSON.stringify(registeredApis, null, 2));
100
100
  // execSync(`open ${p}`);
101
- throw new Error(`현재 파일에 사전 등록된 API가 없습니다. ${filePath}`);
101
+ // throw new Error(`현재 파일에 사전 등록된 API가 없습니다. ${filePath}`);
102
+ return [];
102
103
  }
103
104
 
104
105
  // 등록된 API에 현재 메소드 타입 정보 확장
@@ -229,6 +230,10 @@ function resolveTypeNode(typeNode: ts.TypeNode): ApiParamType {
229
230
  };
230
231
  }
231
232
  break;
233
+ case ts.SyntaxKind.ParenthesizedType:
234
+ // 괄호로 묶인 타입 (예: (A & B)[] 에서 (A & B))
235
+ // 내부 타입을 재귀적으로 resolve
236
+ return resolveTypeNode((typeNode as ts.ParenthesizedTypeNode).type);
232
237
  case undefined:
233
238
  throw new Error(`typeNode undefined`);
234
239
  }
@@ -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
+ }
@@ -1,12 +1,14 @@
1
1
  import { hot } from "@sonamu-kit/hmr-hook";
2
2
  import assert from "assert";
3
3
  import chalk from "chalk";
4
+ import { EventEmitter } from "events";
4
5
  import { mkdir, readFile, writeFile } from "fs/promises";
5
6
  import inflection from "inflection";
6
7
  import { minimatch } from "minimatch";
7
8
  import path, { dirname } from "path";
8
9
  import { group, unique } from "radashi";
9
10
  import type { z } from "zod";
11
+ import type { WorkflowMetadata } from "..";
10
12
  import { registeredApis } from "../api/decorators";
11
13
  import { Sonamu } from "../api/sonamu";
12
14
  import { EntityManager, type EntityNamesRecord } from "../entity/entity-manager";
@@ -31,6 +33,7 @@ import {
31
33
  loadApis,
32
34
  loadModels,
33
35
  loadTypes,
36
+ loadWorkflows,
34
37
  } from "./module-loader";
35
38
 
36
39
  type DiffGroups = {
@@ -41,7 +44,9 @@ export class Syncer {
41
44
  apis: LoadedApis = [];
42
45
  types: LoadedTypes = {};
43
46
  models: LoadedModels = {};
47
+ workflows: Map<string, WorkflowMetadata[]> = new Map();
44
48
  isSyncing: boolean = false;
49
+ eventEmitter: EventEmitter = new EventEmitter();
45
50
 
46
51
  /**
47
52
  * 체크섬이 변경된 부분에 대해 싱크를 진행합니다.
@@ -97,17 +102,24 @@ export class Syncer {
97
102
  console.log(chalk.bold(`🔄 Invalidated:`));
98
103
 
99
104
  for (const invalidatedPath of invalidatedPaths) {
100
- // 만약 model.ts 파일이 변경(invalidate)되었다? 그러면 registeredApis 중에서 이 모델에 해당하는 api들은 지워줘요.
101
- // registeredApis는 통으로 날려버릴 없습니다. registeredApis 올라오는 친구들은 초기 로드시 또는 HMR시에만 등록되기 때문입니다.
102
- // 따라서 model.ts 파일의 변경으로 다음번 새로운 eval이 예상되는 시점에서만, 모델에서 나온 registeredApis들을 지워줄 수 있습니다.
103
- const removedApis = this.removeInvalidatedRegisteredApis(invalidatedPath);
104
- if (removedApis.length > 0) {
105
- console.log(
106
- chalk.blue(`- ${path.relative(Sonamu.apiRootPath, invalidatedPath)}`),
107
- chalk.gray(`(with ${removedApis.length} APIs)`),
105
+ try {
106
+ // 만약 model.ts 파일이 변경(invalidate)되었다? 그러면 registeredApis 중에서 모델에 해당하는 api들은 지워줘요.
107
+ // registeredApis는 통으로 날려버릴 없습니다. registeredApis에 올라오는 친구들은 초기 로드시 또는 HMR시에만 등록되기 때문입니다.
108
+ // 따라서 model.ts 파일의 변경으로 다음번 새로운 eval이 예상되는 이 시점에서만, 이 모델에서 나온 registeredApis들을 지워줄 수 있습니다.
109
+ const removedApis = this.removeInvalidatedRegisteredApis(invalidatedPath);
110
+ if (removedApis.length > 0) {
111
+ console.log(
112
+ chalk.blue(`- ${path.relative(Sonamu.apiRootPath, invalidatedPath)}`),
113
+ chalk.gray(`(with ${removedApis.length} APIs)`),
114
+ );
115
+ } else {
116
+ console.log(chalk.blue(`- ${path.relative(Sonamu.apiRootPath, invalidatedPath)}`));
117
+ }
118
+ } catch (e) {
119
+ console.error(e);
120
+ console.error(
121
+ chalk.red(`Failed to remove invalidated registered APIs for ${invalidatedPath}`),
108
122
  );
109
- } else {
110
- console.log(chalk.blue(`- ${path.relative(Sonamu.apiRootPath, invalidatedPath)}`));
111
123
  }
112
124
  }
113
125
  }
@@ -127,8 +139,9 @@ export class Syncer {
127
139
  await this.autoloadTypes();
128
140
  await this.autoloadModels();
129
141
  await this.autoloadApis();
142
+ await this.autoloadWorkflows();
130
143
 
131
- this.syncUI();
144
+ this.eventEmitter.emit("onHMRCompleted");
132
145
  }
133
146
 
134
147
  removeInvalidatedRegisteredApis(
@@ -199,6 +212,11 @@ export class Syncer {
199
212
  this.apis = await loadApis();
200
213
  }
201
214
 
215
+ async autoloadWorkflows() {
216
+ this.workflows = await loadWorkflows();
217
+ await Sonamu.workflows.synchronize(this.workflows);
218
+ }
219
+
202
220
  /**
203
221
  * 실제 싱크를 수행하는 본체입니다.
204
222
  * 변경된 파일들을 타입별로 분류하고 각 타입에 맞는 액션을 실행합니다.
@@ -235,6 +253,11 @@ export class Syncer {
235
253
  await this.actionSyncConfig();
236
254
  }
237
255
 
256
+ // 트리거: workflow
257
+ if (diffTypes.includes("workflow")) {
258
+ await this.autoloadWorkflows();
259
+ }
260
+
238
261
  return {
239
262
  diffTypes,
240
263
  };
@@ -537,16 +560,6 @@ export class Syncer {
537
560
  );
538
561
  }
539
562
 
540
- syncUI() {
541
- const uiPort = Sonamu.config.ui?.port ?? 57000;
542
-
543
- if (!isTest()) {
544
- fetch(`http://127.0.0.1:${uiPort}/api/reload`, {
545
- method: "GET",
546
- }).catch((e) => console.log(chalk.dim(`Failed to reload Sonamu UI: ${e.message}`)));
547
- }
548
- }
549
-
550
563
  /**
551
564
  * 하위호환용 프록시 메소드입니다.
552
565
  */
@@ -0,0 +1,71 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import type {
3
+ SchemaInput,
4
+ SchemaOutput,
5
+ StandardSchemaV1,
6
+ WorkflowSpec,
7
+ } from "@sonamu-kit/tasks/internal";
8
+ import inflection from "inflection";
9
+ import type { Executable } from "../types/types";
10
+ import type { WorkflowFunction } from "./workflow-manager";
11
+
12
+ // 워크플로우의 메타데이터 객체
13
+ export interface WorkflowMetadata {
14
+ type: "workflow";
15
+ id: string;
16
+ name: string;
17
+ version: string | null;
18
+ schema: StandardSchemaV1<unknown, unknown> | undefined;
19
+ schedules: {
20
+ name: string;
21
+ expression: string;
22
+ input: Executable<SchemaInput<unknown, unknown> | undefined>;
23
+ }[];
24
+ fn: WorkflowFunction<unknown, unknown>;
25
+ }
26
+
27
+ // 워크플로우 정의 과정에서의 옵션
28
+ export type DefineWorkflowOptions<
29
+ Input,
30
+ Output,
31
+ TSchema extends StandardSchemaV1 | undefined = undefined,
32
+ > = Omit<
33
+ WorkflowSpec<SchemaOutput<TSchema, Input>, Output, SchemaInput<TSchema, Input>>,
34
+ "name"
35
+ > & {
36
+ name?: string;
37
+ schedules?: {
38
+ name?: string;
39
+ expression: string;
40
+ input?: Executable<SchemaInput<TSchema, Input> | undefined>;
41
+ }[];
42
+ };
43
+
44
+ // 워크플로우 정의를 위한 데코레이터,
45
+ // 이것들은 syncer에서 한번에 load한 다음, WorkflowManager에서 synchronize를 통해 등록됨.
46
+ export function workflow<Input, Output, TSchema extends StandardSchemaV1 | undefined = undefined>(
47
+ options: DefineWorkflowOptions<Input, Output, TSchema>,
48
+ ) {
49
+ return (fn: WorkflowFunction<SchemaOutput<TSchema, Input>, Output>) => {
50
+ const id = randomUUID();
51
+ const workflowName = options.name ?? inflection.underscore(fn.name);
52
+
53
+ const decorated: WorkflowMetadata = {
54
+ type: "workflow" as const,
55
+ id,
56
+ name: workflowName,
57
+ schema: options.schema,
58
+ version: options.version ?? null,
59
+ schedules: (options.schedules ?? []).map((schedule) => {
60
+ return {
61
+ name: schedule.name ?? `${workflowName}[${schedule.expression}]`,
62
+ expression: schedule.expression,
63
+ input: schedule.input,
64
+ };
65
+ }),
66
+ fn: fn as WorkflowFunction<unknown, unknown>,
67
+ };
68
+
69
+ return decorated;
70
+ };
71
+ }
@@ -0,0 +1,84 @@
1
+ import type { DurationString, StepApi } from "@sonamu-kit/tasks/internal";
2
+ import inflection from "inflection";
3
+
4
+ export type StepFunction<TArgs extends unknown[], TResult> = (...args: TArgs) => TResult;
5
+ export type RunnableStep<TArgs extends unknown[], TResult> = {
6
+ run: StepFunction<TArgs, Promise<TResult>>;
7
+ };
8
+
9
+ export type MethodNames<T, TKey extends keyof T> = T[TKey] extends (
10
+ ...args: infer _TArgs
11
+ ) => infer _TResult
12
+ ? TKey
13
+ : never;
14
+
15
+ export type MethodArguments<T, TKey extends keyof T> = T[TKey] extends (
16
+ ...args: infer TArgs
17
+ ) => unknown
18
+ ? TArgs
19
+ : never;
20
+
21
+ export type MethodReturnType<T, TKey extends keyof T> = T[TKey] extends (
22
+ this: T,
23
+ ...args: infer _TArgs
24
+ ) => infer TResult
25
+ ? TResult
26
+ : never;
27
+
28
+ export class StepWrapper {
29
+ readonly #stepApi: StepApi;
30
+
31
+ constructor(stepApi: StepApi) {
32
+ this.#stepApi = stepApi;
33
+ }
34
+
35
+ get<
36
+ T,
37
+ TKey extends keyof T,
38
+ TArgs extends MethodArguments<T, TKey>,
39
+ TResult extends MethodReturnType<T, TKey>,
40
+ >(config: { name: string }, object: T, name: MethodNames<T, TKey>): RunnableStep<TArgs, TResult>;
41
+ get<
42
+ T,
43
+ TKey extends keyof T,
44
+ TArgs extends MethodArguments<T, TKey>,
45
+ TResult extends MethodReturnType<T, TKey>,
46
+ >(object: T, name: MethodNames<T, TKey>): RunnableStep<TArgs, TResult>;
47
+ get<
48
+ T,
49
+ TKey extends keyof T,
50
+ TArgs extends MethodArguments<T, TKey>,
51
+ TResult extends MethodReturnType<T, TKey>,
52
+ >(
53
+ ...args: [{ name: string }, T, MethodNames<T, TKey>] | [T, MethodNames<T, TKey>]
54
+ ): RunnableStep<TArgs, TResult> {
55
+ let config: { name: string };
56
+ let fn: StepFunction<TArgs, Exclude<TResult, never>>;
57
+
58
+ if (args.length === 2) {
59
+ const [rawObject, methodName] = args;
60
+ const method = rawObject[methodName] as CallableFunction;
61
+ config = { name: inflection.underscore(methodName.toString()) };
62
+
63
+ fn = (...args: TArgs) => method.bind(rawObject)(...args);
64
+ } else {
65
+ const [rawConfig, rawObject, name] = args;
66
+ const method = rawObject[name] as CallableFunction;
67
+
68
+ config = { name: rawConfig.name ?? inflection.underscore(name.toString()) };
69
+ fn = (...args: TArgs) => method.bind(rawObject)(...args);
70
+ }
71
+
72
+ return {
73
+ run: ((stepApi: StepApi) => {
74
+ return (...args: TArgs) => {
75
+ return stepApi.run(config, () => fn(...args));
76
+ };
77
+ })(this.#stepApi),
78
+ };
79
+ }
80
+
81
+ sleep(name: string, duration: DurationString) {
82
+ return this.#stepApi.sleep(name, duration);
83
+ }
84
+ }