@hile/message-worker-thread 2.1.1 → 3.0.0

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 ADDED
@@ -0,0 +1,295 @@
1
+ # AI Guide For @hile/message-worker-thread
2
+
3
+
4
+
5
+ <!-- Generated by scripts/build-ai-context.mjs from docs/ai. Do not edit by hand. -->
6
+
7
+
8
+
9
+ Purpose: Use MessageModem over worker_threads Worker or MessagePort transports.
10
+
11
+
12
+
13
+ Use this file when an AI agent installs the npm package and needs package-local examples, package selection rules, boundaries, and verification steps.
14
+
15
+
16
+
17
+ ## Package Selection
18
+
19
+
20
+
21
+ Use `@hile/message-worker-thread` for: Use MessageModem over worker_threads Worker or MessagePort transports.
22
+
23
+
24
+
25
+ # Messaging And Microservices
26
+
27
+ Packages: `@hile/message-modem`, `@hile/message-ws`, `@hile/message-ipc`, `@hile/message-worker-thread`, `@hile/message-loader`, `@hile/micro`, `@hile/micro-dynamic-configs`.
28
+
29
+ ## Copy-Paste Example
30
+
31
+ Message handler file:
32
+
33
+ ```ts
34
+ // src/messages/ping.msg.ts
35
+ import { defineMessage } from '@hile/message-loader'
36
+
37
+ export default defineMessage(async ({ data, params }) => {
38
+ return {
39
+ type: 'pong',
40
+ data,
41
+ params,
42
+ timestamp: Date.now(),
43
+ }
44
+ })
45
+ ```
46
+
47
+ Microservice boot file:
48
+
49
+ ```ts
50
+ // src/services/app.boot.ts
51
+ import { defineService } from '@hile/core'
52
+ import { Application } from '@hile/micro'
53
+
54
+ export default defineService('micro.app', async (shutdown) => {
55
+ const app = new Application({
56
+ namespace: process.env.MICRO_NAMESPACE ?? 'example.service',
57
+ registry: {
58
+ host: process.env.REGISTRY_HOST ?? '127.0.0.1',
59
+ port: Number(process.env.REGISTRY_PORT ?? 9876),
60
+ },
61
+ advertiseHost: process.env.HILE_ADVERTISE_HOST ?? '127.0.0.1',
62
+ })
63
+
64
+ await app.load(new URL('../messages', import.meta.url).pathname)
65
+ const stop = await app.listen(Number(process.env.MICRO_PORT ?? 0))
66
+ shutdown(stop)
67
+ return app
68
+ })
69
+ ```
70
+
71
+ Caller:
72
+
73
+ ```ts
74
+ const result = await app.call('example.service', '/ping', { hello: 'world' })
75
+ ```
76
+
77
+ ## More Examples
78
+
79
+ Streaming handler:
80
+
81
+ ```ts
82
+ // src/messages/events.msg.ts
83
+ import { defineMessage } from '@hile/message-loader'
84
+
85
+ export default defineMessage(async function* () {
86
+ for (let i = 0; i < 3; i++) {
87
+ yield { seq: i }
88
+ }
89
+ })
90
+ ```
91
+
92
+ Streaming caller:
93
+
94
+ ```ts
95
+ const stream = await app.stream('example.service', '/events', {})
96
+ for await (const chunk of stream) {
97
+ console.log(chunk)
98
+ }
99
+ ```
100
+
101
+ Custom WebSocket modem:
102
+
103
+ ```ts
104
+ import { MessageWs } from '@hile/message-ws'
105
+ import type WebSocket from 'ws'
106
+
107
+ class RpcWs extends MessageWs {
108
+ constructor(ws: WebSocket, private readonly dispatch: (url: string, data: unknown) => Promise<unknown>) {
109
+ super(ws)
110
+ }
111
+
112
+ protected exec(data: { url: string; data: unknown }) {
113
+ return this.dispatch(data.url, data.data)
114
+ }
115
+
116
+ request<T>(url: string, data: unknown, timeout = 30_000) {
117
+ return this._send<T>({ url, data }, { timeout })
118
+ }
119
+ }
120
+ ```
121
+
122
+ Notice that `request()` returns a `Promise<T>`. Await it directly.
123
+
124
+ ## Use When
125
+
126
+ 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.
127
+
128
+ ## Do Not Use When
129
+
130
+ - Do not use `stream()` for normal single-result calls.
131
+ - Do not rely on message IDs for business idempotency. They are transport IDs.
132
+ - Do not bypass `defineMessage()` for file-loaded handlers.
133
+
134
+ ## Install
135
+
136
+ ```bash
137
+ pnpm add @hile/micro @hile/message-loader @hile/message-ws
138
+ ```
139
+
140
+ Use transport-specific packages only when you need to build custom IPC or worker-thread bridges.
141
+
142
+ ## Imports
143
+
144
+ ```ts
145
+ import { defineMessage, MessageLoader } from '@hile/message-loader'
146
+ import { Application, Registry, Server } from '@hile/micro'
147
+ import { MessageWs } from '@hile/message-ws'
148
+ import { MessageIpc } from '@hile/message-ipc'
149
+ import { MessageWorkerThread } from '@hile/message-worker-thread'
150
+ ```
151
+
152
+ ## Compose With
153
+
154
+ - `@hile/context` propagates context in micro message metadata.
155
+ - `@hile/redis-idempotency` protects retryable side effects in message handlers.
156
+ - `@hile/redis-stream-queue` is better for durable background jobs.
157
+
158
+ ## Runtime And Lifecycle Notes
159
+
160
+ - `MessageLoader` maps `*.msg.*` files to routes using `@hile/loader`.
161
+ - `MessageLoader.dispatch(path, data, extras?)` invokes the matched handler.
162
+ - `MessageModem._send()` returns a `Promise`.
163
+ - `MessageModem._stream()` returns a Node `Readable` in object mode.
164
+ - A stream request requires `exec()` to return an async iterable.
165
+ - `Application.call(namespace, url, data, options?)` returns a promise.
166
+ - `Application.stream(namespace, url, data, options?)` returns a readable stream.
167
+ - `Application.publish(topic, payload)` returns an object with `update()` and `unpublish()`.
168
+ - `Application.subscribe(topic, callback)` returns an unsubscribe function.
169
+ - `Registry` stores service addresses and retained config/topic state under `~/.registry`.
170
+
171
+ ## Anti-Patterns
172
+
173
+ - Appending a secondary response getter to `client.request('/x', data)`
174
+ - Returning a plain object from a handler called through `stream()`.
175
+ - Using pub/sub as a durable queue.
176
+ - Forgetting to register `shutdown(await app.listen(...))`.
177
+
178
+ ## Verification Checklist
179
+
180
+ - Message files default-export `defineMessage(...)`.
181
+ - RPC callers use `await app.call(...)`.
182
+ - Streaming handlers are async generators.
183
+ - Registry is started before application nodes need discovery.
184
+ - Micro apps use stable namespaces and advertise reachable hosts.
185
+
186
+
187
+
188
+ # Related Recipes
189
+
190
+
191
+
192
+ # Micro RPC With Message Loader
193
+
194
+ ## Complete Example
195
+
196
+ Provider handler:
197
+
198
+ ```ts
199
+ // src/messages/charge.msg.ts
200
+ import { defineMessage } from '@hile/message-loader'
201
+
202
+ export default defineMessage(async ({ data }) => {
203
+ return { charged: true, input: data }
204
+ })
205
+ ```
206
+
207
+ Provider boot:
208
+
209
+ ```ts
210
+ // src/services/app.boot.ts
211
+ import { defineService } from '@hile/core'
212
+ import { Application } from '@hile/micro'
213
+
214
+ export default defineService('billing.micro', async (shutdown) => {
215
+ const app = new Application({
216
+ namespace: 'billing',
217
+ registry: { host: '127.0.0.1', port: 9876 },
218
+ advertiseHost: '127.0.0.1',
219
+ })
220
+
221
+ await app.load(new URL('../messages', import.meta.url).pathname)
222
+ const stop = await app.listen(9101)
223
+ shutdown(stop)
224
+ return app
225
+ })
226
+ ```
227
+
228
+ Consumer:
229
+
230
+ ```ts
231
+ const result = await app.call('billing', '/charge', {
232
+ tenantId: 't1',
233
+ amount: 100,
234
+ })
235
+ ```
236
+
237
+ ## File Layout
238
+
239
+ ```text
240
+ provider/
241
+ src/messages/charge.msg.ts
242
+ src/services/app.boot.ts
243
+ consumer/
244
+ src/models/payments/pay.model.ts
245
+ ```
246
+
247
+ ## User Intent
248
+
249
+ Use this recipe when services communicate over Hile registry-backed RPC.
250
+
251
+ ## Packages To Use
252
+
253
+ - `@hile/micro`
254
+ - `@hile/message-loader`
255
+ - `@hile/context` when context must cross service boundaries
256
+ - `@hile/redis-idempotency` for retryable side effects
257
+
258
+ ## Implementation Steps
259
+
260
+ 1. Start a Registry with `hile registry`.
261
+ 2. Start providers with stable namespaces.
262
+ 3. Load `*.msg.ts` handlers through `app.load()`.
263
+ 4. Call providers with `await app.call(namespace, url, data)`.
264
+ 5. Use `app.stream()` only for async-generator handlers.
265
+
266
+ ## Failure And Cleanup Behavior
267
+
268
+ - `Application.call()` may retry; side-effecting handlers need idempotency.
269
+ - Registry disconnect triggers reconnect; apps re-declare topics and subscriptions.
270
+ - Circuit breaker excludes failing nodes for cooldown.
271
+
272
+ ## Verification Checklist
273
+
274
+ - Registry is reachable.
275
+ - Provider namespace matches consumer call.
276
+ - Handlers default-export `defineMessage()`.
277
+ - Consumer code awaits `app.call(...)` directly.
278
+
279
+
280
+
281
+ # Global Guardrails
282
+
283
+
284
+
285
+ ## Never Generate These Patterns
286
+
287
+ - Do not call `loadService()` at module top level; it starts resources during import.
288
+ - Do not default-export plain functions from `*.boot.*` files; `hile start` expects a Hile service.
289
+ - Do not set `ctx.body` and also return a controller value.
290
+ - Do not assume `@hile/http` Zod validation mutates or coerces `ctx.query`, `ctx.params`, or `ctx.request.body`.
291
+ - Do not put reusable business logic only in controllers, pages, queue workers, or message handlers.
292
+ - Do not use old message examples that append a secondary response getter; current request APIs return promises directly.
293
+ - Do not claim exactly-once delivery or execution from Redis locks, queues, idempotency, or rate limits.
294
+ - Do not use queue `jobId` as the only side-effect idempotency boundary.
295
+ - Do not log the entire async context by default.
package/README.md CHANGED
@@ -1,139 +1,90 @@
1
1
  # @hile/message-worker-thread
