@procwire/client 1.0.0 → 1.0.1
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/LICENSE +21 -0
- package/README.md +246 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +39 -17
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sebastian Webdev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# @procwire/client
|
|
2
|
+
|
|
3
|
+
Child-side API for Procwire IPC.
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
- **Client** - Fluent builder for registering handlers
|
|
8
|
+
- **RequestContext** - `respond`, `ack`, `chunk`, `end`, `error`
|
|
9
|
+
- **Event emission** to parent process
|
|
10
|
+
- **Cancellation** via `ctx.aborted` and `ctx.onAbort()`
|
|
11
|
+
- **Async response methods** - backpressure-safe
|
|
12
|
+
- **~2.5 GB/s throughput** on named pipes
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @procwire/client
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Requirements:** Node.js >= 22
|
|
21
|
+
|
|
22
|
+
**Dependencies:** `@procwire/protocol`, `@procwire/codecs`
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { Client } from "@procwire/client";
|
|
28
|
+
|
|
29
|
+
const client = new Client()
|
|
30
|
+
.handle("query", async (data, ctx) => {
|
|
31
|
+
const results = await search(data);
|
|
32
|
+
ctx.respond(results);
|
|
33
|
+
})
|
|
34
|
+
.handle("insert", async (data, ctx) => {
|
|
35
|
+
ctx.ack({ accepted: true });
|
|
36
|
+
await processInBackground(data);
|
|
37
|
+
})
|
|
38
|
+
.event("progress");
|
|
39
|
+
|
|
40
|
+
await client.start();
|
|
41
|
+
|
|
42
|
+
// Emit events to parent
|
|
43
|
+
client.emitEvent("progress", { percent: 50 });
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## API Reference
|
|
47
|
+
|
|
48
|
+
### Client
|
|
49
|
+
|
|
50
|
+
Fluent builder for registering method handlers and events.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
const client = new Client(options?)
|
|
54
|
+
.handle(name, handler, definition?)
|
|
55
|
+
.event(name, definition?)
|
|
56
|
+
.start();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### Constructor Options
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
interface ClientOptions {
|
|
63
|
+
defaultCodec?: Codec; // Default codec for all methods/events
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### `.handle(name, handler, definition?)`
|
|
68
|
+
|
|
69
|
+
Register a method handler.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
client.handle("process", async (data, ctx) => {
|
|
73
|
+
// Handle request and send response
|
|
74
|
+
ctx.respond(result);
|
|
75
|
+
}, {
|
|
76
|
+
response: "result", // "result" | "stream" | "ack" | "none"
|
|
77
|
+
codec: msgpackCodec, // Optional, defaults to msgpack
|
|
78
|
+
cancellable: true, // Support AbortSignal from parent
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
#### `.event(name, definition?)`
|
|
83
|
+
|
|
84
|
+
Register an event that can be emitted to parent.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
client.event("progress", { codec: msgpackCodec });
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### `.start()`
|
|
91
|
+
|
|
92
|
+
Start listening for requests from parent.
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
await client.start();
|
|
96
|
+
// Client is now ready to receive requests
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### `.emitEvent(name, data)`
|
|
100
|
+
|
|
101
|
+
Emit an event to the parent process.
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
client.emitEvent("progress", { percent: 75 });
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### RequestContext
|
|
108
|
+
|
|
109
|
+
Passed to method handlers to send responses back to parent.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
interface RequestContext {
|
|
113
|
+
readonly requestId: number; // For correlation
|
|
114
|
+
readonly method: string; // Method being handled
|
|
115
|
+
readonly aborted: boolean; // Was request aborted?
|
|
116
|
+
|
|
117
|
+
onAbort(callback: () => void): void; // Abort callback
|
|
118
|
+
|
|
119
|
+
respond(data: unknown): Promise<void>; // Full response
|
|
120
|
+
ack(data?: unknown): Promise<void>; // Acknowledgment only
|
|
121
|
+
chunk(data: unknown): Promise<void>; // Stream chunk
|
|
122
|
+
end(): Promise<void>; // End stream
|
|
123
|
+
error(err: Error | string): Promise<void>; // Error response
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Important:** All response methods are async to handle backpressure. Always `await` them.
|
|
128
|
+
|
|
129
|
+
### Response Patterns
|
|
130
|
+
|
|
131
|
+
#### Single Response (`result`)
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
client.handle("query", async (data, ctx) => {
|
|
135
|
+
const result = await processQuery(data);
|
|
136
|
+
await ctx.respond(result);
|
|
137
|
+
}, { response: "result" });
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### Streaming Response (`stream`)
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
client.handle("generate", async (data, ctx) => {
|
|
144
|
+
for (const item of generateItems(data)) {
|
|
145
|
+
await ctx.chunk(item);
|
|
146
|
+
}
|
|
147
|
+
await ctx.end();
|
|
148
|
+
}, { response: "stream" });
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### Acknowledgment (`ack`)
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
client.handle("enqueue", async (data, ctx) => {
|
|
155
|
+
await ctx.ack({ queued: true, position: 42 });
|
|
156
|
+
// Continue processing after acknowledgment
|
|
157
|
+
await processInBackground(data);
|
|
158
|
+
}, { response: "ack" });
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
#### Fire-and-Forget (`none`)
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
client.handle("log", (data, ctx) => {
|
|
165
|
+
logger.info(data);
|
|
166
|
+
// No response needed
|
|
167
|
+
}, { response: "none" });
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### Error Response
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
client.handle("validate", async (data, ctx) => {
|
|
174
|
+
try {
|
|
175
|
+
const result = validate(data);
|
|
176
|
+
await ctx.respond(result);
|
|
177
|
+
} catch (e) {
|
|
178
|
+
await ctx.error(e);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Cancellation
|
|
184
|
+
|
|
185
|
+
Handle request cancellation from parent.
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
client.handle("longTask", async (data, ctx) => {
|
|
189
|
+
const resources = await acquireResources();
|
|
190
|
+
|
|
191
|
+
// Register cleanup on abort
|
|
192
|
+
ctx.onAbort(() => {
|
|
193
|
+
resources.release();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Check abort status periodically
|
|
197
|
+
for (const item of items) {
|
|
198
|
+
if (ctx.aborted) {
|
|
199
|
+
return; // Stop processing
|
|
200
|
+
}
|
|
201
|
+
await ctx.chunk(process(item));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
await ctx.end();
|
|
205
|
+
}, { response: "stream", cancellable: true });
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Error Handling
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { ProcwireClientError, ClientErrors } from "@procwire/client";
|
|
212
|
+
|
|
213
|
+
// Error factories
|
|
214
|
+
ClientErrors.methodNotFound("unknown"); // Unknown method called
|
|
215
|
+
ClientErrors.handlerError("process", err); // Handler threw error
|
|
216
|
+
ClientErrors.alreadyStarted(); // start() called twice
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Architecture
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
┌─────────────────────────────────────────┐
|
|
223
|
+
│ Parent Process │
|
|
224
|
+
│ ┌───────────────────────────────────┐ │
|
|
225
|
+
│ │ Module (uses @procwire/core) │ │
|
|
226
|
+
│ │ - send(), stream(), onEvent() │ │
|
|
227
|
+
│ └───────────────┬───────────────────┘ │
|
|
228
|
+
└──────────────────┼──────────────────────┘
|
|
229
|
+
│
|
|
230
|
+
┌──────────────┴──────────────┐
|
|
231
|
+
│ Control: stdio (JSON-RPC) │
|
|
232
|
+
│ Data: named pipe (BINARY) │
|
|
233
|
+
└──────────────┬──────────────┘
|
|
234
|
+
│
|
|
235
|
+
┌──────────────────┼──────────────────────┐
|
|
236
|
+
│ Child Process │
|
|
237
|
+
│ ┌───────────────┴───────────────────┐ │
|
|
238
|
+
│ │ Client (uses @procwire/client) │ │
|
|
239
|
+
│ │ - handle(), event(), emitEvent() │ │
|
|
240
|
+
│ └───────────────────────────────────┘ │
|
|
241
|
+
└─────────────────────────────────────────┘
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
MIT
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@procwire/client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Child-side client for Procwire IPC",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ipc",
|
|
7
|
+
"procwire",
|
|
8
|
+
"client",
|
|
9
|
+
"child-process"
|
|
10
|
+
],
|
|
11
|
+
"author": "Sebastian Webdev",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/SebastianWebdev/procwire.git",
|
|
15
|
+
"directory": "packages/client"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/SebastianWebdev/procwire/issues"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://www.procwire.dev",
|
|
5
21
|
"type": "module",
|
|
6
22
|
"sideEffects": false,
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=22"
|
|
26
|
+
},
|
|
7
27
|
"exports": {
|
|
8
28
|
".": {
|
|
9
29
|
"types": "./dist/index.d.ts",
|
|
@@ -13,26 +33,28 @@
|
|
|
13
33
|
"main": "./dist/index.js",
|
|
14
34
|
"types": "./dist/index.d.ts",
|
|
15
35
|
"files": [
|
|
16
|
-
"dist"
|
|
36
|
+
"dist",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE"
|
|
17
39
|
],
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"test": "vitest run",
|
|
22
|
-
"clean": "rm -rf dist"
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public",
|
|
42
|
+
"provenance": true
|
|
23
43
|
},
|
|
24
44
|
"dependencies": {
|
|
25
|
-
"@procwire/protocol": "
|
|
26
|
-
"@procwire/codecs": "
|
|
45
|
+
"@procwire/protocol": "1.0.1",
|
|
46
|
+
"@procwire/codecs": "1.0.1"
|
|
27
47
|
},
|
|
28
48
|
"devDependencies": {
|
|
49
|
+
"@types/node": "^22.0.0",
|
|
50
|
+
"rimraf": "^6.0.1",
|
|
51
|
+
"typescript": "^5.9.3",
|
|
29
52
|
"vitest": "^2.1.9"
|
|
30
53
|
},
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
54
|
+
"scripts": {
|
|
55
|
+
"clean": "rimraf dist \"*.tsbuildinfo\"",
|
|
56
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
57
|
+
"build": "tsc -p tsconfig.build.json",
|
|
58
|
+
"test": "vitest run"
|
|
59
|
+
}
|
|
60
|
+
}
|