@agoric/async-flow 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.
Files changed (58) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/LICENSE +201 -0
  3. package/README.md +40 -0
  4. package/docs/async-flow-states.key +0 -0
  5. package/docs/async-flow-states.md +15 -0
  6. package/docs/async-flow-states.png +0 -0
  7. package/index.d.ts +3 -0
  8. package/index.d.ts.map +1 -0
  9. package/index.js +2 -0
  10. package/package.json +66 -0
  11. package/src/async-flow.d.ts +94 -0
  12. package/src/async-flow.d.ts.map +1 -0
  13. package/src/async-flow.js +520 -0
  14. package/src/bijection.d.ts +31 -0
  15. package/src/bijection.d.ts.map +1 -0
  16. package/src/bijection.js +207 -0
  17. package/src/convert.d.ts +6 -0
  18. package/src/convert.d.ts.map +1 -0
  19. package/src/convert.js +133 -0
  20. package/src/endowments.d.ts +16 -0
  21. package/src/endowments.d.ts.map +1 -0
  22. package/src/endowments.js +292 -0
  23. package/src/ephemera.d.ts +3 -0
  24. package/src/ephemera.d.ts.map +1 -0
  25. package/src/ephemera.js +39 -0
  26. package/src/equate.d.ts +2 -0
  27. package/src/equate.d.ts.map +1 -0
  28. package/src/equate.js +123 -0
  29. package/src/log-store.d.ts +27 -0
  30. package/src/log-store.d.ts.map +1 -0
  31. package/src/log-store.js +169 -0
  32. package/src/replay-membrane.d.ts +40 -0
  33. package/src/replay-membrane.d.ts.map +1 -0
  34. package/src/replay-membrane.js +752 -0
  35. package/src/type-guards.d.ts +4 -0
  36. package/src/type-guards.d.ts.map +1 -0
  37. package/src/type-guards.js +68 -0
  38. package/src/types.d.ts +67 -0
  39. package/src/types.d.ts.map +1 -0
  40. package/src/types.js +196 -0
  41. package/test/async-flow-crank.test.js +102 -0
  42. package/test/async-flow-early-completion.test.js +203 -0
  43. package/test/async-flow-no-this.js +65 -0
  44. package/test/async-flow.test.js +383 -0
  45. package/test/bad-host.test.js +210 -0
  46. package/test/bijection.test.js +124 -0
  47. package/test/convert.test.js +132 -0
  48. package/test/endowments.test.js +157 -0
  49. package/test/equate.test.js +120 -0
  50. package/test/log-store.test.js +120 -0
  51. package/test/prepare-test-env-ava.js +28 -0
  52. package/test/replay-membrane-eventual.test.js +217 -0
  53. package/test/replay-membrane-settlement.test.js +173 -0
  54. package/test/replay-membrane-zombie.test.js +187 -0
  55. package/test/replay-membrane.test.js +297 -0
  56. package/tsconfig.build.json +11 -0
  57. package/tsconfig.json +13 -0
  58. package/typedoc.json +8 -0
