posipaki 0.49.1 → 0.50.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/{actor-MIvmdVJD.js → fetch-DBJeP47R.js} +123 -2
- package/dist/fetch-DBJeP47R.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/port/index.js +2 -2
- package/dist/port/node.d.ts +5 -1
- package/dist/port/node.d.ts.map +1 -1
- package/dist/port/node.js +101 -80
- package/dist/port/node.js.map +1 -1
- package/dist/{port-0vYKSxBM.js → port-ByBSeVPm.js} +2 -111
- package/dist/port-ByBSeVPm.js.map +1 -0
- package/dist/process-eCAv2tIS.d.ts.map +1 -1
- package/dist/remote/index.d.ts +26 -1
- package/dist/remote/index.d.ts.map +1 -1
- package/dist/remote/index.js +41 -2
- package/dist/remote/index.js.map +1 -1
- package/dist/remote/node.d.ts +1 -1
- package/dist/remote/node.js +3 -3
- package/dist/{version-DNJbW_di.js → version-_BGyK0ZQ.js} +2 -2
- package/dist/{version-DNJbW_di.js.map → version-_BGyK0ZQ.js.map} +1 -1
- package/dist/{websocket-By6xlyLp.js → websocket-Dqv9GfDy.js} +2 -2
- package/dist/{websocket-By6xlyLp.js.map → websocket-Dqv9GfDy.js.map} +1 -1
- package/package.json +1 -1
- package/dist/actor-MIvmdVJD.js.map +0 -1
- package/dist/port-0vYKSxBM.js.map +0 -1
|
@@ -13,6 +13,9 @@ function held(inner) {
|
|
|
13
13
|
const failers = /* @__PURE__ */ new Set();
|
|
14
14
|
const kept = [];
|
|
15
15
|
let live = false;
|
|
16
|
+
/** The ending, kept for a reader that arrives after it: a channel that is over is not one to wait on. */
|
|
17
|
+
let ending = null;
|
|
18
|
+
let failed = null;
|
|
16
19
|
const emit = (frame) => {
|
|
17
20
|
for (const reader of [...readers]) reader(frame);
|
|
18
21
|
};
|
|
@@ -30,9 +33,11 @@ function held(inner) {
|
|
|
30
33
|
emit(frame);
|
|
31
34
|
});
|
|
32
35
|
inner.onClose((reason) => {
|
|
36
|
+
ending = { reason };
|
|
33
37
|
for (const closer of [...closers]) closer(reason);
|
|
34
38
|
});
|
|
35
39
|
inner.onError((error) => {
|
|
40
|
+
failed = { error };
|
|
36
41
|
for (const failer of [...failers]) failer(error);
|
|
37
42
|
});
|
|
38
43
|
return {
|
|
@@ -45,12 +50,20 @@ function held(inner) {
|
|
|
45
50
|
};
|
|
46
51
|
},
|
|
47
52
|
onClose(handler) {
|
|
53
|
+
if (ending !== null) {
|
|
54
|
+
handler(ending.reason);
|
|
55
|
+
return () => {};
|
|
56
|
+
}
|
|
48
57
|
closers.add(handler);
|
|
49
58
|
return () => {
|
|
50
59
|
closers.delete(handler);
|
|
51
60
|
};
|
|
52
61
|
},
|
|
53
62
|
onError(handler) {
|
|
63
|
+
if (failed !== null) {
|
|
64
|
+
handler(failed.error);
|
|
65
|
+
return () => {};
|
|
66
|
+
}
|
|
54
67
|
failers.add(handler);
|
|
55
68
|
return () => {
|
|
56
69
|
failers.delete(handler);
|
|
@@ -258,6 +271,114 @@ function portActor(spec) {
|
|
|
258
271
|
});
|
|
259
272
|
}
|
|
260
273
|
//#endregion
|
|
261
|
-
|
|
274
|
+
//#region src/port/fetch.ts
|
|
275
|
+
/**
|
|
276
|
+
* A URL as a channel of the body it answers with.
|
|
277
|
+
*
|
|
278
|
+
* Frames are the chunks of the body as they arrive, so a caller reads a stream rather than waiting for a whole
|
|
279
|
+
* response. `close()` aborts the request, and the far side hears it.
|
|
280
|
+
*/
|
|
281
|
+
async function fetchPort(url, options = {}) {
|
|
282
|
+
const controller = new AbortController();
|
|
283
|
+
const response = await answered(url, {
|
|
284
|
+
...options.init,
|
|
285
|
+
signal: controller.signal
|
|
286
|
+
}, options.connectMs);
|
|
287
|
+
const readers = /* @__PURE__ */ new Set();
|
|
288
|
+
const closers = /* @__PURE__ */ new Set();
|
|
289
|
+
const failers = /* @__PURE__ */ new Set();
|
|
290
|
+
const ending = { status: response.status };
|
|
291
|
+
let ended = false;
|
|
292
|
+
const end = () => {
|
|
293
|
+
if (ended) return;
|
|
294
|
+
ended = true;
|
|
295
|
+
for (const closer of [...closers]) closer(ending);
|
|
296
|
+
};
|
|
297
|
+
const frames = {
|
|
298
|
+
send: () => Promise.reject(/* @__PURE__ */ new Error("a response body has no input; send on the request instead")),
|
|
299
|
+
onFrame(handler) {
|
|
300
|
+
readers.add(handler);
|
|
301
|
+
return () => {
|
|
302
|
+
readers.delete(handler);
|
|
303
|
+
};
|
|
304
|
+
},
|
|
305
|
+
onClose(handler) {
|
|
306
|
+
if (ended) {
|
|
307
|
+
handler(ending);
|
|
308
|
+
return () => {};
|
|
309
|
+
}
|
|
310
|
+
closers.add(handler);
|
|
311
|
+
return () => {
|
|
312
|
+
closers.delete(handler);
|
|
313
|
+
};
|
|
314
|
+
},
|
|
315
|
+
onError(handler) {
|
|
316
|
+
failers.add(handler);
|
|
317
|
+
return () => {
|
|
318
|
+
failers.delete(handler);
|
|
319
|
+
};
|
|
320
|
+
},
|
|
321
|
+
async close() {
|
|
322
|
+
controller.abort();
|
|
323
|
+
await reader?.cancel().catch(() => void 0);
|
|
324
|
+
},
|
|
325
|
+
info: {
|
|
326
|
+
url,
|
|
327
|
+
status: response.status
|
|
328
|
+
},
|
|
329
|
+
control: () => {}
|
|
330
|
+
};
|
|
331
|
+
const reader = response.body?.getReader();
|
|
332
|
+
const port = held(frames);
|
|
333
|
+
const pump = async () => {
|
|
334
|
+
if (reader === void 0) {
|
|
335
|
+
end();
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
for (;;) {
|
|
340
|
+
const chunk = await reader.read();
|
|
341
|
+
if (chunk.done) {
|
|
342
|
+
end();
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
for (const target of [...readers]) target(chunk.value);
|
|
346
|
+
}
|
|
347
|
+
} catch (error) {
|
|
348
|
+
for (const failer of [...failers]) failer(error);
|
|
349
|
+
end();
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
pump();
|
|
353
|
+
return port;
|
|
354
|
+
}
|
|
355
|
+
/** The response head, or a rejection that says why there is none. */
|
|
356
|
+
/**
|
|
357
|
+
* A response that is not a stream: the far end answered, and the answer was a refusal.
|
|
358
|
+
*
|
|
359
|
+
* The status is here as a fact and in the sentence, because a caller that decides what to do next wants the
|
|
360
|
+
* number and a caller reading a log wants the words.
|
|
361
|
+
*/
|
|
362
|
+
var HttpRefusal = class extends Error {
|
|
363
|
+
constructor(status, message) {
|
|
364
|
+
super(message);
|
|
365
|
+
this.status = status;
|
|
366
|
+
this.name = "HttpRefusal";
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
async function answered(url, init, connectMs) {
|
|
370
|
+
const attempt = fetch(url, init).catch((error) => Promise.reject(named(url, error)));
|
|
371
|
+
const response = connectMs === void 0 ? await attempt : await Promise.race([attempt, new Promise((_resolve, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error(`fetchPort: ${url} answered nothing within ${connectMs}ms`)), connectMs))]);
|
|
372
|
+
if (!response.ok) {
|
|
373
|
+
const body = await response.text().catch(() => "");
|
|
374
|
+
throw new HttpRefusal(response.status, `fetchPort: ${url} answered ${response.status}${body === "" ? "" : `: ${body.slice(0, 200)}`}`);
|
|
375
|
+
}
|
|
376
|
+
return response;
|
|
377
|
+
}
|
|
378
|
+
function named(url, error) {
|
|
379
|
+
return /* @__PURE__ */ new Error(`fetchPort: ${url}: ${error instanceof Error ? error.message : String(error)}`);
|
|
380
|
+
}
|
|
381
|
+
//#endregion
|
|
382
|
+
export { held as a, splitChannel as i, fetchPort as n, portActor as r, HttpRefusal as t };
|
|
262
383
|
|
|
263
|
-
//# sourceMappingURL=
|
|
384
|
+
//# sourceMappingURL=fetch-DBJeP47R.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-DBJeP47R.js","names":[],"sources":["../src/port/held.ts","../src/port/split.ts","../src/port/actor.ts","../src/port/fetch.ts"],"sourcesContent":["// ── a channel that keeps what nobody was reading yet ────────────────────────\n//\n// A medium can deliver a frame in the same turn a reader is being installed, and a channel with no subscriber\n// drops it. This wraps a channel so nothing is dropped for want of a listener: what arrives while nobody is\n// subscribed is kept, and every subscriber that arrives before the first frame is delivered is given it.\n//\n// The window ends at the first delivery. After that the channel is live, and a subscriber that comes later has\n// missed what went before it — which is what any live channel means, and the alternative would be an unbounded\n// copy of everything ever said.\n\nimport type { Channel } from \"./channel.js\";\n\n/**\n * `inner`, with the frames that arrive before anyone is subscribed kept for the readers that follow.\n *\n * The kept frames are not bounded, because a bound depends on what the far end may say before anyone listens,\n * and that is not something this adapter can know.\n */\nexport function held<F, C>(inner: Channel<F, C>): Channel<F, C> {\n const readers = new Set<(frame: F) => void>();\n const closers = new Set<(reason: C) => void>();\n const failers = new Set<(error: unknown) => void>();\n const kept: F[] = [];\n let live = false;\n /** The ending, kept for a reader that arrives after it: a channel that is over is not one to wait on. */\n let ending: { reason: C } | null = null;\n let failed: { error: unknown } | null = null;\n\n const emit = (frame: F): void => {\n for (const reader of [...readers]) reader(frame);\n };\n\n inner.onFrame((frame) => {\n if (live) {\n emit(frame);\n return;\n }\n if (readers.size === 0) {\n kept.push(frame);\n return;\n }\n // Somebody is reading: from here the channel is live, and what was kept has been taken.\n live = true;\n kept.length = 0;\n emit(frame);\n });\n inner.onClose((reason) => {\n ending = { reason };\n for (const closer of [...closers]) closer(reason);\n });\n inner.onError((error) => {\n failed = { error };\n for (const failer of [...failers]) failer(error);\n });\n\n return {\n send: (frame) => inner.send(frame),\n onFrame(handler) {\n readers.add(handler);\n // Handed to every subscriber that arrives before the first delivery, not only to the first.\n for (const frame of kept) handler(frame);\n return () => {\n readers.delete(handler);\n };\n },\n onClose(handler) {\n if (ending !== null) {\n handler(ending.reason);\n return () => {};\n }\n closers.add(handler);\n return () => {\n closers.delete(handler);\n };\n },\n onError(handler) {\n if (failed !== null) {\n handler(failed.error);\n return () => {};\n }\n failers.add(handler);\n return () => {\n failers.delete(handler);\n };\n },\n close: (reason) => inner.close(reason),\n info: inner.info,\n control: (command) => inner.control(command),\n };\n}\n","// ── bytes into parts: the splitter ──────────────────────────────────────────\n//\n// Cuts a stream of bytes on 0x0a; what lies between two cuts is a line. Every byte medium does this: a stdio pair,\n// a socket, an HTTP body.\n\n// Splitting on the byte is safe for UTF-8: a continuation byte is never 0x0a, so a character cannot be cut in\n// half. A character that straddles two chunks waits in the buffer until the rest arrives.\n//\n// Text is decoded by a StringDecoder for the same reason: half a character waits rather than becoming two\n// replacement marks.\n//\n// A line may announce octets after it. The splitter does not know that rule, so it is passed in: given a line,\n// how many raw octets follow it? The seam answers with `$chunk`; a medium with no such thing answers none.\n\nimport { StringDecoder } from \"node:string_decoder\";\n\nimport type { Channel, Part } from \"./channel.js\";\nimport { held } from \"./held.js\";\n\nconst NL = 0x0a;\nconst CR = 0x0d;\n\n/** Given a line, how many raw octets follow it. Default: none, this is a plain line. */\nexport type Announcement = (line: string) => number | null;\n\nconst NOTHING: Announcement = () => null;\n\n/**\n * One byte channel in, one channel of parts out.\n *\n * The returned channel's `send` is the same rule backwards: a part becomes its line, a terminator, and the\n * octets it carried.\n */\nexport function splitChannel<C = string>(\n inner: Channel<Uint8Array, C>,\n announced: Announcement = NOTHING,\n): Channel<Part, C> {\n const readers = new Set<(part: Part) => void>();\n const closers = new Set<(reason: C) => void>();\n const failers = new Set<(error: unknown) => void>();\n const decoder = new StringDecoder(\"utf8\");\n /** Bytes that are not yet a frame: a line, or the octets a line announced. */\n let buffered = Buffer.alloc(0);\n /** The line whose announced octets are still arriving. */\n let pending: { line: string; bytes: number } | null = null;\n let ended = false;\n let reason: C | null = null;\n\n const emit = (part: Part): void => {\n for (const reader of [...readers]) reader(part);\n };\n\n /** The far end is gone: our own readers hear it. */\n const close = (why: C): void => {\n if (ended) return;\n ended = true;\n reason = why;\n for (const closer of [...closers]) closer(why);\n };\n\n /**\n * End it here: the far end hears a close, our own readers do not, because we are the one who asked. The\n * reason stays null: a sentence from this side is not `C`, which is what the other end had to say.\n */\n const shut = (): void => {\n if (ended) return;\n ended = true;\n };\n\n /** Hand over what the buffer holds, as far as the framing allows. */\n const drain = (): void => {\n while (!ended) {\n if (pending !== null) {\n if (buffered.length < pending.bytes) return;\n const bytes = buffered.subarray(0, pending.bytes);\n buffered = buffered.subarray(pending.bytes);\n const line = pending.line;\n pending = null;\n emit({ line, bytes: new Uint8Array(bytes) });\n continue;\n }\n const cut = buffered.indexOf(NL);\n if (cut === -1) return;\n const raw = buffered.subarray(0, cut);\n buffered = buffered.subarray(cut + 1);\n const line = raw.length > 0 && raw[raw.length - 1] === CR ? raw.subarray(0, raw.length - 1) : raw;\n const text = decoder.write(line);\n const count = announced(text);\n if (count === null) {\n emit({ line: text, bytes: null });\n continue;\n }\n pending = { line: text, bytes: count };\n if (count === 0) {\n pending = null;\n emit({ line: text, bytes: new Uint8Array(0) });\n }\n }\n };\n\n // Built and wrapped before the inner channel is read: the medium may have kept frames for its first\n // reader, and subscribing drains them at once — a part produced then must be a reader's to find, not the\n // wire's to lose.\n const parts: Channel<Part, C> = held<Part, C>({\n send: async (part: Part) => {\n const head = Buffer.from(`${part.line}\\n`, \"utf8\");\n const body = part.bytes === null ? head : Buffer.concat([head, Buffer.from(part.bytes)]);\n await inner.send(new Uint8Array(body));\n },\n onFrame: (handler) => {\n readers.add(handler);\n return () => {\n readers.delete(handler);\n };\n },\n onClose: (handler) => {\n if (ended && reason !== null) {\n handler(reason);\n return () => {};\n }\n closers.add(handler);\n return () => {\n closers.delete(handler);\n };\n },\n onError: (handler) => {\n failers.add(handler);\n return () => {\n failers.delete(handler);\n };\n },\n close: async (why = \"closed here\") => {\n shut();\n await inner.close(why);\n },\n info: inner.info,\n control: (command) => inner.control(command),\n });\n\n inner.onFrame((chunk) => {\n if (ended) return;\n buffered = buffered.length === 0 ? Buffer.from(chunk) : Buffer.concat([buffered, Buffer.from(chunk)]);\n drain();\n });\n inner.onError((error) => {\n for (const failer of [...failers]) failer(error);\n });\n inner.onClose((why) => {\n if (ended) return;\n // Whatever is left without a terminator is still a line: a peer that hangs up after writing has said\n // something, and dropping it would be the framing deciding to lose the last word.\n if (pending === null && buffered.length > 0) {\n const rest = buffered;\n buffered = Buffer.alloc(0);\n emit({ line: decoder.write(rest) + decoder.end(), bytes: null });\n } else {\n decoder.end();\n }\n close(why);\n });\n\n return parts;\n}\n","// ── a port as an actor ──────────────────────────────────────────────────────\n//\n// One actor that owns one channel and speaks for it: what it is told to send goes in, what the channel delivers\n// comes out as DATA, and the way the channel ended is left in the actor's state, where a parent reads anyway.\n//\n// The channel is opened in `setup`, which the runtime awaits, so an actor that is ready has a channel: a send is\n// never asked to wait for one, and a port that cannot open fails its start rather than looking alive. What a\n// channel is — a process, a socket, a pair in memory — is `open`'s business; the actor knows only that it has one.\n\nimport type { ActorDefinition } from \"../actor-types.js\";\nimport { defineActor, defineMessages } from \"../define-actor.js\";\nimport type { Message } from \"../types.js\";\nimport type { Channel } from \"./channel.js\";\n\n/** Write this to the channel. */\nexport interface SendMessage<F> extends Message {\n type: \"SEND\";\n data: F;\n}\n\n/** Ask the channel's own world to act on what is behind it; a medium without one ignores it. */\nexport interface SignalMessage extends Message {\n type: \"SIGNAL\";\n signal: string;\n}\n\n/** A frame the channel delivered. */\nexport interface DataMessage<F> extends Message {\n type: \"DATA\";\n data: F;\n}\n\nexport type PortIn<F> = SendMessage<F> | SignalMessage;\nexport type PortOut<F> = DataMessage<F>;\n\n/** What a parent may read of a port. The ending is here because it is state, not news. */\nexport interface PortPublic<C> {\n /** The channel is open. False before it opens and after it has ended. */\n alive: boolean;\n /** How it ended, or null while it has not. */\n end: C | null;\n /** What the medium knows about itself — a process its pid — as the channel reported it. */\n info: Record<string, unknown>;\n}\n\n/** What a medium's actor is handed: a name, and a way to open the channel it will own. */\nexport interface PortActorSpec<Args, F, C = string> {\n name: string;\n /** Opens the channel this actor owns. Called once, when the actor starts. */\n open(args: Args): Promise<Channel<F, C>>;\n}\n\ntype PortState<F, C> = {\n public: PortPublic<C>;\n private: { channel: Channel<F, C>; args: unknown };\n};\n\n/**\n * An actor over one channel.\n *\n * In `SEND {data}` writes to it and `SIGNAL {signal}` asks the medium to act on what is behind it, through\n * the channel's own `control`; posipaki's `STOP` ends the actor, which closes the channel. Out `DATA {data}`\n * for every frame. The channel's ending is left in `state.end`, what the medium knows about itself in\n * `state.info`, and then the actor stops.\n */\nexport function portActor<Args, F, C = string>(\n spec: PortActorSpec<Args, F, C>,\n): ActorDefinition<Args, PortState<F, C>, PortIn<F>, PortOut<F>, {}> {\n return defineActor({\n name: spec.name,\n inMessages: defineMessages<PortIn<F>>(),\n outMessages: defineMessages<PortOut<F>>(),\n setup: async (args: Args): Promise<PortState<F, C>> => {\n const channel = await spec.open(args);\n return {\n public: { alive: true, end: null, info: channel.info },\n private: { channel, args },\n };\n },\n afterStart() {\n const { channel } = this.state.private;\n channel.onFrame((data) => {\n this.emit({ type: \"DATA\", data });\n });\n channel.onClose((reason) => {\n this.state.public.alive = false;\n this.state.public.end = reason;\n this.exit();\n });\n },\n async beforeEnd() {\n this.state.public.alive = false;\n await this.state.private.channel.close();\n },\n handlers: {\n async SEND(msg: SendMessage<F>) {\n await this.state.private.channel.send(msg.data);\n },\n SIGNAL(msg: SignalMessage) {\n this.state.private.channel.control({ signal: msg.signal });\n },\n },\n });\n}\n","// ── an HTTP request as a port ───────────────────────────────────────────────\n//\n// The response body is the channel's frames, `close()` aborts the request, and the ending is what the response\n// said about itself. A request that never got a response is a rejection, the way a spawner that cannot connect\n// is: a non-2xx, a refused connection, a promise that never settles.\n\nimport type { Channel } from \"./channel.js\";\nimport { held } from \"./held.js\";\n\n/** What a response said when its body was over. */\nexport interface HttpEnd {\n /** The status the far end answered with. The frames that came before it are its body. */\n readonly status: number;\n}\n\nexport interface FetchPortOptions {\n /** Passed to `fetch`: method, headers, body. The request's own signal is not read; `close()` aborts. */\n init?: RequestInit;\n /** How long to wait for the response head. No bound by default: a stream may be long. */\n connectMs?: number;\n}\n\n/**\n * A URL as a channel of the body it answers with.\n *\n * Frames are the chunks of the body as they arrive, so a caller reads a stream rather than waiting for a whole\n * response. `close()` aborts the request, and the far side hears it.\n */\nexport async function fetchPort(url: string, options: FetchPortOptions = {}): Promise<Channel<Uint8Array, HttpEnd>> {\n const controller = new AbortController();\n const response = await answered(url, { ...options.init, signal: controller.signal }, options.connectMs);\n\n const readers = new Set<(frame: Uint8Array) => void>();\n const closers = new Set<(reason: HttpEnd) => void>();\n const failers = new Set<(error: unknown) => void>();\n const ending: HttpEnd = { status: response.status };\n let ended = false;\n\n const end = (): void => {\n if (ended) return;\n ended = true;\n for (const closer of [...closers]) closer(ending);\n };\n\n const frames: Channel<Uint8Array, HttpEnd> = {\n send: () => Promise.reject(new Error(\"a response body has no input; send on the request instead\")),\n onFrame(handler) {\n readers.add(handler);\n return () => {\n readers.delete(handler);\n };\n },\n onClose(handler) {\n if (ended) {\n handler(ending);\n return () => {};\n }\n closers.add(handler);\n return () => {\n closers.delete(handler);\n };\n },\n onError(handler) {\n failers.add(handler);\n return () => {\n failers.delete(handler);\n };\n },\n async close() {\n controller.abort();\n await reader?.cancel().catch(() => undefined);\n },\n info: { url, status: response.status },\n control: () => {},\n };\n\n const reader = response.body?.getReader();\n // Wrapped before the first read: a body can deliver a chunk in the same turn the port is handed over.\n const port = held(frames);\n\n const pump = async (): Promise<void> => {\n if (reader === undefined) {\n end();\n return;\n }\n try {\n for (;;) {\n const chunk = await reader.read();\n if (chunk.done) {\n end();\n return;\n }\n for (const target of [...readers]) target(chunk.value);\n }\n } catch (error) {\n for (const failer of [...failers]) failer(error);\n end();\n }\n };\n void pump();\n\n return port;\n}\n\n/** The response head, or a rejection that says why there is none. */\n/**\n * A response that is not a stream: the far end answered, and the answer was a refusal.\n *\n * The status is here as a fact and in the sentence, because a caller that decides what to do next wants the\n * number and a caller reading a log wants the words.\n */\nexport class HttpRefusal extends Error {\n constructor(\n readonly status: number,\n message: string,\n ) {\n super(message);\n this.name = \"HttpRefusal\";\n }\n}\n\nasync function answered(url: string, init: RequestInit, connectMs: number | undefined): Promise<Response> {\n const attempt = fetch(url, init).catch((error: unknown) => Promise.reject(named(url, error)));\n const response =\n connectMs === undefined\n ? await attempt\n : await Promise.race([\n attempt,\n new Promise<never>((_resolve, reject) =>\n setTimeout(() => reject(new Error(`fetchPort: ${url} answered nothing within ${connectMs}ms`)), connectMs),\n ),\n ]);\n\n if (!response.ok) {\n const body = await response.text().catch(() => \"\");\n throw new HttpRefusal(\n response.status,\n `fetchPort: ${url} answered ${response.status}${body === \"\" ? \"\" : `: ${body.slice(0, 200)}`}`,\n );\n }\n return response;\n}\n\nfunction named(url: string, error: unknown): Error {\n return new Error(`fetchPort: ${url}: ${error instanceof Error ? error.message : String(error)}`);\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ declare function defineActor<Args, InternalState, InMsg extends Message, OutMsg
|
|
|
20
20
|
//#endregion
|
|
21
21
|
//#region src/version.d.ts
|
|
22
22
|
/** posipaki's release version. Must equal package.json's `version`. */
|
|
23
|
-
declare const LIB_VERSION = "0.
|
|
23
|
+
declare const LIB_VERSION = "0.50.0";
|
|
24
24
|
//#endregion
|
|
25
25
|
export { type ActorConfig, type ActorContext, type ActorDecorated, type ActorDefinition, type ActorPlugin, type ActorReflection, type AnyProcess, AsyncProcess, type AsyncProcessFn, type ExitMessage, type HandlerFn, type HandlerOptions, type HookResult, LIB_VERSION, type Message, type MethodOptions, type PluginTransform, Process, type ProcessCtx, type ProcessFn, type SenderInfo, type SenderOrigin, type SpawnedFrom, type StopMessage, type WithSender, type WithoutSender, asyncify, callHook, chainHook, defineActor, defineMessages, mergeConfigs, propagateError, runDispatch, runDispatchAsync, spawn, spawnAsync, stopPropagation };
|
|
26
26
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { i as runDispatch } from "./util-Brtfgv68.js";
|
|
|
2
2
|
import { a as spawnAsync, i as runDispatchAsync, o as asyncify, t as AsyncProcess } from "./process.async-B1FqZyD1.js";
|
|
3
3
|
import { n as chainHook, o as propagateError, r as mergeConfigs, s as stopPropagation, t as callHook } from "./hooks-CgXhmY4r.js";
|
|
4
4
|
import { n as defineMessages, t as defineActor } from "./define-actor-DR1P1PA5.js";
|
|
5
|
-
import { t as LIB_VERSION } from "./version-
|
|
5
|
+
import { t as LIB_VERSION } from "./version-_BGyK0ZQ.js";
|
|
6
6
|
//#region src/process.ts
|
|
7
7
|
/** @deprecated Use {@link spawnAsync} instead. */
|
|
8
8
|
function spawn(fn, pname, tp) {
|
package/dist/port/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import {
|
|
1
|
+
import { a as streamChannel, i as settled, n as portReactor, o as memoryChannelPair, r as all, s as ChannelClosed, t as duplexChannel } from "../port-ByBSeVPm.js";
|
|
2
|
+
import { a as held, i as splitChannel, n as fetchPort, r as portActor, t as HttpRefusal } from "../fetch-DBJeP47R.js";
|
|
3
3
|
export { ChannelClosed, HttpRefusal, all, duplexChannel, fetchPort, held, memoryChannelPair, portActor, portReactor, settled, splitChannel, streamChannel };
|
package/dist/port/node.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { l as ActorDefinition } from "../hooks-DO-efRM_.js";
|
|
2
2
|
import { c as PortIn, l as PortOut, m as Channel, t as PortListener, u as PortPublic } from "../reactor-4MaIKVnw.js";
|
|
3
|
+
import { Server } from "node:http";
|
|
3
4
|
//#region src/port/node/spawn.d.ts
|
|
4
5
|
/** How a process ended: it exited with a code, or a signal terminated it. */
|
|
5
6
|
type ProcessEnd = {
|
|
@@ -34,12 +35,15 @@ interface HttpListenerOptions<Meta> {
|
|
|
34
35
|
/** A port listener that is also an HTTP server: the port it bound, and the pairs it hands out. */
|
|
35
36
|
interface HttpListener<Meta> extends PortListener<Uint8Array, Uint8Array, Meta> {
|
|
36
37
|
readonly port: number;
|
|
38
|
+
/** The server itself, for what only a consumer of it can do — a WebSocket upgrade, a second route. */
|
|
39
|
+
readonly server: Server;
|
|
37
40
|
}
|
|
38
41
|
/**
|
|
39
42
|
* A server whose requests arrive as pairs of ports.
|
|
40
43
|
*
|
|
41
44
|
* `identify` runs before a pair exists, so a refused request costs no stream and the caller reads a status with
|
|
42
|
-
* the reason in it. The request body is the inbound port; the response body is the outbound one
|
|
45
|
+
* the reason in it. The request body is the inbound port; the response body is the outbound one, and the head
|
|
46
|
+
* is one command on it.
|
|
43
47
|
*/
|
|
44
48
|
declare function httpListener<Meta = unknown>(options?: HttpListenerOptions<Meta>): HttpListener<Meta>;
|
|
45
49
|
//#endregion
|
package/dist/port/node.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.ts","names":[],"sources":["../../src/port/node/spawn.ts","../../src/port/node/listener.ts","../../src/port/node/process.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"node.d.ts","names":[],"sources":["../../src/port/node/spawn.ts","../../src/port/node/listener.ts","../../src/port/node/process.ts"],"mappings":";;;;;KAcY;WACG;WAAuB;;WACvB;WAAyB;;UACvB;EACf;EACA,MAAM,OAAO;;EAEb;;UAEe;WACN,QAAQ,QAAQ,YAAY;WAC5B,QAAQ,QAAQ,YAAY;;;iBAIvB,UACd,mBACA,UAAS,mBACR,QAAQ,QAAQ,YAAY;;iBAIT,WACpB,mBACA,UAAS,mBACR,QAAQ;;;UCxBM,oBAAoB;EACnC;EACA;;EAEA,UAAU,SAAS,UAAU,OAAO,QAAQ;;;UAI7B,aAAa,cAAc,aAAa,YAAY,YAAY;WACtE;;WAEA,QAAQ;;;;;;;;;iBAgBH,aAAa,gBAAgB,UAAS,oBAAoB,QAAa,aAAa;;;;UC9BnF;EACf;EACA;EACA,MAAM,OAAO;;EAEb;;;cAiDW,YAGX,gBAHqB;;;;;;GAGrB,OAAA,WAAA,mBAAA,QAAA,WAAA;;cAGW,UAGX,gBAHmB;;;;;;mBAGnB"}
|
package/dist/port/node.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as held, i as splitChannel, r as portActor, t as HttpRefusal } from "../fetch-DBJeP47R.js";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
+
import { createServer } from "node:http";
|
|
3
4
|
//#region src/port/node/spawn.ts
|
|
4
5
|
const DEFAULT_KILL_AFTER_MS = 5e3;
|
|
5
6
|
/** A command as a channel of bytes: stdout in, stdin out, and its exit when it closes. */
|
|
@@ -154,15 +155,13 @@ async function shutdown(child, killAfterMs) {
|
|
|
154
155
|
* A server whose requests arrive as pairs of ports.
|
|
155
156
|
*
|
|
156
157
|
* `identify` runs before a pair exists, so a refused request costs no stream and the caller reads a status with
|
|
157
|
-
* the reason in it. The request body is the inbound port; the response body is the outbound one
|
|
158
|
+
* the reason in it. The request body is the inbound port; the response body is the outbound one, and the head
|
|
159
|
+
* is one command on it.
|
|
158
160
|
*/
|
|
159
161
|
function httpListener(options = {}) {
|
|
160
|
-
const runtime = globalThis.Bun;
|
|
161
|
-
if (runtime === void 0) throw new Error("httpListener: this needs Bun's server");
|
|
162
162
|
const waiting = [];
|
|
163
163
|
const buffered = [];
|
|
164
164
|
let closed = false;
|
|
165
|
-
let server = null;
|
|
166
165
|
const take = (pair) => {
|
|
167
166
|
const next = waiting.shift();
|
|
168
167
|
if (next === void 0) {
|
|
@@ -171,29 +170,36 @@ function httpListener(options = {}) {
|
|
|
171
170
|
}
|
|
172
171
|
next.accept(pair);
|
|
173
172
|
};
|
|
174
|
-
server =
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
173
|
+
const server = createServer((request, response) => {
|
|
174
|
+
(async () => {
|
|
175
|
+
if (closed) {
|
|
176
|
+
response.writeHead(503).end("the listener is closed");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
179
|
let meta;
|
|
180
180
|
try {
|
|
181
|
-
meta = options.identify === void 0 ? void 0 : await options.identify(request);
|
|
181
|
+
meta = options.identify === void 0 ? void 0 : await options.identify(asRequest(request));
|
|
182
182
|
} catch (error) {
|
|
183
|
-
|
|
183
|
+
const status = error instanceof HttpRefusal ? error.status : 401;
|
|
184
|
+
response.writeHead(status).end(error instanceof Error ? error.message : String(error));
|
|
185
|
+
return;
|
|
184
186
|
}
|
|
185
|
-
const outbound = responsePort();
|
|
187
|
+
const outbound = responsePort(response);
|
|
186
188
|
take({
|
|
187
189
|
inbound: requestPort(request),
|
|
188
|
-
outbound
|
|
190
|
+
outbound,
|
|
189
191
|
meta
|
|
190
192
|
});
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
+
})();
|
|
193
194
|
});
|
|
195
|
+
server.listen(options.port ?? 0, options.hostname);
|
|
194
196
|
return {
|
|
195
197
|
get port() {
|
|
196
|
-
|
|
198
|
+
const address = server.address();
|
|
199
|
+
return typeof address === "object" && address !== null ? address.port : 0;
|
|
200
|
+
},
|
|
201
|
+
get server() {
|
|
202
|
+
return server;
|
|
197
203
|
},
|
|
198
204
|
accept() {
|
|
199
205
|
const next = buffered.shift();
|
|
@@ -209,11 +215,20 @@ function httpListener(options = {}) {
|
|
|
209
215
|
async close() {
|
|
210
216
|
closed = true;
|
|
211
217
|
for (const next of waiting.splice(0)) next.refuse(/* @__PURE__ */ new Error("httpListener: closed"));
|
|
212
|
-
|
|
213
|
-
server
|
|
218
|
+
server.closeAllConnections?.();
|
|
219
|
+
await new Promise((done) => server.close(() => done()));
|
|
214
220
|
}
|
|
215
221
|
};
|
|
216
222
|
}
|
|
223
|
+
/** The request as `identify` sees it: method, url and headers, and no body — the body is the inbound port. */
|
|
224
|
+
function asRequest(request) {
|
|
225
|
+
const host = request.headers.host ?? "localhost";
|
|
226
|
+
const method = request.method ?? "GET";
|
|
227
|
+
return new Request(`http://${host}${request.url ?? "/"}`, {
|
|
228
|
+
method,
|
|
229
|
+
headers: Object.entries(request.headers).flatMap(([name, value]) => value === void 0 ? [] : [[name, Array.isArray(value) ? value.join(", ") : value]])
|
|
230
|
+
});
|
|
231
|
+
}
|
|
217
232
|
/** The request's body as the inbound port: frames as they arrive, ended when the body is. */
|
|
218
233
|
function requestPort(request) {
|
|
219
234
|
const readers = /* @__PURE__ */ new Set();
|
|
@@ -222,7 +237,7 @@ function requestPort(request) {
|
|
|
222
237
|
const end = () => {
|
|
223
238
|
if (ended) return;
|
|
224
239
|
ended = true;
|
|
225
|
-
for (const closer of
|
|
240
|
+
for (const closer of closers) closer(void 0);
|
|
226
241
|
};
|
|
227
242
|
const inbound = {
|
|
228
243
|
send: () => Promise.reject(/* @__PURE__ */ new Error("a request body has no input; that side is the caller's")),
|
|
@@ -246,84 +261,90 @@ function requestPort(request) {
|
|
|
246
261
|
return () => {};
|
|
247
262
|
},
|
|
248
263
|
close: async () => {
|
|
249
|
-
await request.body?.cancel().catch(() => void 0);
|
|
250
264
|
end();
|
|
265
|
+
request.destroy();
|
|
251
266
|
},
|
|
252
267
|
info: {
|
|
253
|
-
method: request.method,
|
|
254
|
-
url: request.url
|
|
268
|
+
method: request.method ?? "GET",
|
|
269
|
+
url: request.url ?? "/"
|
|
255
270
|
},
|
|
256
271
|
control: () => {}
|
|
257
272
|
};
|
|
258
|
-
const reader = request.body?.getReader();
|
|
259
273
|
const port = held(inbound);
|
|
260
|
-
(
|
|
261
|
-
if (
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
end();
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
for (const target of [...readers]) target(chunk.value);
|
|
272
|
-
}
|
|
273
|
-
})();
|
|
274
|
+
request.on("data", (chunk) => {
|
|
275
|
+
if (ended) return;
|
|
276
|
+
const bytes = new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
277
|
+
for (const target of readers) target(bytes);
|
|
278
|
+
});
|
|
279
|
+
request.on("end", end);
|
|
280
|
+
request.on("close", end);
|
|
281
|
+
request.on("error", end);
|
|
274
282
|
return port;
|
|
275
283
|
}
|
|
276
|
-
/**
|
|
277
|
-
|
|
284
|
+
/**
|
|
285
|
+
* The response's channel: the head is one command, the body is what `send` writes, and `close` ends it.
|
|
286
|
+
*
|
|
287
|
+
* `node:http` is told the head separately from the body, which is why a handler states it before the first byte,
|
|
288
|
+
* and why a handler that writes without stating one gets the 200 the wire would have given anyway.
|
|
289
|
+
*/
|
|
290
|
+
function responsePort(response) {
|
|
278
291
|
const readers = /* @__PURE__ */ new Set();
|
|
279
|
-
|
|
292
|
+
let stated = false;
|
|
280
293
|
let ended = false;
|
|
294
|
+
const state = (head) => {
|
|
295
|
+
if (stated) return;
|
|
296
|
+
stated = true;
|
|
297
|
+
response.writeHead(head.status, head.headers);
|
|
298
|
+
};
|
|
281
299
|
const end = () => {
|
|
282
300
|
if (ended) return;
|
|
283
301
|
ended = true;
|
|
284
|
-
for (const
|
|
302
|
+
for (const reader of readers) reader(void 0);
|
|
285
303
|
};
|
|
304
|
+
response.on("close", end);
|
|
286
305
|
return {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
return () => {};
|
|
294
|
-
},
|
|
295
|
-
onClose(handler) {
|
|
296
|
-
if (ended) {
|
|
297
|
-
handler(void 0);
|
|
298
|
-
return () => {};
|
|
299
|
-
}
|
|
300
|
-
closers.add(handler);
|
|
301
|
-
return () => {
|
|
302
|
-
closers.delete(handler);
|
|
303
|
-
};
|
|
304
|
-
},
|
|
305
|
-
onError() {
|
|
306
|
-
return () => {};
|
|
307
|
-
},
|
|
308
|
-
async close() {
|
|
309
|
-
end();
|
|
310
|
-
},
|
|
311
|
-
info: {},
|
|
312
|
-
control: () => {}
|
|
306
|
+
async send(frame) {
|
|
307
|
+
if (ended) throw new Error("this response is over");
|
|
308
|
+
if (!stated) state({ status: 200 });
|
|
309
|
+
await new Promise((done) => {
|
|
310
|
+
response.write(frame, () => done());
|
|
311
|
+
});
|
|
313
312
|
},
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
});
|
|
322
|
-
},
|
|
323
|
-
cancel() {
|
|
324
|
-
end();
|
|
313
|
+
onFrame() {
|
|
314
|
+
return () => {};
|
|
315
|
+
},
|
|
316
|
+
onClose(handler) {
|
|
317
|
+
if (ended) {
|
|
318
|
+
handler(void 0);
|
|
319
|
+
return () => {};
|
|
325
320
|
}
|
|
326
|
-
|
|
321
|
+
readers.add(handler);
|
|
322
|
+
return () => {
|
|
323
|
+
readers.delete(handler);
|
|
324
|
+
};
|
|
325
|
+
},
|
|
326
|
+
onError() {
|
|
327
|
+
return () => {};
|
|
328
|
+
},
|
|
329
|
+
async close() {
|
|
330
|
+
if (ended) return;
|
|
331
|
+
if (!stated) state({ status: 200 });
|
|
332
|
+
ended = true;
|
|
333
|
+
await new Promise((done) => {
|
|
334
|
+
response.end(() => done());
|
|
335
|
+
});
|
|
336
|
+
},
|
|
337
|
+
info: {},
|
|
338
|
+
control(command) {
|
|
339
|
+
const head = command.head;
|
|
340
|
+
if (typeof head !== "object" || head === null) return;
|
|
341
|
+
const status = head.status;
|
|
342
|
+
const headers = head.headers;
|
|
343
|
+
state({
|
|
344
|
+
status: typeof status === "number" ? status : 200,
|
|
345
|
+
...typeof headers === "object" && headers !== null ? { headers } : {}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
327
348
|
};
|
|
328
349
|
}
|
|
329
350
|
//#endregion
|