@procwire/client 1.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/dist/client.d.ts +135 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +385 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +32 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/request-context.d.ts +55 -0
- package/dist/request-context.d.ts.map +1 -0
- package/dist/request-context.js +130 -0
- package/dist/request-context.js.map +1 -0
- package/dist/types.d.ts +100 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +38 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client class - Child-side API for Procwire IPC.
|
|
3
|
+
*
|
|
4
|
+
* RESPONSIBILITIES:
|
|
5
|
+
* - Register method handlers
|
|
6
|
+
* - Register events
|
|
7
|
+
* - Create named pipe server
|
|
8
|
+
* - Send $init to parent
|
|
9
|
+
* - Handle incoming requests
|
|
10
|
+
* - Emit events to parent
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { EventEmitter } from "node:events";
|
|
15
|
+
import type { MethodDefinition, EventDefinition, MethodHandler, ClientOptions } from "./types.js";
|
|
16
|
+
/**
|
|
17
|
+
* Client - Child-side API for Procwire IPC.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const client = new Client()
|
|
22
|
+
* .handle('query', async (data, ctx) => {
|
|
23
|
+
* const results = await search(data);
|
|
24
|
+
* ctx.respond(results);
|
|
25
|
+
* })
|
|
26
|
+
* .handle('insert', async (data, ctx) => {
|
|
27
|
+
* ctx.ack({ accepted: true });
|
|
28
|
+
* await processInBackground(data);
|
|
29
|
+
* })
|
|
30
|
+
* .event('progress');
|
|
31
|
+
*
|
|
32
|
+
* await client.start();
|
|
33
|
+
*
|
|
34
|
+
* // Emit events to parent
|
|
35
|
+
* client.emitEvent('progress', { percent: 50 });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare class Client extends EventEmitter {
|
|
39
|
+
private _defaultCodec;
|
|
40
|
+
private _methods;
|
|
41
|
+
private _events;
|
|
42
|
+
private _server;
|
|
43
|
+
private _socket;
|
|
44
|
+
private _frameBuffer;
|
|
45
|
+
private _methodNameToId;
|
|
46
|
+
private _methodIdToName;
|
|
47
|
+
private _eventNameToId;
|
|
48
|
+
private _abortCallbacks;
|
|
49
|
+
private _activeContexts;
|
|
50
|
+
private _started;
|
|
51
|
+
private readonly _headerPool;
|
|
52
|
+
private _headerPoolIndex;
|
|
53
|
+
private _drainWaiter;
|
|
54
|
+
constructor(options?: ClientOptions);
|
|
55
|
+
/**
|
|
56
|
+
* Register a method handler.
|
|
57
|
+
*
|
|
58
|
+
* @param method - Method name
|
|
59
|
+
* @param handler - Handler function
|
|
60
|
+
* @param options - Method configuration
|
|
61
|
+
* @returns this for chaining
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* client.handle('query', async (data, ctx) => {
|
|
66
|
+
* ctx.respond({ results: [] });
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
handle<TData = unknown>(method: string, handler: MethodHandler<TData>, options?: Partial<MethodDefinition>): this;
|
|
71
|
+
/**
|
|
72
|
+
* Register an event that can be emitted to parent.
|
|
73
|
+
*
|
|
74
|
+
* @param name - Event name
|
|
75
|
+
* @param options - Event configuration
|
|
76
|
+
* @returns this for chaining
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* client.event('progress');
|
|
81
|
+
* client.event('status', { codec: arrowCodec });
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
event(name: string, options?: Partial<EventDefinition>): this;
|
|
85
|
+
/**
|
|
86
|
+
* Start the client.
|
|
87
|
+
*
|
|
88
|
+
* Creates named pipe server, waits for listen, then sends $init to parent.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* await client.start();
|
|
93
|
+
* // Client is now ready to receive requests
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
start(): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Emit an event to parent.
|
|
99
|
+
*
|
|
100
|
+
* @param eventName - Event name (must be registered with .event())
|
|
101
|
+
* @param data - Event data
|
|
102
|
+
* @returns Promise that resolves when the event has been written
|
|
103
|
+
* and socket buffer has drained (if backpressure occurred).
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* await client.emitEvent('progress', { percent: 50 });
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
emitEvent(eventName: string, data: unknown): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Graceful shutdown.
|
|
113
|
+
*/
|
|
114
|
+
shutdown(): Promise<void>;
|
|
115
|
+
/**
|
|
116
|
+
* Whether client is connected to parent.
|
|
117
|
+
*/
|
|
118
|
+
get connected(): boolean;
|
|
119
|
+
private _generatePipePath;
|
|
120
|
+
private _createPipeServer;
|
|
121
|
+
private _sendInit;
|
|
122
|
+
private _handleFrame;
|
|
123
|
+
private _handleAbort;
|
|
124
|
+
private _sendErrorResponse;
|
|
125
|
+
private _acquireHeaderBuffer;
|
|
126
|
+
/**
|
|
127
|
+
* Send a frame with proper backpressure handling.
|
|
128
|
+
* Used for emitting events to parent.
|
|
129
|
+
*
|
|
130
|
+
* Uses RING+SYNC pattern: write BEFORE await for allocation-free headers.
|
|
131
|
+
* DrainWaiter singleton prevents MaxListenersExceededWarning.
|
|
132
|
+
*/
|
|
133
|
+
private _sendFrame;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAa3C,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAQlG;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,MAAO,SAAQ,YAAY;IACtC,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,QAAQ,CAAwE;IACxF,OAAO,CAAC,OAAO,CAAsC;IAErD,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,YAAY,CAA4B;IAEhD,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,eAAe,CAA6B;IACpD,OAAO,CAAC,cAAc,CAA6B;IAEnD,OAAO,CAAC,eAAe,CAAsC;IAC7D,OAAO,CAAC,eAAe,CAAyC;IAChE,OAAO,CAAC,QAAQ,CAAS;IAGzB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAE1B;IACF,OAAO,CAAC,gBAAgB,CAAK;IAG7B,OAAO,CAAC,YAAY,CAA4B;gBAEpC,OAAO,CAAC,EAAE,aAAa;IASnC;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,KAAK,GAAG,OAAO,EACpB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,EAC7B,OAAO,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAClC,IAAI;IAiBP;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI;IAY7D;;;;;;;;;;OAUG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA8B5B;;;;;;;;;;;;OAYG;IACG,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBhE;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAO/B;;OAEG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;IAMD,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,iBAAiB;IA0BzB,OAAO,CAAC,SAAS;IAqCjB,OAAO,CAAC,YAAY;IAyEpB,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,kBAAkB;IAsB1B,OAAO,CAAC,oBAAoB;IAM5B;;;;;;OAMG;YACW,UAAU;CAgCzB"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client class - Child-side API for Procwire IPC.
|
|
3
|
+
*
|
|
4
|
+
* RESPONSIBILITIES:
|
|
5
|
+
* - Register method handlers
|
|
6
|
+
* - Register events
|
|
7
|
+
* - Create named pipe server
|
|
8
|
+
* - Send $init to parent
|
|
9
|
+
* - Handle incoming requests
|
|
10
|
+
* - Emit events to parent
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { EventEmitter } from "node:events";
|
|
15
|
+
import { createServer } from "node:net";
|
|
16
|
+
import { FrameBuffer, Flags, encodeHeaderInto, HEADER_SIZE, HEADER_POOL_SIZE, ABORT_METHOD_ID, DrainWaiter, } from "@procwire/protocol";
|
|
17
|
+
import { msgpackCodec, codecDeserialize } from "@procwire/codecs";
|
|
18
|
+
import { RequestContextImpl } from "./request-context.js";
|
|
19
|
+
import { ClientErrors } from "./errors.js";
|
|
20
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
21
|
+
// CLIENT CLASS
|
|
22
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
23
|
+
/**
|
|
24
|
+
* Client - Child-side API for Procwire IPC.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const client = new Client()
|
|
29
|
+
* .handle('query', async (data, ctx) => {
|
|
30
|
+
* const results = await search(data);
|
|
31
|
+
* ctx.respond(results);
|
|
32
|
+
* })
|
|
33
|
+
* .handle('insert', async (data, ctx) => {
|
|
34
|
+
* ctx.ack({ accepted: true });
|
|
35
|
+
* await processInBackground(data);
|
|
36
|
+
* })
|
|
37
|
+
* .event('progress');
|
|
38
|
+
*
|
|
39
|
+
* await client.start();
|
|
40
|
+
*
|
|
41
|
+
* // Emit events to parent
|
|
42
|
+
* client.emitEvent('progress', { percent: 50 });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export class Client extends EventEmitter {
|
|
46
|
+
_defaultCodec;
|
|
47
|
+
_methods = new Map();
|
|
48
|
+
_events = new Map();
|
|
49
|
+
_server = null;
|
|
50
|
+
_socket = null;
|
|
51
|
+
_frameBuffer = null;
|
|
52
|
+
_methodNameToId = new Map();
|
|
53
|
+
_methodIdToName = new Map();
|
|
54
|
+
_eventNameToId = new Map();
|
|
55
|
+
_abortCallbacks = new Map();
|
|
56
|
+
_activeContexts = new Map();
|
|
57
|
+
_started = false;
|
|
58
|
+
// Ring buffer for headers (OPT-02: allocation-free sends)
|
|
59
|
+
_headerPool = Array.from({ length: HEADER_POOL_SIZE }, () => Buffer.allocUnsafe(HEADER_SIZE));
|
|
60
|
+
_headerPoolIndex = 0;
|
|
61
|
+
// OPT-04: Backpressure tracking via singleton DrainWaiter
|
|
62
|
+
_drainWaiter = null;
|
|
63
|
+
constructor(options) {
|
|
64
|
+
super();
|
|
65
|
+
this._defaultCodec = options?.defaultCodec ?? msgpackCodec;
|
|
66
|
+
}
|
|
67
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
68
|
+
// BUILDER API
|
|
69
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
70
|
+
/**
|
|
71
|
+
* Register a method handler.
|
|
72
|
+
*
|
|
73
|
+
* @param method - Method name
|
|
74
|
+
* @param handler - Handler function
|
|
75
|
+
* @param options - Method configuration
|
|
76
|
+
* @returns this for chaining
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```typescript
|
|
80
|
+
* client.handle('query', async (data, ctx) => {
|
|
81
|
+
* ctx.respond({ results: [] });
|
|
82
|
+
* });
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
handle(method, handler, options) {
|
|
86
|
+
if (this._started) {
|
|
87
|
+
throw ClientErrors.cannotAddHandlerAfterStart();
|
|
88
|
+
}
|
|
89
|
+
this._methods.set(method, {
|
|
90
|
+
def: {
|
|
91
|
+
response: options?.response ?? "result",
|
|
92
|
+
codec: options?.codec ?? this._defaultCodec,
|
|
93
|
+
cancellable: options?.cancellable ?? false,
|
|
94
|
+
},
|
|
95
|
+
handler: handler,
|
|
96
|
+
});
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Register an event that can be emitted to parent.
|
|
101
|
+
*
|
|
102
|
+
* @param name - Event name
|
|
103
|
+
* @param options - Event configuration
|
|
104
|
+
* @returns this for chaining
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* client.event('progress');
|
|
109
|
+
* client.event('status', { codec: arrowCodec });
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
event(name, options) {
|
|
113
|
+
if (this._started) {
|
|
114
|
+
throw ClientErrors.cannotAddEventAfterStart();
|
|
115
|
+
}
|
|
116
|
+
this._events.set(name, {
|
|
117
|
+
codec: options?.codec ?? this._defaultCodec,
|
|
118
|
+
});
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Start the client.
|
|
123
|
+
*
|
|
124
|
+
* Creates named pipe server, waits for listen, then sends $init to parent.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* await client.start();
|
|
129
|
+
* // Client is now ready to receive requests
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
async start() {
|
|
133
|
+
if (this._started) {
|
|
134
|
+
throw ClientErrors.alreadyStarted();
|
|
135
|
+
}
|
|
136
|
+
this._started = true;
|
|
137
|
+
// Assign IDs to methods and events
|
|
138
|
+
let methodId = 1;
|
|
139
|
+
for (const name of this._methods.keys()) {
|
|
140
|
+
this._methodNameToId.set(name, methodId);
|
|
141
|
+
this._methodIdToName.set(methodId, name);
|
|
142
|
+
methodId++;
|
|
143
|
+
}
|
|
144
|
+
let eventId = 1;
|
|
145
|
+
for (const name of this._events.keys()) {
|
|
146
|
+
this._eventNameToId.set(name, eventId);
|
|
147
|
+
eventId++;
|
|
148
|
+
}
|
|
149
|
+
// Create pipe path
|
|
150
|
+
const pipePath = this._generatePipePath();
|
|
151
|
+
// Create server and wait for listen
|
|
152
|
+
await this._createPipeServer(pipePath);
|
|
153
|
+
// Send $init to parent (via stdout, JSON-RPC control plane)
|
|
154
|
+
this._sendInit(pipePath);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Emit an event to parent.
|
|
158
|
+
*
|
|
159
|
+
* @param eventName - Event name (must be registered with .event())
|
|
160
|
+
* @param data - Event data
|
|
161
|
+
* @returns Promise that resolves when the event has been written
|
|
162
|
+
* and socket buffer has drained (if backpressure occurred).
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* await client.emitEvent('progress', { percent: 50 });
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
async emitEvent(eventName, data) {
|
|
170
|
+
if (!this._socket) {
|
|
171
|
+
throw ClientErrors.notConnected();
|
|
172
|
+
}
|
|
173
|
+
const eventId = this._eventNameToId.get(eventName);
|
|
174
|
+
if (eventId === undefined) {
|
|
175
|
+
throw ClientErrors.unknownEvent(eventName);
|
|
176
|
+
}
|
|
177
|
+
const eventDef = this._events.get(eventName);
|
|
178
|
+
const codec = eventDef.codec ?? this._defaultCodec;
|
|
179
|
+
await this._sendFrame(eventId, 0, data, codec, Flags.DIRECTION_TO_PARENT);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Graceful shutdown.
|
|
183
|
+
*/
|
|
184
|
+
async shutdown() {
|
|
185
|
+
this._socket?.destroy();
|
|
186
|
+
this._server?.close();
|
|
187
|
+
this._socket = null;
|
|
188
|
+
this._server = null;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Whether client is connected to parent.
|
|
192
|
+
*/
|
|
193
|
+
get connected() {
|
|
194
|
+
return this._socket !== null && !this._socket.destroyed;
|
|
195
|
+
}
|
|
196
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
197
|
+
// PRIVATE: Initialization
|
|
198
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
199
|
+
_generatePipePath() {
|
|
200
|
+
const id = Math.random().toString(36).slice(2, 10);
|
|
201
|
+
return process.platform === "win32"
|
|
202
|
+
? `\\\\.\\pipe\\procwire-${process.pid}-${id}`
|
|
203
|
+
: `/tmp/procwire-${process.pid}-${id}.sock`;
|
|
204
|
+
}
|
|
205
|
+
_createPipeServer(pipePath) {
|
|
206
|
+
return new Promise((resolve, reject) => {
|
|
207
|
+
this._server = createServer((socket) => {
|
|
208
|
+
this._socket = socket;
|
|
209
|
+
this._drainWaiter = new DrainWaiter(socket);
|
|
210
|
+
this._frameBuffer = new FrameBuffer();
|
|
211
|
+
socket.on("data", (chunk) => {
|
|
212
|
+
const frames = this._frameBuffer.push(chunk);
|
|
213
|
+
for (const frame of frames) {
|
|
214
|
+
this._handleFrame(frame);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
socket.on("error", (err) => this.emit("error", err));
|
|
218
|
+
socket.on("close", () => {
|
|
219
|
+
this._drainWaiter?.clear();
|
|
220
|
+
this.emit("disconnected");
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
this._server.on("error", reject);
|
|
224
|
+
this._server.listen(pipePath, () => resolve());
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
_sendInit(pipePath) {
|
|
228
|
+
const schema = {
|
|
229
|
+
methods: Object.fromEntries(Array.from(this._methods.entries()).map(([name, { def }]) => [
|
|
230
|
+
name,
|
|
231
|
+
{
|
|
232
|
+
id: this._methodNameToId.get(name),
|
|
233
|
+
response: def.response,
|
|
234
|
+
},
|
|
235
|
+
])),
|
|
236
|
+
events: Object.fromEntries(Array.from(this._events.keys()).map((name) => [
|
|
237
|
+
name,
|
|
238
|
+
{ id: this._eventNameToId.get(name) },
|
|
239
|
+
])),
|
|
240
|
+
};
|
|
241
|
+
const initMessage = {
|
|
242
|
+
jsonrpc: "2.0",
|
|
243
|
+
method: "$init",
|
|
244
|
+
params: {
|
|
245
|
+
pipe: pipePath,
|
|
246
|
+
schema,
|
|
247
|
+
version: "2.0.0",
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
// Write to stdout (JSON-RPC control plane)
|
|
251
|
+
console.log(JSON.stringify(initMessage));
|
|
252
|
+
}
|
|
253
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
254
|
+
// PRIVATE: Frame Handling
|
|
255
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
256
|
+
_handleFrame(frame) {
|
|
257
|
+
const { header } = frame;
|
|
258
|
+
// Abort signal (reserved methodId)
|
|
259
|
+
if (header.methodId === ABORT_METHOD_ID) {
|
|
260
|
+
this._handleAbort(header.requestId);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
// Request from parent
|
|
264
|
+
const methodName = this._methodIdToName.get(header.methodId);
|
|
265
|
+
if (!methodName) {
|
|
266
|
+
// Unknown method - send error response
|
|
267
|
+
this._sendErrorResponse(header.requestId, header.methodId, `Unknown method ID: ${header.methodId}`);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const methodEntry = this._methods.get(methodName);
|
|
271
|
+
if (!methodEntry)
|
|
272
|
+
return;
|
|
273
|
+
const { def, handler } = methodEntry;
|
|
274
|
+
const codec = def.codec ?? this._defaultCodec;
|
|
275
|
+
const data = codecDeserialize(codec, frame);
|
|
276
|
+
// Create request context with shared DrainWaiter
|
|
277
|
+
const ctx = new RequestContextImpl(header.requestId, methodName, header.methodId, codec, this._socket, this._abortCallbacks, () => this._acquireHeaderBuffer(), this._drainWaiter);
|
|
278
|
+
// Track active context for abort handling
|
|
279
|
+
this._activeContexts.set(header.requestId, ctx);
|
|
280
|
+
// Call handler
|
|
281
|
+
try {
|
|
282
|
+
const result = handler(data, ctx);
|
|
283
|
+
if (result instanceof Promise) {
|
|
284
|
+
result
|
|
285
|
+
.catch((err) => {
|
|
286
|
+
if (!ctx.responded) {
|
|
287
|
+
// ctx.error() is async - fire and forget with error handling
|
|
288
|
+
ctx.error(err).catch(() => {
|
|
289
|
+
/* ignore - socket may be closed */
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
.finally(() => {
|
|
294
|
+
this._activeContexts.delete(header.requestId);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
this._activeContexts.delete(header.requestId);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
if (!ctx.responded) {
|
|
303
|
+
// ctx.error() is async - fire and forget with error handling
|
|
304
|
+
ctx.error(err).catch(() => {
|
|
305
|
+
/* ignore - socket may be closed */
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
this._activeContexts.delete(header.requestId);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
_handleAbort(requestId) {
|
|
312
|
+
// Mark context as aborted
|
|
313
|
+
const ctx = this._activeContexts.get(requestId);
|
|
314
|
+
if (ctx) {
|
|
315
|
+
ctx._markAborted();
|
|
316
|
+
}
|
|
317
|
+
// Call abort callbacks
|
|
318
|
+
const callbacks = this._abortCallbacks.get(requestId);
|
|
319
|
+
if (callbacks) {
|
|
320
|
+
for (const cb of callbacks) {
|
|
321
|
+
try {
|
|
322
|
+
cb();
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
/* ignore */
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
this._abortCallbacks.delete(requestId);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
_sendErrorResponse(requestId, methodId, message) {
|
|
332
|
+
const payload = this._defaultCodec.serialize(message);
|
|
333
|
+
const headerBuf = this._acquireHeaderBuffer();
|
|
334
|
+
encodeHeaderInto(headerBuf, {
|
|
335
|
+
methodId,
|
|
336
|
+
flags: Flags.IS_RESPONSE | Flags.IS_ERROR | Flags.DIRECTION_TO_PARENT,
|
|
337
|
+
requestId,
|
|
338
|
+
payloadLength: payload.length,
|
|
339
|
+
});
|
|
340
|
+
// RING+SYNC: No await in sync function, buffer used immediately
|
|
341
|
+
this._socket?.cork();
|
|
342
|
+
this._socket?.write(headerBuf);
|
|
343
|
+
this._socket?.write(payload);
|
|
344
|
+
this._socket?.uncork();
|
|
345
|
+
}
|
|
346
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
347
|
+
// PRIVATE: Frame Sending
|
|
348
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
349
|
+
_acquireHeaderBuffer() {
|
|
350
|
+
const buffer = this._headerPool[this._headerPoolIndex];
|
|
351
|
+
this._headerPoolIndex = (this._headerPoolIndex + 1) % HEADER_POOL_SIZE;
|
|
352
|
+
return buffer;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Send a frame with proper backpressure handling.
|
|
356
|
+
* Used for emitting events to parent.
|
|
357
|
+
*
|
|
358
|
+
* Uses RING+SYNC pattern: write BEFORE await for allocation-free headers.
|
|
359
|
+
* DrainWaiter singleton prevents MaxListenersExceededWarning.
|
|
360
|
+
*/
|
|
361
|
+
async _sendFrame(methodId, requestId, data, codec, flags) {
|
|
362
|
+
if (!this._socket || !this._drainWaiter)
|
|
363
|
+
return;
|
|
364
|
+
const payload = codec.serialize(data);
|
|
365
|
+
const headerBuf = this._acquireHeaderBuffer();
|
|
366
|
+
encodeHeaderInto(headerBuf, {
|
|
367
|
+
methodId,
|
|
368
|
+
flags,
|
|
369
|
+
requestId,
|
|
370
|
+
payloadLength: payload.length,
|
|
371
|
+
});
|
|
372
|
+
// Write BEFORE await to prevent deadlock.
|
|
373
|
+
// Buffer.from() creates a copy because Named Pipes on Windows
|
|
374
|
+
// may not synchronously copy buffer data.
|
|
375
|
+
this._socket.cork();
|
|
376
|
+
this._socket.write(Buffer.from(headerBuf));
|
|
377
|
+
const canContinue = this._socket.write(payload);
|
|
378
|
+
this._socket.uncork();
|
|
379
|
+
// OPT-04: Wait AFTER write if backpressure - ring buffer no longer needed
|
|
380
|
+
if (!canContinue) {
|
|
381
|
+
await this._drainWaiter.waitForDrain();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error types and factory functions for @procwire/client.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Base error class for all Procwire client errors.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ProcwireClientError extends Error {
|
|
10
|
+
constructor(message: string);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Factory functions for Client-related errors.
|
|
14
|
+
*/
|
|
15
|
+
export declare const ClientErrors: {
|
|
16
|
+
/** Cannot add handlers after start */
|
|
17
|
+
readonly cannotAddHandlerAfterStart: () => ProcwireClientError;
|
|
18
|
+
/** Cannot add events after start */
|
|
19
|
+
readonly cannotAddEventAfterStart: () => ProcwireClientError;
|
|
20
|
+
/** Client already started */
|
|
21
|
+
readonly alreadyStarted: () => ProcwireClientError;
|
|
22
|
+
/** Client not connected */
|
|
23
|
+
readonly notConnected: () => ProcwireClientError;
|
|
24
|
+
/** Unknown event name */
|
|
25
|
+
readonly unknownEvent: (eventName: string) => ProcwireClientError;
|
|
26
|
+
/** Response already sent */
|
|
27
|
+
readonly responseAlreadySent: () => ProcwireClientError;
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,eAAO,MAAM,YAAY;IACvB,sCAAsC;;IAGtC,oCAAoC;;IAGpC,6BAA6B;;IAG7B,2BAA2B;;IAG3B,yBAAyB;uCACC,MAAM;IAEhC,4BAA4B;;CAEpB,CAAC"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error types and factory functions for @procwire/client.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Base error class for all Procwire client errors.
|
|
8
|
+
*/
|
|
9
|
+
export class ProcwireClientError extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "ProcwireClientError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Factory functions for Client-related errors.
|
|
17
|
+
*/
|
|
18
|
+
export const ClientErrors = {
|
|
19
|
+
/** Cannot add handlers after start */
|
|
20
|
+
cannotAddHandlerAfterStart: () => new ProcwireClientError("Cannot add handlers after start()"),
|
|
21
|
+
/** Cannot add events after start */
|
|
22
|
+
cannotAddEventAfterStart: () => new ProcwireClientError("Cannot add events after start()"),
|
|
23
|
+
/** Client already started */
|
|
24
|
+
alreadyStarted: () => new ProcwireClientError("Client already started"),
|
|
25
|
+
/** Client not connected */
|
|
26
|
+
notConnected: () => new ProcwireClientError("Client not connected"),
|
|
27
|
+
/** Unknown event name */
|
|
28
|
+
unknownEvent: (eventName) => new ProcwireClientError(`Unknown event: ${eventName}`),
|
|
29
|
+
/** Response already sent */
|
|
30
|
+
responseAlreadySent: () => new ProcwireClientError("Response already sent"),
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,sCAAsC;IACtC,0BAA0B,EAAE,GAAG,EAAE,CAAC,IAAI,mBAAmB,CAAC,mCAAmC,CAAC;IAE9F,oCAAoC;IACpC,wBAAwB,EAAE,GAAG,EAAE,CAAC,IAAI,mBAAmB,CAAC,iCAAiC,CAAC;IAE1F,6BAA6B;IAC7B,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI,mBAAmB,CAAC,wBAAwB,CAAC;IAEvE,2BAA2B;IAC3B,YAAY,EAAE,GAAG,EAAE,CAAC,IAAI,mBAAmB,CAAC,sBAAsB,CAAC;IAEnE,yBAAyB;IACzB,YAAY,EAAE,CAAC,SAAiB,EAAE,EAAE,CAAC,IAAI,mBAAmB,CAAC,kBAAkB,SAAS,EAAE,CAAC;IAE3F,4BAA4B;IAC5B,mBAAmB,EAAE,GAAG,EAAE,CAAC,IAAI,mBAAmB,CAAC,uBAAuB,CAAC;CACnE,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @procwire/client - Child-side API for Procwire IPC.
|
|
3
|
+
*
|
|
4
|
+
* This package provides the client-side implementation for child processes
|
|
5
|
+
* to communicate with the parent process using Procwire's binary protocol.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { Client } from '@procwire/client';
|
|
10
|
+
*
|
|
11
|
+
* const client = new Client()
|
|
12
|
+
* .handle('query', async (data, ctx) => {
|
|
13
|
+
* const results = await search(data);
|
|
14
|
+
* ctx.respond(results);
|
|
15
|
+
* })
|
|
16
|
+
* .handle('insert', async (data, ctx) => {
|
|
17
|
+
* ctx.ack({ accepted: true });
|
|
18
|
+
* await processInBackground(data);
|
|
19
|
+
* })
|
|
20
|
+
* .event('progress');
|
|
21
|
+
*
|
|
22
|
+
* await client.start();
|
|
23
|
+
*
|
|
24
|
+
* // Emit events to parent
|
|
25
|
+
* client.emitEvent('progress', { percent: 50 });
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @module
|
|
29
|
+
*/
|
|
30
|
+
export { Client } from "./client.js";
|
|
31
|
+
export { RequestContextImpl } from "./request-context.js";
|
|
32
|
+
export { ProcwireClientError, ClientErrors } from "./errors.js";
|
|
33
|
+
export type { ResponseType, MethodDefinition, EventDefinition, ClientOptions, MethodHandler, RequestContext, } from "./types.js";
|
|
34
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChE,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,aAAa,EACb,cAAc,GACf,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @procwire/client - Child-side API for Procwire IPC.
|
|
3
|
+
*
|
|
4
|
+
* This package provides the client-side implementation for child processes
|
|
5
|
+
* to communicate with the parent process using Procwire's binary protocol.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { Client } from '@procwire/client';
|
|
10
|
+
*
|
|
11
|
+
* const client = new Client()
|
|
12
|
+
* .handle('query', async (data, ctx) => {
|
|
13
|
+
* const results = await search(data);
|
|
14
|
+
* ctx.respond(results);
|
|
15
|
+
* })
|
|
16
|
+
* .handle('insert', async (data, ctx) => {
|
|
17
|
+
* ctx.ack({ accepted: true });
|
|
18
|
+
* await processInBackground(data);
|
|
19
|
+
* })
|
|
20
|
+
* .event('progress');
|
|
21
|
+
*
|
|
22
|
+
* await client.start();
|
|
23
|
+
*
|
|
24
|
+
* // Emit events to parent
|
|
25
|
+
* client.emitEvent('progress', { percent: 50 });
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @module
|
|
29
|
+
*/
|
|
30
|
+
export { Client } from "./client.js";
|
|
31
|
+
export { RequestContextImpl } from "./request-context.js";
|
|
32
|
+
export { ProcwireClientError, ClientErrors } from "./errors.js";
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RequestContext implementation for method handlers.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import type { Socket } from "node:net";
|
|
7
|
+
import type { DrainWaiter } from "@procwire/protocol";
|
|
8
|
+
import type { Codec } from "@procwire/codecs";
|
|
9
|
+
import type { RequestContext } from "./types.js";
|
|
10
|
+
/**
|
|
11
|
+
* Internal implementation of RequestContext.
|
|
12
|
+
*
|
|
13
|
+
* Passed to method handlers to allow sending responses.
|
|
14
|
+
* All response methods are async to properly handle socket backpressure.
|
|
15
|
+
*/
|
|
16
|
+
export declare class RequestContextImpl implements RequestContext {
|
|
17
|
+
readonly requestId: number;
|
|
18
|
+
readonly method: string;
|
|
19
|
+
private readonly _methodId;
|
|
20
|
+
private readonly _codec;
|
|
21
|
+
private readonly _socket;
|
|
22
|
+
private readonly _abortCallbacks;
|
|
23
|
+
private readonly _acquireHeader;
|
|
24
|
+
private readonly _drainWaiter;
|
|
25
|
+
private _aborted;
|
|
26
|
+
private _responded;
|
|
27
|
+
constructor(requestId: number, method: string, _methodId: number, _codec: Codec, _socket: Socket, _abortCallbacks: Map<number, Set<() => void>>, _acquireHeader: () => Buffer, _drainWaiter: DrainWaiter);
|
|
28
|
+
get aborted(): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Whether a response has been sent.
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
get responded(): boolean;
|
|
34
|
+
onAbort(callback: () => void): void;
|
|
35
|
+
respond(data: unknown): Promise<void>;
|
|
36
|
+
ack(data?: unknown): Promise<void>;
|
|
37
|
+
chunk(data: unknown): Promise<void>;
|
|
38
|
+
end(): Promise<void>;
|
|
39
|
+
error(err: Error | string): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Mark context as aborted.
|
|
42
|
+
* @internal Called by Client when abort frame received.
|
|
43
|
+
*/
|
|
44
|
+
_markAborted(): void;
|
|
45
|
+
private _ensureNotResponded;
|
|
46
|
+
/**
|
|
47
|
+
* Send response data with proper backpressure handling.
|
|
48
|
+
*
|
|
49
|
+
* Uses RING+SYNC pattern: write BEFORE await for allocation-free headers.
|
|
50
|
+
* DrainWaiter singleton prevents MaxListenersExceededWarning.
|
|
51
|
+
*/
|
|
52
|
+
private _sendResponse;
|
|
53
|
+
private _cleanup;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=request-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../src/request-context.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjD;;;;;GAKG;AACH,qBAAa,kBAAmB,YAAW,cAAc;aAKrC,SAAS,EAAE,MAAM;aACjB,MAAM,EAAE,MAAM;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAX/B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAS;gBAGT,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,KAAK,EACb,OAAO,EAAE,MAAM,EACf,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,EAC7C,cAAc,EAAE,MAAM,MAAM,EAC5B,YAAY,EAAE,WAAW;IAG5C,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;;OAGG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAS7B,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAOrC,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAUlC,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAInC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IAUpB,KAAK,CAAC,GAAG,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAW/C;;;OAGG;IACH,YAAY,IAAI,IAAI;IAIpB,OAAO,CAAC,mBAAmB;IAM3B;;;;;OAKG;YACW,aAAa;IA+B3B,OAAO,CAAC,QAAQ;CAGjB"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RequestContext implementation for method handlers.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { Flags, encodeHeaderInto } from "@procwire/protocol";
|
|
7
|
+
import { ClientErrors } from "./errors.js";
|
|
8
|
+
/**
|
|
9
|
+
* Internal implementation of RequestContext.
|
|
10
|
+
*
|
|
11
|
+
* Passed to method handlers to allow sending responses.
|
|
12
|
+
* All response methods are async to properly handle socket backpressure.
|
|
13
|
+
*/
|
|
14
|
+
export class RequestContextImpl {
|
|
15
|
+
requestId;
|
|
16
|
+
method;
|
|
17
|
+
_methodId;
|
|
18
|
+
_codec;
|
|
19
|
+
_socket;
|
|
20
|
+
_abortCallbacks;
|
|
21
|
+
_acquireHeader;
|
|
22
|
+
_drainWaiter;
|
|
23
|
+
_aborted = false;
|
|
24
|
+
_responded = false;
|
|
25
|
+
constructor(requestId, method, _methodId, _codec, _socket, _abortCallbacks, _acquireHeader, _drainWaiter) {
|
|
26
|
+
this.requestId = requestId;
|
|
27
|
+
this.method = method;
|
|
28
|
+
this._methodId = _methodId;
|
|
29
|
+
this._codec = _codec;
|
|
30
|
+
this._socket = _socket;
|
|
31
|
+
this._abortCallbacks = _abortCallbacks;
|
|
32
|
+
this._acquireHeader = _acquireHeader;
|
|
33
|
+
this._drainWaiter = _drainWaiter;
|
|
34
|
+
}
|
|
35
|
+
get aborted() {
|
|
36
|
+
return this._aborted;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Whether a response has been sent.
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
get responded() {
|
|
43
|
+
return this._responded;
|
|
44
|
+
}
|
|
45
|
+
onAbort(callback) {
|
|
46
|
+
let callbacks = this._abortCallbacks.get(this.requestId);
|
|
47
|
+
if (!callbacks) {
|
|
48
|
+
callbacks = new Set();
|
|
49
|
+
this._abortCallbacks.set(this.requestId, callbacks);
|
|
50
|
+
}
|
|
51
|
+
callbacks.add(callback);
|
|
52
|
+
}
|
|
53
|
+
async respond(data) {
|
|
54
|
+
this._ensureNotResponded();
|
|
55
|
+
this._responded = true;
|
|
56
|
+
await this._sendResponse(data, Flags.IS_RESPONSE | Flags.DIRECTION_TO_PARENT);
|
|
57
|
+
this._cleanup();
|
|
58
|
+
}
|
|
59
|
+
async ack(data) {
|
|
60
|
+
this._ensureNotResponded();
|
|
61
|
+
this._responded = true;
|
|
62
|
+
await this._sendResponse(data ?? null, Flags.IS_RESPONSE | Flags.IS_ACK | Flags.DIRECTION_TO_PARENT);
|
|
63
|
+
this._cleanup();
|
|
64
|
+
}
|
|
65
|
+
async chunk(data) {
|
|
66
|
+
await this._sendResponse(data, Flags.IS_RESPONSE | Flags.IS_STREAM | Flags.DIRECTION_TO_PARENT);
|
|
67
|
+
}
|
|
68
|
+
async end() {
|
|
69
|
+
this._ensureNotResponded();
|
|
70
|
+
this._responded = true;
|
|
71
|
+
await this._sendResponse(null, Flags.IS_RESPONSE | Flags.IS_STREAM | Flags.STREAM_END | Flags.DIRECTION_TO_PARENT);
|
|
72
|
+
this._cleanup();
|
|
73
|
+
}
|
|
74
|
+
async error(err) {
|
|
75
|
+
this._ensureNotResponded();
|
|
76
|
+
this._responded = true;
|
|
77
|
+
const message = err instanceof Error ? err.message : err;
|
|
78
|
+
await this._sendResponse(message, Flags.IS_RESPONSE | Flags.IS_ERROR | Flags.DIRECTION_TO_PARENT);
|
|
79
|
+
this._cleanup();
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Mark context as aborted.
|
|
83
|
+
* @internal Called by Client when abort frame received.
|
|
84
|
+
*/
|
|
85
|
+
_markAborted() {
|
|
86
|
+
this._aborted = true;
|
|
87
|
+
}
|
|
88
|
+
_ensureNotResponded() {
|
|
89
|
+
if (this._responded) {
|
|
90
|
+
throw ClientErrors.responseAlreadySent();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Send response data with proper backpressure handling.
|
|
95
|
+
*
|
|
96
|
+
* Uses RING+SYNC pattern: write BEFORE await for allocation-free headers.
|
|
97
|
+
* DrainWaiter singleton prevents MaxListenersExceededWarning.
|
|
98
|
+
*/
|
|
99
|
+
async _sendResponse(data, flags) {
|
|
100
|
+
// Empty payload cases:
|
|
101
|
+
// 1. STREAM_END frames (null data)
|
|
102
|
+
// 2. ACK without data (null/undefined data with IS_ACK flag)
|
|
103
|
+
// Don't serialize null - just use empty buffer (required for rawCodec compatibility)
|
|
104
|
+
const isStreamEnd = (flags & Flags.STREAM_END) !== 0;
|
|
105
|
+
const isEmptyAck = (flags & Flags.IS_ACK) !== 0 && data == null;
|
|
106
|
+
const payload = isStreamEnd || isEmptyAck ? Buffer.alloc(0) : this._codec.serialize(data);
|
|
107
|
+
const headerBuf = this._acquireHeader();
|
|
108
|
+
encodeHeaderInto(headerBuf, {
|
|
109
|
+
methodId: this._methodId,
|
|
110
|
+
flags,
|
|
111
|
+
requestId: this.requestId,
|
|
112
|
+
payloadLength: payload.length,
|
|
113
|
+
});
|
|
114
|
+
// Write BEFORE await to prevent deadlock.
|
|
115
|
+
// Buffer.from() creates a copy because Named Pipes on Windows
|
|
116
|
+
// may not synchronously copy buffer data.
|
|
117
|
+
this._socket.cork();
|
|
118
|
+
this._socket.write(Buffer.from(headerBuf));
|
|
119
|
+
const canContinue = this._socket.write(payload);
|
|
120
|
+
this._socket.uncork();
|
|
121
|
+
// OPT-04: Wait AFTER write if backpressure - ring buffer no longer needed
|
|
122
|
+
if (!canContinue) {
|
|
123
|
+
await this._drainWaiter.waitForDrain();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
_cleanup() {
|
|
127
|
+
this._abortCallbacks.delete(this.requestId);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=request-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-context.js","sourceRoot":"","sources":["../src/request-context.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for @procwire/client.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import type { Codec } from "@procwire/codecs";
|
|
7
|
+
/**
|
|
8
|
+
* Response type for methods.
|
|
9
|
+
*/
|
|
10
|
+
export type ResponseType = "result" | "stream" | "ack" | "none";
|
|
11
|
+
/**
|
|
12
|
+
* Method definition for registration.
|
|
13
|
+
*/
|
|
14
|
+
export interface MethodDefinition {
|
|
15
|
+
/** Expected response type */
|
|
16
|
+
response: ResponseType;
|
|
17
|
+
/** Codec for serialization (defaults to msgpack) */
|
|
18
|
+
codec?: Codec;
|
|
19
|
+
/** Can be cancelled via AbortSignal? */
|
|
20
|
+
cancellable?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Event definition for registration.
|
|
24
|
+
*/
|
|
25
|
+
export interface EventDefinition {
|
|
26
|
+
/** Codec for serialization (defaults to msgpack) */
|
|
27
|
+
codec?: Codec;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Client configuration options.
|
|
31
|
+
*/
|
|
32
|
+
export interface ClientOptions {
|
|
33
|
+
/** Default codec for methods and events */
|
|
34
|
+
defaultCodec?: Codec;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Method handler function type.
|
|
38
|
+
*/
|
|
39
|
+
export type MethodHandler<TData = unknown> = (data: TData, ctx: RequestContext) => void | Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Request context passed to method handlers.
|
|
42
|
+
*
|
|
43
|
+
* Provides methods to send responses back to parent.
|
|
44
|
+
*
|
|
45
|
+
* All response methods are async to properly handle backpressure.
|
|
46
|
+
* Always await these methods to prevent deadlocks with large payloads.
|
|
47
|
+
*/
|
|
48
|
+
export interface RequestContext {
|
|
49
|
+
/** Request ID for correlation */
|
|
50
|
+
readonly requestId: number;
|
|
51
|
+
/** Method name being handled */
|
|
52
|
+
readonly method: string;
|
|
53
|
+
/** Was request aborted by parent? */
|
|
54
|
+
readonly aborted: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Register callback to be called when request is aborted.
|
|
57
|
+
*/
|
|
58
|
+
onAbort(callback: () => void): void;
|
|
59
|
+
/**
|
|
60
|
+
* Send full response to parent.
|
|
61
|
+
* Sets IS_RESPONSE flag.
|
|
62
|
+
*
|
|
63
|
+
* @returns Promise that resolves when the response has been written
|
|
64
|
+
* and socket buffer has drained (if backpressure occurred).
|
|
65
|
+
*/
|
|
66
|
+
respond(data: unknown): Promise<void>;
|
|
67
|
+
/**
|
|
68
|
+
* Send acknowledgment to parent.
|
|
69
|
+
* Sets IS_RESPONSE | IS_ACK flags.
|
|
70
|
+
*
|
|
71
|
+
* @returns Promise that resolves when the response has been written
|
|
72
|
+
* and socket buffer has drained (if backpressure occurred).
|
|
73
|
+
*/
|
|
74
|
+
ack(data?: unknown): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Send stream chunk to parent.
|
|
77
|
+
* Sets IS_RESPONSE | IS_STREAM flags.
|
|
78
|
+
*
|
|
79
|
+
* @returns Promise that resolves when the chunk has been written
|
|
80
|
+
* and socket buffer has drained (if backpressure occurred).
|
|
81
|
+
*/
|
|
82
|
+
chunk(data: unknown): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* End stream.
|
|
85
|
+
* Sets IS_RESPONSE | IS_STREAM | STREAM_END flags.
|
|
86
|
+
*
|
|
87
|
+
* @returns Promise that resolves when the end marker has been written
|
|
88
|
+
* and socket buffer has drained (if backpressure occurred).
|
|
89
|
+
*/
|
|
90
|
+
end(): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Send error response to parent.
|
|
93
|
+
* Sets IS_RESPONSE | IS_ERROR flags.
|
|
94
|
+
*
|
|
95
|
+
* @returns Promise that resolves when the error has been written
|
|
96
|
+
* and socket buffer has drained (if backpressure occurred).
|
|
97
|
+
*/
|
|
98
|
+
error(err: Error | string): Promise<void>;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAE9C;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6BAA6B;IAC7B,QAAQ,EAAE,YAAY,CAAC;IACvB,oDAAoD;IACpD,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,wCAAwC;IACxC,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,KAAK,CAAC,EAAE,KAAK,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,YAAY,CAAC,EAAE,KAAK,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,CAAC,KAAK,GAAG,OAAO,IAAI,CAC3C,IAAI,EAAE,KAAK,EACX,GAAG,EAAE,cAAc,KAChB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1B;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,iCAAiC;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,gCAAgC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,qCAAqC;IACrC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAE1B;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAEpC;;;;;;OAMG;IACH,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtC;;;;;;OAMG;IACH,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC;;;;;;OAMG;IACH,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpC;;;;;;OAMG;IACH,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAErB;;;;;;OAMG;IACH,KAAK,CAAC,GAAG,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@procwire/client",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Child-side client for Procwire IPC",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.build.json",
|
|
20
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"clean": "rm -rf dist"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@procwire/protocol": "workspace:*",
|
|
26
|
+
"@procwire/codecs": "workspace:*"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"vitest": "^2.1.9"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"ipc",
|
|
33
|
+
"procwire",
|
|
34
|
+
"client",
|
|
35
|
+
"child-process"
|
|
36
|
+
],
|
|
37
|
+
"license": "MIT"
|
|
38
|
+
}
|