-
Notifications
You must be signed in to change notification settings - Fork 189
/
marshal.js
119 lines (105 loc) · 3.7 KB
/
marshal.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import { Far } from '@endo/far';
import { makeMarshal } from '@endo/marshal';
import { isStreamCell } from './lib-chainStorage.js';
const { Fail } = assert;
/**
* Should be a union with Remotable, but that's `any`, making this type meaningless
*
* @typedef {{ getBoardId: () => string }} BoardRemote
*/
/**
* @param {*} slotInfo
* @returns {BoardRemote}
*/
export const makeBoardRemote = ({ boardId, iface }) => {
const nonalleged = iface ? iface.replace(/^Alleged: /, '') : '';
return Far(`BoardRemote${nonalleged}`, { getBoardId: () => boardId });
};
export const slotToBoardRemote = (boardId, iface) =>
makeBoardRemote({ boardId, iface });
export const boardValToSlot = val => {
if ('getBoardId' in val) {
return val.getBoardId();
}
Fail`unknown obj in boardSlottingMarshaller.valToSlot ${val}`;
};
/**
* A marshaller which can serialize getBoardId() -bearing
* Remotables. This allows the caller to pick their slots. The
* deserializer is configurable: the default cannot handle
* Remotable-bearing data.
*
* @param {(slot: string, iface: string) => any} [slotToVal]
* @returns {Omit<import('@endo/marshal').Marshal<string>, 'serialize' | 'unserialize'>}
*/
export const boardSlottingMarshaller = (slotToVal = undefined) => {
return makeMarshal(boardValToSlot, slotToVal, {
serializeBodyFormat: 'smallcaps',
});
};
// TODO: Consolidate with `insistCapData` functions from swingset-liveslots,
// swingset-xsnap-supervisor, etc.
/**
* @param {unknown} data
* @returns {asserts data is import('@endo/marshal').CapData<string>}
*/
const assertCapData = data => {
assert.typeof(data, 'object');
assert(data);
assert.typeof(data.body, 'string');
assert(Array.isArray(data.slots));
// XXX check that the .slots array elements are actually strings
};
harden(assertCapData);
/**
* Read and unmarshal a value from a map representation of vstorage data
*
* @param {Map<string, string>} data
* @param {string} key
* @param {ReturnType<typeof import('@endo/marshal').makeMarshal>['fromCapData']} fromCapData
* @param {number} index index of the desired value in a deserialized stream cell
*/
export const unmarshalFromVstorage = (data, key, fromCapData, index) => {
const serialized = data.get(key) || Fail`no data for ${key}`;
assert.typeof(serialized, 'string');
assert.typeof(index, 'number');
const streamCell = JSON.parse(serialized);
if (!isStreamCell(streamCell)) {
throw Fail`not a StreamCell: ${streamCell}`;
}
const { values } = streamCell;
values.length > 0 || Fail`no StreamCell values: ${streamCell}`;
const marshalled = values.at(index);
assert.typeof(marshalled, 'string');
/** @type {import("@endo/marshal").CapData<string>} */
const capData = harden(JSON.parse(marshalled));
assertCapData(capData);
const unmarshalled = fromCapData(capData);
return unmarshalled;
};
harden(unmarshalFromVstorage);
/**
* Provide access to object graphs serialized in vstorage.
*
* @param {Array<[string, string]>} entries
* @param {(slot: string, iface?: string) => any} [slotToVal]
*/
export const makeHistoryReviver = (entries, slotToVal = undefined) => {
const board = boardSlottingMarshaller(slotToVal);
const vsMap = new Map(entries);
const fromCapData = (...args) =>
Reflect.apply(board.fromCapData, board, args);
const getItem = key => unmarshalFromVstorage(vsMap, key, fromCapData, -1);
const children = prefix => {
prefix.endsWith('.') || Fail`prefix must end with '.'`;
return harden([
...new Set(
entries
.map(([k, _]) => k)
.filter(k => k.startsWith(prefix))
.map(k => k.slice(prefix.length).split('.')[0]),
),
]);
};
return harden({ getItem, children, has: k => vsMap.get(k) !== undefined });
};