Skip to content

Commit

Permalink
docs(xsnap): document XS handleCommand async idiom
Browse files Browse the repository at this point in the history
  • Loading branch information
dckc committed Jan 23, 2021
1 parent ef175c5 commit 758751d
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ function workerLog(first, ...args) {
workerLog(`supervisor started`);

/**
* @param { (cmd: ArrayBuffer) => ArrayBuffer } issueCommand
* @typedef { [unknown, ...unknown[]] } Tagged
* @typedef { (item: Tagged) => unknown } DataHandler
* @typedef { (item: Tagged) => Promise<Tagged> } AsyncHandler
* Wrap byte-level protocols with tagged array codec.
*
* @param { (cmd: ArrayBuffer) => ArrayBuffer } issueCommand as from xsnap
* @typedef { [unknown, ...unknown[]] } Tagged tagged array
*/
function ManagerPort(issueCommand) {
function managerPort(issueCommand) {
/** @type { (item: Tagged) => ArrayBuffer } */
const encode = item => encoder.encode(JSON.stringify(item)).buffer;

Expand All @@ -46,20 +46,31 @@ function ManagerPort(issueCommand) {
send: item => {
issueCommand(encode(item));
},
/** @type { DataHandler } */
/** @type { (item: Tagged) => unknown } */
call: item => decodeData(issueCommand(encode(item))),
/** @type { (f: AsyncHandler) => ((msg: ArrayBuffer) => { result?: ArrayBuffer })} */
handler: f => msg => {
const report = {};
f(decode(msg))
.then(item => {
workerLog('result', item);
report.result = encode(item);
})
.catch(err => {
report.result = encode(['err', err.message]);
});
return report;

/**
* Wrap an async Tagged handler in the xsnap async reporting idiom.
*
* @param { (item: Tagged) => Promise<Tagged> } f async Tagged handler
* @returns { (msg: ArrayBuffer) => Report<ArrayBuffer> } xsnap style handleCommand
*
* @typedef { { result?: T } } Report<T> report T when idle
* @template T
*/
handlerFrom(f) {
return msg => {
const report = {};
f(decode(msg))
.then(item => {
workerLog('result', item);
report.result = encode(item);
})
.catch(err => {
report.result = encode(['err', err.message]);
});
return report;
};
},
});
}
Expand All @@ -76,7 +87,7 @@ function runAndWait(f, errmsg) {
}

/**
* @param { ReturnType<ManagerPort> } port
* @param { ReturnType<managerPort> } port
*/
function makeWorker(port) {
/** @type { Record<string, (...args: unknown[]) => void> | null } */
Expand Down Expand Up @@ -214,13 +225,12 @@ function makeWorker(port) {
}

return harden({
/** @type { AsyncHandler } */
handleItem,
});
}

// @ts-ignore xsnap provides issueCommand global
// eslint-disable-next-line no-undef
const port = ManagerPort(issueCommand);
const port = managerPort(issueCommand);
const worker = makeWorker(port);
globalThis.handleCommand = port.handler(worker.handleItem);
globalThis.handleCommand = port.handlerFrom(worker.handleItem);
4 changes: 4 additions & 0 deletions packages/xsnap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ The parent and child communicate using "commands".
and receive as response from the Node.js parent.
- The XS child can implement a synchronous `handleCommand` function to respond
to commands from the Node.js parent.
- The XS child `handleCommand` may be asynchronous after a fashion: it
may return an object and, before the promise queue becomes empty,
set the `result` property of this object to an `ArrayBuffer`.
See the **evaluate and report** test for an example.
- The Node.js parent uses an asynchronous `issueCommand` method to send a
request and receive a response from the XS child.
- The Node.js parent can implement an asynchronous `handleCommand` function to
Expand Down

0 comments on commit 758751d

Please sign in to comment.