Skip to content

Commit

Permalink
feat: implement and test makeNameHubKit as specified
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelfig committed Mar 26, 2021
1 parent adbd6e5 commit 7deac62
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 0 deletions.
82 changes: 82 additions & 0 deletions packages/cosmic-swingset/lib/ag-solo/vats/nameHub.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// @ts-check

import { E } from '@agoric/eventual-send';
import { Far } from '@agoric/marshal';
import { makePromiseKit } from '@agoric/promise-kit';
import { makeStore } from '@agoric/store';

import './types.js';

/**
* @returns {NameHubKit}
*/
export const makeNameHubKit = () => {
/** @typedef {Partial<PromiseRecord<unknown> & { value: unknown }>} NameRecord */
/** @type {Store<unknown, NameRecord>} */
const keyToRecord = makeStore('nameKey');

/** @type {NameHub} */
const nameHub = {
async lookup(...path) {
if (path.length === 0) {
return nameHub;
}
const [first, ...remaining] = path;
const record = keyToRecord.get(first);
/** @type {any} */
const firstValue = record.promise || record.value;
if (remaining.length === 0) {
return firstValue;
}
return E(firstValue).lookup(...remaining);
},
};

/** @type {NameAdmin} */
const nameAdmin = {
reserve(key) {
if (keyToRecord.has(key)) {
// If we already have a promise, don't use a new one.
if (keyToRecord.get(key).promise) {
return;
}
keyToRecord.set(key, makePromiseKit());
} else {
keyToRecord.init(key, makePromiseKit());
}
},
update(key, newValue) {
const record = harden({ value: newValue });
if (keyToRecord.has(key)) {
const old = keyToRecord.get(key);
if (old.resolve) {
old.resolve(newValue);
}
keyToRecord.set(key, record);
} else {
keyToRecord.init(key, record);
}
},
delete(key) {
if (keyToRecord.has(key)) {
// Reject only if already exists.
const old = keyToRecord.get(key);
if (old.reject) {
old.reject(Error(`Value has been deleted`));
// Silence unhandled rejections.
old.promise && old.promise.catch(_ => {});
}
}
// This delete may throw. Reflect it to callers.
keyToRecord.delete(key);
},
};

const nameHubKit = {
nameHub: Far('nameHub', nameHub),
nameAdmin: Far('nameAdmin', nameAdmin),
};

harden(nameHubKit);
return nameHubKit;
};
27 changes: 27 additions & 0 deletions packages/cosmic-swingset/lib/ag-solo/vats/types.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,34 @@
// @ts-check

/**
* @typedef {Object} Board
* @property {(id: string) => any} getValue
* @property {(value: any) => string} getId
* @property {(value: any) => boolean} has
* @property {() => string[]} ids
*/

/**
* @typedef {Object} NameHub
* @property {(...path: Array<unknown>) => Promise<unknown>} lookup Look up a
* path of keys starting from the current NameHub.
*/

/**
* @typedef {Object} NameAdmin
* @property {(key: unknown) => void} reserve Mark a key as reserved; will
* return a promise that is fulfilled when the key is updated (or rejected when
* deleted).
* @property {(key: unknown, newValue: unknown) => void} update Fulfill an
* outstanding reserved promise (if any) to the newValue and set the key to the
* newValue.
* @property {(key: unknown) => void} delete Delete a value and reject an
* outstanding reserved promise (if any).
*/

/**
* @typedef {Object} NameHubKit A kit of a NameHub and its corresponding
* NameAdmin.
* @property {NameHub} nameHub
* @property {NameAdmin} nameAdmin
*/
53 changes: 53 additions & 0 deletions packages/cosmic-swingset/test/unitTests/test-name-hub.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import '@agoric/install-ses';
import test from 'ava';

import { makeNameHubKit } from '../../lib/ag-solo/vats/nameHub';

test('makeNameHubKit - reserve and update', async t => {
const { nameAdmin, nameHub } = makeNameHubKit();

await t.throwsAsync(() => nameHub.lookup('hello'), {
message: '"nameKey" not found: (a string)',
});

// Try reserving and looking up.
nameAdmin.reserve('hello');

let lookedUpHello = false;
const lookupHelloP = nameHub
.lookup('hello')
.finally(() => (lookedUpHello = true));

t.falsy(lookedUpHello);
nameAdmin.update('hello', 'foo');
t.is(await lookupHelloP, 'foo');
t.truthy(lookedUpHello);

nameAdmin.update('hello', 'foo2');
t.is(await nameHub.lookup('hello'), 'foo2');
});

test('makeNameHubKit - reserve and delete', async t => {
const { nameAdmin, nameHub } = makeNameHubKit();

await t.throwsAsync(() => nameHub.lookup('goodbye'), {
message: '"nameKey" not found: (a string)',
});

nameAdmin.reserve('goodbye');
let lookedUpGoodbye = false;
const lookupGoodbyeP = nameHub
.lookup('bar')
.finally(() => (lookedUpGoodbye = true));

t.falsy(lookedUpGoodbye);
nameAdmin.delete('goodbye');
await t.throwsAsync(lookupGoodbyeP, {
message: '"nameKey" not found: (a string)',
});
t.truthy(lookedUpGoodbye);

await t.throwsAsync(() => nameHub.lookup('goodbye'), {
message: '"nameKey" not found: (a string)',
});
});

0 comments on commit 7deac62

Please sign in to comment.