@simplysm/service-server 13.0.98 → 13.0.99

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
@@ -1,6 +1,8 @@
1
1
  # @simplysm/service-server
2
2
 
3
- Service module (server) -- Fastify-based service server with WebSocket support, JWT authentication, and built-in ORM/SMTP/auto-update services.
3
+ Simplysm package - service module (server)
4
+
5
+ Fastify-based service server with WebSocket support, JWT authentication, and built-in ORM/SMTP/auto-update services. Works with `@simplysm/service-client` for client-server communication.
4
6
 
5
7
  ## Installation
6
8
 
@@ -8,68 +10,108 @@ Service module (server) -- Fastify-based service server with WebSocket support,
8
10
  npm install @simplysm/service-server
9
11
  ```
10
12
 
11
- ## Exports
13
+ ## API Overview
12
14
 
13
- ```typescript
14
- import {
15
- // Main
16
- ServiceServer,
17
- createServiceServer,
18
- type ServiceServerOptions,
19
- // Auth
20
- type AuthTokenPayload,
21
- signJwt,
22
- verifyJwt,
23
- decodeJwt,
24
- // Core
25
- type ServiceContext,
26
- createServiceContext,
27
- getServiceAuthPermissions,
28
- auth,
29
- type ServiceDefinition,
30
- defineService,
31
- type ServiceMethods,
32
- executeServiceMethod,
33
- // Transport - Socket
34
- type WebSocketHandler,
35
- createWebSocketHandler,
36
- type ServiceSocket,
37
- createServiceSocket,
38
- // Transport - HTTP
39
- handleHttpRequest,
40
- handleUpload,
41
- handleStaticFile,
42
- // Protocol
43
- type ServerProtocolWrapper,
44
- createServerProtocolWrapper,
45
- // Services
46
- OrmService,
47
- type OrmServiceType,
48
- AutoUpdateService,
49
- type AutoUpdateServiceType,
50
- SmtpClientService,
51
- type SmtpClientServiceType,
52
- // Utils
53
- getConfig,
54
- // Legacy
55
- handleV1Connection,
56
- } from "@simplysm/service-server";
57
- ```
15
+ ### Types
16
+ | API | Type | Description |
17
+ |-----|------|-------------|
18
+ | `ServiceServerOptions` | interface | Server config (rootPath, port, ssl, auth, services) |
19
+
20
+ -> See [docs/server.md](./docs/server.md) for details.
21
+
22
+ ### Auth
23
+ | API | Type | Description |
24
+ |-----|------|-------------|
25
+ | `AuthTokenPayload` | interface | JWT payload with roles and custom data |
26
+ | `signJwt` | function | Sign a JWT token (HS256, 12h expiry) |
27
+ | `verifyJwt` | function | Verify and decode a JWT token |
28
+ | `decodeJwt` | function | Decode JWT without verification |
29
+
30
+ -> See [docs/auth.md](./docs/auth.md) for details.
31
+
32
+ ### Core
33
+ | API | Type | Description |
34
+ |-----|------|-------------|
35
+ | `ServiceContext` | interface | Context with server, socket, auth, config access |
36
+ | `createServiceContext` | function | Create a service context |
37
+ | `ServiceDefinition` | interface | Service definition (name, factory, auth) |
38
+ | `defineService` | function | Define a service with name and factory |
39
+ | `auth` | function | Auth wrapper for services/methods (login/roles) |
40
+ | `getServiceAuthPermissions` | function | Read auth permissions from wrapped function |
41
+ | `ServiceMethods` | type | Extract method types from ServiceDefinition |
42
+ | `executeServiceMethod` | function | Execute service method with auth checks |
43
+
44
+ -> See [docs/core.md](./docs/core.md) for details.
45
+
46
+ ### Transport - Socket
47
+ | API | Type | Description |
48
+ |-----|------|-------------|
49
+ | `WebSocketHandler` | interface | Multi-connection WebSocket manager |
50
+ | `createWebSocketHandler` | function | Create WebSocket handler |
51
+ | `ServiceSocket` | interface | Single WebSocket connection manager |
52
+ | `createServiceSocket` | function | Create service socket |
53
+
54
+ -> See [docs/transport.md](./docs/transport.md) for details.
55
+
56
+ ### Transport - HTTP
57
+ | API | Type | Description |
58
+ |-----|------|-------------|
59
+ | `handleHttpRequest` | function | Handle HTTP API requests |
60
+ | `handleUpload` | function | Handle file upload (multipart) |
61
+ | `handleStaticFile` | function | Serve static files from www/ |
62
+
63
+ -> See [docs/transport.md](./docs/transport.md) for details.
64
+
65
+ ### Protocol
66
+ | API | Type | Description |
67
+ |-----|------|-------------|
68
+ | `ServerProtocolWrapper` | interface | Server protocol with worker thread offloading |
69
+ | `createServerProtocolWrapper` | function | Create server protocol wrapper |
70
+
71
+ -> See [docs/transport.md](./docs/transport.md) for details.
72
+
73
+ ### Services
74
+ | API | Type | Description |
75
+ |-----|------|-------------|
76
+ | `OrmService` | const | Built-in ORM service (DB connection, queries) |
77
+ | `AutoUpdateService` | const | Built-in auto-update service |
78
+ | `SmtpClientService` | const | Built-in SMTP email service |
79
+
80
+ -> See [docs/services.md](./docs/services.md) for details.
81
+
82
+ ### Utils
83
+ | API | Type | Description |
84
+ |-----|------|-------------|
85
+ | `getConfig` | function | Read JSON config with caching and live-reload |
58
86
 
59
- ## Quick Start
87
+ -> See [docs/server.md](./docs/server.md) for details.
88
+
89
+ ### Main
90
+ | API | Type | Description |
91
+ |-----|------|-------------|
92
+ | `ServiceServer` | class | Main server (Fastify, WebSocket, auth, events) |
93
+ | `createServiceServer` | function | Factory to create ServiceServer |
94
+
95
+ -> See [docs/server.md](./docs/server.md) for details.
96
+
97
+ ### Legacy
98
+ | API | Type | Description |
99
+ |-----|------|-------------|
100
+ | `handleV1Connection` | function | V1 legacy client handler (auto-update only) |
101
+
102
+ -> See [docs/server.md](./docs/server.md) for details.
103
+
104
+ ## Usage Examples
105
+
106
+ ### Basic Server Setup
60
107
 
61
108
  ```typescript
