@simplysm/service-client 13.0.72 → 13.0.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +454 -27
  2. package/package.json +4 -4
package/README.md CHANGED
@@ -2,48 +2,475 @@
2
2
 
3
3
  Simplysm package - Service module (client)
4
4
 
5
+ Provides a WebSocket-based service client for communicating with a Simplysm service server. Includes typed RPC calls, file transfer, ORM connectivity, and server-sent event support.
6
+
5
7
  ## Installation
6
8
 
9
+ ```bash
7
10
  pnpm add @simplysm/service-client
11
+ ```
12
+
13
+ ## Main Modules
8
14
 
9
- ## Source Index
15
+ ### Transport
10
16
 
11
- ### Types
17
+ #### `ServiceClient`
12
18
 
13
- | Source | Exports | Description | Test |
14
- |--------|---------|-------------|------|
15
- | `src/types/connection-config.ts` | `ServiceConnectionConfig` | Host, port, SSL, and reconnect options for a service connection | - |
16
- | `src/types/progress.types.ts` | `ServiceProgress`, `ServiceProgressState` | Callbacks and state shape for tracking request/response progress | - |
19
+ The primary entry point. Manages the WebSocket connection lifecycle, service calls, authentication, file transfer, and event subscriptions.
17
20
 
18
- ### Transport
21
+ Extends `EventEmitter` and emits:
22
+
23
+ | Event | Payload | Description |
24
+ |---|---|---|
25
+ | `"state"` | `"connected" \| "closed" \| "reconnecting"` | Connection state changes |
26
+ | `"request-progress"` | `ServiceProgressState` | Upload progress |
27
+ | `"response-progress"` | `ServiceProgressState` | Download progress |
28
+ | `"reload"` | `Set<string>` | Hot-reload notification with changed file paths |
29
+
30
+ ```typescript
31
+ import { ServiceClient } from "@simplysm/service-client";
32
+
33
+ const client = new ServiceClient("my-app", {
34
+ host: "localhost",
35
+ port: 3000,
36
+ ssl: false,
37
+ maxReconnectCount: 10,
38
+ });
39
+
40
+ client.on("state", (state) => {
41
+ console.log("Connection state:", state);
42
+ });
43
+
44
+ await client.connect();
45
+ ```
46
+
47
+ **Properties**
48
+
49
+ | Property | Type | Description |
50
+ |---|---|---|
51
+ | `name` | `string` | Client identifier sent to the server |
52
+ | `options` | `ServiceConnectionConfig` | Connection configuration (readonly) |
53
+ | `connected` | `boolean` | Whether the WebSocket is currently connected |
54
+ | `hostUrl` | `string` | HTTP base URL derived from `options` (e.g. `http://localhost:3000`) |
55
+
56
+ **Methods**
57
+
58
+ - `connect(): Promise<void>` — Opens the WebSocket connection.
59
+ - `close(): Promise<void>` — Closes the WebSocket connection.
60
+ - `auth(token: string): Promise<void>` — Sends an auth token to the server and stores it for automatic re-authentication on reconnect.
61
+ - `send(serviceName, methodName, params, progress?): Promise<unknown>` — Sends a raw RPC call. Prefer `getService()` for type safety.
62
+ - `getService<TService>(serviceName): RemoteService<TService>` — Returns a typed proxy object where every method of `TService` is wrapped to return a `Promise`.
63
+ - `addEventListener(eventDef, info, cb): Promise<string>` — Subscribes to a server event. Returns a listener key.
64
+ - `removeEventListener(key): Promise<void>` — Unsubscribes from a server event by key.
65
+ - `emitToServer(eventDef, infoSelector, data): Promise<void>` — Triggers a server event for listeners matching `infoSelector`.
66
+ - `uploadFile(files): Promise<ServiceUploadResult[]>` — Uploads files to the server. Requires prior `auth()` call.
67
+ - `downloadFileBuffer(relPath): Promise<Bytes>` — Downloads a file from the server as a `Uint8Array`.
68
+
69
+ ```typescript
70
+ import { ServiceClient } from "@simplysm/service-client";
71
+ import type { MyService } from "./my-service";
72
+
73
+ const client = new ServiceClient("my-app", { host: "localhost", port: 3000 });
74
+ await client.connect();
75
+ await client.auth("my-token");
76
+
77
+ const svc = client.getService<MyService>("MyService");
78
+ const result = await svc.getData(42);
79
+
80
+ await client.close();
81
+ ```
82
+
83
+ ---
84
+
85
+ #### `createServiceClient`
86
+
87
+ Factory function that creates a `ServiceClient` instance.
88
+
89
+ ```typescript
90
+ import { createServiceClient } from "@simplysm/service-client";
91
+
92
+ const client = createServiceClient("my-app", {
93
+ host: "localhost",
94
+ port: 3000,
95
+ ssl: false,
96
+ });
97
+ ```
98
+
99
+ ---
100
+
101
+ #### `SocketProvider` (interface)
102
+
103
+ Low-level WebSocket abstraction. Manages connection, heartbeat, and automatic reconnection.
104
+
105
+ ```typescript
106
+ export interface SocketProvider {
107
+ readonly clientName: string;
108
+ readonly connected: boolean;
109
+ on<K extends keyof SocketProviderEvents & string>(type: K, listener: (data: SocketProviderEvents[K]) => void): void;
110
+ off<K extends keyof SocketProviderEvents & string>(type: K, listener: (data: SocketProviderEvents[K]) => void): void;
111
+ connect(): Promise<void>;
112
+ close(): Promise<void>;
113
+ send(data: Bytes): Promise<void>;
114
+ }
115
+ ```
116
+
117
+ ---
118
+
119
+ #### `SocketProviderEvents` (interface)
120
+
121
+ Events emitted by a `SocketProvider`.
122
+
123
+ | Event | Payload |
124
+ |---|---|
125
+ | `"message"` | `Bytes` — incoming raw bytes |
126
+ | `"state"` | `"connected" \| "closed" \| "reconnecting"` |
127
+
128
+ ---
129
+
130
+ #### `createSocketProvider`
19
131
 
