@hile/micro 4.0.3 → 4.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.
- package/AI.md +118 -4
- package/README.md +4 -0
- package/dist/application.d.ts +2 -0
- package/dist/application.js +31 -3
- package/dist/client.d.ts +9 -8
- package/dist/client.js +24 -5
- package/dist/message.d.ts +2 -0
- package/package.json +7 -6
package/AI.md
CHANGED
|
@@ -106,6 +106,42 @@ for await (const chunk of stream) {
|
|
|
106
106
|
}
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
Streaming request body with a normal response:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
// src/messages/upload.msg.ts
|
|
113
|
+
import { defineMicroMessage } from '@hile/micro'
|
|
114
|
+
|
|
115
|
+
export default defineMicroMessage(async ({ data, input, invocation }) => {
|
|
116
|
+
if (!input) throw new Error('upload body is required')
|
|
117
|
+
|
|
118
|
+
let bytes = 0
|
|
119
|
+
for await (const chunk of input) bytes += Buffer.byteLength(chunk)
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
filename: data.filename,
|
|
123
|
+
bytes,
|
|
124
|
+
requestId: invocation.context.values.requestId,
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { createReadStream } from 'node:fs'
|
|
131
|
+
|
|
132
|
+
const result = await app.call(
|
|
133
|
+
'example.service',
|
|
134
|
+
'/upload',
|
|
135
|
+
{ filename: 'archive.tar' },
|
|
136
|
+
{ context, input: createReadStream('/tmp/archive.tar') },
|
|
137
|
+
)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
If no structured metadata is needed, pass an `AsyncIterable`, `Uint8Array`, or
|
|
141
|
+
`ArrayBuffer` directly as `data`; Hile sends it as the request input stream and
|
|
142
|
+
the handler receives `data === undefined`. `app.stream()` accepts the same
|
|
143
|
+
request input forms when both request and response need to stream.
|
|
144
|
+
|
|
109
145
|
Custom WebSocket modem:
|
|
110
146
|
|
|
111
147
|
```ts
|
|
@@ -129,6 +165,68 @@ class RpcWs extends MessageWs {
|
|
|
129
165
|
|
|
130
166
|
Notice that `request()` returns a `Promise<T>`. Await it directly.
|
|
131
167
|
|
|
168
|
+
## Stream Wire Model
|
|
169
|
+
|
|
170
|
+
One request ID owns the structured request and both optional stream directions:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
type RequestFrame = {
|
|
174
|
+
id: number
|
|
175
|
+
mode: MESSAGE_MODEM_TYPE.REQUEST
|
|
176
|
+
twoway: boolean
|
|
177
|
+
data?: unknown
|
|
178
|
+
streams?: {
|
|
179
|
+
input?: true
|
|
180
|
+
output?: { window?: number }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
type StreamDataFrame = {
|
|
185
|
+
id: number
|
|
186
|
+
mode: MESSAGE_MODEM_TYPE.STREAM_DATA
|
|
187
|
+
twoway: false
|
|
188
|
+
data: {
|
|
189
|
+
direction: 'input' | 'output'
|
|
190
|
+
seq: number
|
|
191
|
+
payload?: unknown
|
|
192
|
+
final: boolean
|
|
193
|
+
status?: string | number
|
|
194
|
+
message?: string
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
type StreamCreditFrame = {
|
|
199
|
+
id: number
|
|
200
|
+
mode: MESSAGE_MODEM_TYPE.STREAM_CREDIT
|
|
201
|
+
twoway: false
|
|
202
|
+
data: {
|
|
203
|
+
direction: 'input' | 'output'
|
|
204
|
+
seq: number
|
|
205
|
+
window?: number
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
type StreamCancelFrame = {
|
|
210
|
+
id: number
|
|
211
|
+
mode: MESSAGE_MODEM_TYPE.STREAM_CANCEL
|
|
212
|
+
twoway: false
|
|
213
|
+
data: {
|
|
214
|
+
direction: 'input' | 'output'
|
|
215
|
+
status?: string | number
|
|
216
|
+
message?: string
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`data` is the structured message payload; stream chunks never get embedded in
|
|
222
|
+
it. The request frame declares the active directions, and every later stream
|
|
223
|
+
frame reuses the request ID. Input and output sequence numbers, credits, and
|
|
224
|
+
cancellation are independent.
|
|
225
|
+
|
|
226
|
+
A non-final stream frame must carry a payload other than `null` or `undefined`.
|
|
227
|
+
Node `Readable` reserves those values for its own end/no-op semantics, so Hile
|
|
228
|
+
rejects them instead of silently losing a credit or ending a stream early.
|
|
229
|
+
|
|
132
230
|
## Use When
|
|
133
231
|
|
|
134
232
|
Use the message packages for request/response messaging over WebSocket, process IPC, worker threads, file-system message handlers, service discovery, streaming RPC, and registry-backed pub/sub.
|
|
@@ -136,6 +234,7 @@ Use the message packages for request/response messaging over WebSocket, process
|
|
|
136
234
|
## Do Not Use When
|
|
137
235
|
|
|
138
236
|
- Do not use `stream()` for normal single-result calls.
|
|
237
|
+
- Do not enable retries for a streamed request input. Input streams are consumed once and cannot be replayed safely.
|
|
139
238
|
- Do not rely on message IDs for business idempotency. They are transport IDs.
|
|
140
239
|
- Use `defineMicroMessage()` for Micro business handlers; reserve generic `defineMessage()` for transport-neutral loaders.
|
|
141
240
|
- Do not pass zero, fractional, non-finite, or oversized message timeouts. Explicit timeout values must be safe integers from `1` through `2_147_483_647` milliseconds.
|
|
@@ -170,14 +269,21 @@ import { MessageWorkerThread } from '@hile/message-worker-thread'
|
|
|
170
269
|
- `MessageLoader` maps `*.msg.*` files to routes using `@hile/loader`.
|
|
171
270
|
- `MessageLoader.dispatch(path, data, extras?)` invokes the matched handler.
|
|
172
271
|
- `MessageModem._send()` returns a `Promise`.
|
|
272
|
+
- `_send()` and `_stream()` accept `options.input` as an `AsyncIterable`, `Uint8Array`, or `ArrayBuffer`. Passing one of those values directly as `data` automatically selects request streaming and leaves the handler's structured `data` undefined.
|
|
173
273
|
- `MessageModem._send()` and `_push()` use a `30_000` ms timeout when none is provided. An explicit timeout must be a safe integer from `1` through `2_147_483_647`; invalid values throw `TypeError` before a message is sent.
|
|
174
274
|
- `MessageModem._stream()` returns a Node `Readable` in object mode.
|
|
175
|
-
- Stream `timeout` and `idleTimeout` values use the same `1` through `2_147_483_647` ms range. The stream `window` must be a safe integer from `1` through `64` and defaults to `1`.
|
|
275
|
+
- Stream `timeout` and `idleTimeout` values use the same `1` through `2_147_483_647` ms range. Idle timeout is refreshed by valid input or output stream activity. The response stream `window` must be a safe integer from `1` through `64` and defaults to `1`; request input uses receiver-issued credit with a window of `1`.
|
|
176
276
|
- Each modem schedules request, total-stream, and idle-stream deadlines through one internal deadline scheduler. This reduces active Node.js timers without changing timeout, cancellation, ordering, or error semantics.
|
|
177
|
-
-
|
|
277
|
+
- Request and response chunks share `STREAM_DATA` frames and carry an explicit `direction: 'input' | 'output'`; each direction has independent sequence and credit state. `STREAM_CANCEL` stops one direction. A cancel carrying `status` fails the owning request immediately; `ABORT` cancels the whole invocation.
|
|
278
|
+
- This frame model replaces the former response-only `stream`, `streamVersion`, and `streamWindow` fields. All peers on one transport connection must use compatible `@hile/message-modem` and transport package versions; do not mix old and new peers during a rolling deployment.
|
|
279
|
+
- `@hile/message-ws` sends `Uint8Array` and `ArrayBuffer` request and response chunks as native binary frames rather than JSON/base64. Public `decodeMessageFrame()` payloads remain isolated from caller-owned input by default; the owned WebSocket `RawData` path uses a zero-copy binary view internally.
|
|
280
|
+
- `@hile/message-ipc` transparently Base64-wraps only binary `STREAM_DATA` payloads so request and response streams survive Node child-process IPC's default JSON serialization. Other IPC frames keep their ordinary object representation.
|
|
178
281
|
- A stream request requires `exec()` to return an async iterable.
|
|
179
|
-
- `
|
|
180
|
-
- `Application.
|
|
282
|
+
- `defineMicroMessage()` handlers receive request streams as `input: Readable | undefined`, separately from structured `data` and `invocation`.
|
|
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
|
+
- `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()` default retries to `0` when request input is streamed. Explicit nonzero retries fail before service discovery because streamed input is non-replayable.
|
|
286
|
+
- 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.
|
|
181
287
|
- `Application.publish(topic, payload)` returns an object with `update()` and `unpublish()`.
|
|
182
288
|
- `Application.subscribe(topic, callback)` returns an unsubscribe function.
|
|
183
289
|
- `Registry` stores service addresses and retained config/topic state under `~/.registry`.
|
|
@@ -186,6 +292,7 @@ import { MessageWorkerThread } from '@hile/message-worker-thread'
|
|
|
186
292
|
|
|
187
293
|
- Appending a secondary response getter to `client.request('/x', data)`
|
|
188
294
|
- Returning a plain object from a handler called through `stream()`.
|
|
295
|
+
- Retrying a consumed request stream or hiding it inside a replay-unsafe factory.
|
|
189
296
|
- Using pub/sub as a durable queue.
|
|
190
297
|
- Forgetting to register `shutdown(await app.listen(...))`.
|
|
191
298
|
|
|
@@ -194,6 +301,8 @@ import { MessageWorkerThread } from '@hile/message-worker-thread'
|
|
|
194
301
|
- Micro message files default-export `defineMicroMessage(...)` and receive `invocation.context`.
|
|
195
302
|
- RPC callers use `await app.call(..., { context })`.
|
|
196
303
|
- Streaming handlers are async generators.
|
|
304
|
+
- Request-stream handlers consume `input` and callers either pass `options.input` alongside metadata or pass a stream directly as `data`.
|
|
305
|
+
- Streamed request calls do not configure nonzero retries.
|
|
197
306
|
- Custom modem timeout values use the documented safe-integer range.
|
|
198
307
|
- Registry is started before application nodes need discovery.
|
|
199
308
|
- Micro apps use stable namespaces and advertise reachable hosts.
|
|
@@ -294,10 +403,13 @@ Use this recipe when services communicate over Hile registry-backed RPC.
|
|
|
294
403
|
3. Default-export `defineMicroMessage()` handlers and load them through `app.load()`.
|
|
295
404
|
4. Create context at ingress and call providers with `await app.call(namespace, url, data, { context })`.
|
|
296
405
|
5. Use `app.stream()` only for async-generator handlers.
|
|
406
|
+
6. For streamed request bodies, consume `input` in the `defineMicroMessage()` handler and pass the source through `options.input`; pass the stream as `data` only when no structured metadata is needed.
|
|
297
407
|
|
|
298
408
|
## Failure And Cleanup Behavior
|
|
299
409
|
|
|
300
410
|
- `Application.call()` may retry; side-effecting handlers need idempotency.
|
|
411
|
+
- A streamed request body is non-replayable. Its retry default is `0`, and an explicit nonzero retry count is rejected before discovery.
|
|
412
|
+
- Request input and response output have independent credit-based backpressure and may be active together.
|
|
301
413
|
- Registry disconnect triggers reconnect; apps re-declare topics and subscriptions.
|
|
302
414
|
- Circuit breaker excludes failing nodes for cooldown.
|
|
303
415
|
|
|
@@ -307,6 +419,7 @@ Use this recipe when services communicate over Hile registry-backed RPC.
|
|
|
307
419
|
- Provider namespace matches consumer call.
|
|
308
420
|
- Handlers default-export `defineMicroMessage()` and consume explicit invocation context when needed.
|
|
309
421
|
- Consumer code awaits `app.call(..., { context })` directly.
|
|
422
|
+
- Streamed request handlers consume `input: Readable`, preserve structured metadata in `data`, and do not enable retries.
|
|
310
423
|
|
|
311
424
|
# Runtime Dynamic Config
|
|
312
425
|
|
|
@@ -508,6 +621,7 @@ Use this recipe when `app.subscribe()` receives config updates that should rebui
|
|
|
508
621
|
- Do not assume `@hile/http` Zod validation mutates or coerces `ctx.query`, `ctx.params`, or `ctx.request.body`.
|
|
509
622
|
- Do not put reusable business logic only in controllers, pages, queue workers, or message handlers.
|
|
510
623
|
- Do not use old message examples that append a secondary response getter; current request APIs return promises directly.
|
|
624
|
+
- Do not invent service-specific HTTP-in-Micro envelopes or Base64 file bodies; use `@hile/http-over-micro` and its request/response streams.
|
|
511
625
|
- Do not claim exactly-once delivery or execution from Redis locks, queues, idempotency, or rate limits.
|
|
512
626
|
- Do not use queue `jobId` as the only side-effect idempotency boundary.
|
|
513
627
|
- Do not log the entire async context by default.
|
package/README.md
CHANGED
|
@@ -72,12 +72,14 @@ const result = await app.call('example.service', '/ping', { hello: 'world' }, {
|
|
|
72
72
|
## Boundaries
|
|
73
73
|
|
|
74
74
|
- Do not use `stream()` for normal single-result calls.
|
|
75
|
+
- Do not enable retries for a streamed request input. Input streams are consumed once and cannot be replayed safely.
|
|
75
76
|
- Do not rely on message IDs for business idempotency. They are transport IDs.
|
|
76
77
|
- Use `defineMicroMessage()` for Micro business handlers; reserve generic `defineMessage()` for transport-neutral loaders.
|
|
77
78
|
- Do not pass zero, fractional, non-finite, or oversized message timeouts. Explicit timeout values must be safe integers from `1` through `2_147_483_647` milliseconds.
|
|
78
79
|
|
|
79
80
|
- Appending a secondary response getter to `client.request('/x', data)`
|
|
80
81
|
- Returning a plain object from a handler called through `stream()`.
|
|
82
|
+
- Retrying a consumed request stream or hiding it inside a replay-unsafe factory.
|
|
81
83
|
- Using pub/sub as a durable queue.
|
|
82
84
|
- Forgetting to register `shutdown(await app.listen(...))`.
|
|
83
85
|
|
|
@@ -86,6 +88,8 @@ const result = await app.call('example.service', '/ping', { hello: 'world' }, {
|
|
|
86
88
|
- Micro message files default-export `defineMicroMessage(...)` and receive `invocation.context`.
|
|
87
89
|
- RPC callers use `await app.call(..., { context })`.
|
|
88
90
|
- Streaming handlers are async generators.
|
|
91
|
+
- Request-stream handlers consume `input` and callers either pass `options.input` alongside metadata or pass a stream directly as `data`.
|
|
92
|
+
- Streamed request calls do not configure nonzero retries.
|
|
89
93
|
- Custom modem timeout values use the documented safe-integer range.
|
|
90
94
|
- Registry is started before application nodes need discovery.
|
|
91
95
|
- Micro apps use stable namespaces and advertise reachable hosts.
|
package/dist/application.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ExecutionContext } from '@hile/context';
|
|
2
|
+
import { type MessageInput } from '@hile/message-modem';
|
|
2
3
|
import { Client, type ClientStreamOptions } from './client.js';
|
|
3
4
|
import { Server, type MicroServerProps } from './server.js';
|
|
4
5
|
import type { RegistryAddress, RegistryTopicSnapshot, RegistryTopicSnapshotsResult, RegistryTopicSummary } from './registry';
|
|
@@ -54,6 +55,7 @@ export type ApplicationCallOptions = {
|
|
|
54
55
|
timeout?: number;
|
|
55
56
|
retries?: number;
|
|
56
57
|
signal?: AbortSignal;
|
|
58
|
+
input?: MessageInput;
|
|
57
59
|
};
|
|
58
60
|
export type ApplicationStreamOptions = ClientStreamOptions & {
|
|
59
61
|
retries?: number;
|
package/dist/application.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MissingExecutionContextError, parseExecutionContext, } from '@hile/context';
|
|
2
|
+
import { isMessageInput, MessageInputError } from '@hile/message-modem';
|
|
2
3
|
import { Server } from './server.js';
|
|
3
4
|
var RegistryLookupStatus;
|
|
4
5
|
(function (RegistryLookupStatus) {
|
|
@@ -94,6 +95,21 @@ function resolveCircuitBreakerOptions(options) {
|
|
|
94
95
|
shouldRetry: options?.shouldRetry ?? DEFAULT_CIRCUIT_BREAKER.shouldRetry,
|
|
95
96
|
};
|
|
96
97
|
}
|
|
98
|
+
function resolveRequestRetries(data, input, retries) {
|
|
99
|
+
if (input !== undefined) {
|
|
100
|
+
if (!isMessageInput(input)) {
|
|
101
|
+
throw new TypeError('Micro request input must be an AsyncIterable, Uint8Array, or ArrayBuffer');
|
|
102
|
+
}
|
|
103
|
+
if (isMessageInput(data)) {
|
|
104
|
+
throw new TypeError('A micro request accepts only one request input stream');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const hasInput = input !== undefined || isMessageInput(data);
|
|
108
|
+
if (hasInput && retries !== undefined && retries !== 0) {
|
|
109
|
+
throw new TypeError('Streamed request input is non-replayable and requires retries: 0');
|
|
110
|
+
}
|
|
111
|
+
return retries ?? (hasInput ? 0 : 1);
|
|
112
|
+
}
|
|
97
113
|
export class Application extends Server {
|
|
98
114
|
registry;
|
|
99
115
|
reconnectTimeout;
|
|
@@ -514,6 +530,8 @@ export class Application extends Server {
|
|
|
514
530
|
};
|
|
515
531
|
}
|
|
516
532
|
shouldRecordCircuitFailure(err) {
|
|
533
|
+
if (err instanceof MessageInputError)
|
|
534
|
+
return false;
|
|
517
535
|
try {
|
|
518
536
|
return this._circuitBreaker.shouldRecordFailure(err);
|
|
519
537
|
}
|
|
@@ -756,7 +774,8 @@ export class Application extends Server {
|
|
|
756
774
|
if (!options?.context)
|
|
757
775
|
throw new MissingExecutionContextError(`micro call ${namespace}${url}`);
|
|
758
776
|
const context = parseExecutionContext(options.context);
|
|
759
|
-
const { timeout = this._requestTimeoutMs,
|
|
777
|
+
const { timeout = this._requestTimeoutMs, signal, input } = options;
|
|
778
|
+
const retries = resolveRequestRetries(data, input, options.retries);
|
|
760
779
|
let remainingRetries = retries;
|
|
761
780
|
let retrySourceError;
|
|
762
781
|
let hasRetrySourceError = false;
|
|
@@ -776,6 +795,7 @@ export class Application extends Server {
|
|
|
776
795
|
context,
|
|
777
796
|
timeout: timeout ?? this._requestTimeoutMs,
|
|
778
797
|
signal,
|
|
798
|
+
input,
|
|
779
799
|
});
|
|
780
800
|
this.recordSuccess(namespace, client.host, client.port, probe);
|
|
781
801
|
return result;
|
|
@@ -799,7 +819,8 @@ export class Application extends Server {
|
|
|
799
819
|
if (!options?.context)
|
|
800
820
|
throw new MissingExecutionContextError(`micro stream ${namespace}${url}`);
|
|
801
821
|
const context = parseExecutionContext(options.context);
|
|
802
|
-
const { signal,
|
|
822
|
+
const { signal, timeout, idleTimeout, window, input } = options;
|
|
823
|
+
const retries = resolveRequestRetries(data, input, options.retries);
|
|
803
824
|
let remainingRetries = retries;
|
|
804
825
|
let retrySourceError;
|
|
805
826
|
let hasRetrySourceError = false;
|
|
@@ -815,7 +836,14 @@ export class Application extends Server {
|
|
|
815
836
|
}
|
|
816
837
|
const { client, probe } = selected;
|
|
817
838
|
try {
|
|
818
|
-
const readable = client.stream(url, data, {
|
|
839
|
+
const readable = client.stream(url, data, {
|
|
840
|
+
context,
|
|
841
|
+
signal,
|
|
842
|
+
timeout,
|
|
843
|
+
idleTimeout,
|
|
844
|
+
window,
|
|
845
|
+
input,
|
|
846
|
+
});
|
|
819
847
|
return this.trackCircuitStream(namespace, client.host, client.port, probe, readable);
|
|
820
848
|
}
|
|
821
849
|
catch (err) {
|
package/dist/client.d.ts
CHANGED
|
@@ -3,16 +3,21 @@ import { type ExecutionContext } from '@hile/context';
|
|
|
3
3
|
import { Server } from './server.js';
|
|
4
4
|
import { WebSocket } from 'ws';
|
|
5
5
|
import { EventEmitter } from 'node:events';
|
|
6
|
+
import type { Readable } from 'node:stream';
|
|
7
|
+
import { type MessageInput } from '@hile/message-modem';
|
|
6
8
|
export interface ClientProps {
|
|
7
9
|
host: string;
|
|
8
10
|
port: number;
|
|
9
11
|
server: Server;
|
|
10
12
|
ws: WebSocket;
|
|
11
13
|
}
|
|
12
|
-
export interface
|
|
14
|
+
export interface ClientRequestOptions {
|
|
13
15
|
context: ExecutionContext;
|
|
14
16
|
signal?: AbortSignal;
|
|
15
17
|
timeout?: number;
|
|
18
|
+
input?: MessageInput;
|
|
19
|
+
}
|
|
20
|
+
export interface ClientStreamOptions extends ClientRequestOptions {
|
|
16
21
|
idleTimeout?: number;
|
|
17
22
|
window?: number;
|
|
18
23
|
}
|
|
@@ -38,12 +43,8 @@ export declare class Client extends MessageWs {
|
|
|
38
43
|
readonly events: EventEmitter<any>;
|
|
39
44
|
constructor(props: ClientProps);
|
|
40
45
|
private startHeartbeat;
|
|
41
|
-
protected exec(data: MicroMessage, signal?: AbortSignal): Promise<any>;
|
|
42
|
-
request<T = any>(url: string, data: any, options:
|
|
43
|
-
context: ExecutionContext;
|
|
44
|
-
timeout?: number;
|
|
45
|
-
signal?: AbortSignal;
|
|
46
|
-
}): Promise<T>;
|
|
46
|
+
protected exec(data: MicroMessage, signal?: AbortSignal, input?: Readable): Promise<any>;
|
|
47
|
+
request<T = any>(url: string, data: any, options: ClientRequestOptions): Promise<T>;
|
|
47
48
|
/** Framework-internal transport path. Business requests must use request() with context. */
|
|
48
49
|
requestControl<T = any>(url: string, data: any, options?: {
|
|
49
50
|
timeout?: number;
|
|
@@ -59,6 +60,6 @@ export declare class Client extends MessageWs {
|
|
|
59
60
|
timeout?: number;
|
|
60
61
|
signal?: AbortSignal;
|
|
61
62
|
}): void;
|
|
62
|
-
stream(url: string, data: any, options: ClientStreamOptions):
|
|
63
|
+
stream(url: string, data: any, options: ClientStreamOptions): Readable;
|
|
63
64
|
dispose(): void;
|
|
64
65
|
}
|
package/dist/client.js
CHANGED
|
@@ -2,6 +2,7 @@ import { MessageWs } from "@hile/message-ws";
|
|
|
2
2
|
import { createInvocationContext, MissingExecutionContextError, parseExecutionContext, } from '@hile/context';
|
|
3
3
|
import { WebSocket } from 'ws';
|
|
4
4
|
import { EventEmitter } from 'node:events';
|
|
5
|
+
import { isMessageInput } from '@hile/message-modem';
|
|
5
6
|
const FRAMEWORK_CONTROL_ROUTES = new Set([
|
|
6
7
|
'/-/config/get',
|
|
7
8
|
'/-/configs',
|
|
@@ -40,6 +41,15 @@ function getEnvelopeContext(data) {
|
|
|
40
41
|
const context = data.metadata?.context;
|
|
41
42
|
return context === undefined ? undefined : parseExecutionContext(context);
|
|
42
43
|
}
|
|
44
|
+
function splitMessageInput(data, input) {
|
|
45
|
+
if (input !== undefined) {
|
|
46
|
+
if (isMessageInput(data)) {
|
|
47
|
+
throw new TypeError('A micro request accepts only one request input stream');
|
|
48
|
+
}
|
|
49
|
+
return { data, input };
|
|
50
|
+
}
|
|
51
|
+
return isMessageInput(data) ? { data: undefined, input: data } : { data };
|
|
52
|
+
}
|
|
43
53
|
export class Client extends MessageWs {
|
|
44
54
|
server;
|
|
45
55
|
socket;
|
|
@@ -80,7 +90,7 @@ export class Client extends MessageWs {
|
|
|
80
90
|
}
|
|
81
91
|
}, checkInterval);
|
|
82
92
|
}
|
|
83
|
-
async exec(data, signal) {
|
|
93
|
+
async exec(data, signal, input) {
|
|
84
94
|
if (data.url === '/-/heartbeat') {
|
|
85
95
|
this.lastHeartbeat = Date.now();
|
|
86
96
|
return;
|
|
@@ -99,6 +109,7 @@ export class Client extends MessageWs {
|
|
|
99
109
|
client: this,
|
|
100
110
|
metadata: data.metadata,
|
|
101
111
|
signal,
|
|
112
|
+
input,
|
|
102
113
|
invocation,
|
|
103
114
|
});
|
|
104
115
|
}
|
|
@@ -107,8 +118,12 @@ export class Client extends MessageWs {
|
|
|
107
118
|
throw new Error('Client is not online');
|
|
108
119
|
if (!options?.context)
|
|
109
120
|
throw new MissingExecutionContextError(`micro client request ${url}`);
|
|
110
|
-
const { context, ...transport } = options;
|
|
111
|
-
|
|
121
|
+
const { context, input, ...transport } = options;
|
|
122
|
+
const request = splitMessageInput(data, input);
|
|
123
|
+
return this._send(createEnvelope(url, request.data, context), {
|
|
124
|
+
...transport,
|
|
125
|
+
input: request.input,
|
|
126
|
+
});
|
|
112
127
|
}
|
|
113
128
|
/** Framework-internal transport path. Business requests must use request() with context. */
|
|
114
129
|
requestControl(url, data, options) {
|
|
@@ -135,8 +150,12 @@ export class Client extends MessageWs {
|
|
|
135
150
|
throw new Error('Client is not online');
|
|
136
151
|
if (!options?.context)
|
|
137
152
|
throw new MissingExecutionContextError(`micro client stream ${url}`);
|
|
138
|
-
const { context, ...transport } = options;
|
|
139
|
-
|
|
153
|
+
const { context, input, ...transport } = options;
|
|
154
|
+
const request = splitMessageInput(data, input);
|
|
155
|
+
return this._stream(createEnvelope(url, request.data, context), {
|
|
156
|
+
...transport,
|
|
157
|
+
input: request.input,
|
|
158
|
+
});
|
|
140
159
|
}
|
|
141
160
|
dispose() {
|
|
142
161
|
if (this.heartbeatTimer)
|
package/dist/message.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { InvocationContext } from '@hile/context';
|
|
2
|
+
import type { Readable } from 'node:stream';
|
|
2
3
|
import { type MessageFunction, type MessageRegisterProps } from '@hile/message-loader';
|
|
3
4
|
import type { Client, MicroMessageMetadata } from './client';
|
|
4
5
|
export type MicroMessageHandlerExtras = {
|
|
5
6
|
client: Client;
|
|
6
7
|
metadata?: MicroMessageMetadata;
|
|
7
8
|
signal?: AbortSignal;
|
|
9
|
+
input?: Readable;
|
|
8
10
|
invocation: InvocationContext;
|
|
9
11
|
};
|
|
10
12
|
export type MicroMessageFunction<T = any> = MessageFunction<T, MicroMessageHandlerExtras>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hile/micro",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -24,13 +24,14 @@
|
|
|
24
24
|
"vitest": "^4.0.18"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@hile/context": "^4.0.
|
|
28
|
-
"@hile/logger": "^4.0.
|
|
29
|
-
"@hile/message-loader": "^4.0.
|
|
30
|
-
"@hile/message-
|
|
27
|
+
"@hile/context": "^4.0.3",
|
|
28
|
+
"@hile/logger": "^4.0.2",
|
|
29
|
+
"@hile/message-loader": "^4.0.5",
|
|
30
|
+
"@hile/message-modem": "^4.0.4",
|
|
31
|
+
"@hile/message-ws": "^4.0.4",
|
|
31
32
|
"internal-ip": "^9.0.0",
|
|
32
33
|
"ws": "^8.21.0",
|
|
33
34
|
"yaml": "^2.9.0"
|
|
34
35
|
},
|
|
35
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "c57b2c5a3c017ae56d4a862a089b2af01368c3ba"
|
|
36
37
|
}
|