@simplysm/service-common 13.0.69 → 13.0.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -251
- package/dist/protocol/create-service-protocol.d.ts +14 -14
- package/dist/protocol/create-service-protocol.js +4 -4
- package/dist/protocol/create-service-protocol.js.map +1 -1
- package/dist/protocol/protocol.types.d.ts +17 -17
- package/dist/protocol/protocol.types.d.ts.map +1 -1
- package/dist/protocol/protocol.types.js +5 -5
- package/dist/service-types/auto-update-service.types.d.ts +5 -5
- package/dist/service-types/orm-service.types.d.ts +4 -4
- package/dist/service-types/orm-service.types.d.ts.map +1 -1
- package/dist/types.d.ts +5 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -6
- package/src/protocol/create-service-protocol.ts +27 -27
- package/src/protocol/protocol.types.ts +35 -35
- package/src/service-types/auto-update-service.types.ts +5 -5
- package/src/service-types/orm-service.types.ts +4 -4
- package/src/types.ts +5 -5
- package/tests/define-event.spec.ts +21 -0
- package/tests/protocol/service-protocol.spec.ts +336 -0
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/service-common",
|
|
3
|
-
"version": "13.0.
|
|
4
|
-
"description": "
|
|
5
|
-
"author": "
|
|
3
|
+
"version": "13.0.70",
|
|
4
|
+
"description": "Simplysm package - Service module (common)",
|
|
5
|
+
"author": "simplysm",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -14,11 +14,12 @@
|
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|
|
17
|
-
"src"
|
|
17
|
+
"src",
|
|
18
|
+
"tests"
|
|
18
19
|
],
|
|
19
20
|
"sideEffects": false,
|
|
20
21
|
"dependencies": {
|
|
21
|
-
"@simplysm/core-common": "13.0.
|
|
22
|
-
"@simplysm/orm-common": "13.0.
|
|
22
|
+
"@simplysm/core-common": "13.0.70",
|
|
23
|
+
"@simplysm/orm-common": "13.0.70"
|
|
23
24
|
}
|
|
24
25
|
}
|
|
@@ -11,52 +11,52 @@ import {
|
|
|
11
11
|
import { PROTOCOL_CONFIG, type ServiceMessage } from "./protocol.types";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* Service protocol interface
|
|
15
15
|
*
|
|
16
16
|
* Binary Protocol V2:
|
|
17
17
|
* - Header: 28 bytes (UUID 16 + TotalSize 8 + Index 4)
|
|
18
18
|
* - Body: JSON
|
|
19
|
-
* -
|
|
20
|
-
* -
|
|
19
|
+
* - Auto chunking: splits into 300KB chunks when exceeding 3MB
|
|
20
|
+
* - Max message size: 100MB
|
|
21
21
|
*/
|
|
22
22
|
export interface ServiceProtocol {
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
24
|
+
* Encode a message (auto-split if needed)
|
|
25
25
|
*/
|
|
26
26
|
encode(uuid: string, message: ServiceMessage): { chunks: Bytes[]; totalSize: number };
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
*
|
|
29
|
+
* Decode a message (auto-reassemble chunked packets)
|
|
30
30
|
*/
|
|
31
31
|
decode<T extends ServiceMessage>(bytes: Bytes): ServiceMessageDecodeResult<T>;
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
34
|
+
* Dispose the protocol instance.
|
|
35
35
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
36
|
+
* Releases the internal chunk accumulator's GC timer and frees memory.
|
|
37
|
+
* Must be called when the protocol instance is no longer needed.
|
|
38
38
|
*/
|
|
39
39
|
dispose(): void;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
43
|
+
* Message decode result type (union)
|
|
44
44
|
*
|
|
45
|
-
* - `type: "complete"`:
|
|
46
|
-
* - `type: "progress"`:
|
|
45
|
+
* - `type: "complete"`: all chunks received and message reassembly is complete
|
|
46
|
+
* - `type: "progress"`: chunked message in progress (only some chunks arrived)
|
|
47
47
|
*/
|
|
48
48
|
export type ServiceMessageDecodeResult<TMessage extends ServiceMessage> =
|
|
49
49
|
| { type: "complete"; uuid: string; message: TMessage }
|
|
50
50
|
| { type: "progress"; uuid: string; totalSize: number; completedSize: number };
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
53
|
+
* Create a service protocol encoder/decoder
|
|
54
54
|
*
|
|
55
55
|
* Binary Protocol V2:
|
|
56
56
|
* - Header: 28 bytes (UUID 16 + TotalSize 8 + Index 4)
|
|
57
57
|
* - Body: JSON
|
|
58
|
-
* -
|
|
59
|
-
* -
|
|
58
|
+
* - Auto chunking: splits into 300KB chunks when exceeding 3MB
|
|
59
|
+
* - Max message size: 100MB
|
|
60
60
|
*/
|
|
61
61
|
export function createServiceProtocol(): ServiceProtocol {
|
|
62
62
|
// -------------------------------------------------------------------
|
|
@@ -80,9 +80,9 @@ export function createServiceProtocol(): ServiceProtocol {
|
|
|
80
80
|
// -------------------------------------------------------------------
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
*
|
|
83
|
+
* Encode a message chunk (header + body)
|
|
84
84
|
*
|
|
85
|
-
*
|
|
85
|
+
* Header structure (28 bytes, Big Endian):
|
|
86
86
|
* ```
|
|
87
87
|
* Offset Size Field
|
|
88
88
|
* ------ ---- -----
|
|
@@ -128,20 +128,20 @@ export function createServiceProtocol(): ServiceProtocol {
|
|
|
128
128
|
|
|
129
129
|
const totalSize = msgBytes.length;
|
|
130
130
|
|
|
131
|
-
//
|
|
131
|
+
// Total size limit check (performed first)
|
|
132
132
|
if (totalSize > PROTOCOL_CONFIG.MAX_TOTAL_SIZE) {
|
|
133
|
-
throw new ArgumentError("
|
|
133
|
+
throw new ArgumentError("Message size exceeds the limit.", {
|
|
134
134
|
totalSize,
|
|
135
135
|
maxSize: PROTOCOL_CONFIG.MAX_TOTAL_SIZE,
|
|
136
136
|
});
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
//
|
|
139
|
+
// Return as-is if small enough
|
|
140
140
|
if (totalSize <= PROTOCOL_CONFIG.SPLIT_MESSAGE_SIZE) {
|
|
141
141
|
return { chunks: [encodeChunk({ uuid, totalSize, index: 0 }, msgBytes)], totalSize };
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
//
|
|
144
|
+
// Split into chunks
|
|
145
145
|
const chunks: Bytes[] = [];
|
|
146
146
|
let offset = 0;
|
|
147
147
|
let index = 0;
|
|
@@ -161,13 +161,13 @@ export function createServiceProtocol(): ServiceProtocol {
|
|
|
161
161
|
|
|
162
162
|
decode<T extends ServiceMessage>(bytes: Bytes): ServiceMessageDecodeResult<T> {
|
|
163
163
|
if (bytes.length < 28) {
|
|
164
|
-
throw new ArgumentError("
|
|
164
|
+
throw new ArgumentError("Buffer size is smaller than header size.", {
|
|
165
165
|
bufferSize: bytes.length,
|
|
166
166
|
minimumSize: 28,
|
|
167
167
|
});
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
// 1.
|
|
170
|
+
// 1. Read header
|
|
171
171
|
|
|
172
172
|
// UUID
|
|
173
173
|
const uuidBytes = bytes.subarray(0, 16);
|
|
@@ -178,9 +178,9 @@ export function createServiceProtocol(): ServiceProtocol {
|
|
|
178
178
|
const totalSize = Number(headerView.getBigUint64(16, false));
|
|
179
179
|
const index = headerView.getUint32(24, false);
|
|
180
180
|
|
|
181
|
-
//
|
|
181
|
+
// Total size limit check (performed first)
|
|
182
182
|
if (totalSize > PROTOCOL_CONFIG.MAX_TOTAL_SIZE) {
|
|
183
|
-
throw new ArgumentError("
|
|
183
|
+
throw new ArgumentError("Message size exceeds the limit.", {
|
|
184
184
|
totalSize,
|
|
185
185
|
maxSize: PROTOCOL_CONFIG.MAX_TOTAL_SIZE,
|
|
186
186
|
});
|
|
@@ -194,7 +194,7 @@ export function createServiceProtocol(): ServiceProtocol {
|
|
|
194
194
|
chunks: [],
|
|
195
195
|
}));
|
|
196
196
|
if (accItem.chunks[index] == null) {
|
|
197
|
-
//
|
|
197
|
+
// Duplicate packet guard
|
|
198
198
|
accItem.chunks[index] = bodyBytes;
|
|
199
199
|
accItem.completedSize += bodyBytes.length;
|
|
200
200
|
}
|
|
@@ -207,14 +207,14 @@ export function createServiceProtocol(): ServiceProtocol {
|
|
|
207
207
|
completedSize: accItem.completedSize,
|
|
208
208
|
};
|
|
209
209
|
} else {
|
|
210
|
-
accumulator.delete(uuid); //
|
|
210
|
+
accumulator.delete(uuid); // Free memory
|
|
211
211
|
|
|
212
212
|
const resultBytes = bytesConcat(accItem.chunks.filterExists());
|
|
213
213
|
let messageArr: [string, unknown];
|
|
214
214
|
try {
|
|
215
215
|
messageArr = jsonParse<[string, unknown]>(new TextDecoder().decode(resultBytes));
|
|
216
216
|
} catch (err) {
|
|
217
|
-
throw new ArgumentError("
|
|
217
|
+
throw new ArgumentError("Failed to decode message.", { uuid, cause: err });
|
|
218
218
|
}
|
|
219
219
|
return {
|
|
220
220
|
type: "complete",
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
// Protocol Constants
|
|
3
3
|
// ----------------------------------------------------------------------
|
|
4
4
|
|
|
5
|
-
/**
|
|
5
|
+
/** Service protocol configuration */
|
|
6
6
|
export const PROTOCOL_CONFIG = {
|
|
7
|
-
/**
|
|
7
|
+
/** Max message size (100MB) */
|
|
8
8
|
MAX_TOTAL_SIZE: 100 * 1024 * 1024,
|
|
9
|
-
/**
|
|
9
|
+
/** Chunking threshold (3MB) */
|
|
10
10
|
SPLIT_MESSAGE_SIZE: 3 * 1024 * 1024,
|
|
11
|
-
/**
|
|
11
|
+
/** Chunk size (300KB) */
|
|
12
12
|
CHUNK_SIZE: 300 * 1024,
|
|
13
|
-
/** GC
|
|
13
|
+
/** GC interval (10s) */
|
|
14
14
|
GC_INTERVAL: 10 * 1000,
|
|
15
|
-
/**
|
|
15
|
+
/** Incomplete message expiry time (60s) */
|
|
16
16
|
EXPIRE_TIME: 60 * 1000,
|
|
17
17
|
} as const;
|
|
18
18
|
|
|
@@ -34,10 +34,10 @@ export type ServiceMessage =
|
|
|
34
34
|
| ServiceEventMessage;
|
|
35
35
|
|
|
36
36
|
export type ServiceServerMessage =
|
|
37
|
-
| ServiceReloadMessage //
|
|
37
|
+
| ServiceReloadMessage // Notification
|
|
38
38
|
| ServiceResponseMessage
|
|
39
39
|
| ServiceErrorMessage
|
|
40
|
-
| ServiceEventMessage; //
|
|
40
|
+
| ServiceEventMessage; // Notification
|
|
41
41
|
|
|
42
42
|
export type ServiceServerRawMessage = ServiceProgressMessage | ServiceServerMessage;
|
|
43
43
|
|
|
@@ -50,28 +50,28 @@ export type ServiceClientMessage =
|
|
|
50
50
|
| ServiceEmitEventMessage;
|
|
51
51
|
|
|
52
52
|
// ----------------------------------------------------------------------
|
|
53
|
-
// System
|
|
53
|
+
// System (common)
|
|
54
54
|
// ----------------------------------------------------------------------
|
|
55
55
|
|
|
56
|
-
/**
|
|
56
|
+
/** Server: reload command to client */
|
|
57
57
|
export interface ServiceReloadMessage {
|
|
58
58
|
name: "reload";
|
|
59
59
|
body: {
|
|
60
|
-
clientName: string | undefined; //
|
|
61
|
-
changedFileSet: Set<string>; //
|
|
60
|
+
clientName: string | undefined; // Client name
|
|
61
|
+
changedFileSet: Set<string>; // Changed file list
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
/**
|
|
65
|
+
/** Server: progress notification for received chunked message */
|
|
66
66
|
export interface ServiceProgressMessage {
|
|
67
67
|
name: "progress";
|
|
68
68
|
body: {
|
|
69
|
-
totalSize: number; //
|
|
70
|
-
completedSize: number; //
|
|
69
|
+
totalSize: number; // Total size (bytes)
|
|
70
|
+
completedSize: number; // Completed size (bytes)
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
/**
|
|
74
|
+
/** Server: error notification */
|
|
75
75
|
export interface ServiceErrorMessage {
|
|
76
76
|
name: "error";
|
|
77
77
|
body: {
|
|
@@ -84,72 +84,72 @@ export interface ServiceErrorMessage {
|
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
/**
|
|
87
|
+
/** Client: authentication message */
|
|
88
88
|
export interface ServiceAuthMessage {
|
|
89
89
|
name: "auth";
|
|
90
|
-
body: string; //
|
|
90
|
+
body: string; // Token
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
// ----------------------------------------------------------------------
|
|
94
94
|
// Service.Method
|
|
95
95
|
// ----------------------------------------------------------------------
|
|
96
96
|
|
|
97
|
-
/**
|
|
97
|
+
/** Client: service method request */
|
|
98
98
|
export interface ServiceRequestMessage {
|
|
99
99
|
name: `${string}.${string}`; // ${service}.${method}
|
|
100
100
|
body: unknown[]; // params
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
/**
|
|
103
|
+
/** Server: service method response */
|
|
104
104
|
export interface ServiceResponseMessage {
|
|
105
105
|
name: "response";
|
|
106
106
|
body?: unknown; // result
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
// ----------------------------------------------------------------------
|
|
110
|
-
//
|
|
110
|
+
// Events
|
|
111
111
|
// ----------------------------------------------------------------------
|
|
112
112
|
|
|
113
|
-
/**
|
|
113
|
+
/** Client: add event listener */
|
|
114
114
|
export interface ServiceAddEventListenerMessage {
|
|
115
115
|
name: "evt:add";
|
|
116
116
|
body: {
|
|
117
|
-
key: string; //
|
|
118
|
-
name: string; //
|
|
119
|
-
info: unknown; //
|
|
117
|
+
key: string; // Listener key (uuid) - needed for removeEventListener
|
|
118
|
+
name: string; // Event name (Type.name)
|
|
119
|
+
info: unknown; // Additional listener info for filtering when events fire
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
/**
|
|
123
|
+
/** Client: remove event listener */
|
|
124
124
|
export interface ServiceRemoveEventListenerMessage {
|
|
125
125
|
name: "evt:remove";
|
|
126
126
|
body: {
|
|
127
|
-
key: string; //
|
|
127
|
+
key: string; // Listener key (uuid)
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
/**
|
|
131
|
+
/** Client: request event listener info list */
|
|
132
132
|
export interface ServiceGetEventListenerInfosMessage {
|
|
133
133
|
name: "evt:gets";
|
|
134
134
|
body: {
|
|
135
|
-
name: string; //
|
|
135
|
+
name: string; // Event name
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
/**
|
|
139
|
+
/** Client: emit event */
|
|
140
140
|
export interface ServiceEmitEventMessage {
|
|
141
141
|
name: "evt:emit";
|
|
142
142
|
body: {
|
|
143
|
-
keys: string[]; //
|
|
144
|
-
data: unknown; //
|
|
143
|
+
keys: string[]; // Listener key list
|
|
144
|
+
data: unknown; // Data
|
|
145
145
|
};
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
/**
|
|
148
|
+
/** Server: event notification */
|
|
149
149
|
export interface ServiceEventMessage {
|
|
150
150
|
name: "evt:on";
|
|
151
151
|
body: {
|
|
152
|
-
keys: string[]; //
|
|
153
|
-
data: unknown; //
|
|
152
|
+
keys: string[]; // Listener key list
|
|
153
|
+
data: unknown; // Data
|
|
154
154
|
};
|
|
155
155
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Auto-update service interface
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Retrieves the latest version info for client applications.
|
|
5
5
|
*/
|
|
6
6
|
export interface AutoUpdateService {
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
* @param platform
|
|
10
|
-
* @returns
|
|
8
|
+
* Retrieve the latest version info for the specified platform.
|
|
9
|
+
* @param platform Target platform (e.g., "win32", "darwin", "linux")
|
|
10
|
+
* @returns Latest version info, or undefined if no version exists
|
|
11
11
|
*/
|
|
12
12
|
getLastVersion(platform: string): Promise<
|
|
13
13
|
| {
|
|
@@ -7,10 +7,10 @@ import type {
|
|
|
7
7
|
} from "@simplysm/orm-common";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* ORM
|
|
10
|
+
* ORM service interface
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
* MySQL, MSSQL, PostgreSQL
|
|
12
|
+
* Provides database connection, transaction management, and query execution.
|
|
13
|
+
* Supports MySQL, MSSQL, and PostgreSQL.
|
|
14
14
|
*/
|
|
15
15
|
export interface OrmService {
|
|
16
16
|
getInfo(opt: DbConnOptions & { configName: string }): Promise<{
|
|
@@ -45,5 +45,5 @@ export interface OrmService {
|
|
|
45
45
|
): Promise<void>;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
/**
|
|
48
|
+
/** Database connection options */
|
|
49
49
|
export type DbConnOptions = { configName?: string; config?: Record<string, unknown> };
|
package/src/types.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* File upload result
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Contains information about a file uploaded to the server.
|
|
5
5
|
*/
|
|
6
6
|
export interface ServiceUploadResult {
|
|
7
|
-
/**
|
|
7
|
+
/** Storage path on the server */
|
|
8
8
|
path: string;
|
|
9
|
-
/**
|
|
9
|
+
/** Original filename */
|
|
10
10
|
filename: string;
|
|
11
|
-
/**
|
|
11
|
+
/** File size (bytes) */
|
|
12
12
|
size: number;
|
|
13
13
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// packages/service-common/tests/define-event.spec.ts
|
|
2
|
+
import { describe, it, expect } from "vitest";
|
|
3
|
+
import { defineEvent } from "@simplysm/service-common";
|
|
4
|
+
|
|
5
|
+
describe("defineEvent", () => {
|
|
6
|
+
it("create event definition with given name", () => {
|
|
7
|
+
const evt = defineEvent<{ channel: string }, string>("OrderUpdated");
|
|
8
|
+
expect(evt.eventName).toBe("OrderUpdated");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("can be used for type inference (checked at compile time)", () => {
|
|
12
|
+
const _evt = defineEvent<{ orderId: number }, { status: string }>("OrderUpdated");
|
|
13
|
+
|
|
14
|
+
// Type level verification — fails at compile time if incorrect
|
|
15
|
+
const info: typeof _evt.$info = { orderId: 123 };
|
|
16
|
+
const data: typeof _evt.$data = { status: "shipped" };
|
|
17
|
+
|
|
18
|
+
expect(info.orderId).toBe(123);
|
|
19
|
+
expect(data.status).toBe("shipped");
|
|
20
|
+
});
|
|
21
|
+
});
|