UNPKG

9.84 kBJavaScriptView Raw
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.
4var 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.
7var MISSING_VALUE = {};
8var 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.
12var 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}()); };
113function 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.
127var globalKey = "@wry/context:Slot";
128var host =
129// Prefer globalThis when available.
130// https://github.com/benjamn/wryware/issues/347
131maybe(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.
142var globalHost = host;
143var 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
167var bind = Slot.bind, noContext = Slot.noContext;
168function 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.
173function 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}
200function 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.
206var wrappedFibers = [];
207function 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
227export { Slot, asyncFromGen, bind, noContext, setTimeoutWithContext as setTimeout, wrapYieldingFiberMethods };
228//# sourceMappingURL=context.esm.js.map