20
- | Source | Exports | Description | Test |
21
- |--------|---------|-------------|------|
22
- | `src/transport/socket-provider.ts` | `SocketProviderEvents`, `SocketProvider`, `createSocketProvider` | WebSocket wrapper with heartbeat, auto-reconnect, and ping/pong | - |
23
- | `src/transport/service-transport.ts` | `ServiceTransportEvents`, `ServiceTransport`, `createServiceTransport` | Sends protocol messages over a socket and matches responses by UUID | - |
132
+ Factory function that creates a `SocketProvider`.
133
+
134
+ - Sends a ping every 5 seconds and considers the connection lost if no message is received for 30 seconds.
135
+ - Retries up to `maxReconnectCount` times on unexpected disconnection with a 3-second delay. Set to `0` to disable reconnection.
136
+
137
+ ```typescript
138
+ import { createSocketProvider } from "@simplysm/service-client";
139
+
140
+ const socket = createSocketProvider("ws://localhost:3000/ws", "my-app", 10);
141
+ await socket.connect();
142
+ ```
143
+
144
+ | Parameter | Type | Description |
145
+ |---|---|---|
146
+ | `url` | `string` | WebSocket server URL |
147
+ | `clientName` | `string` | Client identifier |
148
+ | `maxReconnectCount` | `number` | Maximum number of reconnection attempts |
149
+
150
+ ---
151
+
152
+ #### `ServiceTransport` (interface)
153
+
154
+ Handles message framing, chunked encoding/decoding, and request/response correlation on top of a `SocketProvider`.
155
+
156
+ ```typescript
157
+ export interface ServiceTransport {
158
+ on<K extends keyof ServiceTransportEvents & string>(type: K, listener: (data: ServiceTransportEvents[K]) => void): void;
159
+ off<K extends keyof ServiceTransportEvents & string>(type: K, listener: (data: ServiceTransportEvents[K]) => void): void;
160
+ send(message: ServiceClientMessage, progress?: ServiceProgress): Promise<unknown>;
161
+ }
162
+ ```
163
+
164
+ ---
165
+
166
+ #### `ServiceTransportEvents` (interface)
167
+
168
+ Events emitted by a `ServiceTransport`.
169
+
170
+ | Event | Payload |
171
+ |---|---|
172
+ | `"reload"` | `Set<string>` — set of changed file paths |
173
+ | `"event"` | `{ keys: string[]; data: unknown }` — server-pushed event |
174
+
175
+ ---
176
+
177
+ #### `createServiceTransport`
178
+
179
+ Factory function that creates a `ServiceTransport` from a `SocketProvider` and a `ClientProtocolWrapper`.
180
+
181
+ ```typescript
182
+ import { createServiceTransport } from "@simplysm/service-client";
183
+
184
+ const transport = createServiceTransport(socket, protocolWrapper);
185
+ ```
186
+
187
+ ---
24
188
 
25
189
  ### Protocol
26
190
 
