sonamu 0.4.12 → 0.4.14
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/.pnp.cjs +5 -5
- package/dist/{base-model-BvVra-8f.d.mts → base-model-CEB0H0aO.d.mts} +1 -1
- package/dist/{base-model-Br6krkwK.d.ts → base-model-CrqDMYhI.d.ts} +1 -1
- package/dist/bin/cli.js +51 -51
- package/dist/bin/cli.mjs +2 -2
- package/dist/{chunk-ZLFDB43J.js → chunk-2WAC2GER.js} +147 -96
- package/dist/chunk-2WAC2GER.js.map +1 -0
- package/dist/{chunk-FKZK27YL.mjs → chunk-C3IPIF6O.mjs} +2 -2
- package/dist/{chunk-INTZUNZ6.js → chunk-EXHKSVTE.js} +7 -7
- package/dist/{chunk-LNZTU4JC.mjs → chunk-FCERKIIF.mjs} +104 -53
- package/dist/chunk-FCERKIIF.mjs.map +1 -0
- package/dist/{chunk-JQJTQQ7D.mjs → chunk-HGIBJYOU.mjs} +2 -2
- package/dist/{chunk-NPLUHS5L.mjs → chunk-JKSOJRQA.mjs} +2 -2
- package/dist/{chunk-FYLFH3Q6.js → chunk-OTKKFP3Y.js} +100 -100
- package/dist/{chunk-IEMX4VPN.js → chunk-UZ2IY5VE.js} +4 -4
- package/dist/database/drivers/knex/base-model.d.mts +2 -2
- package/dist/database/drivers/knex/base-model.d.ts +2 -2
- package/dist/database/drivers/knex/base-model.js +8 -8
- package/dist/database/drivers/knex/base-model.mjs +3 -3
- package/dist/database/drivers/kysely/base-model.d.mts +2 -2
- package/dist/database/drivers/kysely/base-model.d.ts +2 -2
- package/dist/database/drivers/kysely/base-model.js +9 -9
- package/dist/database/drivers/kysely/base-model.mjs +3 -3
- package/dist/index.d.mts +17 -4
- package/dist/index.d.ts +17 -4
- package/dist/index.js +13 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/api/caster.ts +2 -2
- package/src/api/code-converters.ts +7 -0
- package/src/api/decorators.ts +36 -4
- package/src/shared/web.shared.ts.txt +225 -0
- package/src/syncer/syncer.ts +7 -1
- package/src/templates/service.template.ts +50 -9
- package/dist/chunk-LNZTU4JC.mjs.map +0 -1
- package/dist/chunk-ZLFDB43J.js.map +0 -1
- package/dist/{chunk-FKZK27YL.mjs.map → chunk-C3IPIF6O.mjs.map} +0 -0
- package/dist/{chunk-INTZUNZ6.js.map → chunk-EXHKSVTE.js.map} +0 -0
- package/dist/{chunk-JQJTQQ7D.mjs.map → chunk-HGIBJYOU.mjs.map} +0 -0
- package/dist/{chunk-NPLUHS5L.mjs.map → chunk-JKSOJRQA.mjs.map} +0 -0
- package/dist/{chunk-FYLFH3Q6.js.map → chunk-OTKKFP3Y.js.map} +0 -0
- package/dist/{chunk-IEMX4VPN.js.map → chunk-UZ2IY5VE.js.map} +0 -0
- package/dist/{model-DWoinpJ7.d.mts → model-aFgomcdc.d.mts} +4 -4
- package/dist/{model-DWoinpJ7.d.ts → model-aFgomcdc.d.ts} +4 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonamu",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.14",
|
|
4
4
|
"description": "Sonamu — TypeScript Fullstack API Framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"qs": "^6.11.0",
|
|
60
60
|
"tsicli": "^1.0.5",
|
|
61
61
|
"uuid": "^8.3.2",
|
|
62
|
-
"zod": "
|
|
62
|
+
"zod": "3.25.76"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@types/fs-extra": "^9.0.13",
|
package/src/api/caster.ts
CHANGED
|
@@ -15,8 +15,8 @@ function isZodNumberAnyway(zodType: z.ZodType<any>) {
|
|
|
15
15
|
) {
|
|
16
16
|
} else if (
|
|
17
17
|
zodType instanceof z.ZodOptional &&
|
|
18
|
-
zodType._def
|
|
19
|
-
zodType._type
|
|
18
|
+
zodType._def?.innerType instanceof z.ZodOptional &&
|
|
19
|
+
zodType._type?.def?.innerType instanceof z.ZodNumber
|
|
20
20
|
) {
|
|
21
21
|
return true;
|
|
22
22
|
}
|
|
@@ -415,6 +415,13 @@ export function apiParamToTsCode(
|
|
|
415
415
|
.join(", ");
|
|
416
416
|
}
|
|
417
417
|
|
|
418
|
+
export function apiParamToTsCodeAsObject(
|
|
419
|
+
params: ApiParam[],
|
|
420
|
+
injectImportKeys: string[]
|
|
421
|
+
): string {
|
|
422
|
+
return `{ ${params.map((param) => `${param.name}${param.optional ? "?" : ""}: ${apiParamTypeToTsType(param.type, injectImportKeys)}${param.defaultDef ? `= ${param.defaultDef}` : ""}`).join(", ")} }`;
|
|
423
|
+
}
|
|
424
|
+
|
|
418
425
|
export function apiParamTypeToTsType(
|
|
419
426
|
paramType: ApiParamType,
|
|
420
427
|
injectImportKeys: string[]
|
package/src/api/decorators.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { HTTPMethods } from "fastify";
|
|
2
2
|
import inflection from "inflection";
|
|
3
3
|
import { ApiParam, ApiParamType } from "../types/types";
|
|
4
|
+
import { z } from "zod";
|
|
4
5
|
|
|
5
6
|
export type ServiceClient =
|
|
6
7
|
| "axios"
|
|
7
8
|
| "axios-multipart"
|
|
8
9
|
| "swr"
|
|
9
|
-
| "socketio"
|
|
10
10
|
| "window-fetch";
|
|
11
11
|
export type ApiDecoratorOptions = {
|
|
12
12
|
httpMethod?: HTTPMethods;
|
|
@@ -22,17 +22,27 @@ export type ApiDecoratorOptions = {
|
|
|
22
22
|
guards?: string[];
|
|
23
23
|
description?: string;
|
|
24
24
|
};
|
|
25
|
+
export type StreamDecoratorOptions = {
|
|
26
|
+
type: "sse"; // | 'ws
|
|
27
|
+
events: z.ZodObject<any>;
|
|
28
|
+
path?: string;
|
|
29
|
+
resourceName?: string;
|
|
30
|
+
guards?: string[];
|
|
31
|
+
description?: string;
|
|
32
|
+
};
|
|
25
33
|
export const registeredApis: {
|
|
26
34
|
modelName: string;
|
|
27
35
|
methodName: string;
|
|
28
36
|
path: string;
|
|
29
37
|
options: ApiDecoratorOptions;
|
|
38
|
+
streamOptions?: StreamDecoratorOptions;
|
|
30
39
|
}[] = [];
|
|
31
40
|
export type ExtendedApi = {
|
|
32
41
|
modelName: string;
|
|
33
42
|
methodName: string;
|
|
34
43
|
path: string;
|
|
35
44
|
options: ApiDecoratorOptions;
|
|
45
|
+
streamOptions?: StreamDecoratorOptions;
|
|
36
46
|
typeParameters: ApiParamType.TypeParam[];
|
|
37
47
|
parameters: ApiParam[];
|
|
38
48
|
returnType: ApiParamType;
|
|
@@ -55,12 +65,34 @@ export function api(options: ApiDecoratorOptions = {}) {
|
|
|
55
65
|
true
|
|
56
66
|
)}/${inflection.camelize(propertyKey, true)}`;
|
|
57
67
|
|
|
58
|
-
|
|
68
|
+
registeredApis.push({
|
|
59
69
|
modelName,
|
|
60
70
|
methodName,
|
|
61
71
|
path: options.path ?? defaultPath,
|
|
62
72
|
options,
|
|
63
|
-
};
|
|
64
|
-
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function stream(options: StreamDecoratorOptions) {
|
|
78
|
+
return function (target: Object, propertyKey: string) {
|
|
79
|
+
const modelName = target.constructor.name.match(/(.+)Class$/)![1];
|
|
80
|
+
const methodName = propertyKey;
|
|
81
|
+
|
|
82
|
+
const defaultPath = `/${inflection.camelize(
|
|
83
|
+
modelName.replace(/Model$/, "").replace(/Frame$/, ""),
|
|
84
|
+
true
|
|
85
|
+
)}/${inflection.camelize(propertyKey, true)}`;
|
|
86
|
+
|
|
87
|
+
registeredApis.push({
|
|
88
|
+
modelName,
|
|
89
|
+
methodName,
|
|
90
|
+
path: options.path ?? defaultPath,
|
|
91
|
+
options: {
|
|
92
|
+
...options,
|
|
93
|
+
httpMethod: "GET",
|
|
94
|
+
},
|
|
95
|
+
streamOptions: options,
|
|
96
|
+
});
|
|
65
97
|
};
|
|
66
98
|
}
|
|
@@ -126,3 +126,228 @@ export const SQLDateTimeString = z
|
|
|
126
126
|
.max(19)
|
|
127
127
|
.describe("SQLDateTimeString");
|
|
128
128
|
export type SQLDateTimeString = z.infer<typeof SQLDateTimeString>;
|
|
129
|
+
|
|
130
|
+
/*
|
|
131
|
+
Stream
|
|
132
|
+
*/
|
|
133
|
+
export type SSEStreamOptions = {
|
|
134
|
+
enabled?: boolean;
|
|
135
|
+
retry?: number;
|
|
136
|
+
retryInterval?: number;
|
|
137
|
+
};
|
|
138
|
+
export type SSEStreamState = {
|
|
139
|
+
isConnected: boolean;
|
|
140
|
+
error: string | null;
|
|
141
|
+
retryCount: number;
|
|
142
|
+
isEnded: boolean;
|
|
143
|
+
};
|
|
144
|
+
export type EventHandlers<T> = {
|
|
145
|
+
[K in keyof T]: (data: T[K]) => void;
|
|
146
|
+
};
|
|
147
|
+
import { useEffect, useRef, useState } from "react";
|
|
148
|
+
|
|
149
|
+
export function useSSEStream<T extends Record<string, any>>(
|
|
150
|
+
url: string,
|
|
151
|
+
params: Record<string, any>,
|
|
152
|
+
handlers: {
|
|
153
|
+
[K in keyof T]?: (data: T[K]) => void;
|
|
154
|
+
},
|
|
155
|
+
options: SSEStreamOptions = {}
|
|
156
|
+
): SSEStreamState {
|
|
157
|
+
const { enabled = true, retry = 3, retryInterval = 3000 } = options;
|
|
158
|
+
|
|
159
|
+
const [state, setState] = useState<SSEStreamState>({
|
|
160
|
+
isConnected: false,
|
|
161
|
+
error: null,
|
|
162
|
+
retryCount: 0,
|
|
163
|
+
isEnded: false,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const eventSourceRef = useRef<EventSource | null>(null);
|
|
167
|
+
const retryTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
168
|
+
const handlersRef = useRef(handlers);
|
|
169
|
+
|
|
170
|
+
// handlers를 ref로 관리해서 재연결 없이 업데이트
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
handlersRef.current = handlers;
|
|
173
|
+
}, [handlers]);
|
|
174
|
+
|
|
175
|
+
// 연결 함수
|
|
176
|
+
const connect = () => {
|
|
177
|
+
if (!enabled) return;
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
// 기존 연결이 있으면 정리
|
|
181
|
+
if (eventSourceRef.current) {
|
|
182
|
+
eventSourceRef.current.close();
|
|
183
|
+
eventSourceRef.current = null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 재시도 타이머 정리
|
|
187
|
+
if (retryTimeoutRef.current) {
|
|
188
|
+
clearTimeout(retryTimeoutRef.current);
|
|
189
|
+
retryTimeoutRef.current = null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// URL에 파라미터 추가
|
|
193
|
+
const queryString = qs.stringify(params);
|
|
194
|
+
const fullUrl = queryString ? `${url}?${queryString}` : url;
|
|
195
|
+
|
|
196
|
+
const eventSource = new EventSource(fullUrl);
|
|
197
|
+
eventSourceRef.current = eventSource;
|
|
198
|
+
|
|
199
|
+
// 연결 시도 중 상태 표시
|
|
200
|
+
setState((prev) => ({
|
|
201
|
+
...prev,
|
|
202
|
+
isConnected: false,
|
|
203
|
+
error: null,
|
|
204
|
+
isEnded: false,
|
|
205
|
+
}));
|
|
206
|
+
|
|
207
|
+
eventSource.onopen = () => {
|
|
208
|
+
setState((prev) => ({
|
|
209
|
+
...prev,
|
|
210
|
+
isConnected: true,
|
|
211
|
+
error: null,
|
|
212
|
+
retryCount: 0,
|
|
213
|
+
isEnded: false,
|
|
214
|
+
}));
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
eventSource.onerror = (event) => {
|
|
218
|
+
// 이미 다른 연결로 교체되었는지 확인
|
|
219
|
+
if (eventSourceRef.current !== eventSource) {
|
|
220
|
+
return; // 이미 새로운 연결이 있으면 무시
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
setState((prev) => ({
|
|
224
|
+
...prev,
|
|
225
|
+
isConnected: false,
|
|
226
|
+
error: "Connection failed",
|
|
227
|
+
isEnded: false,
|
|
228
|
+
}));
|
|
229
|
+
|
|
230
|
+
// 자동 재연결 시도
|
|
231
|
+
if (state.retryCount < retry) {
|
|
232
|
+
retryTimeoutRef.current = setTimeout(() => {
|
|
233
|
+
// 여전히 같은 연결인지 확인
|
|
234
|
+
if (eventSourceRef.current === eventSource) {
|
|
235
|
+
setState((prev) => ({
|
|
236
|
+
...prev,
|
|
237
|
+
retryCount: prev.retryCount + 1,
|
|
238
|
+
isEnded: false,
|
|
239
|
+
}));
|
|
240
|
+
connect();
|
|
241
|
+
}
|
|
242
|
+
}, retryInterval);
|
|
243
|
+
} else {
|
|
244
|
+
setState((prev) => ({
|
|
245
|
+
...prev,
|
|
246
|
+
error: `Connection failed after ${retry} attempts`,
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// 공통 'end' 이벤트 처리 (사용자 정의 이벤트와 별도)
|
|
252
|
+
eventSource.addEventListener("end", () => {
|
|
253
|
+
console.log("SSE 연결 정상종료");
|
|
254
|
+
if (eventSourceRef.current === eventSource) {
|
|
255
|
+
eventSource.close();
|
|
256
|
+
eventSourceRef.current = null;
|
|
257
|
+
setState((prev) => ({
|
|
258
|
+
...prev,
|
|
259
|
+
isConnected: false,
|
|
260
|
+
error: null, // 정상 종료
|
|
261
|
+
isEnded: true,
|
|
262
|
+
}));
|
|
263
|
+
|
|
264
|
+
if (handlersRef.current.end) {
|
|
265
|
+
const endHandler = handlersRef.current.end;
|
|
266
|
+
endHandler("end" as T[string]);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// 각 이벤트 타입별 리스너 등록
|
|
272
|
+
Object.keys(handlersRef.current).forEach((eventType) => {
|
|
273
|
+
const handler = handlersRef.current[eventType as keyof T];
|
|
274
|
+
if (handler) {
|
|
275
|
+
eventSource.addEventListener(eventType, (event) => {
|
|
276
|
+
// 여전히 현재 연결인지 확인
|
|
277
|
+
if (eventSourceRef.current !== eventSource) {
|
|
278
|
+
return; // 이미 새로운 연결로 교체되었으면 무시
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
const data = JSON.parse(event.data);
|
|
283
|
+
handler(data);
|
|
284
|
+
} catch (error) {
|
|
285
|
+
console.error(
|
|
286
|
+
`Failed to parse SSE data for event ${eventType}:`,
|
|
287
|
+
error
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
setState((prev) => ({
|
|
291
|
+
...prev,
|
|
292
|
+
isEnded: false,
|
|
293
|
+
}));
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// 기본 message 이벤트 처리 (event 타입이 없는 경우)
|
|
299
|
+
eventSource.onmessage = (event) => {
|
|
300
|
+
// 여전히 현재 연결인지 확인
|
|
301
|
+
if (eventSourceRef.current !== eventSource) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const data = JSON.parse(event.data);
|
|
307
|
+
// 'message' 핸들러가 있으면 호출
|
|
308
|
+
const messageHandler = handlersRef.current["message" as keyof T];
|
|
309
|
+
if (messageHandler) {
|
|
310
|
+
messageHandler(data);
|
|
311
|
+
}
|
|
312
|
+
} catch (error) {
|
|
313
|
+
console.error("Failed to parse SSE message:", error);
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
} catch (error) {
|
|
317
|
+
setState((prev) => ({
|
|
318
|
+
...prev,
|
|
319
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
320
|
+
isConnected: false,
|
|
321
|
+
isEnded: false,
|
|
322
|
+
}));
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// 연결 시작
|
|
327
|
+
useEffect(() => {
|
|
328
|
+
if (enabled) {
|
|
329
|
+
connect();
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return () => {
|
|
333
|
+
// cleanup
|
|
334
|
+
if (eventSourceRef.current) {
|
|
335
|
+
eventSourceRef.current.close();
|
|
336
|
+
eventSourceRef.current = null;
|
|
337
|
+
}
|
|
338
|
+
if (retryTimeoutRef.current) {
|
|
339
|
+
clearTimeout(retryTimeoutRef.current);
|
|
340
|
+
retryTimeoutRef.current = null;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
}, [url, JSON.stringify(params), enabled]);
|
|
344
|
+
|
|
345
|
+
// 파라미터가 변경되면 재연결
|
|
346
|
+
useEffect(() => {
|
|
347
|
+
if (enabled && eventSourceRef.current) {
|
|
348
|
+
connect();
|
|
349
|
+
}
|
|
350
|
+
}, [JSON.stringify(params)]);
|
|
351
|
+
|
|
352
|
+
return state;
|
|
353
|
+
}
|
package/src/syncer/syncer.ts
CHANGED
|
@@ -130,11 +130,16 @@ export class Syncer {
|
|
|
130
130
|
async sync(): Promise<void> {
|
|
131
131
|
const { targets } = Sonamu.config.sync;
|
|
132
132
|
|
|
133
|
+
// 번들러 여부에 따라 현재 디렉토리가 바뀌므로
|
|
134
|
+
const currentDirname = __dirname.endsWith("/syncer")
|
|
135
|
+
? __dirname
|
|
136
|
+
: path.join(__dirname, "./syncer");
|
|
137
|
+
|
|
133
138
|
// 트리거와 무관하게 shared 분배
|
|
134
139
|
await Promise.all(
|
|
135
140
|
targets.map(async (target) => {
|
|
136
141
|
const srcCodePath = path
|
|
137
|
-
.join(
|
|
142
|
+
.join(currentDirname, `../shared/${target}.shared.ts.txt`)
|
|
138
143
|
.replace("/dist/", "/src/");
|
|
139
144
|
if (!fs.existsSync(srcCodePath)) {
|
|
140
145
|
return;
|
|
@@ -158,6 +163,7 @@ export class Syncer {
|
|
|
158
163
|
return;
|
|
159
164
|
}
|
|
160
165
|
fs.writeFileSync(dstCodePath, fs.readFileSync(srcCodePath));
|
|
166
|
+
console.log(chalk.blue("shared.ts is synced"));
|
|
161
167
|
})
|
|
162
168
|
);
|
|
163
169
|
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
apiParamTypeToTsType,
|
|
8
8
|
apiParamToTsCode,
|
|
9
9
|
unwrapPromiseOnce,
|
|
10
|
+
zodTypeToTsTypeDef,
|
|
11
|
+
apiParamToTsCodeAsObject,
|
|
10
12
|
} from "../api/code-converters";
|
|
11
13
|
import { ExtendedApi } from "../api/decorators";
|
|
12
14
|
import { Template } from "./base-template";
|
|
@@ -43,7 +45,7 @@ export class Template__service extends Template {
|
|
|
43
45
|
`import { z } from 'zod';`,
|
|
44
46
|
`import qs from "qs";`,
|
|
45
47
|
`import useSWR, { SWRResponse } from "swr";`,
|
|
46
|
-
`import { fetch, ListResult, SWRError, SwrOptions, handleConditional, swrPostFetcher } from '../sonamu.shared';`,
|
|
48
|
+
`import { fetch, ListResult, SWRError, SwrOptions, handleConditional, swrPostFetcher, EventHandlers, SSEStreamOptions, useSSEStream } from '../sonamu.shared';`,
|
|
47
49
|
...(hasAxiosProgressEvent
|
|
48
50
|
? [`import { AxiosProgressEvent } from 'axios';`]
|
|
49
51
|
: []),
|
|
@@ -91,6 +93,12 @@ export class Template__service extends Template {
|
|
|
91
93
|
importKeys
|
|
92
94
|
);
|
|
93
95
|
|
|
96
|
+
// 파라미터 정의 (객체 형태)
|
|
97
|
+
const paramsDefAsObject = apiParamToTsCodeAsObject(
|
|
98
|
+
paramsWithoutContext,
|
|
99
|
+
importKeys
|
|
100
|
+
);
|
|
101
|
+
|
|
94
102
|
// 리턴 타입 정의
|
|
95
103
|
const returnTypeDef = apiParamTypeToTsType(
|
|
96
104
|
unwrapPromiseOnce(api.returnType),
|
|
@@ -102,11 +110,14 @@ export class Template__service extends Template {
|
|
|
102
110
|
.map((param) => param.name)
|
|
103
111
|
.join(", ")} }`;
|
|
104
112
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
113
|
+
// 기본 URL
|
|
114
|
+
const apiBaseUrl = `${Sonamu.config.route.prefix}${api.path}`;
|
|
115
|
+
|
|
116
|
+
return [
|
|
117
|
+
// 클라이언트별로 생성
|
|
118
|
+
..._.sortBy(api.options.clients, (client) =>
|
|
119
|
+
client === "swr" ? 0 : 1
|
|
120
|
+
).map((client) => {
|
|
110
121
|
switch (client) {
|
|
111
122
|
case "axios":
|
|
112
123
|
return this.renderAxios(
|
|
@@ -143,12 +154,15 @@ export class Template__service extends Template {
|
|
|
143
154
|
paramsDef,
|
|
144
155
|
payloadDef
|
|
145
156
|
);
|
|
146
|
-
case "socketio":
|
|
147
157
|
default:
|
|
148
158
|
return `// Not supported ${inflection.camelize(client, true)} yet.`;
|
|
149
159
|
}
|
|
150
|
-
})
|
|
151
|
-
|
|
160
|
+
}),
|
|
161
|
+
// 스트리밍인 경우
|
|
162
|
+
...(api.streamOptions
|
|
163
|
+
? [this.renderStream(api, apiBaseUrl, paramsDefAsObject)]
|
|
164
|
+
: []),
|
|
165
|
+
].join("\n");
|
|
152
166
|
})
|
|
153
167
|
.join("\n\n");
|
|
154
168
|
|
|
@@ -274,4 +288,31 @@ export async function ${api.methodName}${typeParamsDef}(${paramsDef}): Promise<R
|
|
|
274
288
|
}
|
|
275
289
|
`.trim();
|
|
276
290
|
}
|
|
291
|
+
|
|
292
|
+
renderStream(
|
|
293
|
+
api: ExtendedApi,
|
|
294
|
+
apiBaseUrl: string,
|
|
295
|
+
paramsDefAsObject: string
|
|
296
|
+
) {
|
|
297
|
+
if (!api.streamOptions) {
|
|
298
|
+
return "// streamOptions not found";
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const methodNameStream = api.options.resourceName
|
|
302
|
+
? "use" + inflection.camelize(api.options.resourceName)
|
|
303
|
+
: "use" + inflection.camelize(api.methodName);
|
|
304
|
+
const methodNameStreamCamelized = inflection.camelize(
|
|
305
|
+
methodNameStream,
|
|
306
|
+
true
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
const eventsTypeDef = zodTypeToTsTypeDef(api.streamOptions.events);
|
|
310
|
+
|
|
311
|
+
return ` export function ${methodNameStreamCamelized}(
|
|
312
|
+
params: ${paramsDefAsObject},
|
|
313
|
+
handlers: EventHandlers<${eventsTypeDef} & { end?: () => void }>,
|
|
314
|
+
options: SSEStreamOptions) {
|
|
315
|
+
return useSSEStream<${eventsTypeDef}>(\`${apiBaseUrl}\`, params, handlers, options);
|
|
316
|
+
}`;
|
|
317
|
+
}
|
|
277
318
|
}
|