@intx/workflow-host 0.2.2
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 +176 -0
- package/README.md +287 -0
- package/dist/adapters/blob-substrate.d.ts +49 -0
- package/dist/adapters/blob-substrate.js +140 -0
- package/dist/adapters/repo-store.d.ts +39 -0
- package/dist/adapters/repo-store.js +344 -0
- package/dist/adapters/spawn-child.d.ts +74 -0
- package/dist/adapters/spawn-child.js +152 -0
- package/dist/adapters/step-invoker.d.ts +114 -0
- package/dist/adapters/step-invoker.js +360 -0
- package/dist/child/env-bootstrap.d.ts +56 -0
- package/dist/child/env-bootstrap.js +120 -0
- package/dist/child/from-process-env.d.ts +127 -0
- package/dist/child/from-process-env.js +183 -0
- package/dist/child/index.d.ts +9 -0
- package/dist/child/index.js +9 -0
- package/dist/child/outbound-mail-bridge.d.ts +36 -0
- package/dist/child/outbound-mail-bridge.js +143 -0
- package/dist/child/proxy-repo-store.d.ts +27 -0
- package/dist/child/proxy-repo-store.js +200 -0
- package/dist/child/run-child.d.ts +320 -0
- package/dist/child/run-child.js +900 -0
- package/dist/child/self-discovery.d.ts +29 -0
- package/dist/child/self-discovery.js +57 -0
- package/dist/child/substrate-write-bridge.d.ts +72 -0
- package/dist/child/substrate-write-bridge.js +188 -0
- package/dist/child/supervisor-backed-transport.d.ts +10 -0
- package/dist/child/supervisor-backed-transport.js +113 -0
- package/dist/child/warm-agent-cache.d.ts +78 -0
- package/dist/child/warm-agent-cache.js +112 -0
- package/dist/drain-controller.d.ts +37 -0
- package/dist/drain-controller.js +46 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/ipc/control-channel.d.ts +336 -0
- package/dist/ipc/control-channel.js +532 -0
- package/dist/ipc/crypto.d.ts +46 -0
- package/dist/ipc/crypto.js +126 -0
- package/dist/ipc/envelope.d.ts +53 -0
- package/dist/ipc/envelope.js +88 -0
- package/dist/ipc/event-channel.d.ts +677 -0
- package/dist/ipc/event-channel.js +278 -0
- package/dist/ipc/index.d.ts +4 -0
- package/dist/ipc/index.js +143 -0
- package/dist/mail-bus/hub-transport-adapter.d.ts +30 -0
- package/dist/mail-bus/hub-transport-adapter.js +76 -0
- package/dist/mail-bus/index.d.ts +1 -0
- package/dist/mail-bus/index.js +1 -0
- package/dist/seams/index.d.ts +3 -0
- package/dist/seams/index.js +3 -0
- package/dist/seams/scheduler-adapter.d.ts +3 -0
- package/dist/seams/scheduler-adapter.js +24 -0
- package/dist/seams/scheduler.d.ts +94 -0
- package/dist/seams/scheduler.js +397 -0
- package/dist/seams/signal-channel.d.ts +74 -0
- package/dist/seams/signal-channel.js +304 -0
- package/dist/supervisor/cancel-signing.d.ts +68 -0
- package/dist/supervisor/cancel-signing.js +144 -0
- package/dist/supervisor/child-termination.d.ts +51 -0
- package/dist/supervisor/child-termination.js +76 -0
- package/dist/supervisor/credentials.d.ts +101 -0
- package/dist/supervisor/credentials.js +153 -0
- package/dist/supervisor/dispatch-attribution.d.ts +37 -0
- package/dist/supervisor/dispatch-attribution.js +114 -0
- package/dist/supervisor/drain-timeout.d.ts +127 -0
- package/dist/supervisor/drain-timeout.js +231 -0
- package/dist/supervisor/index.d.ts +7 -0
- package/dist/supervisor/index.js +6 -0
- package/dist/supervisor/recycle.d.ts +212 -0
- package/dist/supervisor/recycle.js +440 -0
- package/dist/supervisor/run-event-compaction.d.ts +34 -0
- package/dist/supervisor/run-event-compaction.js +115 -0
- package/dist/supervisor/spawn-env.d.ts +39 -0
- package/dist/supervisor/spawn-env.js +36 -0
- package/dist/supervisor/supervisor.d.ts +202 -0
- package/dist/supervisor/supervisor.js +2244 -0
- package/dist/supervisor/terminal-broadcaster.d.ts +45 -0
- package/dist/supervisor/terminal-broadcaster.js +184 -0
- package/dist/supervisor/types.d.ts +542 -0
- package/dist/supervisor/types.js +10 -0
- package/package.json +35 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// Child-side outbound-mail bridge (OUTBOUND half of mailbox ownership,
|
|
2
|
+
// §3a).
|
|
3
|
+
//
|
|
4
|
+
// The workflow-process child holds no signing key for the agent's
|
|
5
|
+
// identity. The supervisor is the sole mail owner: it holds the host
|
|
6
|
+
// transport against which the agent's address is registered with its
|
|
7
|
+
// `CryptoProvider`, and it is the only process that can emit signed mail
|
|
8
|
+
// on the agent's behalf. So a step agent never calls `transport.send`
|
|
9
|
+
// directly; its mail tools are backed by a transport whose outbound side
|
|
10
|
+
// routes through this bridge, which forwards the structured
|
|
11
|
+
// `OutboundMessage` plus the sender (agent) address up over the control
|
|
12
|
+
// IPC. The supervisor performs the actual signed send and replies with
|
|
13
|
+
// the `SendReceipt`.
|
|
14
|
+
//
|
|
15
|
+
// Lifecycle of one outbound send:
|
|
16
|
+
//
|
|
17
|
+
// 1. The agent's mail tool (or the step reply path) calls the
|
|
18
|
+
// supervisor-backed transport's `send`. The transport calls
|
|
19
|
+
// `bridge.submit(senderAddress, message)`.
|
|
20
|
+
// 2. `submit` mints a `requestId`, registers a pending awaiter, and
|
|
21
|
+
// emits `outbound.message` upstream carrying the JSON-projected
|
|
22
|
+
// message (attachment bytes base64-encoded).
|
|
23
|
+
// 3. The supervisor receives the request, performs the signed send
|
|
24
|
+
// through the host transport (`MailBusBindings.sendOutbound`), and
|
|
25
|
+
// replies with `outbound.result` carrying the `SendReceipt` (or a
|
|
26
|
+
// structured failure).
|
|
27
|
+
// 4. The bridge resolves / rejects the pending awaiter; the
|
|
28
|
+
// transport's `send` returns the receipt to the mail tool. A
|
|
29
|
+
// supervisor-side failure (unregistered sender, signing failure,
|
|
30
|
+
// transport rejection) surfaces as a rejection so the agent's
|
|
31
|
+
// mail-tool call fails loudly rather than silently dropping the
|
|
32
|
+
// send.
|
|
33
|
+
import { getLogger } from "@intx/log";
|
|
34
|
+
import { base64Encode } from "@intx/types";
|
|
35
|
+
const logger = getLogger(["workflow-host", "child", "outbound-mail-bridge"]);
|
|
36
|
+
/**
|
|
37
|
+
* Construct the child-side outbound-mail bridge. Pending sends live in
|
|
38
|
+
* a map keyed by `requestId`; the bridge resolves the awaiter when the
|
|
39
|
+
* supervisor's matching `outbound.result` lands.
|
|
40
|
+
*/
|
|
41
|
+
export function createChildOutboundMailBridge(opts) {
|
|
42
|
+
const pending = new Map();
|
|
43
|
+
const allocate = opts.allocateRequestId ?? defaultRequestIdAllocator();
|
|
44
|
+
return {
|
|
45
|
+
get pendingCount() {
|
|
46
|
+
return pending.size;
|
|
47
|
+
},
|
|
48
|
+
async submit(senderAddress, message) {
|
|
49
|
+
const requestId = allocate();
|
|
50
|
+
const resultPromise = new Promise((resolve, reject) => {
|
|
51
|
+
pending.set(requestId, { resolve, reject });
|
|
52
|
+
});
|
|
53
|
+
try {
|
|
54
|
+
await opts.upstreamSender.send({
|
|
55
|
+
type: "outbound.message",
|
|
56
|
+
data: {
|
|
57
|
+
requestId,
|
|
58
|
+
senderAddress,
|
|
59
|
+
message: projectOutboundMessage(message),
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (cause) {
|
|
64
|
+
pending.delete(requestId);
|
|
65
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
66
|
+
throw new Error(`workflow-child outbound mail: upstream send failed for requestId ${requestId}: ${reason}`, { cause });
|
|
67
|
+
}
|
|
68
|
+
return resultPromise;
|
|
69
|
+
},
|
|
70
|
+
handleResult(data) {
|
|
71
|
+
const entry = pending.get(data.requestId);
|
|
72
|
+
if (entry === undefined) {
|
|
73
|
+
logger.warn `outbound.result landed with no pending entry; requestId=${data.requestId} dropped`;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
pending.delete(data.requestId);
|
|
77
|
+
if (data.result.ok) {
|
|
78
|
+
entry.resolve({
|
|
79
|
+
messageId: data.result.messageId,
|
|
80
|
+
status: data.result.status,
|
|
81
|
+
});
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
entry.reject(new Error(`workflow-child outbound mail (requestId=${data.requestId}) rejected by supervisor: ${data.result.reason}`));
|
|
85
|
+
},
|
|
86
|
+
cancelAll(reason) {
|
|
87
|
+
for (const [requestId, entry] of pending) {
|
|
88
|
+
entry.reject(new Error(`workflow-child outbound mail (requestId=${requestId}) cancelled: ${reason}`));
|
|
89
|
+
}
|
|
90
|
+
pending.clear();
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Project a runtime `OutboundMessage` into the IPC wire shape. Optional
|
|
96
|
+
* fields are omitted when absent (the wire validator spells them
|
|
97
|
+
* optional), and attachment bytes ride base64-encoded so the NDJSON
|
|
98
|
+
* control channel stays text-safe.
|
|
99
|
+
*/
|
|
100
|
+
function projectOutboundMessage(message) {
|
|
101
|
+
const payload = {
|
|
102
|
+
to: message.to,
|
|
103
|
+
type: message.type,
|
|
104
|
+
};
|
|
105
|
+
if (message.cc !== undefined)
|
|
106
|
+
payload.cc = message.cc;
|
|
107
|
+
if (message.subject !== undefined)
|
|
108
|
+
payload.subject = message.subject;
|
|
109
|
+
if (message.content !== undefined)
|
|
110
|
+
payload.content = message.content;
|
|
111
|
+
if (message.payload !== undefined)
|
|
112
|
+
payload.payload = message.payload;
|
|
113
|
+
if (message.summary !== undefined)
|
|
114
|
+
payload.summary = message.summary;
|
|
115
|
+
if (message.inReplyTo !== undefined)
|
|
116
|
+
payload.inReplyTo = message.inReplyTo;
|
|
117
|
+
if (message.correlationId !== undefined) {
|
|
118
|
+
payload.correlationId = message.correlationId;
|
|
119
|
+
}
|
|
120
|
+
if (message.sessionId !== undefined)
|
|
121
|
+
payload.sessionId = message.sessionId;
|
|
122
|
+
if (message.tenantId !== undefined)
|
|
123
|
+
payload.tenantId = message.tenantId;
|
|
124
|
+
if (message.attachments !== undefined) {
|
|
125
|
+
payload.attachments = message.attachments.map((a) => ({
|
|
126
|
+
name: a.name,
|
|
127
|
+
contentType: a.contentType,
|
|
128
|
+
dataBase64: bytesToBase64(a.data),
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
return payload;
|
|
132
|
+
}
|
|
133
|
+
function bytesToBase64(bytes) {
|
|
134
|
+
return base64Encode(bytes);
|
|
135
|
+
}
|
|
136
|
+
function defaultRequestIdAllocator() {
|
|
137
|
+
let counter = 0;
|
|
138
|
+
return () => {
|
|
139
|
+
counter += 1;
|
|
140
|
+
const rand = Math.random().toString(36).slice(2, 10);
|
|
141
|
+
return `om-${String(counter)}-${rand}`;
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { RepoId, RepoStore } from "@intx/hub-sessions/substrate";
|
|
2
|
+
import type { ChildSubstrateWriteBridge } from "./substrate-write-bridge.js";
|
|
3
|
+
export interface CreateProxyWorkflowRunRepoStoreOpts {
|
|
4
|
+
/**
|
|
5
|
+
* Bare substrate handle the child opens against the shared on-disk
|
|
6
|
+
* data dir. Used for the read-only methods that consult the
|
|
7
|
+
* substrate's local state -- `getRepoDir` (path computation, no
|
|
8
|
+
* I/O), `resolveRef`, `listRefs`, `resolveHead`, `createPack`. The
|
|
9
|
+
* bare store is never used as a writer here; its
|
|
10
|
+
* `writeTreePreservingPrefix` / `writeTree` / `receivePack` are not
|
|
11
|
+
* reachable through this proxy.
|
|
12
|
+
*/
|
|
13
|
+
bareStore: RepoStore;
|
|
14
|
+
/**
|
|
15
|
+
* Substrate-write bridge that forwards proxied writes over the
|
|
16
|
+
* control IPC into the supervisor.
|
|
17
|
+
*/
|
|
18
|
+
bridge: ChildSubstrateWriteBridge;
|
|
19
|
+
/**
|
|
20
|
+
* Workflow-run repo id this proxy services. Used as a guard: writes
|
|
21
|
+
* targeting any other repo id surface a structured failure so a
|
|
22
|
+
* stray call against a non-workflow-run repo does not silently land
|
|
23
|
+
* in the wrong substrate.
|
|
24
|
+
*/
|
|
25
|
+
workflowRunRepoId: RepoId;
|
|
26
|
+
}
|
|
27
|
+
export declare function createProxyWorkflowRunRepoStore(opts: CreateProxyWorkflowRunRepoStoreOpts): RepoStore;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// Child-side proxy `RepoStore` for the workflow-run repo.
|
|
2
|
+
//
|
|
3
|
+
// Wraps a bare read-only substrate handle (the child constructs one
|
|
4
|
+
// against the shared on-disk data dir for `getRepoDir` + other read
|
|
5
|
+
// paths) and intercepts `writeTreePreservingPrefix` so the write is
|
|
6
|
+
// proxied over the control IPC into the supervisor's substrate. The
|
|
7
|
+
// supervisor is the sole writer of the workflow-run repo's ref; the
|
|
8
|
+
// child has no write authority and would race the supervisor's
|
|
9
|
+
// claim-check writes if it opened its own.
|
|
10
|
+
//
|
|
11
|
+
// Subscription fan-out: `RepoStore.subscribe` is a local-process
|
|
12
|
+
// in-memory subscriber pattern; the bare-store's `subscribe` only
|
|
13
|
+
// fires when a write lands on THAT particular substrate instance. The
|
|
14
|
+
// supervisor's writes against its own substrate do not reach the
|
|
15
|
+
// child's bare-store subscribers. The proxy therefore maintains its
|
|
16
|
+
// own per-ref subscriber list and synthesizes a `ref.updated` event to
|
|
17
|
+
// every subscriber after each successful proxied write -- the on-disk
|
|
18
|
+
// repo already carries the new commit (the supervisor's substrate
|
|
19
|
+
// commits before responding to the IPC), so subscribers that follow
|
|
20
|
+
// up with a tree read (via `subscribeKind`'s `getRepoDir` +
|
|
21
|
+
// `readBlobAtCommit` path) see the prospective tree's bytes.
|
|
22
|
+
//
|
|
23
|
+
// Methods that mutate state via paths other than
|
|
24
|
+
// `writeTreePreservingPrefix` (`initRepo`, `writeTree`, `receivePack`)
|
|
25
|
+
// throw on call. The workflow-host runtime body and adapters do not
|
|
26
|
+
// invoke these against the workflow-run repo proxy today; a future
|
|
27
|
+
// caller that tries to surfaces a structured failure rather than a
|
|
28
|
+
// silent disk write that would corrupt the single-writer invariant.
|
|
29
|
+
export function createProxyWorkflowRunRepoStore(opts) {
|
|
30
|
+
const { bareStore, bridge, workflowRunRepoId } = opts;
|
|
31
|
+
// Per-ref subscriber lists. The proxy fans `ref.updated` events to
|
|
32
|
+
// every subscriber on the ref after a successful proxied write so
|
|
33
|
+
// a `subscribeKind` loop watching this ref (the signal channel's
|
|
34
|
+
// `awaitNext` is the canonical consumer) wakes up.
|
|
35
|
+
const subscribers = new Map();
|
|
36
|
+
// Per-ref last-seen sha so the synthesized `ref.updated` carries the
|
|
37
|
+
// matching `oldSha`. Subscriber semantics in `subscribeKind` use the
|
|
38
|
+
// oldSha to enumerate commits added on top of the prior tip.
|
|
39
|
+
const lastSha = new Map();
|
|
40
|
+
function refKey(repoId, ref) {
|
|
41
|
+
return `${repoId.kind}/${repoId.id}/${ref}`;
|
|
42
|
+
}
|
|
43
|
+
function notifyRefUpdate(repoId, ref, oldSha, newSha) {
|
|
44
|
+
const key = refKey(repoId, ref);
|
|
45
|
+
const set = subscribers.get(key);
|
|
46
|
+
if (set === undefined)
|
|
47
|
+
return;
|
|
48
|
+
const event = {
|
|
49
|
+
type: "ref.updated",
|
|
50
|
+
ref,
|
|
51
|
+
oldSha,
|
|
52
|
+
newSha,
|
|
53
|
+
};
|
|
54
|
+
// The substrate's subscribe contract emits one entry per commit
|
|
55
|
+
// with the seq value derived at commit time. The proxy does not
|
|
56
|
+
// know the canonical seq the substrate assigned (the substrate
|
|
57
|
+
// computes it from the ref's history); the field is informational
|
|
58
|
+
// for the subscribe iterator's consumers. `subscribeKind` does
|
|
59
|
+
// not consult the value -- it walks the commit tree from `oldSha`
|
|
60
|
+
// to `newSha` itself -- so any monotonic value preserves the
|
|
61
|
+
// downstream contract. Use a per-ref monotonic counter.
|
|
62
|
+
const seq = (lastRefSeq.get(key) ?? 0) + 1;
|
|
63
|
+
lastRefSeq.set(key, seq);
|
|
64
|
+
for (const sub of set) {
|
|
65
|
+
if (sub.closed)
|
|
66
|
+
continue;
|
|
67
|
+
const entry = { seq, event };
|
|
68
|
+
if (sub.waiter !== null) {
|
|
69
|
+
const w = sub.waiter;
|
|
70
|
+
sub.waiter = null;
|
|
71
|
+
w({ value: entry, done: false });
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
sub.buffer.push(entry);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const lastRefSeq = new Map();
|
|
78
|
+
return {
|
|
79
|
+
initRepo: (_repoId, _initOpts) => {
|
|
80
|
+
throw new Error("workflow-child proxy substrate: initRepo is not supported (writes are proxied to the supervisor)");
|
|
81
|
+
},
|
|
82
|
+
writeTree: (_principal, _repoId, _ref, _content) => {
|
|
83
|
+
throw new Error("workflow-child proxy substrate: writeTree is not supported (writes are proxied to the supervisor)");
|
|
84
|
+
},
|
|
85
|
+
receivePack: (_principal, _repoId, _ref, _pack, _commitSha, _expectedOldSha) => {
|
|
86
|
+
throw new Error("workflow-child proxy substrate: receivePack is not supported (writes are proxied to the supervisor)");
|
|
87
|
+
},
|
|
88
|
+
writeTreeDelta: (_principal, _repoId, _ref, _args) => {
|
|
89
|
+
throw new Error("workflow-child proxy substrate: writeTreeDelta is not supported (claim-check writes run supervisor-side)");
|
|
90
|
+
},
|
|
91
|
+
async writeTreePreservingPrefix(_principal, repoId, ref, args) {
|
|
92
|
+
if (repoId.kind !== workflowRunRepoId.kind ||
|
|
93
|
+
repoId.id !== workflowRunRepoId.id) {
|
|
94
|
+
throw new Error(`workflow-child proxy substrate: writeTreePreservingPrefix targeting ${repoId.kind}/${repoId.id} is not supported (proxy services ${workflowRunRepoId.kind}/${workflowRunRepoId.id})`);
|
|
95
|
+
}
|
|
96
|
+
const key = refKey(repoId, ref);
|
|
97
|
+
const priorSha = lastSha.has(key)
|
|
98
|
+
? (lastSha.get(key) ?? null)
|
|
99
|
+
: await bareStore.resolveRef(_principal, repoId, ref);
|
|
100
|
+
const result = await bridge.submit({
|
|
101
|
+
repoId: { kind: repoId.kind, id: repoId.id },
|
|
102
|
+
ref,
|
|
103
|
+
preservePrefix: args.preservePrefix,
|
|
104
|
+
message: args.message,
|
|
105
|
+
merge: args.merge,
|
|
106
|
+
});
|
|
107
|
+
lastSha.set(key, result.commitSha);
|
|
108
|
+
notifyRefUpdate(repoId, ref, priorSha, result.commitSha);
|
|
109
|
+
// The terminal signal is consumed supervisor-side (where the real
|
|
110
|
+
// substrate write happens); the child-proxied result carries only
|
|
111
|
+
// the commit, so report no terminal runs to the runtime body.
|
|
112
|
+
return { commitSha: result.commitSha, newlyTerminalRuns: [] };
|
|
113
|
+
},
|
|
114
|
+
createPack: bareStore.createPack.bind(bareStore),
|
|
115
|
+
commitPackedTip: bareStore.commitPackedTip.bind(bareStore),
|
|
116
|
+
resolveRef: bareStore.resolveRef.bind(bareStore),
|
|
117
|
+
listRefs: bareStore.listRefs.bind(bareStore),
|
|
118
|
+
resolveHead: bareStore.resolveHead.bind(bareStore),
|
|
119
|
+
getRepoDir: bareStore.getRepoDir.bind(bareStore),
|
|
120
|
+
subscribe(_principal, repoId, ref, subOpts) {
|
|
121
|
+
// Synthesizing the subscribe surface in the proxy: the bare
|
|
122
|
+
// store's `subscribe` would only fire from its own writes, but
|
|
123
|
+
// the writes for this ref happen in the supervisor's address
|
|
124
|
+
// space. Subscribers attached here receive events whenever the
|
|
125
|
+
// proxy's `writeTreePreservingPrefix` returns successfully.
|
|
126
|
+
//
|
|
127
|
+
// `from: { seq }` replay against historical commits is not
|
|
128
|
+
// emitted here today: the runtime body's signal-channel and
|
|
129
|
+
// similar consumers attach with `from: "head"` so only events
|
|
130
|
+
// committed after subscription fire. A `from: { seq }` caller
|
|
131
|
+
// (a resume path that wants to replay) would currently miss
|
|
132
|
+
// historical commits -- the bare store's `subscribe` is the
|
|
133
|
+
// path that supports replay today, and resume code can call it
|
|
134
|
+
// directly if needed.
|
|
135
|
+
const key = refKey(repoId, ref);
|
|
136
|
+
let set = subscribers.get(key);
|
|
137
|
+
if (set === undefined) {
|
|
138
|
+
set = new Set();
|
|
139
|
+
subscribers.set(key, set);
|
|
140
|
+
}
|
|
141
|
+
const sub = {
|
|
142
|
+
buffer: [],
|
|
143
|
+
waiter: null,
|
|
144
|
+
closed: false,
|
|
145
|
+
signal: subOpts.signal,
|
|
146
|
+
};
|
|
147
|
+
set.add(sub);
|
|
148
|
+
const cleanup = () => {
|
|
149
|
+
sub.closed = true;
|
|
150
|
+
const current = subscribers.get(key);
|
|
151
|
+
if (current !== undefined) {
|
|
152
|
+
current.delete(sub);
|
|
153
|
+
if (current.size === 0)
|
|
154
|
+
subscribers.delete(key);
|
|
155
|
+
}
|
|
156
|
+
if (sub.waiter !== null) {
|
|
157
|
+
const w = sub.waiter;
|
|
158
|
+
sub.waiter = null;
|
|
159
|
+
w({ value: undefined, done: true });
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
const onAbort = () => {
|
|
163
|
+
cleanup();
|
|
164
|
+
};
|
|
165
|
+
if (subOpts.signal.aborted) {
|
|
166
|
+
cleanup();
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
subOpts.signal.addEventListener("abort", onAbort, { once: true });
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
[Symbol.asyncIterator]() {
|
|
173
|
+
return this;
|
|
174
|
+
},
|
|
175
|
+
next() {
|
|
176
|
+
if (sub.closed) {
|
|
177
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
178
|
+
}
|
|
179
|
+
if (sub.buffer.length > 0) {
|
|
180
|
+
const next = sub.buffer.shift();
|
|
181
|
+
if (next === undefined) {
|
|
182
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
183
|
+
}
|
|
184
|
+
return Promise.resolve({ value: next, done: false });
|
|
185
|
+
}
|
|
186
|
+
return new Promise((resolve) => {
|
|
187
|
+
sub.waiter = resolve;
|
|
188
|
+
});
|
|
189
|
+
},
|
|
190
|
+
return() {
|
|
191
|
+
cleanup();
|
|
192
|
+
return Promise.resolve({
|
|
193
|
+
value: undefined,
|
|
194
|
+
done: true,
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
}
|