@@ -0,0 +1,207 @@
1
+ import { b, Fail } from '@endo/errors';
2
+ import { M } from '@endo/patterns';
3
+ import { Far, isPassable } from '@endo/pass-style';
4
+ import { toPassableCap } from '@agoric/vow';
5
+ import { makeEphemera } from './ephemera.js';
6
+
7
+ /**
8
+ * @import {PassableCap} from '@endo/pass-style'
9
+ * @import {Zone} from '@agoric/base-zone'
10
+ * @import {Vow} from '@agoric/vow'
11
+ * @import {Ephemera} from './types.js';
12
+ */
13
+
14
+ const BijectionI = M.interface('Bijection', {
15
+ reset: M.call().returns(),
16
+ unwrapInit: M.call(M.raw(), M.any()).returns(M.raw()),
17
+ hasGuest: M.call(M.raw()).returns(M.boolean()),
18
+ hasHost: M.call(M.any()).returns(M.boolean()),
19
+ has: M.call(M.raw(), M.any()).returns(M.boolean()),
20
+ guestToHost: M.call(M.raw()).returns(M.any()),
21
+ hostToGuest: M.call(M.any()).returns(M.raw()),
22
+ });
23
+
24
+ /**
25
+ * @param {unknown} k
26
+ */
27
+ const toKey = k =>
28
+ // @ts-expect-error k specificity
29
+ isPassable(k) ? toPassableCap(k) : k;
30
+
31
+ /**
32
+ * Makes a store like a WeakMapStore except that Promises and Vows can also be
33
+ * used as keys.
34
+ * NOTE: This depends on promise identity being stable!
35
+ *
36
+ * @param {string} name
37
+ */
38
+ const makeVowishStore = name => {
39
+ // This internal map could be (and was) a WeakMap. But there are various ways
40
+ // in which a WeakMap is more expensive than a Map. The main advantage is
41
+ // that a WeakMap can drop entries whose keys are not otherwise retained.
42
+ // But async-flow only uses a bijection together with a log-store that happens
43
+ // to durably retain all the host-side keys of the associated bijection, so
44
+ // this additional feature of the bijection is irrelevant. When the bijection
45
+ // is reset or revived in a new incarnation, these vowishStores will be gone
46
+ // anyway, dropping all the guest-side objects.
47
+ const map = new Map();
48
+
49
+ return Far(name, {
50
+ init: (k, v) => {
51
+ const k2 = toKey(k);
52
+ !map.has(k2) ||
53
+ // separate line so I can set a breakpoint
54
+ Fail`${b(name)} key already bound: ${k} -> ${map.get(k2)} vs ${v}`;
55
+ map.set(k2, v);
56
+ },
57
+ has: k => map.has(toKey(k)),
58
+ get: k => {
59
+ const k2 = toKey(k);
60
+ map.has(k2) ||
61
+ // separate line so I can set a breakpoint
62
+ Fail`${b(name)} key not found: ${k}`;
63
+ return map.get(k2);
64
+ },
65
+ });
66
+ };
67
+
68
+ /** @typedef {ReturnType<makeVowishStore>} VowishStore */
69
+
70
+ /**
71
+ * As suggested by the name, this *mostly* represents a mathematical bijection,
72
+ * i.e., a one-to-one mapping. But rather than a general bijection map store
73
+ * (which would be interesting), this one is specialized to support the
74
+ * async-flow replay-membrane, where the two sides are "guest" and "host".
75
+ *
76
+ * If `unwrap` is omitted, it defaults to an identity function on its
77
+ * `guestWrapper` argument, in which case this does represent exactly a
78
+ * mathematical bijection between host and guest.
79
+ *
80
+ * If `unwrap` is provided, it supports the unwrapping of guest wrappers, into
81
+ * so-call unwrapped guests, like state records or functions,
82
+ * that are not themselves `Passable`. This was motivated to support endowments,
83
+ * which are often similar non-passables on the host-side.
84
+ * However, it can support the unwrapping of any guest remotable wrapper.
85
+ * When `unwrap` returns something `!==` its `guestWrapper` argument,
86
+ * then we preserve the bijection (one-to-one mapping) between the host
87
+ * and the unwrapped guest. To support the internal bookkeeping of the
88
+ * replay-membrane, we also map the guestWrapper to that same host, but
89
+ * not vice versa. Since the guest wrapper should not be visible outside
90
+ * the replay-membrane, this extra bookkeeping should be invisible.
91
+ *
92
+ * This bijection only grows monotonically until reset, which clears the entire
93
+ * mapping. Until reset, each pair, once entered, cannot be altered or deleted.
94
+ * The mapping itself is completely ephemeral, but the bijection object itself
95
+ * is durable. The mapping is also effectively reset by reincarnation, i.e,
96
+ * on upgrade.
97
+ * See also https://github.com/Agoric/agoric-sdk/issues/9365
98
+ *
99
+ * To eventually address https://github.com/Agoric/agoric-sdk/issues/9301
100
+ * the bijection itself persists to support passing guest-created remotables
101
+ * and promises through the membrane.
102
+ * The resulting host wrappers must not only survive upgrade, then must
103
+ * reestablish their mapping to the correct corresponding guest objects that
104
+ * they are taken to wrap. We plan to do this via `equate` repopulating
105
+ * the bijection by the time the host wrapper needs to know what
106
+ * corresponding guest it is now taken to wrap.
107
+ *
108
+ * @param {Zone} zone
109
+ * @param {(hostWrapper: PassableCap | Vow, guestWrapper: PassableCap) => unknown} [unwrap]
110
+ * defaults to identity function on `guestWrapper` arg
111
+ */
112
+ export const prepareBijection = (
113
+ zone,
114
+ unwrap = (_hostWrapper, guestWrapper) => guestWrapper,
115
+ ) => {
116
+ /** @type {Ephemera<Bijection, VowishStore>} */
117
+ const g2h = makeEphemera(() => makeVowishStore('guestToHost'));
118
+ /** @type {Ephemera<Bijection, VowishStore>} */
119
+ const h2g = makeEphemera(() => makeVowishStore('hostToGuest'));
120
+
121
+ // Guest arguments and results are now unguarded, i.e., guarded by `M.raw()`,
122
+ // so that they can be non-passables. Therefore, we need to harden these
123
+ // here.
124
+ return zone.exoClass('Bijection', BijectionI, () => ({}), {
125
+ reset() {
126
+ const { self } = this;
127
+
128
+ g2h.resetFor(self);
129
+ h2g.resetFor(self);
130
+ },
131
+ unwrapInit(g, h) {
132
+ harden(g);
133
+ const { self } = this;
134
+ const guestToHost = g2h.for(self);
135
+ const hostToGuest = h2g.for(self);
136
+
137
+ const gUnwrapped = unwrap(h, g);
138
+ !hostToGuest.has(h) ||
139
+ Fail`hostToGuest key already bound: ${h} -> ${hostToGuest.get(h)} vs ${gUnwrapped}`;
140
+ guestToHost.init(gUnwrapped, h);
141
+ hostToGuest.init(h, gUnwrapped);
142
+ self.has(gUnwrapped, h) ||
143
+ // separate line so I can set a breakpoint
144
+ Fail`internal: ${g} <-> ${h}`;
145
+ if (g !== gUnwrapped) {
146
+ // When they are different, also map g to h without mapping h to g
147
+ !guestToHost.has(g) ||
148
+ // separate line so I can set a breakpoint
149
+ Fail`hidden guest wrapper already bound ${g}`;
150
+ guestToHost.init(g, h);
151
+ }
152
+ return gUnwrapped;
153
+ },
154
+ hasGuest(g) {
155
+ harden(g);
156
+ const { self } = this;
157
+ const guestToHost = g2h.for(self);
158
+
159
+ return guestToHost.has(g);
160
+ },
161
+ hasHost(h) {
162
+ const { self } = this;
163
+ const hostToGuest = h2g.for(self);
164
+
165
+ return hostToGuest.has(h);
166
+ },
167
+ has(g, h) {
168
+ harden(g);
169
+ const { self } = this;
170
+ const guestToHost = g2h.for(self);
171
+ const hostToGuest = h2g.for(self);
172
+
173
+ if (guestToHost.has(g)) {
174
+ toPassableCap(guestToHost.get(g)) === toPassableCap(h) ||
175
+ Fail`internal: g->h ${g} -> ${h} vs ${guestToHost.get(g)}`;
176
+ hostToGuest.get(h) === g ||
177
+ Fail`internal h->g: ${h} -> ${g} vs ${hostToGuest.get(h)}`;
178
+ return true;
179
+ } else {
180
+ !hostToGuest.has(h) ||
181
+ Fail`internal: unexpected h->g ${h} -> ${hostToGuest.get(h)}`;
182
+ return false;
183
+ }
184
+ },
185
+ guestToHost(g) {
186
+ harden(g);
187
+ const { self } = this;
188
+ const guestToHost = g2h.for(self);
189
+
190
+ return guestToHost.get(g);
191
+ },
192
+ hostToGuest(h) {
193
+ const { self } = this;
194
+ const hostToGuest = h2g.for(self);
195
+
196
+ // Even though result is unguarded, i.e., guarded by `M.raw()`, don't
197
+ // need to harden here because was already harden when added to
198
+ // collection.
199
+ return hostToGuest.get(h);
200
+ },
201
+ });
202
+ };
203
+ harden(prepareBijection);
204
+
205
+ /**
206
+ * @typedef {ReturnType<ReturnType<prepareBijection>>} Bijection
207
+ */
@@ -0,0 +1,6 @@
1
+ export function makeConvertKit(bijection: any, makeGuestForHostRemotable: any, makeGuestForHostVow: any): {
2
+ guestToHost: (specimen: Passable, label?: string | undefined) => any;
3
+ hostToGuest: (specimen: Passable, label?: string | undefined) => any;
4
+ };
5
+ import type { Passable } from '@endo/pass-style';
6
+ //# sourceMappingURL=convert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["convert.js"],"names":[],"mappings":"AA+EO;4BARM,QAAQ;4BAAR,QAAQ;EA4DpB;8BAtH0B,kBAAkB"}
package/src/convert.js ADDED
@@ -0,0 +1,133 @@
1
+ import { Fail, X, annotateError, makeError, q } from '@endo/errors';
2
+ import { throwLabeled } from '@endo/common/throw-labeled.js';
3
+ import {
4
+ getErrorConstructor,
5
+ getTag,
6
+ isObject,
7
+ makeTagged,
8
+ passStyleOf,
9
+ } from '@endo/pass-style';
10
+ import { isVow } from '@agoric/vow/src/vow-utils.js';
11
+ import { objectMap } from '@endo/common/object-map.js';
12
+
13
+ /**
14
+ * @import {Passable} from '@endo/pass-style'
15
+ */
16
+
17
+ const makeConvert = (convertRemotable, convertPromiseOrVow, convertError) => {
18
+ const convertRecur = (specimen, label) => {
19
+ // Open code the synchronous part of applyLabelingError, because
20
+ // we need to preserve returned promise identity.
21
+ // TODO switch to Richard Gibson's suggestion for a better way
22
+ // to keep track of the error labeling.
23
+ // See https://github.com/endojs/endo/pull/1795#issuecomment-1756093032
24
+ if (label === undefined) {
25
+ // eslint-disable-next-line no-use-before-define
26
+ return innerConvert(specimen);
27
+ }
28
+ try {
29
+ // eslint-disable-next-line no-use-before-define
30
+ return innerConvert(specimen);
31
+ } catch (err) {
32
+ throwLabeled(err, label);
33
+ }
34
+ };
35
+
36
+ const innerConvert = specimen => {
37
+ if (!isObject(specimen)) {
38
+ return specimen;
39
+ }
40
+ const passStyle = passStyleOf(specimen);
41
+ switch (passStyle) {
42
+ case 'copyArray': {
43
+ return specimen.map((element, i) => convertRecur(element, i));
44
+ }
45
+ case 'copyRecord': {
46
+ return objectMap(specimen, (value, name) => convertRecur(value, name));
47
+ }
48
+ case 'tagged': {
49
+ if (isVow(specimen)) {
50
+ return convertPromiseOrVow(specimen);
51
+ }
52
+ const tag = getTag(specimen);
53
+ const { payload } = specimen;
54
+ return makeTagged(tag, convertRecur(payload, `${tag} payload`));
55
+ }
56
+ case 'error': {
57
+ return convertError(specimen);
58
+ }
59
+ case 'remotable': {
60
+ return convertRemotable(specimen);
61
+ }
62
+ case 'promise': {
63
+ return convertPromiseOrVow(specimen);
64
+ }
65
+ default: {
66
+ throw Fail`unexpected passStyle ${q(passStyle)}`;
67
+ }
68
+ }
69
+ };
70
+
71
+ /**
72
+ * @param {Passable} specimen
73
+ * @param {string} [label]
74
+ */
75
+ const convert = (specimen, label = undefined) =>
76
+ convertRecur(harden(specimen), label);
77
+ return harden(convert);
78
+ };
79
+
80
+ export const makeConvertKit = (
81
+ bijection,
82
+ makeGuestForHostRemotable,
83
+ makeGuestForHostVow,
84
+ ) => {
85
+ const guestToHost = makeConvert(
86
+ gRem => {
87
+ if (bijection.hasGuest(gRem)) {
88
+ return bijection.guestToHost(gRem);
89
+ }
90
+ throw Fail`cannot yet send guest remotables ${gRem}`;
91
+ },
92
+ gProm => {
93
+ if (bijection.hasGuest(gProm)) {
94
+ return bijection.guestToHost(gProm);
95
+ }
96
+ throw Fail`cannot yet send guest promises ${gProm}`;
97
+ },
98
+ gErr => {
99
+ const hErr = harden(
100
+ makeError(gErr.message, getErrorConstructor(gErr.name)),
101
+ );
102
+ annotateError(hErr, X`from guest error ${gErr}`);
103
+ return hErr;
104
+ },
105
+ );
106
+
107
+ const hostToGuest = makeConvert(
108
+ hRem => {
109
+ if (bijection.hasHost(hRem)) {
110
+ return bijection.hostToGuest(hRem);
111
+ }
112
+ const gRem = makeGuestForHostRemotable(hRem);
113
+ return bijection.unwrapInit(gRem, hRem);
114
+ },
115
+ hVow => {
116
+ if (bijection.hasHost(hVow)) {
117
+ return bijection.hostToGuest(hVow);
118
+ }
119
+ const gP = makeGuestForHostVow(hVow);
120
+ return bijection.unwrapInit(gP, hVow);
121
+ },
122
+ hErr => {
123
+ const gErr = harden(
124
+ makeError(hErr.message, getErrorConstructor(hErr.name)),
125
+ );
126
+ annotateError(gErr, X`from host error ${hErr}`);
127
+ return gErr;
128
+ },
129
+ );
130
+
131
+ return harden({ guestToHost, hostToGuest });
132
+ };
133
+ harden(makeConvertKit);
@@ -0,0 +1,16 @@
1
+ export function forwardingMethods(rem: any): {
2
+ [k: string]: any;
3
+ };
4
+ export function makeStateRecord(dataRecord: object): object;
5
+ export function prepareEndowmentTools(outerZone: Zone, outerOptions?: PreparationOptions | undefined): {
6
+ prepareEndowment: (zone: Zone, tag: string, e: unknown) => any;
7
+ unwrap: (wrapped: any, guestWrapped: any) => any;
8
+ };
9
+ export type EndowmentKind = "promise" | "storable" | "far" | "function" | "array" | "record" | "state";
10
+ export type EndowmentTools = ReturnType<(outerZone: Zone, outerOptions?: PreparationOptions | undefined) => {
11
+ prepareEndowment: (zone: Zone, tag: string, e: unknown) => any;
12
+ unwrap: (wrapped: any, guestWrapped: any) => any;
13
+ }>;
14
+ import type { Zone } from '@agoric/base-zone';
15
+ import type { PreparationOptions } from '../src/types.js';
16
+ //# sourceMappingURL=endowments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endowments.d.ts","sourceRoot":"","sources":["endowments.js"],"names":[],"mappings":"AA4CO;;EASN;AAWM,4CAHI,MAAM,GACJ,MAAM,CAqBhB;AAMI,iDAHI,IAAI;6BA6GF,IAAI,OACJ,MAAM,KACN,OAAO;;EAyFnB;4BA5QY,SAAS,GAAG,UAAU,GAAG,KAAK,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO;6BAgR1E,UAAU,aA5MZ,IAAI;6BA6GF,IAAI,OACJ,MAAM,KACN,OAAO;;EA6F0B;0BAtRvB,mBAAmB;wCAEL,iBAAiB"}
@@ -0,0 +1,292 @@
1
+ import { Fail } from '@endo/errors';
2
+ import { E } from '@endo/eventual-send';
3
+ import { isPromise } from '@endo/promise-kit';
4
+ import { isRemotable, isPassable, GET_METHOD_NAMES } from '@endo/pass-style';
5
+ import { M, objectMap } from '@endo/patterns';
6
+ import { prepareVowTools, toPassableCap } from '@agoric/vow';
7
+ import { isVow } from '@agoric/vow/src/vow-utils.js';
8
+ import { isUpgradeDisconnection } from '@agoric/internal/src/upgrade-api.js';
9
+ import { PropertyKeyShape } from './type-guards.js';
10
+
11
+ /**
12
+ * @import {RemotableObject} from '@endo/pass-style'
13
+ * @import {Zone} from '@agoric/base-zone'
14
+ * @import {Callable} from '@agoric/internal'
15
+ * @import {PreparationOptions} from '../src/types.js'
16
+ */
17
+
18
+ /**
19
+ * @typedef {'promise' | 'storable' | 'far' | 'function' | 'array' | 'record' | 'state'} EndowmentKind
20
+ */
21
+
22
+ const {
23
+ getOwnPropertyDescriptor,
24
+ getOwnPropertyDescriptors,
25
+ create,
26
+ fromEntries,
27
+ entries,
28
+ prototype: objectPrototype,
29
+ } = Object;
30
+ const { ownKeys } = Reflect;
31
+
32
+ const FunctionWrapperI = M.interface('FunctionWrapper', {
33
+ apply: M.call(M.array()).returns(M.any()),
34
+ });
35
+
36
+ const StateAccessorI = M.interface('StateAccessor', {
37
+ get: M.call(PropertyKeyShape).returns(M.any()),
38
+ set: M.call(PropertyKeyShape, M.any()).returns(),
39
+ });
40
+
41
+ const UnwrapperI = M.interface('Unwrapper', {
42
+ unwrap: M.call(M.remotable('guestWrapped')).returns(M.raw()),
43
+ });
44
+
45
+ export const forwardingMethods = rem => {
46
+ const keys = rem[GET_METHOD_NAMES]();
47
+ const makeMethodEntry = key =>
48
+ entries({
49
+ [key](...args) {
50
+ return rem[key](...args);
51
+ },
52
+ })[0];
53
+ return fromEntries(keys.map(makeMethodEntry));
54
+ };
55
+
56
+ /**
57
+ * Given a possibly mutable (and therefore unhardened) record, return a
58
+ * corresponding state record that acts identically for normal
59
+ * gets and sets, but is implemented using accessors, so it will be recognized
60
+ * as a state record.
61
+ *
62
+ * @param {object} dataRecord
63
+ * @returns {object}
64
+ */
65
+ export const makeStateRecord = dataRecord =>
66
+ harden(
67
+ create(
68
+ objectPrototype,
69
+ fromEntries(
70
+ ownKeys(dataRecord).flatMap(key =>
71
+ entries(
72
+ getOwnPropertyDescriptors({
73
+ get [key]() {
74
+ return dataRecord[key];
75
+ },
76
+ set [key](newValue) {
77
+ dataRecord[key] = newValue;
78
+ },
79
+ }),
80
+ ),
81
+ ),
82
+ ),
83
+ ),
84
+ );
85
+
86
+ /**
87
+ * @param {Zone} outerZone
88
+ * @param {PreparationOptions} [outerOptions]
89
+ */
90
+ export const prepareEndowmentTools = (outerZone, outerOptions = {}) => {
91
+ const { vowTools = prepareVowTools(outerZone) } = outerOptions;
92
+ const { makeVowKit } = vowTools;
93
+
94
+ const functionUnwrapper = outerZone.exo('FunctionUnwrapper', UnwrapperI, {
95
+ unwrap(guestWrapped) {
96
+ const unwrapped = (...args) => guestWrapped.apply(args);
97
+ return harden(unwrapped);
98
+ },
99
+ });
100
+
101
+ const makeStateUnwrapper = outerZone.exoClass(
102
+ 'StateUnwrapper',
103
+ UnwrapperI,
104
+ keys => ({ keys }),
105
+ {
106
+ unwrap(guestWrapped) {
107
+ const { state } = this;
108
+ const { keys } = state;
109
+ return harden(
110
+ create(
111
+ objectPrototype,
112
+ fromEntries(
113
+ keys.flatMap(key =>
114
+ entries(
115
+ getOwnPropertyDescriptors({
116
+ get [key]() {
117
+ return guestWrapped.get(key);
118
+ },
119
+ set [key](newValue) {
120
+ guestWrapped.set(key, newValue);
121
+ },
122
+ }),
123
+ ),
124
+ ),
125
+ ),
126
+ ),
127
+ );
128
+ },
129
+ },
130
+ );
131
+
132
+ /**
133
+ * Endowment taxonomy. Expected to grow over time.
134
+ * Defined within `prepareEndowmentTools` because isStorable depends on zone.
135
+ *
136
+ * @param {unknown} e
137
+ * @returns {EndowmentKind}
138
+ */
139
+ const endowmentKindOf = e => {
140
+ harden(e);
141
+ if (isPromise(e)) {
142
+ return 'promise';
143
+ } else if (outerZone.isStorable(e)) {
144
+ return 'storable';
145
+ } else if (isPassable(e) && isRemotable(e)) {
146
+ return 'far';
147
+ } else if (typeof e === 'function') {
148
+ return 'function';
149
+ } else if (typeof e === 'object') {
150
+ if (e === null) {
151
+ throw Fail`internal: null is always storable`;
152
+ }
153
+ if (Array.isArray(e)) {
154
+ return 'array';
155
+ }
156
+ const keys = ownKeys(e);
157
+ keys.length >= 1 || Fail`empty record should be storable ${e}`;
158
+ const desc = /** @type {PropertyDescriptor} */ (
159
+ getOwnPropertyDescriptor(e, keys[0])
160
+ );
161
+ if ('value' in desc) {
162
+ return 'record';
163
+ } else {
164
+ 'get' in desc || Fail`internal: unexpected descriptor ${desc}`;
165
+ return 'state';
166
+ }
167
+ } else {
168
+ throw Fail`unexpected endowment ${e}`;
169
+ }
170
+ };
171
+ harden(endowmentKindOf);
172
+
173
+ const unwrapMap = outerZone.weakMapStore('unwrapMap', {
174
+ keyShape: M.remotable('wrapped'),
175
+ valueShape: M.remotable('unwrapper'),
176
+ });
177
+
178
+ const unwrapMapHas = k => {
179
+ if (isVow(k) || isRemotable(k)) {
180
+ return unwrapMap.has(toPassableCap(k));
181
+ } else {
182
+ return false;
183
+ }
184
+ };
185
+ const unwrapMapGet = k => unwrapMap.get(toPassableCap(k));
186
+ const unwrapMapSet = (k, v) => {
187
+ const k2 = toPassableCap(k);
188
+ if (unwrapMapHas(k)) {
189
+ unwrapMap.set(k2, v);
190
+ } else {
191
+ unwrapMap.init(k2, v);
192
+ }
193
+ };
194
+
195
+ /**
196
+ * @param {Zone} zone
197
+ * @param {string} tag
198
+ * @param {unknown} e
199
+ */
200
+ const prepareEndowment = (zone, tag, e) => {
201
+ const eKind = endowmentKindOf(e);
202
+ switch (eKind) {
203
+ case 'promise': {
204
+ const p = /** @type {Promise} */ (e);
205
+ // Not using watch or watchPromise because upgrade rejection
206
+ // should leave it to be resolved by the next promise endowment
207
+ const { vow, resolver } = makeVowKit();
208
+ void E.when(
209
+ p,
210
+ v => {
211
+ resolver.resolve(v);
212
+ },
213
+ reason => {
214
+ if (!isUpgradeDisconnection(reason)) {
215
+ resolver.reject(reason);
216
+ }
217
+ },
218
+ );
219
+ return vow;
220
+ }
221
+ case 'storable': {
222
+ return e;
223
+ }
224
+ case 'far': {
225
+ const r = /** @type {RemotableObject} */ (e);
226
+ const methods = forwardingMethods(r);
227
+ return zone.exo(
228
+ tag,
229
+ M.interface('FarWrapped', {}, { defaultGuards: 'raw' }),
230
+ methods,
231
+ );
232
+ }
233
+ case 'function': {
234
+ const f = /** @type {Callable} */ (e);
235
+ const wrapped = zone.exo(tag, FunctionWrapperI, {
236
+ apply(args) {
237
+ return f(...args);
238
+ },
239
+ });
240
+ unwrapMapSet(wrapped, functionUnwrapper);
241
+ return wrapped;
242
+ }
243
+ case 'array': {
244
+ const a = /** @type {unknown[]} */ (e);
245
+ const subZone = zone.subZone(tag);
246
+ return a.map((subE, i) => prepareEndowment(subZone, `${i}`, subE));
247
+ }
248
+ case 'record': {
249
+ const r = /** @type {Record<PropertyKey, unknown>} */ (e);
250
+ const subZone = zone.subZone(tag);
251
+ return objectMap(r, (subE, k) =>
252
+ prepareEndowment(subZone, String(k), subE),
253
+ );
254
+ }
255
+ case 'state': {
256
+ const state = /** @type {Record<PropertyKey, unknown>} */ (e);
257
+ const keys = harden(ownKeys(state));
258
+ const wrapped = zone.exo(tag, StateAccessorI, {
259
+ get(key) {
260
+ return state[key];
261
+ },
262
+ set(key, newValue) {
263
+ state[key] = newValue;
264
+ },
265
+ });
266
+ const stateUnwrapper = makeStateUnwrapper(keys);
267
+ // Need to replace the instance because the keys may be different
268
+ unwrapMapSet(wrapped, stateUnwrapper);
269
+ return wrapped;
270
+ }
271
+ default: {
272
+ throw Fail`unexpected endowment ${e}`;
273
+ }
274
+ }
275
+ };
276
+
277
+ const unwrap = (wrapped, guestWrapped) => {
278
+ if (unwrapMapHas(wrapped)) {
279
+ const unwrapper = unwrapMapGet(wrapped);
280
+ return unwrapper.unwrap(guestWrapped);
281
+ } else {
282
+ return guestWrapped;
283
+ }
284
+ };
285
+
286
+ return harden({ prepareEndowment, unwrap });
287
+ };
288
+ harden(prepareEndowmentTools);
289
+
290
+ /**
291
+ * @typedef {ReturnType<prepareEndowmentTools>} EndowmentTools
292
+ */
@@ -0,0 +1,3 @@
1
+ export function makeEphemera<S extends WeakKey = WeakKey, V extends unknown = any>(reinit: (self: S) => V): Ephemera<S, V>;
2
+ import type { Ephemera } from './types.js';
3
+ //# sourceMappingURL=ephemera.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ephemera.d.ts","sourceRoot":"","sources":["ephemera.js"],"names":[],"mappings":"AAsBO,6BALiB,CAAC,SAAX,OAAQ,YACF,CAAC,gCACV,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GACZ,SAAS,CAAC,EAAC,CAAC,CAAC,CAiBzB;8BApC0B,YAAY"}