@simplysm/service-client 14.0.1 → 14.0.5

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 (46) hide show
  1. package/README.md +127 -0
  2. package/dist/features/file-client.d.ts +3 -2
  3. package/dist/features/file-client.d.ts.map +1 -1
  4. package/dist/features/file-client.js +3 -3
  5. package/dist/features/file-client.js.map +1 -1
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/protocol/client-protocol-wrapper.d.ts.map +1 -1
  11. package/dist/protocol/client-protocol-wrapper.js +6 -5
  12. package/dist/protocol/client-protocol-wrapper.js.map +1 -1
  13. package/dist/service-client.d.ts +3 -3
  14. package/dist/service-client.d.ts.map +1 -1
  15. package/dist/service-client.js +0 -3
  16. package/dist/service-client.js.map +1 -1
  17. package/dist/transport/service-transport.d.ts +0 -1
  18. package/dist/transport/service-transport.d.ts.map +1 -1
  19. package/dist/transport/service-transport.js +1 -7
  20. package/dist/transport/service-transport.js.map +1 -1
  21. package/dist/transport/socket-provider.d.ts.map +1 -1
  22. package/dist/transport/socket-provider.js +5 -0
  23. package/dist/transport/socket-provider.js.map +1 -1
  24. package/dist/types/browser-compat.d.ts +33 -0
  25. package/dist/types/browser-compat.d.ts.map +1 -0
  26. package/dist/types/browser-compat.js +17 -0
  27. package/dist/types/browser-compat.js.map +1 -0
  28. package/dist/workers/client-protocol.worker.js +1 -1
  29. package/dist/workers/client-protocol.worker.js.map +1 -1
  30. package/docs/browser-compat-types.md +77 -0
  31. package/docs/client-protocol-wrapper.md +35 -0
  32. package/docs/connection-options.md +21 -0
  33. package/docs/features.md +147 -0
  34. package/docs/progress-types.md +37 -0
  35. package/docs/service-client.md +89 -0
  36. package/docs/service-transport.md +60 -0
  37. package/docs/socket-provider.md +72 -0
  38. package/package.json +6 -5
  39. package/src/features/file-client.ts +6 -5
  40. package/src/index.ts +1 -0
  41. package/src/protocol/client-protocol-wrapper.ts +13 -9
  42. package/src/service-client.ts +2 -5
  43. package/src/transport/service-transport.ts +1 -7
  44. package/src/transport/socket-provider.ts +6 -0
  45. package/src/types/browser-compat.ts +47 -0
  46. package/src/workers/client-protocol.worker.ts +1 -1
