@simplysm/service-server 13.0.100 → 14.0.4

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 (88) hide show
  1. package/README.md +79 -133
  2. package/dist/auth/auth-token-payload.js +2 -1
  3. package/dist/auth/auth-token-payload.js.map +1 -6
  4. package/dist/auth/jwt-manager.js +21 -21
  5. package/dist/auth/jwt-manager.js.map +1 -6
  6. package/dist/core/define-service.d.ts +12 -12
  7. package/dist/core/define-service.d.ts.map +1 -1
  8. package/dist/core/define-service.js +77 -63
  9. package/dist/core/define-service.js.map +1 -6
  10. package/dist/core/service-executor.d.ts.map +1 -1
  11. package/dist/core/service-executor.js +42 -32
  12. package/dist/core/service-executor.js.map +1 -6
  13. package/dist/index.d.ts +0 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +11 -2
  16. package/dist/index.js.map +1 -6
  17. package/dist/legacy/v1-auto-update-handler.d.ts +2 -2
  18. package/dist/legacy/v1-auto-update-handler.js +42 -35
  19. package/dist/legacy/v1-auto-update-handler.js.map +1 -6
  20. package/dist/protocol/protocol-wrapper.d.ts +9 -9
  21. package/dist/protocol/protocol-wrapper.js +64 -46
  22. package/dist/protocol/protocol-wrapper.js.map +1 -6
  23. package/dist/service-server.d.ts +2 -1
  24. package/dist/service-server.d.ts.map +1 -1
  25. package/dist/service-server.js +183 -165
  26. package/dist/service-server.js.map +1 -6
  27. package/dist/services/auto-update-service.js +35 -34
  28. package/dist/services/auto-update-service.js.map +1 -6
  29. package/dist/services/orm-service.js +114 -120
  30. package/dist/services/orm-service.js.map +1 -6
  31. package/dist/transport/http/http-request-handler.d.ts.map +1 -1
  32. package/dist/transport/http/http-request-handler.js +58 -46
  33. package/dist/transport/http/http-request-handler.js.map +1 -6
  34. package/dist/transport/http/static-file-handler.js +42 -39
  35. package/dist/transport/http/static-file-handler.js.map +1 -6
  36. package/dist/transport/http/upload-handler.d.ts.map +1 -1
  37. package/dist/transport/http/upload-handler.js +60 -55
  38. package/dist/transport/http/upload-handler.js.map +1 -6
  39. package/dist/transport/socket/service-socket.d.ts +13 -13
  40. package/dist/transport/socket/service-socket.js +132 -108
  41. package/dist/transport/socket/service-socket.js.map +1 -6
  42. package/dist/transport/socket/websocket-handler.d.ts +9 -13
  43. package/dist/transport/socket/websocket-handler.d.ts.map +1 -1
  44. package/dist/transport/socket/websocket-handler.js +148 -139
  45. package/dist/transport/socket/websocket-handler.js.map +1 -6
  46. package/dist/types/server-options.d.ts +1 -1
  47. package/dist/types/server-options.d.ts.map +1 -1
  48. package/dist/types/server-options.js +2 -1
  49. package/dist/types/server-options.js.map +1 -6
  50. package/dist/utils/config-manager.js +48 -46
  51. package/dist/utils/config-manager.js.map +1 -6
  52. package/dist/workers/service-protocol.worker.js +8 -11
  53. package/dist/workers/service-protocol.worker.js.map +1 -6
  54. package/docs/auth.md +28 -16
  55. package/docs/core.md +113 -54
  56. package/docs/legacy.md +21 -0
  57. package/docs/main.md +81 -0
  58. package/docs/protocol.md +31 -0
  59. package/docs/services.md +49 -44
  60. package/docs/transport.md +81 -76
  61. package/docs/types.md +31 -0
  62. package/docs/utilities.md +27 -0
  63. package/package.json +12 -13
  64. package/src/auth/jwt-manager.ts +2 -2
  65. package/src/core/define-service.ts +19 -19
  66. package/src/core/service-executor.ts +23 -17
  67. package/src/index.ts +10 -12
  68. package/src/legacy/v1-auto-update-handler.ts +10 -10
  69. package/src/protocol/protocol-wrapper.ts +16 -16
  70. package/src/service-server.ts +51 -43
  71. package/src/services/auto-update-service.ts +1 -1
  72. package/src/services/orm-service.ts +7 -7
  73. package/src/transport/http/http-request-handler.ts +16 -10
  74. package/src/transport/http/static-file-handler.ts +8 -8
  75. package/src/transport/http/upload-handler.ts +16 -9
  76. package/src/transport/socket/service-socket.ts +22 -22
  77. package/src/transport/socket/websocket-handler.ts +50 -70
  78. package/src/types/server-options.ts +1 -1
  79. package/src/utils/config-manager.ts +11 -11
  80. package/dist/services/smtp-client-service.d.ts +0 -8
  81. package/dist/services/smtp-client-service.d.ts.map +0 -1
  82. package/dist/services/smtp-client-service.js +0 -46
  83. package/dist/services/smtp-client-service.js.map +0 -6
  84. package/docs/server.md +0 -126
  85. package/src/services/smtp-client-service.ts +0 -59
  86. package/tests/define-service.spec.ts +0 -66
  87. package/tests/orm-service.spec.ts +0 -83
  88. package/tests/service-executor.spec.ts +0 -114
