@simplysm/service-server 13.0.29 → 13.0.31

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/README.md CHANGED
@@ -16,41 +16,49 @@ pnpm add @simplysm/service-server
16
16
 
17
17
  ### Core Functions and Classes
18
18
 
19
- - [`createServiceServer`](#basic-server-configuration) - Factory function for creating a ServiceServer instance
20
- - [`ServiceServer`](docs/server.md#serviceserver) - Main server class. Creates Fastify instance and configures routes/plugins
19
+ - [`createServiceServer`](#basic-server-configuration) - Factory function for creating a `ServiceServer` instance
20
+ - [`ServiceServer`](docs/server.md#serviceserver) - Main server class. Creates a Fastify instance and configures routes/plugins
21
21
  - [`defineService`](#custom-services) - Defines a service with a factory function pattern
22
- - `ServiceExecutor` - Internal executor that handles service method discovery, auth checks, and execution
22
+ - [`ServiceContext`](docs/server.md#servicecontext) - Context object passed to service factory functions
23
+ - `runServiceMethod` - Internal function that dispatches a service method call (auth checks + execution)
23
24
 
24
25
  ### Authentication
25
26
 
26
- - [`auth`](#authentication) - Function wrapper that sets authentication permissions at service or method level
27
- - [`getServiceAuthPermissions`](#authentication) - Queries auth permissions for a service or method (used internally by `ServiceExecutor`)
28
- - [`JwtManager`](docs/authentication.md#jwtmanager) - JWT token generation/verification/decoding based on jose library (HS256, 12-hour expiration)
27
+ - [`auth`](#authentication) - Wrapper that sets authentication requirements at service or method level
28
+ - [`getServiceAuthPermissions`](docs/authentication.md#getserviceauthpermissions) - Reads auth permissions from an `auth()`-wrapped function
29
+ - [`signJwt`](docs/authentication.md#jwt-functions) - Generate a JWT token (HS256, 12-hour expiration)
30
+ - [`verifyJwt`](docs/authentication.md#jwt-functions) - Verify and decode a JWT token
31
+ - [`decodeJwt`](docs/authentication.md#jwt-functions) - Decode a JWT token without verification (synchronous)
29
32
  - [`AuthTokenPayload`](docs/authentication.md#authtokenpayload) - JWT payload interface (includes `roles`, `data`)
30
33
 
31
34
  ### Transport Layer - WebSocket
32
35
 
33
- - `WebSocketHandler` - Handles WebSocket connection management, message routing, and event distribution
34
- - [`ServiceSocket`](docs/transport.md#servicesocket) - Wraps individual WebSocket connections. Manages ping/pong, protocol encoding/decoding, event listener management
36
+ - [`WebSocketHandler`](docs/transport.md#websockethandler) - Interface for managing multiple WebSocket connections, routing messages, and broadcasting events
37
+ - `createWebSocketHandler` - Factory function that creates a `WebSocketHandler` instance
38
+ - [`ServiceSocket`](docs/transport.md#servicesocket) - Interface wrapping a single WebSocket connection. Manages ping/pong, protocol encoding/decoding, event listener management
39
+ - `createServiceSocket` - Factory function that creates a `ServiceSocket` instance
35
40
 
36
41
  ### Transport Layer - HTTP
37
42
 
38
- - `HttpRequestHandler` - Calls service methods via HTTP at `/api/:service/:method` route
39
- - [`UploadHandler`](docs/transport.md#file-upload) - Handles multipart file upload at `/upload` route (auth required)
40
- - `StaticFileHandler` - Serves static files. Prevents path traversal and blocks hidden files
43
+ - `handleHttpRequest` - Handles service method calls via HTTP at `/api/:service/:method`
44
+ - `handleUpload` - Handles multipart file upload at `/upload` (auth required)
45
+ - `handleStaticFile` - Serves static files from `rootPath/www/`. Prevents path traversal and blocks hidden files
41
46
 
42
47
  ### Protocol
43
48
 
44
- - [`ProtocolWrapper`](docs/transport.md#protocolwrapper) - Message encoding/decoding wrapper. Messages over 30KB are processed in worker threads
49
+ - [`ProtocolWrapper`](docs/transport.md#protocolwrapper) - Interface for message encoding/decoding. Messages over 30KB are processed in worker threads
50
+ - `createProtocolWrapper` - Factory function that creates a `ProtocolWrapper` instance
45
51
 
46
52
  ### Built-in Services
47
53
 
48
54
  - [`OrmService`](docs/built-in-services.md#ormservice) - DB connection/transaction/query execution (WebSocket only, auth required)
55
+ - `OrmServiceType` - Type alias for `OrmService` method signatures (for client-side type sharing)
49
56
  - [`AutoUpdateService`](docs/built-in-services.md#autoupdateservice) - App auto-update (provides latest version query and download path)
57
+ - `AutoUpdateServiceType` - Type alias for `AutoUpdateService` method signatures (for client-side type sharing)
50
58
 
51
59
  ### Utilities
52
60
 
53
- - [`getConfig`](docs/server.md#getconfig) - JSON config file loading with caching and real-time file watching (auto expiration based on LazyGcMap)
61
+ - [`getConfig`](docs/server.md#getconfig) - JSON config file loading with caching and real-time file watching
54
62
 
55
63
  ### Legacy
56
64
 
@@ -119,7 +127,8 @@ The `ctx` parameter provides access to server resources:
119
127
  - `ctx.http` - HTTP request/reply objects (HTTP only, undefined for WebSocket)
120
128
  - `ctx.authInfo` - Authentication info (set via JWT token)
121
129
  - `ctx.clientName` - Client identifier
122
- - `ctx.getConfig(name)` - Get server config by name
130
+ - `ctx.clientPath` - Resolved per-client directory path (`rootPath/www/{clientName}`)
131
+ - `ctx.getConfig(section)` - Get server config by section name
123
132
 
124
133
  ### Authentication
125
134
 
@@ -41,7 +41,7 @@ Method-level `auth()` overrides service-level settings.
41
41
 
42
42
  ## getServiceAuthPermissions
43
43
 
44
- Query auth permissions for a given service definition or method. Primarily used internally by `ServiceExecutor`, but exported for advanced use cases.
44
+ Read auth permissions from an `auth()`-wrapped function. Returns `undefined` if the function is not wrapped with `auth()`. Primarily used internally by `runServiceMethod`, but exported for advanced use cases.
45
45
 
46
46
  ```typescript
47
47
  import { defineService, auth, getServiceAuthPermissions } from "@simplysm/service-server";
@@ -55,43 +55,52 @@ const servicePerms = someServiceDef.authPermissions;
55
55
  // string[] if service-level auth is set, or undefined for public
56
56
  ```
57
57
 
58
- ## JWT Token Management
58
+ | Signature | Returns | Description |
59
+ |-----------|---------|------|
60
+ | `getServiceAuthPermissions(fn: Function)` | `string[] \| undefined` | Returns the permission array attached by `auth()`, or `undefined` if not wrapped |
59
61
 
60
- ### JwtManager
62
+ ## JWT Functions
61
63
 
62
- `JwtManager<TAuthInfo>` handles JWT operations internally. Access its functionality through `ServiceServer` methods.
63
-
64
- | Method | Returns | Description |
65
- |--------|---------|------|
66
- | `sign(payload)` | `Promise<string>` | Generate a JWT token (HS256, 12-hour expiration) |
67
- | `verify(token)` | `Promise<AuthTokenPayload<TAuthInfo>>` | Verify token signature and expiration, return payload |
68
- | `decode(token)` | `AuthTokenPayload<TAuthInfo>` | Decode token without verification (synchronous) |
69
-
70
- Generate and verify JWT tokens through the `ServiceServer` instance:
64
+ Standalone functions for JWT token generation and verification using the `jose` library (HS256 algorithm, 12-hour expiration).
71
65
 
72
66
  ```typescript
73
- import { createServiceServer } from "@simplysm/service-server";
74
-
75
- const server = createServiceServer({
76
- port: 8080,
77
- rootPath: "/app/data",
78
- auth: { jwtSecret: "my-secret-key" },
79
- services: [],
80
- });
67
+ import { signJwt, verifyJwt, decodeJwt } from "@simplysm/service-server";
81
68
 
82
69
  // Generate token (12-hour expiration, HS256 algorithm)
83
- const token = await server.generateAuthToken({
70
+ const token = await signJwt("my-secret-key", {
84
71
  roles: ["admin", "user"],
85
72
  data: { userId: 1, name: "John" },
86
73
  });
87
74
 
88
- // Verify token
89
- const payload = await server.verifyAuthToken(token);
75
+ // Verify token (throws on expiry or invalid signature)
76
+ const payload = await verifyJwt("my-secret-key", token);
90
77
  // payload.roles: ["admin", "user"]
91
78
  // payload.data: { userId: 1, name: "John" }
79
+
80
+ // Decode token without verification (synchronous, no secret needed)
81
+ const decoded = decodeJwt(token);
82
+ ```
83
+
84
+ | Function | Returns | Description |
85
+ |----------|---------|------|
86
+ | `signJwt<TAuthInfo>(jwtSecret, payload)` | `Promise<string>` | Generate a JWT token (HS256, 12-hour expiration) |
87
+ | `verifyJwt<TAuthInfo>(jwtSecret, token)` | `Promise<AuthTokenPayload<TAuthInfo>>` | Verify token signature and expiration, return payload. Throws on invalid or expired tokens |
88
+ | `decodeJwt<TAuthInfo>(token)` | `AuthTokenPayload<TAuthInfo>` | Decode token without verification (synchronous) |
89
+
90
+ In most cases, use `ServiceServer` methods instead of calling these functions directly:
91
+
92
+ ```typescript
93
+ // Generate token via server (preferred)
94
+ const token = await server.generateAuthToken({
95
+ roles: ["admin"],
96
+ data: { userId: 1 },
97
+ });
98
+
99
+ // Verify token via server (preferred)
100
+ const payload = await server.verifyAuthToken(token);
92
101
  ```
93
102
 
94
- ### AuthTokenPayload
103
+ ## AuthTokenPayload
95
104
 
96
105
  ```typescript
97
106
  import type { AuthTokenPayload } from "@simplysm/service-server";
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## OrmService
4
4
 
5
- Provides database connection/query/transaction via WebSocket. `auth()` wrapper is applied, requiring login.
5
+ Provides database connection/query/transaction via WebSocket. The `auth()` wrapper is applied, requiring login.
6
6
 
7
7
  ```typescript
8
8
  import { createServiceServer, OrmService } from "@simplysm/service-server";
@@ -48,39 +48,16 @@ Methods provided by `OrmService`:
48
48
 
49
49
  When a WebSocket connection is closed, all DB connections opened from that socket are automatically cleaned up.
50
50
 
51
- ## CryptoService
52
-
53
- Provides SHA256 hash and AES-256-CBC symmetric key encryption/decryption.
51
+ Use `OrmServiceType` to share method signatures with the client:
54
52
 
55
53
  ```typescript
56
- import { createServiceServer, CryptoService } from "@simplysm/service-server";
57
-
58
- const server = createServiceServer({
59
- port: 8080,
60
- rootPath: "/app/data",
61
- services: [CryptoService],
62
- });
63
- ```
64
-
65
- `.config.json` config:
66
-
67
- ```json
68
- {
69
- "crypto": {
70
- "key": "your-32-byte-secret-key-here!!"
71
- }
72
- }
54
+ import type { OrmServiceType } from "@simplysm/service-server";
55
+ // OrmServiceType = ServiceMethods<typeof OrmService>
73
56
  ```
74
57
 
75
- | Method | Returns | Description |
76
- |--------|---------|------|
77
- | `encrypt(data)` | `Promise<string>` | Generate SHA256 HMAC hash (one-way). `data` is `string \| Uint8Array` |
78
- | `encryptAes(data)` | `Promise<string>` | AES-256-CBC encryption. `data` is `Uint8Array`. Returns hex string in `iv:encrypted` format |
79
- | `decryptAes(encText)` | `Promise<Uint8Array>` | AES-256-CBC decryption. Returns original binary |
80
-
81
58
  ## AutoUpdateService
82
59
 
83
- Supports auto-update for client apps. Searches for latest version files by platform in the client directory.
60
+ Supports auto-update for client apps. Searches for the latest version files by platform in the client directory. No auth is required.
84
61
 
85
62
  ```typescript
86
63
  import { createServiceServer, AutoUpdateService } from "@simplysm/service-server";
@@ -104,7 +81,7 @@ rootPath/www/{clientName}/{platform}/updates/
104
81
 
105
82
  | Method | Returns | Description |
106
83
  |--------|---------|------|
107
- | `getLastVersion(platform)` | `Promise<{ version: string; downloadPath: string } \| undefined>` | Returns latest version and download path for the platform. Returns `undefined` if no update |
84
+ | `getLastVersion(platform)` | `Promise<{ version: string; downloadPath: string } \| undefined>` | Returns the latest version and download path for the platform. Returns `undefined` if no update files exist |
108
85
 
109
86
  Return value example:
110
87
 
@@ -114,3 +91,10 @@ Return value example:
114
91
  downloadPath: "/my-app/android/updates/1.0.1.apk",
115
92
  }
116
93
  ```
94
+
95
+ Use `AutoUpdateServiceType` to share method signatures with the client:
96
+
97
+ ```typescript
98
+ import type { AutoUpdateServiceType } from "@simplysm/service-server";
99
+ // AutoUpdateServiceType = ServiceMethods<typeof AutoUpdateService>
100
+ ```
package/docs/server.md CHANGED
@@ -133,6 +133,7 @@ The `ctx` parameter provides access to server resources within service methods.
133
133
  | `ctx.server` | `ServiceServer<TAuthInfo>` | Server instance reference |
134
134
  | `ctx.socket` | `ServiceSocket \| undefined` | WebSocket connection (`undefined` for HTTP calls) |
135
135
  | `ctx.http` | `{ clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> } \| undefined` | HTTP request context |
136
+ | `ctx.legacy` | `{ clientName?: string } \| undefined` | V1 legacy context (auto-update only) |
136
137
  | `ctx.authInfo` | `TAuthInfo \| undefined` | Authenticated user's custom data (from JWT `data` field) |
137
138
  | `ctx.clientName` | `string \| undefined` | Client app name (validated against path traversal) |
138
139
  | `ctx.clientPath` | `string \| undefined` | Resolved per-client directory path (`rootPath/www/{clientName}`) |
@@ -143,6 +144,16 @@ The `ctx` parameter provides access to server resources within service methods.
143
144
  |--------|---------|------|
144
145
  | `ctx.getConfig<T>(section)` | `Promise<T>` | Read a section from `.config.json` (root + client configs merged) |
145
146
 
147
+ ### createServiceContext
148
+
149
+ Factory function that creates a `ServiceContext` object. Used internally by the server and exported for advanced use cases (e.g., calling services programmatically without an active connection).
150
+
151
+ ```typescript
152
+ import { createServiceContext } from "@simplysm/service-server";
153
+
154
+ const ctx = createServiceContext(server, socket, httpContext, legacyContext);
155
+ ```
156
+
146
157
  ## Config File Reference
147
158
 
148
159
  Read sections from `.config.json` files using `ctx.getConfig()`. Root and per-client configs are automatically merged.
@@ -217,7 +228,7 @@ The following routes are automatically registered when `ServiceServer.listen()`
217
228
  ## Full Server Example
218
229
 
219
230
  ```typescript
220
- import { createServiceServer, defineService, auth, OrmService, CryptoService } from "@simplysm/service-server";
231
+ import { createServiceServer, defineService, auth, OrmService } from "@simplysm/service-server";
221
232
  import { defineEvent } from "@simplysm/service-common";
222
233
 
223
234
  // Define a custom service with auth
@@ -244,7 +255,7 @@ const server = createServiceServer({
244
255
  port: 8080,
245
256
  rootPath: "/app/data",
246
257
  auth: { jwtSecret: "my-secret-key" },
247
- services: [UserService, PublicService, OrmService, CryptoService],
258
+ services: [UserService, PublicService, OrmService],
248
259
  });
249
260
 
250
261
  server.on("ready", () => {
package/docs/transport.md CHANGED
@@ -1,8 +1,23 @@
1
1
  # Transport Layer
2
2
 
3
+ ## WebSocketHandler
4
+
5
+ `WebSocketHandler` is an interface that manages multiple WebSocket connections, routes messages to services, and handles event broadcasting. Create an instance with `createWebSocketHandler`.
6
+
7
+ **Methods:**
8
+
9
+ | Method | Returns | Description |
10
+ |--------|---------|------|
11
+ | `addSocket(socket, clientId, clientName, connReq)` | `void` | Add a new WebSocket connection. If a connection with the same `clientId` already exists, it is closed first |
12
+ | `closeAll()` | `void` | Close all active connections |
13
+ | `broadcastReload(clientName, changedFileSet)` | `Promise<void>` | Send a reload message to all connected clients |
14
+ | `emitToServer(eventDef, infoSelector, data)` | `Promise<void>` | Emit an event to clients whose registered event listeners match the `infoSelector` |
15
+
16
+ `WebSocketHandler` is created and managed internally by `ServiceServer`. Access its functionality through `ServiceServer` methods (`emitEvent`, `broadcastReload`).
17
+
3
18
  ## ServiceSocket
4
19
 
5
- `ServiceSocket` extends `EventEmitter` and wraps an individual WebSocket connection. It is available in service methods as `ctx.socket` when the request comes via WebSocket.
20
+ `ServiceSocket` is an interface wrapping a single WebSocket connection. It is available in service methods as `ctx.socket` when the request comes via WebSocket. Create an instance with `createServiceSocket`.
6
21
 
7
22
  **Properties:**
8
23
 
@@ -22,6 +37,8 @@
22
37
  | `addEventListener(key, eventName, info)` | `void` | Register an event listener for this socket |
23
38
  | `removeEventListener(key)` | `void` | Remove an event listener by key |
24
39
  | `getEventListeners(eventName)` | `{ key, info }[]` | Get all event listeners for a given event name |
40
+ | `filterEventTargetKeys(targetKeys)` | `string[]` | Filter the given keys to only those registered on this socket |
41
+ | `on(event, handler)` | `void` | Register an event handler (`error`, `close`, or `message`) |
25
42
 
26
43
  **Events:**
27
44
 
@@ -57,6 +74,16 @@ Body: ["World"]
57
74
  - Parameters are passed in array form (in the order of method arguments).
58
75
  - For GET requests, pass a JSON-serialized array in the `json` query parameter.
59
76
 
77
+ ### handleHttpRequest
78
+
79
+ The internal handler function registered at `/api/:service/:method`. Exported for advanced use cases.
80
+
81
+ ```typescript
82
+ import { handleHttpRequest } from "@simplysm/service-server";
83
+
84
+ await handleHttpRequest(req, reply, jwtSecret, runMethod);
85
+ ```
86
+
60
87
  ## File Upload
61
88
 
62
89
  Upload files via multipart request to the `/upload` endpoint. Auth token is required.
@@ -81,6 +108,31 @@ const results = await response.json();
81
108
 
82
109
  Uploaded files are stored in the `rootPath/www/uploads/` directory with UUID-based filenames.
83
110
 
111
+ ### handleUpload
112
+
113
+ The internal handler function registered at `/upload`. Exported for advanced use cases.
114
+
115
+ ```typescript
116
+ import { handleUpload } from "@simplysm/service-server";
117
+
118
+ await handleUpload(req, reply, rootPath, jwtSecret);
119
+ ```
120
+
121
+ ### handleStaticFile
122
+
123
+ The internal handler function for static file serving. Exported for advanced use cases.
124
+
125
+ ```typescript
126
+ import { handleStaticFile } from "@simplysm/service-server";
127
+
128
+ await handleStaticFile(req, reply, rootPath, urlPath);
129
+ ```
130
+
131
+ Security behavior:
132
+ - Blocks path traversal (`..`, outside `rootPath/www/`)
133
+ - Returns 403 for files starting with `.` (hidden files)
134
+ - Redirects directory paths without trailing slash to path with trailing slash
135
+
84
136
  ## Real-time Event Publishing
85
137
 
86
138
  Publish events to connected clients from the server using `defineEvent` from `@simplysm/service-common`.
@@ -108,7 +160,7 @@ await server.broadcastReload("my-app", new Set(["main.js"]));
108
160
 
109
161
  ## ProtocolWrapper
110
162
 
111
- Handles encoding/decoding of WebSocket messages. Automatically branches between main thread and worker thread based on message size.
163
+ `ProtocolWrapper` is an interface for encoding/decoding WebSocket messages. Create an instance with `createProtocolWrapper`. Automatically branches between main thread and worker thread based on message size.
112
164
 
113
165
  ```typescript
114
166
  import { createProtocolWrapper } from "@simplysm/service-server";
@@ -138,7 +190,7 @@ Worker thread branching:
138
190
  | 30KB or less | Processed directly in main thread |
139
191
  | Over 30KB | Processed in worker thread (max 4GB memory allocation) |
140
192
 
141
- Messages containing large binary data (Uint8Array) also branch to worker thread.
193
+ Messages containing large binary data (Uint8Array) also branch to worker thread regardless of size.
142
194
 
143
195
  ## Legacy: handleV1Connection
144
196
 
@@ -148,5 +200,11 @@ Handles V1 protocol WebSocket clients. Only supports the `SdAutoUpdateService.ge
148
200
  import { handleV1Connection, AutoUpdateService } from "@simplysm/service-server";
149
201
 
150
202
  // Used internally by ServiceServer for WebSocket connections without ver=2 query parameter
151
- handleV1Connection(webSocket, autoUpdateService);
203
+ handleV1Connection(webSocket, autoUpdateMethods, clientNameSetter);
152
204
  ```
205
+
206
+ | Parameter | Type | Description |
207
+ |-----------|------|------|
208
+ | `socket` | `WebSocket` | The raw WebSocket connection |
209
+ | `autoUpdateMethods` | `{ getLastVersion: (platform: string) => Promise<any> }` | Auto-update service methods |
210
+ | `clientNameSetter` | `((clientName: string \| undefined) => void) \| undefined` | Optional callback to set the client name from the V1 request |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/service-server",
3
- "version": "13.0.29",
3
+ "version": "13.0.31",
4
4
  "description": "심플리즘 패키지 - 서비스 모듈 (server)",
5
5
  "author": "김석래",
6
6
  "license": "Apache-2.0",
@@ -34,11 +34,11 @@
34
34
  "semver": "^7.7.4",
35
35
  "utf-8-validate": "^6.0.6",
36
36
  "ws": "^8.19.0",
37
- "@simplysm/core-common": "13.0.29",
38
- "@simplysm/core-node": "13.0.29",
39
- "@simplysm/orm-node": "13.0.29",
40
- "@simplysm/orm-common": "13.0.29",
41
- "@simplysm/service-common": "13.0.29"
37
+ "@simplysm/core-common": "13.0.31",
38
+ "@simplysm/core-node": "13.0.31",
39
+ "@simplysm/orm-common": "13.0.31",
40
+ "@simplysm/orm-node": "13.0.31",
41
+ "@simplysm/service-common": "13.0.31"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/semver": "^7.7.1",