@simplysm/service-server 13.0.69 → 13.0.70

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 (49) hide show
  1. package/README.md +48 -249
  2. package/dist/auth/jwt-manager.js +2 -2
  3. package/dist/auth/jwt-manager.js.map +1 -1
  4. package/dist/core/define-service.js +2 -2
  5. package/dist/core/define-service.js.map +1 -1
  6. package/dist/core/service-executor.js +5 -5
  7. package/dist/core/service-executor.js.map +1 -1
  8. package/dist/legacy/v1-auto-update-handler.d.ts +2 -2
  9. package/dist/legacy/v1-auto-update-handler.js +2 -2
  10. package/dist/legacy/v1-auto-update-handler.js.map +1 -1
  11. package/dist/service-server.js +11 -11
  12. package/dist/service-server.js.map +1 -1
  13. package/dist/services/auto-update-service.js +1 -1
  14. package/dist/services/auto-update-service.js.map +1 -1
  15. package/dist/services/orm-service.js +6 -6
  16. package/dist/services/orm-service.js.map +1 -1
  17. package/dist/transport/http/http-request-handler.js +1 -1
  18. package/dist/transport/http/http-request-handler.js.map +1 -1
  19. package/dist/transport/http/static-file-handler.js +3 -3
  20. package/dist/transport/http/upload-handler.js +2 -2
  21. package/dist/transport/http/upload-handler.js.map +1 -1
  22. package/dist/transport/socket/service-socket.js +2 -2
  23. package/dist/transport/socket/service-socket.js.map +1 -1
  24. package/dist/transport/socket/websocket-handler.d.ts.map +1 -1
  25. package/dist/transport/socket/websocket-handler.js +11 -9
  26. package/dist/transport/socket/websocket-handler.js.map +1 -1
  27. package/dist/utils/config-manager.js +7 -7
  28. package/dist/utils/config-manager.js.map +1 -1
  29. package/package.json +9 -9
  30. package/src/auth/jwt-manager.ts +2 -2
  31. package/src/core/define-service.ts +2 -2
  32. package/src/core/service-executor.ts +13 -13
  33. package/src/legacy/v1-auto-update-handler.ts +8 -8
  34. package/src/service-server.ts +28 -28
  35. package/src/services/auto-update-service.ts +1 -1
  36. package/src/services/orm-service.ts +6 -6
  37. package/src/transport/http/http-request-handler.ts +5 -5
  38. package/src/transport/http/static-file-handler.ts +7 -7
  39. package/src/transport/http/upload-handler.ts +3 -3
  40. package/src/transport/socket/service-socket.ts +4 -4
  41. package/src/transport/socket/websocket-handler.ts +12 -10
  42. package/src/utils/config-manager.ts +11 -11
  43. package/tests/define-service.spec.ts +85 -0
  44. package/tests/orm-service.spec.ts +83 -0
  45. package/tests/service-executor.spec.ts +114 -0
  46. package/docs/authentication.md +0 -114
  47. package/docs/built-in-services.md +0 -100
  48. package/docs/server.md +0 -374
  49. package/docs/transport.md +0 -273
