| 863 | // cancels. Events are pushed by the test through emit(), so the run |
| 864 | // stays live for as long as the test wants it to. |
| 865 | function liveRpc(): { |
| 866 | shell: ShellRPC; |
| 867 | emit: (event: ExecEvent) => void; |
| 868 | } { |
| 869 | let subscriber: ReadableStreamDefaultController<ExecEvent> | undefined; |
| 870 | const subscribe = (id: string): ReadableStream<ExecEvent> => { |
| 871 | if (subscriber !== undefined) { |
| 872 | throw new Error(`EEXEC_BUSY: exec ${id} already has a live subscriber`); |
| 873 | } |
| 874 | return new ReadableStream<ExecEvent>({ |
| 875 | start(c) { |
| 876 | subscriber = c; |
| 877 | }, |
| 878 | cancel() { |
| 879 | subscriber = undefined; |
| 880 | }, |
| 881 | }); |
| 882 | }; |
| 883 | return { |
| 884 | // The runner closes the subscriber's stream after the exit |
| 885 | // event; result() drains until close. |
| 886 | emit: (event) => { |
| 887 | subscriber?.enqueue(event); |
| 888 | if (event.name === "exit") subscriber?.close(); |
| 889 | }, |
| 890 | shell: { |
| 891 | async exec(input) { |
| 892 | const id = input.id ?? "runner-minted-id"; |
| 893 | return { id, events: subscribe(id) }; |
| 894 | }, |
| 895 | async getExec(input) { |
| 896 | return { id: input.id, events: subscribe(input.id) }; |
| 897 | }, |
| 898 | async killExec() {}, |
| 899 | async disposeExec() {}, |
| 900 | }, |
| 901 | }; |
| 902 | } |