sonamu 0.7.15 → 0.7.17
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/ai/providers/rtzr/error.d.ts +1 -1
- package/dist/ai/providers/rtzr/error.d.ts.map +1 -1
- package/dist/api/config.d.ts +1 -0
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +1 -1
- package/dist/api/decorators.d.ts +1 -1
- package/dist/api/decorators.d.ts.map +1 -1
- package/dist/api/decorators.js +1 -1
- package/dist/api/sonamu.d.ts +3 -1
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +51 -40
- package/dist/database/base-model.d.ts +16 -6
- package/dist/database/base-model.d.ts.map +1 -1
- package/dist/database/base-model.js +44 -3
- package/dist/database/base-model.types.d.ts +29 -48
- package/dist/database/base-model.types.d.ts.map +1 -1
- package/dist/database/base-model.types.js +12 -2
- package/dist/database/puri.d.ts +2 -1
- package/dist/database/puri.d.ts.map +1 -1
- package/dist/database/puri.js +2 -1
- package/dist/database/puri.types.d.ts +3 -3
- package/dist/database/puri.types.d.ts.map +1 -1
- package/dist/database/puri.types.js +1 -1
- package/dist/entity/entity-manager.d.ts +8 -4
- package/dist/entity/entity-manager.d.ts.map +1 -1
- package/dist/entity/entity.d.ts +10 -1
- package/dist/entity/entity.d.ts.map +1 -1
- package/dist/entity/entity.js +84 -39
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/syncer/checksum.d.ts +8 -3
- package/dist/syncer/checksum.d.ts.map +1 -1
- package/dist/syncer/checksum.js +17 -9
- package/dist/syncer/code-generator.js +7 -2
- package/dist/syncer/syncer.d.ts +6 -6
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +27 -13
- package/dist/tasks/workflow-manager.d.ts +3 -3
- package/dist/tasks/workflow-manager.d.ts.map +1 -1
- package/dist/tasks/workflow-manager.js +15 -11
- package/dist/template/implementations/generated.template.d.ts.map +1 -1
- package/dist/template/implementations/generated.template.js +8 -6
- package/dist/template/implementations/model.template.js +5 -5
- package/dist/template/implementations/services.template.d.ts +17 -0
- package/dist/template/implementations/services.template.d.ts.map +1 -0
- package/dist/template/implementations/services.template.js +159 -0
- package/dist/template/implementations/view_form.template.js +2 -2
- package/dist/template/implementations/view_id_async_select.template.js +2 -2
- package/dist/template/implementations/view_list.template.js +5 -5
- package/dist/types/types.d.ts +43 -25
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js +29 -17
- package/dist/ui/ai-api.d.ts +2 -0
- package/dist/ui/ai-api.d.ts.map +1 -1
- package/dist/ui/ai-api.js +43 -49
- package/dist/ui/ai-client.d.ts +10 -0
- package/dist/ui/ai-client.d.ts.map +1 -1
- package/dist/ui/ai-client.js +457 -437
- package/dist/ui/api.d.ts.map +1 -1
- package/dist/ui/api.js +14 -3
- package/dist/ui-web/assets/{index-J9MCfjCd.js → index-DzqUrTB-.js} +56 -59
- package/dist/ui-web/index.html +1 -1
- package/package.json +12 -8
- package/src/api/config.ts +3 -0
- package/src/api/decorators.ts +6 -1
- package/src/api/sonamu.ts +71 -52
- package/src/database/base-model.ts +66 -11
- package/src/database/base-model.types.ts +79 -76
- package/src/database/puri.ts +5 -1
- package/src/database/puri.types.ts +3 -6
- package/src/entity/entity.ts +83 -34
- package/src/index.ts +1 -0
- package/src/shared/app.shared.ts.txt +1 -1
- package/src/shared/web.shared.ts.txt +0 -43
- package/src/syncer/checksum.ts +31 -9
- package/src/syncer/code-generator.ts +8 -1
- package/src/syncer/syncer.ts +38 -26
- package/src/tasks/workflow-manager.ts +16 -12
- package/src/template/implementations/generated.template.ts +17 -3
- package/src/template/implementations/model.template.ts +4 -4
- package/src/template/implementations/services.template.ts +226 -0
- package/src/template/implementations/view_form.template.ts +1 -1
- package/src/template/implementations/view_id_async_select.template.ts +1 -1
- package/src/template/implementations/view_list.template.ts +4 -4
- package/src/types/types.ts +33 -16
- package/src/ui/ai-api.ts +61 -60
- package/src/ui/ai-client.ts +535 -499
- package/src/ui/api.ts +14 -2
- package/src/ui/entity.instructions.md +536 -0
- package/dist/template/implementations/service.template.d.ts +0 -29
- package/dist/template/implementations/service.template.d.ts.map +0 -1
- package/dist/template/implementations/service.template.js +0 -202
- package/dist/ui-web/assets/provider-utils_false-BKJD46kk.js +0 -1
- package/dist/ui-web/assets/provider-utils_false-Bu5lmX18.js +0 -1
- package/src/template/implementations/service.template.ts +0 -328
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import inflection from "inflection";
|
|
2
|
+
import { diff, unique } from "radashi";
|
|
3
|
+
import {
|
|
4
|
+
apiParamToTsCode,
|
|
5
|
+
apiParamTypeToTsType,
|
|
6
|
+
unwrapPromiseOnce,
|
|
7
|
+
} from "../../api/code-converters";
|
|
8
|
+
import type { ExtendedApi } from "../../api/decorators";
|
|
9
|
+
import { Sonamu } from "../../api/sonamu";
|
|
10
|
+
import type { TemplateOptions } from "../../types/types";
|
|
11
|
+
import { ApiParamType } from "../../types/types";
|
|
12
|
+
import { assertDefined } from "../../utils/utils";
|
|
13
|
+
import { Template } from "../template";
|
|
14
|
+
|
|
15
|
+
export class Template__services extends Template {
|
|
16
|
+
constructor() {
|
|
17
|
+
super("services");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getTargetAndPath() {
|
|
21
|
+
return {
|
|
22
|
+
target: ":target/src/services",
|
|
23
|
+
path: `services.generated.ts`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
render({}: TemplateOptions["services"]) {
|
|
28
|
+
const { apis } = Sonamu.syncer;
|
|
29
|
+
|
|
30
|
+
// 모델별로 그룹화
|
|
31
|
+
const apisByModel = new Map<string, ExtendedApi[]>();
|
|
32
|
+
for (const api of apis) {
|
|
33
|
+
const modelName = api.modelName.replace(/Model$/, "").replace(/Frame$/, "");
|
|
34
|
+
if (!apisByModel.has(modelName)) {
|
|
35
|
+
apisByModel.set(modelName, []);
|
|
36
|
+
}
|
|
37
|
+
apisByModel.get(modelName)?.push(api);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const importKeys: string[] = [];
|
|
41
|
+
const namespaces: string[] = [];
|
|
42
|
+
let typeParamNames: string[] = [];
|
|
43
|
+
|
|
44
|
+
for (const [modelName, modelApis] of apisByModel) {
|
|
45
|
+
const functions: string[] = [];
|
|
46
|
+
|
|
47
|
+
for (const api of modelApis) {
|
|
48
|
+
// Context 제외한 파라미터
|
|
49
|
+
const paramsWithoutContext = api.parameters.filter(
|
|
50
|
+
(param) =>
|
|
51
|
+
!ApiParamType.isContext(param.type) &&
|
|
52
|
+
!ApiParamType.isRefKnex(param.type) &&
|
|
53
|
+
!(param.optional === true && param.name.startsWith("_")),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// 타입 파라미터 정의
|
|
57
|
+
const typeParametersAsTsType = api.typeParameters
|
|
58
|
+
.map((typeParam) => apiParamTypeToTsType(typeParam, importKeys))
|
|
59
|
+
.join(", ");
|
|
60
|
+
const typeParamsDef = typeParametersAsTsType ? `<${typeParametersAsTsType}>` : "";
|
|
61
|
+
typeParamNames = typeParamNames.concat(api.typeParameters.map((tp) => tp.id));
|
|
62
|
+
|
|
63
|
+
// 파라미터 정의
|
|
64
|
+
const paramsDef = apiParamToTsCode(paramsWithoutContext, importKeys);
|
|
65
|
+
const paramNames = paramsWithoutContext.map((p) => p.name).join(", ");
|
|
66
|
+
|
|
67
|
+
// 리턴 타입 정의
|
|
68
|
+
const returnTypeDef = apiParamTypeToTsType(
|
|
69
|
+
assertDefined(unwrapPromiseOnce(api.returnType)),
|
|
70
|
+
importKeys,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// 기본 URL
|
|
74
|
+
const apiBaseUrl = `${Sonamu.config.api.route.prefix}${api.path}`;
|
|
75
|
+
|
|
76
|
+
const clients = api.options.clients || [];
|
|
77
|
+
|
|
78
|
+
// 1. axios 함수 생성
|
|
79
|
+
// resourceName이 있으면 get + resourceName 형태로 함수명 생성
|
|
80
|
+
const methodName = api.options.resourceName
|
|
81
|
+
? `get${inflection.camelize(api.options.resourceName)}`
|
|
82
|
+
: api.methodName;
|
|
83
|
+
|
|
84
|
+
// axios-multipart 처리 (파일 업로드)
|
|
85
|
+
if (clients.includes("axios-multipart")) {
|
|
86
|
+
const isMultiple = api.uploadOptions?.mode === "multiple";
|
|
87
|
+
const fileParamName = isMultiple ? "files" : "file";
|
|
88
|
+
const fileParamType = isMultiple ? "File[]" : "File";
|
|
89
|
+
|
|
90
|
+
const formDataAppend = isMultiple
|
|
91
|
+
? `${fileParamName}.forEach(f => { formData.append("${fileParamName}", f); });`
|
|
92
|
+
: `formData.append("${fileParamName}", ${fileParamName});`;
|
|
93
|
+
|
|
94
|
+
const otherParamsAppend = paramsWithoutContext
|
|
95
|
+
.map((param) => `formData.append('${param.name}', String(${param.name}));`)
|
|
96
|
+
.join("\n ");
|
|
97
|
+
|
|
98
|
+
const paramsDefComma = paramsDef !== "" ? ", " : "";
|
|
99
|
+
functions.push(
|
|
100
|
+
`
|
|
101
|
+
export async function ${methodName}${typeParamsDef}(
|
|
102
|
+
${paramsDef}${paramsDefComma}
|
|
103
|
+
${fileParamName}: ${fileParamType},
|
|
104
|
+
onUploadProgress?: (pe: AxiosProgressEvent) => void
|
|
105
|
+
): Promise<${returnTypeDef}> {
|
|
106
|
+
const formData = new FormData();
|
|
107
|
+
${formDataAppend}
|
|
108
|
+
${otherParamsAppend}
|
|
109
|
+
return fetch({
|
|
110
|
+
method: 'POST',
|
|
111
|
+
url: \`${apiBaseUrl}\`,
|
|
112
|
+
headers: {
|
|
113
|
+
"Content-Type": "multipart/form-data",
|
|
114
|
+
},
|
|
115
|
+
onUploadProgress,
|
|
116
|
+
data: formData,
|
|
117
|
+
${api.options.timeout ? `signal: AbortSignal.timeout(${api.options.timeout}),` : ""}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
`.trim(),
|
|
121
|
+
);
|
|
122
|
+
} else if (api.options.httpMethod === "GET") {
|
|
123
|
+
const hasParams = paramsWithoutContext.length > 0;
|
|
124
|
+
functions.push(
|
|
125
|
+
`
|
|
126
|
+
export async function ${methodName}${typeParamsDef}(${paramsDef}): Promise<${returnTypeDef}> {
|
|
127
|
+
return fetch({
|
|
128
|
+
method: "GET",
|
|
129
|
+
url: \`${apiBaseUrl}${hasParams ? `?\${qs.stringify({ ${paramNames} })}` : ""}\`,
|
|
130
|
+
${api.options.timeout ? `signal: AbortSignal.timeout(${api.options.timeout}),` : ""}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
`.trim(),
|
|
134
|
+
);
|
|
135
|
+
} else {
|
|
136
|
+
const hasParams = paramsWithoutContext.length > 0;
|
|
137
|
+
functions.push(
|
|
138
|
+
`
|
|
139
|
+
export async function ${methodName}${typeParamsDef}(${paramsDef}): Promise<${returnTypeDef}> {
|
|
140
|
+
return fetch({
|
|
141
|
+
method: "${api.options.httpMethod}",
|
|
142
|
+
url: \`${apiBaseUrl}\`,
|
|
143
|
+
${hasParams ? `data: { ${paramNames} },` : ""}
|
|
144
|
+
${api.options.timeout ? `signal: AbortSignal.timeout(${api.options.timeout}),` : ""}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
`.trim(),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 2. queryOptions + useQuery (tanstack-query)
|
|
152
|
+
if (clients.includes("tanstack-query")) {
|
|
153
|
+
const hookName = api.options.resourceName
|
|
154
|
+
? inflection.camelize(api.options.resourceName, true)
|
|
155
|
+
: inflection.camelize(api.methodName, true);
|
|
156
|
+
|
|
157
|
+
// queryOptions
|
|
158
|
+
functions.push(
|
|
159
|
+
`
|
|
160
|
+
export const ${methodName}QueryOptions = ${typeParamsDef}(${paramsDef}) => queryOptions({
|
|
161
|
+
queryKey: ['${modelName}', '${methodName}'${paramNames ? `, ${paramNames}` : ""}],
|
|
162
|
+
queryFn: () => ${methodName}(${paramNames})
|
|
163
|
+
});
|
|
164
|
+
`.trim(),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// useQuery hook
|
|
168
|
+
functions.push(
|
|
169
|
+
`
|
|
170
|
+
export const use${inflection.camelize(hookName)} = ${typeParamsDef}(${paramsDef}${
|
|
171
|
+
paramsDef ? ", " : ""
|
|
172
|
+
}options?: { enabled?: boolean }) =>
|
|
173
|
+
useQuery({
|
|
174
|
+
...${methodName}QueryOptions(${paramNames}),
|
|
175
|
+
...options
|
|
176
|
+
});
|
|
177
|
+
`.trim(),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 3. useMutation (tanstack-mutation)
|
|
182
|
+
if (clients.includes("tanstack-mutation")) {
|
|
183
|
+
const hookName = inflection.camelize(api.methodName);
|
|
184
|
+
const mutationParamType =
|
|
185
|
+
paramsWithoutContext.length > 0
|
|
186
|
+
? `{ ${paramsWithoutContext
|
|
187
|
+
.map((p) => `${p.name}: ${apiParamTypeToTsType(p.type, [])}`)
|
|
188
|
+
.join(", ")} }`
|
|
189
|
+
: "void";
|
|
190
|
+
const mutationParamNames =
|
|
191
|
+
paramsWithoutContext.length > 0
|
|
192
|
+
? paramsWithoutContext.map((p) => `params.${p.name}`).join(", ")
|
|
193
|
+
: "";
|
|
194
|
+
|
|
195
|
+
functions.push(
|
|
196
|
+
`
|
|
197
|
+
export const use${hookName}Mutation = ${typeParamsDef}() => useMutation({
|
|
198
|
+
mutationFn: (params: ${mutationParamType}) => ${methodName}(${mutationParamNames})
|
|
199
|
+
});
|
|
200
|
+
`.trim(),
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
namespaces.push(
|
|
206
|
+
`
|
|
207
|
+
export namespace ${modelName}Service {
|
|
208
|
+
${functions.join("\n\n")}
|
|
209
|
+
}
|
|
210
|
+
`.trim(),
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
...this.getTargetAndPath(),
|
|
216
|
+
body: namespaces.join("\n\n"),
|
|
217
|
+
importKeys: diff(unique(importKeys), [...typeParamNames, "ListResult"]),
|
|
218
|
+
customHeaders: [
|
|
219
|
+
`import { queryOptions, useQuery, useMutation } from '@tanstack/react-query';`,
|
|
220
|
+
`import type { AxiosProgressEvent } from 'axios';`,
|
|
221
|
+
`import qs from 'qs';`,
|
|
222
|
+
`import { type ListResult, fetch } from './sonamu.shared';`,
|
|
223
|
+
],
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -256,7 +256,7 @@ import { defaultCatch } from '@/services/sonamu.shared';
|
|
|
256
256
|
// import { useCommonModal } from "@/admin-common/CommonModal";
|
|
257
257
|
|
|
258
258
|
import { ${names.capital}SaveParams } from '@/services/${names.fs}/${names.fs}.types';
|
|
259
|
-
import { ${names.capital}Service } from '@/services
|
|
259
|
+
import { ${names.capital}Service } from '@/services/services.generated';
|
|
260
260
|
import { ${names.capital}SubsetA } from '@/services/sonamu.generated';
|
|
261
261
|
${unique(
|
|
262
262
|
columns
|
|
@@ -40,7 +40,7 @@ import { DropdownProps, DropdownItemProps, DropdownOnSearchChangeData, Dropdown
|
|
|
40
40
|
import { ${names.capital}SubsetKey, ${
|
|
41
41
|
names.capital
|
|
42
42
|
}SubsetMapping } from "@/services/sonamu.generated";
|
|
43
|
-
import { ${names.capital}Service } from "@/services
|
|
43
|
+
import { ${names.capital}Service } from "@/services/services.generated";
|
|
44
44
|
import { ${names.capital}ListParams } from "@/services/${names.fs}/${names.fs}.types";
|
|
45
45
|
|
|
46
46
|
export function ${names.capital}IdAsyncSelect<T extends ${names.capital}SubsetKey>(
|
|
@@ -326,7 +326,7 @@ import { DateTime } from "luxon";
|
|
|
326
326
|
import { DelButton, EditButton, AppBreadcrumbs, AddButton, useSelection, useListParams, SonamuCol, numF, formatDate, formatDateTime } from '@sonamu-kit/react-sui';
|
|
327
327
|
|
|
328
328
|
import { ${names.capital}SubsetA } from "@/services/sonamu.generated";
|
|
329
|
-
import { ${names.capital}Service } from '@/services
|
|
329
|
+
import { ${names.capital}Service } from '@/services/services.generated';
|
|
330
330
|
import { ${names.capital}ListParams } from '@/services/${names.fs}/${names.fs}.types';
|
|
331
331
|
${columnImports}
|
|
332
332
|
${filterColumns
|
|
@@ -346,7 +346,7 @@ export default function ${names.capital}List({}: ${names.capital}ListProps) {
|
|
|
346
346
|
});
|
|
347
347
|
|
|
348
348
|
// 리스트 쿼리
|
|
349
|
-
const { data,
|
|
349
|
+
const { data, refetch, isLoading } = ${names.capital}Service.use${
|
|
350
350
|
names.capitalPlural
|
|
351
351
|
}('A', listParams);
|
|
352
352
|
const { rows, total } = data ?? {};
|
|
@@ -359,7 +359,7 @@ export default function ${names.capital}List({}: ${names.capital}ListProps) {
|
|
|
359
359
|
}
|
|
360
360
|
|
|
361
361
|
${names.capital}Service.del(ids).then(() => {
|
|
362
|
-
|
|
362
|
+
refetch();
|
|
363
363
|
});
|
|
364
364
|
};
|
|
365
365
|
|
|
@@ -371,7 +371,7 @@ export default function ${names.capital}List({}: ${names.capital}ListProps) {
|
|
|
371
371
|
}
|
|
372
372
|
|
|
373
373
|
${names.capital}Service.del(selectedKeys).then(() => {
|
|
374
|
-
|
|
374
|
+
refetch();
|
|
375
375
|
});
|
|
376
376
|
};
|
|
377
377
|
|
package/src/types/types.ts
CHANGED
|
@@ -104,6 +104,7 @@ export type UuidArrayProp = CommonProp & {
|
|
|
104
104
|
export type VirtualProp = CommonProp & {
|
|
105
105
|
type: "virtual";
|
|
106
106
|
id: string;
|
|
107
|
+
virtualType?: "query" | "code"; // default: "code"
|
|
107
108
|
}; // PG: none / TS: any(id) / JSON: any
|
|
108
109
|
export type VectorProp = CommonProp & {
|
|
109
110
|
type: "vector";
|
|
@@ -264,6 +265,17 @@ export type EntityIndex = {
|
|
|
264
265
|
*/
|
|
265
266
|
lists?: number;
|
|
266
267
|
};
|
|
268
|
+
|
|
269
|
+
// SubsetField 타입: string 또는 internal 옵션이 있는 객체
|
|
270
|
+
export type SubsetField = string | { field: string; internal?: boolean };
|
|
271
|
+
|
|
272
|
+
export function normalizeSubsetField(f: SubsetField): string {
|
|
273
|
+
return typeof f === "string" ? f : f.field;
|
|
274
|
+
}
|
|
275
|
+
export function isInternalSubsetField(f: SubsetField): boolean {
|
|
276
|
+
return typeof f !== "string" && f.internal === true;
|
|
277
|
+
}
|
|
278
|
+
|
|
267
279
|
export type EntityJson = {
|
|
268
280
|
id: string;
|
|
269
281
|
parentId?: string;
|
|
@@ -272,7 +284,7 @@ export type EntityJson = {
|
|
|
272
284
|
props: EntityProp[];
|
|
273
285
|
indexes: EntityIndex[];
|
|
274
286
|
subsets: {
|
|
275
|
-
[subset: string]:
|
|
287
|
+
[subset: string]: SubsetField[];
|
|
276
288
|
};
|
|
277
289
|
enums: {
|
|
278
290
|
[enumId: string]: {
|
|
@@ -285,6 +297,9 @@ export type EntitySubsetRow = {
|
|
|
285
297
|
has: {
|
|
286
298
|
[key: string]: boolean;
|
|
287
299
|
};
|
|
300
|
+
isInternal: {
|
|
301
|
+
[key: string]: boolean;
|
|
302
|
+
};
|
|
288
303
|
children: EntitySubsetRow[];
|
|
289
304
|
prefixes: string[];
|
|
290
305
|
relationEntity?: string;
|
|
@@ -413,6 +428,14 @@ export function isJsonProp(p: unknown): p is JsonProp {
|
|
|
413
428
|
export function isVirtualProp(p: unknown): p is VirtualProp {
|
|
414
429
|
return (p as VirtualProp)?.type === "virtual";
|
|
415
430
|
}
|
|
431
|
+
export function isVirtualCodeProp(p: unknown): p is VirtualProp {
|
|
432
|
+
if (!isVirtualProp(p)) return false;
|
|
433
|
+
return p.virtualType !== "query"; // undefined도 "code"로 취급
|
|
434
|
+
}
|
|
435
|
+
export function isVirtualQueryProp(p: unknown): p is VirtualProp {
|
|
436
|
+
if (!isVirtualProp(p)) return false;
|
|
437
|
+
return p.virtualType === "query";
|
|
438
|
+
}
|
|
416
439
|
export function isVectorSingleProp(p: unknown): p is VectorProp {
|
|
417
440
|
return (p as VectorProp)?.type === "vector";
|
|
418
441
|
}
|
|
@@ -951,6 +974,7 @@ const VirtualPropSchema = z
|
|
|
951
974
|
...BasePropFields,
|
|
952
975
|
type: z.literal("virtual"),
|
|
953
976
|
id: z.string(),
|
|
977
|
+
virtualType: z.enum(["query", "code"]).optional(),
|
|
954
978
|
})
|
|
955
979
|
.strict();
|
|
956
980
|
|
|
@@ -1172,7 +1196,12 @@ export const EntityJsonSchema = z
|
|
|
1172
1196
|
parentId: z.string().optional().describe("부모 Entity ID"),
|
|
1173
1197
|
props: z.array(EntityPropSchema),
|
|
1174
1198
|
indexes: z.array(EntityIndexSchema),
|
|
1175
|
-
subsets: z.record(
|
|
1199
|
+
subsets: z.record(
|
|
1200
|
+
z.string(),
|
|
1201
|
+
z.array(
|
|
1202
|
+
z.union([z.string(), z.object({ field: z.string(), internal: z.boolean().optional() })]),
|
|
1203
|
+
),
|
|
1204
|
+
),
|
|
1176
1205
|
enums: z.record(z.string(), z.record(z.string(), z.string())),
|
|
1177
1206
|
})
|
|
1178
1207
|
.strict();
|
|
@@ -1208,19 +1237,7 @@ export const TemplateOptions = z.object({
|
|
|
1208
1237
|
bridge: z.object({
|
|
1209
1238
|
entityId: z.string(),
|
|
1210
1239
|
}),
|
|
1211
|
-
|
|
1212
|
-
namesRecord: z.object({
|
|
1213
|
-
fs: z.string(),
|
|
1214
|
-
fsPlural: z.string(),
|
|
1215
|
-
camel: z.string(),
|
|
1216
|
-
camelPlural: z.string(),
|
|
1217
|
-
capital: z.string(),
|
|
1218
|
-
capitalPlural: z.string(),
|
|
1219
|
-
upper: z.string(),
|
|
1220
|
-
constant: z.string(),
|
|
1221
|
-
}),
|
|
1222
|
-
modelTsPath: z.string(),
|
|
1223
|
-
}),
|
|
1240
|
+
services: z.object({}),
|
|
1224
1241
|
view_list: z.object({
|
|
1225
1242
|
entityId: z.string(),
|
|
1226
1243
|
extra: z.unknown(),
|
|
@@ -1273,7 +1290,7 @@ export const TemplateKey = z.enum([
|
|
|
1273
1290
|
"model",
|
|
1274
1291
|
"model_test",
|
|
1275
1292
|
"bridge",
|
|
1276
|
-
"
|
|
1293
|
+
"services",
|
|
1277
1294
|
"view_list",
|
|
1278
1295
|
"view_list_columns",
|
|
1279
1296
|
"view_search_input",
|
package/src/ui/ai-api.ts
CHANGED
|
@@ -1,60 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
1
|
+
import { convertToModelMessages, type UIMessage } from "ai";
|
|
2
|
+
import type { FastifyInstance } from "fastify";
|
|
3
|
+
import { BadRequestException } from "../exceptions/so-exceptions";
|
|
4
|
+
import type { FixtureRecord } from "../types/types";
|
|
5
|
+
import { aiClient } from "./ai-client";
|
|
6
|
+
|
|
7
|
+
export async function setAiApi(server: FastifyInstance) {
|
|
8
|
+
await aiClient.init();
|
|
9
|
+
|
|
10
|
+
server.post("/api/ai/fixture/chat", async (request, reply) => {
|
|
11
|
+
const { messages, fixtureRecords } = request.body as {
|
|
12
|
+
messages: UIMessage[];
|
|
13
|
+
fixtureRecords?: FixtureRecord[];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
if (!fixtureRecords || fixtureRecords.length === 0) {
|
|
17
|
+
throw new BadRequestException("픽스쳐 레코드가 없습니다. 픽스쳐 조회 후 시도하세요.");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const result = aiClient.handleFixture(await convertToModelMessages(messages), fixtureRecords);
|
|
21
|
+
const response = result.toUIMessageStreamResponse();
|
|
22
|
+
|
|
23
|
+
reply.raw.writeHead(response.status, Object.fromEntries(Object.entries(response.headers)));
|
|
24
|
+
|
|
25
|
+
if (response.body) {
|
|
26
|
+
const reader = response.body.getReader();
|
|
27
|
+
while (true) {
|
|
28
|
+
const { done, value } = await reader.read();
|
|
29
|
+
if (done) break;
|
|
30
|
+
reply.raw.write(value);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
reply.raw.end();
|
|
35
|
+
return reply;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Entity/Enum 생성용 AI Chat Stream
|
|
39
|
+
server.post("/api/ai/entity/chat", async (request, reply) => {
|
|
40
|
+
const { messages } = request.body as {
|
|
41
|
+
messages: UIMessage[];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const result = aiClient.handleEntity(await convertToModelMessages(messages));
|
|
45
|
+
const response = result.toUIMessageStreamResponse();
|
|
46
|
+
|
|
47
|
+
reply.raw.writeHead(response.status, Object.fromEntries(Object.entries(response.headers)));
|
|
48
|
+
|
|
49
|
+
if (response.body) {
|
|
50
|
+
const reader = response.body.getReader();
|
|
51
|
+
while (true) {
|
|
52
|
+
const { done, value } = await reader.read();
|
|
53
|
+
if (done) break;
|
|
54
|
+
reply.raw.write(value);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
reply.raw.end();
|
|
59
|
+
return reply;
|
|
60
|
+
});
|
|
61
|
+
}
|