2
2
 
3
- 基于 `@hile/message-modem` Node.js Worker Threads 通信抽象实现。让主线程与 Worker 线程之间的请求/响应通信像调用函数一样简单。
3
+ <!-- Generated by scripts/build-ai-context.mjs from docs/ai. Do not edit by hand. -->
4
4
 
5
- ## 安装
5
+ Use MessageModem over worker_threads Worker or MessagePort transports.
6
6
 
7
- ```bash
8
- pnpm add @hile/message-worker-thread
9
- ```
7
+ This README is intentionally short and example-first. The complete AI-facing guide ships in `AI.md` in this package.
10
8
 
11
- ## 核心特性
12
-
13
- - **双端支持** — 主线程传入 `Worker` 或 `MessagePort`,Worker 线程端零配置
14
- - **继承模式** — 继承 `MessageWorkerThread` 并实现 `exec` 方法
15
- - **请求/响应** — 继承 `MessageModem` 的全部能力
16
- - **超时控制** — 默认 30 秒,可按请求自定义
17
- - **主动中止** — `abort()` 取消等待并通知对端
18
- - **错误传播** — `Exception` 带 status 透传,普通 Error 映射为 500
19
- - **资源清理** — `dispose()` 移除监听,避免内存泄漏
20
-
21
- ## 快速开始
22
-
23
- ### 第一步:定义子类
24
-
25
- ```typescript
26
- import { MessageWorkerThread } from '@hile/message-worker-thread';
27
- import { Exception } from '@hile/message-modem';
28
-
29
- class ComputeWorker extends MessageWorkerThread {
30
- protected async exec(data: any): Promise<any> {
31
- switch (data?.action) {
32
- case 'compute':
33
- return data.value * 2;
34
- case 'restricted':
35
- throw new Exception(403, 'not allowed');
36
- default:
37
- return data;
38
- }
39
- }
40
-
41
- public request<T = any>(data: T, timeout?: number) {
42
- return this._send(data, timeout);
43
- }
44
- }
45
- ```
9
+ ## When To Use
46
10
 
