@simplysm/service-server 13.0.96 → 13.0.98
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 +79 -345
- package/docs/auth.md +48 -0
- package/docs/core.md +161 -0
- package/docs/server.md +206 -0
- package/docs/services.md +176 -0
- package/docs/transport.md +152 -0
- package/package.json +8 -8
- package/docs/builtin-services.md +0 -249
- package/docs/transport-protocol.md +0 -200
package/docs/server.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Server
|
|
2
|
+
|
|
3
|
+
## ServiceServerOptions
|
|
4
|
+
|
|
5
|
+
Server configuration options.
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
interface ServiceServerOptions {
|
|
9
|
+
rootPath: string;
|
|
10
|
+
port: number;
|
|
11
|
+
ssl?: {
|
|
12
|
+
pfxBytes: Uint8Array;
|
|
13
|
+
passphrase: string;
|
|
14
|
+
};
|
|
15
|
+
auth?: {
|
|
16
|
+
jwtSecret: string;
|
|
17
|
+
};
|
|
18
|
+
services: ServiceDefinition[];
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## ServerProtocolWrapper
|
|
23
|
+
|
|
24
|
+
Server-side protocol wrapper. Automatically offloads heavy message encoding/decoding to a worker thread while using main thread for lightweight operations.
|
|
25
|
+
|
|
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 }>`.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
class ServiceServer<TAuthInfo = unknown> extends EventEmitter<{ ready: void; close: void }> {
|
|
88
|
+
isOpen: boolean;
|
|
89
|
+
readonly fastify: FastifyInstance;
|
|
90
|
+
readonly options: ServiceServerOptions;
|
|
91
|
+
|
|
92
|
+
constructor(options: ServiceServerOptions);
|
|
93
|
+
|
|
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>(
|
|
98
|
+
eventDef: ServiceEventDef<TInfo, TData>,
|
|
99
|
+
infoSelector: (item: TInfo) => boolean,
|
|
100
|
+
data: TData,
|
|
101
|
+
): Promise<void>;
|
|
102
|
+
async signAuthToken(payload: AuthTokenPayload<TAuthInfo>): Promise<string>;
|
|
103
|
+
async verifyAuthToken(token: string): Promise<AuthTokenPayload<TAuthInfo>>;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### `listen`
|
|
108
|
+
|
|
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`
|
|
141
|
+
|
|
142
|
+
Emit an event to matching WebSocket clients.
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
async emitEvent<TInfo, TData>(
|
|
146
|
+
eventDef: ServiceEventDef<TInfo, TData>,
|
|
147
|
+
infoSelector: (item: TInfo) => boolean,
|
|
148
|
+
data: TData,
|
|
149
|
+
): Promise<void>;
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### `signAuthToken`
|
|
153
|
+
|
|
154
|
+
Sign a JWT auth token.
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
async signAuthToken(payload: AuthTokenPayload<TAuthInfo>): Promise<string>;
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### `verifyAuthToken`
|
|
161
|
+
|
|
162
|
+
Verify a JWT auth token.
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
async verifyAuthToken(token: string): Promise<AuthTokenPayload<TAuthInfo>>;
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## `createServiceServer`
|
|
169
|
+
|
|
170
|
+
Factory function.
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
function createServiceServer<TAuthInfo = unknown>(
|
|
174
|
+
options: ServiceServerOptions,
|
|
175
|
+
): ServiceServer<TAuthInfo>;
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Example
|
|
179
|
+
|
|
180
|
+
```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();
|
|
206
|
+
```
|
package/docs/services.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Built-in Services
|
|
2
|
+
|
|
3
|
+
Pre-defined service definitions ready to use with `ServiceServer`.
|
|
4
|
+
|
|
5
|
+
## OrmService
|
|
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
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
const OrmService: ServiceDefinition;
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Service name: `"Orm"`
|
|
20
|
+
|
|
21
|
+
### Methods
|
|
22
|
+
|
|
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
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## AutoUpdateService
|
|
75
|
+
|
|
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
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const AutoUpdateService: ServiceDefinition;
|
|
86
|
+
```
|
|
87
|
+
|
|
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
|
+
```
|
|
100
|
+
|
|
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
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## SmtpClientService
|
|
111
|
+
|
|
112
|
+
SMTP email sending service. No authentication required.
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { SmtpClientService, type SmtpClientServiceType } from "@simplysm/service-server";
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Definition
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
const SmtpClientService: ServiceDefinition;
|
|
122
|
+
```
|
|
123
|
+
|
|
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
|
+
```
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Transport
|
|
2
|
+
|
|
3
|
+
## WebSocket Transport
|
|
4
|
+
|
|
5
|
+
### `WebSocketHandler`
|
|
6
|
+
|
|
7
|
+
Manages multiple WebSocket connections, routes messages to services, and handles event broadcasting.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
interface WebSocketHandler {
|
|
11
|
+
addSocket(socket: WebSocket, clientId: string, clientName: string, connReq: FastifyRequest): void;
|
|
12
|
+
closeAll(): void;
|
|
13
|
+
broadcastReload(clientName: string | undefined, changedFileSet: Set<string>): Promise<void>;
|
|
14
|
+
emit<TInfo, TData>(
|
|
15
|
+
eventDef: ServiceEventDef<TInfo, TData>,
|
|
16
|
+
infoSelector: (item: TInfo) => boolean,
|
|
17
|
+
data: TData,
|
|
18
|
+
): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### `createWebSocketHandler`
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
function createWebSocketHandler(
|
|
26
|
+
runMethod: (def: {
|
|
27
|
+
serviceName: string;
|
|
28
|
+
methodName: string;
|
|
29
|
+
params: unknown[];
|
|
30
|
+
socket?: ServiceSocket;
|
|
31
|
+
}) => Promise<unknown>,
|
|
32
|
+
jwtSecret: string | undefined,
|
|
33
|
+
): WebSocketHandler;
|
|
34
|
+
```
|
|
35
|
+
|
|
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
|
+
### `ServiceSocket`
|
|
45
|
+
|
|
46
|
+
Manages a single WebSocket connection with protocol encoding/decoding, ping/pong keep-alive, and event listener tracking.
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
interface ServiceSocket {
|
|
50
|
+
readonly connectedAtDateTime: DateTime;
|
|
51
|
+
readonly clientName: string;
|
|
52
|
+
readonly connReq: FastifyRequest;
|
|
53
|
+
authTokenPayload?: AuthTokenPayload;
|
|
54
|
+
|
|
55
|
+
close(): void;
|
|
56
|
+
send(uuid: string, msg: ServiceServerMessage): Promise<number>;
|
|
57
|
+
addListener(key: string, eventName: string, info: unknown): void;
|
|
58
|
+
removeListener(key: string): void;
|
|
59
|
+
getEventListeners(eventName: string): Array<{ key: string; info: unknown }>;
|
|
60
|
+
filterEventTargetKeys(targetKeys: string[]): string[];
|
|
61
|
+
|
|
62
|
+
on(event: "error", handler: (err: Error) => void): void;
|
|
63
|
+
on(event: "close", handler: (code: number) => void): void;
|
|
64
|
+
on(event: "message", handler: (data: { uuid: string; msg: ServiceClientMessage }) => void): void;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### `createServiceSocket`
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
function createServiceSocket(
|
|
72
|
+
socket: WebSocket,
|
|
73
|
+
clientId: string,
|
|
74
|
+
clientName: string,
|
|
75
|
+
connReq: FastifyRequest,
|
|
76
|
+
): ServiceSocket;
|
|
77
|
+
```
|
|
78
|
+
|
|
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
|
+
## HTTP Transport
|
|
89
|
+
|
|
90
|
+
### `handleHttpRequest`
|
|
91
|
+
|
|
92
|
+
Handle HTTP API requests (GET/POST) to `/api/:service/:method`.
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
async function handleHttpRequest<TAuthInfo = unknown>(
|
|
96
|
+
req: FastifyRequest,
|
|
97
|
+
reply: FastifyReply,
|
|
98
|
+
jwtSecret: string | undefined,
|
|
99
|
+
runMethod: (def: {
|
|
100
|
+
serviceName: string;
|
|
101
|
+
methodName: string;
|
|
102
|
+
params: unknown[];
|
|
103
|
+
http: { clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> };
|
|
104
|
+
}) => Promise<unknown>,
|
|
105
|
+
): Promise<void>;
|
|
106
|
+
```
|
|
107
|
+
|
|
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
|
+
### `handleUpload`
|
|
115
|
+
|
|
116
|
+
Handle multipart file upload to `/upload`.
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
async function handleUpload(
|
|
120
|
+
req: FastifyRequest,
|
|
121
|
+
reply: FastifyReply,
|
|
122
|
+
rootPath: string,
|
|
123
|
+
jwtSecret: string | undefined,
|
|
124
|
+
): Promise<void>;
|
|
125
|
+
```
|
|
126
|
+
|
|
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
|
+
### `handleStaticFile`
|
|
134
|
+
|
|
135
|
+
Handle static file serving.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
async function handleStaticFile(
|
|
139
|
+
req: FastifyRequest,
|
|
140
|
+
reply: FastifyReply,
|
|
141
|
+
rootPath: string,
|
|
142
|
+
urlPath: string,
|
|
143
|
+
): Promise<void>;
|
|
144
|
+
```
|
|
145
|
+
|
|
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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/service-server",
|
|
3
|
-
"version": "13.0.
|
|
3
|
+
"version": "13.0.98",
|
|
4
4
|
"description": "Simplysm package - service module (server)",
|
|
5
5
|
"author": "simplysm",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -30,17 +30,17 @@
|
|
|
30
30
|
"bufferutil": "^4.1.0",
|
|
31
31
|
"consola": "^3.4.2",
|
|
32
32
|
"fastify": "^5.8.2",
|
|
33
|
-
"jose": "^6.2.
|
|
33
|
+
"jose": "^6.2.2",
|
|
34
34
|
"mime": "^4.1.0",
|
|
35
|
-
"nodemailer": "^8.0.
|
|
35
|
+
"nodemailer": "^8.0.3",
|
|
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.
|
|
40
|
-
"@simplysm/core-common": "13.0.
|
|
41
|
-
"@simplysm/orm-
|
|
42
|
-
"@simplysm/
|
|
43
|
-
"@simplysm/
|
|
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"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/nodemailer": "^7.0.11",
|