@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/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,4 @@
1
+ export * from "./tools.js";
2
+ export * from "./types.js";
3
+ export { default as makeE } from "./E.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":""}
package/src/index.js ADDED
@@ -0,0 +1,6 @@
1
+ // @ts-check
2
+ export * from './tools.js';
3
+ export { default as makeE } from './E.js';
4
+
5
+ // eslint-disable-next-line import/export
6
+ export * from './types.js';
@@ -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);