47
- ### 第二步:主线程
11
+ 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.
48
12
 
49
- ```typescript
50
- import { Worker } from 'node:worker_threads';
13
+ ## Install
51
14
 
52
- class MainThread extends MessageWorkerThread {
53
- protected async exec(data: any): Promise<any> {
54
- return { reply: 'from main', query: data };
55
- }
15
+ ```bash
16
+ pnpm add @hile/message-worker-thread
17
+ ```
56
18
 
57
- public request<T = any>(data: T, timeout?: number) {
58
- return this._send(data, timeout);
59
- }
60
- }
19
+ ## Copy-Paste Example
61
20
 
62
- const worker = new Worker('./worker.js');
63
- const wt = new MainThread(worker);
21
+ Message handler file:
64
22
 
65
- const result = await wt.request({ action: 'compute', value: 42 }).response();
66
- console.log(result); // 84
23
+ ```ts
24
+ // src/messages/ping.msg.ts
25
+ import { defineMessage } from '@hile/message-loader'
67
26
 
68
- wt.dispose();
69
- await worker.terminate();
27
+ export default defineMessage(async ({ data, params }) => {
28
+ return {
29
+ type: 'pong',
30
+ data,
31
+ params,
32
+ timestamp: Date.now(),
33
+ }
34
+ })
70
35
  ```
71
36
 
72
- ### 第三步:Worker 线程(worker.js)
73
-
74
- ```typescript
75
- const wt = new ComputeWorker(); // 无参数 → 自动使用 parentPort
37
+ Microservice boot file:
38
+
39
+ ```ts
40
+ // src/services/app.boot.ts
41
+ import { defineService } from '@hile/core'
42
+ import { Application } from '@hile/micro'
43
+
44
+ export default defineService('micro.app', async (shutdown) => {
45
+ const app = new Application({
46
+ namespace: process.env.MICRO_NAMESPACE ?? 'example.service',
47
+ registry: {
48
+ host: process.env.REGISTRY_HOST ?? '127.0.0.1',
49
+ port: Number(process.env.REGISTRY_PORT ?? 9876),
50
+ },
51
+ advertiseHost: process.env.HILE_ADVERTISE_HOST ?? '127.0.0.1',
52
+ })
53
+
54
+ await app.load(new URL('../messages', import.meta.url).pathname)
55
+ const stop = await app.listen(Number(process.env.MICRO_PORT ?? 0))
56
+ shutdown(stop)
57
+ return app
58
+ })
76
59
  ```
