Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(daemon): Add reverseLocate() and followLocatorNameChanges() #2195

Merged
merged 8 commits into from
Apr 17, 2024
2 changes: 1 addition & 1 deletion packages/cli/demo/cat.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ const inventoryComponent = async ($parent, $end, powers) => {
$parent.insertBefore($ul, $end);

const $names = new Map();
for await (const change of makeRefIterator(E(powers).followChanges())) {
for await (const change of makeRefIterator(E(powers).followNameChanges())) {
if ('add' in change) {
const name = change.add;

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const list = async ({ directoryPath, follow, json }) =>
agent = E(agent).lookup(...directoryPath.split('.'));
}
if (follow) {
const topic = await E(agent).followNames();
const topic = await E(agent).followNameChanges();
const iterator = makeRefIterator(topic);
if (json) {
for await (const change of iterator) {
Expand Down
2 changes: 1 addition & 1 deletion packages/daemon/src/daemon.js
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ const makeDaemonCore = async (
has: disallowedFn,
identify: disallowedFn,
list: disallowedFn,
followChanges: disallowedFn,
followNameChanges: disallowedFn,
lookup: disallowedFn,
reverseLookup: disallowedFn,
write: disallowedFn,
Expand Down
45 changes: 38 additions & 7 deletions packages/daemon/src/directory.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { E } from '@endo/far';
import { makeExo } from '@endo/exo';
import { M } from '@endo/patterns';
import { makeIteratorRef } from './reader-ref.js';
import { formatLocator } from './locator.js';
import { formatLocator, idFromLocator } from './locator.js';

const { quote: q } = assert;

Expand Down Expand Up @@ -97,6 +97,30 @@ export const makeDirectoryMaker = ({
return formatLocator(id, formulaType);
};

/** @type {import('./types.js').EndoDirectory['reverseLocate']} */
const reverseLocate = async locator => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silly idea:

  • “locate” means “get locator for name”
  • “nominate” means “get name from locator”

const id = idFromLocator(locator);
return petStore.reverseIdentify(id);
};

/** @type {import('./types.js').EndoDirectory['followLocatorNameChanges']} */
const followLocatorNameChanges = async function* followLocatorNameChanges(
locator,
) {
const id = idFromLocator(locator);
for await (const idNameChange of petStore.followIdNameChanges(id)) {
/** @type {any} */
const locatorNameChange = {
...idNameChange,
...(Object.hasOwn(idNameChange, 'add')
? { add: locator }
: { remove: locator }),
};

yield /** @type {import('./types.js').LocatorNameChange} */ locatorNameChange;
}
};

/** @type {import('./types.js').EndoDirectory['list']} */
const list = async (...petNamePath) => {
if (petNamePath.length === 0) {
Expand All @@ -123,16 +147,18 @@ export const makeDirectoryMaker = ({
return harden(Array.from(identities).sort());
};

/** @type {import('./types.js').EndoDirectory['followChanges']} */
const followChanges = async function* followChanges(...petNamePath) {
/** @type {import('./types.js').EndoDirectory['followNameChanges']} */
const followNameChanges = async function* followNameChanges(
...petNamePath
) {
if (petNamePath.length === 0) {
yield* petStore.follow();
yield* petStore.followNameChanges();
return;
}
const hub = /** @type {import('./types.js').NameHub} */ (
await lookup(...petNamePath)
);
yield* hub.followChanges();
yield* hub.followNameChanges();
};

/** @type {import('./types.js').EndoDirectory['remove']} */
Expand Down Expand Up @@ -206,9 +232,11 @@ export const makeDirectoryMaker = ({
has,
identify,
locate,
reverseLocate,
followLocatorNameChanges,
list,
listIdentifiers,
followChanges,
followNameChanges,
lookup,
reverseLookup,
write,
Expand Down Expand Up @@ -236,7 +264,10 @@ export const makeDirectoryMaker = ({
M.interface('EndoDirectory', {}, { defaultGuards: 'passable' }),
{
...directory,
followChanges: () => makeIteratorRef(directory.followChanges()),
/** @param {string} locator */
followLocatorNameChanges: locator =>
makeIteratorRef(directory.followLocatorNameChanges(locator)),
followNameChanges: () => makeIteratorRef(directory.followNameChanges()),
},
);
};
Expand Down
13 changes: 10 additions & 3 deletions packages/daemon/src/guest.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ export const makeGuestMaker = ({ provide, makeMailbox, makeDirectoryNode }) => {
has,
identify,
locate,
reverseLocate,
list,
listIdentifiers,
followChanges,
followNameChanges,
followLocatorNameChanges,
lookup,
reverseLookup,
write,
Expand Down Expand Up @@ -85,9 +87,11 @@ export const makeGuestMaker = ({ provide, makeMailbox, makeDirectoryNode }) => {
identify,
reverseIdentify,
locate,
reverseLocate,
list,
listIdentifiers,
followChanges,
followLocatorNameChanges,
followNameChanges,
lookup,
reverseLookup,
write,
Expand All @@ -113,8 +117,11 @@ export const makeGuestMaker = ({ provide, makeMailbox, makeDirectoryNode }) => {
M.interface('EndoGuest', {}, { defaultGuards: 'passable' }),
{
...guest,
followChanges: () => makeIteratorRef(guest.followChanges()),
/** @param {string} locator */
followLocatorNameChanges: locator =>
makeIteratorRef(guest.followLocatorNameChanges(locator)),
followMessages: () => makeIteratorRef(guest.followMessages()),
followNameChanges: () => makeIteratorRef(guest.followNameChanges()),
},
);
};
Expand Down
13 changes: 10 additions & 3 deletions packages/daemon/src/host.js
Original file line number Diff line number Diff line change
Expand Up @@ -469,9 +469,11 @@ export const makeHostMaker = ({
identify,
lookup,
locate,
reverseLocate,
list,
listIdentifiers,
followChanges,
followNameChanges,
followLocatorNameChanges,
reverseLookup,
write,
remove,
Expand All @@ -498,9 +500,11 @@ export const makeHostMaker = ({
identify,
reverseIdentify,
locate,
reverseLocate,
list,
listIdentifiers,
followChanges,
followLocatorNameChanges,
followNameChanges,
lookup,
reverseLookup,
write,
Expand Down Expand Up @@ -538,8 +542,11 @@ export const makeHostMaker = ({
M.interface('EndoHost', {}, { defaultGuards: 'passable' }),
{
...host,
followChanges: () => makeIteratorRef(host.followChanges()),
/** @param {string} locator */
followLocatorNameChanges: locator =>
makeIteratorRef(host.followLocatorNameChanges(locator)),
followMessages: () => makeIteratorRef(host.followMessages()),
followNameChanges: () => makeIteratorRef(host.followNameChanges()),
},
);

Expand Down
36 changes: 23 additions & 13 deletions packages/daemon/src/locator.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @ts-check

import { parseId, isValidNumber } from './formula-identifier.js';
import { formatId, isValidNumber, parseId } from './formula-identifier.js';
import { isValidFormulaType } from './formula-type.js';

const { quote: q } = assert;
Expand Down Expand Up @@ -28,48 +28,50 @@ const isValidLocatorType = allegedType =>
*/
const assertValidLocatorType = allegedType => {
if (!isValidLocatorType(allegedType)) {
assert.Fail`Unrecognized locator type ${q(allegedType)}`;
throw assert.error(`Unrecognized locator type ${q(allegedType)}`);
rekmarks marked this conversation as resolved.
Show resolved Hide resolved
}
};

/** @param {string} allegedLocator */
/**
* @param {string} allegedLocator
* @returns {{ formulaType: string, node: string, number: string }}
*/
export const parseLocator = allegedLocator => {
const errorPrefix = `Invalid locator ${q(allegedLocator)}:`;

if (!URL.canParse(allegedLocator)) {
assert.Fail`${errorPrefix} Invalid URL.`;
throw assert.error(`${errorPrefix} Invalid URL.`);
}
const url = new URL(allegedLocator);

if (!allegedLocator.startsWith('endo://')) {
assert.Fail`${errorPrefix} Invalid protocol.`;
throw assert.error(`${errorPrefix} Invalid protocol.`);
}

const node = url.host;
if (!isValidNumber(node)) {
assert.Fail`${errorPrefix} Invalid node identifier.`;
throw assert.error(`${errorPrefix} Invalid node identifier.`);
}

if (
url.searchParams.size !== 2 ||
!url.searchParams.has('id') ||
!url.searchParams.has('type')
) {
assert.Fail`${errorPrefix} Invalid search params.`;
throw assert.error(`${errorPrefix} Invalid search params.`);
}

const id = url.searchParams.get('id');
if (id === null || !isValidNumber(id)) {
assert.Fail`${errorPrefix} Invalid id.`;
const number = url.searchParams.get('id');
if (number === null || !isValidNumber(number)) {
throw assert.error(`${errorPrefix} Invalid id.`);
}

const formulaType = url.searchParams.get('type');
if (formulaType === null || !isValidLocatorType(formulaType)) {
assert.Fail`${errorPrefix} Invalid type.`;
throw assert.error(`${errorPrefix} Invalid type.`);
}

/** @type {{ formulaType: string, node: string, id: string }} */
return { formulaType, node, id };
return { formulaType, node, number };
};

/** @param {string} allegedLocator */
Expand All @@ -94,3 +96,11 @@ export const formatLocator = (id, formulaType) => {

return url.toString();
};

/**
* @param {string} locator
*/
export const idFromLocator = locator => {
const { number, node } = parseLocator(locator);
return formatId({ number, node });
};
4 changes: 4 additions & 0 deletions packages/daemon/src/multimap.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ const internalMakeMultimap = mapConstructor => {
get: key => map.get(key)?.keys().next().value,

getAllFor: key => Array.from(map.get(key) ?? []),

has: key => map.has(key),
};
};

Expand Down Expand Up @@ -92,6 +94,8 @@ export const makeBidirectionalMultimap = () => {
return keyForValues.deleteAll(key);
},

has: key => keyForValues.has(key),

hasValue: value => valueForKey.has(value),

get: key => keyForValues.get(key),
Expand Down
28 changes: 24 additions & 4 deletions packages/daemon/src/pet-sitter.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,35 @@ export const makePetSitter = (petStore, specialNames) => {
const list = () =>
harden([...Object.keys(specialNames).sort(), ...petStore.list()]);

/** @type {import('./types.js').PetStore['follow']} */
const follow = async function* currentAndSubsequentNames() {
/** @type {import('./types.js').PetStore['followNameChanges']} */
const followNameChanges = async function* currentAndSubsequentNames() {
for (const name of Object.keys(specialNames).sort()) {
const idRecord = idRecordForName(name);
yield /** @type {{ add: string, value: import('./types.js').IdRecord }} */ ({
add: name,
value: idRecord,
});
}
yield* petStore.follow();
yield* petStore.followNameChanges();
};

/** @type {import('./types.js').PetStore['followIdNameChanges']} */
const followIdNameChanges = async function* currentAndSubsequentIds(id) {
const subscription = petStore.followIdNameChanges(id);

const idSpecialNames = Object.entries(specialNames)
.filter(([_, specialId]) => specialId === id)
.map(([specialName, _]) => specialName);

// The first published event contains the existing names for the id, if any.
const { value: existingNames } = await subscription.next();
existingNames?.names?.unshift(...idSpecialNames);
existingNames?.names?.sort();
yield /** @type {import('./types.js').PetStoreIdNameChange} */ (
existingNames
);

yield* subscription;
};

/** @type {import('./types.js').PetStore['reverseIdentify']} */
Expand All @@ -77,7 +96,8 @@ export const makePetSitter = (petStore, specialNames) => {
identifyLocal,
reverseIdentify,
list,
follow,
followIdNameChanges,
followNameChanges,
write,
remove,
rename,
Expand Down
Loading
Loading