1 | // This currentContext variable will only be used if the makeSlotClass
|
2 | // function is called, which happens only if this is the first copy of the
|
3 | // @wry/context package to be imported.
|
4 | var currentContext = null;
|
5 | // This unique internal object is used to denote the absence of a value
|
6 | // for a given Slot, and is never exposed to outside code.
|
7 | var MISSING_VALUE = {};
|
8 | var idCounter = 1;
|
9 | // Although we can't do anything about the cost of duplicated code from
|
10 | // accidentally bundling multiple copies of the @wry/context package, we can
|
11 | // avoid creating the Slot class more than once using makeSlotClass.
|
12 | var makeSlotClass = function () { return /** @class */ (function () {
|
13 | function Slot() {
|
14 | // If you have a Slot object, you can find out its slot.id, but you cannot
|
15 | // guess the slot.id of a Slot you don't have access to, thanks to the
|
16 | // randomized suffix.
|
17 | this.id = [
|
18 | "slot",
|
19 | idCounter++,
|
20 | Date.now(),
|
21 | Math.random().toString(36).slice(2),
|
22 | ].join(":");
|
23 | }
|
24 | Slot.prototype.hasValue = function () {
|
25 | for (var context_1 = currentContext; context_1; context_1 = context_1.parent) {
|
26 | // We use the Slot object iself as a key to its value, which means the
|
27 | // value cannot be obtained without a reference to the Slot object.
|
28 | if (this.id in context_1.slots) {
|
29 | var value = context_1.slots[this.id];
|
30 | if (value === MISSING_VALUE)
|
31 | break;
|
32 | if (context_1 !== currentContext) {
|
33 | // Cache the value in currentContext.slots so the next lookup will
|
34 | // be faster. This caching is safe because the tree of contexts and
|
35 | // the values of the slots are logically immutable.
|
36 | currentContext.slots[this.id] = value;
|
37 | }
|
38 | return true;
|
39 | }
|
40 | }
|
41 | if (currentContext) {
|
42 | // If a value was not found for this Slot, it's never going to be found
|
43 | // no matter how many times we look it up, so we might as well cache
|
44 | // the absence of the value, too.
|
45 | currentContext.slots[this.id] = MISSING_VALUE;
|
46 | }
|
47 | return false;
|
48 | };
|
49 | Slot.prototype.getValue = function () {
|
50 | if (this.hasValue()) {
|
51 | return currentContext.slots[this.id];
|
52 | }
|
53 | };
|
54 | Slot.prototype.withValue = function (value, callback,
|
55 | // Given the prevalence of arrow functions, specifying arguments is likely
|
56 | // to be much more common than specifying `this`, hence this ordering:
|
57 | args, thisArg) {
|
58 | var _a;
|
59 | var slots = (_a = {
|
60 | __proto__: null
|
61 | },
|
62 | _a[this.id] = value,
|
63 | _a);
|
64 | var parent = currentContext;
|
65 | currentContext = { parent: parent, slots: slots };
|
66 | try {
|
67 | // Function.prototype.apply allows the arguments array argument to be
|
68 | // omitted or undefined, so args! is fine here.
|
69 | return callback.apply(thisArg, args);
|
70 | }
|
71 | finally {
|
72 | currentContext = parent;
|
73 | }
|
74 | };
|
75 | // Capture the current context and wrap a callback function so that it
|
76 | // reestablishes the captured context when called.
|
77 | Slot.bind = function (callback) {
|
78 | var context = currentContext;
|
79 | return function () {
|
80 | var saved = currentContext;
|
81 | try {
|
82 | currentContext = context;
|
83 | return callback.apply(this, arguments);
|
84 | }
|
85 | finally {
|
86 | currentContext = saved;
|
87 | }
|
88 | };
|
89 | };
|
90 | // Immediately run a callback function without any captured context.
|
91 | Slot.noContext = function (callback,
|
92 | // Given the prevalence of arrow functions, specifying arguments is likely
|
93 | // to be much more common than specifying `this`, hence this ordering:
|
94 | args, thisArg) {
|
95 | if (currentContext) {
|
96 | var saved = currentContext;
|
97 | try {
|
98 | currentContext = null;
|
99 | // Function.prototype.apply allows the arguments array argument to be
|
100 | // omitted or undefined, so args! is fine here.
|
101 | return callback.apply(thisArg, args);
|
102 | }
|
103 | finally {
|
104 | currentContext = saved;
|
105 | }
|
106 | }
|
107 | else {
|
108 | return callback.apply(thisArg, args);
|
109 | }
|
110 | };
|
111 | return Slot;
|
112 | }()); };
|
113 | function maybe(fn) {
|
114 | try {
|
115 | return fn();
|
116 | }
|
117 | catch (ignored) { }
|
118 | }
|
119 | // We store a single global implementation of the Slot class as a permanent
|
120 | // non-enumerable property of the globalThis object. This obfuscation does
|
121 | // nothing to prevent access to the Slot class, but at least it ensures the
|
122 | // implementation (i.e. currentContext) cannot be tampered with, and all copies
|
123 | // of the @wry/context package (hopefully just one) will share the same Slot
|
124 | // implementation. Since the first copy of the @wry/context package to be
|
125 | // imported wins, this technique imposes a steep cost for any future breaking
|
126 | // changes to the Slot class.
|
127 | var globalKey = "@wry/context:Slot";
|
128 | var host =
|
129 | // Prefer globalThis when available.
|
130 | // https://github.com/benjamn/wryware/issues/347
|
131 | maybe(function () { return globalThis; }) ||
|
132 | // Fall back to global, which works in Node.js and may be converted by some
|
133 | // bundlers to the appropriate identifier (window, self, ...) depending on the
|
134 | // bundling target. https://github.com/endojs/endo/issues/576#issuecomment-1178515224
|
135 | maybe(function () { return global; }) ||
|
136 | // Otherwise, use a dummy host that's local to this module. We used to fall
|
137 | // back to using the Array constructor as a namespace, but that was flagged in
|
138 | // https://github.com/benjamn/wryware/issues/347, and can be avoided.
|
139 | Object.create(null);
|
140 | // Whichever globalHost we're using, make TypeScript happy about the additional
|
141 | // globalKey property.
|
142 | var globalHost = host;
|
143 | var Slot = globalHost[globalKey] ||
|
144 | // Earlier versions of this package stored the globalKey property on the Array
|
145 | // constructor, so we check there as well, to prevent Slot class duplication.
|
146 | Array[globalKey] ||
|
147 | (function (Slot) {
|
148 | try {
|
149 | Object.defineProperty(globalHost, globalKey, {
|
150 | value: Slot,
|
151 | enumerable: false,
|
152 | writable: false,
|
153 | // When it was possible for globalHost to be the Array constructor (a
|
154 | // legacy Slot dedup strategy), it was important for the property to be
|
155 | // configurable:true so it could be deleted. That does not seem to be as
|
156 | // important when globalHost is the global object, but I don't want to
|
157 | // cause similar problems again, and configurable:true seems safest.
|
158 | // https://github.com/endojs/endo/issues/576#issuecomment-1178274008
|
159 | configurable: true
|
160 | });
|
161 | }
|
162 | finally {
|
163 | return Slot;
|
164 | }
|
165 | })(makeSlotClass());
|
166 |
|
167 | var bind = Slot.bind, noContext = Slot.noContext;
|
168 | function setTimeoutWithContext(callback, delay) {
|
169 | return setTimeout(bind(callback), delay);
|
170 | }
|
171 | // Turn any generator function into an async function (using yield instead
|
172 | // of await), with context automatically preserved across yields.
|
173 | function asyncFromGen(genFn) {
|
174 | return function () {
|
175 | var gen = genFn.apply(this, arguments);
|
176 | var boundNext = bind(gen.next);
|
177 | var boundThrow = bind(gen.throw);
|
178 | return new Promise(function (resolve, reject) {
|
179 | function invoke(method, argument) {
|
180 | try {
|
181 | var result = method.call(gen, argument);
|
182 | }
|
183 | catch (error) {
|
184 | return reject(error);
|
185 | }
|
186 | var next = result.done ? resolve : invokeNext;
|
187 | if (isPromiseLike(result.value)) {
|
188 | result.value.then(next, result.done ? reject : invokeThrow);
|
189 | }
|
190 | else {
|
191 | next(result.value);
|
192 | }
|
193 | }
|
194 | var invokeNext = function (value) { return invoke(boundNext, value); };
|
195 | var invokeThrow = function (error) { return invoke(boundThrow, error); };
|
196 | invokeNext();
|
197 | });
|
198 | };
|
199 | }
|
200 | function isPromiseLike(value) {
|
201 | return value && typeof value.then === "function";
|
202 | }
|
203 | // If you use the fibers npm package to implement coroutines in Node.js,
|
204 | // you should call this function at least once to ensure context management
|
205 | // remains coherent across any yields.
|
206 | var wrappedFibers = [];
|
207 | function wrapYieldingFiberMethods(Fiber) {
|
208 | // There can be only one implementation of Fiber per process, so this array
|
209 | // should never grow longer than one element.
|
210 | if (wrappedFibers.indexOf(Fiber) < 0) {
|
211 | var wrap = function (obj, method) {
|
212 | var fn = obj[method];
|
213 | obj[method] = function () {
|
214 | return noContext(fn, arguments, this);
|
215 | };
|
216 | };
|
217 | // These methods can yield, according to
|
218 | // https://github.com/laverdet/node-fibers/blob/ddebed9b8ae3883e57f822e2108e6943e5c8d2a8/fibers.js#L97-L100
|
219 | wrap(Fiber, "yield");
|
220 | wrap(Fiber.prototype, "run");
|
221 | wrap(Fiber.prototype, "throwInto");
|
222 | wrappedFibers.push(Fiber);
|
223 | }
|
224 | return Fiber;
|
225 | }
|
226 |
|
227 | export { Slot, asyncFromGen, bind, noContext, setTimeoutWithContext as setTimeout, wrapYieldingFiberMethods };
|
228 | //# sourceMappingURL=context.esm.js.map
|