@endo/eventual-send 1.0.1 → 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@endo/eventual-send",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Extend a Promise class to implement the eventual-send API",
5
5
  "type": "module",
6
6
  "main": "src/no-shim.js",
@@ -35,11 +35,11 @@
35
35
  },
36
36
  "homepage": "https://github.com/endojs/endo#readme",
37
37
  "dependencies": {
38
- "@endo/env-options": "^1.0.1"
38
+ "@endo/env-options": "^1.1.0"
39
39
  },
40
40
  "devDependencies": {
41
- "@endo/lockdown": "^1.0.1",
42
- "@endo/ses-ava": "^1.0.1",
41
+ "@endo/lockdown": "^1.0.2",
42
+ "@endo/ses-ava": "^1.1.0",
43
43
  "ava": "^5.3.0",
44
44
  "c8": "^7.14.0",
45
45
  "tsd": "^0.28.1"
@@ -70,7 +70,7 @@
70
70
  "timeout": "2m"
71
71
  },
72
72
  "typeCoverage": {
73
- "atLeast": 77.79
73
+ "atLeast": 77.81
74
74
  },
75
- "gitHead": "c02aec92c3caa417d6bdf3c4f555f0b2694d9f9e"
75
+ "gitHead": "373f9eebab66c94ed42350473c90fb25e6054f0a"
76
76
  }
package/src/E.d.ts CHANGED
@@ -1,15 +1,7 @@
1
1
  export default makeE;
