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.
- package/dist/api/config.d.ts +10 -6
- 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 +49 -5
- package/dist/bin/cli.js +118 -170
- 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 +82 -3
- package/dist/database/puri.d.ts.map +1 -1
- package/dist/database/puri.js +180 -14
- package/dist/database/puri.types.d.ts +33 -6
- package/dist/database/puri.types.d.ts.map +1 -1
- package/dist/database/puri.types.js +1 -1
- package/dist/database/puri.types.test-d.js +1 -1
- 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.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.d.ts.map +1 -1
- package/dist/syncer/api-parser.js +7 -2
- 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 +5 -1
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +28 -19
- 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/types/types.d.ts +26 -10
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +15 -2
- package/dist/ui/ai-api.d.ts +1 -0
- package/dist/ui/ai-api.d.ts.map +1 -0
- package/dist/ui/ai-api.js +50 -0
- package/dist/ui/ai-client.d.ts +1 -0
- package/dist/ui/ai-client.d.ts.map +1 -0
- package/dist/ui/ai-client.js +438 -0
- package/dist/ui/api.d.ts +3 -0
- package/dist/ui/api.d.ts.map +1 -0
- package/dist/ui/api.js +680 -0
- package/dist/ui-web/assets/brand-icons-Cu_C0hZ4.svg +1008 -0
- package/dist/ui-web/assets/brand-icons-F3SPCeH1.woff +0 -0
- package/dist/ui-web/assets/brand-icons-XL9sxUpA.woff2 +0 -0
- package/dist/ui-web/assets/brand-icons-sqJ2Pg7a.eot +0 -0
- package/dist/ui-web/assets/brand-icons-ubhWoxly.ttf +0 -0
- package/dist/ui-web/assets/flags-DOLqOU7Y.png +0 -0
- package/dist/ui-web/assets/icons-BOCtAERH.woff +0 -0
- package/dist/ui-web/assets/icons-CHzK1VD9.eot +0 -0
- package/dist/ui-web/assets/icons-D29ZQHHw.ttf +0 -0
- package/dist/ui-web/assets/icons-Du6TOHnR.woff2 +0 -0
- package/dist/ui-web/assets/icons-RwhydX30.svg +1518 -0
- package/dist/ui-web/assets/index-CpaB9P6g.css +1 -0
- package/dist/ui-web/assets/index-J9MCfjCd.js +95 -0
- package/dist/ui-web/assets/outline-icons-BfdLr8tr.svg +366 -0
- package/dist/ui-web/assets/outline-icons-DD8jm0uy.ttf +0 -0
- package/dist/ui-web/assets/outline-icons-DInHoiqI.woff2 +0 -0
- package/dist/ui-web/assets/outline-icons-LX8adJ4n.eot +0 -0
- package/dist/ui-web/assets/outline-icons-aQ88nltS.woff +0 -0
- package/dist/ui-web/assets/provider-utils_false-BKJD46kk.js +1 -0
- package/dist/ui-web/assets/provider-utils_false-Bu5lmX18.js +1 -0
- package/dist/ui-web/index.html +13 -0
- package/dist/ui-web/vite.svg +1 -0
- 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 +9 -13
- package/dist/vector/types.d.ts.map +1 -1
- package/dist/vector/types.js +1 -1
- package/package.json +9 -5
- package/src/api/config.ts +15 -11
- package/src/api/sonamu.ts +60 -6
- package/src/bin/cli.ts +57 -119
- 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 +238 -27
- package/src/database/puri.types.test-d.ts +1 -1
- package/src/database/puri.types.ts +49 -6
- 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.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 +6 -1
- package/src/syncer/file-patterns.ts +11 -9
- package/src/syncer/module-loader.ts +35 -0
- package/src/syncer/syncer.ts +34 -21
- 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/types/types.ts +20 -4
- package/src/ui/ai-api.ts +60 -0
- package/src/ui/ai-client.ts +499 -0
- package/src/ui/api.ts +786 -0
- 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 +10 -14
- 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
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { BackendPostgres, OpenWorkflow, type Worker } from "@sonamu-kit/tasks";
|
|
2
|
+
import type {
|
|
3
|
+
RunnableWorkflow,
|
|
4
|
+
SchemaInput,
|
|
5
|
+
SchemaOutput,
|
|
6
|
+
StandardSchemaV1,
|
|
7
|
+
StepApi,
|
|
8
|
+
WorkflowRunHandle,
|
|
9
|
+
WorkflowSpec,
|
|
10
|
+
} from "@sonamu-kit/tasks/internal";
|
|
11
|
+
import assert from "assert";
|
|
12
|
+
import type { Knex } from "knex";
|
|
13
|
+
import { schedule as cronSchedule, type ScheduledTask } from "node-cron";
|
|
14
|
+
import type { ZodObject } from "zod";
|
|
15
|
+
import type { Context } from "../api/context";
|
|
16
|
+
import { Sonamu } from "../api/sonamu";
|
|
17
|
+
import { Naite } from "../naite/naite";
|
|
18
|
+
import { createMockSSEFactory } from "../stream/sse";
|
|
19
|
+
import type { Executable } from "../types/types";
|
|
20
|
+
import type { WorkflowMetadata } from "./decorator";
|
|
21
|
+
import { StepWrapper } from "./step-wrapper";
|
|
22
|
+
|
|
23
|
+
export interface WorkflowOptions {
|
|
24
|
+
// worker에서 동시 실행할 태스크 수, 기본은 CPU 코어 수 - 1개
|
|
25
|
+
concurrency?: number;
|
|
26
|
+
|
|
27
|
+
// worker에서 사용할 pub/sub 여부, 기본은 true
|
|
28
|
+
usePubSub?: boolean;
|
|
29
|
+
|
|
30
|
+
// pub/sub으로 태스크를 수신했을 때 워크플로우 실행까지 지연 시간, 기본은 500ms
|
|
31
|
+
listenDelay?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Workflow 함수의 타입, @sonamu-kit/tasks와 다른 점은 step을 한번 감싼 형태.
|
|
35
|
+
export type WorkflowFunction<Input, Output> = (
|
|
36
|
+
params: Readonly<{
|
|
37
|
+
input: Input;
|
|
38
|
+
step: StepWrapper;
|
|
39
|
+
version: string | null;
|
|
40
|
+
}>,
|
|
41
|
+
) => Promise<Output> | Output;
|
|
42
|
+
|
|
43
|
+
// Workflow를 등록할 때를 위한 타입
|
|
44
|
+
export type WorkflowCreateOptions<
|
|
45
|
+
Input,
|
|
46
|
+
Output,
|
|
47
|
+
TSchema extends StandardSchemaV1 | undefined = undefined,
|
|
48
|
+
> = Omit<
|
|
49
|
+
WorkflowSpec<SchemaOutput<TSchema, Input>, Output, SchemaInput<TSchema, Input>>,
|
|
50
|
+
"version"
|
|
51
|
+
> & {
|
|
52
|
+
id: string;
|
|
53
|
+
version: string | null;
|
|
54
|
+
function: WorkflowFunction<SchemaOutput<TSchema, Input>, Output>;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export class WorkflowManager {
|
|
58
|
+
// backend 인스턴스
|
|
59
|
+
readonly #backend: BackendPostgres;
|
|
60
|
+
|
|
61
|
+
// OpenWorkflow 인스턴스
|
|
62
|
+
readonly #ow: OpenWorkflow;
|
|
63
|
+
|
|
64
|
+
// Worker 인스턴스 (없을 수 있으며, 이 때는 분산된 서버 환경에서 DB에 publish만 한다고 가정함)
|
|
65
|
+
#worker: Worker | null;
|
|
66
|
+
|
|
67
|
+
// 파일 경로 -> 파일에 정의된 워크플로우 메타데이터 목록
|
|
68
|
+
#workflowsMap: Map<string, WorkflowMetadata[]>;
|
|
69
|
+
|
|
70
|
+
// Task 이름 -> Task 정보 및 Input 값, Workflow ID
|
|
71
|
+
#scheduledTasks: Map<
|
|
72
|
+
string,
|
|
73
|
+
{
|
|
74
|
+
task: ScheduledTask;
|
|
75
|
+
inputFn: Executable<SchemaInput<unknown, unknown>>;
|
|
76
|
+
workflowId: string;
|
|
77
|
+
}
|
|
78
|
+
>;
|
|
79
|
+
|
|
80
|
+
private constructor(backend: BackendPostgres) {
|
|
81
|
+
this.#backend = backend;
|
|
82
|
+
this.#ow = new OpenWorkflow({ backend });
|
|
83
|
+
this.#worker = null;
|
|
84
|
+
this.#workflowsMap = new Map();
|
|
85
|
+
this.#scheduledTasks = new Map();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 정의된 워크플로우 및 워크플로우에 대한 scheduled tasks를 동기화합니다.
|
|
90
|
+
*/
|
|
91
|
+
async synchronize(workflowMap: Map<string, WorkflowMetadata[]>) {
|
|
92
|
+
// 1. 삭제된 파일은 일괄 삭제
|
|
93
|
+
await Promise.allSettled(
|
|
94
|
+
Array.from(this.#workflowsMap.entries())
|
|
95
|
+
.filter(([key]) => !workflowMap.has(key))
|
|
96
|
+
.flatMap(([_, workflows]) =>
|
|
97
|
+
workflows.map((workflow) => {
|
|
98
|
+
return Promise.allSettled([
|
|
99
|
+
...workflow.schedules.map((schedule) => this.unscheduleTask(schedule.name)),
|
|
100
|
+
this.unregisterWorkflow(workflow),
|
|
101
|
+
]);
|
|
102
|
+
}),
|
|
103
|
+
),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// 2. 새로 추가된 파일은 일괄 등록
|
|
107
|
+
await Promise.allSettled(
|
|
108
|
+
Array.from(workflowMap.entries())
|
|
109
|
+
.filter(([key]) => !this.#workflowsMap.has(key))
|
|
110
|
+
.flatMap(([_, workflows]) =>
|
|
111
|
+
workflows.map((workflow) => {
|
|
112
|
+
this.registerWorkflow({
|
|
113
|
+
id: workflow.id,
|
|
114
|
+
name: workflow.name,
|
|
115
|
+
version: workflow.version ?? null,
|
|
116
|
+
schema: workflow.schema,
|
|
117
|
+
function: workflow.fn,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return Promise.allSettled(
|
|
121
|
+
workflow.schedules.map((schedule) => this.scheduleTask(workflow, schedule)),
|
|
122
|
+
);
|
|
123
|
+
}),
|
|
124
|
+
),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// 3. 기존과 다른 것을 diff친 다음, 삭제 후 재등록
|
|
128
|
+
await Promise.allSettled(
|
|
129
|
+
Array.from(workflowMap.entries())
|
|
130
|
+
.filter(([key]) => this.#workflowsMap.has(key))
|
|
131
|
+
.map<[string, [WorkflowMetadata[], WorkflowMetadata[]]]>(([key, newWorkflows]) => {
|
|
132
|
+
const previousWorkflows = this.#workflowsMap.get(key);
|
|
133
|
+
assert(previousWorkflows, "previous workflows not found");
|
|
134
|
+
return [key, [previousWorkflows, newWorkflows]];
|
|
135
|
+
})
|
|
136
|
+
.map(async ([_, [previousWorkflows, newWorkflows]]) => {
|
|
137
|
+
// 기존 것들을 삭제부터 해야함.
|
|
138
|
+
await Promise.allSettled(
|
|
139
|
+
previousWorkflows
|
|
140
|
+
.filter((prevItem) => !newWorkflows.some((newItem) => newItem.id === prevItem.id))
|
|
141
|
+
.map((prevItem) => {
|
|
142
|
+
return Promise.allSettled([
|
|
143
|
+
...prevItem.schedules.map((schedule) => this.unscheduleTask(schedule.name)),
|
|
144
|
+
this.unregisterWorkflow(prevItem),
|
|
145
|
+
]);
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// 새로 추가된 것들을 등록
|
|
150
|
+
await Promise.allSettled(
|
|
151
|
+
newWorkflows
|
|
152
|
+
.filter(
|
|
153
|
+
(newItem) => !previousWorkflows.some((prevItem) => prevItem.id === newItem.id),
|
|
154
|
+
)
|
|
155
|
+
.map(async (newItem) => {
|
|
156
|
+
this.registerWorkflow({
|
|
157
|
+
id: newItem.id,
|
|
158
|
+
name: newItem.name,
|
|
159
|
+
version: newItem.version ?? null,
|
|
160
|
+
schema: newItem.schema,
|
|
161
|
+
function: newItem.fn,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return Promise.allSettled(
|
|
165
|
+
newItem.schedules.map((schedule) => this.scheduleTask(newItem, schedule)),
|
|
166
|
+
);
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
169
|
+
}),
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
this.#workflowsMap = workflowMap;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 워크플로우를 실행
|
|
176
|
+
run<Input, Output, TSchema extends StandardSchemaV1 | undefined = undefined>(
|
|
177
|
+
options: Omit<WorkflowCreateOptions<Input, Output, TSchema>, "function" | "schema" | "id">,
|
|
178
|
+
input: SchemaInput<TSchema, Input>,
|
|
179
|
+
): Promise<WorkflowRunHandle<Output>> {
|
|
180
|
+
return this.#ow.runWorkflow(
|
|
181
|
+
{
|
|
182
|
+
name: options.name,
|
|
183
|
+
version: options.version ?? undefined,
|
|
184
|
+
},
|
|
185
|
+
input,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// cron task를 등록
|
|
190
|
+
async scheduleTask(
|
|
191
|
+
workflow: Pick<WorkflowMetadata, "id" | "name" | "version">,
|
|
192
|
+
schedule: WorkflowMetadata["schedules"][number],
|
|
193
|
+
) {
|
|
194
|
+
const task = cronSchedule(
|
|
195
|
+
schedule.expression,
|
|
196
|
+
(async (
|
|
197
|
+
{ name, version }: Pick<WorkflowMetadata, "name" | "version">,
|
|
198
|
+
{ input }: WorkflowMetadata["schedules"][number],
|
|
199
|
+
) => {
|
|
200
|
+
const inputData = await (typeof input === "function"
|
|
201
|
+
? Promise.resolve(input())
|
|
202
|
+
: Promise.resolve(input));
|
|
203
|
+
|
|
204
|
+
return this.run({ name, version: version ?? null }, inputData);
|
|
205
|
+
}).bind(this, workflow, schedule),
|
|
206
|
+
{
|
|
207
|
+
name: schedule.name,
|
|
208
|
+
timezone: Sonamu.config.api.timezone,
|
|
209
|
+
noOverlap: false,
|
|
210
|
+
},
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
this.#scheduledTasks.set(schedule.name, {
|
|
214
|
+
task,
|
|
215
|
+
inputFn: schedule.input,
|
|
216
|
+
workflowId: workflow.id,
|
|
217
|
+
});
|
|
218
|
+
await task.start();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// cron task를 중지
|
|
222
|
+
async unscheduleTask(name: string) {
|
|
223
|
+
const taskItem = this.#scheduledTasks.get(name);
|
|
224
|
+
if (!taskItem) {
|
|
225
|
+
console.error("scheduled task not found", name);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
this.#scheduledTasks.delete(name);
|
|
230
|
+
await taskItem.task.stop();
|
|
231
|
+
await taskItem.task.destroy();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 워크플로우를 등록, 관련된 스케줄은 별도로 등록해야 함.
|
|
235
|
+
registerWorkflow<Input, Output, TSchema extends StandardSchemaV1 | undefined = undefined>(
|
|
236
|
+
options: WorkflowCreateOptions<Input, Output, TSchema>,
|
|
237
|
+
): RunnableWorkflow<SchemaOutput<TSchema, Input>, Output, SchemaInput<TSchema, Input>> {
|
|
238
|
+
const fn = async (
|
|
239
|
+
params: Readonly<{
|
|
240
|
+
input: SchemaOutput<TSchema, Input>;
|
|
241
|
+
step: StepApi;
|
|
242
|
+
version: string | null;
|
|
243
|
+
}>,
|
|
244
|
+
) => {
|
|
245
|
+
const baseContext = {
|
|
246
|
+
request: null,
|
|
247
|
+
reply: null,
|
|
248
|
+
headers: {},
|
|
249
|
+
createSSE: (schema: ZodObject) => createMockSSEFactory(schema),
|
|
250
|
+
naiteStore: Naite.createStore(),
|
|
251
|
+
user: null,
|
|
252
|
+
passport: {
|
|
253
|
+
login: async () => {},
|
|
254
|
+
logout: () => {},
|
|
255
|
+
},
|
|
256
|
+
} as unknown as Context;
|
|
257
|
+
|
|
258
|
+
const contextProvider = Sonamu.config.tasks?.contextProvider;
|
|
259
|
+
const context: Context = contextProvider
|
|
260
|
+
? await Promise.resolve(contextProvider(baseContext))
|
|
261
|
+
: baseContext;
|
|
262
|
+
|
|
263
|
+
const step = new StepWrapper(params.step);
|
|
264
|
+
return Sonamu.asyncLocalStorage.run({ context }, () =>
|
|
265
|
+
options.function({ input: params.input, step, version: params.version }),
|
|
266
|
+
);
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const workflow = this.#ow.defineWorkflow(
|
|
270
|
+
{
|
|
271
|
+
name: options.name,
|
|
272
|
+
version: options.version ?? undefined,
|
|
273
|
+
schema: options.schema,
|
|
274
|
+
},
|
|
275
|
+
fn,
|
|
276
|
+
);
|
|
277
|
+
return workflow;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 워크플로우를 등록 해제, 관련된 스케줄은 별도로 해제해야 함.
|
|
281
|
+
async unregisterWorkflow(workflow: Pick<WorkflowMetadata, "name" | "version" | "id">) {
|
|
282
|
+
this.#ow.unregisterWorkflow(workflow.name, workflow.version ?? null);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Worker를 설정 후 시작
|
|
286
|
+
async setupWorker(options: WorkflowOptions) {
|
|
287
|
+
this.#worker = this.#ow.newWorker(options);
|
|
288
|
+
await this.#worker.start();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Worker를 중지
|
|
292
|
+
async stopWorker() {
|
|
293
|
+
await this.#worker?.stop();
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// cron task들을 모두 중지
|
|
297
|
+
async stopSchedules() {
|
|
298
|
+
await Promise.allSettled(
|
|
299
|
+
Array.from(this.#scheduledTasks.values()).map(({ task }) => Promise.resolve(task.stop())),
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// cron task들을 모두 정리
|
|
304
|
+
async destroySchedules() {
|
|
305
|
+
await Promise.allSettled(
|
|
306
|
+
Array.from(this.#scheduledTasks.values()).map(({ task }) => Promise.resolve(task.destroy())),
|
|
307
|
+
);
|
|
308
|
+
this.#scheduledTasks.clear();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async destroy() {
|
|
312
|
+
await this.stopSchedules();
|
|
313
|
+
await this.stopWorker();
|
|
314
|
+
await this.destroySchedules();
|
|
315
|
+
await this.#backend.stop();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
[Symbol.asyncDispose]() {
|
|
319
|
+
return this.destroy();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// BackendPostgres에서 처리하는 것들이 있어서 Knex 커넥션이 아니라 설정값을 넣어줘야함.
|
|
323
|
+
static async create(
|
|
324
|
+
dbConf: Knex.Config,
|
|
325
|
+
runMigrations: boolean = true,
|
|
326
|
+
): Promise<WorkflowManager> {
|
|
327
|
+
const backend = await BackendPostgres.connect(dbConf, { runMigrations });
|
|
328
|
+
return new WorkflowManager(backend);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
@@ -190,6 +190,14 @@ export class Template__generated extends Template {
|
|
|
190
190
|
.map((prop) => (prop.type === "relation" ? `${prop.name}_id` : prop.name))
|
|
191
191
|
.concat("id");
|
|
192
192
|
|
|
193
|
+
/**
|
|
194
|
+
* hasVector props
|
|
195
|
+
* - vector 타입인 컬럼
|
|
196
|
+
*/
|
|
197
|
+
const hasVectorColumns = entity.props
|
|
198
|
+
.filter((prop) => prop.type === "vector" || prop.type === "vector[]")
|
|
199
|
+
.map((prop) => prop.name);
|
|
200
|
+
|
|
193
201
|
/**
|
|
194
202
|
* generated props
|
|
195
203
|
* - generated 속성이 있는 컬럼 (INSERT/UPDATE 시 값 제공 불가)
|
|
@@ -202,7 +210,8 @@ export class Template__generated extends Template {
|
|
|
202
210
|
fulltextColumns.length > 0 ||
|
|
203
211
|
virtualProps.length > 0 ||
|
|
204
212
|
hasDefaultColumns.length > 0 ||
|
|
205
|
-
generatedColumns.length > 0
|
|
213
|
+
generatedColumns.length > 0 ||
|
|
214
|
+
hasVectorColumns.length > 0;
|
|
206
215
|
|
|
207
216
|
const lines = [
|
|
208
217
|
`export const ${schemaName} = ${schemaBody};`,
|
|
@@ -217,15 +226,20 @@ export class Template__generated extends Template {
|
|
|
217
226
|
(virtualProps.length > 0
|
|
218
227
|
? `readonly __virtual__: readonly [${virtualProps.map((prop) => `"${prop}"`).join(", ")}],`
|
|
219
228
|
: "") +
|
|
229
|
+
(hasDefaultColumns.length > 0
|
|
230
|
+
? `readonly __hasDefault__: readonly [${hasDefaultColumns
|
|
231
|
+
.map((col) => `"${col}"`)
|
|
232
|
+
.join(", ")}],`
|
|
233
|
+
: "") +
|
|
220
234
|
(
|
|
221
|
-
|
|
222
|
-
? `readonly
|
|
235
|
+
generatedColumns.length > 0
|
|
236
|
+
? `readonly __generated__: readonly [${generatedColumns
|
|
223
237
|
.map((col) => `"${col}"`)
|
|
224
238
|
.join(", ")}],`
|
|
225
239
|
: ""
|
|
226
240
|
) +
|
|
227
|
-
(
|
|
228
|
-
? `readonly
|
|
241
|
+
(hasVectorColumns.length > 0
|
|
242
|
+
? `readonly __vector__: readonly [${hasVectorColumns
|
|
229
243
|
.map((col) => `"${col}"`)
|
|
230
244
|
.join(", ")}],`
|
|
231
245
|
: "")
|
|
@@ -265,7 +279,6 @@ export class Template__generated extends Template {
|
|
|
265
279
|
const filterBody = propNodes
|
|
266
280
|
.map((propNode) => propNodeToZodTypeDef(propNode, importKeys))
|
|
267
281
|
.join("\n");
|
|
268
|
-
|
|
269
282
|
const schemaBody = `
|
|
270
283
|
z.object({
|
|
271
284
|
num: z.number().int().nonnegative(),
|
package/src/types/types.ts
CHANGED
|
@@ -13,7 +13,7 @@ export function zArrayable<T extends z.ZodTypeAny>(shape: T): z.ZodUnion<[T, z.Z
|
|
|
13
13
|
export type DistributiveOmit<T, K extends keyof any> = T extends any ? Omit<T, K> : never;
|
|
14
14
|
|
|
15
15
|
/*
|
|
16
|
-
Model-
|
|
16
|
+
Model-Definition
|
|
17
17
|
*/
|
|
18
18
|
export type GeneratedColumnType = "STORED" | "VIRTUAL";
|
|
19
19
|
export type GeneratedColumn = {
|
|
@@ -227,7 +227,7 @@ export type EntityIndex = {
|
|
|
227
227
|
type: "index" | "unique" | "hnsw" | "ivfflat";
|
|
228
228
|
columns: EntityIndexColumn[];
|
|
229
229
|
name: string;
|
|
230
|
-
using?: "btree" | "hash" | "gin" | "gist";
|
|
230
|
+
using?: "btree" | "hash" | "gin" | "gist" | "pgroonga";
|
|
231
231
|
nullsNotDistinct?: boolean; // unique index only
|
|
232
232
|
/**
|
|
233
233
|
* HNSW (Hierarchical Navigable Small World) 인덱스: 각 노드의 최대 연결 수
|
|
@@ -492,6 +492,19 @@ export type SubsetQuery = {
|
|
|
492
492
|
export const SonamuQueryMode = z.enum(["both", "list", "count"]);
|
|
493
493
|
export type SonamuQueryMode = z.infer<typeof SonamuQueryMode>;
|
|
494
494
|
|
|
495
|
+
/* Semantic Query */
|
|
496
|
+
export const SonamuSemanticParams = z
|
|
497
|
+
.object({
|
|
498
|
+
semanticQuery: z.object({
|
|
499
|
+
embedding: z.array(z.number()).min(1024).max(1024),
|
|
500
|
+
threshold: z.number().optional(),
|
|
501
|
+
method: z.enum(["cosine", "l2", "inner_product"]).optional(),
|
|
502
|
+
which: z.string(),
|
|
503
|
+
}),
|
|
504
|
+
})
|
|
505
|
+
.partial();
|
|
506
|
+
export type SonamuSemanticParams = z.infer<typeof SonamuSemanticParams>;
|
|
507
|
+
|
|
495
508
|
/* Knex Migration */
|
|
496
509
|
export type KnexError = {
|
|
497
510
|
code: string;
|
|
@@ -557,7 +570,7 @@ export type MigrationIndex = {
|
|
|
557
570
|
type: "unique" | "index" | "hnsw" | "ivfflat";
|
|
558
571
|
columns: EntityIndexColumn[];
|
|
559
572
|
name: string;
|
|
560
|
-
using?: "btree" | "hash" | "gin" | "gist";
|
|
573
|
+
using?: "btree" | "hash" | "gin" | "gist" | "pgroonga";
|
|
561
574
|
nullsNotDistinct?: boolean;
|
|
562
575
|
/** HNSW (Hierarchical Navigable Small World): 각 노드의 최대 연결 수 */
|
|
563
576
|
m?: number;
|
|
@@ -1143,7 +1156,7 @@ const EntityIndexSchema = z
|
|
|
1143
1156
|
type: z.enum(["index", "unique", "hnsw", "ivfflat"]),
|
|
1144
1157
|
columns: z.array(EntityIndexColumnSchema),
|
|
1145
1158
|
name: z.string().min(1).max(63),
|
|
1146
|
-
using: z.enum(["btree", "hash", "gin", "gist"]).optional(),
|
|
1159
|
+
using: z.enum(["btree", "hash", "gin", "gist", "pgroonga"]).optional(),
|
|
1147
1160
|
nullsNotDistinct: z.boolean().optional(),
|
|
1148
1161
|
m: z.number().optional(),
|
|
1149
1162
|
efConstruction: z.number().optional(),
|
|
@@ -1334,6 +1347,9 @@ export type ManyToManyBaseSchema<FromIdKey extends string, ToIdKey extends strin
|
|
|
1334
1347
|
[K in `${ToIdKey}_id`]: number;
|
|
1335
1348
|
};
|
|
1336
1349
|
|
|
1350
|
+
// 객체, 함수, 비동기 함수를 모두 포괄하는 타입
|
|
1351
|
+
export type Executable<T> = T | Promise<T> | (() => T) | (() => Promise<T>);
|
|
1352
|
+
|
|
1337
1353
|
export type SonamuFastifyConfig = {
|
|
1338
1354
|
contextProvider: (
|
|
1339
1355
|
defaultContext: Pick<Context, "request" | "reply" | "headers" | "createSSE" | "naiteStore"> &
|
package/src/ui/ai-api.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// import { convertToModelMessages, type UIMessage } from "ai";
|
|
2
|
+
// import type { FastifyInstance } from "fastify";
|
|
3
|
+
// import { BadRequestException, type FixtureRecord } from "sonamu";
|
|
4
|
+
// import { aiClient } from "./ai-client";
|
|
5
|
+
|
|
6
|
+
// export async function setAiApi(server: FastifyInstance) {
|
|
7
|
+
// await aiClient.init();
|
|
8
|
+
|
|
9
|
+
// server.post("/api/ai/fixture/chat", async (request, reply) => {
|
|
10
|
+
// const { messages, fixtureRecords } = request.body as {
|
|
11
|
+
// messages: UIMessage[];
|
|
12
|
+
// fixtureRecords?: FixtureRecord[];
|
|
13
|
+
// };
|
|
14
|
+
|
|
15
|
+
// if (!fixtureRecords || fixtureRecords.length === 0) {
|
|
16
|
+
// throw new BadRequestException("픽스쳐 레코드가 없습니다. 픽스쳐 조회 후 시도하세요.");
|
|
17
|
+
// }
|
|
18
|
+
|
|
19
|
+
// const result = aiClient.handleFixture(convertToModelMessages(messages), fixtureRecords);
|
|
20
|
+
// const response = result.toUIMessageStreamResponse();
|
|
21
|
+
|
|
22
|
+
// reply.raw.writeHead(response.status, Object.fromEntries(response.headers.entries()));
|
|
23
|
+
|
|
24
|
+
// if (response.body) {
|
|
25
|
+
// const reader = response.body.getReader();
|
|
26
|
+
// while (true) {
|
|
27
|
+
// const { done, value } = await reader.read();
|
|
28
|
+
// if (done) break;
|
|
29
|
+
// reply.raw.write(value);
|
|
30
|
+
// }
|
|
31
|
+
// }
|
|
32
|
+
|
|
33
|
+
// reply.raw.end();
|
|
34
|
+
// return reply;
|
|
35
|
+
// });
|
|
36
|
+
|
|
37
|
+
// // Entity/Enum 생성용 AI Chat Stream
|
|
38
|
+
// server.post("/api/ai/entity/chat", async (request, reply) => {
|
|
39
|
+
// const { messages } = request.body as {
|
|
40
|
+
// messages: UIMessage[];
|
|
41
|
+
// };
|
|
42
|
+
|
|
43
|
+
// const result = aiClient.handleEntity(convertToModelMessages(messages));
|
|
44
|
+
// const response = result.toUIMessageStreamResponse();
|
|
45
|
+
|
|
46
|
+
// reply.raw.writeHead(response.status, Object.fromEntries(response.headers.entries()));
|
|
47
|
+
|
|
48
|
+
// if (response.body) {
|
|
49
|
+
// const reader = response.body.getReader();
|
|
50
|
+
// while (true) {
|
|
51
|
+
// const { done, value } = await reader.read();
|
|
52
|
+
// if (done) break;
|
|
53
|
+
// reply.raw.write(value);
|
|
54
|
+
// }
|
|
55
|
+
// }
|
|
56
|
+
|
|
57
|
+
// reply.raw.end();
|
|
58
|
+
// return reply;
|
|
59
|
+
// });
|
|
60
|
+
// }
|