77
60
 
78
- ## API
79
-
80
- ### `MessageWorkerThread`(抽象类)
81
-
82
- | 方法 | 签名 | 说明 |
83
- |------|------|------|
84
- | `constructor` | `new SubClass(port?: Worker \| MessagePort)` | 主线程传 Worker/MessagePort;Worker 线程不传参数 |
85
- | `exec` | `protected abstract exec(data: any): Promise<any>` | 子类实现:处理对端请求 |
86
- | `_send` | `protected _send<T>(data: T, timeout?: number)` | 发送双向请求(`twoway: true`),返回 `{ abort, response }`。子类自行暴露为 public |
87
- | `_push` | `protected _push<T>(data: T, timeout?: number)` | 发送单向推送(`twoway: false`),接收方不回复 RESPONSE |
88
- | `dispose` | `dispose(): void` | 移除消息监听,释放资源 |
89
-
90
- ### `_send` 返回值
91
-
92
- | 属性 | 类型 | 说明 |
93
- |------|------|------|
94
- | `abort` | `() => void` | 中止请求 |
95
- | `response` | `<U>() => Promise<U>` | 等待对端响应 |
61
+ Caller:
96
62
 
97
- ## 也可以用 MessageChannel
98
-
99
- ```typescript
100
- import { MessageChannel } from 'node:worker_threads';
101
-
102
- const { port1, port2 } = new MessageChannel();
103
- const side1 = new MySide(port1);
104
- const side2 = new MySide(port2);
105
-
106
- const result = await side1.request('hello').response();
63
+ ```ts
64
+ const result = await app.call('example.service', '/ping', { hello: 'world' })
107
65
  ```
108
66
 
109
- ## 超时与中止
67
+ ## Boundaries
110
68
 
111
- ```typescript
112
- import { AbortException, Exception } from '@hile/message-modem';
69
+ - Do not use `stream()` for normal single-result calls.
70
+ - Do not rely on message IDs for business idempotency. They are transport IDs.
71
+ - Do not bypass `defineMessage()` for file-loaded handlers.
113
72
 