2
2
  export type EProxy = ReturnType<(HandledPromise: {
3
3
  new <R>(executor: import("./handled-promise.js").HandledExecutor<R>, unfulfilledHandler?: import("./handled-promise.js").Handler<Promise<unknown>> | undefined): Promise<R>;
4
- prototype: Promise<unknown>; /**
5
- * E.sendOnly returns a proxy similar to E, but for which the results
6
- * are ignored (undefined is returned).
7
- *
8
- * @template T
9
- * @param {T} x target for method/function call
10
- * @returns {ESendOnlyCallableOrMethods<RemoteFunctions<T>>} method/function call proxy
11
- * @readonly
12
- */
4
+ prototype: Promise<unknown>;
13
5
  } & PromiseConstructor & import("./handled-promise.js").HandledPromiseStaticMethods) => (<T>(x: T) => ECallableOrMethods<RemoteFunctions<T>>) & {
14
6
  /**
15
7
  * E.get(x) returns a proxy on which you can get arbitrary properties.
package/src/E.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"E.d.ts","sourceRoot":"","sources":["E.js"],"names":[],"mappings":";qBAiNc;;iCArCN;;;;;;;;OAQG;;IAnCH;;;;;;;;;;OAUG;;IAMH;;;;;;;;OAQG;;;;;;IAGH;;;;;;;;OAQG;;IAMH;;;;;;;;;;;OAWG;;EAYoB;;;;;yDAQlB,KAAK,KAAK,GAAG,OAAO,SAAS,EAAE,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;;;;;0BAQ9D,KAAK,CAAC,EAAE,aAAa,CAAC,EAAE,OAAO,SAAS,EAAE,QAAQ,CAAC,CAAC;sBAMpD,YAAY,CAAC,CAAC,GAAG,CAAC;yHAQZ,WAAW,CAAC,CAAC,KAAK,QAAQ,QAAQ,WAAW,CAAC,CAAC,CAAC,CAAC;;;gFAwB7C,WAAW,CAAC,CAAC,KAAK,QAAQ,IAAI,CAAC;;4CAczC,CACZ,CAAK,SAAS,OAAO,SAAS,EAAE,QAAQ,GAChC,kBAAkB,CAAC,CAAC,GAAG,iBAAiB,SAAS,CAAC,CAAC,CAAC,GACpD,iBAAiB,SAAS,CAAC,CAAC,CAAC,CAClC;oCAKS,CACZ,CAAK,SAAS,OAAO,SAAS,EAAE,QAAQ,GAChC,UAAU,CAAC,CAAC,GAAG,SAAS,SAAS,CAAC,CAAC,CAAC,GACpC,SAAS,SAAS,CAAC,CAAC,CAAC,CAC1B;;;;;;;;;;;;;;;;+EAyBe,WAAW,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC;;;;;;uCAmCrC;IACZ,OAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;IACxB,OAAW,EAAE,OAAO,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CACvC;;;;;wEAUe,WAAW,CAAC,CAAC,KAAK,KAAK,QAAQ,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;AA3OxE;;GAEG;AACH,uCAFW,OAAO,SAAS,EAAE,yBAAyB;IAgB9C;;;;;;;;;;OAUG;;IAMH;;;;;;;;OAQG;;;;;;IAGH;;;;;;;;OAQG;;IAMH;;;;;;;;;;;OAWG;yIAHiB,GAAG;EAW9B"}
1
+ {"version":3,"file":"E.d.ts","sourceRoot":"","sources":["E.js"],"names":[],"mappings":";qBAoPc;;;;IAhEN;;;;;;;;;;OAUG;;IAMH;;;;;;;;OAQG;;;;;;IAGH;;;;;;;;OAQG;;IAMH;;;;;;;;;;;OAWG;;EAYoB;;;;;yDAQlB,KAAK,KAAK,GAAG,OAAO,SAAS,EAAE,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;;;;;0BAQ9D,KAAK,CAAC,EAAE,aAAa,CAAC,EAAE,OAAO,SAAS,EAAE,QAAQ,CAAC,CAAC;sBAMpD,YAAY,CAAC,CAAC,GAAG,CAAC;yHAQZ,WAAW,CAAC,CAAC,KAAK,QAAQ,QAAQ,WAAW,CAAC,CAAC,CAAC,CAAC;;;gFAwB7C,WAAW,CAAC,CAAC,KAAK,QAAQ,IAAI,CAAC;;4CAczC,CACZ,CAAK,SAAS,OAAO,SAAS,EAAE,QAAQ,GAChC,kBAAkB,CAAC,CAAC,GAAG,iBAAiB,SAAS,CAAC,CAAC,CAAC,GACpD,iBAAiB,SAAS,CAAC,CAAC,CAAC,CAClC;oCAKS,CACZ,CAAK,SAAS,OAAO,SAAS,EAAE,QAAQ,GAChC,UAAU,CAAC,CAAC,GAAG,SAAS,SAAS,CAAC,CAAC,CAAC,GACpC,SAAS,SAAS,CAAC,CAAC,CAAC,CAC1B;;;;;;;;;;;;;;;;+EAyBe,WAAW,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC;;;;;;uCAmCrC;IACZ,OAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;IACxB,OAAW,EAAE,OAAO,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CACvC;;;;;wEAUe,WAAW,CAAC,CAAC,KAAK,KAAK,QAAQ,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;AA3OxE;;GAEG;AACH,uCAFW,OAAO,SAAS,EAAE,yBAAyB;IAgB9C;;;;;;;;;;OAUG;;IAMH;;;;;;;;OAQG;;;;;;IAGH;;;;;;;;OAQG;;IAMH;;;;;;;;;;;OAWG;yIAHiB,GAAG;EAW9B"}
package/src/E.js CHANGED
@@ -1,8 +1,11 @@
1
1
  import { trackTurns } from './track-turns.js';
2
+ import { makeMessageBreakpointTester } from './message-breakpoints.js';
2
3
 
3
4
  const { details: X, quote: q, Fail } = assert;
4
5
  const { assign, create } = Object;
5
6
 
7
+ const onSend = makeMessageBreakpointTester('ENDO_SEND_BREAKPOINTS');
8
+
6
9
  /** @type {ProxyHandler<any>} */
7
10
  const baseFreezableProxyHandler = {
8
11
  set(_target, _prop, _value) {
@@ -31,38 +34,55 @@ const baseFreezableProxyHandler = {
31
34
  /**
32
35
  * A Proxy handler for E(x).
33
36
  *
34
- * @param {any} x Any value passed to E(x)
37
+ * @param {any} recipient Any value passed to E(x)
35
38
  * @param {import('./types').HandledPromiseConstructor} HandledPromise
36
39
  * @returns {ProxyHandler} the Proxy handler
37
40
  */
38
- const makeEProxyHandler = (x, HandledPromise) =>
41
+ const makeEProxyHandler = (recipient, HandledPromise) =>
39
42
  harden({
40
43
  ...baseFreezableProxyHandler,
41
- get: (_target, p, receiver) => {
44
+ get: (_target, propertyKey, receiver) => {
42
45
  return harden(
43
46
  {
44
47
  // This function purposely checks the `this` value (see above)
45
48
  // In order to be `this` sensitive it is defined using concise method
46
49
  // syntax rather than as an arrow function. To ensure the function
47
50
  // is not constructable, it also avoids the `function` syntax.
48
- [p](...args) {
51
+ [propertyKey](...args) {
49
52
  if (this !== receiver) {
50
53
  // Reject the async function call
51
54
  return HandledPromise.reject(
52
55
  assert.error(
53
- X`Unexpected receiver for "${p}" method of E(${q(x)})`,
56
+ X`Unexpected receiver for "${propertyKey}" method of E(${q(
57
+ recipient,
58
+ )})`,
54
59
  ),
55
60
  );
56
61
  }
57
62
 
58
- return HandledPromise.applyMethod(x, p, args);
63
+ if (onSend && onSend.shouldBreakpoint(recipient, propertyKey)) {
64
+ // eslint-disable-next-line no-debugger
65
+ debugger; // LOOK UP THE STACK
66
+ // Stopped at a breakpoint on eventual-send of a method-call
67
+ // message,
68
+ // so that you can walk back on the stack to see how we came to
69
+ // make this eventual-send
70
+ }
71
+ return HandledPromise.applyMethod(recipient, propertyKey, args);
59
72
  },
60
73
  // @ts-expect-error https://github.com/microsoft/TypeScript/issues/50319
61
- }[p],
74
+ }[propertyKey],
62
75
  );
63
76
  },
64
77
  apply: (_target, _thisArg, argArray = []) => {
65
- return HandledPromise.applyFunction(x, argArray);
78
+ if (onSend && onSend.shouldBreakpoint(recipient, undefined)) {
79
+ // eslint-disable-next-line no-debugger
80
+ debugger; // LOOK UP THE STACK
81
+ // Stopped at a breakpoint on eventual-send of a function-call message,
82
+ // so that you can walk back on the stack to see how we came to
83
+ // make this eventual-send
84
+ }
85
+ return HandledPromise.applyFunction(recipient, argArray);
66
86
  },
67
87
  has: (_target, _p) => {
68
88
  // We just pretend everything exists.
@@ -74,35 +94,50 @@ const makeEProxyHandler = (x, HandledPromise) =>
74
94
  * A Proxy handler for E.sendOnly(x)
75
95
  * It is a variant on the E(x) Proxy handler.
76
96
  *
77
- * @param {any} x Any value passed to E.sendOnly(x)
97
+ * @param {any} recipient Any value passed to E.sendOnly(x)
78
98
  * @param {import('./types').HandledPromiseConstructor} HandledPromise
79
99
  * @returns {ProxyHandler} the Proxy handler
80
100
  */
81
- const makeESendOnlyProxyHandler = (x, HandledPromise) =>
101
+ const makeESendOnlyProxyHandler = (recipient, HandledPromise) =>
82
102
  harden({
83
103
  ...baseFreezableProxyHandler,
84
- get: (_target, p, receiver) => {
104
+ get: (_target, propertyKey, receiver) => {
85
105
  return harden(
86
106
  {
87
107
  // This function purposely checks the `this` value (see above)
88
108
  // In order to be `this` sensitive it is defined using concise method
89
109
  // syntax rather than as an arrow function. To ensure the function
90
110
  // is not constructable, it also avoids the `function` syntax.
91
- [p](...args) {
111
+ [propertyKey](...args) {
92
112
  // Throw since the function returns nothing
93
113
  this === receiver ||
94
- Fail`Unexpected receiver for "${q(p)}" method of E.sendOnly(${q(
95
- x,
96
- )})`;
97
- HandledPromise.applyMethodSendOnly(x, p, args);
114
+ Fail`Unexpected receiver for "${q(
115
+ propertyKey,
116
+ )}" method of E.sendOnly(${q(recipient)})`;
117
+ if (onSend && onSend.shouldBreakpoint(recipient, propertyKey)) {
118
+ // eslint-disable-next-line no-debugger
119
+ debugger; // LOOK UP THE STACK
120
+ // Stopped at a breakpoint on eventual-send of a method-call
121
+ // message,
122
+ // so that you can walk back on the stack to see how we came to
123
+ // make this eventual-send
124
+ }
125
+ HandledPromise.applyMethodSendOnly(recipient, propertyKey, args);
98
126
  return undefined;
99
127
  },
100
128
  // @ts-expect-error https://github.com/microsoft/TypeScript/issues/50319
101
- }[p],
129
+ }[propertyKey],
102
130
  );
103
131
  },
104
132
  apply: (_target, _thisArg, argsArray = []) => {
105
- HandledPromise.applyFunctionSendOnly(x, argsArray);
133
+ if (onSend && onSend.shouldBreakpoint(recipient, undefined)) {
134
+ // eslint-disable-next-line no-debugger
135
+ debugger; // LOOK UP THE STACK
136
+ // Stopped at a breakpoint on eventual-send of a function-call message,
137
+ // so that you can walk back on the stack to see how we came to
138
+ // make this eventual-send
139
+ }
140
+ HandledPromise.applyFunctionSendOnly(recipient, argsArray);
106
141
  return undefined;
107
142
  },
108
143
  has: (_target, _p) => {
package/src/local.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export function getMethodNames(val: any): (string | symbol)[];
2
- export function localApplyFunction(t: any, args: any): any;
3
- export function localApplyMethod(t: any, method: any, args: any): any;
2
+ export function localApplyFunction(recipient: any, args: any): any;
3
+ export function localApplyMethod(recipient: any, methodName: any, args: any): any;
4
4
  export function localGet(t: any, key: any): any;
5
5
  //# sourceMappingURL=local.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"local.d.ts","sourceRoot":"","sources":["local.js"],"names":[],"mappings":"AA0CO,oCAHI,GAAG,GACD,CAAC,MAAM,GAAC,MAAM,CAAC,EAAE,CAqB7B;AAKM,2DAON;AAEM,sEAwBN;AAEM,gDAAmC"}
1
+ {"version":3,"file":"local.d.ts","sourceRoot":"","sources":["local.js"],"names":[],"mappings":"AA8CO,oCAHI,GAAG,GACD,CAAC,MAAM,GAAC,MAAM,CAAC,EAAE,CAqB7B;AAKM,mEAkBN;AAEM,kFAqCN;AAEM,gDAAmC"}
package/src/local.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { makeMessageBreakpointTester } from './message-breakpoints.js';
2
+
1
3
  const { details: X, quote: q, Fail } = assert;
2
4
 
3
5
  const { getOwnPropertyDescriptors, getPrototypeOf, freeze } = Object;
@@ -5,6 +7,8 @@ const { apply, ownKeys } = Reflect;
5
7
 
6
8
  const ntypeof = specimen => (specimen === null ? 'null' : typeof specimen);
7
9
 
10
+ const onDelivery = makeMessageBreakpointTester('ENDO_DELIVERY_BREAKPOINTS');
11
+
8
12
  /**
9
13
  * TODO Consolidate with `isObject` that's currently in `@endo/marshal`
10
14
  *
@@ -64,39 +68,63 @@ export const getMethodNames = val => {
64
68
  // ses creates `harden`, and so cannot rely on `harden` at top level.
65
69
  freeze(getMethodNames);
66
70
 
67
- export const localApplyFunction = (t, args) => {
68
- typeof t === 'function' ||
71
+ export const localApplyFunction = (recipient, args) => {
72
+ typeof recipient === 'function' ||
69
73
  assert.fail(
70
- X`Cannot invoke target as a function; typeof target is ${q(ntypeof(t))}`,
74
+ X`Cannot invoke target as a function; typeof target is ${q(
75
+ ntypeof(recipient),
76
+ )}`,
71
77
  TypeError,
72
78
  );
73
- return apply(t, undefined, args);
79
+ if (onDelivery && onDelivery.shouldBreakpoint(recipient, undefined)) {
80
+ // eslint-disable-next-line no-debugger
81
+ debugger; // STEP INTO APPLY
82
+ // Stopped at a breakpoint on this delivery of an eventual function call
83
+ // so that you can step *into* the following `apply` in order to see the
84
+ // function call as it happens. Or step *over* to see what happens
85
+ // after the function call returns.
86
+ }
87
+ const result = apply(recipient, undefined, args);
88
+ return result;
74
89
  };
75
90
 
76
- export const localApplyMethod = (t, method, args) => {
77
- if (method === undefined || method === null) {
91
+ export const localApplyMethod = (recipient, methodName, args) => {
92
+ if (methodName === undefined || methodName === null) {
78
93
  // Base case; bottom out to apply functions.
79
- return localApplyFunction(t, args);
94
+ return localApplyFunction(recipient, args);
80
95
  }
81
- if (t === undefined || t === null) {
96
+ if (recipient === undefined || recipient === null) {
82
97
  assert.fail(
83
- X`Cannot deliver ${q(method)} to target; typeof target is ${q(
84
- ntypeof(t),
98
+ X`Cannot deliver ${q(methodName)} to target; typeof target is ${q(
99
+ ntypeof(recipient),
85
100
  )}`,
86
101
  TypeError,
87
102
  );
88
103
  }
89
- const fn = t[method];
104
+ const fn = recipient[methodName];
90
105
  if (fn === undefined) {
91
106
  assert.fail(
92
- X`target has no method ${q(method)}, has ${q(getMethodNames(t))}`,
107
+ X`target has no method ${q(methodName)}, has ${q(
108
+ getMethodNames(recipient),
109
+ )}`,
93
110
  TypeError,
94
111
  );
95
112
  }
96
113
  const ftype = ntypeof(fn);
97
114
  typeof fn === 'function' ||
98
- Fail`invoked method ${q(method)} is not a function; it is a ${q(ftype)}`;
99
- return apply(fn, t, args);
115
+ Fail`invoked method ${q(methodName)} is not a function; it is a ${q(
116
+ ftype,
117
+ )}`;
118
+ if (onDelivery && onDelivery.shouldBreakpoint(recipient, methodName)) {
119
+ // eslint-disable-next-line no-debugger
120
+ debugger; // STEP INTO APPLY
121
+ // Stopped at a breakpoint on this delivery of an eventual method call
122
+ // so that you can step *into* the following `apply` in order to see the
123
+ // method call as it happens. Or step *over* to see what happens
124
+ // after the method call returns.
125
+ }
126
+ const result = apply(fn, recipient, args);
127
+ return result;
100
128
  };
101
129
 
102
130
  export const localGet = (t, key) => t[key];
@@ -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":"AAyFO,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,179 @@
1
+ import { getEnvironmentOption } from '@endo/env-options';
2
+
3
+ const { quote: q, Fail } = assert;
4
+
5
+ const { hasOwn, freeze, entries } = Object;
6
+
7
+ /**
8
+ * @typedef {string | '*'} MatchStringTag
9
+ * A star `'*'` matches any recipient. Otherwise, the string is
10
+ * matched against the value of a recipient's `@@toStringTag`
11
+ * after stripping out any leading `'Alleged: '` or `'DebugName: '`
12
+ * prefix. For objects defined with `Far` this is the first argument,
13
+ * known as the `farName`. For exos, this is the tag.
14
+ */
15
+ /**
16
+ * @typedef {string | '*'} MatchMethodName
17
+ * A star `'*'` matches any method name. Otherwise, the string is
18
+ * matched against the method name. Currently, this is only an exact match.
19
+ * However, beware that we may introduce a string syntax for
20
+ * symbol method names.
21
+ */
22
+ /**
23
+ * @typedef {number | '*'} MatchCountdown
24
+ * A star `'*'` will always breakpoint. Otherwise, the string
25
+ * must be a non-negative integer. Once that is zero, always breakpoint.
26
+ * Otherwise decrement by one each time it matches until it reaches zero.
27
+ * In other words, the countdown represents the number of
28
+ * breakpoint occurrences to skip before actually breakpointing.
29
+ */
30
+
31
+ /**
32
+ * This is the external JSON representation, in which
33
+ * - the outer property name is the class-like tag or '*',
34
+ * - the inner property name is the method name or '*',
35
+ * - the value is a non-negative integer countdown or '*'.
36
+ *
37
+ * @typedef {Record<MatchStringTag, Record<MatchMethodName, MatchCountdown>>} MessageBreakpoints
38
+ */
39
+
40
+ /**
41
+ * This is the internal JSON representation, in which
42
+ * - the outer property name is the method name or '*',
43
+ * - the inner property name is the class-like tag or '*',
44
+ * - the value is a non-negative integer countdown or '*'.
45
+ *
46
+ * @typedef {Record<MatchMethodName, Record<MatchStringTag, MatchCountdown>>} BreakpointTable
47
+ */
48
+
49
+ /**
50
+ * @typedef {object} MessageBreakpointTester
51
+ * @property {() => MessageBreakpoints} getBreakpoints
52
+ * @property {(newBreakpoints?: MessageBreakpoints) => void} setBreakpoints
53
+ * @property {(
54
+ * recipient: object,
55
+ * methodName: string | symbol | undefined
56
+ * ) => boolean} shouldBreakpoint
57
+ */
58
+
59
+ /**
60
+ * @param {any} val
61
+ * @returns {val is Record<string, any>}
62
+ */
63
+ const isJSONRecord = val =>
64
+ typeof val === 'object' && val !== null && !Array.isArray(val);
65
+
66
+ /**
67
+ * Return `tag` after stripping off any `'Alleged: '` or `'DebugName: '`
68
+ * prefix if present.
69
+ * ```js
70
+ * simplifyTag('Alleged: moola issuer') === 'moola issuer'
71
+ * ```
72
+ * If there are multiple such prefixes, only the outer one is removed.
73
+ *
74
+ * @param {string} tag
75
+ * @returns {string}
76
+ */
77
+ const simplifyTag = tag => {
78
+ for (const prefix of ['Alleged: ', 'DebugName: ']) {
79
+ if (tag.startsWith(prefix)) {
80
+ return tag.slice(prefix.length);
81
+ }
82
+ }
83
+ return tag;
84
+ };
85
+
86
+ /**
87
+ * @param {string} optionName
88
+ * @returns {MessageBreakpointTester | undefined}
89
+ */
90
+ export const makeMessageBreakpointTester = optionName => {
91
+ let breakpoints = JSON.parse(getEnvironmentOption(optionName, 'null'));
92
+
93
+ if (breakpoints === null) {
94
+ return undefined;
95
+ }
96
+
97
+ /** @type {BreakpointTable} */
98
+ let breakpointsTable;
99
+
100
+ const getBreakpoints = () => breakpoints;
101
+ freeze(getBreakpoints);
102
+
103
+ const setBreakpoints = (newBreakpoints = breakpoints) => {
104
+ isJSONRecord(newBreakpoints) ||
105
+ Fail`Expected ${q(optionName)} option to be a JSON breakpoints record`;
106
+
107
+ /** @type {BreakpointTable} */
108
+ // @ts-expect-error confused by __proto__
109
+ const newBreakpointsTable = { __proto__: null };
110
+
111
+ for (const [tag, methodBPs] of entries(newBreakpoints)) {
112
+ tag === simplifyTag(tag) ||
113
+ Fail`Just use simple tag ${q(simplifyTag(tag))} rather than ${q(tag)}`;
114
+ isJSONRecord(methodBPs) ||
115
+ Fail`Expected ${q(optionName)} option's ${q(
116
+ tag,
117
+ )} to be a JSON methods breakpoints record`;
118
+ for (const [methodName, count] of entries(methodBPs)) {
119
+ count === '*' ||
120
+ (typeof count === 'number' &&
121
+ Number.isSafeInteger(count) &&
122
+ count >= 0) ||
123
+ Fail`Expected ${q(optionName)} option's ${q(tag)}.${q(
124
+ methodName,
125
+ )} to be "*" or a non-negative integer`;
126
+
127
+ const classBPs = hasOwn(newBreakpointsTable, methodName)
128
+ ? newBreakpointsTable[methodName]
129
+ : (newBreakpointsTable[methodName] = {
130
+ // @ts-expect-error confused by __proto__
131
+ __proto__: null,
132
+ });
133
+ classBPs[tag] = count;
134
+ }
135
+ }
136
+ breakpoints = newBreakpoints;
137
+ breakpointsTable = newBreakpointsTable;
138
+ };
139
+ freeze(setBreakpoints);
140
+
141
+ const shouldBreakpoint = (recipient, methodName) => {
142
+ if (methodName === undefined || methodName === null) {
143
+ // TODO enable function breakpointing
144
+ return false;
145
+ }
146
+ const classBPs = breakpointsTable[methodName] || breakpointsTable['*'];
147
+ if (classBPs === undefined) {
148
+ return false;
149
+ }
150
+ let tag = simplifyTag(recipient[Symbol.toStringTag]);
151
+ let count = classBPs[tag];
152
+ if (count === undefined) {
153
+ tag = '*';
154
+ count = classBPs[tag];
155
+ if (count === undefined) {
156
+ return false;
157
+ }
158
+ }
159
+ if (count === '*') {
160
+ return true;
161
+ }
162
+ if (count === 0) {
163
+ return true;
164
+ }
165
+ assert(typeof count === 'number' && count >= 1);
166
+ classBPs[tag] = count - 1;
167
+ return false;
168
+ };
169
+ freeze(shouldBreakpoint);
170
+
171
+ const breakpointTester = freeze({
172
+ getBreakpoints,
173
+ setBreakpoints,
174
+ shouldBreakpoint,
175
+ });
176
+ breakpointTester.setBreakpoints();
177
+ return breakpointTester;
178
+ };
179
+ freeze(makeMessageBreakpointTester);
@@ -1 +1 @@
1
- {"version":3,"file":"track-turns.d.ts","sourceRoot":"","sources":["track-turns.js"],"names":[],"mappings":"AA4FO,mEAiBN;;;;;uCAMuB,GAAG,EAAE,KAAK,GAAG"}
1
+ {"version":3,"file":"track-turns.d.ts","sourceRoot":"","sources":["track-turns.js"],"names":[],"mappings":"AAwFO,mEAiBN;;;;;uCAMuB,GAAG,EAAE,KAAK,GAAG"}
@@ -1,7 +1,8 @@
1
1
  /* global globalThis */
2
- import { makeEnvironmentCaptor } from '@endo/env-options';
3
-
4
- const { getEnvironmentOption } = makeEnvironmentCaptor(globalThis);
2
+ import {
3
+ getEnvironmentOption,
4
+ environmentOptionsListHas,
5
+ } from '@endo/env-options';
5
6
 
6
7
  // NOTE: We can't import these because they're not in scope before lockdown.
7
8
  // import { assert, details as X, Fail } from '@agoric/assert';
@@ -17,18 +18,13 @@ let hiddenPriorError;
17
18
  let hiddenCurrentTurn = 0;
18
19
  let hiddenCurrentEvent = 0;
19
20
 
20
- const DEBUG = getEnvironmentOption('DEBUG', '');
21
-
22
21
  // Turn on if you seem to be losing error logging at the top of the event loop
23
- const VERBOSE = DEBUG.split(':').includes('track-turns');
22
+ const VERBOSE = environmentOptionsListHas('DEBUG', 'track-turns');
24
23
 
25
24
  // Track-turns is disabled by default and can be enabled by an environment
26
25
  // option.
27
- const TRACK_TURNS = getEnvironmentOption('TRACK_TURNS', 'disabled');
28
- if (TRACK_TURNS !== 'enabled' && TRACK_TURNS !== 'disabled') {
29
- throw TypeError(`unrecognized TRACK_TURNS ${JSON.stringify(TRACK_TURNS)}`);
30
- }
31
- const ENABLED = (TRACK_TURNS || 'disabled') === 'enabled';
26
+ const ENABLED =
27
+ getEnvironmentOption('TRACK_TURNS', 'disabled', ['enabled']) === 'enabled';
32
28
 
33
29
  // We hoist the following functions out of trackTurns() to discourage the
34
30
  // closures from holding onto 'args' or 'func' longer than necessary,
package/utils.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { getMethodNames } from "./src/local.js";
2
+ export { makeMessageBreakpointTester } from "./src/message-breakpoints.js";
2
3
  //# sourceMappingURL=utils.d.ts.map
package/utils.js CHANGED
@@ -1 +1,2 @@
1
1
  export { getMethodNames } from './src/local.js';
2
+ export { makeMessageBreakpointTester } from './src/message-breakpoints.js';