@hile/micro-dynamic-configs 4.0.4 → 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 +114 -4
- package/README.md +4 -0
- package/package.json +4 -4
package/AI.md
CHANGED
|
@@ -105,6 +105,42 @@ for await (const chunk of stream) {
|
|
|
105
105
|
}
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
Streaming request body with a normal response:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
// src/messages/upload.msg.ts
|
|
112
|
+
import { defineMicroMessage } from '@hile/micro'
|
|
113
|
+
|
|
114
|
+
export default defineMicroMessage(async ({ data, input, invocation }) => {
|
|
115
|
+
if (!input) throw new Error('upload body is required')
|
|
116
|
+
|
|
117
|
+
let bytes = 0
|
|
118
|
+
for await (const chunk of input) bytes += Buffer.byteLength(chunk)
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
filename: data.filename,
|
|
122
|
+
bytes,
|
|
123
|
+
requestId: invocation.context.values.requestId,
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import { createReadStream } from 'node:fs'
|
|
130
|
+
|
|
131
|
+
const result = await app.call(
|
|
132
|
+
'example.service',
|
|
133
|
+
'/upload',
|
|
134
|
+
{ filename: 'archive.tar' },
|
|
135
|
+
{ context, input: createReadStream('/tmp/archive.tar') },
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
If no structured metadata is needed, pass an `AsyncIterable`, `Uint8Array`, or
|
|
140
|
+
`ArrayBuffer` directly as `data`; Hile sends it as the request input stream and
|
|
141
|
+
the handler receives `data === undefined`. `app.stream()` accepts the same
|
|
142
|
+
request input forms when both request and response need to stream.
|
|
143
|
+
|
|
108
144
|
Custom WebSocket modem:
|
|
109
145
|
|
|
110
146
|
```ts
|
|
@@ -128,6 +164,68 @@ class RpcWs extends MessageWs {
|
|
|
128
164
|
|
|
129
165
|
Notice that `request()` returns a `Promise<T>`. Await it directly.
|
|
130
166
|
|
|
167
|
+
## Stream Wire Model
|
|
168
|
+
|
|
169
|
+
One request ID owns the structured request and both optional stream directions:
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
type RequestFrame = {
|
|
173
|
+
id: number
|
|
174
|
+
mode: MESSAGE_MODEM_TYPE.REQUEST
|
|
175
|
+
twoway: boolean
|
|
176
|
+
data?: unknown
|
|
177
|
+
streams?: {
|
|
178
|
+
input?: true
|
|
179
|
+
output?: { window?: number }
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
type StreamDataFrame = {
|
|
184
|
+
id: number
|
|
185
|
+
mode: MESSAGE_MODEM_TYPE.STREAM_DATA
|
|
186
|
+
twoway: false
|
|
187
|
+
data: {
|
|
188
|
+
direction: 'input' | 'output'
|
|
189
|
+
seq: number
|
|
190
|
+
payload?: unknown
|
|
191
|
+
final: boolean
|
|
192
|
+
status?: string | number
|
|
193
|
+
message?: string
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
type StreamCreditFrame = {
|
|
198
|
+
id: number
|
|
199
|
+
mode: MESSAGE_MODEM_TYPE.STREAM_CREDIT
|
|
200
|
+
twoway: false
|
|
201
|
+
data: {
|
|
202
|
+
direction: 'input' | 'output'
|
|
203
|
+
seq: number
|
|
204
|
+
window?: number
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
type StreamCancelFrame = {
|
|
209
|
+
id: number
|
|
210
|
+
mode: MESSAGE_MODEM_TYPE.STREAM_CANCEL
|
|
211
|
+
twoway: false
|
|
212
|
+
data: {
|
|
213
|
+
direction: 'input' | 'output'
|
|
214
|
+
status?: string | number
|
|
215
|
+
message?: string
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`data` is the structured message payload; stream chunks never get embedded in
|
|
221
|
+
it. The request frame declares the active directions, and every later stream
|
|
222
|
+
frame reuses the request ID. Input and output sequence numbers, credits, and
|
|
223
|
+
cancellation are independent.
|
|
224
|
+
|
|
225
|
+
A non-final stream frame must carry a payload other than `null` or `undefined`.
|
|
226
|
+
Node `Readable` reserves those values for its own end/no-op semantics, so Hile
|
|
227
|
+
rejects them instead of silently losing a credit or ending a stream early.
|
|
228
|
+
|
|
131
229
|
## Use When
|
|
132
230
|
|
|
133
231
|
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.
|
|
@@ -135,6 +233,7 @@ Use the message packages for request/response messaging over WebSocket, process
|
|
|
135
233
|
## Do Not Use When
|
|
136
234
|
|
|
137
235
|
- Do not use `stream()` for normal single-result calls.
|
|
236
|
+
- Do not enable retries for a streamed request input. Input streams are consumed once and cannot be replayed safely.
|
|
138
237
|
- Do not rely on message IDs for business idempotency. They are transport IDs.
|
|
139
238
|
- Use `defineMicroMessage()` for Micro business handlers; reserve generic `defineMessage()` for transport-neutral loaders.
|
|
140
239
|
- 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.
|
|
@@ -169,14 +268,21 @@ import { MessageWorkerThread } from '@hile/message-worker-thread'
|
|
|
169
268
|
- `MessageLoader` maps `*.msg.*` files to routes using `@hile/loader`.
|
|
170
269
|
- `MessageLoader.dispatch(path, data, extras?)` invokes the matched handler.
|
|
171
270
|
- `MessageModem._send()` returns a `Promise`.
|
|
271
|
+
- `_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.
|
|
172
272
|
- `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.
|
|
173
273
|
- `MessageModem._stream()` returns a Node `Readable` in object mode.
|
|
174
|
-
- 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`.
|
|
274
|
+
- 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`.
|
|
175
275
|
- 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.
|
|
176
|
-
-
|
|
276
|
+
- 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.
|
|
277
|
+
- 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.
|
|
278
|
+
- `@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.
|
|
279
|
+
- `@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.
|
|
177
280
|
- A stream request requires `exec()` to return an async iterable.
|
|
178
|
-
- `
|
|
179
|
-
- `Application.
|
|
281
|
+
- `defineMicroMessage()` handlers receive request streams as `input: Readable | undefined`, separately from structured `data` and `invocation`.
|
|
282
|
+
- `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.
|
|
283
|
+
- `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.
|
|
284
|
+
- `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.
|
|
285
|
+
- 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.
|
|
180
286
|
- `Application.publish(topic, payload)` returns an object with `update()` and `unpublish()`.
|
|
181
287
|
- `Application.subscribe(topic, callback)` returns an unsubscribe function.
|
|
182
288
|
- `Registry` stores service addresses and retained config/topic state under `~/.registry`.
|
|
@@ -185,6 +291,7 @@ import { MessageWorkerThread } from '@hile/message-worker-thread'
|
|
|
185
291
|
|
|
186
292
|
- Appending a secondary response getter to `client.request('/x', data)`
|
|
187
293
|
- Returning a plain object from a handler called through `stream()`.
|
|
294
|
+
- Retrying a consumed request stream or hiding it inside a replay-unsafe factory.
|
|
188
295
|
- Using pub/sub as a durable queue.
|
|
189
296
|
- Forgetting to register `shutdown(await app.listen(...))`.
|
|
190
297
|
|
|
@@ -193,6 +300,8 @@ import { MessageWorkerThread } from '@hile/message-worker-thread'
|
|
|
193
300
|
- Micro message files default-export `defineMicroMessage(...)` and receive `invocation.context`.
|
|
194
301
|
- RPC callers use `await app.call(..., { context })`.
|
|
195
302
|
- Streaming handlers are async generators.
|
|
303
|
+
- Request-stream handlers consume `input` and callers either pass `options.input` alongside metadata or pass a stream directly as `data`.
|
|
304
|
+
- Streamed request calls do not configure nonzero retries.
|
|
196
305
|
- Custom modem timeout values use the documented safe-integer range.
|
|
197
306
|
- Registry is started before application nodes need discovery.
|
|
198
307
|
- Micro apps use stable namespaces and advertise reachable hosts.
|
|
@@ -287,6 +396,7 @@ Use this recipe when config changes should persist to Redis and be pushed throug
|
|
|
287
396
|
- Do not assume `@hile/http` Zod validation mutates or coerces `ctx.query`, `ctx.params`, or `ctx.request.body`.
|
|
288
397
|
- Do not put reusable business logic only in controllers, pages, queue workers, or message handlers.
|
|
289
398
|
- Do not use old message examples that append a secondary response getter; current request APIs return promises directly.
|
|
399
|
+
- Do not invent service-specific HTTP-in-Micro envelopes or Base64 file bodies; use `@hile/http-over-micro` and its request/response streams.
|
|
290
400
|
- Do not claim exactly-once delivery or execution from Redis locks, queues, idempotency, or rate limits.
|
|
291
401
|
- Do not use queue `jobId` as the only side-effect idempotency boundary.
|
|
292
402
|
- 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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hile/micro-dynamic-configs",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"vitest": "^4.0.18"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@hile/ioredis": "^4.0.
|
|
27
|
-
"@hile/micro": "^4.0.
|
|
26
|
+
"@hile/ioredis": "^4.0.2",
|
|
27
|
+
"@hile/micro": "^4.0.5",
|
|
28
28
|
"ioredis": "^5.11.0",
|
|
29
29
|
"zod": "^4.4.3"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "c57b2c5a3c017ae56d4a862a089b2af01368c3ba"
|
|
32
32
|
}
|