@simplysm/service-server 13.0.0-beta.22 → 13.0.0-beta.23

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 (2) hide show
  1. package/README.md +296 -71
  2. package/package.json +6 -6
package/README.md CHANGED
@@ -16,61 +16,62 @@ pnpm add @simplysm/service-server
16
16
 
17
17
  ### Core Classes
18
18
 
19
- | Module | Path | Description |
20
- |------|------|------|
21
- | `ServiceServer` | `service-server.ts` | Main server class. Creates Fastify instance and configures routes/plugins |
22
- | `ServiceBase` | `core/service-base.ts` | Service base abstract class. All custom services must inherit from this |
23
- | `ServiceExecutor` | `core/service-executor.ts` | Internal executor that handles service method discovery, auth checks, and execution |
19
+ | Module | Description |
20
+ |--------|------|
21
+ | `ServiceServer` | Main server class. Creates Fastify instance and configures routes/plugins |
22
+ | `ServiceBase` | Service base abstract class. All custom services must inherit from this |
23
+ | `ServiceExecutor` | Internal executor that handles service method discovery, auth checks, and execution |
24
24
 
25
25
  ### Authentication
26
26
 
27
- | Module | Path | Description |
28
- |------|------|------|
29
- | `JwtManager` | `auth/jwt-manager.ts` | JWT token generation/verification/decoding based on jose library (HS256, 12-hour expiration) |
30
- | `Authorize` | `auth/auth.decorators.ts` | Stage 3 decorator. Sets authentication permissions at class or method level |
31
- | `AuthTokenPayload` | `auth/auth-token-payload.ts` | JWT payload interface (includes `roles`, `data`) |
27
+ | Module | Description |
28
+ |--------|------|
29
+ | `JwtManager` | JWT token generation/verification/decoding based on jose library (HS256, 12-hour expiration) |
30
+ | `Authorize` | Stage 3 decorator. Sets authentication permissions at class or method level |
31
+ | `getAuthPermissions` | Queries auth permissions for a service class/method (used internally by `ServiceExecutor`) |
32
+ | `AuthTokenPayload` | JWT payload interface (includes `roles`, `data`) |
32
33
 
33
34
  ### Transport Layer - WebSocket
34
35
 
35
- | Module | Path | Description |
36
- |------|------|------|
37
- | `WebSocketHandler` | `transport/socket/websocket-handler.ts` | Handles WebSocket connection management, message routing, and event distribution |
38
- | `ServiceSocket` | `transport/socket/service-socket.ts` | Wraps individual WebSocket connections. Manages ping/pong, protocol encoding/decoding, event listener management |
36
+ | Module | Description |
37
+ |--------|------|
38
+ | `WebSocketHandler` | Handles WebSocket connection management, message routing, and event distribution |
39
+ | `ServiceSocket` | Wraps individual WebSocket connections. Manages ping/pong, protocol encoding/decoding, event listener management |
39
40
 
40
41
  ### Transport Layer - HTTP
41
42
 
42
- | Module | Path | Description |
43
- |------|------|------|
44
- | `HttpRequestHandler` | `transport/http/http-request-handler.ts` | Calls service methods via HTTP at `/api/:service/:method` route |
45
- | `UploadHandler` | `transport/http/upload-handler.ts` | Handles multipart file upload at `/upload` route (auth required) |
46
- | `StaticFileHandler` | `transport/http/static-file-handler.ts` | Serves static files. Prevents path traversal and blocks hidden files |
43
+ | Module | Description |
44
+ |--------|------|
45
+ | `HttpRequestHandler` | Calls service methods via HTTP at `/api/:service/:method` route |
46
+ | `UploadHandler` | Handles multipart file upload at `/upload` route (auth required) |
47
+ | `StaticFileHandler` | Serves static files. Prevents path traversal and blocks hidden files |
47
48
 
48
49
  ### Protocol
49
50
 
50
- | Module | Path | Description |
51
- |------|------|------|
52
- | `ProtocolWrapper` | `protocol/protocol-wrapper.ts` | Message encoding/decoding wrapper. Messages over 30KB are processed in worker threads |
51
+ | Module | Description |
52
+ |--------|------|
53
+ | `ProtocolWrapper` | Message encoding/decoding wrapper. Messages over 30KB are processed in worker threads |
53
54
 
