@oh-my-pi/pi-utils 15.13.2 → 15.13.3

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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.13.3] - 2026-06-15
6
+
7
+ ### Added
8
+
9
+ - Added `installWorkerInbox(port)` / `consumeWorkerInbox()` to `@oh-my-pi/pi-utils/worker-host`. A self-dispatching CLI host that imports a Bun worker module dynamically attaches the worker's real `message` listener after Bun flushes the messages the parent posted before spawn, dropping a synchronously-posted `init`. The host installs this buffering inbox synchronously in the entry's sync prefix so a listener exists at flush time; the worker module consumes it and binds the real handler, replaying anything buffered.
10
+
5
11
  ## [15.13.1] - 2026-06-15
6
12
 
7
13
  ### Added
@@ -2,3 +2,46 @@
2
2
  export declare function declareWorkerHostEntry(): void;
3
3
  /** Main-module path of the self-dispatching CLI host, or null outside it. */
4
4
  export declare function workerHostEntry(): string | null;
5
+ /**
6
+ * Buffers messages a Bun worker thread receives before its real handler is
7
+ * attached, then hands them off once it is.
8
+ *
9
+ * Bun delivers messages the parent posted before the worker spawned exactly
10
+ * once — when the worker entry module's top-level evaluation completes — to the
11
+ * `message` listeners present at that moment. A worker whose handler attaches
12
+ * via a later `await import(...)` therefore misses that flush. The
13
+ * self-dispatching CLI host imports each worker module dynamically from inside
14
+ * its argv dispatch, so the worker's own `parentPort.on("message")` lands after
15
+ * the flush and the parent's synchronously-posted `init` handshake is dropped —
16
+ * every run then stalls until the init timeout fires and silently falls back to
17
+ * the inline worker (issue: eval cells always taking the full timeout).
18
+ *
19
+ * The host calls {@link installWorkerInbox} synchronously in the entry's sync
20
+ * prefix (before importing the worker module) so a `parentPort` listener exists
21
+ * at flush time; the worker module then {@link consumeWorkerInbox}es it and
22
+ * binds the real handler, replaying anything buffered. Re-dispatching through
23
+ * `parentPort.emit("message", …)` is not an option — Bun's port is an
24
+ * `EventTarget` whose `emit` throws — so the inbox calls the handler directly.
25
+ */
26
+ export interface WorkerInbox {
27
+ /** Route buffered and subsequent messages to `handler`; returns an unbind fn. */
28
+ bind(handler: (message: unknown) => void): () => void;
29
+ }
30
+ /** Minimal `parentPort` surface the inbox needs (Node/Bun `MessagePort`). */
31
+ interface MessageListenerPort {
32
+ on(event: "message", listener: (value: unknown) => void): unknown;
33
+ }
34
+ /**
35
+ * Attach a buffering `message` listener on `port` synchronously and stash the
36
+ * resulting inbox for the worker module to {@link consumeWorkerInbox}. MUST be
37
+ * called in the entry module's synchronous prefix — before the worker module is
38
+ * imported — so the listener exists when Bun flushes pre-spawn messages.
39
+ */
40
+ export declare function installWorkerInbox(port: MessageListenerPort): WorkerInbox;
41
+ /**
42
+ * Take the inbox installed by {@link installWorkerInbox} for this worker, or
43
+ * `null` when the worker module was loaded directly (no host pre-buffering, so
44
+ * the module's own synchronous top-level listener already wins the flush).
45
+ */
46
+ export declare function consumeWorkerInbox(): WorkerInbox | null;
47
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-utils",
4
- "version": "15.13.2",
4
+ "version": "15.13.3",
5
5
  "description": "Shared utilities for pi packages",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -31,7 +31,7 @@
31
31
  "fmt": "biome format --write ."
32
32
  },
33
33
  "dependencies": {
34
- "@oh-my-pi/pi-natives": "15.13.2",
34
+ "@oh-my-pi/pi-natives": "15.13.3",
35
35
  "beautiful-mermaid": "^1.1.3",
36
36
  "handlebars": "^4.7.9",
37
37
  "winston": "^3.19.0",
@@ -17,3 +17,74 @@ export function declareWorkerHostEntry(): void {
17
17
  export function workerHostEntry(): string | null {
18
18
  return workerHostMain;
19
19
  }
20
+
21
+ /**
22
+ * Buffers messages a Bun worker thread receives before its real handler is
23
+ * attached, then hands them off once it is.
24
+ *
25
+ * Bun delivers messages the parent posted before the worker spawned exactly
26
+ * once — when the worker entry module's top-level evaluation completes — to the
27
+ * `message` listeners present at that moment. A worker whose handler attaches
28
+ * via a later `await import(...)` therefore misses that flush. The
29
+ * self-dispatching CLI host imports each worker module dynamically from inside
30
+ * its argv dispatch, so the worker's own `parentPort.on("message")` lands after
31
+ * the flush and the parent's synchronously-posted `init` handshake is dropped —
32
+ * every run then stalls until the init timeout fires and silently falls back to
33
+ * the inline worker (issue: eval cells always taking the full timeout).
34
+ *
35
+ * The host calls {@link installWorkerInbox} synchronously in the entry's sync
36
+ * prefix (before importing the worker module) so a `parentPort` listener exists
37
+ * at flush time; the worker module then {@link consumeWorkerInbox}es it and
38
+ * binds the real handler, replaying anything buffered. Re-dispatching through
39
+ * `parentPort.emit("message", …)` is not an option — Bun's port is an
40
+ * `EventTarget` whose `emit` throws — so the inbox calls the handler directly.
41
+ */
42
+ export interface WorkerInbox {
43
+ /** Route buffered and subsequent messages to `handler`; returns an unbind fn. */
44
+ bind(handler: (message: unknown) => void): () => void;
45
+ }
46
+
47
+ /** Minimal `parentPort` surface the inbox needs (Node/Bun `MessagePort`). */
48
+ interface MessageListenerPort {
49
+ on(event: "message", listener: (value: unknown) => void): unknown;
50
+ }
51
+
52
+ let pendingInbox: WorkerInbox | null = null;
53
+
54
+ /**
55
+ * Attach a buffering `message` listener on `port` synchronously and stash the
56
+ * resulting inbox for the worker module to {@link consumeWorkerInbox}. MUST be
57
+ * called in the entry module's synchronous prefix — before the worker module is
58
+ * imported — so the listener exists when Bun flushes pre-spawn messages.
59
+ */
60
+ export function installWorkerInbox(port: MessageListenerPort): WorkerInbox {
61
+ const queue: unknown[] = [];
62
+ let handler: ((message: unknown) => void) | null = null;
63
+ port.on("message", (data: unknown) => {
64
+ if (handler) handler(data);
65
+ else queue.push(data);
66
+ });
67
+ const inbox: WorkerInbox = {
68
+ bind(next) {
69
+ handler = next;
70
+ for (const data of queue) next(data);
71
+ queue.length = 0;
72
+ return () => {
73
+ if (handler === next) handler = null;
74
+ };
75
+ },
76
+ };
77
+ pendingInbox = inbox;
78
+ return inbox;
79
+ }
80
+
81
+ /**
82
+ * Take the inbox installed by {@link installWorkerInbox} for this worker, or
83
+ * `null` when the worker module was loaded directly (no host pre-buffering, so
84
+ * the module's own synchronous top-level listener already wins the flush).
85
+ */
86
+ export function consumeWorkerInbox(): WorkerInbox | null {
87
+ const inbox = pendingInbox;
88
+ pendingInbox = null;
89
+ return inbox;
90
+ }