@@ -0,0 +1,35 @@
1
+ # Client Protocol Wrapper
2
+
3
+ ## `ClientProtocolWrapper`
4
+
5
+ Client-side protocol encoder/decoder wrapper. Automatically offloads heavy encode/decode operations to a Web Worker when available, falling back to main-thread processing for small messages or when workers are unsupported.
6
+
7
+ ```typescript
8
+ export interface ClientProtocolWrapper {
9
+ encode(uuid: string, message: ServiceMessage): Promise<{ chunks: Bytes[]; totalSize: number }>;
10
+ decode(bytes: Bytes): Promise<ServiceMessageDecodeResult<ServiceMessage>>;
11
+ dispose(): void;
12
+ }
13
+ ```
14
+
15
+ | Method | Parameters | Return | Description |
16
+ |--------|-----------|--------|-------------|
17
+ | `encode` | `uuid: string, message: ServiceMessage` | `Promise<{ chunks: Bytes[]; totalSize: number }>` | Encodes a message into binary chunks. Offloads to worker for large messages |
18
+ | `decode` | `bytes: Bytes` | `Promise<ServiceMessageDecodeResult<ServiceMessage>>` | Decodes received binary data. Uses zero-copy transfer for large payloads |
19
+ | `dispose` | none | `void` | Disposes the underlying protocol and worker resolver resources |
20
+
21
+ ## `createClientProtocolWrapper`
22
+
23
+ Creates a `ClientProtocolWrapper` from a `ServiceProtocol` instance.
24
+
25
+ ```typescript
26
+ export function createClientProtocolWrapper(
27
+ protocol: ServiceProtocol,
28
+ ): ClientProtocolWrapper;
29
+ ```
30
+
31
+ | Parameter | Type | Description |
32
+ |-----------|------|-------------|
33
+ | `protocol` | `ServiceProtocol` | The base protocol encoder/decoder (from `@simplysm/service-common`) |
34
+
35
+ Worker offloading threshold: 30KB. Messages below this size are processed on the main thread. The worker is a shared singleton across all `ClientProtocolWrapper` instances. Worker operations that exceed 60 seconds are automatically timed out and rejected.
@@ -0,0 +1,21 @@
1
+ # Connection Options
2
+
3
+ ## `ServiceConnectionOptions`
4
+
5
+ Connection options for the service client.
6
+
7
+ ```typescript
8
+ export interface ServiceConnectionOptions {
9
+ port: number;
10
+ host: string;
11
+ ssl?: boolean;
12
+ maxReconnectCount?: number;
13
+ }
14
+ ```
15
+
16
+ | Field | Type | Description |
17
+ |-------|------|-------------|
18
+ | `port` | `number` | Server port |
19
+ | `host` | `string` | Server hostname |
20
+ | `ssl` | `boolean?` | Enable SSL/TLS (uses `wss://` and `https://` when true) |
21
+ | `maxReconnectCount` | `number?` | Maximum reconnection attempts. Set to `0` to disable reconnection and disconnect immediately |
@@ -0,0 +1,147 @@
1
+ # Event/File/ORM Client
2
+
3
+ ## `EventClient`
4
+
5
+ Event subscription client for the service event system.
6
+
7
+ ```typescript
8
+ export interface EventClient {
9
+ addListener<TInfo, TData>(
10
+ eventDef: ServiceEventDef<TInfo, TData>,
11
+ info: TInfo,
12
+ cb: (data: TData) => PromiseLike<void>,
13
+ ): Promise<string>;
14
+ removeListener(key: string): Promise<void>;
15
+ emit<TInfo, TData>(
16
+ eventDef: ServiceEventDef<TInfo, TData>,
17
+ infoSelector: (item: TInfo) => boolean,
18
+ data: TData,
19
+ ): Promise<void>;
20
+ resubscribeAll(): Promise<void>;
21
+ }
22
+ ```
23
+
24
+ | Method | Parameters | Return | Description |
25
+ |--------|-----------|--------|-------------|
26
+ | `addListener` | `eventDef: ServiceEventDef<TInfo, TData>, info: TInfo, cb: (data: TData) => PromiseLike<void>` | `Promise<string>` | Subscribes to an event. Returns a listener key (UUID) for later removal |
27
+ | `removeListener` | `key: string` | `Promise<void>` | Unsubscribes by listener key |
28
+ | `emit` | `eventDef: ServiceEventDef<TInfo, TData>, infoSelector: (item: TInfo) => boolean, data: TData` | `Promise<void>` | Queries matching listeners from the server, then emits the event to them |
29
+ | `resubscribeAll` | none | `Promise<void>` | Re-registers all local listeners on the server (used after reconnection) |
30
+
31
+ ## `createEventClient`
32
+
33
+ ```typescript
34
+ export function createEventClient(transport: ServiceTransport): EventClient;
35
+ ```
36
+
37
+ | Parameter | Type | Description |
38
+ |-----------|------|-------------|
39
+ | `transport` | `ServiceTransport` | The service transport for sending event messages |
40
+
41
+ ## `FileClient`
42
+
43
+ File upload and download client using HTTP `fetch`.
44
+
45
+ ```typescript
46
+ export interface FileClient {
47
+ download(relPath: string): Promise<Bytes>;
48
+ upload(
49
+ files: File[] | FileCollection | { name: string; data: BlobInput }[],
50
+ authToken: string,
51
+ ): Promise<ServiceUploadResult[]>;
52
+ }
53
+ ```
54
+
55
+ | Method | Parameters | Return | Description |
56
+ |--------|-----------|--------|-------------|
57
+ | `download` | `relPath: string` | `Promise<Bytes>` | Downloads a file by relative path as `Uint8Array` |
58
+ | `upload` | `files: File[] \| FileCollection \| { name: string; data: BlobInput }[], authToken: string` | `Promise<ServiceUploadResult[]>` | Uploads files via multipart form data with Bearer token authentication |
59
+
60
+ ## `createFileClient`
61
+
62
+ ```typescript
63
+ export function createFileClient(hostUrl: string, clientName: string): FileClient;
64
+ ```
65
+
66
+ | Parameter | Type | Description |
67
+ |-----------|------|-------------|
68
+ | `hostUrl` | `string` | Full host URL (e.g., `http://localhost:3000`) |
69
+ | `clientName` | `string` | Client identifier sent in `x-sd-client-name` header |
70
+
71
+ ## `OrmConnectOptions`
72
+
73
+ ORM connection options for client-side database context usage.
74
+
75
+ ```typescript
76
+ export interface OrmConnectOptions<TDef extends DbContextDef<any, any, any>> {
77
+ dbContextDef: TDef;
78
+ connOpt: DbConnOptions & { configName: string };
79
+ dbContextOpt?: {
80
+ database: string;
81
+ schema: string;
82
+ };
83
+ }
84
+ ```
85
+
86
+ | Field | Type | Description |
87
+ |-------|------|-------------|
88
+ | `dbContextDef` | `TDef` | Database context definition |
89
+ | `connOpt` | `DbConnOptions & { configName: string }` | Connection options with required config name |
90
+ | `dbContextOpt` | `{ database: string; schema: string }?` | Optional database/schema override |
91
+
92
+ ## `OrmClientConnector`
93
+
94
+ ORM client connector that opens a database context over the service RPC layer.
95
+
96
+ ```typescript
97
+ export interface OrmClientConnector {
98
+ connect<TDef extends DbContextDef<any, any, any>, R>(
99
+ config: OrmConnectOptions<TDef>,
100
+ callback: (db: DbContextInstance<TDef>) => Promise<R> | R,
101
+ ): Promise<R>;
102
+ connectWithoutTransaction<TDef extends DbContextDef<any, any, any>, R>(
103
+ config: OrmConnectOptions<TDef>,
104
+ callback: (db: DbContextInstance<TDef>) => Promise<R> | R,
105
+ ): Promise<R>;
106
+ }
107
+ ```
108
+
109
+ | Method | Parameters | Return | Description |
110
+ |--------|-----------|--------|-------------|
111
+ | `connect` | `config: OrmConnectOptions<TDef>, callback: (db: DbContextInstance<TDef>) => Promise<R> \| R` | `Promise<R>` | Opens a DB context with automatic transaction (begin/commit/rollback). Wraps foreign key constraint errors with a user-friendly message |
112
+ | `connectWithoutTransaction` | `config: OrmConnectOptions<TDef>, callback: (db: DbContextInstance<TDef>) => Promise<R> \| R` | `Promise<R>` | Opens a DB context without automatic transaction management |
113
+
114
+ ## `createOrmClientConnector`
115
+
116
+ ```typescript
117
+ export function createOrmClientConnector(serviceClient: ServiceClient): OrmClientConnector;
118
+ ```
119
+
120
+ | Parameter | Type | Description |
121
+ |-----------|------|-------------|
122
+ | `serviceClient` | `ServiceClient` | The service client instance for RPC communication |
123
+
124
+ ## `OrmClientDbContextExecutor`
125
+
126
+ Implements `DbContextExecutor` (from `@simplysm/orm-common`) for client-side ORM usage over the service RPC layer. Proxies all database operations through the remote `OrmService`.
127
+
128
+ ```typescript
129
+ export class OrmClientDbContextExecutor implements DbContextExecutor {
130
+ constructor(
131
+ client: ServiceClient,
132
+ opt: DbConnOptions & { configName: string },
133
+ );
134
+ }
135
+ ```
136
+
137
+ | Method | Parameters | Return | Description |
138
+ |--------|-----------|--------|-------------|
139
+ | `getInfo` | none | `Promise<{ dialect: Dialect; database?: string; schema?: string }>` | Gets database info via remote ORM service |
140
+ | `connect` | none | `Promise<void>` | Opens a remote connection |
141
+ | `beginTransaction` | `isolationLevel?: IsolationLevel` | `Promise<void>` | Begins a remote transaction |
142
+ | `commitTransaction` | none | `Promise<void>` | Commits the remote transaction |
143
+ | `rollbackTransaction` | none | `Promise<void>` | Rolls back the remote transaction |
144
+ | `close` | none | `Promise<void>` | Closes the remote connection |
145
+ | `executeDefs` | `defs: QueryDef[], options?: (ResultMeta \| undefined)[]` | `Promise<T[][]>` | Executes query definitions via remote ORM service |
146
+ | `executeParametrized` | `query: string, params?: unknown[]` | `Promise<unknown[][]>` | Executes a parameterized query via remote ORM service |
147
+ | `bulkInsert` | `tableName: string, columnDefs: Record<string, ColumnMeta>, records: Record<string, unknown>[]` | `Promise<void>` | Performs bulk insert via remote ORM service |
@@ -0,0 +1,37 @@
1
+ # Progress Types
2
+
3
+ ## `ServiceProgress`
4
+
5
+ Callback hooks for monitoring message progress at different stages.
6
+
7
+ ```typescript
8
+ export interface ServiceProgress {
9
+ request?: (s: ServiceProgressState) => void;
10
+ response?: (s: ServiceProgressState) => void;
11
+ server?: (s: ServiceProgressState) => void;
12
+ }
13
+ ```
14
+
15
+ | Field | Type | Description |
16
+ |-------|------|-------------|
17
+ | `request` | `(s: ServiceProgressState) => void` | Called during request encoding/sending progress |
18
+ | `response` | `(s: ServiceProgressState) => void` | Called during response decoding progress |
19
+ | `server` | `(s: ServiceProgressState) => void` | Called when server reports chunk receive progress |
20
+
21
+ ## `ServiceProgressState`
22
+
23
+ Progress state for a single message transfer.
24
+
25
+ ```typescript
26
+ export interface ServiceProgressState {
27
+ uuid: string;
28
+ totalSize: number;
29
+ completedSize: number;
30
+ }
31
+ ```
32
+
33
+ | Field | Type | Description |
34
+ |-------|------|-------------|
35
+ | `uuid` | `string` | Message UUID |
36
+ | `totalSize` | `number` | Total message size in bytes |
37
+ | `completedSize` | `number` | Bytes transferred so far |
@@ -0,0 +1,89 @@
1
+ # Main ServiceClient
2
+
3
+ ## `ServiceClient`
4
+
5
+ Main service client class. Extends `EventEmitter` with progress and state events. Composes `SocketProvider`, `ServiceTransport`, `EventClient`, `FileClient`, and `ClientProtocolWrapper` internally.
6
+
7
+ ```typescript
8
+ export class ServiceClient extends EventEmitter<{
9
+ "request-progress": ServiceProgressState;
10
+ "response-progress": ServiceProgressState;
11
+ "server-progress": ServiceProgressState;
12
+ "state": "connected" | "closed" | "reconnecting";
13
+ }> {
14
+ constructor(name: string, options: ServiceConnectionOptions);
15
+ }
16
+ ```
17
+
18
+ ### Events
19
+
20
+ | Event | Data Type | Description |
21
+ |-------|-----------|-------------|
22
+ | `request-progress` | `ServiceProgressState` | Request sending progress (chunked messages) |
23
+ | `response-progress` | `ServiceProgressState` | Response receiving progress (chunked messages) |
24
+ | `server-progress` | `ServiceProgressState` | Server-side chunk reception progress |
25
+ | `state` | `"connected" \| "closed" \| "reconnecting"` | Connection state change |
26
+
27
+ ### Properties
28
+
29
+ | Property | Type | Description |
30
+ |----------|------|-------------|
31
+ | `name` | `string` (readonly) | Client name |
32
+ | `options` | `ServiceConnectionOptions` (readonly) | Connection options |
33
+ | `connected` | `boolean` (readonly, getter) | Whether the client is currently connected |
34
+ | `hostUrl` | `string` (readonly, getter) | Full host URL computed from options (e.g., `https://host:port`) |
35
+
36
+ ### Methods
37
+
38
+ | Method | Signature | Description |
39
+ |--------|-----------|-------------|
40
+ | `getService` | `getService<TService>(serviceName: string): ServiceProxy<TService>` | Returns a type-safe proxy that maps method calls to RPC requests |
41
+ | `connect` | `connect(): Promise<void>` | Opens the WebSocket connection |
42
+ | `close` | `close(): Promise<void>` | Closes the connection and disposes protocol resources |
43
+ | `send` | `send(serviceName: string, methodName: string, params: unknown[], progress?: ServiceProgress): Promise<unknown>` | Sends an RPC call directly |
44
+ | `auth` | `auth(token: string): Promise<void>` | Authenticates with a JWT token. Token is cached for reconnection |
45
+ | `addListener` | `addListener<TInfo, TData>(eventDef: ServiceEventDef<TInfo, TData>, info: TInfo, cb: (data: TData) => PromiseLike<void>): Promise<string>` | Subscribes to an event. Returns listener key. Throws if not connected |
46
+ | `removeListener` | `removeListener(key: string): Promise<void>` | Unsubscribes from an event by key |
47
+ | `emitEvent` | `emitEvent<TInfo, TData>(eventDef: ServiceEventDef<TInfo, TData>, infoSelector: (item: TInfo) => boolean, data: TData): Promise<void>` | Emits an event to matching listeners |
48
+ | `uploadFile` | `uploadFile(files: File[] \| FileCollection \| { name: string; data: BlobInput }[]): Promise<ServiceUploadResult[]>` | Uploads files. Requires prior `auth()` call |
49
+ | `downloadFileBuffer` | `downloadFileBuffer(relPath: string): Promise<Bytes>` | Downloads a file as `Uint8Array` |
50
+
51
+ ### Reconnection Behavior
52
+
53
+ On reconnection (`state === "connected"` after a disconnect):
54
+ 1. Re-authenticates with the cached token (if `auth()` was previously called)
55
+ 2. Re-subscribes all event listeners via `EventClient.resubscribeAll()`
56
+
57
+ ## `ServiceProxy`
58
+
59
+ Type utility that wraps all methods of a service interface to return `Promise<Awaited<R>>`.
60
+
61
+ ```typescript
62
+ export type ServiceProxy<TService> = {
63
+ [K in keyof TService]: TService[K] extends (...args: infer P) => infer R
64
+ ? (...args: P) => Promise<Awaited<R>>
65
+ : never;
66
+ };
67
+ ```
68
+
69
+ Non-function properties are mapped to `never`.
70
+
71
+ ## `createServiceClient`
72
+
73
+ Factory function to create a `ServiceClient` instance.
74
+
75
+ ```typescript
76
+ export function createServiceClient(
77
+ name: string,
78
+ options: ServiceConnectionOptions,
79
+ ): ServiceClient;
80
+ ```
81
+
82
+ | Parameter | Type | Description |
83
+ |-----------|------|-------------|
84
+ | `name` | `string` | Client identifier name |
85
+ | `options` | `ServiceConnectionOptions` | Connection options (host, port, SSL, maxReconnectCount) |
86
+
87
+ **Returns:** `ServiceClient`
88
+
89
+ Default `maxReconnectCount` is `10` if not specified in options.
@@ -0,0 +1,60 @@
1
+ # Service Transport
2
+
3
+ ## `ServiceTransportEvents`
4
+
5
+ Event map for the service transport.
6
+
7
+ ```typescript
8
+ export interface ServiceTransportEvents {
9
+ event: { keys: string[]; data: unknown };
10
+ }
11
+ ```
12
+
13
+ | Event | Data Type | Description |
14
+ |-------|-----------|-------------|
15
+ | `event` | `{ keys: string[]; data: unknown }` | Server-side event broadcast received |
16
+
17
+ ## `ServiceTransport`
18
+
19
+ Service-level message transport built on top of `SocketProvider` and `ClientProtocolWrapper`. Manages pending request/response correlation via UUIDs, handles progress notifications, and dispatches events.
20
+
21
+ ```typescript
22
+ export interface ServiceTransport {
23
+ on<K extends keyof ServiceTransportEvents & string>(
24
+ type: K,
25
+ listener: (data: ServiceTransportEvents[K]) => void,
26
+ ): void;
27
+ off<K extends keyof ServiceTransportEvents & string>(
28
+ type: K,
29
+ listener: (data: ServiceTransportEvents[K]) => void,
30
+ ): void;
31
+ send(message: ServiceClientMessage, progress?: ServiceProgress): Promise<unknown>;
32
+ }
33
+ ```
34
+
35
+ | Method | Parameters | Return | Description |
36
+ |--------|-----------|--------|-------------|
37
+ | `on` | `type: K, listener` | `void` | Subscribe to transport events |
38
+ | `off` | `type: K, listener` | `void` | Unsubscribe from transport events |
39
+ | `send` | `message: ServiceClientMessage, progress?: ServiceProgress` | `Promise<unknown>` | Sends a service message and awaits the server response. Returns the response body |
40
+
41
+ ## `createServiceTransport`
42
+
43
+ Creates a `ServiceTransport` instance.
44
+
45
+ ```typescript
46
+ export function createServiceTransport(
47
+ socket: SocketProvider,
48
+ protocol: ClientProtocolWrapper,
49
+ ): ServiceTransport;
50
+ ```
51
+
52
+ | Parameter | Type | Description |
53
+ |-----------|------|-------------|
54
+ | `socket` | `SocketProvider` | The socket provider for sending/receiving binary data |
55
+ | `protocol` | `ClientProtocolWrapper` | The protocol wrapper for encoding/decoding messages |
56
+
57
+ Behavior:
58
+ - Generates a UUID per `send()` call and registers a pending resolver
59
+ - On socket disconnect (`closed`/`reconnecting`), rejects all pending requests
60
+ - Routes server messages: `response` resolves, `error` rejects, `evt:on` emits event, `progress` invokes progress callbacks
@@ -0,0 +1,72 @@
1
+ # Socket Provider
2
+
3
+ ## `SocketProviderEvents`
4
+
5
+ Event map for the socket provider.
6
+
7
+ ```typescript
8
+ export interface SocketProviderEvents {
9
+ message: Bytes;
10
+ state: "connected" | "closed" | "reconnecting";
11
+ }
12
+ ```
13
+
14
+ | Event | Data Type | Description |
15
+ |-------|-----------|-------------|
16
+ | `message` | `Bytes` | Raw binary message received from server |
17
+ | `state` | `"connected" \| "closed" \| "reconnecting"` | Connection state change |
18
+
19
+ ## `SocketProvider`
20
+
21
+ Low-level WebSocket abstraction with automatic reconnection and heartbeat keepalive. Works in both browser and Node.js environments (uses `ws` package as polyfill in Node.js).
22
+
23
+ ```typescript
24
+ export interface SocketProvider {
25
+ readonly clientName: string;
26
+ readonly connected: boolean;
27
+ on<K extends keyof SocketProviderEvents & string>(
28
+ type: K,
29
+ listener: (data: SocketProviderEvents[K]) => void,
30
+ ): void;
31
+ off<K extends keyof SocketProviderEvents & string>(
32
+ type: K,
33
+ listener: (data: SocketProviderEvents[K]) => void,
34
+ ): void;
35
+ connect(): Promise<void>;
36
+ close(): Promise<void>;
37
+ send(data: Bytes): Promise<void>;
38
+ }
39
+ ```
40
+
41
+ | Member | Kind | Description |
42
+ |--------|------|-------------|
43
+ | `clientName` | property (readonly) | Client identifier name |
44
+ | `connected` | property (readonly) | Whether currently connected |
45
+ | `on` | method | Subscribe to an event |
46
+ | `off` | method | Unsubscribe from an event |
47
+ | `connect` | method | Opens the WebSocket connection. Throws on initial connection failure |
48
+ | `close` | method | Closes the connection gracefully |
49
+ | `send` | method | Sends binary data. Waits for connection if not yet open |
50
+
51
+ ## `createSocketProvider`
52
+
53
+ Creates a `SocketProvider` instance with auto-reconnect and heartbeat.
54
+
55
+ ```typescript
56
+ export function createSocketProvider(
57
+ url: string,
58
+ clientName: string,
59
+ maxReconnectCount: number,
60
+ ): SocketProvider;
61
+ ```
62
+
63
+ | Parameter | Type | Description |
64
+ |-----------|------|-------------|
65
+ | `url` | `string` | WebSocket URL (e.g., `ws://localhost:3000/ws`) |
66
+ | `clientName` | `string` | Client identifier |
67
+ | `maxReconnectCount` | `number` | Maximum reconnection attempts |
68
+
69
+ Internal constants:
70
+ - Heartbeat timeout: 30 seconds
71
+ - Heartbeat interval: 5 seconds (sends ping `0x01`, expects pong `0x02`)
72
+ - Reconnect delay: 3 seconds between attempts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/service-client",
3
- "version": "14.0.1",
3
+ "version": "14.0.5",
4
4
  "description": "심플리즘 패키지 - 서비스 (client)",