@@ -15,10 +15,10 @@ import type {
15
15
  const logger = consola.withTag("service-server:ServiceSocket");
16
16
 
17
17
  /**
18
- * Service socket interface
18
+ * 서비스 소켓 인터페이스
19
19
  *
20
- * Manages a single WebSocket connection with protocol encoding/decoding,
21
- * ping/pong keep-alive, and event listener tracking.
20
+ * 프로토콜 인코딩/디코딩, ping/pong 연결 유지,
21
+ * 이벤트 리스너 추적이 포함된 단일 WebSocket 연결을 관리한다.
22
22
  */
23
23
  export interface ServiceSocket {
24
24
  readonly connectedAtDateTime: DateTime;
@@ -27,37 +27,37 @@ export interface ServiceSocket {
27
27
  authTokenPayload?: AuthTokenPayload;
28
28
 
29
29
  /**
30
- * Close the WebSocket connection
30
+ * WebSocket 연결을 닫는다
31
31
  */
32
32
  close(): void;
33
33
 
34
34
  /**
35
- * Send a message to the client
35
+ * 클라이언트에 메시지를 전송한다
36
36
  */
37
37
  send(uuid: string, msg: ServiceServerMessage): Promise<number>;
38
38
 
39
39
  /**
40
- * Register an event listener with key/name/info
40
+ * key/name/info로 이벤트 리스너를 등록한다
41
41
  */
42
42
  addListener(key: string, eventName: string, info: unknown): void;
43
43
 
44
44
  /**
45
- * Remove an event listener by key
45
+ * key로 이벤트 리스너를 제거한다
46
46
  */
47
47
  removeListener(key: string): void;
48
48
 
49
49
  /**
50
- * Get all event listeners for a specific event name
50
+ * 특정 이벤트 이름에 해당하는 모든 이벤트 리스너를 조회한다
51
51
  */
52
52
  getEventListeners(eventName: string): Array<{ key: string; info: unknown }>;
53
53
 
54
54
  /**
55
- * Filter target keys that exist in this socket's listeners
55
+ * 소켓의 리스너에 존재하는 대상 키를 필터링한다
56
56
  */
57
57
  filterEventTargetKeys(targetKeys: string[]): string[];
58
58
 
59
59
  /**
60
- * Register event handlers
60
+ * 이벤트 핸들러를 등록한다
61
61
  */
62
62
  on(event: "error", handler: (err: Error) => void): void;
63
63
  on(event: "close", handler: (code: number) => void): void;
@@ -65,10 +65,10 @@ export interface ServiceSocket {
65
65
  }
66
66
 
67
67
  /**
68
- * Create a service socket instance
68
+ * 서비스 소켓 인스턴스를 생성한다
69
69
  *
70
- * Manages a single WebSocket connection with protocol encoding/decoding,
71
- * ping/pong keep-alive, and event listener tracking.
70
+ * 프로토콜 인코딩/디코딩, ping/pong 연결 유지,
71
+ * 이벤트 리스너 추적이 포함된 단일 WebSocket 연결을 관리한다.
72
72
  */
73
73
  export function createServiceSocket(
74
74
  socket: WebSocket,
@@ -77,10 +77,10 @@ export function createServiceSocket(
77
77
  connReq: FastifyRequest,
78
78
  ): ServiceSocket {
79
79
  // -------------------------------------------------------------------
80
- // State
80
+ // 상태
81
81
  // -------------------------------------------------------------------
82
82
 
83
- const PING_INTERVAL = 5000; // Send ping every 5s
83
+ const PING_INTERVAL = 5000; // 5초마다 ping 전송
84
84
  const PONG_PACKET = new Uint8Array([0x02]);
85
85
 
86
86
  const protocol = createServerProtocolWrapper();
@@ -97,7 +97,7 @@ export function createServiceSocket(
97
97
  const connectedAtDateTime = new DateTime();
98
98
 
99
99
  // -------------------------------------------------------------------
100
- // Helpers
100
+ // 헬퍼
101
101
  // -------------------------------------------------------------------
102
102
 
103
103
  async function sendInternal(uuid: string, msg: ServiceServerRawMessage): Promise<number> {
@@ -121,11 +121,11 @@ export function createServiceSocket(
121
121
  }
122
122
 
123
123
  // -------------------------------------------------------------------
124
- // Event Handlers
124
+ // 이벤트 핸들러
125
125
  // -------------------------------------------------------------------
126
126
 
127
127
  function onError(err: Error): void {
128
- logger.error("WebSocket client error", err);
128
+ logger.error("WebSocket 클라이언트 에러", err);
129
129
  emitEvent("error", err);
130
130
  }
131
131
 
@@ -137,7 +137,7 @@ export function createServiceSocket(
137
137
 
138
138
  async function onMessage(msgBuffer: Bytes): Promise<void> {
139
139
  try {
140
- // Handle pong response to ping
140
+ // ping에 대한 pong 응답 처리
141
141
  if (msgBuffer.length === 1 && msgBuffer[0] === 0x01) {
142
142
  if (socket.readyState === WebSocket.OPEN) {
143
143
  socket.send(PONG_PACKET);
@@ -159,12 +159,12 @@ export function createServiceSocket(
159
159
  emitEvent("message", { uuid: decodeResult.uuid, msg });
160
160
  }
161
161
  } catch (err) {
162
- logger.error("Error processing WebSocket message", err);
162
+ logger.error("WebSocket 메시지 처리 중 에러 발생", err);
163
163
  }
164
164
  }
165
165
 
166
166
  // -------------------------------------------------------------------
167
- // Setup
167
+ // 설정
168
168
  // -------------------------------------------------------------------
169
169
 
170
170
  socket.on("close", onClose);
@@ -186,7 +186,7 @@ export function createServiceSocket(
186
186
  }, PING_INTERVAL);
187
187
 
188
188
  // -------------------------------------------------------------------
189
- // Public API
189
+ // 공개 API
190
190
  // -------------------------------------------------------------------
191
191
 
192
192
  return {
@@ -1,5 +1,5 @@
1
1
  import type { WebSocket } from "ws";
2
- import { Uuid } from "@simplysm/core-common";
2
+ import { Uuid, env } from "@simplysm/core-common";
3
3
  import type { ServiceEventDef, ServiceClientMessage } from "@simplysm/service-common";
4
4
  import { createServiceSocket, type ServiceSocket } from "./service-socket";
5
5
  import { verifyJwt } from "../../auth/jwt-manager";
@@ -9,29 +9,24 @@ import consola from "consola";
9
9
  const logger = consola.withTag("service-server:WebSocketHandler");
10
10
 
11
11
  /**
12
- * WebSocket handler interface
12
+ * WebSocket 핸들러 인터페이스
13
13
  *
14
- * Manages multiple WebSocket connections, routes messages to services,
15
- * and handles event broadcasting.
14
+ * 여러 WebSocket 연결을 관리하고, 메시지를 서비스로 라우팅하며,
15
+ * 이벤트 브로드캐스팅을 처리한다.
16
16
  */
17
17
  export interface WebSocketHandler {
18
18
  /**
19
- * Add a new WebSocket connection
19
+ * WebSocket 연결을 추가한다
20
20
  */
21
21
  addSocket(socket: WebSocket, clientId: string, clientName: string, connReq: FastifyRequest): void;
22
22
 
23
23
  /**
24
- * Close all active connections
24
+ * 모든 활성 연결을 닫는다
25
25
  */
26
26
  closeAll(): void;
27
27
 
28
28
  /**
29
- * Broadcast reload message to all clients
30
- */
31
- broadcastReload(clientName: string | undefined, changedFileSet: Set<string>): Promise<void>;
32
-
33
- /**
34
- * Emit event to matching clients
29
+ * 매칭되는 클라이언트에 이벤트를 발생시킨다
35
30
  */
36
31
  emit<TInfo, TData>(
37
32
  eventDef: ServiceEventDef<TInfo, TData>,
@@ -41,10 +36,10 @@ export interface WebSocketHandler {
41
36
  }
42
37
 
43
38
  /**
44
- * Create a WebSocket handler instance
39
+ * WebSocket 핸들러 인스턴스를 생성한다
45
40
  *
46
- * Manages multiple WebSocket connections, routes messages to services,
47
- * and handles event broadcasting.
41
+ * 여러 WebSocket 연결을 관리하고, 메시지를 서비스로 라우팅하며,
42
+ * 이벤트 브로드캐스팅을 처리한다.
48
43
  */
49
44
  export function createWebSocketHandler(
50
45
  runMethod: (def: {
@@ -56,13 +51,13 @@ export function createWebSocketHandler(
56
51
  jwtSecret: string | undefined,
57
52
  ): WebSocketHandler {
58
53
  // -------------------------------------------------------------------
59
- // State
54
+ // 상태
60
55
  // -------------------------------------------------------------------
61
56
 
62
57
  const socketMap = new Map<string, ServiceSocket>();
63
58
 
64
59
  // -------------------------------------------------------------------
65
- // Helpers
60
+ // 헬퍼
66
61
  // -------------------------------------------------------------------
67
62
 
68
63
  async function processRequest(
@@ -72,7 +67,9 @@ export function createWebSocketHandler(
72
67
  ): Promise<number> {
73
68
  try {
74
69
  if (message.name.includes(".") && Array.isArray(message.body)) {
75
- const [serviceName, methodName] = message.name.split(".");
70
+ const dotIndex = message.name.indexOf(".");
71
+ const serviceName = message.name.substring(0, dotIndex);
72
+ const methodName = message.name.substring(dotIndex + 1);
76
73
 
77
74
  const result = await runMethod({
78
75
  serviceName,
@@ -99,35 +96,34 @@ export function createWebSocketHandler(
99
96
  } else if (message.name === "evt:emit") {
100
97
  const { keys, data } = message.body as { keys: string[]; data: unknown };
101
98
 
102
- for (const subSock of socketMap.values()) {
103
- const targetKeys = subSock.filterEventTargetKeys(keys);
104
- if (targetKeys.length > 0) {
105
- await subSock.send(uuid, {
106
- name: "evt:on",
107
- body: {
108
- keys: targetKeys,
109
- data,
110
- },
111
- });
112
- }
113
- }
99
+ await Promise.allSettled(
100
+ Array.from(socketMap.values()).map(async (subSock) => {
101
+ const targetKeys = subSock.filterEventTargetKeys(keys);
102
+ if (targetKeys.length > 0) {
103
+ await subSock.send(uuid, {
104
+ name: "evt:on",
105
+ body: { keys: targetKeys, data },
106
+ });
107
+ }
108
+ }),
109
+ );
114
110
 
115
111
  return await serviceSocket.send(uuid, { name: "response" });
116
112
  } else if (message.name === "auth") {
117
- if (jwtSecret == null) throw new Error("JWT Secret is not defined.");
113
+ if (jwtSecret == null) throw new Error("JWT Secret 정의되지 않았습니다.");
118
114
 
119
115
  const token = message.body;
120
116
  serviceSocket.authTokenPayload = await verifyJwt(jwtSecret, token);
121
117
  return await serviceSocket.send(uuid, { name: "response" });
122
118
  } else {
123
- const err = new Error("Invalid request.");
119
+ const err = new Error("유효하지 않은 요청입니다.");
124
120
 
125
121
  return await serviceSocket.send(uuid, {
126
122
  name: "error",
127
123
  body: {
128
124
  name: err.name,
129
125
  message: err.message,
130
- stack: err.stack,
126
+ ...(env.DEV ? { stack: err.stack } : {}),
131
127
  code: "BAD_MESSAGE",
132
128
  },
133
129
  });
@@ -136,7 +132,7 @@ export function createWebSocketHandler(
136
132
  const error =
137
133
  err instanceof Error
138
134
  ? err
139
- : new Error(typeof err === "string" ? err : "An unknown error has occurred.");
135
+ : new Error(typeof err === "string" ? err : " 없는 에러가 발생했습니다.");
140
136
 
141
137
  return serviceSocket.send(uuid, {
142
138
  name: "error",
@@ -144,14 +140,14 @@ export function createWebSocketHandler(
144
140
  name: error.name,
145
141
  message: error.message,
146
142
  code: "INTERNAL_ERROR",
147
- stack: error.stack,
143
+ ...(env.DEV ? { stack: error.stack } : {}),
148
144
  },
149
145
  });
150
146
  }
151
147
  }
152
148
 
153
149
  // -------------------------------------------------------------------
154
- // Public API
150
+ // 공개 API
155
151
  // -------------------------------------------------------------------
156
152
 
157
153
  return {
@@ -164,7 +160,7 @@ export function createWebSocketHandler(
164
160
  try {
165
161
  const serviceSocket = createServiceSocket(socket, clientId, clientName, connReq);
166
162
 
167
- // Disconnect existing connection
163
+ // 기존 연결 해제
168
164
  const prevServiceSocket = socketMap.get(clientId);
169
165
  if (prevServiceSocket != null) {
170
166
  prevServiceSocket.close();
@@ -172,32 +168,32 @@ export function createWebSocketHandler(
172
168
  const connectionDateTimeText =
173
169
  prevServiceSocket.connectedAtDateTime.toFormatString("yyyy:MM:dd HH:mm:ss.fff");
174
170
  logger.debug(
175
- `Disconnected previous client connection: ${clientId}: ${connectionDateTimeText}`,
171
+ `이전 클라이언트 연결 해제됨: ${clientId}: ${connectionDateTimeText}`,
176
172
  );
177
173
  }
178
174
 
179
175
  socketMap.set(clientId, serviceSocket);
180
176
 
181
177
  serviceSocket.on("close", (code) => {
182
- logger.debug(`Client disconnected: (code: ${code})`);
178
+ logger.debug(`클라이언트 연결 해제됨: (code: ${code})`);
183
179
 
184
180
  if (socketMap.get(clientId) !== serviceSocket) return;
185
181
  socketMap.delete(clientId);
186
182
  });
187
183
 
188
184
  serviceSocket.on("message", async ({ uuid, msg }) => {
189
- logger.debug("Request received", msg);
185
+ logger.debug("요청 수신됨", msg);
190
186
  const sentSize = await processRequest(serviceSocket, uuid, msg);
191
- logger.debug(`Response sent (size: ${sentSize})`);
187
+ logger.debug(`응답 전송됨 (크기: ${sentSize})`);
192
188
  });
193
189
 
194
- logger.debug("Client connected", {
190
+ logger.debug("클라이언트 연결됨", {
195
191
  clientId,
196
192
  remoteAddress: connReq.socket.remoteAddress,
197
193
  socketSize: socketMap.size,
198
194
  });
199
195
  } catch (err) {
200
- logger.error("Error handling connection", err);
196
+ logger.error("연결 처리 중 에러 발생", err);
201
197
  socket.terminate();
202
198
  }
203
199
  },
@@ -208,21 +204,6 @@ export function createWebSocketHandler(
208
204
  }
209
205
  },
210
206
 
211
- async broadcastReload(
212
- clientName: string | undefined,
213
- changedFileSet: Set<string>,
214
- ): Promise<void> {
215
- for (const serviceSocket of socketMap.values()) {
216
- await serviceSocket.send(Uuid.generate().toString(), {
217
- name: "reload",
218
- body: {
219
- clientName,
220
- changedFileSet,
221
- },
222
- });
223
- }
224
- },
225
-
226
207
  async emit<TInfo, TData>(
227
208
  eventDef: ServiceEventDef<TInfo, TData>,
228
209
  infoSelector: (item: TInfo) => boolean,
@@ -234,18 +215,17 @@ export function createWebSocketHandler(
234
215
  .filter((item) => infoSelector(item.info as TInfo))
235
216
  .map((item) => item.key);
236
217
 
237
- for (const subSock of socketMap.values()) {
238
- const subTargetKeys = subSock.filterEventTargetKeys(targetKeys);
239
- if (subTargetKeys.length > 0) {
240
- await subSock.send(Uuid.generate().toString(), {
241
- name: "evt:on",
242
- body: {
243
- keys: subTargetKeys,
244
- data,
245
- },
246
- });
247
- }
248
- }
218
+ await Promise.allSettled(
219
+ Array.from(socketMap.values()).map(async (subSock) => {
220
+ const subTargetKeys = subSock.filterEventTargetKeys(targetKeys);
221
+ if (subTargetKeys.length > 0) {
222
+ await subSock.send(Uuid.generate().toString(), {
223
+ name: "evt:on",
224
+ body: { keys: subTargetKeys, data },
225
+ });
226
+ }
227
+ }),
228
+ );
249
229
  },
250
230
  };
251
231
  }
@@ -9,6 +9,6 @@ export interface ServiceServerOptions {
9
9
  };
10
10
  auth?: {
11
11
  jwtSecret: string;
12
- };
12
+ } | false;
13
13
  services: ServiceDefinition[];
14
14
  }
@@ -5,12 +5,12 @@ import consola from "consola";
5
5
 
6
6
  const logger = consola.withTag("service-server:ConfigManager");
7
7
 
8
- // Value: Config object, Key: file path
8
+ // 값: 설정 객체, 키: 파일 경로
9
9
  const _cache = new LazyGcMap<string, unknown>({
10
- gcInterval: 10 * 60 * 1000, // Every 10 minutes
11
- expireTime: 60 * 60 * 1000, // Expire after 1 hour
10
+ gcInterval: 10 * 60 * 1000, // 10분마다
11
+ expireTime: 60 * 60 * 1000, // 1시간 만료
12
12
  onExpire: async (filePath) => {
13
- logger.debug(`Config cache expired and watcher released: ${path.basename(filePath)}`);
13
+ logger.debug(`설정 캐시 만료 워처 해제됨: ${path.basename(filePath)}`);
14
14
  await closeWatcher(filePath);
15
15
  },
16
16
  });
@@ -18,18 +18,18 @@ const _cache = new LazyGcMap<string, unknown>({
18
18
  const _watchers = new Map<string, FsWatcher>();
19
19
 
20
20
  export async function getConfig<TConfig>(filePath: string): Promise<TConfig | undefined> {
21
- // 1. Cache hit (time auto-renewed)
21
+ // 1. 캐시 히트 (시간 자동 갱신)
22
22
  if (_cache.has(filePath)) {
23
23
  return _cache.get(filePath) as TConfig;
24
24
  }
25
25
 
26
26
  if (!(await fsx.exists(filePath))) return undefined;
27
27
 
28
- // 2. Load and cache
28
+ // 2. 로드 캐시
29
29
  const config = await fsx.readJson(filePath);
30
30
  _cache.set(filePath, config);
31
31
 
32
- // 3. Register watcher
32
+ // 3. 워처 등록
33
33
  if (!_watchers.has(filePath)) {
34
34
  try {
35
35
  const watcher = await FsWatcher.watch([filePath]);
@@ -39,20 +39,20 @@ export async function getConfig<TConfig>(filePath: string): Promise<TConfig | un
39
39
  if (!(await fsx.exists(filePath))) {
40
40
  _cache.delete(filePath);
41
41
  await closeWatcher(filePath);
42
- logger.debug(`Config file deleted: ${path.basename(filePath)}`);
42
+ logger.debug(`설정 파일 삭제됨: ${path.basename(filePath)}`);
43
43
  return;
44
44
  }
45
45
 
46
46
  try {
47
47
  const newConfig = await fsx.readJson(filePath);
48
48
  _cache.set(filePath, newConfig);
49
- logger.debug(`Config file live-reloaded: ${path.basename(filePath)}`);
49
+ logger.debug(`설정 파일 실시간 리로드됨: ${path.basename(filePath)}`);
50
50
  } catch (err) {
51
- logger.warn(`Config file reload failed: ${filePath}`, err);
51
+ logger.warn(`설정 파일 리로드 실패: ${filePath}`, err);
52
52
  }
53
53
  });
54
54
  } catch (err) {
55
- logger.error(`Watch failed: ${filePath}`, err);
55
+ logger.error(`워치 실패: ${filePath}`, err);
56
56
  }
57
57
  }
58
58
 
@@ -1,8 +0,0 @@
1
- import { type ServiceMethods } from "../core/define-service";
2
- import type { SmtpClientSendByDefaultOption, SmtpClientSendOption } from "@simplysm/service-common";
3
- export declare const SmtpClientService: import("..").ServiceDefinition<{
4
- send(options: SmtpClientSendOption): Promise<string>;
5
- sendByConfig(configName: string, options: SmtpClientSendByDefaultOption): Promise<string>;
6
- }>;
7
- export type SmtpClientServiceType = ServiceMethods<typeof SmtpClientService>;
8
- //# sourceMappingURL=smtp-client-service.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"smtp-client-service.d.ts","sourceRoot":"","sources":["..\\..\\src\\services\\smtp-client-service.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,KAAK,EAEV,6BAA6B,EAC7B,oBAAoB,EACrB,MAAM,0BAA0B,CAAC;AAElC,eAAO,MAAM,iBAAiB;kBACR,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC;6BA6B3B,MAAM,WAAW,6BAA6B,GAAG,OAAO,CAAC,MAAM,CAAC;EAkB9F,CAAC;AAEJ,MAAM,MAAM,qBAAqB,GAAG,cAAc,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
@@ -1,46 +0,0 @@
1
- import nodemailer from "nodemailer";
2
- import { defineService } from "../core/define-service.js";
3
- const SmtpClientService = defineService("SmtpClient", (ctx) => ({
4
- async send(options) {
5
- return new Promise((resolve, reject) => {
6
- const transport = nodemailer.createTransport({
7
- host: options.host,
8
- port: options.port,
9
- secure: options.secure,
10
- auth: options.user != null ? {
11
- user: options.user,
12
- pass: options.pass
13
- } : void 0,
14
- tls: {
15
- rejectUnauthorized: false
16
- }
17
- });
18
- transport.sendMail(options, (err, info) => {
19
- if (err) {
20
- reject(err);
21
- return;
22
- }
23
- resolve(info.messageId);
24
- });
25
- });
26
- },
27
- async sendByConfig(configName, options) {
28
- const config = (await ctx.getConfig("smtp"))[configName];
29
- if (config == null) {
30
- throw new Error(`SMTP config not found: ${configName}`);
31
- }
32
- return this.send({
33
- user: config.user,
34
- pass: config.pass,
35
- host: config.host,
36
- port: config.port,
37
- secure: config.secure,
38
- from: `"${config.senderName}" <${config.senderEmail ?? config.user}>`,
39
- ...options
40
- });
41
- }
42
- }));
43
- export {
44
- SmtpClientService
45
- };
46
- //# sourceMappingURL=smtp-client-service.js.map
@@ -1,6 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/services/smtp-client-service.ts"],
4
- "mappings": "AAAA,OAAO,gBAAgB;AACvB,SAAS,qBAA0C;AAO5C,MAAM,oBAAoB,cAAc,cAAc,CAAC,SAAS;AAAA,EACrE,MAAM,KAAK,SAAgD;AACzD,WAAO,IAAI,QAAgB,CAAC,SAAS,WAAW;AAC9C,YAAM,YAAY,WAAW,gBAAgB;AAAA,QAC3C,MAAM,QAAQ;AAAA,QACd,MAAM,QAAQ;AAAA,QACd,QAAQ,QAAQ;AAAA,QAChB,MACE,QAAQ,QAAQ,OACZ;AAAA,UACE,MAAM,QAAQ;AAAA,UACd,MAAM,QAAQ;AAAA,QAChB,IACA;AAAA,QACN,KAAK;AAAA,UACH,oBAAoB;AAAA,QACtB;AAAA,MACF,CAAC;AAED,gBAAU,SAAS,SAAuC,CAAC,KAAK,SAAS;AACvE,YAAI,KAAK;AACP,iBAAO,GAAG;AACV;AAAA,QACF;AAEA,gBAAQ,KAAK,SAAS;AAAA,MACxB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,aAAa,YAAoB,SAAyD;AAC9F,UAAM,UACJ,MAAM,IAAI,UAAgE,MAAM,GAChF,UAAU;AACZ,QAAI,UAAU,MAAM;AAClB,YAAM,IAAI,MAAM,0BAA0B,UAAU,EAAE;AAAA,IACxD;AAEA,WAAO,KAAK,KAAK;AAAA,MACf,MAAM,OAAO;AAAA,MACb,MAAM,OAAO;AAAA,MACb,MAAM,OAAO;AAAA,MACb,MAAM,OAAO;AAAA,MACb,QAAQ,OAAO;AAAA,MACf,MAAM,IAAI,OAAO,UAAU,MAAM,OAAO,eAAe,OAAO,IAAI;AAAA,MAClE,GAAG;AAAA,IACL,CAAC;AAAA,EACH;AACF,EAAE;",
5
- "names": []
6
- }
package/docs/server.md DELETED
@@ -1,126 +0,0 @@
1
- # Server
2
-
3
- ## `ServiceServerOptions`
4
-
5
- Configuration options for `ServiceServer`.
6
-
7
- ```typescript
8
- interface ServiceServerOptions {
9
- rootPath: string;
10
- port: number;
11
- ssl?: {
12
- pfxBytes: Uint8Array;
13
- passphrase: string;
14
- };
15
- auth?: {
16
- jwtSecret: string;
17
- };
18
- services: ServiceDefinition[];
19
- }
20
- ```
21
-
22
- | Field | Type | Description |
23
- |-------|------|-------------|
24
- | `rootPath` | `string` | Root path for static files, uploads, and config |
25
- | `port` | `number` | Server port |
26
- | `ssl` | `object` | SSL/TLS config (PFX certificate) |
27
- | `ssl.pfxBytes` | `Uint8Array` | PFX certificate bytes |
28
- | `ssl.passphrase` | `string` | Certificate passphrase |
29
- | `auth` | `object` | Authentication config |
30
- | `auth.jwtSecret` | `string` | JWT signing secret |
31
- | `services` | `ServiceDefinition[]` | Service definitions to register |
32
-
33
- ## `ServiceServer`
34
-
35
- Fastify-based service server with WebSocket support, JWT authentication, and built-in services. Extends `EventEmitter`.
36
-
37
- ```typescript
38
- class ServiceServer<TAuthInfo = unknown> extends EventEmitter<{
39
- ready: void;
40
- close: void;
41
- }> {
42
- isOpen: boolean;
43
- readonly fastify: FastifyInstance;
44
- readonly options: ServiceServerOptions;
45
-
46
- constructor(options: ServiceServerOptions);
47
-
48
- listen(): Promise<void>;
49
- close(): Promise<void>;
50
- broadcastReload(clientName: string | undefined, changedFileSet: Set<string>): Promise<void>;
51
- emitEvent<TInfo, TData>(
52
- eventDef: ServiceEventDef<TInfo, TData>,
53
- infoSelector: (item: TInfo) => boolean,
54
- data: TData,
55
- ): Promise<void>;
56
- signAuthToken(payload: AuthTokenPayload<TAuthInfo>): Promise<string>;
57
- verifyAuthToken(token: string): Promise<AuthTokenPayload<TAuthInfo>>;
58
- }
59
- ```
60
-
61
- | Property | Type | Description |
62
- |----------|------|-------------|
63
- | `isOpen` | `boolean` | Whether server is listening |
64
- | `fastify` | `FastifyInstance` | Underlying Fastify instance |
65
- | `options` | `ServiceServerOptions` | Server options |
66
-
67
- | Method | Description |
68
- |--------|-------------|
69
- | `listen()` | Start the server (registers plugins, routes, WebSocket, graceful shutdown) |
70
- | `close()` | Stop the server (close all connections) |
71
- | `broadcastReload()` | Broadcast reload to all connected clients |
72
- | `emitEvent()` | Emit event to matching clients |
73
- | `signAuthToken()` | Sign a JWT token |
74
- | `verifyAuthToken()` | Verify and decode a JWT token |
75
-
76
- ### Registered Routes
77
-
78
- | Route | Method | Description |
79
- |-------|--------|-------------|
80
- | `/api/:service/:method` | GET/POST | HTTP service method endpoint |
81
- | `/upload` | POST | File upload endpoint (multipart) |
82
- | `/ws` | WebSocket | WebSocket service endpoint (Protocol V2) |
83
- | `/` | WebSocket | WebSocket endpoint (V1 legacy + V2) |
84
- | `/*` | GET | Static file serving from `www/` |
85
-
86
- ### Registered Plugins
87
-
88
- - `@fastify/websocket` - WebSocket support
89
- - `@fastify/helmet` - Security headers (CSP, HSTS)
90
- - `@fastify/multipart` - File upload
91
- - `@fastify/static` - Static file serving
92
- - `@fastify/cors` - CORS support
93
-
94
- ## `createServiceServer`
95
-
96
- Factory function to create a `ServiceServer` instance.
97
-
98
- ```typescript
99
- function createServiceServer<TAuthInfo = unknown>(
100
- options: ServiceServerOptions,
101
- ): ServiceServer<TAuthInfo>;
102
- ```
103
-
104
- ## Utils
105
-
106
- ### `getConfig`
107
-
108
- Read JSON configuration from file with caching and live-reload via file watcher. Cache expires after 1 hour.
109
-
110
- ```typescript
111
- async function getConfig<TConfig>(filePath: string): Promise<TConfig | undefined>;
112
- ```
113
-
114
- ## Legacy
115
-
116
- ### `handleV1Connection`
117
-
118
- V1 legacy client handler. Only auto-update is supported; all other requests return an upgrade-required error.
119
-
120
- ```typescript
121
- function handleV1Connection(
122
- socket: WebSocket,
123
- autoUpdateMethods: { getLastVersion: (platform: string) => Promise<any> },
124
- clientNameSetter?: (clientName: string | undefined) => void,
125
- ): void;
126
- ```