27
- | Source | Exports | Description | Test |
28
- |--------|---------|-------------|------|
29
- | `src/protocol/client-protocol-wrapper.ts` | `ClientProtocolWrapper`, `createClientProtocolWrapper` | Encodes/decodes protocol messages, offloading large payloads to a Web Worker | - |
191
+ #### `ClientProtocolWrapper` (interface)
192
+
193
+ Wraps a `ServiceProtocol` and transparently offloads encode/decode work to a Web Worker for large payloads (threshold: 30 KB), falling back to the main thread when Workers are unavailable.
194
+
195
+ ```typescript
196
+ export interface ClientProtocolWrapper {
197
+ encode(uuid: string, message: ServiceMessage): Promise<{ chunks: Bytes[]; totalSize: number }>;
198
+ decode(bytes: Bytes): Promise<ServiceMessageDecodeResult<ServiceMessage>>;
199
+ }
200
+ ```
201
+
202
+ ---
203
+
204
+ #### `createClientProtocolWrapper`
205
+
206
+ Factory function that creates a `ClientProtocolWrapper` from a `ServiceProtocol`.
207
+
208
+ ```typescript
209
+ import { createClientProtocolWrapper } from "@simplysm/service-client";
210
+ import { createServiceProtocol } from "@simplysm/service-common";
211
+
212
+ const protocol = createServiceProtocol();
213
+ const wrapper = createClientProtocolWrapper(protocol);
214
+ ```
215
+
216
+ ---
30
217
 
31
218
  ### Features
32
219
 
