@agoric/vow 0.1.1-dev-c5284e4.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/LICENSE +201 -0
- package/README.md +115 -0
- package/package.json +53 -0
- package/src/E.d.ts +156 -0
- package/src/E.d.ts.map +1 -0
- package/src/E.js +448 -0
- package/src/index.d.ts +4 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.js +6 -0
- package/src/message-breakpoints.d.ts +44 -0
- package/src/message-breakpoints.d.ts.map +1 -0
- package/src/message-breakpoints.js +180 -0
- package/src/tools.d.ts +9 -0
- package/src/tools.d.ts.map +1 -0
- package/src/tools.js +19 -0
- package/src/track-turns.d.ts +7 -0
- package/src/track-turns.d.ts.map +1 -0
- package/src/track-turns.js +114 -0
- package/src/types.d.ts +44 -0
- package/src/types.d.ts.map +1 -0
- package/src/types.js +78 -0
- package/src/vow-utils.d.ts +4 -0
- package/src/vow-utils.d.ts.map +1 -0
- package/src/vow-utils.js +34 -0
- package/src/vow.d.ts +3 -0
- package/src/vow.d.ts.map +1 -0
- package/src/vow.js +138 -0
- package/src/watch-promise.d.ts +10 -0
- package/src/watch-promise.d.ts.map +1 -0
- package/src/watch-promise.js +61 -0
- package/src/watch.d.ts +3 -0
- package/src/watch.d.ts.map +1 -0
- package/src/watch.js +163 -0
- package/src/when.d.ts +3 -0
- package/src/when.d.ts.map +1 -0
- package/src/when.js +56 -0
package/src/E.js
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { trackTurns } from './track-turns.js';
|
|
3
|
+
import { makeMessageBreakpointTester } from './message-breakpoints.js';
|
|
4
|
+
|
|
5
|
+
const { details: X, quote: q, Fail } = assert;
|
|
6
|
+
const { assign, create } = Object;
|
|
7
|
+
|
|
8
|
+
const onSend = makeMessageBreakpointTester('ENDO_SEND_BREAKPOINTS');
|
|
9
|
+
|
|
10
|
+
/** @typedef {(...args: any[]) => any} Callable */
|
|
11
|
+
|
|
12
|
+
/** @type {ProxyHandler<any>} */
|
|
13
|
+
const baseFreezableProxyHandler = {
|
|
14
|
+
set(_target, _prop, _value) {
|
|
15
|
+
return false;
|
|
16
|
+
},
|
|
17
|
+
isExtensible(_target) {
|
|
18
|
+
return false;
|
|
19
|
+
},
|
|
20
|
+
setPrototypeOf(_target, _value) {
|
|
21
|
+
return false;
|
|
22
|
+
},
|
|
23
|
+
deleteProperty(_target, _prop) {
|
|
24
|
+
return false;
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// E Proxy handlers pretend that any property exists on the target and returns
|
|
29
|
+
// a function for their value. While this function is "bound" by context, it is
|
|
30
|
+
// meant to be called as a method. For that reason, the returned function
|
|
31
|
+
// includes a check that the `this` argument corresponds to the initial
|
|
32
|
+
// receiver when the function was retrieved.
|
|
33
|
+
// E Proxy handlers also forward direct calls to the target in case the remote
|
|
34
|
+
// is a function instead of an object. No such receiver checks are necessary in
|
|
35
|
+
// that case.
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A Proxy handler for E(x).
|
|
39
|
+
*
|
|
40
|
+
* @param {any} recipient Any value passed to E(x)
|
|
41
|
+
* @param {import('@endo/eventual-send').HandledPromiseConstructor} HandledPromise
|
|
42
|
+
* @param {(x: any) => Promise<any>} unwrap
|
|
43
|
+
* @returns {ProxyHandler<any>} the Proxy handler
|
|
44
|
+
*/
|
|
45
|
+
const makeEProxyHandler = (recipient, HandledPromise, unwrap) =>
|
|
46
|
+
harden({
|
|
47
|
+
...baseFreezableProxyHandler,
|
|
48
|
+
get: (_target, propertyKey, receiver) => {
|
|
49
|
+
return harden(
|
|
50
|
+
{
|
|
51
|
+
/**
|
|
52
|
+
* This function purposely checks the `this` value (see above)
|
|
53
|
+
* In order to be `this` sensitive it is defined using concise method
|
|
54
|
+
* syntax rather than as an arrow function. To ensure the function
|
|
55
|
+
* is not constructable, it also avoids the `function` syntax.
|
|
56
|
+
* @param {...any[]} args
|
|
57
|
+
*/
|
|
58
|
+
[propertyKey](...args) {
|
|
59
|
+
if (this !== receiver) {
|
|
60
|
+
// Reject the async function call
|
|
61
|
+
return HandledPromise.reject(
|
|
62
|
+
assert.error(
|
|
63
|
+
X`Unexpected receiver for "${q(propertyKey)}" method of E(${q(
|
|
64
|
+
recipient,
|
|
65
|
+
)})`,
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (onSend && onSend.shouldBreakpoint(recipient, propertyKey)) {
|
|
71
|
+
// eslint-disable-next-line no-debugger
|
|
72
|
+
debugger; // LOOK UP THE STACK
|
|
73
|
+
// Stopped at a breakpoint on eventual-send of a method-call message,
|
|
74
|
+
// so that you can walk back on the stack to see how we came to
|
|
75
|
+
// make this eventual-send
|
|
76
|
+
}
|
|
77
|
+
return unwrap(
|
|
78
|
+
HandledPromise.applyMethod(unwrap(recipient), propertyKey, args),
|
|
79
|
+
);
|
|
80
|
+
},
|
|
81
|
+
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/50319
|
|
82
|
+
}[propertyKey],
|
|
83
|
+
);
|
|
84
|
+
},
|
|
85
|
+
apply: (_target, _thisArg, argArray = []) => {
|
|
86
|
+
if (onSend && onSend.shouldBreakpoint(recipient, undefined)) {
|
|
87
|
+
// eslint-disable-next-line no-debugger
|
|
88
|
+
debugger; // LOOK UP THE STACK
|
|
89
|
+
// Stopped at a breakpoint on eventual-send of a function-call message,
|
|
90
|
+
// so that you can walk back on the stack to see how we came to
|
|
91
|
+
// make this eventual-send
|
|
92
|
+
}
|
|
93
|
+
return unwrap(HandledPromise.applyFunction(unwrap(recipient), argArray));
|
|
94
|
+
},
|
|
95
|
+
has: (_target, _p) => {
|
|
96
|
+
// We just pretend everything exists.
|
|
97
|
+
return true;
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* A Proxy handler for E.sendOnly(x)
|
|
103
|
+
* It is a variant on the E(x) Proxy handler.
|
|
104
|
+
*
|
|
105
|
+
* @param {any} recipient Any value passed to E.sendOnly(x)
|
|
106
|
+
* @param {import('@endo/eventual-send').HandledPromiseConstructor} HandledPromise
|
|
107
|
+
* @param {(x: any) => Promise<any>} unwrap
|
|
108
|
+
* @returns {ProxyHandler<any>} the Proxy handler
|
|
109
|
+
*/
|
|
110
|
+
const makeESendOnlyProxyHandler = (recipient, HandledPromise, unwrap) =>
|
|
111
|
+
harden({
|
|
112
|
+
...baseFreezableProxyHandler,
|
|
113
|
+
get: (_target, propertyKey, receiver) => {
|
|
114
|
+
return harden(
|
|
115
|
+
{
|
|
116
|
+
/**
|
|
117
|
+
* This function purposely checks the `this` value (see above)
|
|
118
|
+
* In order to be `this` sensitive it is defined using concise method
|
|
119
|
+
* syntax rather than as an arrow function. To ensure the function
|
|
120
|
+
* is not constructable, it also avoids the `function` syntax.
|
|
121
|
+
* @param {...any[]} args
|
|
122
|
+
*/
|
|
123
|
+
[propertyKey](...args) {
|
|
124
|
+
// Throw since the function returns nothing
|
|
125
|
+
this === receiver ||
|
|
126
|
+
Fail`Unexpected receiver for "${q(
|
|
127
|
+
propertyKey,
|
|
128
|
+
)}" method of E.sendOnly(${q(recipient)})`;
|
|
129
|
+
if (onSend && onSend.shouldBreakpoint(recipient, propertyKey)) {
|
|
130
|
+
// eslint-disable-next-line no-debugger
|
|
131
|
+
debugger; // LOOK UP THE STACK
|
|
132
|
+
// Stopped at a breakpoint on eventual-send of a method-call message,
|
|
133
|
+
// so that you can walk back on the stack to see how we came to
|
|
134
|
+
// make this eventual-send
|
|
135
|
+
}
|
|
136
|
+
HandledPromise.applyMethodSendOnly(
|
|
137
|
+
unwrap(recipient),
|
|
138
|
+
propertyKey,
|
|
139
|
+
args,
|
|
140
|
+
);
|
|
141
|
+
return undefined;
|
|
142
|
+
},
|
|
143
|
+
// @ts-expect-error https://github.com/microsoft/TypeScript/issues/50319
|
|
144
|
+
}[propertyKey],
|
|
145
|
+
);
|
|
146
|
+
},
|
|
147
|
+
apply: (_target, _thisArg, argsArray = []) => {
|
|
148
|
+
if (onSend && onSend.shouldBreakpoint(recipient, undefined)) {
|
|
149
|
+
// eslint-disable-next-line no-debugger
|
|
150
|
+
debugger; // LOOK UP THE STACK
|
|
151
|
+
// Stopped at a breakpoint on eventual-send of a function-call message,
|
|
152
|
+
// so that you can walk back on the stack to see how we came to
|
|
153
|
+
// make this eventual-send
|
|
154
|
+
}
|
|
155
|
+
HandledPromise.applyFunctionSendOnly(unwrap(recipient), argsArray);
|
|
156
|
+
return undefined;
|
|
157
|
+
},
|
|
158
|
+
has: (_target, _p) => {
|
|
159
|
+
// We just pretend that everything exists.
|
|
160
|
+
return true;
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* A Proxy handler for E.get(x)
|
|
166
|
+
* It is a variant on the E(x) Proxy handler.
|
|
167
|
+
*
|
|
168
|
+
* @param {any} x Any value passed to E.get(x)
|
|
169
|
+
* @param {import('@endo/eventual-send').HandledPromiseConstructor} HandledPromise
|
|
170
|
+
* @param {(x: any) => Promise<any>} unwrap
|
|
171
|
+
* @returns {ProxyHandler<any>} the Proxy handler
|
|
172
|
+
*/
|
|
173
|
+
const makeEGetProxyHandler = (x, HandledPromise, unwrap) =>
|
|
174
|
+
harden({
|
|
175
|
+
...baseFreezableProxyHandler,
|
|
176
|
+
has: (_target, _prop) => true,
|
|
177
|
+
get: (_target, prop) => HandledPromise.get(unwrap(x), prop),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
/** @param {any} x */
|
|
181
|
+
const resolve = x => HandledPromise.resolve(x);
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @template [A={}]
|
|
185
|
+
* @template {(x: any) => Promise<any>} [U=(x: any) => Promise<any>]
|
|
186
|
+
* @param {import('@endo/eventual-send').HandledPromiseConstructor} HandledPromise
|
|
187
|
+
* @param {object} [powers]
|
|
188
|
+
* @param {U} [powers.unwrap]
|
|
189
|
+
* @param {A} [powers.additional]
|
|
190
|
+
*/
|
|
191
|
+
const makeE = (
|
|
192
|
+
HandledPromise,
|
|
193
|
+
{
|
|
194
|
+
additional = /** @type {A} */ ({}),
|
|
195
|
+
unwrap = /** @type {U} */ (resolve),
|
|
196
|
+
} = {},
|
|
197
|
+
) => {
|
|
198
|
+
return harden(
|
|
199
|
+
assign(
|
|
200
|
+
/**
|
|
201
|
+
* E(x) returns a proxy on which you can call arbitrary methods. Each of these
|
|
202
|
+
* method calls returns a promise. The method will be invoked on whatever
|
|
203
|
+
* 'x' designates (or resolves to) in a future turn, not this one.
|
|
204
|
+
*
|
|
205
|
+
* @template T
|
|
206
|
+
* @param {T} x target for method/function call
|
|
207
|
+
* @returns {ECallableOrMethods<RemoteFunctions<T>>} method/function call proxy
|
|
208
|
+
*/
|
|
209
|
+
x =>
|
|
210
|
+
harden(
|
|
211
|
+
new Proxy(() => {}, makeEProxyHandler(x, HandledPromise, unwrap)),
|
|
212
|
+
),
|
|
213
|
+
{
|
|
214
|
+
/**
|
|
215
|
+
* E.get(x) returns a proxy on which you can get arbitrary properties.
|
|
216
|
+
* Each of these properties returns a promise for the property. The promise
|
|
217
|
+
* value will be the property fetched from whatever 'x' designates (or
|
|
218
|
+
* resolves to) in a future turn, not this one.
|
|
219
|
+
*
|
|
220
|
+
* @template T
|
|
221
|
+
* @param {T} x target for property get
|
|
222
|
+
* @returns {EGetters<LocalRecord<T>>} property get proxy
|
|
223
|
+
* @readonly
|
|
224
|
+
*/
|
|
225
|
+
get: x =>
|
|
226
|
+
harden(
|
|
227
|
+
new Proxy(
|
|
228
|
+
create(null),
|
|
229
|
+
makeEGetProxyHandler(x, HandledPromise, unwrap),
|
|
230
|
+
),
|
|
231
|
+
),
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* E.resolve(x) converts x to a handled promise. It is
|
|
235
|
+
* shorthand for HandledPromise.resolve(x)
|
|
236
|
+
*
|
|
237
|
+
* @template T
|
|
238
|
+
* @param {T} x value to convert to a handled promise
|
|
239
|
+
* @returns {Promise<Awaited<T>>} handled promise for x
|
|
240
|
+
* @readonly
|
|
241
|
+
*/
|
|
242
|
+
resolve: x => resolve(unwrap(x)),
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* E.sendOnly returns a proxy similar to E, but for which the results
|
|
246
|
+
* are ignored (undefined is returned).
|
|
247
|
+
*
|
|
248
|
+
* @template T
|
|
249
|
+
* @param {T} x target for method/function call
|
|
250
|
+
* @returns {ESendOnlyCallableOrMethods<RemoteFunctions<T>>} method/function call proxy
|
|
251
|
+
* @readonly
|
|
252
|
+
*/
|
|
253
|
+
sendOnly: x =>
|
|
254
|
+
harden(
|
|
255
|
+
new Proxy(
|
|
256
|
+
() => {},
|
|
257
|
+
makeESendOnlyProxyHandler(x, HandledPromise, unwrap),
|
|
258
|
+
),
|
|
259
|
+
),
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* E.when(x, res, rej) is equivalent to
|
|
263
|
+
* unwrapped(x).then(onfulfilled, onrejected)
|
|
264
|
+
*
|
|
265
|
+
* @template T
|
|
266
|
+
* @template [TResult1=Unwrap<T>]
|
|
267
|
+
* @template [TResult2=never]
|
|
268
|
+
* @param {ERef<T>} x value to convert to a handled promise
|
|
269
|
+
* @param {(value: Unwrap<T>) => ERef<TResult1>} [onfulfilled]
|
|
270
|
+
* @param {(reason: any) => ERef<TResult2>} [onrejected]
|
|
271
|
+
* @returns {Promise<TResult1 | TResult2>}
|
|
272
|
+
* @readonly
|
|
273
|
+
*/
|
|
274
|
+
when: (x, onfulfilled, onrejected) => {
|
|
275
|
+
const unwrapped = resolve(unwrap(x));
|
|
276
|
+
if (onfulfilled == null && onrejected == null) {
|
|
277
|
+
return unwrapped;
|
|
278
|
+
}
|
|
279
|
+
return unwrapped.then(
|
|
280
|
+
...trackTurns(/** @type {const} */ ([onfulfilled, onrejected])),
|
|
281
|
+
);
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
additional,
|
|
285
|
+
),
|
|
286
|
+
);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
export default makeE;
|
|
290
|
+
|
|
291
|
+
/** @typedef {ReturnType<makeE>} EProxy */
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* `DataOnly<T>` means to return a record type `T2` consisting only of
|
|
295
|
+
* properties that are *not* functions.
|
|
296
|
+
*
|
|
297
|
+
* @template T The type to be filtered.
|
|
298
|
+
* @typedef {Omit<T, FilteredKeys<T, Callable>>} DataOnly
|
|
299
|
+
*/
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* @see {@link https://github.com/microsoft/TypeScript/issues/31394}
|
|
303
|
+
* @template T
|
|
304
|
+
* @typedef {PromiseLike<T> | T} ERef
|
|
305
|
+
*/
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* @template {Callable} T
|
|
309
|
+
* @typedef {(
|
|
310
|
+
* ReturnType<T> extends PromiseLike<infer U> // if function returns a promise
|
|
311
|
+
* ? T // return the function
|
|
312
|
+
* : (...args: Parameters<T>) => Promise<Awaited<ReturnType<T>>> // make it return a promise
|
|
313
|
+
* )} ECallable
|
|
314
|
+
*/
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* @template T
|
|
318
|
+
* @typedef {{
|
|
319
|
+
* readonly [P in keyof T]: T[P] extends Callable
|
|
320
|
+
* ? ECallable<T[P]>
|
|
321
|
+
* : never;
|
|
322
|
+
* }} EMethods
|
|
323
|
+
*/
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* @template T
|
|
327
|
+
* @typedef {{
|
|
328
|
+
* readonly [P in keyof T]: T[P] extends PromiseLike<infer U>
|
|
329
|
+
* ? T[P]
|
|
330
|
+
* : Promise<Awaited<T[P]>>;
|
|
331
|
+
* }} EGetters
|
|
332
|
+
*/
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* @template {Callable} T
|
|
336
|
+
* @typedef {(...args: Parameters<T>) => Promise<void>} ESendOnlyCallable
|
|
337
|
+
*/
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* @template T
|
|
341
|
+
* @typedef {{
|
|
342
|
+
* readonly [P in keyof T]: T[P] extends Callable
|
|
343
|
+
* ? ESendOnlyCallable<T[P]>
|
|
344
|
+
* : never;
|
|
345
|
+
* }} ESendOnlyMethods
|
|
346
|
+
*/
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* @template T
|
|
350
|
+
* @typedef {(
|
|
351
|
+
* T extends Callable
|
|
352
|
+
* ? ESendOnlyCallable<T> & ESendOnlyMethods<Required<T>>
|
|
353
|
+
* : ESendOnlyMethods<Required<T>>
|
|
354
|
+
* )} ESendOnlyCallableOrMethods
|
|
355
|
+
*/
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* @template T
|
|
359
|
+
* @typedef {(
|
|
360
|
+
* T extends Callable
|
|
361
|
+
* ? ECallable<T> & EMethods<Required<T>>
|
|
362
|
+
* : EMethods<Required<T>>
|
|
363
|
+
* )} ECallableOrMethods
|
|
364
|
+
*/
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Return a union of property names/symbols/numbers P for which the record element T[P]'s type extends U.
|
|
368
|
+
*
|
|
369
|
+
* Given const x = { a: 123, b: 'hello', c: 42, 49: () => {}, 53: 67 },
|
|
370
|
+
*
|
|
371
|
+
* FilteredKeys<typeof x, number> is the type 'a' | 'c' | 53.
|
|
372
|
+
* FilteredKeys<typeof x, string> is the type 'b'.
|
|
373
|
+
* FilteredKeys<typeof x, 42 | 67> is the type 'c' | 53.
|
|
374
|
+
* FilteredKeys<typeof x, boolean> is the type never.
|
|
375
|
+
*
|
|
376
|
+
* @template T
|
|
377
|
+
* @template U
|
|
378
|
+
* @typedef {{ [P in keyof T]: T[P] extends U ? P : never; }[keyof T]} FilteredKeys
|
|
379
|
+
*/
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* `PickCallable<T>` means to return a single root callable or a record type
|
|
383
|
+
* consisting only of properties that are functions.
|
|
384
|
+
*
|
|
385
|
+
* @template T
|
|
386
|
+
* @typedef {(
|
|
387
|
+
* T extends Callable
|
|
388
|
+
* ? (...args: Parameters<T>) => ReturnType<T> // a root callable, no methods
|
|
389
|
+
* : Pick<T, FilteredKeys<T, Callable>> // any callable methods
|
|
390
|
+
* )} PickCallable
|
|
391
|
+
*/
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* `RemoteFunctions<T>` means to return the functions and properties that are remotely callable.
|
|
395
|
+
*
|
|
396
|
+
* @template T
|
|
397
|
+
* @typedef {(
|
|
398
|
+
* T extends import('@endo/eventual-send').RemotableBrand<infer L, infer R> // if a given T is some remote interface R
|
|
399
|
+
* ? PickCallable<R> // then return the callable properties of R
|
|
400
|
+
* : Awaited<T> extends import('@endo/eventual-send').RemotableBrand<infer L, infer R> // otherwise, if the final resolution of T is some remote interface R
|
|
401
|
+
* ? PickCallable<R> // then return the callable properties of R
|
|
402
|
+
* : Awaited<T> extends import('./types').Vow<infer U>
|
|
403
|
+
* ? RemoteFunctions<U> // then extract the remotable functions of U
|
|
404
|
+
* : T extends PromiseLike<infer U> // otherwise, if T is a promise
|
|
405
|
+
* ? Awaited<T> // then return resolved value T
|
|
406
|
+
* : T // otherwise, return T
|
|
407
|
+
* )} RemoteFunctions
|
|
408
|
+
*/
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* @template T
|
|
412
|
+
* @typedef {T extends PromiseLike<infer U> ? Unwrap<U> :
|
|
413
|
+
* T extends import('./types').Vow<infer U> ? Unwrap<U> :
|
|
414
|
+
* T} Unwrap
|
|
415
|
+
*/
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* @template T
|
|
419
|
+
* @typedef {(
|
|
420
|
+
* T extends import('@endo/eventual-send').RemotableBrand<infer L, infer R>
|
|
421
|
+
* ? L
|
|
422
|
+
* : Awaited<T> extends import('@endo/eventual-send').RemotableBrand<infer L, infer R>
|
|
423
|
+
* ? L
|
|
424
|
+
* : Awaited<T> extends import('./types').Vow<infer U>
|
|
425
|
+
* ? LocalRecord<U>
|
|
426
|
+
* : T extends PromiseLike<infer U>
|
|
427
|
+
* ? Awaited<T>
|
|
428
|
+
* : T
|
|
429
|
+
* )} LocalRecord
|
|
430
|
+
*/
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Type for an object that must only be invoked with E. It supports a given
|
|
434
|
+
* interface but declares all the functions as asyncable.
|
|
435
|
+
*
|
|
436
|
+
* @template T
|
|
437
|
+
* @typedef {(
|
|
438
|
+
* T extends Callable
|
|
439
|
+
* ? (...args: Parameters<T>) => ERef<Awaited<EOnly<ReturnType<T>>>>
|
|
440
|
+
* : T extends Record<PropertyKey, Callable>
|
|
441
|
+
* ? {
|
|
442
|
+
* [K in keyof T]: T[K] extends Callable
|
|
443
|
+
* ? (...args: Parameters<T[K]>) => ERef<Awaited<EOnly<ReturnType<T[K]>>>>
|
|
444
|
+
* : T[K];
|
|
445
|
+
* }
|
|
446
|
+
* : T
|
|
447
|
+
* )} EOnly
|
|
448
|
+
*/
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":""}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export function makeMessageBreakpointTester(optionName: string): MessageBreakpointTester | undefined;
|
|
2
|
+
/**
|
|
3
|
+
* A star `'*'` matches any recipient. Otherwise, the string is
|
|
4
|
+
* matched against the value of a recipient's `@@toStringTag`
|
|
5
|
+
* after stripping out any leading `'Alleged: '` or `'DebugName: '`
|
|
6
|
+
* prefix. For objects defined with `Far` this is the first argument,
|
|
7
|
+
* known as the `farName`. For exos, this is the tag.
|
|
8
|
+
*/
|
|
9
|
+
export type MatchStringTag = string | '*';
|
|
10
|
+
/**
|
|
11
|
+
* A star `'*'` matches any method name. Otherwise, the string is
|
|
12
|
+
* matched against the method name. Currently, this is only an exact match.
|
|
13
|
+
* However, beware that we may introduce a string syntax for
|
|
14
|
+
* symbol method names.
|
|
15
|
+
*/
|
|
16
|
+
export type MatchMethodName = string | '*';
|
|
17
|
+
/**
|
|
18
|
+
* A star `'*'` will always breakpoint. Otherwise, the string
|
|
19
|
+
* must be a non-negative integer. Once that is zero, always breakpoint.
|
|
20
|
+
* Otherwise decrement by one each time it matches until it reaches zero.
|
|
21
|
+
* In other words, the countdown represents the number of
|
|
22
|
+
* breakpoint occurrences to skip before actually breakpointing.
|
|
23
|
+
*/
|
|
24
|
+
export type MatchCountdown = number | '*';
|
|
25
|
+
/**
|
|
26
|
+
* This is the external JSON representation, in which
|
|
27
|
+
* - the outer property name is the class-like tag or '*',
|
|
28
|
+
* - the inner property name is the method name or '*',
|
|
29
|
+
* - the value is a non-negative integer countdown or '*'.
|
|
30
|
+
*/
|
|
31
|
+
export type MessageBreakpoints = Record<MatchStringTag, Record<MatchMethodName, MatchCountdown>>;
|
|
32
|
+
/**
|
|
33
|
+
* This is the internal JSON representation, in which
|
|
34
|
+
* - the outer property name is the method name or '*',
|
|
35
|
+
* - the inner property name is the class-like tag or '*',
|
|
36
|
+
* - the value is a non-negative integer countdown or '*'.
|
|
37
|
+
*/
|
|
38
|
+
export type BreakpointTable = Record<MatchMethodName, Record<MatchStringTag, MatchCountdown>>;
|
|
39
|
+
export type MessageBreakpointTester = {
|
|
40
|
+
getBreakpoints: () => MessageBreakpoints;
|
|
41
|
+
setBreakpoints: (newBreakpoints?: MessageBreakpoints) => void;
|
|
42
|
+
shouldBreakpoint: (recipient: object, methodName: string | symbol | undefined) => boolean;
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=message-breakpoints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-breakpoints.d.ts","sourceRoot":"","sources":["message-breakpoints.js"],"names":[],"mappings":"AA0FO,wDAHI,MAAM,GACJ,uBAAuB,GAAG,SAAS,CA0F/C;;;;;;;;6BA1KY,MAAM,GAAG,GAAG;;;;;;;8BAQZ,MAAM,GAAG,GAAG;;;;;;;;6BAOZ,MAAM,GAAG,GAAG;;;;;;;iCAcZ,OAAO,cAAc,EAAE,OAAO,eAAe,EAAE,cAAc,CAAC,CAAC;;;;;;;8BAS/D,OAAO,eAAe,EAAE,OAAO,cAAc,EAAE,cAAc,CAAC,CAAC;;oBAK9D,MAAM,kBAAkB;sCACN,kBAAkB,KAAK,IAAI;kCAE3C,MAAM,cACL,MAAM,GAAG,MAAM,GAAG,SAAS,KACpC,OAAO"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { getEnvironmentOption } from '@endo/env-options';
|
|
3
|
+
|
|
4
|
+
const { quote: q, Fail } = assert;
|
|
5
|
+
|
|
6
|
+
const { hasOwn, freeze, entries } = Object;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {string | '*'} MatchStringTag
|
|
10
|
+
* A star `'*'` matches any recipient. Otherwise, the string is
|
|
11
|
+
* matched against the value of a recipient's `@@toStringTag`
|
|
12
|
+
* after stripping out any leading `'Alleged: '` or `'DebugName: '`
|
|
13
|
+
* prefix. For objects defined with `Far` this is the first argument,
|
|
14
|
+
* known as the `farName`. For exos, this is the tag.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {string | '*'} MatchMethodName
|
|
18
|
+
* A star `'*'` matches any method name. Otherwise, the string is
|
|
19
|
+
* matched against the method name. Currently, this is only an exact match.
|
|
20
|
+
* However, beware that we may introduce a string syntax for
|
|
21
|
+
* symbol method names.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {number | '*'} MatchCountdown
|
|
25
|
+
* A star `'*'` will always breakpoint. Otherwise, the string
|
|
26
|
+
* must be a non-negative integer. Once that is zero, always breakpoint.
|
|
27
|
+
* Otherwise decrement by one each time it matches until it reaches zero.
|
|
28
|
+
* In other words, the countdown represents the number of
|
|
29
|
+
* breakpoint occurrences to skip before actually breakpointing.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* This is the external JSON representation, in which
|
|
34
|
+
* - the outer property name is the class-like tag or '*',
|
|
35
|
+
* - the inner property name is the method name or '*',
|
|
36
|
+
* - the value is a non-negative integer countdown or '*'.
|
|
37
|
+
*
|
|
38
|
+
* @typedef {Record<MatchStringTag, Record<MatchMethodName, MatchCountdown>>} MessageBreakpoints
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* This is the internal JSON representation, in which
|
|
43
|
+
* - the outer property name is the method name or '*',
|
|
44
|
+
* - the inner property name is the class-like tag or '*',
|
|
45
|
+
* - the value is a non-negative integer countdown or '*'.
|
|
46
|
+
*
|
|
47
|
+
* @typedef {Record<MatchMethodName, Record<MatchStringTag, MatchCountdown>>} BreakpointTable
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {object} MessageBreakpointTester
|
|
52
|
+
* @property {() => MessageBreakpoints} getBreakpoints
|
|
53
|
+
* @property {(newBreakpoints?: MessageBreakpoints) => void} setBreakpoints
|
|
54
|
+
* @property {(
|
|
55
|
+
* recipient: object,
|
|
56
|
+
* methodName: string | symbol | undefined
|
|
57
|
+
* ) => boolean} shouldBreakpoint
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {any} val
|
|
62
|
+
* @returns {val is Record<string, any>}
|
|
63
|
+
*/
|
|
64
|
+
const isJSONRecord = val =>
|
|
65
|
+
typeof val === 'object' && val !== null && !Array.isArray(val);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Return `tag` after stripping off any `'Alleged: '` or `'DebugName: '`
|
|
69
|
+
* prefix if present.
|
|
70
|
+
* ```js
|
|
71
|
+
* simplifyTag('Alleged: moola issuer') === 'moola issuer'
|
|
72
|
+
* ```
|
|
73
|
+
* If there are multiple such prefixes, only the outer one is removed.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} tag
|
|
76
|
+
* @returns {string}
|
|
77
|
+
*/
|
|
78
|
+
const simplifyTag = tag => {
|
|
79
|
+
for (const prefix of ['Alleged: ', 'DebugName: ']) {
|
|
80
|
+
if (tag.startsWith(prefix)) {
|
|
81
|
+
return tag.slice(prefix.length);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return tag;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {string} optionName
|
|
89
|
+
* @returns {MessageBreakpointTester | undefined}
|
|
90
|
+
*/
|
|
91
|
+
export const makeMessageBreakpointTester = optionName => {
|
|
92
|
+
let breakpoints = JSON.parse(getEnvironmentOption(optionName, 'null'));
|
|
93
|
+
|
|
94
|
+
if (breakpoints === null) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** @type {BreakpointTable} */
|
|
99
|
+
let breakpointsTable;
|
|
100
|
+
|
|
101
|
+
const getBreakpoints = () => breakpoints;
|
|
102
|
+
freeze(getBreakpoints);
|
|
103
|
+
|
|
104
|
+
const setBreakpoints = (newBreakpoints = breakpoints) => {
|
|
105
|
+
isJSONRecord(newBreakpoints) ||
|
|
106
|
+
Fail`Expected ${q(optionName)} option to be a JSON breakpoints record`;
|
|
107
|
+
|
|
108
|
+
/** @type {BreakpointTable} */
|
|
109
|
+
// @ts-expect-error confused by __proto__
|
|
110
|
+
const newBreakpointsTable = { __proto__: null };
|
|
111
|
+
|
|
112
|
+
for (const [tag, methodBPs] of entries(newBreakpoints)) {
|
|
113
|
+
tag === simplifyTag(tag) ||
|
|
114
|
+
Fail`Just use simple tag ${q(simplifyTag(tag))} rather than ${q(tag)}`;
|
|
115
|
+
isJSONRecord(methodBPs) ||
|
|
116
|
+
Fail`Expected ${q(optionName)} option's ${q(
|
|
117
|
+
tag,
|
|
118
|
+
)} to be a JSON methods breakpoints record`;
|
|
119
|
+
for (const [methodName, count] of entries(methodBPs)) {
|
|
120
|
+
count === '*' ||
|
|
121
|
+
(typeof count === 'number' &&
|
|
122
|
+
Number.isSafeInteger(count) &&
|
|
123
|
+
count >= 0) ||
|
|
124
|
+
Fail`Expected ${q(optionName)} option's ${q(tag)}.${q(
|
|
125
|
+
methodName,
|
|
126
|
+
)} to be "*" or a non-negative integer`;
|
|
127
|
+
|
|
128
|
+
const classBPs = hasOwn(newBreakpointsTable, methodName)
|
|
129
|
+
? newBreakpointsTable[methodName]
|
|
130
|
+
: (newBreakpointsTable[methodName] = {
|
|
131
|
+
// @ts-expect-error confused by __proto__
|
|
132
|
+
__proto__: null,
|
|
133
|
+
});
|
|
134
|
+
classBPs[tag] = count;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
breakpoints = newBreakpoints;
|
|
138
|
+
breakpointsTable = newBreakpointsTable;
|
|
139
|
+
};
|
|
140
|
+
freeze(setBreakpoints);
|
|
141
|
+
|
|
142
|
+
const shouldBreakpoint = (recipient, methodName) => {
|
|
143
|
+
if (methodName === undefined || methodName === null) {
|
|
144
|
+
// TODO enable function breakpointing
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
const classBPs = breakpointsTable[methodName] || breakpointsTable['*'];
|
|
148
|
+
if (classBPs === undefined) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
let tag = simplifyTag(recipient[Symbol.toStringTag]);
|
|
152
|
+
let count = classBPs[tag];
|
|
153
|
+
if (count === undefined) {
|
|
154
|
+
tag = '*';
|
|
155
|
+
count = classBPs[tag];
|
|
156
|
+
if (count === undefined) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (count === '*') {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
if (count === 0) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
assert(typeof count === 'number' && count >= 1);
|
|
167
|
+
classBPs[tag] = count - 1;
|
|
168
|
+
return false;
|
|
169
|
+
};
|
|
170
|
+
freeze(shouldBreakpoint);
|
|
171
|
+
|
|
172
|
+
const breakpointTester = freeze({
|
|
173
|
+
getBreakpoints,
|
|
174
|
+
setBreakpoints,
|
|
175
|
+
shouldBreakpoint,
|
|
176
|
+
});
|
|
177
|
+
breakpointTester.setBreakpoints();
|
|
178
|
+
return breakpointTester;
|
|
179
|
+
};
|
|
180
|
+
freeze(makeMessageBreakpointTester);
|