62
- import {
63
- createServiceServer,
64
- defineService,
65
- auth,
66
- OrmService,
67
- AutoUpdateService,
68
- } from "@simplysm/service-server";
109
+ import { createServiceServer, defineService, auth } from "@simplysm/service-server";
110
+ import { OrmService, AutoUpdateService } from "@simplysm/service-server";
69
111
 
70
112
  // Define a custom service
71
113
  const HealthService = defineService("Health", (ctx) => ({
72
- check: () => ({ status: "ok" }),
114
+ check: () => ({ status: "ok", time: new Date().toISOString() }),
73
115
  }));
74
116
 
75
117
  // Define an authenticated service
@@ -89,10 +131,33 @@ const server = createServiceServer({
89
131
  await server.listen();
90
132
  ```
91
133
 
92
- ## Documentation
134
+ ### JWT Authentication
135
+
136
+ ```typescript
137
+ import { signJwt, verifyJwt } from "@simplysm/service-server";
93
138
 
94
- - [Auth](docs/auth.md)
95
- - [Core](docs/core.md)
96
- - [Transport](docs/transport.md)
97
- - [Built-in Services](docs/services.md)
98
- - [Server](docs/server.md)
139
+ // Sign token (in login handler)
140
+ const token = await server.signAuthToken({
141
+ roles: ["user", "admin"],
142
+ data: { userId: 123, name: "John" },
143
+ });
144
+
145
+ // Verify token
146
+ const payload = await server.verifyAuthToken(token);
147
+ console.log(payload.data.name); // "John"
148
+ ```
149
+
150
+ ### Server-Side Event Emission
151
+
152
+ ```typescript
153
+ import { defineEvent } from "@simplysm/service-common";
154
+
155
+ const OrderUpdated = defineEvent<{ orderId: number }, { status: string }>("OrderUpdated");
156
+
157
+ // Emit to all listeners with matching orderId
158
+ await server.emitEvent(
159
+ OrderUpdated,
160
+ (info) => info.orderId === 123,
161
+ { status: "shipped" },
162
+ );
163
+ ```
package/docs/auth.md CHANGED
@@ -4,7 +4,7 @@ JWT-based authentication utilities using the `jose` library (HS256 algorithm).
4
4
 
5
5
  ## `AuthTokenPayload`
6
6
 
7
- JWT token payload structure. Extends `JWTPayload` from `jose`.
7
+ JWT token payload with roles and custom data. Extends `JWTPayload` from `jose`.
8
8
 
9
9
  ```typescript
10
10
  interface AuthTokenPayload<TAuthInfo = unknown> extends JWTPayload {
@@ -13,9 +13,14 @@ interface AuthTokenPayload<TAuthInfo = unknown> extends JWTPayload {
13
13
  }
14
14
  ```
15
15
 
16
+ | Field | Type | Description |
17
+ |-------|------|-------------|
18
+ | `roles` | `string[]` | User roles for permission checking |
19
+ | `data` | `TAuthInfo` | Custom auth data (user info, etc.) |
20
+
16
21
  ## `signJwt`
17
22
 
18
- Sign a JWT token. Tokens expire after 12 hours.
23
+ Sign a JWT token with HS256 algorithm. Token expires in 12 hours.
19
24
 
20
25
  ```typescript
21
26
  async function signJwt<TAuthInfo = unknown>(
@@ -24,9 +29,14 @@ async function signJwt<TAuthInfo = unknown>(
24
29
  ): Promise<string>;
25
30
  ```
26
31
 
32
+ | Parameter | Type | Description |
33
+ |-----------|------|-------------|
34
+ | `jwtSecret` | `string` | Secret key for signing |
35
+ | `payload` | `AuthTokenPayload<TAuthInfo>` | Token payload |
36
+
27
37
  ## `verifyJwt`
28
38
 
29
- Verify a JWT token and return the payload.
39
+ Verify and decode a JWT token. Throws on expired or invalid tokens.
30
40
 
31
41
  ```typescript
32
42
  async function verifyJwt<TAuthInfo = unknown>(
@@ -35,13 +45,14 @@ async function verifyJwt<TAuthInfo = unknown>(
35
45
  ): Promise<AuthTokenPayload<TAuthInfo>>;
36
46
  ```
37
47
 
38
- Throws:
39
- - `"Token has expired."` if the token is expired
40
- - `"Invalid token."` for all other verification failures
48
+ | Parameter | Type | Description |
49
+ |-----------|------|-------------|
50
+ | `jwtSecret` | `string` | Secret key for verification |
51
+ | `token` | `string` | JWT token string |
41
52
 
42
53
  ## `decodeJwt`
43
54
 
44
- Decode a JWT token without verification.
55
+ Decode a JWT token without verification (for reading payload only).
45
56
 
46
57
  ```typescript
47
58
  function decodeJwt<TAuthInfo = unknown>(token: string): AuthTokenPayload<TAuthInfo>;
package/docs/core.md CHANGED
@@ -1,10 +1,8 @@
1
1
  # Core
2
2
 
3
- Service definition, context, authentication, and method execution.
3
+ ## `ServiceContext`
4
4
 
5
- ## ServiceContext
6
-
7
- Context object passed to service factory functions.
5
+ Context object passed to service factory functions. Provides access to server, socket, auth info, and configuration.
8
6
 
9
7
  ```typescript
10
8
  interface ServiceContext<TAuthInfo = unknown> {
@@ -14,7 +12,9 @@ interface ServiceContext<TAuthInfo = unknown> {
14
12
  clientName: string;
15
13
  authTokenPayload?: AuthTokenPayload<TAuthInfo>;
16
14
  };
17
- legacy?: { clientName?: string };
15
+ legacy?: {
16
+ clientName?: string;
17
+ };
18
18
 
19
19
  get authInfo(): TAuthInfo | undefined;
20
20
  get clientName(): string | undefined;
@@ -23,13 +23,20 @@ interface ServiceContext<TAuthInfo = unknown> {
23
23
  }
24
24
  ```
25
25
 
26
- **Properties:**
27
- - `authInfo` -- Authenticated user data (from socket or HTTP auth token)
28
- - `clientName` -- Client application name (validated for path traversal)
29
- - `clientPath` -- Resolved client directory path (`{rootPath}/www/{clientName}`)
30
- - `getConfig(section)` -- Reads config from `.config.json` files (root + client-specific, merged)
26
+ | Property | Type | Description |
27
+ |----------|------|-------------|
28
+ | `server` | `ServiceServer<TAuthInfo>` | Server instance |
29
+ | `socket` | `ServiceSocket` | WebSocket connection (if via WebSocket) |
30
+ | `http` | `object` | HTTP request info (if via HTTP) |
31
+ | `legacy` | `object` | V1 legacy context (auto-update only) |
32
+ | `authInfo` | `TAuthInfo \| undefined` | Authenticated user data |
33
+ | `clientName` | `string \| undefined` | Client name |
34
+ | `clientPath` | `string \| undefined` | Resolved client path on disk |
35
+ | `getConfig()` | `<T>(section) => Promise<T>` | Read config from `.config.json` files |
36
+
37
+ ## `createServiceContext`
31
38
 
32
- ### `createServiceContext`
39
+ Create a service context instance.
33
40
 
34
41
  ```typescript
35
42
  function createServiceContext<TAuthInfo = unknown>(
@@ -40,44 +47,9 @@ function createServiceContext<TAuthInfo = unknown>(
40
47
  ): ServiceContext<TAuthInfo>;
41
48
  ```
42
49
 
43
- ---
44
-
45
- ## Auth Helpers
46
-
47
- ### `getServiceAuthPermissions`
48
-
49
- Read auth permissions from an `auth()`-wrapped function. Returns `undefined` if not wrapped.
50
-
51
- ```typescript
52
- function getServiceAuthPermissions(fn: Function): string[] | undefined;
53
- ```
54
-
55
- ### `auth`
56
-
57
- Auth wrapper for service factories and methods.
58
-
59
- ```typescript
60
- // Login required (no specific roles)
61
- function auth<TFunction extends (...args: any[]) => any>(fn: TFunction): TFunction;
62
-
63
- // Login required with specific roles
64
- function auth<TFunction extends (...args: any[]) => any>(
65
- permissions: string[],
66
- fn: TFunction,
67
- ): TFunction;
68
- ```
69
-
70
- **Usage levels:**
71
- - Service-level: `auth((ctx) => ({ ... }))` -- all methods require login
72
- - Service-level with roles: `auth(["admin"], (ctx) => ({ ... }))`
73
- - Method-level: `auth(() => result)` -- this method requires login
74
- - Method-level with roles: `auth(["admin"], () => result)`
75
-
76
- ---
50
+ ## `ServiceDefinition`
77
51
 
78
- ## Service Definition
79
-
80
- ### `ServiceDefinition`
52
+ Service definition object. Contains name, factory function, and optional auth permissions.
81
53
 
82
54
  ```typescript
83
55
  interface ServiceDefinition<TMethods = Record<string, (...args: any[]) => any>> {
@@ -87,7 +59,13 @@ interface ServiceDefinition<TMethods = Record<string, (...args: any[]) => any>>
87
59
  }
88
60
  ```
89
61
 
90
- ### `defineService`
62
+ | Field | Type | Description |
63
+ |-------|------|-------------|
64
+ | `name` | `string` | Service name |
65
+ | `factory` | `(ctx: ServiceContext) => TMethods` | Factory function that creates method object |
66
+ | `authPermissions` | `string[]` | Required permissions (from `auth()` wrapper) |
67
+
68
+ ## `defineService`
91
69
 
92
70
  Define a service with a name and factory function.
93
71
 
@@ -98,40 +76,48 @@ function defineService<TMethods extends Record<string, (...args: any[]) => any>>
98
76
  ): ServiceDefinition<TMethods>;
99
77
  ```
100
78
 
101
- **Example:**
102
- ```typescript
103
- const HealthService = defineService("Health", (ctx) => ({
104
- check: () => ({ status: "ok" }),
105
- }));
106
-
107
- const UserService = defineService("User", auth((ctx) => ({
108
- getProfile: () => ctx.authInfo,
109
- adminOnly: auth(["admin"], () => "admin"),
110
- })));
111
- ```
79
+ | Parameter | Type | Description |
80
+ |-----------|------|-------------|
81
+ | `name` | `string` | Service name |
82
+ | `factory` | `(ctx: ServiceContext) => TMethods` | Factory function |
112
83
 
113
- ### `ServiceMethods`
84
+ ## `auth`
114
85
 
115
- Extract method signatures from a `ServiceDefinition` for client-side type sharing.
86
+ Auth wrapper for service factories and methods. Can be applied at service-level or method-level with optional role requirements.
116
87
 
117
88
  ```typescript
118
- type ServiceMethods<TDefinition> =
119
- TDefinition extends ServiceDefinition<infer M> ? M : never;
89
+ function auth<TFunction extends (...args: any[]) => any>(fn: TFunction): TFunction;
90
+ function auth<TFunction extends (...args: any[]) => any>(
91
+ permissions: string[],
92
+ fn: TFunction,
93
+ ): TFunction;
120
94
  ```
121
95
 
122
- **Example:**
96
+ | Overload | Description |
97
+ |----------|-------------|
98
+ | `auth(fn)` | Require login (any authenticated user) |
99
+ | `auth(["admin"], fn)` | Require specific roles |
100
+
101
+ ## `getServiceAuthPermissions`
102
+
103
+ Read auth permissions from an `auth()`-wrapped function. Returns `undefined` if not wrapped.
104
+
123
105
  ```typescript
124
- export type UserServiceType = ServiceMethods<typeof UserService>;
125
- // Client: client.getService<UserServiceType>("User");
106
+ function getServiceAuthPermissions(fn: Function): string[] | undefined;
126
107
  ```
127
108
 
128
- ---
109
+ ## `ServiceMethods`
110
+
111
+ Extract method signatures from a `ServiceDefinition` for client-side type sharing.
129
112
 
130
- ## Service Execution
113
+ ```typescript
114
+ type ServiceMethods<TDefinition> =
115
+ TDefinition extends ServiceDefinition<infer M> ? M : never;
116
+ ```
131
117
 
132
- ### `executeServiceMethod`
118
+ ## `executeServiceMethod`
133
119
 
134
- Execute a service method with auth checking.
120
+ Execute a service method with authentication and authorization checks.
135
121
 
136
122
  ```typescript
137
123
  async function executeServiceMethod(
@@ -145,17 +131,3 @@ async function executeServiceMethod(
145
131
  },
146
132
  ): Promise<unknown>;
147
133
  ```
148
-
149
- **Behavior:**
150
- 1. Finds the service definition by name
151
- 2. Validates the client name (path traversal guard)
152
- 3. Creates a `ServiceContext`
153
- 4. Invokes the factory to create the method object
154
- 5. Checks auth permissions (method-level first, then service-level fallback)
155
- 6. Executes the method with provided params
156
-
157
- Throws:
158
- - `"Service [name] not found."` if service is not registered
159
- - `"Method [service.method] not found."` if method does not exist
160
- - `"Login is required."` if auth is required but no token is present
161
- - `"Insufficient permissions."` if the user lacks required roles
package/docs/server.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Server
2
2
 
3
- ## ServiceServerOptions
3
+ ## `ServiceServerOptions`
4
4
 
5
- Server configuration options.
5
+ Configuration options for `ServiceServer`.
6
6
 
7
7
  ```typescript
8
8
  interface ServiceServerOptions {
@@ -19,188 +19,108 @@ interface ServiceServerOptions {
19
19
  }
20
20
  ```
21
21
 
22
- ## ServerProtocolWrapper
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 |
23
32
 
24
- Server-side protocol wrapper. Automatically offloads heavy message encoding/decoding to a worker thread while using main thread for lightweight operations.
33
+ ## `ServiceServer`
25
34
 
26
- ```typescript
27
- interface ServerProtocolWrapper {
28
- encode(uuid: string, message: ServiceMessage): Promise<{ chunks: Bytes[]; totalSize: number }>;
29
- decode(bytes: Bytes): Promise<ServiceMessageDecodeResult<ServiceMessage>>;
30
- dispose(): void;
31
- }
32
- ```
33
-
34
- ### `createServerProtocolWrapper`
35
-
36
- ```typescript
37
- function createServerProtocolWrapper(): ServerProtocolWrapper;
38
- ```
39
-
40
- **Behavior:**
41
- - Messages with `Uint8Array` body or arrays containing `Uint8Array` are encoded via worker thread
42
- - Messages larger than 30KB are decoded via worker thread
43
- - Worker is a lazy singleton shared across all protocol wrappers (4GB memory limit)
44
- - Small messages are processed on the main thread using `createServiceProtocol()`
45
-
46
- ---
47
-
48
- ## Config Utilities
49
-
50
- ### `getConfig`
51
-
52
- Read and cache a JSON config file with automatic live-reload via file watcher.
53
-
54
- ```typescript
55
- async function getConfig<TConfig>(filePath: string): Promise<TConfig | undefined>;
56
- ```
57
-
58
- **Behavior:**
59
- - Returns `undefined` if file does not exist
60
- - Caches loaded config in a `LazyGcMap` (expires after 1 hour, GC runs every 10 minutes)
61
- - Registers a file watcher that live-reloads config on changes
62
- - Watcher is cleaned up when the cache entry expires
63
-
64
- ---
65
-
66
- ## Legacy
67
-
68
- ### `handleV1Connection`
69
-
70
- V1 legacy client handler. Only auto-update is supported; all other requests return an upgrade-required error.
71
-
72
- ```typescript
73
- function handleV1Connection(
74
- socket: WebSocket,
75
- autoUpdateMethods: { getLastVersion: (platform: string) => Promise<any> },
76
- clientNameSetter?: (clientName: string | undefined) => void,
77
- ): void;
78
- ```
79
-
80
- ---
81
-
82
- ## ServiceServer
83
-
84
- Main server class. Extends `EventEmitter<{ ready: void; close: void }>`.
35
+ Fastify-based service server with WebSocket support, JWT authentication, and built-in services. Extends `EventEmitter`.
85
36
 
86
37
  ```typescript
87
- class ServiceServer<TAuthInfo = unknown> extends EventEmitter<{ ready: void; close: void }> {
38
+ class ServiceServer<TAuthInfo = unknown> extends EventEmitter<{
39
+ ready: void;
40
+ close: void;
41
+ }> {
88
42
  isOpen: boolean;
89
43
  readonly fastify: FastifyInstance;
90
44
  readonly options: ServiceServerOptions;
91
45
 
92
46
  constructor(options: ServiceServerOptions);
93
47
 
94
- async listen(): Promise<void>;
95
- async close(): Promise<void>;
96
- async broadcastReload(clientName: string | undefined, changedFileSet: Set<string>): Promise<void>;
97
- async emitEvent<TInfo, TData>(
48
+ listen(): Promise<void>;
49
+ close(): Promise<void>;
50
+ broadcastReload(clientName: string | undefined, changedFileSet: Set<string>): Promise<void>;
51
+ emitEvent<TInfo, TData>(
98
52
  eventDef: ServiceEventDef<TInfo, TData>,
99
53
  infoSelector: (item: TInfo) => boolean,
100
54
  data: TData,
101
55
  ): Promise<void>;
102
- async signAuthToken(payload: AuthTokenPayload<TAuthInfo>): Promise<string>;
103
- async verifyAuthToken(token: string): Promise<AuthTokenPayload<TAuthInfo>>;
56
+ signAuthToken(payload: AuthTokenPayload<TAuthInfo>): Promise<string>;
57
+ verifyAuthToken(token: string): Promise<AuthTokenPayload<TAuthInfo>>;
104
58
  }
105
59
  ```
106
60
 
107
- ### `listen`
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
108
93
 
109
- Start the server. Registers all Fastify plugins and routes:
110
-
111
- - `@fastify/websocket` -- WebSocket support
112
- - `@fastify/helmet` -- Security headers
113
- - `@fastify/multipart` -- File upload support
114
- - `@fastify/static` -- Static file serving
115
- - `@fastify/cors` -- Cross-origin resource sharing
116
-
117
- **Routes:**
118
- - `GET/POST /api/:service/:method` -- HTTP API endpoint
119
- - `POST /upload` -- File upload endpoint
120
- - `GET /ws` or `GET /` (WebSocket) -- WebSocket connection (V2 protocol with V1 legacy fallback)
121
- - `GET/POST/PUT/DELETE/PATCH/HEAD /*` -- Static file wildcard handler
122
-
123
- Registers graceful shutdown handlers for `SIGINT` and `SIGTERM` (10s timeout before force exit).
124
-
125
- ### `close`
126
-
127
- Close all WebSocket connections and shut down the Fastify server.
128
-
129
- ### `broadcastReload`
130
-
131
- Broadcast a reload message to all connected WebSocket clients.
132
-
133
- ```typescript
134
- async broadcastReload(
135
- clientName: string | undefined,
136
- changedFileSet: Set<string>,
137
- ): Promise<void>;
138
- ```
139
-
140
- ### `emitEvent`
94
+ ## `createServiceServer`
141
95
 
142
- Emit an event to matching WebSocket clients.
96
+ Factory function to create a `ServiceServer` instance.
143
97
 
144
98
  ```typescript
145
- async emitEvent<TInfo, TData>(
146
- eventDef: ServiceEventDef<TInfo, TData>,
147
- infoSelector: (item: TInfo) => boolean,
148
- data: TData,
149
- ): Promise<void>;
99
+ function createServiceServer<TAuthInfo = unknown>(
100
+ options: ServiceServerOptions,
101
+ ): ServiceServer<TAuthInfo>;
150
102
  ```
151
103
 
152
- ### `signAuthToken`
153
-
154
- Sign a JWT auth token.
155
-
156
- ```typescript
157
- async signAuthToken(payload: AuthTokenPayload<TAuthInfo>): Promise<string>;
158
- ```
104
+ ## Utils
159
105
 
160
- ### `verifyAuthToken`
106
+ ### `getConfig`
161
107
 
162
- Verify a JWT auth token.
108
+ Read JSON configuration from file with caching and live-reload via file watcher. Cache expires after 1 hour.
163
109
 
164
110
  ```typescript
165
- async verifyAuthToken(token: string): Promise<AuthTokenPayload<TAuthInfo>>;
111
+ async function getConfig<TConfig>(filePath: string): Promise<TConfig | undefined>;
166
112
  ```
167
113
 
168
- ## `createServiceServer`
169
-
170
- Factory function.
114
+ ## Legacy
171
115
 
172
- ```typescript
173
- function createServiceServer<TAuthInfo = unknown>(
174
- options: ServiceServerOptions,
175
- ): ServiceServer<TAuthInfo>;
176
- ```
116
+ ### `handleV1Connection`
177
117
 
178
- ## Example
118
+ V1 legacy client handler. Only auto-update is supported; all other requests return an upgrade-required error.
179
119
 
180
120
  ```typescript
181
- import {
182
- createServiceServer,
183
- defineService,
184
- auth,
185
- OrmService,
186
- AutoUpdateService,
187
- SmtpClientService,
188
- } from "@simplysm/service-server";
189
-
190
- const MyService = defineService("My", auth((ctx) => ({
191
- hello: (name: string) => `Hello, ${name}!`,
192
- })));
193
-
194
- const server = createServiceServer({
195
- rootPath: "/app",
196
- port: 3000,
197
- auth: { jwtSecret: process.env.JWT_SECRET! },
198
- services: [MyService, OrmService, AutoUpdateService, SmtpClientService],
199
- });
200
-
201
- server.on("ready", () => {
202
- console.log("Server is ready");
203
- });
204
-
205
- await server.listen();
121
+ function handleV1Connection(
122
+ socket: WebSocket,
123
+ autoUpdateMethods: { getLastVersion: (platform: string) => Promise<any> },
124
+ clientNameSetter?: (clientName: string | undefined) => void,
125
+ ): void;
206
126
  ```
package/docs/services.md CHANGED
@@ -2,175 +2,57 @@
2
2
 
3
3
  Pre-defined service definitions ready to use with `ServiceServer`.
4
4
 
5
- ## OrmService
5
+ ## `OrmService`
6
6
 
7
- Database ORM service. Requires authentication (wrapped with `auth()`). Requires WebSocket connection (cannot be used over HTTP).
8
-
9
- ```typescript
10
- import { OrmService, type OrmServiceType } from "@simplysm/service-server";
11
- ```
12
-
13
- ### Definition
7
+ Server-side ORM service implementation. Requires authentication. Manages database connections per WebSocket socket (using `WeakMap`). Supports MySQL, MSSQL, and PostgreSQL via `@simplysm/orm-node`.
14
8
 
15
9
  ```typescript
16
10
  const OrmService: ServiceDefinition;
17
11
  ```
18
12
 
19
- Service name: `"Orm"`
20
-
21
- ### Methods
13
+ Implements the `OrmService` interface from `@simplysm/service-common`:
22
14
 
23
- ```typescript
24
- interface OrmServiceType {
25
- getInfo(opt: DbConnOptions & { configName: string }): Promise<{
26
- dialect: Dialect;
27
- database?: string;
28
- schema?: string;
29
- }>;
30
- connect(opt: DbConnOptions & { configName: string }): Promise<number>;
31
- close(connId: number): Promise<void>;
32
- beginTransaction(connId: number, isolationLevel?: IsolationLevel): Promise<void>;
33
- commitTransaction(connId: number): Promise<void>;
34
- rollbackTransaction(connId: number): Promise<void>;
35
- executeParametrized(connId: number, query: string, params?: unknown[]): Promise<unknown[][]>;
36
- executeDefs(
37
- connId: number,
38
- defs: QueryDef[],
39
- options?: (ResultMeta | undefined)[],
40
- ): Promise<unknown[][]>;
41
- bulkInsert(
42
- connId: number,
43
- tableName: string,
44
- columnDefs: Record<string, ColumnMeta>,
45
- records: Record<string, unknown>[],
46
- ): Promise<void>;
47
- }
48
- ```
49
-
50
- **Behavior:**
51
- - Database connections are tracked per WebSocket socket using a `WeakMap`
52
- - Connections are automatically cleaned up when the socket closes
53
- - Configuration is loaded from `.config.json` under the `"orm"` section
54
- - Supports `mssql-azure` dialect (mapped to `mssql` for query building)
55
-
56
- **Configuration example** (`.config.json`):
57
- ```json
58
- {
59
- "orm": {
60
- "myDb": {
61
- "dialect": "mysql",
62
- "host": "localhost",
63
- "port": 3306,
64
- "database": "mydb",
65
- "user": "root",
66
- "password": "secret"
67
- }
68
- }
69
- }
70
- ```
15
+ | Method | Description |
16
+ |--------|-------------|
17
+ | `getInfo()` | Get database dialect and connection info from config |
18
+ | `connect()` | Open a new database connection, returns connection ID |
19
+ | `close()` | Close a database connection |
20
+ | `beginTransaction()` | Begin transaction |
21
+ | `commitTransaction()` | Commit transaction |
22
+ | `rollbackTransaction()` | Rollback transaction |
23
+ | `executeParametrized()` | Execute parameterized SQL |
24
+ | `executeDefs()` | Execute QueryDef array (builds SQL via QueryBuilder) |
25
+ | `bulkInsert()` | Bulk insert records |
71
26
 
72
- ---
27
+ **Note:** ORM service requires WebSocket connection (cannot be used over HTTP).
73
28
 
74
- ## AutoUpdateService
29
+ ## `AutoUpdateService`
75
30
 
76
- Auto-update service for client applications. No authentication required.
77
-
78
- ```typescript
79
- import { AutoUpdateService, type AutoUpdateServiceType } from "@simplysm/service-server";
80
- ```
81
-
82
- ### Definition
31
+ Server-side auto-update service implementation. Scans the client's platform-specific `updates/` directory for version files.
83
32
 
84
33
  ```typescript
85
34
  const AutoUpdateService: ServiceDefinition;
86
35
  ```
87
36
 
88
- Service name: `"AutoUpdate"`
89
-
90
- ### Methods
91
-
92
- ```typescript
93
- interface AutoUpdateServiceType {
94
- getLastVersion(platform: string): Promise<
95
- | { version: string; downloadPath: string }
96
- | undefined
97
- >;
98
- }
99
- ```
37
+ Implements the `AutoUpdateService` interface from `@simplysm/service-common`:
100
38
 
101
- **Behavior:**
102
- - Scans `{clientPath}/{platform}/updates/` for version files
103
- - Android: looks for `.apk` files
104
- - Other platforms: looks for `.exe` files
105
- - Returns the highest semver version found
106
- - Returns `undefined` if no updates directory or no valid versions exist
39
+ | Method | Description |
40
+ |--------|-------------|
41
+ | `getLastVersion(platform)` | Find latest version file for platform (win32, android, etc.) |
107
42
 
108
- ---
43
+ Supported platforms and file extensions:
44
+ - `android`: `.apk` files
45
+ - Other platforms: `.exe` files
109
46
 
110
- ## SmtpClientService
47
+ ## `SmtpClientService`
111
48
 
112
- SMTP email sending service. No authentication required.
113
-
114
- ```typescript
115
- import { SmtpClientService, type SmtpClientServiceType } from "@simplysm/service-server";
116
- ```
117
-
118
- ### Definition
49
+ Server-side SMTP email sending service. Uses `nodemailer` under the hood.
119
50
 
120
51
  ```typescript
121
52
  const SmtpClientService: ServiceDefinition;
122
53
  ```
123
54
 
124
- Service name: `"SmtpClient"`
125
-
126
- ### Methods
127
-
128
- ```typescript
129
- interface SmtpClientServiceType {
130
- send(options: SmtpClientSendOption): Promise<string>;
131
- sendByConfig(configName: string, options: SmtpClientSendByDefaultOption): Promise<string>;
132
- }
133
- ```
134
-
135
- **`send`** -- Send email with explicit SMTP configuration. Returns the message ID.
136
-
137
- **`sendByConfig`** -- Send email using server-side SMTP configuration. Configuration is loaded from `.config.json` under the `"smtp"` section.
138
-
139
- **Configuration example** (`.config.json`):
140
- ```json
141
- {
142
- "smtp": {
143
- "default": {
144
- "senderName": "My App",
145
- "senderEmail": "noreply@example.com",
146
- "user": "smtp-user",
147
- "pass": "smtp-pass",
148
- "host": "smtp.example.com",
149
- "port": 587,
150
- "secure": false
151
- }
152
- }
153
- }
154
- ```
155
-
156
- ---
157
-
158
- ## Usage
159
-
160
- Register built-in services when creating the server:
161
-
162
- ```typescript
163
- import {
164
- createServiceServer,
165
- OrmService,
166
- AutoUpdateService,
167
- SmtpClientService,
168
- } from "@simplysm/service-server";
169
-
170
- const server = createServiceServer({
171
- rootPath: "/app",
172
- port: 3000,
173
- auth: { jwtSecret: "secret" },
174
- services: [OrmService, AutoUpdateService, SmtpClientService],
175
- });
176
- ```
55
+ | Method | Description |
56
+ |--------|-------------|
57
+ | `send(options)` | Send email with full SMTP options |
58
+ | `sendByDefault(options)` | Send email using server's default SMTP config |
package/docs/transport.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ### `WebSocketHandler`
6
6
 
7
- Manages multiple WebSocket connections, routes messages to services, and handles event broadcasting.
7
+ WebSocket handler interface. Manages multiple WebSocket connections, routes messages to services, and handles event broadcasting.
8
8
 
9
9
  ```typescript
10
10
  interface WebSocketHandler {
@@ -19,31 +19,32 @@ interface WebSocketHandler {
19
19
  }
20
20
  ```
21
21
 
22
+ | Method | Description |
23
+ |--------|-------------|
24
+ | `addSocket()` | Add a new WebSocket connection |
25
+ | `closeAll()` | Close all active connections |
26
+ | `broadcastReload()` | Broadcast reload message to all clients |
27
+ | `emit()` | Emit event to matching clients |
28
+
22
29
  ### `createWebSocketHandler`
23
30
 
31
+ Create a WebSocket handler instance.
32
+
24
33
  ```typescript
25
34
  function createWebSocketHandler(
26
35
  runMethod: (def: {
27
36
  serviceName: string;
28
37
  methodName: string;
29
38
  params: unknown[];
30
- socket?: ServiceSocket;
39
+ socket: ServiceSocket;
31
40
  }) => Promise<unknown>,
32
- jwtSecret: string | undefined,
41
+ jwtSecret?: string,
33
42
  ): WebSocketHandler;
34
43
  ```
35
44
 
36
- **Behavior:**
37
- - Routes incoming messages to service methods, auth, and event operations
38
- - Manages a map of connected `ServiceSocket` instances by client ID
39
- - Replaces existing connections for the same client ID
40
- - Handles `auth`, `evt:add`, `evt:remove`, `evt:gets`, `evt:emit`, and service method requests
41
-
42
- ---
43
-
44
45
  ### `ServiceSocket`
45
46
 
46
- Manages a single WebSocket connection with protocol encoding/decoding, ping/pong keep-alive, and event listener tracking.
47
+ Service socket interface. Manages a single WebSocket connection with protocol encoding/decoding, ping/pong keep-alive, and event listener tracking.
47
48
 
48
49
  ```typescript
49
50
  interface ServiceSocket {
@@ -58,15 +59,33 @@ interface ServiceSocket {
58
59
  removeListener(key: string): void;
59
60
  getEventListeners(eventName: string): Array<{ key: string; info: unknown }>;
60
61
  filterEventTargetKeys(targetKeys: string[]): string[];
61
-
62
62
  on(event: "error", handler: (err: Error) => void): void;
63
63
  on(event: "close", handler: (code: number) => void): void;
64
64
  on(event: "message", handler: (data: { uuid: string; msg: ServiceClientMessage }) => void): void;
65
65
  }
66
66
  ```
67
67
 
68
+ | Property | Type | Description |
69
+ |----------|------|-------------|
70
+ | `connectedAtDateTime` | `DateTime` | Connection time |
71
+ | `clientName` | `string` | Client name |
72
+ | `connReq` | `FastifyRequest` | Original Fastify request |
73
+ | `authTokenPayload` | `AuthTokenPayload` | Authenticated token payload |
74
+
75
+ | Method | Description |
76
+ |--------|-------------|
77
+ | `close()` | Close the WebSocket connection |
78
+ | `send()` | Send a message to the client |
79
+ | `addListener()` | Register an event listener |
80
+ | `removeListener()` | Remove an event listener |
81
+ | `getEventListeners()` | Get all listeners for an event name |
82
+ | `filterEventTargetKeys()` | Filter target keys that exist in this socket |
83
+ | `on()` | Register event handlers (error, close, message) |
84
+
68
85
  ### `createServiceSocket`
69
86
 
87
+ Create a service socket instance.
88
+
70
89
  ```typescript
71
90
  function createServiceSocket(
72
91
  socket: WebSocket,
@@ -76,20 +95,11 @@ function createServiceSocket(
76
95
  ): ServiceSocket;
77
96
  ```
78
97
 
79
- **Behavior:**
80
- - Wraps raw WebSocket with protocol encoding/decoding via `ServerProtocolWrapper`
81
- - Sends ping every 5s; terminates if pong not received
82
- - Handles raw ping/pong packets (`0x01` ping, `0x02` pong)
83
- - Tracks event listeners per socket for event broadcasting
84
- - Sends progress notifications for chunked message reception
85
-
86
- ---
87
-
88
98
  ## HTTP Transport
89
99
 
90
100
  ### `handleHttpRequest`
91
101
 
92
- Handle HTTP API requests (GET/POST) to `/api/:service/:method`.
102
+ Handle HTTP API requests. Routes `POST/GET /api/:service/:method` to service methods.
93
103
 
94
104
  ```typescript
95
105
  async function handleHttpRequest<TAuthInfo = unknown>(
@@ -105,15 +115,9 @@ async function handleHttpRequest<TAuthInfo = unknown>(
105
115
  ): Promise<void>;
106
116
  ```
107
117
 
108
- **Behavior:**
109
- - Requires `x-sd-client-name` header
110
- - GET: reads params from `?json=...` query parameter
111
- - POST: reads params from request body (must be an array)
112
- - Parses `Authorization: Bearer <token>` header if present
113
-
114
118
  ### `handleUpload`
115
119
 
116
- Handle multipart file upload to `/upload`.
120
+ Handle file upload requests. Accepts multipart form data with auth token.
117
121
 
118
122
  ```typescript
119
123
  async function handleUpload(
@@ -124,15 +128,9 @@ async function handleUpload(
124
128
  ): Promise<void>;
125
129
  ```
126
130
 
127
- **Behavior:**
128
- - Requires authentication (JWT token in Authorization header)
129
- - Saves files to `{rootPath}/www/uploads/` with UUID filenames
130
- - Returns `ServiceUploadResult[]` with path, filename, and size
131
- - Cleans up incomplete files on error
132
-
133
131
  ### `handleStaticFile`
134
132
 
135
- Handle static file serving.
133
+ Handle static file serving. Serves files from `www/` directory with security checks (path traversal protection, hidden file blocking).
136
134
 
137
135
  ```typescript
138
136
  async function handleStaticFile(
@@ -143,10 +141,24 @@ async function handleStaticFile(
143
141
  ): Promise<void>;
144
142
  ```
145
143
 
146
- **Behavior:**
147
- - Serves files from `{rootPath}/www/`
148
- - Path traversal protection (rejects paths outside allowed root)
149
- - Redirects directories to trailing-slash URLs
150
- - Returns `index.html` for directory requests
151
- - Returns 403 for hidden files (starting with `.`)
152
- - Returns 404 for missing files
144
+ ## Protocol
145
+
146
+ ### `ServerProtocolWrapper`
147
+
148
+ Server-side protocol wrapper interface. Automatically offloads heavy encoding/decoding to a worker thread (>30KB threshold).
149
+
150
+ ```typescript
151
+ interface ServerProtocolWrapper {
152
+ encode(uuid: string, message: ServiceMessage): Promise<{ chunks: Bytes[]; totalSize: number }>;
153
+ decode(bytes: Bytes): Promise<ServiceMessageDecodeResult<ServiceMessage>>;
154
+ dispose(): void;
155
+ }
156
+ ```
157
+
158
+ ### `createServerProtocolWrapper`
159
+
160
+ Create a server protocol wrapper instance.
161
+
162
+ ```typescript
163
+ function createServerProtocolWrapper(): ServerProtocolWrapper;
164
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/service-server",
3
- "version": "13.0.98",
3
+ "version": "13.0.99",
4
4
  "description": "Simplysm package - service module (server)",
5
5
  "author": "simplysm",
6
6
  "license": "Apache-2.0",
@@ -36,11 +36,11 @@
36
36
  "semver": "^7.7.4",
37
37
  "utf-8-validate": "^6.0.6",
38
38
  "ws": "^8.19.0",
39
- "@simplysm/core-node": "13.0.98",
40
- "@simplysm/core-common": "13.0.98",
41
- "@simplysm/orm-common": "13.0.98",
42
- "@simplysm/service-common": "13.0.98",
43
- "@simplysm/orm-node": "13.0.98"
39
+ "@simplysm/core-common": "13.0.99",
40
+ "@simplysm/orm-node": "13.0.99",
41
+ "@simplysm/core-node": "13.0.99",
42
+ "@simplysm/service-common": "13.0.99",
43
+ "@simplysm/orm-common": "13.0.99"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/nodemailer": "^7.0.11",