33
- | Source | Exports | Description | Test |
34
- |--------|---------|-------------|------|
35
- | `src/features/event-client.ts` | `EventClient`, `createEventClient` | Registers, removes, and re-registers server-side event listeners | - |
36
- | `src/features/file-client.ts` | `FileClient`, `createFileClient` | Downloads files via fetch and uploads files via multipart form-data | - |
37
- | `src/features/orm/orm-connect-config.ts` | `OrmConnectConfig` | Config type pairing a DbContextDef with connection options and DB name | - |
38
- | `src/features/orm/orm-client-connector.ts` | `OrmClientConnector`, `createOrmClientConnector` | Opens a remote ORM DB context with or without a transaction | - |
39
- | `src/features/orm/orm-client-db-context-executor.ts` | `OrmClientDbContextExecutor` | DbContextExecutor that delegates all DB operations to the server OrmService | - |
220
+ #### `EventClient` (interface)
221
+
222
+ Manages typed server-sent event subscriptions. Automatically re-registers all listeners after reconnection.
223
+
224
+ ```typescript
225
+ export interface EventClient {
226
+ addListener<TInfo, TData>(
227
+ eventDef: ServiceEventDef<TInfo, TData>,
228
+ info: TInfo,
229
+ cb: (data: TData) => PromiseLike<void>,
230
+ ): Promise<string>;
231
+ removeListener(key: string): Promise<void>;
232
+ emitToServer<TInfo, TData>(
233
+ eventDef: ServiceEventDef<TInfo, TData>,
234
+ infoSelector: (item: TInfo) => boolean,
235
+ data: TData,
236
+ ): Promise<void>;
237
+ reRegisterAll(): Promise<void>;
238
+ }
239
+ ```
240
+
241
+ **Methods**
242
+
243
+ - `addListener(eventDef, info, cb)` — Registers a listener. `info` is sent to the server to filter which events this client receives. Returns a unique key.
244
+ - `removeListener(key)` — Unregisters a listener by the key returned from `addListener`.
245
+ - `emitToServer(eventDef, infoSelector, data)` — Fetches all registered listeners from the server and emits `data` to those where `infoSelector(info)` returns `true`.
246
+ - `reRegisterAll()` — Re-registers all listeners with the server (called automatically on reconnect).
247
+
248
+ ---
249
+
250
+ #### `createEventClient`
251
+
252
+ Factory function that creates an `EventClient` from a `ServiceTransport`.
253
+
254
+ ```typescript
255
+ import { createEventClient } from "@simplysm/service-client";
256
+
257
+ const eventClient = createEventClient(transport);
258
+ ```
259
+
260
+ ---
261
+
262
+ #### `FileClient` (interface)
263
+
264
+ HTTP-based file download and upload utility.
265
+
266
+ ```typescript
267
+ export interface FileClient {
268
+ download(relPath: string): Promise<Bytes>;
269
+ upload(
270
+ files: File[] | FileList | { name: string; data: BlobPart }[],
271
+ authToken: string,
272
+ ): Promise<ServiceUploadResult[]>;
273
+ }
274
+ ```
275
+
276
+ **Methods**
277
+
278
+ - `download(relPath)` — GETs a file from `{hostUrl}{relPath}` and returns its content as a `Uint8Array`.
279
+ - `upload(files, authToken)` — POSTs files to `{hostUrl}/upload` using `multipart/form-data`. Accepts browser `File` objects, a `FileList`, or plain `{ name, data }` objects.
280
+
281
+ ---
282
+
283
+ #### `createFileClient`
284
+
285
+ Factory function that creates a `FileClient`.
286
+
287
+ ```typescript
288
+ import { createFileClient } from "@simplysm/service-client";
289
+
290
+ const fileClient = createFileClient("http://localhost:3000", "my-app");
291
+ ```
292
+
293
+ | Parameter | Type | Description |
294
+ |---|---|---|
295
+ | `hostUrl` | `string` | HTTP base URL of the server |
296
+ | `clientName` | `string` | Sent as the `x-sd-client-name` header |
297
+
298
+ ---
299
+
300
+ #### `OrmConnectConfig<TDef>` (interface)
301
+
302
+ Configuration for connecting to a database through the ORM service.
303
+
304
+ ```typescript
305
+ export interface OrmConnectConfig<TDef extends DbContextDef<any, any, any>> {
306
+ dbContextDef: TDef;
307
+ connOpt: DbConnOptions & { configName: string };
308
+ dbContextOpt?: {
309
+ database: string;
310
+ schema: string;
311
+ };
312
+ }
313
+ ```
314
+
315
+ | Field | Type | Description |
316
+ |---|---|---|
317
+ | `dbContextDef` | `TDef` | The `DbContextDef` definition object |
318
+ | `connOpt` | `DbConnOptions & { configName: string }` | Connection options plus the server-side config name |
319
+ | `dbContextOpt` | `{ database, schema }?` | Overrides the database/schema resolved from the server |
320
+
321
+ ---
322
+
323
+ #### `OrmClientConnector` (interface)
324
+
325
+ Provides a high-level API for running ORM operations through the service client.
326
+
327
+ ```typescript
328
+ export interface OrmClientConnector {
329
+ connect<TDef extends DbContextDef<any, any, any>, R>(
330
+ config: OrmConnectConfig<TDef>,
331
+ callback: (db: DbContextInstance<TDef>) => Promise<R> | R,
332
+ ): Promise<R>;
333
+ connectWithoutTransaction<TDef extends DbContextDef<any, any, any>, R>(
334
+ config: OrmConnectConfig<TDef>,
335
+ callback: (db: DbContextInstance<TDef>) => Promise<R> | R,
336
+ ): Promise<R>;
337
+ }
338
+ ```
339
+
340
+ **Methods**
341
+
342
+ - `connect(config, callback)` — Opens a database connection, begins a transaction, calls `callback`, and commits. Rolls back on error. Foreign key constraint errors are wrapped with a user-friendly message.
343
+ - `connectWithoutTransaction(config, callback)` — Opens a database connection without a transaction and calls `callback`.
344
+
345
+ ```typescript
346
+ import { createOrmClientConnector } from "@simplysm/service-client";
347
+
348
+ const orm = createOrmClientConnector(client);
349
+
350
+ const result = await orm.connect(
351
+ { dbContextDef: MyDbDef, connOpt: { configName: "default" } },
352
+ async (db) => {
353
+ return db.myTable.select().toArray();
354
+ },
355
+ );
356
+ ```
357
+
358
+ ---
359
+
360
+ #### `createOrmClientConnector`
361
+
362
+ Factory function that creates an `OrmClientConnector` from a `ServiceClient`.
363
+
364
+ ```typescript
365
+ import { createOrmClientConnector } from "@simplysm/service-client";
366
+
367
+ const orm = createOrmClientConnector(client);
368
+ ```
369
+
370
+ ---
371
+
372
+ #### `OrmClientDbContextExecutor`
373
+
374
+ Class that implements `DbContextExecutor` by delegating all database operations to the server-side `OrmService` over the service transport. Used internally by `createOrmClientConnector` and typically not instantiated directly.
375
+
376
+ ```typescript
377
+ import { OrmClientDbContextExecutor } from "@simplysm/service-client";
378
+
379
+ const executor = new OrmClientDbContextExecutor(client, {
380
+ configName: "default",
381
+ // ...other DbConnOptions
382
+ });
383
+ ```
384
+
385
+ **Constructor**
386
+
387
+ ```typescript
388
+ constructor(client: ServiceClient, opt: DbConnOptions & { configName: string })
389
+ ```
390
+
391
+ **Methods** (all implement `DbContextExecutor`)
392
+
393
+ - `getInfo(): Promise<{ dialect, database?, schema? }>` — Retrieves database connection metadata.
394
+ - `connect(): Promise<void>` — Opens a server-side database connection.
395
+ - `beginTransaction(isolationLevel?): Promise<void>`
396
+ - `commitTransaction(): Promise<void>`
397
+ - `rollbackTransaction(): Promise<void>`
398
+ - `close(): Promise<void>`
399
+ - `executeDefs(defs, options?): Promise<T[][]>` — Executes query definitions.
400
+ - `executeParametrized(query, params?): Promise<unknown[][]>` — Executes a parameterized raw query.
401
+ - `bulkInsert(tableName, columnDefs, records): Promise<void>` — Performs a bulk insert.
402
+
403
+ ---
404
+
405
+ ## Types
406
+
407
+ ### `ServiceConnectionConfig`
408
+
409
+ ```typescript
410
+ import { ServiceConnectionConfig } from "@simplysm/service-client";
411
+
412
+ export interface ServiceConnectionConfig {
413
+ port: number;
414
+ host: string;
415
+ ssl?: boolean;
416
+ /** Set to 0 to disable reconnect; disconnects immediately */
417
+ maxReconnectCount?: number;
418
+ }
419
+ ```
420
+
421
+ | Field | Type | Default | Description |
422
+ |---|---|---|---|
423
+ | `host` | `string` | — | Server hostname or IP address |
424
+ | `port` | `number` | — | Server port |
425
+ | `ssl` | `boolean?` | `false` | Use `wss://` / `https://` when `true` |
426
+ | `maxReconnectCount` | `number?` | `10` | Maximum reconnection attempts. Set to `0` to disable. |
427
+
428
+ ---
429
+
430
+ ### `ServiceProgress`
431
+
432
+ Callbacks for tracking upload and download progress of a single request.
433
+
434
+ ```typescript
435
+ import { ServiceProgress } from "@simplysm/service-client";
436
+
437
+ export interface ServiceProgress {
438
+ request?: (s: ServiceProgressState) => void;
439
+ response?: (s: ServiceProgressState) => void;
440
+ }
441
+ ```
442
+
443
+ ---
444
+
445
+ ### `ServiceProgressState`
446
+
447
+ Payload passed to `ServiceProgress` callbacks.
448
+
449
+ ```typescript
450
+ import { ServiceProgressState } from "@simplysm/service-client";
451
+
452
+ export interface ServiceProgressState {
453
+ uuid: string;
454
+ totalSize: number;
455
+ completedSize: number;
456
+ }
457
+ ```
458
+
459
+ | Field | Type | Description |
460
+ |---|---|---|
461
+ | `uuid` | `string` | Unique request identifier |
462
+ | `totalSize` | `number` | Total payload size in bytes |
463
+ | `completedSize` | `number` | Bytes transferred so far |
464
+
465
+ ---
40
466
 