54
55
  ### Built-in Services
55
56
 
56
- | Module | Path | Description |
57
- |------|------|------|
58
- | `OrmService` | `services/orm-service.ts` | DB connection/transaction/query execution (WebSocket only, auth required) |
59
- | `CryptoService` | `services/crypto-service.ts` | SHA256 hash and AES-256-CBC encryption/decryption |
60
- | `SmtpService` | `services/smtp-service.ts` | nodemailer-based email sending |
61
- | `AutoUpdateService` | `services/auto-update-service.ts` | App auto-update (provides latest version query and download path) |
57
+ | Module | Description |
58
+ |--------|------|
59
+ | `OrmService` | DB connection/transaction/query execution (WebSocket only, auth required) |
60
+ | `CryptoService` | SHA256 hash and AES-256-CBC encryption/decryption |
61
+ | `SmtpService` | nodemailer-based email sending |
62
+ | `AutoUpdateService` | App auto-update (provides latest version query and download path) |
62
63
 
63
64
  ### Utilities
64
65
 
65
- | Module | Path | Description |
66
- |------|------|------|
67
- | `ConfigManager` | `utils/config-manager.ts` | JSON config file loading/caching/real-time monitoring (auto expiration based on LazyGcMap) |
66
+ | Module | Description |
67
+ |--------|------|
68
+ | `ConfigManager` | JSON config file loading/caching/real-time monitoring (auto expiration based on LazyGcMap) |
68
69
 
69
70
  ### Legacy
70
71
 
71
- | Module | Path | Description |
72
- |------|------|------|
73
- | `handleV1Connection` | `legacy/v1-auto-update-handler.ts` | V1 protocol client compatibility handling (supports auto-update only) |
72
+ | Module | Description |
73
+ |--------|------|
74
+ | `handleV1Connection` | V1 protocol client compatibility handling (supports auto-update only) |
74
75
 
75
76
  ## Usage
76
77
 
