@agoric/zone 0.2.3-dev-8358d20.0 → 0.2.3-dev-8baf0aa.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +13 -7
- package/src/durable.js +47 -16
- package/src/heap.js +38 -18
- package/src/index.js +1 -0
- package/src/is-passable.js +23 -0
- package/src/keys.js +14 -0
- package/src/make-once.js +86 -0
- package/src/virtual.js +37 -15
- package/test/prepare-test-env-ava.js +19 -0
- package/test/test-exos.js +268 -0
- package/test/test-make-once.js +66 -0
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/zone",
|
|
3
|
-
"version": "0.2.3-dev-
|
|
3
|
+
"version": "0.2.3-dev-8baf0aa.0+8baf0aa",
|
|
4
4
|
"description": "Allocation zone abstraction for objects on the heap, persistent stores, etc.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": "https://github.com/Agoric/agoric-sdk",
|
|
7
7
|
"main": "./src/index.js",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "exit 0",
|
|
10
|
-
"test": "
|
|
11
|
-
"test:c8": "
|
|
10
|
+
"test": "ava",
|
|
11
|
+
"test:c8": "c8 $C8_OPTIONS ava --config=ava-nesm.config.js",
|
|
12
12
|
"test:xs": "exit 0",
|
|
13
13
|
"lint-fix": "yarn lint:eslint --fix",
|
|
14
14
|
"lint": "run-s --continue-on-error lint:*",
|
|
@@ -25,9 +25,15 @@
|
|
|
25
25
|
"author": "Agoric",
|
|
26
26
|
"license": "Apache-2.0",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@agoric/store": "0.9.3-dev-
|
|
29
|
-
"@agoric/vat-data": "0.5.3-dev-
|
|
30
|
-
"@endo/
|
|
28
|
+
"@agoric/store": "0.9.3-dev-8baf0aa.0+8baf0aa",
|
|
29
|
+
"@agoric/vat-data": "0.5.3-dev-8baf0aa.0+8baf0aa",
|
|
30
|
+
"@endo/exo": "^0.2.3",
|
|
31
|
+
"@endo/far": "^0.2.19",
|
|
32
|
+
"@endo/pass-style": "^0.1.4"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@agoric/swingset-vat": "0.32.3-dev-8baf0aa.0+8baf0aa",
|
|
36
|
+
"ava": "^5.3.0"
|
|
31
37
|
},
|
|
32
38
|
"publishConfig": {
|
|
33
39
|
"access": "public"
|
|
@@ -42,5 +48,5 @@
|
|
|
42
48
|
"timeout": "20m",
|
|
43
49
|
"workerThreads": false
|
|
44
50
|
},
|
|
45
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "8baf0aabe3cadeb1cd07fd30641d11baf3e68eaf"
|
|
46
52
|
}
|
package/src/durable.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
// @jessie-check
|
|
2
3
|
|
|
4
|
+
import { Far } from '@endo/far';
|
|
3
5
|
import {
|
|
4
6
|
canBeDurable,
|
|
5
7
|
makeScalarMapStore,
|
|
@@ -10,20 +12,33 @@ import {
|
|
|
10
12
|
provideDurableSetStore,
|
|
11
13
|
provideDurableWeakMapStore,
|
|
12
14
|
provideDurableWeakSetStore,
|
|
13
|
-
M,
|
|
14
15
|
} from '@agoric/vat-data';
|
|
15
16
|
|
|
16
|
-
import {
|
|
17
|
+
import { makeOnceKit } from './make-once.js';
|
|
18
|
+
import { agoricVatDataKeys as keys } from './keys.js';
|
|
19
|
+
import { isPassable } from './is-passable.js';
|
|
17
20
|
|
|
18
21
|
const { Fail } = assert;
|
|
19
22
|
|
|
23
|
+
/**
|
|
24
|
+
* A variant of `canBeDurable` that returns `false` instead of ever throwing.
|
|
25
|
+
*
|
|
26
|
+
* @param {unknown} specimen
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
29
|
+
const isStorable = specimen => isPassable(specimen) && canBeDurable(specimen);
|
|
30
|
+
harden(isStorable);
|
|
31
|
+
|
|
20
32
|
/**
|
|
21
33
|
* @param {() => import('@agoric/vat-data').Baggage} getBaggage
|
|
22
34
|
*/
|
|
23
35
|
const attachDurableStores = getBaggage => {
|
|
24
36
|
/** @type {import('.').Zone['mapStore']} */
|
|
25
|
-
const mapStore = (label, options) =>
|
|
26
|
-
|
|
37
|
+
const mapStore = (label, options) => {
|
|
38
|
+
const baggage = getBaggage();
|
|
39
|
+
const ret = provideDurableMapStore(baggage, label, options);
|
|
40
|
+
return ret;
|
|
41
|
+
};
|
|
27
42
|
/** @type {import('.').Zone['setStore']} */
|
|
28
43
|
const setStore = (label, options) =>
|
|
29
44
|
provideDurableSetStore(getBaggage(), label, options);
|
|
@@ -38,7 +53,7 @@ const attachDurableStores = getBaggage => {
|
|
|
38
53
|
return Far('durableStores', {
|
|
39
54
|
// eslint-disable-next-line no-use-before-define
|
|
40
55
|
detached: () => detachedDurableStores,
|
|
41
|
-
isStorable
|
|
56
|
+
isStorable,
|
|
42
57
|
mapStore,
|
|
43
58
|
setStore,
|
|
44
59
|
weakMapStore,
|
|
@@ -47,7 +62,7 @@ const attachDurableStores = getBaggage => {
|
|
|
47
62
|
};
|
|
48
63
|
|
|
49
64
|
/** @type {import('.').Stores} */
|
|
50
|
-
|
|
65
|
+
const detachedDurableStores = attachDurableStores(() =>
|
|
51
66
|
makeScalarMapStore('detached'),
|
|
52
67
|
);
|
|
53
68
|
|
|
@@ -55,10 +70,20 @@ export const detachedDurableStores = attachDurableStores(() =>
|
|
|
55
70
|
* Create a zone whose objects persist between Agoric vat upgrades.
|
|
56
71
|
*
|
|
57
72
|
* @param {import('@agoric/vat-data').Baggage} baggage
|
|
73
|
+
* @param {string} [baseLabel]
|
|
58
74
|
* @returns {import('.').Zone}
|
|
59
75
|
*/
|
|
60
|
-
export const makeDurableZone = baggage => {
|
|
76
|
+
export const makeDurableZone = (baggage, baseLabel = 'durableZone') => {
|
|
61
77
|
baggage || Fail`baggage required`;
|
|
78
|
+
|
|
79
|
+
const attachedStores = attachDurableStores(() => baggage);
|
|
80
|
+
|
|
81
|
+
const { makeOnce, wrapProvider } = makeOnceKit(
|
|
82
|
+
baseLabel,
|
|
83
|
+
attachedStores,
|
|
84
|
+
baggage,
|
|
85
|
+
);
|
|
86
|
+
|
|
62
87
|
/** @type {import('.').Zone['exoClass']} */
|
|
63
88
|
const exoClass = (...args) => prepareExoClass(baggage, ...args);
|
|
64
89
|
/** @type {import('.').Zone['exoClassKit']} */
|
|
@@ -66,22 +91,28 @@ export const makeDurableZone = baggage => {
|
|
|
66
91
|
/** @type {import('.').Zone['exo']} */
|
|
67
92
|
const exo = (...args) => prepareExo(baggage, ...args);
|
|
68
93
|
|
|
69
|
-
const
|
|
94
|
+
const subZoneStore = wrapProvider(attachedStores.mapStore, keys.zone);
|
|
70
95
|
|
|
71
96
|
/** @type {import('.').Zone['subZone']} */
|
|
72
97
|
const subZone = (label, options = {}) => {
|
|
73
|
-
const subBaggage =
|
|
74
|
-
return makeDurableZone(subBaggage);
|
|
98
|
+
const subBaggage = subZoneStore(label, options);
|
|
99
|
+
return makeDurableZone(subBaggage, `${baseLabel}.${label}`);
|
|
75
100
|
};
|
|
76
101
|
|
|
77
102
|
return Far('durableZone', {
|
|
78
|
-
exo,
|
|
79
|
-
exoClass,
|
|
80
|
-
exoClassKit,
|
|
103
|
+
exo: wrapProvider(exo, keys.exo),
|
|
104
|
+
exoClass: wrapProvider(exoClass, keys.exoClass),
|
|
105
|
+
exoClassKit: wrapProvider(exoClassKit, keys.exoClassKit),
|
|
81
106
|
subZone,
|
|
82
|
-
|
|
107
|
+
|
|
108
|
+
makeOnce,
|
|
109
|
+
detached: attachedStores.detached,
|
|
110
|
+
isStorable: attachedStores.isStorable,
|
|
111
|
+
|
|
112
|
+
mapStore: wrapProvider(attachedStores.mapStore, keys.store),
|
|
113
|
+
setStore: wrapProvider(attachedStores.setStore, keys.store),
|
|
114
|
+
weakMapStore: wrapProvider(attachedStores.weakMapStore, keys.store),
|
|
115
|
+
weakSetStore: wrapProvider(attachedStores.weakSetStore, keys.store),
|
|
83
116
|
});
|
|
84
117
|
};
|
|
85
118
|
harden(makeDurableZone);
|
|
86
|
-
|
|
87
|
-
export { M };
|
package/src/heap.js
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
// @jessie-check
|
|
2
3
|
|
|
4
|
+
import { Far } from '@endo/far';
|
|
5
|
+
import { makeExo, defineExoClass, defineExoClassKit } from '@endo/exo';
|
|
3
6
|
import {
|
|
4
|
-
makeExo,
|
|
5
|
-
defineExoClass,
|
|
6
|
-
defineExoClassKit,
|
|
7
7
|
makeScalarMapStore,
|
|
8
8
|
makeScalarSetStore,
|
|
9
9
|
makeScalarWeakMapStore,
|
|
10
10
|
makeScalarWeakSetStore,
|
|
11
|
-
M,
|
|
12
11
|
} from '@agoric/store';
|
|
13
12
|
|
|
14
|
-
import {
|
|
13
|
+
import { makeOnceKit } from './make-once.js';
|
|
14
|
+
import { agoricVatDataKeys as keys } from './keys.js';
|
|
15
|
+
import { isPassable } from './is-passable.js';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* @type {import('.').Stores}
|
|
18
19
|
*/
|
|
19
|
-
const
|
|
20
|
-
detached: () =>
|
|
21
|
-
isStorable:
|
|
20
|
+
const detachedHeapStores = Far('heapStores', {
|
|
21
|
+
detached: () => detachedHeapStores,
|
|
22
|
+
isStorable: isPassable,
|
|
22
23
|
|
|
23
24
|
setStore: makeScalarSetStore,
|
|
24
25
|
mapStore: makeScalarMapStore,
|
|
@@ -27,16 +28,35 @@ const heapStores = Far('heapStores', {
|
|
|
27
28
|
});
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
+
* Create a heap (in-memory) zone that uses the default exo and store implementations.
|
|
31
32
|
*
|
|
32
|
-
* @
|
|
33
|
+
* @param {string} [baseLabel]
|
|
34
|
+
* @returns {import('.').Zone}
|
|
33
35
|
*/
|
|
34
|
-
export const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
export const makeHeapZone = (baseLabel = 'heapZone') => {
|
|
37
|
+
const { makeOnce, wrapProvider } = makeOnceKit(baseLabel, detachedHeapStores);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} label
|
|
41
|
+
* @param {any} _options
|
|
42
|
+
*/
|
|
43
|
+
const makeSubZone = (label, _options) =>
|
|
44
|
+
makeHeapZone(`${baseLabel}.${label}`);
|
|
45
|
+
|
|
46
|
+
return Far('heapZone', {
|
|
47
|
+
exo: wrapProvider(makeExo, keys.exo),
|
|
48
|
+
exoClass: wrapProvider(defineExoClass, keys.exoClass),
|
|
49
|
+
exoClassKit: wrapProvider(defineExoClassKit, keys.exoClassKit),
|
|
50
|
+
subZone: wrapProvider(makeSubZone),
|
|
51
|
+
|
|
52
|
+
makeOnce,
|
|
53
|
+
detached: detachedHeapStores.detached,
|
|
54
|
+
isStorable: detachedHeapStores.isStorable,
|
|
41
55
|
|
|
42
|
-
|
|
56
|
+
mapStore: wrapProvider(detachedHeapStores.mapStore),
|
|
57
|
+
setStore: wrapProvider(detachedHeapStores.setStore),
|
|
58
|
+
weakMapStore: wrapProvider(detachedHeapStores.weakMapStore),
|
|
59
|
+
weakSetStore: wrapProvider(detachedHeapStores.weakSetStore),
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
harden(makeHeapZone);
|
package/src/index.js
CHANGED
|
@@ -19,6 +19,7 @@ defineExoClassKit;
|
|
|
19
19
|
* @property {typeof makeExo} exo create a singleton exo-object instance bound to this zone
|
|
20
20
|
* @property {typeof defineExoClass} exoClass create a maker function that can be used to create exo-objects bound to this zone
|
|
21
21
|
* @property {typeof defineExoClassKit} exoClassKit create a "kit" maker function that can be used to create a record of exo-objects sharing the same state
|
|
22
|
+
* @property {<T>(key: string, maker: (key: string) => T) => T} makeOnce create or retrieve a singleton object bound to this zone
|
|
22
23
|
* @property {(label: string, options?: StoreOptions) => Zone} subZone create a new Zone that can be passed to untrusted consumers without exposing the storage of the parent zone
|
|
23
24
|
*/
|
|
24
25
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { passStyleOf } from '@endo/pass-style';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Is `specimen` Passable? This returns true iff `passStyleOf(specimen)`
|
|
5
|
+
* returns a string. This returns `false` iff `passStyleOf(specimen)` throws.
|
|
6
|
+
* Under no normal circumstance should `isPassable(specimen)` throw.
|
|
7
|
+
*
|
|
8
|
+
* TODO implement an isPassable that does not rely on try/catch, and
|
|
9
|
+
* move it to @endo/pass-style.
|
|
10
|
+
* This implementation is just a standin until then
|
|
11
|
+
*
|
|
12
|
+
* @param {any} specimen
|
|
13
|
+
* @returns {specimen is Passable}
|
|
14
|
+
*/
|
|
15
|
+
export const isPassable = specimen => {
|
|
16
|
+
try {
|
|
17
|
+
// In fact, it never returns undefined. It either returns a
|
|
18
|
+
// string or throws.
|
|
19
|
+
return passStyleOf(specimen) !== undefined;
|
|
20
|
+
} catch (_) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
};
|
package/src/keys.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** @param {string} label */
|
|
2
|
+
const kind = label => `${label}_kindHandle`;
|
|
3
|
+
/** @param {string} label */
|
|
4
|
+
const singleton = label => `${label}_singleton`;
|
|
5
|
+
|
|
6
|
+
/** @type {Record<'exoClass' | 'exoClassKit' | 'exo' | 'store' | 'zone', (label: string) => string[]>} */
|
|
7
|
+
export const agoricVatDataKeys = {
|
|
8
|
+
exoClass: label => harden([kind(label)]),
|
|
9
|
+
exoClassKit: label => harden([kind(label)]),
|
|
10
|
+
exo: label => harden([kind(label), singleton(label)]),
|
|
11
|
+
store: label => harden([label]),
|
|
12
|
+
zone: label => harden([label]),
|
|
13
|
+
};
|
|
14
|
+
harden(agoricVatDataKeys);
|
package/src/make-once.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
const { Fail } = assert;
|
|
3
|
+
|
|
4
|
+
/** @param {string} label */
|
|
5
|
+
const defaultLabelToKeys = label => harden([label]);
|
|
6
|
+
harden(defaultLabelToKeys);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} debugName Only used internally for diagnostics, not available to user code
|
|
10
|
+
* @param {import('.').Stores} stores
|
|
11
|
+
* @param {MapStore<string, any>} [backingStore]
|
|
12
|
+
*/
|
|
13
|
+
export const makeOnceKit = (debugName, stores, backingStore = undefined) => {
|
|
14
|
+
// We need a detached setStore so that it isn't persisted as part of the zone.
|
|
15
|
+
// That way, our usedKeys are only tracked for the current incarnation, which
|
|
16
|
+
// is what we want. Using `debugName` in the label is good for diagnostics,
|
|
17
|
+
// and since it is only for a detached store, it should not be visible to the
|
|
18
|
+
// backing store.
|
|
19
|
+
const usedKeys = stores.detached().setStore(`${debugName} used keys`);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} key
|
|
23
|
+
*/
|
|
24
|
+
const assertOnlyOnce = key => {
|
|
25
|
+
typeof key === 'string' || Fail`key ${key} must be a string`;
|
|
26
|
+
!usedKeys.has(key) ||
|
|
27
|
+
Fail`key ${key} has already been used in this zone and incarnation`;
|
|
28
|
+
|
|
29
|
+
// Mark this key as used. We make no attempt to recover from invalid makers
|
|
30
|
+
// or backingStores.
|
|
31
|
+
usedKeys.add(key);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Ensure the wrapped function is only called once per incarnation. It is
|
|
36
|
+
* expected to update the backing store directly.
|
|
37
|
+
*
|
|
38
|
+
* @template {(key: string, ...rest: unknown[]) => any} T
|
|
39
|
+
* @param {T} provider
|
|
40
|
+
* @param {(label: string) => string[]} [labelToKeys]
|
|
41
|
+
* @returns {T}
|
|
42
|
+
*/
|
|
43
|
+
const wrapProvider = (provider, labelToKeys = defaultLabelToKeys) => {
|
|
44
|
+
/** @type {(...args: Parameters<T>) => ReturnType<T>} */
|
|
45
|
+
const wrapper = (label, ...rest) => {
|
|
46
|
+
for (const key of labelToKeys(label)) {
|
|
47
|
+
assertOnlyOnce(key);
|
|
48
|
+
}
|
|
49
|
+
return provider(label, ...rest);
|
|
50
|
+
};
|
|
51
|
+
return /** @type {T} */ (wrapper);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The best way to understand the purpose of `makeOnce` is to first understand
|
|
56
|
+
* what `makeOnce` does on a durable zone. Used correctly, `makeOnce` should only
|
|
57
|
+
* be called at most once on any zone,key pair during any vat incarnation.
|
|
58
|
+
* Given that constraint, if there is already a value bound to that
|
|
59
|
+
* zone,key pair, it must have been left there by a previous incarnation and
|
|
60
|
+
* `makeOnce` will simply return it. If not, then `maker(key)` is called to
|
|
61
|
+
* determine the initial value of that slot, which will normally be preserved
|
|
62
|
+
* by similar calls to `makeOnce` in future incarnations --- though that will be
|
|
63
|
+
* up to them.
|
|
64
|
+
*
|
|
65
|
+
* Also ensures the maker returns a storable value.
|
|
66
|
+
*
|
|
67
|
+
* @template V
|
|
68
|
+
* @param {string} key The string name of the Zone slot to provide.
|
|
69
|
+
* @param {(key: string) => V} maker Called to create a fresh value to fill an empty slot.
|
|
70
|
+
* @returns {V} The value of the key's slot.
|
|
71
|
+
*/
|
|
72
|
+
const makeOnce = (key, maker) => {
|
|
73
|
+
assertOnlyOnce(key);
|
|
74
|
+
if (backingStore && backingStore.has(key)) {
|
|
75
|
+
return backingStore.get(key);
|
|
76
|
+
}
|
|
77
|
+
const value = maker(key);
|
|
78
|
+
stores.isStorable(value) ||
|
|
79
|
+
Fail`maker return value ${value} is not storable`;
|
|
80
|
+
backingStore && backingStore.init(key, value);
|
|
81
|
+
return value;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return harden({ makeOnce, wrapProvider });
|
|
85
|
+
};
|
|
86
|
+
harden(makeOnceKit);
|
package/src/virtual.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
// @jessie-check
|
|
2
3
|
|
|
4
|
+
import { Far } from '@endo/far';
|
|
3
5
|
import {
|
|
4
|
-
canBeDurable,
|
|
5
6
|
defineVirtualExoClass,
|
|
6
7
|
defineVirtualExoClassKit,
|
|
7
8
|
makeScalarBigMapStore,
|
|
8
9
|
makeScalarBigSetStore,
|
|
9
10
|
makeScalarBigWeakMapStore,
|
|
10
11
|
makeScalarBigWeakSetStore,
|
|
11
|
-
M,
|
|
12
12
|
} from '@agoric/vat-data';
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { makeOnceKit } from './make-once.js';
|
|
15
|
+
import { agoricVatDataKeys as keys } from './keys.js';
|
|
16
|
+
import { isPassable } from './is-passable.js';
|
|
15
17
|
|
|
16
18
|
const emptyRecord = harden({});
|
|
17
19
|
const initEmpty = harden(() => emptyRecord);
|
|
@@ -22,7 +24,7 @@ const initEmpty = harden(() => emptyRecord);
|
|
|
22
24
|
*
|
|
23
25
|
* @type {import('.').Zone['exo']}
|
|
24
26
|
*/
|
|
25
|
-
const
|
|
27
|
+
const makeVirtualExo = (
|
|
26
28
|
label,
|
|
27
29
|
interfaceGuard,
|
|
28
30
|
methods,
|
|
@@ -43,9 +45,9 @@ const defineVirtualExo = (
|
|
|
43
45
|
};
|
|
44
46
|
|
|
45
47
|
/** @type {import('.').Stores} */
|
|
46
|
-
|
|
48
|
+
const detachedVirtualStores = Far('virtualStores', {
|
|
47
49
|
detached: () => detachedVirtualStores,
|
|
48
|
-
isStorable:
|
|
50
|
+
isStorable: isPassable,
|
|
49
51
|
mapStore: makeScalarBigMapStore,
|
|
50
52
|
setStore: makeScalarBigSetStore,
|
|
51
53
|
weakMapStore: makeScalarBigWeakMapStore,
|
|
@@ -56,15 +58,35 @@ export const detachedVirtualStores = Far('virtualStores', {
|
|
|
56
58
|
* A zone that utilizes external storage to reduce the memory footprint of the
|
|
57
59
|
* current vat.
|
|
58
60
|
*
|
|
59
|
-
* @
|
|
61
|
+
* @param {string} [baseLabel]
|
|
62
|
+
* @returns {import('.').Zone}
|
|
60
63
|
*/
|
|
61
|
-
export const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
export const makeVirtualZone = (baseLabel = 'virtualZone') => {
|
|
65
|
+
const { makeOnce, wrapProvider } = makeOnceKit(
|
|
66
|
+
baseLabel,
|
|
67
|
+
detachedVirtualStores,
|
|
68
|
+
);
|
|
66
69
|
|
|
67
|
-
|
|
68
|
-
}
|
|
70
|
+
/**
|
|
71
|
+
* @param {string} label
|
|
72
|
+
* @param {any} _options
|
|
73
|
+
*/
|
|
74
|
+
const makeSubZone = (label, _options) =>
|
|
75
|
+
makeVirtualZone(`${baseLabel}.${label}`);
|
|
69
76
|
|
|
70
|
-
|
|
77
|
+
return Far('virtualZone', {
|
|
78
|
+
exo: wrapProvider(makeVirtualExo, keys.exo),
|
|
79
|
+
exoClass: wrapProvider(defineVirtualExoClass, keys.exoClass),
|
|
80
|
+
exoClassKit: wrapProvider(defineVirtualExoClassKit, keys.exoClassKit),
|
|
81
|
+
subZone: wrapProvider(makeSubZone),
|
|
82
|
+
|
|
83
|
+
makeOnce,
|
|
84
|
+
detached: detachedVirtualStores.detached,
|
|
85
|
+
isStorable: detachedVirtualStores.isStorable,
|
|
86
|
+
|
|
87
|
+
mapStore: wrapProvider(detachedVirtualStores.mapStore),
|
|
88
|
+
setStore: wrapProvider(detachedVirtualStores.setStore),
|
|
89
|
+
weakMapStore: wrapProvider(detachedVirtualStores.weakMapStore),
|
|
90
|
+
weakSetStore: wrapProvider(detachedVirtualStores.weakSetStore),
|
|
91
|
+
});
|
|
92
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import '@agoric/swingset-vat/tools/prepare-test-env-ava.js';
|
|
2
|
+
import { reincarnate } from '@agoric/swingset-vat/tools/setup-vat-data.js';
|
|
3
|
+
|
|
4
|
+
export { test } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js';
|
|
5
|
+
|
|
6
|
+
/** @type {ReturnType<typeof reincarnate>} */
|
|
7
|
+
let incarnation;
|
|
8
|
+
|
|
9
|
+
export const annihilate = () => {
|
|
10
|
+
incarnation = reincarnate({ relaxDurabilityRules: false });
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const getBaggage = () => {
|
|
14
|
+
return incarnation.fakeVomKit.cm.provideBaggage();
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const nextLife = () => {
|
|
18
|
+
incarnation = reincarnate(incarnation);
|
|
19
|
+
};
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// eslint-disable-next-line import/order
|
|
2
|
+
import {
|
|
3
|
+
annihilate,
|
|
4
|
+
getBaggage,
|
|
5
|
+
nextLife,
|
|
6
|
+
test,
|
|
7
|
+
} from './prepare-test-env-ava.js';
|
|
8
|
+
|
|
9
|
+
import { M } from '@agoric/store';
|
|
10
|
+
import * as vatData from '@agoric/vat-data';
|
|
11
|
+
|
|
12
|
+
import { makeDurableZone } from '../durable.js';
|
|
13
|
+
import { makeHeapZone } from '../heap.js';
|
|
14
|
+
import { makeVirtualZone } from '../virtual.js';
|
|
15
|
+
import { agoricVatDataKeys as keys } from '../src/keys.js';
|
|
16
|
+
|
|
17
|
+
/** @typedef {import('../src/index.js').Zone} Zone */
|
|
18
|
+
|
|
19
|
+
// CAUTION: Do not modify this list; it exists to ensure that future versions
|
|
20
|
+
// of @agoric/zone are compatible with the baggage created by older versions,
|
|
21
|
+
// including the legacy implementation of @agoric/vat-data.
|
|
22
|
+
const agoricVatDataCompatibleKeys = [
|
|
23
|
+
'Greeter_kindHandle',
|
|
24
|
+
'GreeterKit_kindHandle',
|
|
25
|
+
'a_kindHandle',
|
|
26
|
+
'a_singleton',
|
|
27
|
+
'mappish',
|
|
28
|
+
'subsub',
|
|
29
|
+
].sort();
|
|
30
|
+
|
|
31
|
+
const bindAllMethodsTo = (obj, that = obj) =>
|
|
32
|
+
Object.fromEntries(
|
|
33
|
+
Object.entries(obj).map(([name, fn]) => [name, fn.bind(that)]),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const greetGuard = M.interface('Greeter', {
|
|
37
|
+
greet: M.call().optional(M.string()).returns(M.string()),
|
|
38
|
+
});
|
|
39
|
+
const greetFacet = {
|
|
40
|
+
greet(greeting = 'Hello') {
|
|
41
|
+
return `${greeting}, ${this.state.nick}`;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const adminGuard = M.interface('GreeterAdmin', {
|
|
46
|
+
setNick: M.call(M.string()).returns(),
|
|
47
|
+
});
|
|
48
|
+
const adminFacet = {
|
|
49
|
+
setNick(nick) {
|
|
50
|
+
this.state.nick = nick;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const combinedGuard = M.interface('GreeterWithAdmin', {
|
|
55
|
+
...greetGuard.methodGuards,
|
|
56
|
+
...adminGuard.methodGuards,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const alreadyExceptionSpec = {
|
|
60
|
+
message: /has already been used/,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const prepareGreeterSingleton = (zone, label, nick) => {
|
|
64
|
+
const myThis = Object.freeze({ state: { nick } });
|
|
65
|
+
return zone.exo(label, combinedGuard, {
|
|
66
|
+
...bindAllMethodsTo(greetFacet, myThis),
|
|
67
|
+
...bindAllMethodsTo(adminFacet, myThis),
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const prepareGreeter = zone =>
|
|
72
|
+
zone.exoClass('Greeter', combinedGuard, nick => ({ nick }), {
|
|
73
|
+
...greetFacet,
|
|
74
|
+
...adminFacet,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const prepareGreeterKit = zone =>
|
|
78
|
+
zone.exoClassKit(
|
|
79
|
+
'GreeterKit',
|
|
80
|
+
{ greeter: greetGuard, admin: adminGuard },
|
|
81
|
+
nick => ({ nick }),
|
|
82
|
+
{
|
|
83
|
+
greeter: greetFacet,
|
|
84
|
+
admin: adminFacet,
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const testGreeter = (t, nick, obj, adminObj = obj) => {
|
|
89
|
+
t.is(obj.greet('Greetings'), `Greetings, ${nick}`);
|
|
90
|
+
t.is(obj.greet(), `Hello, ${nick}`);
|
|
91
|
+
adminObj.setNick(`${nick}2`);
|
|
92
|
+
t.is(obj.greet('Greetings'), `Greetings, ${nick}2`);
|
|
93
|
+
t.is(obj.greet(), `Hello, ${nick}2`);
|
|
94
|
+
adminObj.setNick(nick);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @template T
|
|
99
|
+
* @param {import('ava').Assertions} t
|
|
100
|
+
* @param {() => T} fn
|
|
101
|
+
* @param {*} spec
|
|
102
|
+
* @returns {T}
|
|
103
|
+
*/
|
|
104
|
+
const secondThrows = (t, fn, spec = alreadyExceptionSpec) => {
|
|
105
|
+
const ret = fn();
|
|
106
|
+
t.throws(fn, spec);
|
|
107
|
+
return ret;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @param {import('ava').Assertions} t
|
|
112
|
+
* @param {MapStore} baggage
|
|
113
|
+
*/
|
|
114
|
+
const testFirstVatDataIncarnation = (t, baggage) => {
|
|
115
|
+
const subBaggage = vatData.provideDurableMapStore(baggage, 'sub');
|
|
116
|
+
|
|
117
|
+
const myThis = Object.freeze({ state: { nick: 'Singly' } });
|
|
118
|
+
const singly = vatData.prepareExo(subBaggage, 'a', combinedGuard, {
|
|
119
|
+
...bindAllMethodsTo(greetFacet, myThis),
|
|
120
|
+
...bindAllMethodsTo(adminFacet, myThis),
|
|
121
|
+
});
|
|
122
|
+
testGreeter(t, 'Singly', singly);
|
|
123
|
+
|
|
124
|
+
const makeGreeter = vatData.prepareExoClass(
|
|
125
|
+
subBaggage,
|
|
126
|
+
'Greeter',
|
|
127
|
+
combinedGuard,
|
|
128
|
+
nick => ({ nick }),
|
|
129
|
+
{
|
|
130
|
+
...greetFacet,
|
|
131
|
+
...adminFacet,
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
const classy = makeGreeter('Classy');
|
|
135
|
+
testGreeter(t, 'Classy', classy);
|
|
136
|
+
|
|
137
|
+
const makeGreeterKit = vatData.prepareExoClassKit(
|
|
138
|
+
subBaggage,
|
|
139
|
+
'GreeterKit',
|
|
140
|
+
{ greeter: greetGuard, admin: adminGuard },
|
|
141
|
+
nick => ({ nick }),
|
|
142
|
+
{
|
|
143
|
+
greeter: greetFacet,
|
|
144
|
+
admin: adminFacet,
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
const { greeter: kitty, admin: kittyAdmin } = makeGreeterKit('Kitty');
|
|
148
|
+
testGreeter(t, 'Kitty', kitty, kittyAdmin);
|
|
149
|
+
|
|
150
|
+
const mappish = vatData.provideDurableMapStore(subBaggage, 'mappish');
|
|
151
|
+
mappish.init('singly', singly);
|
|
152
|
+
mappish.init('classy', classy);
|
|
153
|
+
mappish.init('kitty', kitty);
|
|
154
|
+
mappish.init('kittyAdmin', kittyAdmin);
|
|
155
|
+
|
|
156
|
+
vatData.provideDurableMapStore(subBaggage, 'subsub');
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @param {import('ava').Assertions} t
|
|
161
|
+
* @param {Zone} rootZone
|
|
162
|
+
*/
|
|
163
|
+
const testFirstZoneIncarnation = (t, rootZone) => {
|
|
164
|
+
const subZone = secondThrows(t, () => rootZone.subZone('sub'));
|
|
165
|
+
const singly = secondThrows(t, () =>
|
|
166
|
+
prepareGreeterSingleton(subZone, 'a', 'Singly'),
|
|
167
|
+
);
|
|
168
|
+
testGreeter(t, 'Singly', singly);
|
|
169
|
+
|
|
170
|
+
const makeGreeter = secondThrows(t, () => prepareGreeter(subZone));
|
|
171
|
+
const classy = makeGreeter('Classy');
|
|
172
|
+
testGreeter(t, 'Classy', classy);
|
|
173
|
+
|
|
174
|
+
const makeGreeterKit = secondThrows(t, () => prepareGreeterKit(subZone));
|
|
175
|
+
|
|
176
|
+
const { greeter: kitty, admin: kittyAdmin } = makeGreeterKit('Kitty');
|
|
177
|
+
testGreeter(t, 'Kitty', kitty, kittyAdmin);
|
|
178
|
+
|
|
179
|
+
const mappish = secondThrows(t, () => subZone.mapStore('mappish'));
|
|
180
|
+
mappish.init('singly', singly);
|
|
181
|
+
mappish.init('classy', classy);
|
|
182
|
+
mappish.init('kitty', kitty);
|
|
183
|
+
mappish.init('kittyAdmin', kittyAdmin);
|
|
184
|
+
|
|
185
|
+
secondThrows(t, () => subZone.subZone('subsub'));
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @param {import('ava').Assertions} t
|
|
190
|
+
* @param {Zone} rootZone
|
|
191
|
+
*/
|
|
192
|
+
const testSecondZoneIncarnation = (t, rootZone) => {
|
|
193
|
+
const subZone = secondThrows(t, () => rootZone.subZone('sub'));
|
|
194
|
+
const mappish = secondThrows(t, () => subZone.mapStore('mappish'));
|
|
195
|
+
|
|
196
|
+
const singlyReload = secondThrows(t, () =>
|
|
197
|
+
prepareGreeterSingleton(subZone, 'a', 'Singly'),
|
|
198
|
+
);
|
|
199
|
+
const makeGreeter = secondThrows(t, () => prepareGreeter(subZone));
|
|
200
|
+
const makeGreeterKit = secondThrows(t, () => prepareGreeterKit(subZone));
|
|
201
|
+
|
|
202
|
+
const singly = mappish.get('singly');
|
|
203
|
+
t.is(singlyReload, singly);
|
|
204
|
+
testGreeter(t, 'Singly', singly);
|
|
205
|
+
testGreeter(t, 'Classy', mappish.get('classy'));
|
|
206
|
+
testGreeter(t, 'Kitty', mappish.get('kitty'), mappish.get('kittyAdmin'));
|
|
207
|
+
|
|
208
|
+
const classy2 = makeGreeter('Classy2');
|
|
209
|
+
testGreeter(t, 'Classy2', classy2);
|
|
210
|
+
|
|
211
|
+
const { greeter: kitty2, admin: kittyAdmin2 } = makeGreeterKit('Kitty2');
|
|
212
|
+
testGreeter(t, 'Kitty2', kitty2, kittyAdmin2);
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
test('heapZone', t => {
|
|
216
|
+
const zone = makeHeapZone();
|
|
217
|
+
testFirstZoneIncarnation(t, zone);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test.serial('virtualZone', t => {
|
|
221
|
+
annihilate();
|
|
222
|
+
const zone = makeVirtualZone();
|
|
223
|
+
testFirstZoneIncarnation(t, zone);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test.serial('durableZone', t => {
|
|
227
|
+
annihilate();
|
|
228
|
+
|
|
229
|
+
const expectedKeys = [
|
|
230
|
+
...keys.exo('a'),
|
|
231
|
+
...keys.exoClass('Greeter'),
|
|
232
|
+
...keys.exoClassKit('GreeterKit'),
|
|
233
|
+
...keys.store('mappish'),
|
|
234
|
+
...keys.zone('subsub'),
|
|
235
|
+
].sort();
|
|
236
|
+
t.deepEqual(agoricVatDataCompatibleKeys, expectedKeys);
|
|
237
|
+
|
|
238
|
+
nextLife();
|
|
239
|
+
const baggage1 = getBaggage();
|
|
240
|
+
testFirstZoneIncarnation(t, makeDurableZone(baggage1));
|
|
241
|
+
t.deepEqual(
|
|
242
|
+
[...baggage1.get('sub').keys()].sort(),
|
|
243
|
+
agoricVatDataCompatibleKeys,
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
nextLife();
|
|
247
|
+
const baggage2 = getBaggage();
|
|
248
|
+
t.deepEqual(
|
|
249
|
+
[...baggage2.get('sub').keys()].sort(),
|
|
250
|
+
agoricVatDataCompatibleKeys,
|
|
251
|
+
);
|
|
252
|
+
testSecondZoneIncarnation(t, makeDurableZone(baggage2));
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test.serial('vatData migrate to durableZone', t => {
|
|
256
|
+
annihilate();
|
|
257
|
+
|
|
258
|
+
const baggage1 = getBaggage();
|
|
259
|
+
testFirstVatDataIncarnation(t, baggage1);
|
|
260
|
+
t.deepEqual(
|
|
261
|
+
[...baggage1.get('sub').keys()].sort(),
|
|
262
|
+
agoricVatDataCompatibleKeys,
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
nextLife();
|
|
266
|
+
const baggage2 = getBaggage();
|
|
267
|
+
testSecondZoneIncarnation(t, makeDurableZone(baggage2));
|
|
268
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
annihilate,
|
|
3
|
+
getBaggage,
|
|
4
|
+
nextLife,
|
|
5
|
+
test,
|
|
6
|
+
} from './prepare-test-env-ava.js';
|
|
7
|
+
|
|
8
|
+
import { makeDurableZone } from '../durable.js';
|
|
9
|
+
import { makeHeapZone } from '../heap.js';
|
|
10
|
+
import { makeVirtualZone } from '../virtual.js';
|
|
11
|
+
|
|
12
|
+
/** @typedef {import('../src/index.js').Zone} Zone */
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {import('ava').Assertions} t
|
|
16
|
+
* @param {Zone} rootZone
|
|
17
|
+
*/
|
|
18
|
+
const testOnce = (t, rootZone) => {
|
|
19
|
+
const subZone = rootZone.subZone('sub');
|
|
20
|
+
const a = subZone.makeOnce('a', () => 'A');
|
|
21
|
+
t.is(a, 'A');
|
|
22
|
+
t.throws(() => subZone.makeOnce('a', () => 'A'), {
|
|
23
|
+
message: /has already been used/,
|
|
24
|
+
});
|
|
25
|
+
const nonPassable = harden({
|
|
26
|
+
hello() {
|
|
27
|
+
return 'world';
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
t.is(rootZone.isStorable(nonPassable), false);
|
|
31
|
+
t.is(subZone.isStorable(123), true);
|
|
32
|
+
t.throws(() => rootZone.makeOnce('nonPassable', () => nonPassable), {
|
|
33
|
+
message: /is not storable/,
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
test('heapZone', t => {
|
|
38
|
+
testOnce(t, makeHeapZone());
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test.serial('virtualZone', t => {
|
|
42
|
+
testOnce(t, makeVirtualZone());
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test.serial('durableZone', t => {
|
|
46
|
+
annihilate();
|
|
47
|
+
const rootBaggage = getBaggage();
|
|
48
|
+
const rootDurableZone = makeDurableZone(rootBaggage);
|
|
49
|
+
testOnce(t, rootDurableZone);
|
|
50
|
+
|
|
51
|
+
// Do we actually want to refuse to use the same baggage twice?
|
|
52
|
+
const secondDurableZone = makeDurableZone(rootBaggage);
|
|
53
|
+
testOnce(t, secondDurableZone);
|
|
54
|
+
const subDurableZone = makeDurableZone(rootBaggage).subZone('sub');
|
|
55
|
+
t.is(
|
|
56
|
+
subDurableZone.makeOnce('a', () => 'B'),
|
|
57
|
+
'A',
|
|
58
|
+
);
|
|
59
|
+
t.throws(() => subDurableZone.makeOnce('a', () => 'B'), {
|
|
60
|
+
message: /has already been used/,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
nextLife();
|
|
64
|
+
const thirdDurableZone = makeDurableZone(getBaggage());
|
|
65
|
+
testOnce(t, thirdDurableZone);
|
|
66
|
+
});
|