redux-pixies
Version:
The magical asynchronous Redux library
775 lines (741 loc) • 21.9 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
function makePixieShutdownError() {
var e = new Error('Pixie has been destroyed');
e.name = 'PixieShutdownError';
return e;
}
function isPixieShutdownError(e) {
return e instanceof Error && e.name === 'PixieShutdownError';
}
/**
* If a wild pixie returns a bare function, turn that into a proper object.
*/
function fixInstance(instance) {
if (typeof instance === 'function') {
return {
update: instance,
destroy: function destroy() {}
};
}
return instance;
}
/**
* Catches synchronous errors and sends them through `onError`,
* terminating the inner pixie in response. Also prevents `update`
* from running in parallel if if returns a promise.
*/
function babysitPixie(wildPixie) {
function outPixie(input) {
var instance;
var propsCache;
var propsDirty = true;
var updating = false;
var destroyed = false;
var nextPromise;
var rejector;
var resolver;
function destroy() {
if (instance) {
try {
if (rejector) {
var _copy = rejector;
nextPromise = undefined;
rejector = undefined;
resolver = undefined;
_copy(makePixieShutdownError());
}
var copy = instance;
instance = undefined;
copy.destroy();
} catch (e) {
onError(e);
}
destroyed = true;
}
}
// Ignore any callbacks once `destroy` has completed:
function onError(e) {
if (!destroyed) input.onError(e);
destroy();
}
function onOutput(data) {
if (!destroyed) input.onOutput(data);
}
function onUpdateDone() {
updating = false;
tryUpdate();
}
function tryUpdate() {
// eslint-disable-next-line no-unmodified-loop-condition
while (instance && propsDirty && !updating) {
propsDirty = false;
updating = true;
try {
var thenable = instance.update(propsCache);
if (thenable && typeof thenable.then === 'function') {
thenable.then(onUpdateDone, onError);
} else {
updating = false;
}
} catch (e) {
onError(e);
}
}
}
function getNextPromise() {
if (!nextPromise) {
nextPromise = new Promise(function (resolve, reject) {
resolver = resolve;
rejector = reject;
});
}
return nextPromise;
}
var childInput = {
onError: onError,
onOutput: onOutput,
get props() {
return propsCache;
},
nextProps: getNextPromise,
waitFor: function waitFor(condition) {
return new Promise(function (resolve, reject) {
function checkProps() {
try {
var result = condition(propsCache);
if (result != null) resolve(result);else getNextPromise().then(checkProps, reject);
} catch (e) {
reject(e);
}
}
return checkProps();
});
}
};
try {
instance = fixInstance(wildPixie(childInput));
} catch (e) {
onError(e);
}
return {
update: function update(props) {
propsCache = props;
propsDirty = true;
// Update the `nextProps` promise right away:
if (resolver) {
var copy = resolver;
nextPromise = undefined;
rejector = undefined;
resolver = undefined;
copy(props);
}
tryUpdate();
},
destroy: destroy
};
}
outPixie.tame = true;
outPixie.defaultOutput = wildPixie.defaultOutput;
return outPixie;
}
/**
* Accepts a hand-written reducer, and hardens it with error checking.
*/
function tamePixie(pixie) {
return pixie.tame ? pixie : babysitPixie(pixie);
}
function defaultErrorHandler(e, props, onError) {
onError(e);
}
/**
* Intercepts `onError`, shutting down the inner pixie.
*/
function catchPixieError(pixie) {
var errorHandler = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultErrorHandler;
var tamedPixie = tamePixie(pixie);
function outPixie(input) {
var onOutput = input.onOutput;
var instance;
var propsCache;
var destroy = function destroy() {
var copy = instance;
instance = undefined;
if (copy) copy.destroy();
};
function onError(e) {
destroy();
try {
errorHandler(e, propsCache, input.onError);
} catch (e) {
input.onError(e);
}
}
var childInput = {
onError: onError,
onOutput: onOutput
};
return {
update: function update(props) {
propsCache = props;
if (!instance) instance = tamedPixie(childInput);
instance.update(props);
},
destroy: destroy
};
}
outPixie.tame = true;
outPixie.defaultOutput = pixie.defaultOutput;
return outPixie;
}
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
enumerableOnly && (symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
})), keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = null != arguments[i] ? arguments[i] : {};
i % 2 ? ownKeys(Object(source), !0).forEach(function (key) {
_defineProperty(target, key, source[key]);
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
return target;
}
function _typeof(obj) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
}, _typeof(obj);
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
Object.defineProperty(Constructor, "prototype", {
writable: false
});
return Constructor;
}
function _defineProperty(obj, key, value) {
key = _toPropertyKey(key);
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _createForOfIteratorHelper(o, allowArrayLike) {
var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
if (!it) {
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
if (it) o = it;
var i = 0;
var F = function () {};
return {
s: F,
n: function () {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
},
e: function (e) {
throw e;
},
f: F
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var normalCompletion = true,
didErr = false,
err;
return {
s: function () {
it = it.call(o);
},
n: function () {
var step = it.next();
normalCompletion = step.done;
return step;
},
e: function (e) {
didErr = true;
err = e;
},
f: function () {
try {
if (!normalCompletion && it.return != null) it.return();
} finally {
if (didErr) throw err;
}
}
};
}
function _toPrimitive(input, hint) {
if (typeof input !== "object" || input === null) return input;
var prim = input[Symbol.toPrimitive];
if (prim !== undefined) {
var res = prim.call(input, hint || "default");
if (typeof res !== "object") return res;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return (hint === "string" ? String : Number)(input);
}
function _toPropertyKey(arg) {
var key = _toPrimitive(arg, "string");
return typeof key === "symbol" ? key : String(key);
}
/**
* Copies the `output` back into the pixie as a prop.
*/
function reflectPixieOutput(pixie) {
var tamedPixie = tamePixie(pixie);
function outPixie(input) {
var onError = input.onError;
var instance;
var output = pixie.defaultOutput;
var propsCache;
var propsDirty = true;
var updating = false;
var tryUpdate = function tryUpdate() {
// The `update` function can call `onOutput` or `onError`, so loop:
// eslint-disable-next-line no-unmodified-loop-condition
while (instance && propsDirty && !updating) {
propsDirty = false;
updating = true;
instance.update(_objectSpread2(_objectSpread2({}, propsCache), {}, {
output: output
}));
updating = false;
}
};
var onOutput = function onOutput(data) {
if (data !== output) {
output = data;
propsDirty = true;
input.onOutput(data);
tryUpdate();
}
};
var childInput = {
onError: onError,
onOutput: onOutput
};
return {
update: function update(props) {
propsCache = props;
propsDirty = true;
if (!instance) instance = tamedPixie(childInput);
tryUpdate();
},
destroy: function destroy() {
var copy = instance;
instance = undefined;
if (copy) copy.destroy();
}
};
}
outPixie.tame = true;
outPixie.defaultOutput = pixie.defaultOutput;
return outPixie;
}
function defaultOnError(e) {}
function defaultOnOutput(data) {}
/**
* Instantiates a pixie object.
*/
function startPixie(pixie) {
var onError = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : defaultOnError;
var onOutput = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : defaultOnOutput;
return catchPixieError(reflectPixieOutput(pixie))({
onError: onError,
onOutput: onOutput
});
}
/**
* Instantiates a pixie object and attaches it to a redux store.
*/
function attachPixie(store, pixie, onError, onOutput) {
var instance = startPixie(pixie, onError, onOutput);
instance.update({
dispatch: store.dispatch,
output: undefined,
state: store.getState()
});
var unsubscribe = store.subscribe(function () {
instance.update({
dispatch: store.dispatch,
output: undefined,
state: store.getState()
});
});
return function () {
unsubscribe();
instance.destroy();
};
}
/**
* Combines one or more pixies into one.
*/
function combinePixies(pixieMap) {
var defaultOutput = {};
for (var _i = 0, _Object$keys = Object.keys(pixieMap); _i < _Object$keys.length; _i++) {
var _id = _Object$keys[_i];
defaultOutput[_id] = pixieMap[_id].defaultOutput;
}
function outPixie(input) {
var onError = input.onError;
var childInputs = {};
var instances = {};
var outputs = _objectSpread2({}, defaultOutput);
var destroyed = false;
var _loop = function _loop() {
var id = _Object$keys2[_i2];
var onOutput = function onOutput(data) {
if (data !== outputs[id]) {
outputs = _objectSpread2({}, outputs);
outputs[id] = data;
input.onOutput(outputs);
}
};
childInputs[id] = {
onError: onError,
onOutput: onOutput
};
instances[id] = tamePixie(pixieMap[id])(childInputs[id]);
if (destroyed) return "break";
};
for (var _i2 = 0, _Object$keys2 = Object.keys(pixieMap); _i2 < _Object$keys2.length; _i2++) {
var _ret = _loop();
if (_ret === "break") break;
}
return {
update: function update(props) {
for (var _i3 = 0, _Object$keys3 = Object.keys(instances); _i3 < _Object$keys3.length; _i3++) {
var _id2 = _Object$keys3[_i3];
instances[_id2].update(props);
if (destroyed) return;
}
},
destroy: function destroy() {
destroyed = true;
for (var _i4 = 0, _Object$keys4 = Object.keys(instances); _i4 < _Object$keys4.length; _i4++) {
var _id3 = _Object$keys4[_i4];
instances[_id3].destroy();
}
}
};
}
outPixie.tame = true;
outPixie.defaultOutput = defaultOutput;
return outPixie;
}
/**
* If the function throws, send that through `onError`.
*/
function catchify(f, onError) {
return function () {
try {
return f.apply(void 0, arguments);
} catch (e) {
onError(e);
}
};
}
/**
* Returns true if two Javascript values are equal (non-recursively).
*/
function shallowCompare(a, b) {
if (a === b) return true;
// Fast path for primitives:
if (_typeof(a) !== 'object') return false;
if (_typeof(b) !== 'object') return false;
// Filter out `null`:
if (!a || !b) return false;
var keys = Object.getOwnPropertyNames(a);
if (keys.length !== Object.getOwnPropertyNames(b).length) return false;
// We know that both objects have the same number of properties,
// so if every property in `a` has a matching property in `b`,
// the objects must be identical, regardless of key order.
var _iterator = _createForOfIteratorHelper(keys),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var key = _step.value;
if (!Object.prototype.hasOwnProperty.call(b, key) || a[key] !== b[key]) {
return false;
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
return true;
}
/**
* Filters the props going into a pixie.
*/
function filterPixie(pixie, filter) {
var tamedPixie = tamePixie(pixie);
function outPixie(input) {
var onError = input.onError,
onOutput = input.onOutput;
var instance;
var propsCache;
var destroyed = false;
var safeFilter = catchify(filter, onError);
var childInput = {
onError: onError,
onOutput: onOutput
};
return {
update: function update(props) {
var innerProps = safeFilter(props);
if (destroyed) return;
var dirty = !shallowCompare(innerProps, propsCache);
propsCache = innerProps;
// Start or stop the instance:
if (innerProps) {
if (!instance) instance = tamedPixie(childInput);
if (destroyed) return;
if (dirty) instance.update(innerProps);
} else {
if (instance) instance.destroy();
instance = undefined;
}
},
destroy: function destroy() {
destroyed = true;
if (instance) instance.destroy();
instance = undefined;
}
};
}
outPixie.tame = true;
outPixie.defaultOutput = pixie.defaultOutput;
return outPixie;
}
/**
* Combines one or more pixies into one, using a list of keys.
*/
function mapPixie(pixie, listIds, filter) {
var tamedPixie = tamePixie(pixie);
function outPixie(input) {
var onError = input.onError;
var instances = {};
var outputs = {};
var outputsDirty = false;
var propsCache = {};
var updating = false;
var destroyed = false;
var safeListIds = catchify(listIds, onError);
var safeFilter = catchify(filter, onError);
var updateOutputs = function updateOutputs() {
if (outputsDirty && !updating) {
outputsDirty = false;
var newOutputs = {};
for (var _i = 0, _Object$keys = Object.keys(instances); _i < _Object$keys.length; _i++) {
var _id = _Object$keys[_i];
newOutputs[_id] = outputs[_id];
}
outputs = newOutputs;
input.onOutput(outputs);
}
};
return {
update: function update(outerProps) {
var ids = safeListIds(outerProps);
if (destroyed || !ids) return;
// Update or create instances for all keys:
updating = true;
var newInstances = {};
var _iterator = _createForOfIteratorHelper(ids),
_step;
try {
var _loop = function _loop() {
var id = _step.value;
var innerProps = safeFilter(outerProps, id);
if (destroyed) return {
v: void 0
};
var dirty = !shallowCompare(innerProps, propsCache[id]);
propsCache[id] = innerProps;
if (innerProps) {
if (!instances[id]) {
var onOutput = function onOutput(data) {
if (data !== outputs[id]) {
outputs[id] = data;
outputsDirty = true;
updateOutputs();
}
};
instances[id] = tamedPixie({
onError: onError,
onOutput: onOutput
});
if (destroyed) return {
v: void 0
};
}
if (dirty) instances[id].update(innerProps);
if (destroyed) return {
v: void 0
};
newInstances[id] = instances[id];
}
};
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var _ret = _loop();
if (_typeof(_ret) === "object") return _ret.v;
}
// Swap out the instance list, removing unwanted pixies:
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
var oldInstances = instances;
instances = newInstances;
// Destroy any old instances that are not on the list.
// We need to finish this even if it triggers `destroyed`:
for (var _i2 = 0, _Object$keys2 = Object.keys(oldInstances); _i2 < _Object$keys2.length; _i2++) {
var _id2 = _Object$keys2[_i2];
if (!newInstances[_id2]) {
outputsDirty = true;
oldInstances[_id2].destroy();
}
}
if (destroyed) return;
updating = false;
updateOutputs();
},
destroy: function destroy() {
destroyed = true;
for (var _i3 = 0, _Object$keys3 = Object.keys(instances); _i3 < _Object$keys3.length; _i3++) {
var _id3 = _Object$keys3[_i3];
if (instances[_id3]) instances[_id3].destroy();
}
}
};
}
outPixie.tame = true;
outPixie.defaultOutput = {};
return outPixie;
}
/**
* Class-style pixies should inherit from this type.
*/
var Pixie = /*#__PURE__*/function () {
function Pixie(props, callbacks) {
_classCallCheck(this, Pixie);
this.props = props;
}
/**
* Called every time the props change.
*/
_createClass(Pixie, [{
key: "update",
value: function update(props, callbacks) {}
/**
* Called before the pixie is destroyed.
* This is a great place to clean up any resources.
*/
}, {
key: "destroy",
value: function destroy(props, callbacks) {}
}]);
return Pixie;
}();
/**
* Turns a class-style pixie into a tame pixie.
*/
function tameClassPixie(Constructor) {
return tamePixie(function (_ref) {
var onError = _ref.onError,
onOutput = _ref.onOutput;
var callbacks = {
onError: onError,
onOutput: onOutput
};
var instance;
var propsCache;
return {
update: function update(props) {
propsCache = props;
if (!instance) {
instance = new Constructor(props, callbacks);
}
return instance.update(props, callbacks);
},
destroy: function destroy() {
return instance.destroy(propsCache, callbacks);
}
};
});
}
/**
* Update functions can return this to stop all future updates.
*/
var stopUpdates = {
then: function then() {}
};
exports.Pixie = Pixie;
exports.attachPixie = attachPixie;
exports.catchPixieError = catchPixieError;
exports.combinePixies = combinePixies;
exports.filterPixie = filterPixie;
exports.isPixieShutdownError = isPixieShutdownError;
exports.mapPixie = mapPixie;
exports.reflectPixieOutput = reflectPixieOutput;
exports.startPixie = startPixie;
exports.stopUpdates = stopUpdates;
exports.tameClassPixie = tameClassPixie;
exports.tamePixie = tamePixie;
//# sourceMappingURL=redux-pixies.cjs.js.map