-
Notifications
You must be signed in to change notification settings - Fork 226
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement and test makeNameHubKit as specified
- Loading branch information
1 parent
adbd6e5
commit 7deac62
Showing
3 changed files
with
162 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)', | ||
}); | ||
}); |