41
- ### Main
467
+ ### `RemoteService<TService>`
42
468
 
43
- | Source | Exports | Description | Test |
44
- |--------|---------|-------------|------|
45
- | `src/service-client.ts` | `ServiceClient`, `RemoteService`, `createServiceClient` | Top-level client that composes socket, transport, events, and file features | - |
469
+ Utility type that wraps all methods of `TService` so their return types become `Promise`. Non-function properties are excluded (`never`). Used as the return type of `ServiceClient.getService()`.
46
470
 
47
- ## License
471
+ ```typescript
472
+ import type { RemoteService } from "@simplysm/service-client";
48
473
 
49
- Apache-2.0
474
+ type RemoteMyService = RemoteService<MyService>;
475
+ // Every method now returns Promise<ReturnType>
476
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/service-client",
3
- "version": "13.0.72",
3
+ "version": "13.0.75",
4
4
  "description": "Simplysm package - Service module (client)",
5
5
  "author": "simplysm",
6
6
  "license": "Apache-2.0",
@@ -19,9 +19,9 @@
19
19
  "sideEffects": false,
20
20
  "dependencies": {
21
21
  "consola": "^3.4.2",
22
- "@simplysm/core-common": "13.0.72",
23
- "@simplysm/orm-common": "13.0.72",
24
- "@simplysm/service-common": "13.0.72"
22
+ "@simplysm/core-common": "13.0.75",
23
+ "@simplysm/orm-common": "13.0.75",
24
+ "@simplysm/service-common": "13.0.75"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/ws": "^8.18.1",