114
- // 5 秒超时
115
- const result = await wt.request(data, 5000).response();
116
-
117
- // 主动中止
118
- const req = wt.request(data);
119
- setTimeout(() => req.abort(), 3000);
120
- try {
121
- await req.response();
122
- } catch (e) {
123
- if (e instanceof AbortException) {
124
- console.log('请求被中止或超时');
125
- } else if (e instanceof Exception) {
126
- console.log(`远端错误 [${e.status}]: ${e.message}`);
127
- }
128
- }
129
- ```
73
+ - Appending a secondary response getter to `client.request('/x', data)`
74
+ - Returning a plain object from a handler called through `stream()`.
75
+ - Using pub/sub as a durable queue.
76
+ - Forgetting to register `shutdown(await app.listen(...))`.
130
77
 
131
- ## 注意事项
78
+ ## Verify
132
79
 
133
- - `MessageWorkerThread` **抽象类**,不能直接实例化
134
- - Worker 线程端不传参数时,必须在 Worker 线程内运行(`parentPort` 可用)
135
- - 使用完毕后务必 `dispose()` + `worker.terminate()`
80
+ - Message files default-export `defineMessage(...)`.
81
+ - RPC callers use `await app.call(...)`.
82
+ - Streaming handlers are async generators.
83
+ - Registry is started before application nodes need discovery.
84
+ - Micro apps use stable namespaces and advertise reachable hosts.
136
85
 
137
- ## License
86
+ ## More Context
138
87
 
139
- MIT
88
+ - `AI.md` in this package: full package-local AI guide.
89
+ - Root `llms-full.txt`: full monorepo AI context.
90
+ - Root `references/`: source files copied from `docs/ai`.
package/dist/index.d.ts CHANGED
@@ -17,7 +17,7 @@ import { type Worker, type MessagePort } from 'node:worker_threads';
17
17
  * // 主线程
18
18
  * const worker = new Worker('./worker.js');
19
19
  * const wt = new MyWorkerThread(worker);
20
- * const res = await wt.request('hello').response();
20
+ * const res = await wt.request('hello');
21
21
  * wt.dispose();
22
22
  * await worker.terminate();
23
23
  *
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ import { parentPort } from 'node:worker_threads';
17
17
  * // 主线程
18
18
  * const worker = new Worker('./worker.js');
19
19
  * const wt = new MyWorkerThread(worker);
20
- * const res = await wt.request('hello').response();
20
+ * const res = await wt.request('hello');
21
21
  * wt.dispose();
22
22
  * await worker.terminate();
23
23
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hile/message-worker-thread",
3
- "version": "2.1.1",
3
+ "version": "3.0.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -11,7 +11,7 @@
11
11
  "files": [
12
12
  "dist",
13
13
  "README.md",
14
- "SKILL.md"
14
+ "AI.md"
15
15
  ],
16
16
  "license": "MIT",
17
17
  "publishConfig": {
@@ -22,7 +22,7 @@
22
22
  "vitest": "^4.0.18"
23
23
  },
24
24
  "dependencies": {
25
- "@hile/message-modem": "^2.1.1"
25
+ "@hile/message-modem": "^3.0.0"
26
26
  },
27
- "gitHead": "7903ae989bd001d1ed1437cb90c9e828a1909061"
27
+ "gitHead": "0985b6f8abc1f4de0a36324063585fdc3ac1375b"
28
28
  }