package/docs/server.md DELETED
@@ -1,374 +0,0 @@
1
- # ServiceServer
2
-
3
- ## ServiceServer
4
-
5
- `ServiceServer<TAuthInfo>` extends `EventEmitter` and is the main entry point for creating a server.
6
-
7
- **Properties:**
8
-
9
- | Property | Type | Description |
10
- |----------|------|------|
11
- | `options` | `ServiceServerOptions` | Server configuration (read-only, passed via constructor) |
12
- | `isOpen` | `boolean` | Whether the server is currently listening |
13
- | `fastify` | `FastifyInstance` | Underlying Fastify instance (read-only, for advanced use) |
14
-
15
- **Methods:**
16
-
17
- | Method | Returns | Description |
18
- |--------|---------|------|
19
- | `listen()` | `Promise<void>` | Register all plugins/routes and start listening |
20
- | `close()` | `Promise<void>` | Close all WebSocket connections and shut down the server |
21
- | `generateAuthToken(payload)` | `Promise<string>` | Generate a JWT token (HS256, 12-hour expiration) |
22
- | `verifyAuthToken(token)` | `Promise<AuthTokenPayload<TAuthInfo>>` | Verify and decode a JWT token |
23
- | `emitEvent(eventDef, infoSelector, data)` | `Promise<void>` | Publish an event to matching WebSocket clients |
24
- | `broadcastReload(clientName, changedFileSet)` | `Promise<void>` | Send a reload command to all connected clients |
25
-
26
- **Events:**
27
-
28
- | Event | Payload | Description |
29
- |-------|---------|------|
30
- | `ready` | `void` | Emitted when the server starts listening |
31
- | `close` | `void` | Emitted when the server is closed |
32
-
33
- ## createServiceServer
34
-
35
- Factory function for creating a `ServiceServer` instance:
36
-
37
- ```typescript
38
- import { createServiceServer } from "@simplysm/service-server";
39
-
40
- const server = createServiceServer({
41
- port: 8080,
42
- rootPath: "/app/data",
43
- auth: { jwtSecret: "my-secret-key" },
44
- services: [MyService],
45
- });
46
- ```
47
-
48
- ## Server Options (`ServiceServerOptions`)
49
-
50
- ```typescript
51
- import type { ServiceServerOptions } from "@simplysm/service-server";
52
-
53
- interface ServiceServerOptions {
54
- /** Server root path (base directory for static files and config files) */
55
- rootPath: string;
56
- /** Listen port */
57
- port: number;
58
- /** SSL/TLS config (enables HTTPS) */
59
- ssl?: {
60
- pfxBytes: Uint8Array;
61
- passphrase: string;
62
- };
63
- /** JWT authentication config */
64
- auth?: {
65
- jwtSecret: string;
66
- };
67
- /** List of service definitions to register */
68
- services: ServiceDefinition[];
69
- }
70
- ```
71
-
72
- The following structure is expected under `rootPath`:
73
-
74
- ```
75
- rootPath/
76
- .config.json # Root config file
77
- www/ # Static file root
78
- uploads/ # Upload file storage directory
79
- {clientName}/ # Per-client directory
80
- .config.json # Per-client config file
81
- index.html
82
- ```
83
-
84
- ## SSL/HTTPS Server
85
-
86
- ```typescript
87
- import { createServiceServer } from "@simplysm/service-server";
88
- import { fsReadFile } from "@simplysm/core-node";
89
-
90
- const pfxBytes = await fsReadFile("/path/to/cert.pfx");
91
-
92
- const server = createServiceServer({
93
- port: 443,
94
- rootPath: "/app/data",
95
- ssl: {
96
- pfxBytes,
97
- passphrase: "certificate-password",
98
- },
99
- auth: { jwtSecret: "my-secret-key" },
100
- services: [],
101
- });
102
-
103
- await server.listen();
104
- ```
105
-
106
- ## Custom Service Definition
107
-
108
- Define services using the `defineService` function. Service methods are called via RPC from the client.
109
-
110
- ```typescript
111
- import { defineService } from "@simplysm/service-server";
112
-
113
- export const MyService = defineService("My", (ctx) => ({
114
- hello: async (name: string): Promise<string> => {
115
- return `Hello, ${name}!`;
116
- },
117
-
118
- getServerTime: async (): Promise<Date> => {
119
- return new Date();
120
- },
121
- }));
122
-
123
- // Export type for client-side type sharing
124
- export type MyServiceMethods = import("@simplysm/service-server").ServiceMethods<typeof MyService>;
125
- ```
126
-
127
- ### ServiceContext
128
-
129
- The `ctx` parameter provides access to server resources within service methods.
130
-
131
- | Property | Type | Description |
132
- |----------|------|------|
133
- | `ctx.server` | `ServiceServer<TAuthInfo>` | Server instance reference |
134
- | `ctx.socket` | `ServiceSocket \| undefined` | WebSocket connection (`undefined` for HTTP calls) |
135
- | `ctx.http` | `{ clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> } \| undefined` | HTTP request context |
136
- | `ctx.legacy` | `{ clientName?: string } \| undefined` | V1 legacy context (auto-update only) |
137
- | `ctx.authInfo` | `TAuthInfo \| undefined` | Authenticated user's custom data (from JWT `data` field) |
138
- | `ctx.clientName` | `string \| undefined` | Client app name (validated against path traversal) |
139
- | `ctx.clientPath` | `string \| undefined` | Resolved per-client directory path (`rootPath/www/{clientName}`) |
140
-
141
- ### ServiceContext Methods
142
-
143
- | Method | Returns | Description |
144
- |--------|---------|------|
145
- | `ctx.getConfig<T>(section)` | `Promise<T>` | Read a section from `.config.json` (root + client configs merged) |
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
-
157
- | Parameter | Type | Description |
158
- |-----------|------|------|
159
- | `server` | `ServiceServer<TAuthInfo>` | The server instance |
160
- | `socket` | `ServiceSocket \| undefined` | WebSocket connection, or `undefined` for HTTP/legacy |
161
- | `http` | `{ clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> } \| undefined` | HTTP request context |
162
- | `legacy` | `{ clientName?: string } \| undefined` | V1 legacy context (auto-update only) |
163
-
164
- ## ServiceDefinition
165
-
166
- Type describing a registered service. Created by `defineService()`.
167
-
168
- ```typescript
169
- import type { ServiceDefinition } from "@simplysm/service-server";
170
-
171
- interface ServiceDefinition<TMethods = Record<string, (...args: any[]) => any>> {
172
- /** Service name (used as RPC route prefix, e.g., "User" → "User.getProfile") */
173
- name: string;
174
- /** Factory function that returns the service's method object */
175
- factory: (ctx: ServiceContext) => TMethods;
176
- /** Auth permissions required at service level (set by wrapping factory with auth()) */
177
- authPermissions?: string[];
178
- }
179
- ```
180
-
181
- Pass `ServiceDefinition[]` to `ServiceServerOptions.services` when creating the server.
182
-
183
- ## ServiceMethods
184
-
185
- Type utility that extracts method signatures from a `ServiceDefinition`. Use this to share method types with the client without exposing implementation details.
186
-
187
- ```typescript
188
- import type { ServiceMethods } from "@simplysm/service-server";
189
-
190
- export const MyService = defineService("My", (ctx) => ({
191
- hello: async (name: string): Promise<string> => `Hello, ${name}!`,
192
- }));
193
-
194
- // Export type for client-side type sharing
195
- export type MyServiceType = ServiceMethods<typeof MyService>;
196
- // Equivalent to: { hello: (name: string) => Promise<string> }
197
-
198
- // Client usage:
199
- // const service = client.getService<MyServiceType>("My");
200
- // const result = await service.hello("World");
201
- ```
202
-
203
- ## runServiceMethod
204
-
205
- Dispatches a single service method call. Performs auth checks and executes the method. Used internally by `ServiceServer` for both WebSocket and HTTP transports, but exported for advanced use cases such as custom transports or testing.
206
-
207
- ```typescript
208
- import { runServiceMethod } from "@simplysm/service-server";
209
-
210
- const result = await runServiceMethod(server, {
211
- serviceName: "My",
212
- methodName: "hello",
213
- params: ["World"],
214
- socket: serviceSocket, // or undefined for HTTP
215
- http: undefined, // or { clientName, authTokenPayload } for HTTP
216
- });
217
- ```
218
-
219
- | Parameter | Type | Description |
220
- |-----------|------|------|
221
- | `server` | `ServiceServer` | The server instance |
222
- | `def.serviceName` | `string` | Name of the service to dispatch to |
223
- | `def.methodName` | `string` | Name of the method to invoke |
224
- | `def.params` | `unknown[]` | Positional arguments for the method |
225
- | `def.socket` | `ServiceSocket \| undefined` | WebSocket context (for WebSocket requests) |
226
- | `def.http` | `{ clientName: string; authTokenPayload?: AuthTokenPayload } \| undefined` | HTTP context (for HTTP requests) |
227
-
228
- Returns: `Promise<unknown>` — the method's return value.
229
-
230
- Throws if the service or method is not found, or if the caller lacks required auth permissions.
231
-
232
- ## Config File Reference
233
-
234
- Read sections from `.config.json` files using `ctx.getConfig()`. Root and per-client configs are automatically merged.
235
-
236
- ```typescript
237
- import { defineService } from "@simplysm/service-server";
238
-
239
- export const MyService = defineService("My", (ctx) => ({
240
- getDbHost: async (): Promise<string> => {
241
- // Read "mySection" key from rootPath/.config.json or clientPath/.config.json
242
- const config = await ctx.getConfig<{ host: string }>("mySection");
243
- return config.host;
244
- },
245
- }));
246
- ```
247
-
248
- `.config.json` example:
249
-
250
- ```json
251
- {
252
- "mySection": {
253
- "host": "localhost"
254
- },
255
- "orm": {
256
- "default": {
257
- "dialect": "mysql",
258
- "host": "localhost",
259
- "port": 3306,
260
- "database": "mydb",
261
- "user": "root",
262
- "password": "password"
263
- }
264
- }
265
- }
266
- ```
267
-
268
- Config files are cached and automatically refreshed on file changes (LazyGcMap-based, auto expires after 1 hour).
269
-
270
- ## getConfig
271
-
272
- A standalone function that loads, caches, and watches JSON config files. Used internally by `ctx.getConfig()`.
273
-
274
- ```typescript
275
- import { getConfig } from "@simplysm/service-server";
276
-
277
- // Returns undefined if the file does not exist
278
- const config = await getConfig<MyConfig>("/path/to/.config.json");
279
- ```
280
-
281
- | Signature | Returns | Description |
282
- |-----------|---------|------|
283
- | `getConfig<T>(filePath: string)` | `Promise<T \| undefined>` | Load and cache a JSON config file. Returns `undefined` if file not found |
284
-
285
- Behavior:
286
- - Caches file in `LazyGcMap` on first load.
287
- - Registers file change watch (`FsWatcher`) to auto-refresh cache on changes.
288
- - Cache auto-expires after 1 hour of no access, and associated watcher is released.
289
- - GC runs every 10 minutes to check for expired entries.
290
-
291
- ## Server Route Structure
292
-
293
- The following routes are automatically registered when `ServiceServer.listen()` is called:
294
-
295
- | Route | Method | Description |
296
- |--------|--------|------|
297
- | `/api/:service/:method` | GET, POST | Service method call via HTTP |
298
- | `/upload` | POST | Multipart file upload (auth required) |
299
- | `/` | WebSocket | WebSocket connection endpoint |
300
- | `/ws` | WebSocket | WebSocket connection endpoint (alias) |
301
- | `/*` | GET, etc. | Static file serving (based on `rootPath/www/`) |
302
-
303
- ## Full Server Example
304
-
305
- ```typescript
306
- import { createServiceServer, defineService, auth, OrmService } from "@simplysm/service-server";
307
- import { defineEvent } from "@simplysm/service-common";
308
-
309
- // Define a custom service with auth
310
- export const UserService = defineService("User", auth((ctx) => ({
311
- getProfile: async (): Promise<{ name: string }> => {
312
- const userId = (ctx.authInfo as { userId: number; role: string })?.userId;
313
- // Use ctx.getConfig(), ctx.socket, ctx.server, etc.
314
- return { name: "John" };
315
- },
316
-
317
- deleteUser: auth(["admin"], async (targetId: number): Promise<void> => {
318
- // Admin-only operation
319
- }),
320
- })));
321
-
322
- export const PublicService = defineService("Public", (ctx) => ({
323
- healthCheck: async (): Promise<string> => {
324
- return "OK";
325
- },
326
- }));
327
-
328
- // Create and start server
329
- const server = createServiceServer({
330
- port: 8080,
331
- rootPath: "/app/data",
332
- auth: { jwtSecret: "my-secret-key" },
333
- services: [UserService, PublicService, OrmService],
334
- });
335
-
336
- server.on("ready", () => {
337
- console.log("Server is ready on port 8080");
338
- });
339
-
340
- await server.listen();
341
-
342
- // Generate auth token for a user
343
- const token = await server.generateAuthToken({
344
- roles: ["admin"],
345
- data: { userId: 1, role: "admin" },
346
- });
347
-
348
- // Emit events to connected clients
349
- const UserUpdatedEvent = defineEvent<
350
- { userId: number },
351
- { action: string }
352
- >("UserUpdatedEvent");
353
-
354
- await server.emitEvent(
355
- UserUpdatedEvent,
356
- (info) => info.userId === 1,
357
- { action: "profile-updated" },
358
- );
359
- ```
360
-
361
- ## Security
362
-
363
- - **Helmet**: `@fastify/helmet` plugin automatically sets security headers like CSP, HSTS.
364
- - **CORS**: `@fastify/cors` plugin configures CORS.
365
- - **Path Traversal Prevention**: Static file handler and client name validation block `..`, `/`, `\` characters.
366
- - **Hidden File Blocking**: Files starting with `.` return a 403 response.
367
- - **Graceful Shutdown**: Detects `SIGINT`/`SIGTERM` signals to safely close open WebSocket connections and server (10-second timeout).
368
-
369
- ## Caveats
370
-
371
- - `OrmService` is WebSocket-only. Cannot be used via HTTP requests.
372
- - Config files (`.config.json`) contain sensitive information (DB passwords, JWT secrets, etc.), so hidden files (starting with `.`) are automatically blocked by the static file handler.
373
- - WebSocket connection requires query parameters `ver=2`, `clientId`, `clientName`. Without these parameters, it operates in V1 legacy mode.
374
- - If SSL is not configured, the `upgrade-insecure-requests` CSP directive is disabled.
package/docs/transport.md DELETED
@@ -1,273 +0,0 @@
1
- # Transport Layer
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
-
18
- ### createWebSocketHandler
19
-
20
- Factory function that creates a `WebSocketHandler` instance.
21
-
22
- ```typescript
23
- import { createWebSocketHandler } from "@simplysm/service-server";
24
-
25
- const handler = createWebSocketHandler(runMethod, jwtSecret);
26
- ```
27
-
28
- | Parameter | Type | Description |
29
- |-----------|------|------|
30
- | `runMethod` | `(def: { serviceName: string; methodName: string; params: unknown[]; socket?: ServiceSocket }) => Promise<unknown>` | Function to execute service method calls |
31
- | `jwtSecret` | `string \| undefined` | JWT secret for authentication (`undefined` disables auth) |
32
-
33
- **Returns:** `WebSocketHandler`
34
-
35
- ## ServiceSocket
36
-
37
- `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`.
38
-
39
- **Properties:**
40
-
41
- | Property | Type | Description |
42
- |----------|------|------|
43
- | `clientName` | `string` | Client app name (from WebSocket query parameter) |
44
- | `connectedAtDateTime` | `DateTime` | Connection timestamp |
45
- | `authTokenPayload` | `AuthTokenPayload \| undefined` | Authenticated token payload (set after `auth` message) |
46
- | `connReq` | `FastifyRequest` | Original Fastify request that initiated the WebSocket upgrade |
47
-
48
- **Methods:**
49
-
50
- | Method | Returns | Description |
51
- |--------|---------|------|
52
- | `send(uuid, msg)` | `Promise<number>` | Send a message to this client. Returns total bytes sent |
53
- | `close()` | `void` | Terminate the WebSocket connection |
54
- | `addEventListener(key, eventName, info)` | `void` | Register an event listener for this socket |
55
- | `removeEventListener(key)` | `void` | Remove an event listener by key |
56
- | `getEventListeners(eventName)` | `{ key, info }[]` | Get all event listeners for a given event name |
57
- | `filterEventTargetKeys(targetKeys)` | `string[]` | Filter the given keys to only those registered on this socket |
58
- | `on(event, handler)` | `void` | Register an event handler (`error`, `close`, or `message`) |
59
-
60
- **Events:**
61
-
62
- | Event | Payload | Description |
63
- |-------|---------|------|
64
- | `error` | `Error` | WebSocket error occurred |
65
- | `close` | `number` | Connection closed (payload is the close code) |
66
- | `message` | `{ uuid: string; msg: ServiceClientMessage }` | Decoded message received from client |
67
-
68
- ### createServiceSocket
69
-
70
- Factory function that creates a `ServiceSocket` instance wrapping a raw WebSocket.
71
-
72
- ```typescript
73
- import { createServiceSocket } from "@simplysm/service-server";
74
-
75
- const serviceSocket = createServiceSocket(socket, clientId, clientName, connReq);
76
- ```
77
-
78
- | Parameter | Type | Description |
79
- |-----------|------|------|
80
- | `socket` | `WebSocket` | Raw WebSocket connection |
81
- | `clientId` | `string` | Unique client identifier |
82
- | `clientName` | `string` | Client app name |
83
- | `connReq` | `FastifyRequest` | Original Fastify request that initiated the WebSocket upgrade |
84
-
85
- **Returns:** `ServiceSocket`
86
-
87
- ## HTTP API Call
88
-
89
- Service methods can also be called via HTTP through the `/api/:service/:method` path.
90
-
91
- **GET Request:**
92
-
93
- ```
94
- GET /api/MyService/hello?json=["World"]
95
- Header: x-sd-client-name: my-app
96
- Header: Authorization: Bearer <token> (optional)
97
- ```
98
-
99
- **POST Request:**
100
-
101
- ```
102
- POST /api/MyService/hello
103
- Header: Content-Type: application/json
104
- Header: x-sd-client-name: my-app
105
- Header: Authorization: Bearer <token> (optional)
106
- Body: ["World"]
107
- ```
108
-
109
- - The `x-sd-client-name` header is required.
110
- - Parameters are passed in array form (in the order of method arguments).
111
- - For GET requests, pass a JSON-serialized array in the `json` query parameter.
112
-
113
- ### handleHttpRequest
114
-
115
- The internal handler function registered at `/api/:service/:method`. Exported for advanced use cases.
116
-
117
- ```typescript
118
- import { handleHttpRequest } from "@simplysm/service-server";
119
-
120
- await handleHttpRequest(req, reply, jwtSecret, runMethod);
121
- ```
122
-
123
- | Parameter | Type | Description |
124
- |-----------|------|------|
125
- | `req` | `FastifyRequest` | Fastify request object |
126
- | `reply` | `FastifyReply` | Fastify reply object |
127
- | `jwtSecret` | `string \| undefined` | JWT secret for authentication |
128
- | `runMethod` | `(def: { serviceName: string; methodName: string; params: unknown[]; http: { clientName: string; authTokenPayload?: AuthTokenPayload } }) => Promise<unknown>` | Function to execute service method calls |
129
-
130
- ## File Upload
131
-
132
- Upload files via multipart request to the `/upload` endpoint. Auth token is required.
133
-
134
- ```typescript
135
- // Client-side example
136
- const formData = new FormData();
137
- formData.append("file", file);
138
-
139
- const response = await fetch("/upload", {
140
- method: "POST",
141
- headers: {
142
- Authorization: `Bearer ${token}`,
143
- },
144
- body: formData,
145
- });
146
-
147
- // Response: ServiceUploadResult[]
148
- const results = await response.json();
149
- // [{ path: "uploads/uuid.ext", filename: "original-filename.ext", size: 12345 }]
150
- ```
151
-
152
- Uploaded files are stored in the `rootPath/www/uploads/` directory with UUID-based filenames.
153
-
154
- ### handleUpload
155
-
156
- The internal handler function registered at `/upload`. Exported for advanced use cases.
157
-
158
- ```typescript
159
- import { handleUpload } from "@simplysm/service-server";
160
-
161
- await handleUpload(req, reply, rootPath, jwtSecret);
162
- ```
163
-
164
- | Parameter | Type | Description |
165
- |-----------|------|------|
166
- | `req` | `FastifyRequest` | Fastify request object (must be multipart) |
167
- | `reply` | `FastifyReply` | Fastify reply object |
168
- | `rootPath` | `string` | Server root path (files stored under `rootPath/www/uploads/`) |
169
- | `jwtSecret` | `string \| undefined` | JWT secret for authentication |
170
-
171
- ### handleStaticFile
172
-
173
- The internal handler function for static file serving. Exported for advanced use cases.
174
-
175
- ```typescript
176
- import { handleStaticFile } from "@simplysm/service-server";
177
-
178
- await handleStaticFile(req, reply, rootPath, urlPath);
179
- ```
180
-
181
- | Parameter | Type | Description |
182
- |-----------|------|------|
183
- | `req` | `FastifyRequest` | Fastify request object |
184
- | `reply` | `FastifyReply` | Fastify reply object |
185
- | `rootPath` | `string` | Server root path (serves files from `rootPath/www/`) |
186
- | `urlPath` | `string` | URL path relative to the root |
187
-
188
- Security behavior:
189
- - Blocks path traversal (`..`, outside `rootPath/www/`)
190
- - Returns 403 for files starting with `.` (hidden files)
191
- - Redirects directory paths without trailing slash to path with trailing slash
192
-
193
- ## Real-time Event Publishing
194
-
195
- Publish events to connected clients from the server using `defineEvent` from `@simplysm/service-common`.
196
-
197
- ```typescript
198
- import { createServiceServer } from "@simplysm/service-server";
199
- import { defineEvent } from "@simplysm/service-common";
200
-
201
- // Event definition
202
- const OrderUpdatedEvent = defineEvent<
203
- { orderId: number },
204
- { status: string }
205
- >("OrderUpdatedEvent");
206
-
207
- // Publish event from server
208
- await server.emitEvent(
209
- OrderUpdatedEvent,
210
- (info) => info.orderId === 123, // Target filter
211
- { status: "completed" }, // Data to send
212
- );
213
-
214
- // Send reload command to all clients
215
- await server.broadcastReload("my-app", new Set(["main.js"]));
216
- ```
217
-
218
- ## ProtocolWrapper
219
-
220
- `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. Uses a shared worker singleton internally.
221
-
222
- ### createProtocolWrapper
223
-
224
- Factory function that creates a `ProtocolWrapper` instance.
225
-
226
- **Returns:** `ProtocolWrapper` (no parameters)
227
-
228
- ```typescript
229
- import { createProtocolWrapper } from "@simplysm/service-server";
230
-
231
- const protocol = createProtocolWrapper();
232
-
233
- // Encode a message into chunks
234
- const { chunks, totalSize } = await protocol.encode(uuid, message);
235
-
236
- // Decode received bytes
237
- const result = await protocol.decode(bytes);
238
-
239
- // Clean up
240
- protocol.dispose();
241
- ```
242
-
243
- | Method | Returns | Description |
244
- |--------|---------|------|
245
- | `encode(uuid, message)` | `Promise<{ chunks: Uint8Array[]; totalSize: number }>` | Encode a message into transmittable chunks |
246
- | `decode(bytes)` | `Promise<ServiceMessageDecodeResult>` | Decode received bytes into a message |
247
- | `dispose()` | `void` | Clean up internal protocol resources |
248
-
249
- Worker thread branching:
250
-
251
- | Condition | Processing Method |
252
- |------|-----------|
253
- | 30KB or less | Processed directly in main thread |
254
- | Over 30KB | Processed in worker thread (max 4GB memory allocation) |
255
-
256
- Messages containing large binary data (Uint8Array) also branch to worker thread regardless of size.
257
-
258
- ## Legacy: handleV1Connection
259
-
260
- Handles V1 protocol WebSocket clients. Only supports the `SdAutoUpdateService.getLastVersion` command. All other requests return an upgrade-required error.
261
-
262
- ```typescript
263
- import { handleV1Connection, AutoUpdateService } from "@simplysm/service-server";
264
-
265
- // Used internally by ServiceServer for WebSocket connections without ver=2 query parameter
266
- handleV1Connection(webSocket, autoUpdateMethods, clientNameSetter);
267
- ```
268
-
269
- | Parameter | Type | Description |
270
- |-----------|------|------|
271
- | `socket` | `WebSocket` | The raw WebSocket connection |
272
- | `autoUpdateMethods` | `{ getLastVersion: (platform: string) => Promise<any> }` | Auto-update service methods |
273
- | `clientNameSetter` | `((clientName: string \| undefined) => void) \| undefined` | Optional callback to set the client name from the V1 request |