5
5
  "author": "심플리즘",
6
6
  "license": "Apache-2.0",
@@ -14,14 +14,15 @@
14
14
  "types": "./dist/index.d.ts",
15
15
  "files": [
16
16
  "dist",
17
- "src"
17
+ "src",
18
+ "docs"
18
19
  ],
19
20
  "sideEffects": false,
20
21
  "dependencies": {
21
22
  "consola": "^3.4.2",
22
- "@simplysm/core-common": "14.0.1",
23
- "@simplysm/orm-common": "14.0.1",
24
- "@simplysm/service-common": "14.0.1"
23
+ "@simplysm/core-common": "14.0.5",
24
+ "@simplysm/orm-common": "14.0.5",
25
+ "@simplysm/service-common": "14.0.5"
25
26
  },
26
27
  "devDependencies": {
27
28
  "@types/ws": "^8.18.1",
@@ -1,10 +1,11 @@
1
1
  import type { Bytes } from "@simplysm/core-common";
2
2
  import type { ServiceUploadResult } from "@simplysm/service-common";
3
+ import type { BlobInput, FileCollection } from "../types/browser-compat";
3
4
 
4
5
  export interface FileClient {
5
6
  download(relPath: string): Promise<Bytes>;
6
7
  upload(
7
- files: File[] | FileList | { name: string; data: BlobPart }[],
8
+ files: File[] | FileCollection | { name: string; data: BlobInput }[],
8
9
  authToken: string,
9
10
  ): Promise<ServiceUploadResult[]>;
10
11
  }
@@ -24,13 +25,13 @@ export function createFileClient(hostUrl: string, clientName: string): FileClien
24
25
  }
