@hile/micro 4.0.5 → 4.0.6
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/AI.md +5 -0
- package/README.md +2 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +39 -26
- package/package.json +5 -5
package/AI.md
CHANGED
|
@@ -282,6 +282,9 @@ import { MessageWorkerThread } from '@hile/message-worker-thread'
|
|
|
282
282
|
- `defineMicroMessage()` handlers receive request streams as `input: Readable | undefined`, separately from structured `data` and `invocation`.
|
|
283
283
|
- `Application.call(namespace, url, data, options)` requires `options.context` and returns a promise. It accepts `options.input` for a streamed request with a normal response.
|
|
284
284
|
- `Application.stream(namespace, url, data, options)` requires `options.context` and returns a readable response stream; it can carry a request input stream at the same time.
|
|
285
|
+
- `Application.call()` and `Application.stream()` may target the application's own namespace. Self calls use the same Registry-discovered WebSocket and modem protocol as remote calls, so Context validation, request and response streams, cancellation, timeout, retry, circuit-breaker, and backpressure behavior stay uniform; business handlers do not need a local-call branch.
|
|
286
|
+
- A peer address (`host:port`) is the routing and reuse identity, not an individual WebSocket identity. If both peers dial each other concurrently, including an application dialing itself, the server preserves both physical connections while they are active instead of replacing a connection that may carry an in-flight request. Later calls still reuse the cached peer connection.
|
|
287
|
+
- This connection bookkeeping does not add or change wire frames; request, response, stream, credit, cancel, and abort protocol fields remain unchanged.
|
|
285
288
|
- `Application.call()` and `Application.stream()` default retries to `0` when request input is streamed. Explicit nonzero retries fail before service discovery because streamed input is non-replayable.
|
|
286
289
|
- A caller-owned request input source failure is surfaced as `MessageInputError`, aborts the peer invocation, and is not counted against that peer's circuit-breaker health.
|
|
287
290
|
- `Application.publish(topic, payload)` returns an object with `update()` and `unpublish()`.
|
|
@@ -293,6 +296,7 @@ import { MessageWorkerThread } from '@hile/message-worker-thread'
|
|
|
293
296
|
- Appending a secondary response getter to `client.request('/x', data)`
|
|
294
297
|
- Returning a plain object from a handler called through `stream()`.
|
|
295
298
|
- Retrying a consumed request stream or hiding it inside a replay-unsafe factory.
|
|
299
|
+
- Branching business code on whether a Micro target namespace is local or remote.
|
|
296
300
|
- Using pub/sub as a durable queue.
|
|
297
301
|
- Forgetting to register `shutdown(await app.listen(...))`.
|
|
298
302
|
|
|
@@ -300,6 +304,7 @@ import { MessageWorkerThread } from '@hile/message-worker-thread'
|
|
|
300
304
|
|
|
301
305
|
- Micro message files default-export `defineMicroMessage(...)` and receive `invocation.context`.
|
|
302
306
|
- RPC callers use `await app.call(..., { context })`.
|
|
307
|
+
- Same-namespace `call()` and `stream()` use normal Registry discovery, while `streamPeer()` keeps its exact-address selection; all three use the normal WebSocket/modem transport path and callers do not branch on locality.
|
|
303
308
|
- Streaming handlers are async generators.
|
|
304
309
|
- Request-stream handlers consume `input` and callers either pass `options.input` alongside metadata or pass a stream directly as `data`.
|
|
305
310
|
- Streamed request calls do not configure nonzero retries.
|
package/README.md
CHANGED
|
@@ -80,6 +80,7 @@ const result = await app.call('example.service', '/ping', { hello: 'world' }, {
|
|
|
80
80
|
- Appending a secondary response getter to `client.request('/x', data)`
|
|
81
81
|
- Returning a plain object from a handler called through `stream()`.
|
|
82
82
|
- Retrying a consumed request stream or hiding it inside a replay-unsafe factory.
|
|
83
|
+
- Branching business code on whether a Micro target namespace is local or remote.
|
|
83
84
|
- Using pub/sub as a durable queue.
|
|
84
85
|
- Forgetting to register `shutdown(await app.listen(...))`.
|
|
85
86
|
|
|
@@ -87,6 +88,7 @@ const result = await app.call('example.service', '/ping', { hello: 'world' }, {
|
|
|
87
88
|
|
|
88
89
|
- Micro message files default-export `defineMicroMessage(...)` and receive `invocation.context`.
|
|
89
90
|
- RPC callers use `await app.call(..., { context })`.
|
|
91
|
+
- Same-namespace `call()` and `stream()` use normal Registry discovery, while `streamPeer()` keeps its exact-address selection; all three use the normal WebSocket/modem transport path and callers do not branch on locality.
|
|
90
92
|
- Streaming handlers are async generators.
|
|
91
93
|
- Request-stream handlers consume `input` and callers either pass `options.input` alongside metadata or pass a stream directly as `data`.
|
|
92
94
|
- Streamed request calls do not configure nonzero retries.
|
package/dist/server.d.ts
CHANGED
|
@@ -23,12 +23,14 @@ export declare class Server extends MessageLoader {
|
|
|
23
23
|
readonly logger: Logger | Console;
|
|
24
24
|
readonly clients: Map<string, Client>;
|
|
25
25
|
private readonly clientExtras;
|
|
26
|
+
private readonly connections;
|
|
26
27
|
private readonly pendingConnections;
|
|
27
28
|
private readonly announceHost;
|
|
28
29
|
readonly events: EventEmitter<any>;
|
|
29
30
|
get host(): string;
|
|
30
31
|
constructor(namespace: string, props?: MicroServerProps);
|
|
31
32
|
private upstream;
|
|
33
|
+
private removeClient;
|
|
32
34
|
private createClient;
|
|
33
35
|
protected connect(host: string, port: number, timeout?: number, signal?: AbortSignal): Promise<Client>;
|
|
34
36
|
private openConnection;
|
package/dist/server.js
CHANGED
|
@@ -12,6 +12,7 @@ export class Server extends MessageLoader {
|
|
|
12
12
|
logger;
|
|
13
13
|
clients = new Map();
|
|
14
14
|
clientExtras = new Map();
|
|
15
|
+
connections = new Map();
|
|
15
16
|
pendingConnections = new Map();
|
|
16
17
|
announceHost;
|
|
17
18
|
events = new EventEmitter();
|
|
@@ -54,29 +55,42 @@ export class Server extends MessageLoader {
|
|
|
54
55
|
portNum > 65535) {
|
|
55
56
|
return ws.close();
|
|
56
57
|
}
|
|
57
|
-
this.createClient(ws, host, portNum, extras);
|
|
58
|
+
this.createClient(ws, host, portNum, extras, 'inbound');
|
|
58
59
|
}
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
removeClient(client, fallback) {
|
|
61
|
+
const connection = this.connections.get(client) ?? fallback;
|
|
62
|
+
if (!connection)
|
|
63
|
+
return;
|
|
64
|
+
const { key, extras } = connection;
|
|
65
|
+
this.connections.delete(client);
|
|
66
|
+
if (this.clients.get(key) === client) {
|
|
64
67
|
this.clients.delete(key);
|
|
65
68
|
this.clientExtras.delete(key);
|
|
66
|
-
previous.dispose();
|
|
67
|
-
this.events.emit('disconnect', previous, previousExtras);
|
|
68
69
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
client.dispose();
|
|
71
|
+
this.events.emit('disconnect', client, extras);
|
|
72
|
+
}
|
|
73
|
+
createClient(ws, host, port, extras = [], direction) {
|
|
74
|
+
const key = `${host}:${port}`;
|
|
75
|
+
// host:port identifies a peer, not an individual WebSocket. When both peers
|
|
76
|
+
// dial at the same time (including a server dialing itself), the inbound
|
|
77
|
+
// half arrives while an outbound connection for the same peer is pending.
|
|
78
|
+
// Keep that inbound half alive for requests already using it, but reserve
|
|
79
|
+
// the peer cache slot for the outbound half that connect() will return.
|
|
80
|
+
const isDialCollision = direction === 'inbound' && this.pendingConnections.has(key);
|
|
81
|
+
if (!isDialCollision) {
|
|
82
|
+
const previous = this.clients.get(key);
|
|
83
|
+
if (previous) {
|
|
84
|
+
this.removeClient(previous, { key, extras: this.clientExtras.get(key) ?? [] });
|
|
76
85
|
}
|
|
77
|
-
}
|
|
78
|
-
this
|
|
79
|
-
this.
|
|
86
|
+
}
|
|
87
|
+
const client = new Client({ server: this, ws, host, port });
|
|
88
|
+
this.connections.set(client, { key, extras });
|
|
89
|
+
ws.on('close', () => this.removeClient(client));
|
|
90
|
+
if (!isDialCollision) {
|
|
91
|
+
this.clients.set(key, client);
|
|
92
|
+
this.clientExtras.set(key, extras);
|
|
93
|
+
}
|
|
80
94
|
this.events.emit('connect', client, extras);
|
|
81
95
|
return client;
|
|
82
96
|
}
|
|
@@ -91,7 +105,7 @@ export class Server extends MessageLoader {
|
|
|
91
105
|
if (!pending) {
|
|
92
106
|
const controller = new AbortController();
|
|
93
107
|
const promise = this.openConnection(host, port, controller.signal)
|
|
94
|
-
.then(ws => this.createClient(ws, host, port))
|
|
108
|
+
.then(ws => this.createClient(ws, host, port, [], 'outbound'))
|
|
95
109
|
.finally(() => { this.pendingConnections.delete(key); });
|
|
96
110
|
promise.catch(() => undefined);
|
|
97
111
|
pending = { promise, controller, waiters: 0 };
|
|
@@ -202,16 +216,15 @@ export class Server extends MessageLoader {
|
|
|
202
216
|
});
|
|
203
217
|
});
|
|
204
218
|
}
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
this.clientExtras.
|
|
210
|
-
client.dispose();
|
|
211
|
-
this.events.emit('disconnect', client, extras);
|
|
219
|
+
for (const client of [...this.connections.keys()]) {
|
|
220
|
+
this.removeClient(client);
|
|
221
|
+
}
|
|
222
|
+
for (const [key, client] of [...this.clients.entries()]) {
|
|
223
|
+
this.removeClient(client, { key, extras: this.clientExtras.get(key) ?? [] });
|
|
212
224
|
}
|
|
213
225
|
this.clients.clear();
|
|
214
226
|
this.clientExtras.clear();
|
|
227
|
+
this.connections.clear();
|
|
215
228
|
this.wss = undefined;
|
|
216
229
|
this.port = undefined;
|
|
217
230
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hile/micro",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@hile/context": "^4.0.3",
|
|
28
28
|
"@hile/logger": "^4.0.2",
|
|
29
|
-
"@hile/message-loader": "^4.0.
|
|
30
|
-
"@hile/message-modem": "^4.0.
|
|
31
|
-
"@hile/message-ws": "^4.0.
|
|
29
|
+
"@hile/message-loader": "^4.0.6",
|
|
30
|
+
"@hile/message-modem": "^4.0.5",
|
|
31
|
+
"@hile/message-ws": "^4.0.5",
|
|
32
32
|
"internal-ip": "^9.0.0",
|
|
33
33
|
"ws": "^8.21.0",
|
|
34
34
|
"yaml": "^2.9.0"
|
|
35
35
|
},
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "f845e0eb6a9d56ef72d2305624ad44abcdff5063"
|
|
37
37
|
}
|