@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.
@@ -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,8 @@
1
+ import { test } from './prepare-test-env-ava.js';
2
+
3
+ import { makeHeapZone } from '../heap.js';
4
+ import { testMakeOnce } from '../tools/testers.js';
5
+
6
+ test('heapZone', t => {
7
+ testMakeOnce(t, makeHeapZone());
8
+ });
@@ -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
+ });
@@ -0,0 +1,4 @@
1
+ import { wrapTest } from '@endo/ses-ava';
2
+ import rawTest from 'ava';
3
+
4
+ export const test = wrapTest(rawTest);
@@ -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
+ );