25
26
 
26
27
  async function upload(
27
- files: File[] | FileList | { name: string; data: BlobPart }[],
28
+ files: File[] | FileCollection | { name: string; data: BlobInput }[],
28
29
  authToken: string,
29
30
  ): Promise<ServiceUploadResult[]> {
30
31
  const formData = new FormData();
31
- const fileList = files instanceof FileList ? Array.from(files) : files;
32
+ const fileArr = Array.isArray(files) ? files : Array.from(files);
32
33
 
33
- for (const file of fileList) {
34
+ for (const file of fileArr) {
34
35
  if ("data" in file) {
35
36
  // 커스텀 객체 ({ name, data })
36
37
  const blob = file.data instanceof Blob ? file.data : new Blob([file.data]);
@@ -54,7 +55,7 @@ export function createFileClient(hostUrl: string, clientName: string): FileClien
54
55
  throw new Error(`업로드 실패: ${res.statusText}`);
55
56
  }
56
57
 
57
- return res.json();
58
+ return (await res.json()) as ServiceUploadResult[];
58
59
  }
59
60
 
60
61
  return {
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  // 타입
2
+ export * from "./types/browser-compat";
2
3
  export * from "./types/connection-options";
3
4
  export * from "./types/progress.types";
4
5
 
@@ -5,6 +5,8 @@ import type {
5
5
  ServiceMessage,
6
6
  ServiceProtocol,
7
7
  } from "@simplysm/service-common";
8
+ import type { WorkerLike } from "../types/browser-compat";
9
+ import { isWorkerSupported, createBrowserWorker } from "../types/browser-compat";
8
10
 
9
11
  export interface ClientProtocolWrapper {
10
12
  encode(uuid: string, message: ServiceMessage): Promise<{ chunks: Bytes[]; totalSize: number }>;
@@ -13,7 +15,7 @@ export interface ClientProtocolWrapper {
13
15
  }
14
16
 
15
17
  // 공유 worker 상태 (싱글턴 패턴)
16
- let worker: Worker | undefined;
18
+ let worker: WorkerLike | undefined;
17
19
  const workerResolvers = new LazyGcMap<
18
20
  string,
19
21
  { resolve: (res: unknown) => void; reject: (err: Error) => void }
@@ -30,12 +32,12 @@ let workerAvailable: boolean | undefined;
30
32
 
31
33
  function isWorkerAvailable(): boolean {
32
34
  if (workerAvailable === undefined) {
33
- workerAvailable = typeof Worker !== "undefined";
35
+ workerAvailable = isWorkerSupported();
34
36
  }
35
37
  return workerAvailable;
36
38
  }
37
39
 
38
- function getWorker(): Worker | undefined {
40
+ function getWorker(): WorkerLike | undefined {
39
41
  if (!isWorkerAvailable()) {
40
42
  return undefined;
41
43
  }
@@ -43,9 +45,11 @@ function getWorker(): Worker | undefined {
43
45
  if (!worker) {
44
46
  // 모던 번들러 (Vite/Esbuild/Webpack)가 이 구문을 사용하여 Worker를 별도 파일로 분리/로드함
45
47
  // 참고: import.meta.resolve 대신 상대 경로 사용 (Vite 호환성)
46
- worker = new Worker(new URL("../workers/client-protocol.worker.ts", import.meta.url), {
47
- type: "module",
48
- });
48
+ worker = createBrowserWorker(
49
+ new URL("../workers/client-protocol.worker.ts", import.meta.url),
50
+ { type: "module" },
51
+ );
52
+ if (worker == null) return undefined;
49
53
 
50
54
  worker.onmessage = (event: MessageEvent) => {
51
55
  const { id, type, result, error } = event.data as {
@@ -78,14 +82,14 @@ function getWorker(): Worker | undefined {
78
82
  async function runWorker(
79
83
  type: "encode" | "decode",
80
84
  data: unknown,
81
- transferables: Transferable[] = [],
85
+ transferables: ArrayBuffer[] = [],
82
86
  ): Promise<unknown> {
83
87
  return new Promise((resolve, reject) => {
84
88
  const id = Uuid.generate().toString();
85
89
 
86
90
  workerResolvers.set(id, { resolve, reject });
87
91
  // workerAvailable 확인 후 호출되므로 worker는 항상 존재
88
- getWorker()!.postMessage({ id, type, data }, { transfer: transferables });
92
+ getWorker()!.postMessage({ id, type, data }, transferables);
89
93
  });
90
94
  }
91
95
 
@@ -135,7 +139,7 @@ export function createClientProtocolWrapper(protocol: ServiceProtocol): ClientPr
135
139
 
136
140
  // [Worker]
137
141
  // Zero-copy 전송 (버퍼 소유권이 Worker로 이동)
138
- const rawResult = await runWorker("decode", bytes, [bytes.buffer]);
142
+ const rawResult = await runWorker("decode", bytes, [bytes.buffer as ArrayBuffer]);
139
143
 
140
144
  // Worker의 plain object 결과에서 클래스 인스턴스 복원 (DateTime 등)
141
145
  return transfer.decode(rawResult) as ServiceMessageDecodeResult<ServiceMessage>;
@@ -3,6 +3,7 @@ import { EventEmitter } from "@simplysm/core-common";
3
3
  import type { ServiceEventDef } from "@simplysm/service-common";
4
4
  import { createServiceProtocol } from "@simplysm/service-common";
5
5
 
6
+ import type { BlobInput, FileCollection } from "./types/browser-compat";
6
7
  import type { ServiceConnectionOptions } from "./types/connection-options";
7
8
  import type { ServiceProgress, ServiceProgressState } from "./types/progress.types";
8
9
  import { createServiceTransport, type ServiceTransport } from "./transport/service-transport";
@@ -18,7 +19,6 @@ interface ServiceClientEvents {
18
19
  "response-progress": ServiceProgressState;
19
20
  "server-progress": ServiceProgressState;
20
21
  "state": "connected" | "closed" | "reconnecting";
21
- "reload": Set<string>;
22
22
  }
23
23
 
24
24
  export class ServiceClient extends EventEmitter<ServiceClientEvents> {
@@ -74,9 +74,6 @@ export class ServiceClient extends EventEmitter<ServiceClientEvents> {
74
74
  }
75
75
  });
76
76
 
77
- this._transport.on("reload", (changedFiles) => {
78
- this.emit("reload", changedFiles);
79
- });
80
77
  }
81
78
 
82
79
  // 타입 안전성을 위한 프록시 생성 메서드
@@ -154,7 +151,7 @@ export class ServiceClient extends EventEmitter<ServiceClientEvents> {
154
151
  await this._eventClient.emit(eventDef, infoSelector, data);
155
152
  }
156
153
 
157
- async uploadFile(files: File[] | FileList | { name: string; data: BlobPart }[]) {
154
+ async uploadFile(files: File[] | FileCollection | { name: string; data: BlobInput }[]) {
158
155
  if (this._authToken == null) {
159
156
  throw new Error(
160
157
  "인증 토큰이 없습니다. 파일 업로드 전에 auth()를 호출하여 인증해 주세요.",
@@ -10,7 +10,6 @@ import type { ServiceProgress } from "../types/progress.types";
10
10
  import type { SocketProvider } from "./socket-provider";
11
11
 
12
12
  export interface ServiceTransportEvents {
13
- reload: Set<string>;
14
13
  event: { keys: string[]; data: unknown };
15
14
  }
16
15
 
@@ -61,7 +60,7 @@ export function createServiceTransport(
61
60
  pendingRequests.set(uuid, { resolve, reject, progress });
62
61
  });
63
62
 
64
- // Promise가 고아가 되었을 때 unhandled rejection 방지 (예: HMR 정리 )
63
+ // Promise가 고아가 되었을 때 unhandled rejection 방지 (예: 소켓 연결 끊김 시)
65
64
  responsePromise.catch(() => {});
66
65
 
67
66
  // 요청 전송
@@ -139,11 +138,6 @@ export function createServiceTransport(
139
138
  pendingRequests.delete(decoded.uuid);
140
139
 
141
140
  listenerInfo?.reject(toError(decoded.message.body));
142
- } else if (decoded.message.name === "reload") {
143
- const body = decoded.message.body as { clientName: string; changedFileSet: Set<string> };
144
- if (socket.clientName === body.clientName) {
145
- emitter.emit("reload", body.changedFileSet);
146
- }
147
141
  } else if (decoded.message.name === "evt:on") {
148
142
  const body = decoded.message.body as { keys: string[]; data: unknown };
149
143
  emitter.emit("event", { keys: body.keys, data: body.data });