Skip to content

Commit

Permalink
feat(grain): add support for remote grainMaps
Browse files Browse the repository at this point in the history
  • Loading branch information
kumavis committed Nov 21, 2023
1 parent baf72ac commit bb3dd7e
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 14 deletions.
51 changes: 48 additions & 3 deletions packages/grain/src/captp.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,30 @@ import { makeIteratorRef } from '@endo/daemon/reader-ref.js';
import { makeRefIterator } from '@endo/daemon/ref-reader.js';
import { makeArrayGrainFromSyncGrain, makeSubscribedSyncGrainFromAsyncGrain } from './index.js';

// given a grain, returns a remote grain for sending over captp
export const makeRemoteGrain = (localSyncGrain, name = 'grain') => {
return Far(name, {
const makeRemoteGrainInterface = (localSyncGrain) => {
return {
...localSyncGrain,
follow: async (canceled) => {
return makeIteratorRef(localSyncGrain.follow(canceled))
},
}
}

// given a grain, returns a remote grain for sending over captp
export const makeRemoteGrain = (localSyncGrain, name = 'grain') => {
return Far(name, {
...makeRemoteGrainInterface(localSyncGrain),
})
}

export const makeRemoteGrainMap = (localSyncGrainMap, name = 'grainmap') => {
const getGrain = async (key, childName = `${name}/${key}`) => {
const childLocalSyncGrain = localSyncGrainMap.getGrain(key)
return makeRemoteGrain(childLocalSyncGrain, childName)
}
return Far(name, {
...makeRemoteGrainInterface(localSyncGrainMap),
getGrain,
})
}

Expand Down Expand Up @@ -75,6 +92,19 @@ export const makeLocalAsyncGrainFromRemote = (remoteGrain) => {
return asyncGrain
}

export const makeLocalAsyncGrainMapFromRemote = (remoteGrain) => {
const asyncGrain = makeLocalAsyncGrainFromRemote(remoteGrain)
const getGrain = (key) => {
const childRemoteGrain = E(remoteGrain).getGrain(key)
const childLocalAsyncGrain = makeLocalAsyncGrainFromRemote(childRemoteGrain)
return childLocalAsyncGrain
}
return {
...asyncGrain,
getGrain,
}
}

export const makeReadonlyGrainFromRemote = (remoteGrain, initValue) => {
const localAsyncGrain = makeLocalAsyncGrainFromRemote(remoteGrain)
const localSyncGrain = localAsyncGrain.makeSubscribedSyncGrain(initValue)
Expand All @@ -90,3 +120,18 @@ export const makeReadonlyArrayGrainFromRemote = (remoteGrain, initValue = []) =>
const localArrayGrain = makeArrayGrainFromSyncGrain(localSyncGrain).readonly()
return localArrayGrain
}

export const makeReadonlyGrainMapFromRemote = (remoteGrainMap, initValue = {}) => {
const localAsyncGrain = makeLocalAsyncGrainFromRemote(remoteGrainMap)
const localAsyncGrainMap = makeLocalAsyncGrainMapFromRemote(remoteGrainMap)
const localSyncGrain = localAsyncGrain.makeSubscribedSyncGrain(initValue)
const getGrain = (key) => {
const childLocalAsyncGrain = localAsyncGrainMap.getGrain(key)
const childLocalSyncGrain = childLocalAsyncGrain.makeSubscribedSyncGrain()
return childLocalSyncGrain
}
return {
...localSyncGrain,
getGrain,
}
}
53 changes: 42 additions & 11 deletions packages/grain/test/test-index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { makePromiseKit } from '@endo/promise-kit';
import { test } from './prepare-test-env-ava.js';
import { makeSyncGrain, makeSyncArrayGrain } from '../src/index.js';
import { makeSyncGrain, makeSyncArrayGrain, makeSyncGrainMap } from '../src/index.js';
import { makeReadonlyGrainMapFromRemote, makeRemoteGrainMap } from '../src/captp.js';

const delay = (duration) => new Promise(resolve => setTimeout(resolve, duration))

test('grain / set + get', async t => {
const grain = makeSyncGrain();
Expand All @@ -15,12 +18,12 @@ test('grain / subscribe + follow', async t => {
let latestSubscribeValue;
let latestFollowValue;

const unsubscribe = grain.subscribe((start) => {
latestSubscribeValue = start;
const unsubscribe = grain.subscribe((value) => {
latestSubscribeValue = value;
})
;(async function () {
for await (const start of grain.follow(canceled)) {
latestFollowValue = start;
for await (const value of grain.follow(canceled)) {
latestFollowValue = value;
}
})()
const cleanup = () => {
Expand All @@ -32,7 +35,7 @@ test('grain / subscribe + follow', async t => {
grain.set({ hello: 123 });
grain.set({ hello: 123, foo: 'bar' });

await new Promise(resolve => setTimeout(resolve, 1000))
await delay(500)
cleanup();

t.deepEqual(latestSubscribeValue, { hello: 123, foo: 'bar' });
Expand All @@ -46,12 +49,12 @@ test('array grain / subscribe + follow', async t => {
let latestSubscribeValue;
let latestFollowValue;

const unsubscribe = grain.subscribe((start) => {
latestSubscribeValue = start;
const unsubscribe = grain.subscribe((value) => {
latestSubscribeValue = value;
})
;(async function () {
for await (const start of grain.follow(canceled)) {
latestFollowValue = start;
for await (const value of grain.follow(canceled)) {
latestFollowValue = value;
}
})()
const cleanup = () => {
Expand All @@ -63,7 +66,7 @@ test('array grain / subscribe + follow', async t => {
grain.push({ count: 123 });
grain.push({ foo: 'bar' });

await new Promise(resolve => setTimeout(resolve, 1000))
await delay(500)
cleanup();

t.deepEqual(latestSubscribeValue, [
Expand All @@ -78,3 +81,31 @@ test('array grain / subscribe + follow', async t => {
]);
});

test('remote grainMap', async t => {
// create source grainMap
const sourceGrainA = makeSyncGrain('hello');
const sourceGrainB = makeSyncGrain('world');
const sourceGrainMap = makeSyncGrainMap({
a: sourceGrainA,
b: sourceGrainB,
});
const sourceGrainMapRemote = makeRemoteGrainMap(sourceGrainMap);
// --- network boundary ---
const destGrainMap = makeReadonlyGrainMapFromRemote(sourceGrainMapRemote);
const destGrainA = destGrainMap.getGrain('a');
const destGrainB = destGrainMap.getGrain('b');
// test
const { promise: canceled, resolve: cancel } = makePromiseKit();
const followA = destGrainA.follow(canceled);
const followB = destGrainB.follow(canceled);
// skip initial uninitialized values
await followA.next();
await followB.next();
// test
const { value: valueA } = await followA.next();
const { value: valueB } = await followB.next();
t.deepEqual(valueA, 'hello');
t.deepEqual(valueB, 'world');
// cleanup
cancel()
});

0 comments on commit bb3dd7e

Please sign in to comment.