@agoric/base-zone 0.1.1-calypso-dev-84eb287.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/CHANGELOG.md +4 -0
- package/LICENSE +201 -0
- package/README.md +13 -0
- package/heap.d.ts +2 -0
- package/heap.d.ts.map +1 -0
- package/heap.js +3 -0
- package/package.json +62 -0
- package/src/exports.d.ts +8 -0
- package/src/exports.js +2 -0
- package/src/heap.d.ts +2 -0
- package/src/heap.d.ts.map +1 -0
- package/src/heap.js +63 -0
- package/src/index.d.ts +6 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +12 -0
- package/src/is-passable.d.ts +2 -0
- package/src/is-passable.d.ts.map +1 -0
- package/src/is-passable.js +8 -0
- package/src/keys.d.ts +7 -0
- package/src/keys.d.ts.map +1 -0
- package/src/keys.js +21 -0
- package/src/make-once.d.ts +5 -0
- package/src/make-once.d.ts.map +1 -0
- package/src/make-once.js +86 -0
- package/src/prepare-revocable.d.ts +43 -0
- package/src/prepare-revocable.d.ts.map +1 -0
- package/src/prepare-revocable.js +175 -0
- package/src/types.d.ts +64 -0
- package/src/types.d.ts.map +1 -0
- package/src/types.js +44 -0
- package/src/watch-promise.d.ts +29 -0
- package/src/watch-promise.d.ts.map +1 -0
- package/src/watch-promise.js +82 -0
- package/test/exos.test.js +9 -0
- package/test/make-once.test.js +8 -0
- package/test/prepare-revocable.test.js +121 -0
- package/test/prepare-test-env-ava.js +4 -0
- package/tools/greeter.js +74 -0
- package/tools/testers.js +120 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +11 -0
- package/zone-helpers.d.ts +2 -0
- package/zone-helpers.d.ts.map +1 -0
- package/zone-helpers.js +1 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { M } from '@endo/patterns';
|
|
2
|
+
import { fromUniqueEntries } from '@endo/common/from-unique-entries.js';
|
|
3
|
+
|
|
4
|
+
const { Fail, quote: q } = assert;
|
|
5
|
+
|
|
6
|
+
/** @import {Amplify} from '@endo/exo'; */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @template [U=any]
|
|
10
|
+
* @typedef {object} RevocableMakerKit
|
|
11
|
+
* @property {(revocable: U) => boolean} revoke
|
|
12
|
+
* @property {(underlying: U) => U} makeRevocable
|
|
13
|
+
* Forwards to the underlying exo object, until revoked
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {object} RevokerFacet
|
|
18
|
+
* @property {() => boolean} revoke
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @template [U=any]
|
|
23
|
+
* @typedef {object} RevocableKit
|
|
24
|
+
* @property {RevokerFacet} revoker
|
|
25
|
+
* @property {U} revocable
|
|
26
|
+
* Forwards to the underlying exo object, until revoked
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @template [U=any]
|
|
31
|
+
* @typedef {object} RevocableKitThis
|
|
32
|
+
* @property {RevocableKit<U>} facets
|
|
33
|
+
* @property {{ underlying: U }} state
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @template [U=any]
|
|
38
|
+
* @typedef {object} RevocableKitOptions
|
|
39
|
+
* @property {string} [uInterfaceName]
|
|
40
|
+
* The `interfaceName` of the underlying interface guard.
|
|
41
|
+
* Defaults to the `uKindName`.
|
|
42
|
+
* @property {Record<
|
|
43
|
+
* string|symbol,
|
|
44
|
+
* import('@endo/patterns').MethodGuard
|
|
45
|
+
* >} [extraMethodGuards]
|
|
46
|
+
* For guarding the `extraMethods`, if you include them below. These appear
|
|
47
|
+
* only on the synthesized interface guard for the revocable caretaker, and
|
|
48
|
+
* do not necessarily correspond to any method of the underlying.
|
|
49
|
+
* @property {Record<
|
|
50
|
+
* string|symbol,
|
|
51
|
+
* (this: RevocableKitThis<U>, ...args: any[]) => any
|
|
52
|
+
* >} [extraMethods]
|
|
53
|
+
* Extra methods adding behavior only to the revocable caretaker, and
|
|
54
|
+
* do not necessarily correspond to any methods of the underlying.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Make an exo class kit for wrapping an underlying exo class,
|
|
59
|
+
* where the wrapper is a revocable forwarder.
|
|
60
|
+
*
|
|
61
|
+
* @template [U=any]
|
|
62
|
+
* @param {import('@agoric/base-zone').Zone} zone
|
|
63
|
+
* @param {string} uKindName
|
|
64
|
+
* The `kindName` of the underlying exo class
|
|
65
|
+
* @param {(string|symbol)[]} uMethodNames
|
|
66
|
+
* The method names of the underlying exo class that should be represented
|
|
67
|
+
* by transparently-forwarding methods of the revocable caretaker.
|
|
68
|
+
* @param {RevocableKitOptions} [options]
|
|
69
|
+
* @returns {RevocableMakerKit<U>}
|
|
70
|
+
*/
|
|
71
|
+
export const prepareRevocableMakerKit = (
|
|
72
|
+
zone,
|
|
73
|
+
uKindName,
|
|
74
|
+
uMethodNames,
|
|
75
|
+
options = {},
|
|
76
|
+
) => {
|
|
77
|
+
const {
|
|
78
|
+
uInterfaceName = uKindName,
|
|
79
|
+
extraMethodGuards = undefined,
|
|
80
|
+
extraMethods = undefined,
|
|
81
|
+
} = options;
|
|
82
|
+
const RevocableIKit = harden({
|
|
83
|
+
revoker: M.interface(`${uInterfaceName}_revoker`, {
|
|
84
|
+
revoke: M.call().returns(M.boolean()),
|
|
85
|
+
}),
|
|
86
|
+
revocable: M.interface(
|
|
87
|
+
`${uInterfaceName}_revocable`,
|
|
88
|
+
{
|
|
89
|
+
...extraMethodGuards,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
defaultGuards: 'raw',
|
|
93
|
+
},
|
|
94
|
+
),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const revocableKindName = `${uKindName}_caretaker`;
|
|
98
|
+
|
|
99
|
+
/** @type {Amplify<any>} */
|
|
100
|
+
let amplifier;
|
|
101
|
+
|
|
102
|
+
const makeRevocableKit = zone.exoClassKit(
|
|
103
|
+
revocableKindName,
|
|
104
|
+
RevocableIKit,
|
|
105
|
+
underlying => ({
|
|
106
|
+
underlying,
|
|
107
|
+
}),
|
|
108
|
+
{
|
|
109
|
+
revoker: {
|
|
110
|
+
revoke() {
|
|
111
|
+
const { state } = this;
|
|
112
|
+
if (state.underlying === undefined) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
state.underlying = undefined;
|
|
116
|
+
return true;
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
revocable: {
|
|
120
|
+
...fromUniqueEntries(
|
|
121
|
+
uMethodNames.map(name => [
|
|
122
|
+
name,
|
|
123
|
+
{
|
|
124
|
+
// Use concise method syntax for exo methods
|
|
125
|
+
[name](...args) {
|
|
126
|
+
// @ts-expect-error normal exo-this typing confusion
|
|
127
|
+
const { underlying } = this.state;
|
|
128
|
+
underlying !== undefined ||
|
|
129
|
+
Fail`${q(revocableKindName)} revoked`;
|
|
130
|
+
return underlying[name](...args);
|
|
131
|
+
},
|
|
132
|
+
// @ts-expect-error using possible symbol as index type
|
|
133
|
+
}[name],
|
|
134
|
+
]),
|
|
135
|
+
),
|
|
136
|
+
...extraMethods,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
stateShape: {
|
|
141
|
+
underlying: M.opt(M.remotable('underlying')),
|
|
142
|
+
},
|
|
143
|
+
receiveAmplifier: amp => {
|
|
144
|
+
amplifier = amp;
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @param {U} underlying
|
|
151
|
+
* @returns {U}
|
|
152
|
+
*/
|
|
153
|
+
const makeRevocable = underlying =>
|
|
154
|
+
// @ts-expect-error some confusion about UU vs Guarded<U> I think
|
|
155
|
+
makeRevocableKit(underlying).revocable;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @param {U} revocable
|
|
159
|
+
* @returns {boolean}
|
|
160
|
+
*/
|
|
161
|
+
const revoke = revocable => {
|
|
162
|
+
/** @type {RevocableKit<U>} */
|
|
163
|
+
const facets = amplifier(revocable);
|
|
164
|
+
if (facets === undefined) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
return facets.revoker.revoke();
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
return harden({
|
|
171
|
+
revoke,
|
|
172
|
+
makeRevocable,
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
harden(prepareRevocableMakerKit);
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export type KeyCategories = "exoClass" | "exoClassKit" | "exo" | "store" | "zone";
|
|
2
|
+
export type KeyMakers = Record<KeyCategories, (label: string) => string[]>;
|
|
3
|
+
/**
|
|
4
|
+
* A bag of methods for creating defensible objects and
|
|
5
|
+
* collections with the same allocation semantics (ephemeral, persistent, etc)
|
|
6
|
+
*/
|
|
7
|
+
export type Zone = ExoZone & Stores;
|
|
8
|
+
export type ExoZone = {
|
|
9
|
+
/**
|
|
10
|
+
* create a singleton exo-object instance bound to this zone
|
|
11
|
+
*/
|
|
12
|
+
exo: typeof makeExo;
|
|
13
|
+
/**
|
|
14
|
+
* create a maker function that can be used to create exo-objects bound to this zone
|
|
15
|
+
*/
|
|
16
|
+
exoClass: typeof defineExoClass;
|
|
17
|
+
/**
|
|
18
|
+
* create a "kit" maker function that can be used to create a record of exo-objects sharing the same state
|
|
19
|
+
*/
|
|
20
|
+
exoClassKit: typeof defineExoClassKit;
|
|
21
|
+
/**
|
|
22
|
+
* create or retrieve a singleton object bound to this zone
|
|
23
|
+
*/
|
|
24
|
+
makeOnce: <T>(key: string, maker: (key: string) => T) => T;
|
|
25
|
+
/**
|
|
26
|
+
* create a new Zone that can be passed to untrusted consumers without exposing the storage of the parent zone
|
|
27
|
+
*/
|
|
28
|
+
subZone: (label: string, options?: StoreOptions) => Zone;
|
|
29
|
+
/**
|
|
30
|
+
* register a promise watcher created by this zone
|
|
31
|
+
*/
|
|
32
|
+
watchPromise: typeof watchPromise;
|
|
33
|
+
};
|
|
34
|
+
export type Stores = {
|
|
35
|
+
/**
|
|
36
|
+
* obtain store providers which are detached (the stores are anonymous rather than bound to `label` in the zone)
|
|
37
|
+
*/
|
|
38
|
+
detached: () => Stores;
|
|
39
|
+
/**
|
|
40
|
+
* return true if the specimen can be stored in the zone, whether as exo-object state or in a store
|
|
41
|
+
*/
|
|
42
|
+
isStorable: (specimen: unknown) => boolean;
|
|
43
|
+
/**
|
|
44
|
+
* provide a Map-like store named `label` in the zone
|
|
45
|
+
*/
|
|
46
|
+
mapStore: (label: string, options?: StoreOptions) => MapStore<any, any>;
|
|
47
|
+
/**
|
|
48
|
+
* provide a Set-like store named `label` in the zone
|
|
49
|
+
*/
|
|
50
|
+
setStore: <K>(label: string, options?: StoreOptions) => SetStore<K>;
|
|
51
|
+
/**
|
|
52
|
+
* provide a WeakMap-like store named `label` in the zone
|
|
53
|
+
*/
|
|
54
|
+
weakMapStore: <K, V>(label: string, options?: StoreOptions) => WeakMapStore<K, V>;
|
|
55
|
+
/**
|
|
56
|
+
* provide a WeakSet-like store named `label` in the zone
|
|
57
|
+
*/
|
|
58
|
+
weakSetStore: <K>(label: string, options?: StoreOptions) => WeakSetStore<K>;
|
|
59
|
+
};
|
|
60
|
+
import { makeExo } from '@endo/exo';
|
|
61
|
+
import { defineExoClass } from '@endo/exo';
|
|
62
|
+
import { defineExoClassKit } from '@endo/exo';
|
|
63
|
+
import { watchPromise } from './watch-promise.js';
|
|
64
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.js"],"names":[],"mappings":"4BAac,UAAU,GAAG,aAAa,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM;wBACrD,MAAM,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;;;;;mBAGnD,OAAO,GAAG,MAAM;;;;;SAMf,OAAO,OAAO;;;;cACd,OAAO,cAAc;;;;iBACrB,OAAO,iBAAiB;;;;cACxB,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC,KAAK,CAAC;;;;aAChD,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,KAAK,IAAI;;;;kBAC/C,OAAO,YAAY;;;;;;cAKnB,MAAM,MAAM;;;;gBACZ,CAAC,QAAQ,EAAE,OAAO,KAAK,OAAO;;;;cAC9B,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,KAAK,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;;;;cAC7D,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC;;;;kBACzD,CAAC,CAAC,EAAE,CAAC,EAClB,KAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,KAAK,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC;;;;kBAEnD,CAAC,CAAC,EACf,KAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,KAAK,YAAY,CAAC,CAAC,CAAC;;wBAxCH,WAAW;+BAAX,WAAW;kCAAX,WAAW;6BAEzC,oBAAoB"}
|
package/src/types.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// eslint-disable-next-line no-unused-vars
|
|
2
|
+
import { makeExo, defineExoClass, defineExoClassKit } from '@endo/exo';
|
|
3
|
+
// eslint-disable-next-line no-unused-vars
|
|
4
|
+
import { watchPromise } from './watch-promise.js';
|
|
5
|
+
|
|
6
|
+
// Ensure this is a module.
|
|
7
|
+
export {};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @import {Key} from '@endo/patterns';
|
|
11
|
+
* @import {Passable} from '@endo/pass-style';
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** @typedef {'exoClass' | 'exoClassKit' | 'exo' | 'store' | 'zone'} KeyCategories */
|
|
15
|
+
/** @typedef {Record<KeyCategories, (label: string) => string[]>} KeyMakers */
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {ExoZone & Stores} Zone A bag of methods for creating defensible objects and
|
|
19
|
+
* collections with the same allocation semantics (ephemeral, persistent, etc)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {object} ExoZone
|
|
24
|
+
* @property {typeof makeExo} exo create a singleton exo-object instance bound to this zone
|
|
25
|
+
* @property {typeof defineExoClass} exoClass create a maker function that can be used to create exo-objects bound to this zone
|
|
26
|
+
* @property {typeof defineExoClassKit} exoClassKit create a "kit" maker function that can be used to create a record of exo-objects sharing the same state
|
|
27
|
+
* @property {<T>(key: string, maker: (key: string) => T) => T} makeOnce create or retrieve a singleton object bound to this zone
|
|
28
|
+
* @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
|
|
29
|
+
* @property {typeof watchPromise} watchPromise register a promise watcher created by this zone
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @typedef {object} Stores
|
|
34
|
+
* @property {() => Stores} detached obtain store providers which are detached (the stores are anonymous rather than bound to `label` in the zone)
|
|
35
|
+
* @property {(specimen: unknown) => boolean} isStorable return true if the specimen can be stored in the zone, whether as exo-object state or in a store
|
|
36
|
+
* @property {(label: string, options?: StoreOptions) => MapStore<any, any>} mapStore provide a Map-like store named `label` in the zone
|
|
37
|
+
* @property {<K>(label: string, options?: StoreOptions) => SetStore<K>} setStore provide a Set-like store named `label` in the zone
|
|
38
|
+
* @property {<K, V>(
|
|
39
|
+
* label: string, options?: StoreOptions) => WeakMapStore<K, V>
|
|
40
|
+
* } weakMapStore provide a WeakMap-like store named `label` in the zone
|
|
41
|
+
* @property {<K>(
|
|
42
|
+
* label: string, options?: StoreOptions) => WeakSetStore<K>
|
|
43
|
+
* } weakSetStore provide a WeakSet-like store named `label` in the zone
|
|
44
|
+
*/
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A PromiseWatcher method guard callable with one or more arguments, returning void.
|
|
3
|
+
*/
|
|
4
|
+
export const PromiseWatcherHandler: import("@endo/patterns").MethodGuard;
|
|
5
|
+
/**
|
|
6
|
+
* A PromiseWatcher interface that has both onFulfilled and onRejected handlers.
|
|
7
|
+
*/
|
|
8
|
+
export const PromiseWatcherI: import("@endo/patterns").InterfaceGuard<{
|
|
9
|
+
onFulfilled: import("@endo/patterns").MethodGuard;
|
|
10
|
+
onRejected: import("@endo/patterns").MethodGuard;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* A PromiseWatcher interface that has only an onFulfilled handler.
|
|
14
|
+
*/
|
|
15
|
+
export const PromiseWatcherFulfilledI: import("@endo/patterns").InterfaceGuard<{
|
|
16
|
+
onFulfilled: import("@endo/patterns").MethodGuard;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* A PromiseWatcher interface that has only an onRejected handler.
|
|
20
|
+
*/
|
|
21
|
+
export const PromiseWatcherRejectedI: import("@endo/patterns").InterfaceGuard<{
|
|
22
|
+
onRejected: import("@endo/patterns").MethodGuard;
|
|
23
|
+
}>;
|
|
24
|
+
export function watchPromise(p: Promise<any>, watcher: PromiseWatcher, ...watcherArgs: unknown[]): void;
|
|
25
|
+
export type PromiseWatcher = {
|
|
26
|
+
onFulfilled?: ((fulfilment: unknown, ...args: unknown[]) => void) | undefined;
|
|
27
|
+
onRejected?: ((reason: unknown, ...args: unknown[]) => void) | undefined;
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=watch-promise.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch-promise.d.ts","sourceRoot":"","sources":["watch-promise.js"],"names":[],"mappings":"AAQA;;GAEG;AACH,yEAA6E;AAE7E;;GAEG;AACH;;;GAGG;AAEH;;GAEG;AACH;;GAEG;AAEH;;GAEG;AACH;;GAEG;AAuCI,gCALI,OAAO,CAAC,GAAG,CAAC,WACZ,cAAc,kBACX,OAAO,EAAA,GACR,IAAI,CAUhB;;gCA3C0B,OAAO,WAAW,OAAO,EAAE,KAAK,IAAI;2BACxC,OAAO,WAAW,OAAO,EAAE,KAAK,IAAI"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { M } from '@endo/patterns';
|
|
3
|
+
import { E } from '@endo/far';
|
|
4
|
+
|
|
5
|
+
const { Fail } = assert;
|
|
6
|
+
|
|
7
|
+
const { apply } = Reflect;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A PromiseWatcher method guard callable with one or more arguments, returning void.
|
|
11
|
+
*/
|
|
12
|
+
export const PromiseWatcherHandler = M.call(M.raw()).rest(M.raw()).returns();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A PromiseWatcher interface that has both onFulfilled and onRejected handlers.
|
|
16
|
+
*/
|
|
17
|
+
export const PromiseWatcherI = M.interface('PromiseWatcher', {
|
|
18
|
+
onFulfilled: PromiseWatcherHandler,
|
|
19
|
+
onRejected: PromiseWatcherHandler,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A PromiseWatcher interface that has only an onFulfilled handler.
|
|
24
|
+
*/
|
|
25
|
+
export const PromiseWatcherFulfilledI = M.interface('PromiseWatcherFulfilled', {
|
|
26
|
+
onFulfilled: PromiseWatcherHandler,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A PromiseWatcher interface that has only an onRejected handler.
|
|
31
|
+
*/
|
|
32
|
+
export const PromiseWatcherRejectedI = M.interface('PromiseWatcherRejected', {
|
|
33
|
+
onRejected: PromiseWatcherHandler,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {object} PromiseWatcher
|
|
38
|
+
* @property {(fulfilment: unknown, ...args: unknown[]) => void} [onFulfilled]
|
|
39
|
+
* @property {(reason: unknown, ...args: unknown[]) => void} [onRejected]
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Adapt a promise watcher method to E.when.
|
|
44
|
+
* @param {Record<PropertyKey, (...args: unknown[]) => unknown>} that
|
|
45
|
+
* @param {PropertyKey} prop
|
|
46
|
+
* @param {unknown[]} postArgs
|
|
47
|
+
*/
|
|
48
|
+
const callMeMaybe = (that, prop, postArgs) => {
|
|
49
|
+
const fn = that[prop];
|
|
50
|
+
if (!fn) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
assert.typeof(fn, 'function');
|
|
54
|
+
/**
|
|
55
|
+
* @param {unknown} arg value or reason
|
|
56
|
+
*/
|
|
57
|
+
const wrapped = arg => {
|
|
58
|
+
// Don't return a value, to prevent E.when from subscribing to a resulting
|
|
59
|
+
// promise.
|
|
60
|
+
apply(fn, that, [arg, ...postArgs]);
|
|
61
|
+
};
|
|
62
|
+
return wrapped;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Shim the promise watcher behaviour when VatData.watchPromise is not available.
|
|
67
|
+
*
|
|
68
|
+
* @param {Promise<any>} p
|
|
69
|
+
* @param {PromiseWatcher} watcher
|
|
70
|
+
* @param {...unknown} watcherArgs
|
|
71
|
+
* @returns {void}
|
|
72
|
+
*/
|
|
73
|
+
export const watchPromise = (p, watcher, ...watcherArgs) => {
|
|
74
|
+
Promise.resolve(p) === p || Fail`watchPromise only watches promises`;
|
|
75
|
+
const onFulfilled = callMeMaybe(watcher, 'onFulfilled', watcherArgs);
|
|
76
|
+
const onRejected = callMeMaybe(watcher, 'onRejected', watcherArgs);
|
|
77
|
+
onFulfilled ||
|
|
78
|
+
onRejected ||
|
|
79
|
+
Fail`promise watcher must implement at least one handler method`;
|
|
80
|
+
void E.when(p, onFulfilled, onRejected);
|
|
81
|
+
};
|
|
82
|
+
harden(watchPromise);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { test } from './prepare-test-env-ava.js';
|
|
2
|
+
|
|
3
|
+
import { makeHeapZone } from '../heap.js';
|
|
4
|
+
import { testFirstZoneIncarnation } from '../tools/testers.js';
|
|
5
|
+
|
|
6
|
+
test('heapZone', t => {
|
|
7
|
+
const zone = makeHeapZone();
|
|
8
|
+
testFirstZoneIncarnation(t, zone);
|
|
9
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Modeled on test-heap-classes.js
|
|
2
|
+
|
|
3
|
+
import { test } from './prepare-test-env-ava.js';
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line import/order
|
|
6
|
+
import { M } from '@endo/patterns';
|
|
7
|
+
import { makeHeapZone } from '../src/heap.js';
|
|
8
|
+
import { prepareRevocableMakerKit } from '../src/prepare-revocable.js';
|
|
9
|
+
|
|
10
|
+
const UpCounterI = M.interface('UpCounter', {
|
|
11
|
+
incr: M.call()
|
|
12
|
+
// TODO M.number() should not be needed to get a better error message
|
|
13
|
+
.optional(M.and(M.number(), M.gte(0)))
|
|
14
|
+
.returns(M.number()),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const DownCounterI = M.interface('DownCounter', {
|
|
18
|
+
decr: M.call()
|
|
19
|
+
// TODO M.number() should not be needed to get a better error message
|
|
20
|
+
.optional(M.and(M.number(), M.gte(0)))
|
|
21
|
+
.returns(M.number()),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('test revoke defineVirtualExoClass', t => {
|
|
25
|
+
const zone = makeHeapZone('rootZone');
|
|
26
|
+
|
|
27
|
+
const makeUnderlyingUpCounter = zone.exoClass(
|
|
28
|
+
'UpCounter',
|
|
29
|
+
UpCounterI,
|
|
30
|
+
/** @param {number} [x] */
|
|
31
|
+
(x = 0) => ({ x }),
|
|
32
|
+
{
|
|
33
|
+
incr(y = 1) {
|
|
34
|
+
const { state } = this;
|
|
35
|
+
state.x += y;
|
|
36
|
+
return state.x;
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const { revoke, makeRevocable } = prepareRevocableMakerKit(
|
|
42
|
+
zone,
|
|
43
|
+
'UpCounter',
|
|
44
|
+
['incr'],
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const makeUpCounter = x => makeRevocable(makeUnderlyingUpCounter(x));
|
|
48
|
+
|
|
49
|
+
const upCounter = makeUpCounter(3);
|
|
50
|
+
t.is(upCounter.incr(5), 8);
|
|
51
|
+
t.is(revoke(upCounter), true);
|
|
52
|
+
t.throws(() => upCounter.incr(1), {
|
|
53
|
+
message: '"UpCounter_caretaker" revoked',
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('test revoke defineVirtualExoClassKit', t => {
|
|
58
|
+
const zone = makeHeapZone('rootZone');
|
|
59
|
+
|
|
60
|
+
const makeUnderlyingCounterKit = zone.exoClassKit(
|
|
61
|
+
'Counter',
|
|
62
|
+
{ up: UpCounterI, down: DownCounterI },
|
|
63
|
+
/** @param {number} [x] */
|
|
64
|
+
(x = 0) => ({ x }),
|
|
65
|
+
{
|
|
66
|
+
up: {
|
|
67
|
+
incr(y = 1) {
|
|
68
|
+
const { state } = this;
|
|
69
|
+
state.x += y;
|
|
70
|
+
return state.x;
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
down: {
|
|
74
|
+
decr(y = 1) {
|
|
75
|
+
const { state } = this;
|
|
76
|
+
state.x -= y;
|
|
77
|
+
return state.x;
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const { revoke, makeRevocable } = prepareRevocableMakerKit(
|
|
84
|
+
zone,
|
|
85
|
+
'UpCounter',
|
|
86
|
+
['incr'],
|
|
87
|
+
{
|
|
88
|
+
extraMethodGuards: {
|
|
89
|
+
selfRevoke: M.call().returns(M.boolean()),
|
|
90
|
+
},
|
|
91
|
+
extraMethods: {
|
|
92
|
+
selfRevoke() {
|
|
93
|
+
// Could directly use the revoker facet instead, but this
|
|
94
|
+
// should now be considered more of an internal detail that
|
|
95
|
+
// we should deemphasize. This tests the code pattern we wish
|
|
96
|
+
// to encourage.
|
|
97
|
+
const { revocable } = this.facets;
|
|
98
|
+
return revoke(revocable);
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const makeCounterKit = x => {
|
|
105
|
+
const { up: upCounter, down: downCounter } = makeUnderlyingCounterKit(x);
|
|
106
|
+
const revocableUpCounter = makeRevocable(upCounter);
|
|
107
|
+
return harden({
|
|
108
|
+
up: revocableUpCounter,
|
|
109
|
+
down: downCounter,
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const { up: upCounter, down: downCounter } = makeCounterKit(3);
|
|
114
|
+
t.is(upCounter.incr(5), 8);
|
|
115
|
+
t.is(downCounter.decr(), 7);
|
|
116
|
+
t.is(upCounter.selfRevoke(), true);
|
|
117
|
+
t.throws(() => upCounter.incr(3), {
|
|
118
|
+
message: '"UpCounter_caretaker" revoked',
|
|
119
|
+
});
|
|
120
|
+
t.is(downCounter.decr(), 6);
|
|
121
|
+
});
|
package/tools/greeter.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { M, getInterfaceGuardPayload } from '@endo/patterns';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @template {{}} T
|
|
5
|
+
* @param {T} obj
|
|
6
|
+
* @param {unknown} that
|
|
7
|
+
* @returns {T}
|
|
8
|
+
*/
|
|
9
|
+
export const bindAllMethodsTo = (obj, that = obj) =>
|
|
10
|
+
/** @type {T} */
|
|
11
|
+
(
|
|
12
|
+
Object.fromEntries(
|
|
13
|
+
Object.entries(obj).map(([name, fn]) => [name, fn.bind(that)]),
|
|
14
|
+
)
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
export const GreeterI = M.interface('Greeter', {
|
|
18
|
+
greet: M.call().optional(M.string()).returns(M.string()),
|
|
19
|
+
});
|
|
20
|
+
export const greetFacet = {
|
|
21
|
+
greet(greeting = 'Hello') {
|
|
22
|
+
return `${greeting}, ${this.state.nick}`;
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const GreeterAdminI = M.interface('GreeterAdmin', {
|
|
27
|
+
setNick: M.call(M.string()).returns(),
|
|
28
|
+
});
|
|
29
|
+
export const adminFacet = {
|
|
30
|
+
setNick(nick) {
|
|
31
|
+
this.state.nick = nick;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const GreeterWithAdminI = M.interface('GreeterWithAdmin', {
|
|
36
|
+
...getInterfaceGuardPayload(GreeterI).methodGuards,
|
|
37
|
+
...getInterfaceGuardPayload(GreeterAdminI).methodGuards,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {import('../src/types.js').Zone} zone
|
|
42
|
+
* @param {string} label
|
|
43
|
+
* @param {string} nick
|
|
44
|
+
*/
|
|
45
|
+
export const prepareGreeterSingleton = (zone, label, nick) => {
|
|
46
|
+
const myThis = Object.freeze({ state: { nick } });
|
|
47
|
+
return zone.exo(label, GreeterWithAdminI, {
|
|
48
|
+
...bindAllMethodsTo(greetFacet, myThis),
|
|
49
|
+
...bindAllMethodsTo(adminFacet, myThis),
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {import('../src/types.js').Zone} zone
|
|
55
|
+
*/
|
|
56
|
+
export const prepareGreeter = zone =>
|
|
57
|
+
zone.exoClass('Greeter', GreeterWithAdminI, nick => ({ nick }), {
|
|
58
|
+
...greetFacet,
|
|
59
|
+
...adminFacet,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {import('../src/types.js').Zone} zone
|
|
64
|
+
*/
|
|
65
|
+
export const prepareGreeterKit = zone =>
|
|
66
|
+
zone.exoClassKit(
|
|
67
|
+
'GreeterKit',
|
|
68
|
+
{ greeter: GreeterI, admin: GreeterAdminI },
|
|
69
|
+
nick => ({ nick }),
|
|
70
|
+
{
|
|
71
|
+
greeter: greetFacet,
|
|
72
|
+
admin: adminFacet,
|
|
73
|
+
},
|
|
74
|
+
);
|