@@ -101,9 +102,41 @@ server.on("close", () => {
101
102
  await server.close();
102
103
  ```
103
104
 
105
+ ### ServiceServer
106
+
107
+ `ServiceServer<TAuthInfo>` extends `EventEmitter` and is the main entry point for creating a server.
108
+
109
+ **Properties:**
110
+
111
+ | Property | Type | Description |
112
+ |----------|------|------|
113
+ | `options` | `ServiceServerOptions` | Server configuration (read-only, passed via constructor) |
114
+ | `isOpen` | `boolean` | Whether the server is currently listening |
115
+ | `fastify` | `FastifyInstance` | Underlying Fastify instance (read-only, for advanced use) |
116
+
117
+ **Methods:**
118
+
119
+ | Method | Returns | Description |
120
+ |--------|---------|------|
121
+ | `listen()` | `Promise<void>` | Register all plugins/routes and start listening |
122
+ | `close()` | `Promise<void>` | Close all WebSocket connections and shut down the server |
123
+ | `generateAuthToken(payload)` | `Promise<string>` | Generate a JWT token (HS256, 12-hour expiration) |
124
+ | `verifyAuthToken(token)` | `Promise<AuthTokenPayload<TAuthInfo>>` | Verify and decode a JWT token |
125
+ | `emitEvent(eventType, infoSelector, data)` | `Promise<void>` | Publish an event to matching WebSocket clients |
126
+ | `broadcastReload(clientName, changedFileSet)` | `Promise<void>` | Send a reload command to all connected clients |
127
+
128
+ **Events:**
129
+
130
+ | Event | Payload | Description |
131
+ |-------|---------|------|
132
+ | `ready` | `void` | Emitted when the server starts listening |
133
+ | `close` | `void` | Emitted when the server is closed |
134
+
104
135
  ### Server Options (`ServiceServerOptions`)
105
136
 
106
137
  ```typescript
138
+ import type { ServiceServerOptions } from "@simplysm/service-server";
139
+
107
140
  interface ServiceServerOptions {
108
141
  /** Server root path (base directory for static files and config files) */
109
142
  rootPath: string;
@@ -138,6 +171,7 @@ rootPath/
138
171
  ### SSL/HTTPS Server
139
172
 
140
173
  ```typescript
174
+ import { ServiceServer } from "@simplysm/service-server";
141
175
  import { fsReadFile } from "@simplysm/core-node";
142
176
 
143
177
  const pfxBytes = await fsReadFile("/path/to/cert.pfx");
@@ -174,22 +208,32 @@ class MyService extends ServiceBase {
174
208
  }
175
209
  ```
176
210
 
177
- Context accessible within services:
211
+ #### ServiceBase Properties
212
+
213
+ `ServiceBase<TAuthInfo>` is an abstract class. The generic `TAuthInfo` type represents the shape of the authenticated user's data stored in the JWT token.
178
214
 
179
215
  | Property | Type | Description |
180
- |------|------|------|
181
- | `this.server` | `ServiceServer` | Server instance reference |
216
+ |----------|------|------|
217
+ | `this.server` | `ServiceServer<TAuthInfo>` | Server instance reference |
182
218
  | `this.socket` | `ServiceSocket \| undefined` | WebSocket connection (`undefined` for HTTP calls) |
183
- | `this.http` | `{ clientName, authTokenPayload? }` | HTTP request context |
184
- | `this.authInfo` | `TAuthInfo \| undefined` | Authenticated user info |
185
- | `this.clientName` | `string \| undefined` | Client app name |
186
- | `this.clientPath` | `string \| undefined` | Per-client directory path |
219
+ | `this.http` | `{ clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> } \| undefined` | HTTP request context |
220
+ | `this.authInfo` | `TAuthInfo \| undefined` | Authenticated user's custom data (from JWT `data` field) |
221
+ | `this.clientName` | `string \| undefined` | Client app name (validated against path traversal) |
222
+ | `this.clientPath` | `string \| undefined` | Resolved per-client directory path (`rootPath/www/{clientName}`) |
223
+
224
+ #### ServiceBase Methods
225
+
226
+ | Method | Returns | Description |
227
+ |--------|---------|------|
228
+ | `getConfig<T>(section)` | `Promise<T>` | Read a section from `.config.json` (root + client configs merged) |
187
229
 
188
230
  ### Config File Reference
189
231
 
190
232
  Read sections from `.config.json` files using `ServiceBase.getConfig()`. Root and per-client configs are automatically merged.
191
233
 
192
234
  ```typescript
235
+ import { ServiceBase } from "@simplysm/service-server";
236
+
193
237
  class MyService extends ServiceBase {
194
238
  async getDbHost(): Promise<string> {
195
239
  // Read "mySection" key from rootPath/.config.json or clientPath/.config.json
@@ -262,26 +306,65 @@ Decorator behavior:
262
306
 
263
307
  Method-level decorators override class-level settings.
264
308
 
309
+ #### `getAuthPermissions`
310
+
311
+ Query auth permissions for a given service class and method. Primarily used internally by `ServiceExecutor`, but exported for advanced use cases.
312
+
313
+ ```typescript
314
+ import { getAuthPermissions } from "@simplysm/service-server";
315
+
316
+ // Returns string[] if permissions are set, or undefined for public (no decorator)
317
+ const perms = getAuthPermissions(UserService, "deleteUser");
318
+ // ["admin"]
319
+
320
+ const classPerms = getAuthPermissions(UserService);
321
+ // [] (empty array = login required, no specific role)
322
+
323
+ const publicPerms = getAuthPermissions(PublicService, "healthCheck");
324
+ // undefined (no auth required)
325
+ ```
326
+
265
327
  ### JWT Token Management
266
328
 
267
- Generate and verify JWT tokens through the `ServiceServer` instance.
329
+ #### JwtManager
330
+
331
+ `JwtManager<TAuthInfo>` handles JWT operations internally. Access its functionality through `ServiceServer` methods.
332
+
333
+ | Method | Returns | Description |
334
+ |--------|---------|------|
335
+ | `sign(payload)` | `Promise<string>` | Generate a JWT token (HS256, 12-hour expiration) |
336
+ | `verify(token)` | `Promise<AuthTokenPayload<TAuthInfo>>` | Verify token signature and expiration, return payload |
337
+ | `decode(token)` | `AuthTokenPayload<TAuthInfo>` | Decode token without verification (synchronous) |
338
+
339
+ Generate and verify JWT tokens through the `ServiceServer` instance:
268
340
 
269
341
  ```typescript
342
+ import { ServiceServer } from "@simplysm/service-server";
343
+
344
+ const server = new ServiceServer({
345
+ port: 8080,
346
+ rootPath: "/app/data",
347
+ auth: { jwtSecret: "my-secret-key" },
348
+ services: [],
349
+ });
350
+
270
351
  // Generate token (12-hour expiration, HS256 algorithm)
271
352
  const token = await server.generateAuthToken({
272
353
  roles: ["admin", "user"],
273
- data: { userId: 1, name: "홍길동" },
354
+ data: { userId: 1, name: "John" },
274
355
  });
275
356
 
276
357
  // Verify token
277
358
  const payload = await server.verifyAuthToken(token);
278
359
  // payload.roles: ["admin", "user"]
279
- // payload.data: { userId: 1, name: "홍길동" }
360
+ // payload.data: { userId: 1, name: "John" }
280
361
  ```
281
362
 
282
- `AuthTokenPayload` interface:
363
+ #### `AuthTokenPayload`
283
364
 
284
365
  ```typescript
366
+ import type { AuthTokenPayload } from "@simplysm/service-server";
367
+
285
368
  interface AuthTokenPayload<TAuthInfo = unknown> extends JWTPayload {
286
369
  /** User role list (used for permission check in Authorize decorator) */
287
370
  roles: string[];
@@ -290,6 +373,37 @@ interface AuthTokenPayload<TAuthInfo = unknown> extends JWTPayload {
290
373
  }
291
374
  ```
292
375
 
376
+ ### ServiceSocket
377
+
378
+ `ServiceSocket` extends `EventEmitter` and wraps an individual WebSocket connection. It is available in service methods as `this.socket` when the request comes via WebSocket.
379
+
380
+ **Properties:**
381
+
382
+ | Property | Type | Description |
383
+ |----------|------|------|
384
+ | `clientName` | `string` | Client app name (from WebSocket query parameter) |
385
+ | `connectedAtDateTime` | `DateTime` | Connection timestamp |
386
+ | `authTokenPayload` | `AuthTokenPayload \| undefined` | Authenticated token payload (set after `auth` message) |
387
+ | `connReq` | `FastifyRequest` | Original Fastify request that initiated the WebSocket upgrade |
388
+
389
+ **Methods:**
390
+
391
+ | Method | Returns | Description |
392
+ |--------|---------|------|
393
+ | `send(uuid, msg)` | `Promise<number>` | Send a message to this client. Returns total bytes sent |
394
+ | `close()` | `void` | Terminate the WebSocket connection |
395
+ | `addEventListener(key, eventName, info)` | `void` | Register an event listener for this socket |
396
+ | `removeEventListener(key)` | `void` | Remove an event listener by key |
397
+ | `getEventListeners(eventName)` | `{ key, info }[]` | Get all event listeners for a given event name |
398
+
399
+ **Events:**
400
+
401
+ | Event | Payload | Description |
402
+ |-------|---------|------|
403
+ | `error` | `Error` | WebSocket error occurred |
404
+ | `close` | `number` | Connection closed (payload is the close code) |
405
+ | `message` | `{ uuid: string; msg: ServiceClientMessage }` | Decoded message received from client |
406
+
293
407
  ### HTTP API Call
294
408
 
295
409
  Service methods can also be called via HTTP through the `/api/:service/:method` path.
@@ -345,6 +459,7 @@ Uploaded files are stored in the `rootPath/www/uploads/` directory with UUID-bas
345
459
  Publish events to connected clients from the server.
346
460
 
347
461
  ```typescript
462
+ import { ServiceServer } from "@simplysm/service-server";
348
463
  import { ServiceEventListener } from "@simplysm/service-common";
349
464
 
350
465
  // Event definition (from service-common)
@@ -371,6 +486,8 @@ await server.broadcastReload("my-app", new Set(["main.js"]));
371
486
  Provides database connection/query/transaction via WebSocket. `@Authorize()` decorator is applied, requiring login.
372
487
 
373
488
  ```typescript
489
+ import { ServiceServer, OrmService } from "@simplysm/service-server";
490
+
374
491
  const server = new ServiceServer({
375
492
  port: 8080,
376
493
  rootPath: "/app/data",
@@ -398,17 +515,17 @@ Define ORM config in `.config.json`:
398
515
 
399
516
  Methods provided by `OrmService`:
400
517
 
401
- | Method | Description |
402
- |--------|------|
403
- | `getInfo(opt)` | Query DB connection info (dialect, database, schema) |
404
- | `connect(opt)` | Create DB connection. Returns connection ID |
405
- | `close(connId)` | Close DB connection |
406
- | `beginTransaction(connId, isolationLevel?)` | Begin transaction |
407
- | `commitTransaction(connId)` | Commit transaction |
408
- | `rollbackTransaction(connId)` | Rollback transaction |
409
- | `executeParametrized(connId, query, params?)` | Execute parameterized query |
410
- | `executeDefs(connId, defs, options?)` | Execute QueryDef-based queries |
411
- | `bulkInsert(connId, tableName, columnDefs, records)` | Bulk INSERT |
518
+ | Method | Returns | Description |
519
+ |--------|---------|------|
520
+ | `getInfo(opt)` | `Promise<{ dialect, database?, schema? }>` | Query DB connection info |
521
+ | `connect(opt)` | `Promise<number>` | Create DB connection. Returns connection ID |
522
+ | `close(connId)` | `Promise<void>` | Close DB connection |
523
+ | `beginTransaction(connId, isolationLevel?)` | `Promise<void>` | Begin transaction |
524
+ | `commitTransaction(connId)` | `Promise<void>` | Commit transaction |
525
+ | `rollbackTransaction(connId)` | `Promise<void>` | Rollback transaction |
526
+ | `executeParametrized(connId, query, params?)` | `Promise<unknown[][]>` | Execute parameterized query |
527
+ | `executeDefs(connId, defs, options?)` | `Promise<unknown[][]>` | Execute QueryDef-based queries |
528
+ | `bulkInsert(connId, tableName, columnDefs, records)` | `Promise<void>` | Bulk INSERT |
412
529
 
413
530
  When a WebSocket connection is closed, all DB connections opened from that socket are automatically cleaned up.
414
531
 
@@ -417,6 +534,8 @@ When a WebSocket connection is closed, all DB connections opened from that socke
417
534
  Provides SHA256 hash and AES-256-CBC symmetric key encryption/decryption.
418
535
 
419
536
  ```typescript
537
+ import { ServiceServer, CryptoService } from "@simplysm/service-server";
538
+
420
539
  const server = new ServiceServer({
421
540
  port: 8080,
422
541
  rootPath: "/app/data",
@@ -434,17 +553,19 @@ const server = new ServiceServer({
434
553
  }
435
554
  ```
436
555
 
437
- | Method | Description |
438
- |--------|------|
439
- | `encrypt(data)` | Generate SHA256 HMAC hash (one-way) |
440
- | `encryptAes(data)` | AES-256-CBC encryption. Returns hex string in `iv:encrypted` format |
441
- | `decryptAes(encText)` | AES-256-CBC decryption. Returns original binary |
556
+ | Method | Returns | Description |
557
+ |--------|---------|------|
558
+ | `encrypt(data)` | `Promise<string>` | Generate SHA256 HMAC hash (one-way). `data` is `string \| Uint8Array` |
559
+ | `encryptAes(data)` | `Promise<string>` | AES-256-CBC encryption. `data` is `Uint8Array`. Returns hex string in `iv:encrypted` format |
560
+ | `decryptAes(encText)` | `Promise<Uint8Array>` | AES-256-CBC decryption. Returns original binary |
442
561
 
443
562
  ### Built-in Service: SmtpService
444
563
 
445
564
  A nodemailer-based email sending service. Can pass SMTP config directly or reference server config file.
446
565
 
447
566
  ```typescript
567
+ import { ServiceServer, SmtpService } from "@simplysm/service-server";
568
+
448
569
  const server = new ServiceServer({
449
570
  port: 8080,
450
571
  rootPath: "/app/data",
@@ -470,10 +591,10 @@ const server = new ServiceServer({
470
591
  }
471
592
  ```
472
593
 
473
- | Method | Description |
474
- |--------|------|
475
- | `send(options)` | Send email by directly passing SMTP config |
476
- | `sendByConfig(configName, options)` | Send email by referencing SMTP config in config file |
594
+ | Method | Returns | Description |
595
+ |--------|---------|------|
596
+ | `send(options)` | `Promise<string>` | Send email by directly passing SMTP config. Returns message ID |
597
+ | `sendByConfig(configName, options)` | `Promise<string>` | Send email by referencing SMTP config in config file. Returns message ID |
477
598
 
478
599
  `send()` options:
479
600
 
@@ -499,6 +620,8 @@ interface SmtpSendOption {
499
620
  Supports auto-update for client apps. Searches for latest version files by platform in the client directory.
500
621
 
501
622
  ```typescript
623
+ import { ServiceServer, AutoUpdateService } from "@simplysm/service-server";
624
+
502
625
  const server = new ServiceServer({
503
626
  port: 8080,
504
627
  rootPath: "/app/data",
@@ -516,16 +639,16 @@ rootPath/www/{clientName}/{platform}/updates/
516
639
  1.0.1.apk
517
640
  ```
518
641
 
519
- | Method | Description |
520
- |--------|------|
521
- | `getLastVersion(platform)` | Returns latest version and download path for the platform. Returns `undefined` if no update |
642
+ | Method | Returns | Description |
643
+ |--------|---------|------|
644
+ | `getLastVersion(platform)` | `Promise<{ version: string; downloadPath: string } \| undefined>` | Returns latest version and download path for the platform. Returns `undefined` if no update |
522
645
 
523
- Return value:
646
+ Return value example:
524
647
 
525
648
  ```typescript
526
649
  {
527
- version: string; // e.g., "1.0.1"
528
- downloadPath: string; // e.g., "/my-app/android/updates/1.0.1.apk"
650
+ version: "1.0.1",
651
+ downloadPath: "/my-app/android/updates/1.0.1.apk",
529
652
  }
530
653
  ```
531
654
 
@@ -536,18 +659,47 @@ A static utility class that manages loading, caching, and real-time monitoring o
536
659
  ```typescript
537
660
  import { ConfigManager } from "@simplysm/service-server";
538
661
 
662
+ // Returns undefined if the file does not exist
539
663
  const config = await ConfigManager.getConfig<MyConfig>("/path/to/.config.json");
540
664
  ```
541
665
 
666
+ | Method | Returns | Description |
667
+ |--------|---------|------|
668
+ | `ConfigManager.getConfig<T>(filePath)` | `Promise<T \| undefined>` | Load and cache a JSON config file. Returns `undefined` if file not found |
669
+
542
670
  Behavior:
543
671
  - Caches file in `LazyGcMap` on first load.
544
672
  - Registers file change watch (`FsWatcher`) to auto-refresh cache on changes.
545
673
  - Cache auto-expires after 1 hour of no access, and associated watch is released.
674
+ - GC runs every 10 minutes to check for expired entries.
546
675
 
547
676
  ### ProtocolWrapper
548
677
 
549
678
  Handles encoding/decoding of WebSocket messages. Automatically branches between main thread and worker thread based on message size.
550
679
 
680
+ ```typescript
681
+ import { ProtocolWrapper } from "@simplysm/service-server";
682
+
683
+ const protocol = new ProtocolWrapper();
684
+
685
+ // Encode a message into chunks
686
+ const { chunks, totalSize } = await protocol.encode(uuid, message);
687
+
688
+ // Decode received bytes
689
+ const result = await protocol.decode(bytes);
690
+
691
+ // Clean up
692
+ protocol.dispose();
693
+ ```
694
+
695
+ | Method | Returns | Description |
696
+ |--------|---------|------|
697
+ | `encode(uuid, message)` | `Promise<{ chunks: Uint8Array[]; totalSize: number }>` | Encode a message into transmittable chunks |
698
+ | `decode(bytes)` | `Promise<ServiceMessageDecodeResult>` | Decode received bytes into a message |
699
+ | `dispose()` | `void` | Clean up internal protocol resources |
700
+
701
+ Worker thread branching:
702
+
551
703
  | Condition | Processing Method |
552
704
  |------|-----------|
553
705
  | 30KB or less | Processed directly in main thread |
@@ -555,6 +707,79 @@ Handles encoding/decoding of WebSocket messages. Automatically branches between
555
707
 
556
708
  Messages containing large binary data (Uint8Array) also branch to worker thread.
557
709
 
710
+ ### Legacy: handleV1Connection
711
+
712
+ Handles V1 protocol WebSocket clients. Only supports the `SdAutoUpdateService.getLastVersion` command. All other requests return an upgrade-required error.
713
+
714
+ ```typescript
715
+ import { handleV1Connection, AutoUpdateService } from "@simplysm/service-server";
716
+
717
+ // Used internally by ServiceServer for WebSocket connections without ver=2 query parameter
718
+ handleV1Connection(webSocket, autoUpdateService);
719
+ ```
720
+
721
+ ## Full Server Example
722
+
723
+ ```typescript
724
+ import { ServiceServer, ServiceBase, Authorize, OrmService, CryptoService } from "@simplysm/service-server";
725
+ import { ServiceEventListener } from "@simplysm/service-common";
726
+
727
+ // Define a custom service
728
+ @Authorize()
729
+ class UserService extends ServiceBase<{ userId: number; role: string }> {
730
+ async getProfile(): Promise<{ name: string }> {
731
+ const userId = this.authInfo?.userId;
732
+ // Use this.getConfig(), this.socket, this.server, etc.
733
+ return { name: "John" };
734
+ }
735
+
736
+ @Authorize(["admin"])
737
+ async deleteUser(targetId: number): Promise<void> {
738
+ // Admin-only operation
739
+ }
740
+ }
741
+
742
+ class PublicService extends ServiceBase {
743
+ async healthCheck(): Promise<string> {
744
+ return "OK";
745
+ }
746
+ }
747
+
748
+ // Create and start server
749
+ const server = new ServiceServer({
750
+ port: 8080,
751
+ rootPath: "/app/data",
752
+ auth: { jwtSecret: "my-secret-key" },
753
+ services: [UserService, PublicService, OrmService, CryptoService],
754
+ });
755
+
756
+ server.on("ready", () => {
757
+ console.log("Server is ready on port 8080");
758
+ });
759
+
760
+ await server.listen();
761
+
762
+ // Generate auth token for a user
763
+ const token = await server.generateAuthToken({
764
+ roles: ["admin"],
765
+ data: { userId: 1, role: "admin" },
766
+ });
767
+
768
+ // Emit events to connected clients
769
+ class UserUpdatedEvent extends ServiceEventListener<
770
+ { userId: number },
771
+ { action: string }
772
+ > {
773
+ readonly eventName = "UserUpdatedEvent";
774
+ }
775
+
776
+ await server.emitEvent(
777
+ UserUpdatedEvent,
778
+ (info) => info.userId === 1,
779
+ { action: "profile-updated" },
780
+ );
781
+ ```
782
+
558
783
  ## Server Route Structure
559
784
 
560
785
  The following routes are automatically registered when `ServiceServer.listen()` is called:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@simplysm/service-server",
3
3
  "sideEffects": false,
4
- "version": "13.0.0-beta.22",
4
+ "version": "13.0.0-beta.23",
5
5
  "description": "심플리즘 패키지 - 서비스 모듈 (server)",
6
6
  "author": "김석래",
7
7
  "repository": {
@@ -33,11 +33,11 @@
33
33
  "semver": "^7.7.4",
34
34
  "utf-8-validate": "^6.0.6",
35
35
  "ws": "^8.19.0",
36
- "@simplysm/core-common": "13.0.0-beta.22",
37
- "@simplysm/core-node": "13.0.0-beta.22",
38
- "@simplysm/orm-common": "13.0.0-beta.22",
39
- "@simplysm/orm-node": "13.0.0-beta.22",
40
- "@simplysm/service-common": "13.0.0-beta.22"
36
+ "@simplysm/core-common": "13.0.0-beta.23",
37
+ "@simplysm/core-node": "13.0.0-beta.23",
38
+ "@simplysm/orm-common": "13.0.0-beta.23",
39
+ "@simplysm/orm-node": "13.0.0-beta.23",
40
+ "@simplysm/service-common": "13.0.0-beta.23"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/nodemailer": "^7.0.9",