@agoric/zone 0.2.3-dev-8358d20.0 → 0.2.3-dev-5eb596e.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 CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@agoric/zone",
3
- "version": "0.2.3-dev-8358d20.0+8358d20",
3
+ "version": "0.2.3-dev-5eb596e.0+5eb596e",
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": "true || ava",
11
- "test:c8": "true || c8 $C8_OPTIONS ava --config=ava-nesm.config.js",
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-8358d20.0+8358d20",
29
- "@agoric/vat-data": "0.5.3-dev-8358d20.0+8358d20",
30
- "@endo/far": "^0.2.19"
28
+ "@agoric/store": "0.9.3-dev-5eb596e.0+5eb596e",
29
+ "@agoric/vat-data": "0.5.3-dev-5eb596e.0+5eb596e",
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-5eb596e.0+5eb596e",
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": "8358d208a105af24bc267ba4ef928ca7ecf6a4e3"
51
+ "gitHead": "5eb596e63bf4d5b416d4eff187e87af366b34928"
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 { Far } from '@endo/far';
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
- provideDurableMapStore(getBaggage(), label, options);
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: canBeDurable,
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
- export const detachedDurableStores = attachDurableStores(() =>
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 attachedStores = attachDurableStores(() => baggage);
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 = provideDurableMapStore(baggage, label, options);
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
- ...attachedStores,
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 { Far } from '@endo/far';
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 heapStores = Far('heapStores', {
20
- detached: () => heapStores,
21
- isStorable: _specimen => true,
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
- * A heap (in-memory) zone that uses the default exo and store implementations.
31
+ * Create a heap (in-memory) zone that uses the default exo and store implementations.
31
32
  *
32
- * @type {import('.').Zone}
33
+ * @param {string} [baseLabel]
34
+ * @returns {import('.').Zone}
33
35
  */
34
- export const heapZone = Far('heapZone', {
35
- exoClass: defineExoClass,
36
- exoClassKit: defineExoClassKit,
37
- exo: makeExo,
38
- subZone: (_label, _options) => heapZone,
39
- ...heapStores,
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
- export { M };
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);
@@ -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 { Far } from '@endo/far';
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 defineVirtualExo = (
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
- export const detachedVirtualStores = Far('virtualStores', {
48
+ const detachedVirtualStores = Far('virtualStores', {
47
49
  detached: () => detachedVirtualStores,
48
- isStorable: canBeDurable,
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
- * @type {import('.').Zone}
61
+ * @param {string} [baseLabel]
62
+ * @returns {import('.').Zone}
60
63
  */
61
- export const virtualZone = Far('virtualZone', {
62
- exo: defineVirtualExo,
63
- exoClass: defineVirtualExoClass,
64
- exoClassKit: defineVirtualExoClassKit,
65
- subZone: (_label, _options = {}) => virtualZone,
64
+ export const makeVirtualZone = (baseLabel = 'virtualZone') => {
65
+ const { makeOnce, wrapProvider } = makeOnceKit(
66
+ baseLabel,
67
+ detachedVirtualStores,
68
+ );
66
69
 
67
- ...detachedVirtualStores,
68
- });
70
+ /**
71
+ * @param {string} label
72
+ * @param {any} _options
73
+ */
74
+ const makeSubZone = (label, _options) =>
75
+ makeVirtualZone(`${baseLabel}.${label}`);
69
76
 
70
- export { M };
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
+ });