diff --git a/packages/daemon/src/daemon.js b/packages/daemon/src/daemon.js index 15d0b5c214..ae81dffa2c 100644 --- a/packages/daemon/src/daemon.js +++ b/packages/daemon/src/daemon.js @@ -98,6 +98,7 @@ const makeEndoBootstrap = async ( // reference", and not for "what is my name for this promise". /** @type {WeakMap} */ const formulaIdentifierForRef = new WeakMap(); + const getFormulaIdentifierForRef = ref => formulaIdentifierForRef.get(ref); /** * @param {string} sha512 @@ -252,7 +253,7 @@ const makeEndoBootstrap = async ( // Behold, recursion: // eslint-disable-next-line no-use-before-define const hub = provideValueForFormulaIdentifier(hubFormulaIdentifier); - + // @ts-expect-error calling lookup on an unknown object const external = E(hub).lookup(...path); return { external, internal: undefined }; }; @@ -521,13 +522,7 @@ const makeEndoBootstrap = async ( // share a responsibility for maintaining the memoization tables // controllerForFormulaIdentifier and formulaIdentifierForRef, since the // former bypasses the latter in order to avoid a round trip with disk. - - /** - * @param {string} formulaType - The type of the formula. - * @param {string} formulaNumber - The number of the formula. - * @param {import('./types').Formula} formula - The formula. - * @returns {Promise<{ formulaIdentifier: string, value: unknown }>} The value of the formula. - */ + /** @type {import('./types.js').ProvideValueForNumberedFormula} */ const provideValueForNumberedFormula = async ( formulaType, formulaNumber, @@ -583,9 +578,7 @@ const makeEndoBootstrap = async ( return provideValueForNumberedFormula(formulaType, formulaNumber, formula); }; - /** - * @param {string} formulaIdentifier - */ + /** @type {import('./types.js').ProvideControllerForFormulaIdentifier} */ const provideControllerForFormulaIdentifier = formulaIdentifier => { const { type: formulaType, number: formulaNumber } = parseFormulaIdentifier(formulaIdentifier); @@ -619,9 +612,7 @@ const makeEndoBootstrap = async ( return controller; }; - /** - * @param {string} formulaIdentifier - */ + /** @type {import('./types.js').ProvideValueForFormulaIdentifier} */ const provideValueForFormulaIdentifier = async formulaIdentifier => { const controller = /** @type {import('./types.js').Controller<>} */ ( provideControllerForFormulaIdentifier(formulaIdentifier) @@ -639,7 +630,7 @@ const makeEndoBootstrap = async ( }); const makeMailbox = makeMailboxMaker({ - formulaIdentifierForRef, + getFormulaIdentifierForRef, provideValueForFormulaIdentifier, provideControllerForFormulaIdentifier, makeSha512, @@ -673,8 +664,8 @@ const makeEndoBootstrap = async ( * @returns {Promise} */ const makePetStoreInspector = async petStoreFormulaIdentifier => { - const petStore = await provideValueForFormulaIdentifier( - petStoreFormulaIdentifier, + const petStore = /** @type {import('./types').PetStore} */ ( + await provideValueForFormulaIdentifier(petStoreFormulaIdentifier) ); /** diff --git a/packages/daemon/src/mail.js b/packages/daemon/src/mail.js index ea03e986e4..93fe1e7528 100644 --- a/packages/daemon/src/mail.js +++ b/packages/daemon/src/mail.js @@ -8,13 +8,29 @@ import { assertPetName } from './pet-name.js'; const { quote: q } = assert; +/** + * @param {object} args + * @param {import('./types.js').ProvideValueForFormulaIdentifier} args.provideValueForFormulaIdentifier + * @param {import('./types.js').ProvideControllerForFormulaIdentifier} args.provideControllerForFormulaIdentifier + * @param {import('./types.js').GetFormulaIdentifierForRef} args.getFormulaIdentifierForRef + * @param {import('./types.js').MakeSha512} args.makeSha512 + * @param {import('./types.js').ProvideValueForNumberedFormula} args.provideValueForNumberedFormula + */ export const makeMailboxMaker = ({ provideValueForFormulaIdentifier, provideControllerForFormulaIdentifier, - formulaIdentifierForRef, + getFormulaIdentifierForRef, makeSha512, provideValueForNumberedFormula, }) => { + /** + * @param {object} args + * @param {string} args.selfFormulaIdentifier + * @param {import('./types.js').PetStore} args.petStore + * @param {Record} args.specialNames + * @param {import('./types.js').Context} args.context + * @returns {import('./types.js').Mail} + */ const makeMailbox = ({ selfFormulaIdentifier, petStore, @@ -33,10 +49,12 @@ export const makeMailboxMaker = ({ const messagesTopic = makeChangeTopic(); let nextMessageNumber = 0; - /** - * @param {string} petName - * @returns {string | undefined} - */ + /** @type {import('./types.js').Mail['has']} */ + const has = petName => { + return Object.hasOwn(specialNames, petName) || petStore.has(petName); + }; + + /** @type {import('./types.js').Mail['identifyLocal']} */ const identifyLocal = petName => { if (Object.hasOwn(specialNames, petName)) { return specialNames[petName]; @@ -44,10 +62,7 @@ export const makeMailboxMaker = ({ return petStore.identifyLocal(petName); }; - /** - * @param {...string} petNamePath - A sequence of pet names. - * @returns {Promise} The value resolved by the pet name path. - */ + /** @type {import('./types.js').Mail['lookup']} */ const lookup = async (...petNamePath) => { const [headName, ...tailNames] = petNamePath; const formulaIdentifier = identifyLocal(headName); @@ -56,12 +71,14 @@ export const makeMailboxMaker = ({ } // Behold, recursion: return tailNames.reduce( + // @ts-expect-error calling lookup on an unknown object (currentValue, petName) => E(currentValue).lookup(petName), provideValueForFormulaIdentifier(formulaIdentifier), ); }; - const cancel = async (petName, reason = 'Cancelled') => { + /** @type {import('./types.js').Mail['cancel']} */ + const cancel = async (petName, reason = new Error('Cancelled')) => { const formulaIdentifier = identifyLocal(petName); if (formulaIdentifier === undefined) { throw new TypeError(`Unknown pet name: ${q(petName)}`); @@ -72,19 +89,18 @@ export const makeMailboxMaker = ({ formulaIdentifier, ); console.log('Cancelled:'); - return controller.context.cancel(new Error(reason)); + return controller.context.cancel(reason); }; + /** @type {import('./types.js').Mail['list']} */ const list = () => harden(petStore.list()); - + /** @type {import('./types.js').Mail['listSpecial']} */ const listSpecial = () => harden(Object.keys(specialNames).sort()); - + /** @type {import('./types.js').Mail['listAll']} */ const listAll = () => harden([...Object.keys(specialNames).sort(), ...petStore.list()]); - /** - * @param {string} formulaIdentifier - */ + /** @type {import('./types.js').Mail['reverseLookupFormulaIdentifier']} */ const reverseLookupFormulaIdentifier = formulaIdentifier => { const names = Array.from(petStore.reverseLookup(formulaIdentifier)); for (const [specialName, specialFormulaIdentifier] of Object.entries( @@ -97,37 +113,31 @@ export const makeMailboxMaker = ({ return harden(names); }; - /** - * @param {unknown} presence - */ + /** @type {import('./types.js').Mail['reverseLookup']} */ const reverseLookup = async presence => { - const formulaIdentifier = formulaIdentifierForRef.get(await presence); + const formulaIdentifier = getFormulaIdentifierForRef(await presence); if (formulaIdentifier === undefined) { return harden([]); } return reverseLookupFormulaIdentifier(formulaIdentifier); }; - /** - * Takes a sequence of pet names and returns a formula identifier and value - * for the corresponding lookup formula. - * - * @param {string[]} petNamePath - A sequence of pet names. - * @returns {Promise<{ formulaIdentifier: string, value: unknown }>} The formula - * identifier and value of the lookup formula. - */ + /** @type {import('./types.js').Mail['provideLookupFormula']} */ const provideLookupFormula = async petNamePath => { // The lookup formula identifier consists of the hash of the associated // naming hub's formula identifier and the pet name path. - // A "naming hub" is an objected with a variadic lookup method. At present, - // the only such objects are guests and hosts. + // A "naming hub" is an objected with a variadic lookup method. It includes + // objects such as guests and hosts. const hubFormulaIdentifier = identifyLocal('SELF'); + if (hubFormulaIdentifier === undefined) { + throw new Error('No SELF found during LookupFormula creation.'); + } const digester = makeSha512(); digester.updateText(`${hubFormulaIdentifier},${petNamePath.join(',')}`); const lookupFormulaNumber = digester.digestHex(); // TODO:lookup Check if the lookup formula already exists in the store - + /** @type {import('./types.js').LookupFormula} */ const lookupFormula = { /** @type {'lookup'} */ type: 'lookup', @@ -186,9 +196,22 @@ export const makeMailboxMaker = ({ ); }; - const listMessages = async () => - harden(Array.from(messages.values(), dubMessage)); + /** + * @returns {Generator} + */ + const dubAndFilterMessages = function* dubAndFilterMessages() { + for (const message of messages.values()) { + const dubbedMessage = dubMessage(message); + if (dubbedMessage !== undefined) { + yield dubbedMessage; + } + } + }; + /** @type {import('./types.js').Mail['listMessages']} */ + const listMessages = async () => harden(Array.from(dubAndFilterMessages())); + + /** @type {import('./types.js').Mail['followMessages']} */ const followMessages = async () => makeIteratorRef( (async function* currentAndSubsequentMessages() { @@ -196,18 +219,22 @@ export const makeMailboxMaker = ({ for (const message of messages.values()) { const dubbedMessage = dubMessage(message); if (dubbedMessage !== undefined) { - yield dubMessage(message); + yield dubbedMessage; } } for await (const message of subsequentRequests) { const dubbedMessage = dubMessage(message); if (dubbedMessage !== undefined) { - yield dubMessage(message); + yield dubbedMessage; } } })(), ); + /** + * @param {object} partialMessage + * @returns {import('./types.js').InternalMessage} + */ const deliver = partialMessage => { /** @type {import('@endo/promise-kit/src/types.js').PromiseKit} */ const dismissal = makePromiseKit(); @@ -236,6 +263,7 @@ export const makeMailboxMaker = ({ * @param {string} what - user visible description of the desired value * @param {string} who * @param {string} dest + * @returns {Promise} */ const requestFormulaIdentifier = async (what, who, dest) => { /** @type {import('@endo/promise-kit/src/types.js').PromiseKit} */ @@ -255,13 +283,7 @@ export const makeMailboxMaker = ({ return promise; }; - /** - * @param {string} what - * @param {string} responseName - * @param {string} senderFormulaIdentifier - * @param {import('./types.js').PetStore} senderPetStore - * @param {string} [recipientFormulaIdentifier] - */ + /** @type {import('./types.js').Mail['respond']} */ const respond = async ( what, responseName, @@ -297,6 +319,7 @@ export const makeMailboxMaker = ({ return provideValueForFormulaIdentifier(formulaIdentifier); }; + /** @type {import('./types.js').Mail['resolve']} */ const resolve = async (messageNumber, resolutionName) => { assertPetName(resolutionName); if ( @@ -320,10 +343,7 @@ export const makeMailboxMaker = ({ }; // TODO test reject - /** - * @param {number} messageNumber - * @param {string} [message] - */ + /** @type {import('./types.js').Mail['reject']} */ const reject = async (messageNumber, message = 'Declined') => { const req = messages.get(messageNumber); if (req !== undefined) { @@ -335,20 +355,14 @@ export const makeMailboxMaker = ({ } }; - /** - * @param {string} senderFormulaIdentifier - * @param {Array} strings - * @param {Array} edgeNames - * @param {Array} formulaIdentifiers - * @param {string} receiverFormulaIdentifier - */ + /** @type {import('./types.js').Mail['receive']} */ const receive = ( senderFormulaIdentifier, strings, edgeNames, formulaIdentifiers, receiverFormulaIdentifier = selfFormulaIdentifier, - ) => + ) => { deliver({ type: /** @type {const} */ ('package'), strings, @@ -357,13 +371,9 @@ export const makeMailboxMaker = ({ who: senderFormulaIdentifier, dest: receiverFormulaIdentifier, }); + }; - /** - * @param {string} recipientName - * @param {Array} strings - * @param {Array} edgeNames - * @param {Array} petNames - */ + /** @type {import('./types.js').Mail['send']} */ const send = async (recipientName, strings, edgeNames, petNames) => { const recipientFormulaIdentifier = identifyLocal(recipientName); if (recipientFormulaIdentifier === undefined) { @@ -373,9 +383,10 @@ export const makeMailboxMaker = ({ recipientFormulaIdentifier, ); const recipientInternal = await recipientController.internal; - if (recipientInternal === undefined) { + if (recipientInternal === undefined || recipientInternal === null) { throw new Error(`Recipient cannot receive messages: ${recipientName}`); } + // @ts-expect-error We check if its undefined immediately after const { receive: partyReceive } = recipientInternal; if (partyReceive === undefined) { throw new Error(`Recipient cannot receive messages: ${recipientName}`); @@ -421,6 +432,7 @@ export const makeMailboxMaker = ({ ); }; + /** @type {import('./types.js').Mail['dismiss']} */ const dismiss = async messageNumber => { if ( typeof messageNumber !== 'number' || @@ -436,6 +448,7 @@ export const makeMailboxMaker = ({ dismissMessage(); }; + /** @type {import('./types.js').Mail['adopt']} */ const adopt = async (messageNumber, edgeName, petName) => { assertPetName(edgeName); assertPetName(petName); @@ -470,11 +483,7 @@ export const makeMailboxMaker = ({ await petStore.write(petName, formulaIdentifier); }; - /** - * @param {string} recipientName - * @param {string} what - * @param {string} responseName - */ + /** @type {import('./types.js').Mail['request']} */ const request = async (recipientName, what, responseName) => { const recipientFormulaIdentifier = identifyLocal(recipientName); if (recipientFormulaIdentifier === undefined) { @@ -534,10 +543,7 @@ export const makeMailboxMaker = ({ return newResponseP; }; - /** - * @param {string} fromName - * @param {string} toName - */ + /** @type {import('./types.js').Mail['rename']} */ const rename = async (fromName, toName) => { await petStore.rename(fromName, toName); const formulaIdentifier = responses.get(fromName); @@ -547,15 +553,14 @@ export const makeMailboxMaker = ({ } }; - /** - * @param {string} petName - */ + /** @type {import('./types.js').Mail['remove']} */ const remove = async petName => { await petStore.remove(petName); responses.delete(petName); }; return harden({ + has, lookup, reverseLookup, reverseLookupFormulaIdentifier, diff --git a/packages/daemon/src/types.d.ts b/packages/daemon/src/types.d.ts index 8d6e559956..6a81e90da7 100644 --- a/packages/daemon/src/types.d.ts +++ b/packages/daemon/src/types.d.ts @@ -157,7 +157,7 @@ export interface Topic< } export interface Context { - cancel: (reason?: string, logPrefix?: string) => Promise; + cancel: (reason?: unknown, logPrefix?: string) => Promise; cancelled: Promise; disposed: Promise; thisDiesIfThatDies: (formulaIdentifier: string) => void; @@ -182,6 +182,21 @@ export interface Controller { context: Context; } +export type ProvideValueForFormulaIdentifier = ( + formulaIdentifier: string, +) => Promise; +export type ProvideControllerForFormulaIdentifier = ( + formulaIdentifier: string, +) => Controller; +export type GetFormulaIdentifierForRef = (ref: unknown) => string | undefined; +export type MakeSha512 = () => Sha512; + +export type ProvideValueForNumberedFormula = ( + formulaType: string, + formulaNumber: string, + formula: Formula, +) => Promise<{ formulaIdentifier: string; value: unknown }>; + export interface PetStore { has(petName: string): boolean; identifyLocal(petName: string): string | undefined; @@ -204,9 +219,73 @@ export interface PetStore { write(petName: string, formulaIdentifier: string): Promise; remove(petName: string); rename(fromPetName: string, toPetName: string); + /** + * @param formulaIdentifier The formula identifier to look up. + * @returns The formula identifier for the given pet name, or `undefined` if the pet name is not found. + */ reverseLookup(formulaIdentifier: string): Array; } +export interface Mail { + // Partial inheritance from PetStore: + has: PetStore['has']; + rename: PetStore['rename']; + remove: PetStore['remove']; + list: PetStore['list']; + identifyLocal: PetStore['identifyLocal']; + reverseLookup: PetStore['reverseLookup']; + // Extended methods: + lookup(...petNamePath: string[]): Promise; + listSpecial(): Array; + listAll(): Array; + reverseLookupFormulaIdentifier(formulaIdentifier: string): Array; + cancel(petName: string, reason: unknown): Promise; + /** + * Takes a sequence of pet names and returns a formula identifier and value + * for the corresponding lookup formula. + * + * @param petNamePath A sequence of pet names. + * @returns The formula identifier and value of the lookup formula. + */ + provideLookupFormula(petNamePath: string[]): Promise; + // Mail operations: + listMessages(): Promise>; + followMessages(): Promise>>; + request( + recipientName: string, + what: string, + responseName: string, + ): Promise; + respond( + what: string, + responseName: string, + senderFormulaIdentifier: string, + senderPetStore: PetStore, + recipientFormulaIdentifier?: string, + ): Promise; + receive( + senderFormulaIdentifier: string, + strings: Array, + edgeNames: Array, + formulaIdentifiers: Array, + receiverFormulaIdentifier: string, + ): void; + send( + recipientName: string, + strings: Array, + edgeNames: Array, + petNames: Array, + ): Promise; + resolve(messageNumber: number, resolutionName: string): Promise; + reject(messageNumber: number, message?: string): Promise; + dismiss(messageNumber: number): Promise; + adopt( + messageNumber: number, + edgeName: string, + petName: string, + ): Promise; +} + export type RequestFn = ( what: string, responseName: string,