wana
Version:
Easy observable state for React
799 lines (783 loc) • 21.3 kB
JavaScript
'use strict';
var is = require('@alloc/is');
var symbols = require('./c9633b27.js');
var no = require('./ffee02eb.js');
var isDev = require('@alloc/is-dev');
var queueMicrotask = require('@alloc/queue-microtask');
var reactBatchedUpdates = require('react-batched-updates');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var queueMicrotask__default = /*#__PURE__*/_interopDefaultLegacy(queueMicrotask);
let runQueue = [];
const renderQueue = [];
const batch = {
run(effect) {
runQueue.push(effect);
flushAsync();
},
render(depth, effect) {
let i = renderQueue.findIndex((other) => other.depth > depth);
if (i < 0)
i = renderQueue.length;
renderQueue.splice(i, 0, {depth, effect});
flushAsync();
}
};
let isQueued = false;
function flushAsync() {
if (!isQueued) {
isQueued = true;
queueMicrotask__default['default'](() => {
isQueued = false;
if (flushSync()) {
flushAsync();
}
});
}
}
function flushSync() {
reactBatchedUpdates.batchedUpdates(() => {
let runs = 0;
for (const effect of runQueue) {
if (++runs > 1e5)
break;
effect();
}
runQueue = runQueue.slice(runs);
renderQueue.forEach(({effect}) => effect());
renderQueue.length = 0;
});
return !!(runQueue.length || renderQueue.length);
}
const {get} = Map.prototype;
function onChange(observable, key, change) {
const observers = get.call(observable, key);
if (observers) {
observers.nonce++;
observers.onChange(change);
}
}
function emit(target, change) {
no.noto(() => {
const observable = target[symbols.$O];
if (symbols.globals.onChange) {
symbols.globals.onChange(change);
}
if (change.op !== "clear") {
onChange(observable, change.key, change);
} else if (is.is.map(change.oldValue)) {
change.oldValue.forEach((_, key) => onChange(observable, key, change));
}
if (change.key !== symbols.SIZE) {
onChange(observable, symbols.$O, change);
}
});
return true;
}
const emitAdd = (target, key, value) => emit(target, {
op: "add",
target,
key,
value
});
const emitRemove = (target, key, oldValue) => emit(target, {
op: "remove",
target,
key,
oldValue
});
const emitReplace = (target, key, value, oldValue) => emit(target, {
op: "replace",
target,
key,
value,
oldValue
});
const emitSplice = (target, key, value, oldValue) => emit(target, {
op: "splice",
target,
key,
value,
oldValue
});
const emitClear = (target, oldValue) => emit(target, {
op: "clear",
target,
oldValue
});
const emitDefine = (target, key, value, oldValue) => emit(target, {
op: "define",
target,
key,
value,
oldValue
});
const observeAll = [
Symbol.iterator,
"entries",
"every",
"filter",
"find",
"findIndex",
"flat",
"flatMap",
"forEach",
"includes",
"indexOf",
"join",
"keys",
"lastIndexOf",
"map",
"reduce",
"reduceRight",
"slice",
"some",
"values"
];
const wrapIterators = (ctr) => observeAll.reduce((methods, name) => (ctr.prototype[name] && (methods[name] = function(...args) {
const self = this[symbols.$$];
symbols.observe(self, symbols.$O);
return self[name](...args);
}), methods), {});
const ArrayIterators = wrapIterators(Array);
const MapIterators = wrapIterators(Map);
const SetIterators = wrapIterators(Set);
const ArrayOverrides = {
concat(...args) {
const self = this[symbols.$$];
symbols.observe(self, symbols.$O);
args.forEach((arg) => {
arg = arg && arg[symbols.$$];
if (is.is.array(arg)) {
symbols.observe(arg, symbols.$O);
}
});
return no.noto(() => self.concat(...args));
},
copyWithin: symbols.flop,
fill: symbols.flop,
pop() {
const self = this[symbols.$$];
const oldLength = self.length;
if (oldLength) {
const index = oldLength - 1;
const exists = index in self;
const value = self.pop();
if (exists)
emitRemove(self, index, value);
emitReplace(self, symbols.SIZE, index, oldLength);
return value;
}
},
push(...values) {
const self = this[symbols.$$];
const oldLength = self.length;
if (values.length) {
const length = self.push(...values);
emitSplice(self, oldLength, values, symbols.emptyArray);
emitReplace(self, symbols.SIZE, length, oldLength);
return length;
}
return oldLength;
},
reverse() {
const self = this[symbols.$$];
const oldValue = [...self];
self.reverse();
emitSplice(self, 0, [...self], oldValue);
return this;
},
shift() {
const self = this[symbols.$$];
const oldLength = self.length;
if (oldLength) {
const exists = 0 in self;
const value = self.shift();
if (exists)
emitRemove(self, 0, value);
emitReplace(self, symbols.SIZE, oldLength - 1, oldLength);
return value;
}
},
sort(compare) {
const self = this[symbols.$$];
const oldValue = [...self];
self.sort(compare);
emitSplice(self, 0, [...self], oldValue);
return this;
},
splice(start, removeCount = 0, ...values) {
const self = this[symbols.$$];
const oldLength = self.length;
if (start < 0) {
start = Math.max(0, start + oldLength);
} else if (start > oldLength) {
start = oldLength;
}
const removed = self.splice(start, removeCount, ...values);
removeCount = removed.length;
const addCount = values.length;
if (addCount || removeCount) {
const delta = addCount - removeCount;
emitSplice(self, start, values, [...removed]);
if (delta)
emitReplace(self, symbols.SIZE, oldLength + delta, oldLength);
}
return removed;
},
unshift(...values) {
const self = this[symbols.$$];
const oldLength = self.length;
if (values.length) {
const length = self.unshift(...values);
emitSplice(self, 0, values, symbols.emptyArray);
emitReplace(self, symbols.SIZE, length, oldLength);
return length;
}
return oldLength;
}
};
const ObjectTraps = {
has: (self, key) => (symbols.observe(self, key), Reflect.has(self, key)),
get(self, key, proxy) {
if (key === symbols.$$)
return self;
if (key !== symbols.$O) {
const desc = symbols.getDescriptor(self, key);
const get = desc && desc.get;
if (get) {
return get.call(proxy);
}
if (!desc || symbols.hasOwn(self, key)) {
symbols.observe(self, key);
}
}
return self[key];
},
set(self, key, value, proxy) {
const desc = symbols.getDescriptor(self, key);
return desc && desc.get ? Reflect.set(self, key, value, proxy) : setProperty(self, key, value);
},
defineProperty(self, key, desc) {
const prevDesc = symbols.getOwnDescriptor(self, key);
return Reflect.defineProperty(self, key, desc) && (desc.get || prevDesc && prevDesc.get) ? emitDefine(self, key, desc, prevDesc) : prevDesc ? desc.value === prevDesc.value || emitReplace(self, key, desc.value, prevDesc.value) : emitAdd(self, key, desc.value);
},
deleteProperty,
preventExtensions: symbols.nope
};
const ArrayTraps = {
has: ObjectTraps.has,
get(self, key) {
if (key === symbols.$$)
return self;
if (key === "length") {
symbols.observe(self, symbols.SIZE);
return self[key];
}
return ArrayOverrides[key] || symbols.globals.observe && ArrayIterators[key] || self[key];
},
set(self, key, value) {
if (key === "length") {
const oldLength = self.length;
if (value !== oldLength) {
const oldValues = self.slice(value);
self.length = value;
emitReplace(self, symbols.SIZE, value, oldLength);
if (value < oldLength) {
emitSplice(self, value, symbols.emptyArray, oldValues);
}
}
} else {
const oldLength = self.length;
setProperty(self, key, value);
if (oldLength !== self.length) {
emitReplace(self, symbols.SIZE, self.length, oldLength);
}
}
return true;
},
deleteProperty,
preventExtensions: symbols.nope
};
class ObservableMap extends Map {
constructor(source) {
super();
symbols.setHidden(this, symbols.$$, source);
}
get [(symbols.$O)]() {
return this[symbols.$$][symbols.$O];
}
get size() {
symbols.observe(this[symbols.$$], symbols.SIZE);
return this[symbols.$$].size;
}
has(key) {
symbols.observe(this[symbols.$$], key);
return this[symbols.$$].has(key);
}
get(key) {
symbols.observe(this[symbols.$$], key);
return this[symbols.$$].get(key);
}
set(key, value) {
const exists = this[symbols.$$].has(key);
const oldValue = exists ? this[symbols.$$].get(key) : void 0;
if (!exists || value !== oldValue) {
this[symbols.$$].set(key, value);
if (exists) {
emitReplace(this[symbols.$$], key, value, oldValue);
} else {
const oldSize = this[symbols.$$].size;
emitAdd(this[symbols.$$], key, value);
emitReplace(this[symbols.$$], symbols.SIZE, oldSize + 1, oldSize);
}
}
return this;
}
delete(key) {
const oldSize = this[symbols.$$].size;
const oldValue = this[symbols.$$].get(key);
return this[symbols.$$].delete(key) && emitRemove(this[symbols.$$], key, oldValue) && emitReplace(this[symbols.$$], symbols.SIZE, oldSize - 1, oldSize);
}
clear() {
const oldSize = this[symbols.$$].size;
if (oldSize) {
const oldValues = new Map(this[symbols.$$]);
this[symbols.$$].clear();
emitClear(this[symbols.$$], oldValues);
emitReplace(this[symbols.$$], symbols.SIZE, 0, oldSize);
}
}
}
class ObservableSet extends Set {
constructor(source) {
super();
symbols.setHidden(this, symbols.$$, source);
}
get [(symbols.$O)]() {
return this[symbols.$$][symbols.$O];
}
get size() {
symbols.observe(this, symbols.SIZE);
return this[symbols.$$].size;
}
has(value) {
symbols.observe(this[symbols.$$], symbols.SIZE);
return this[symbols.$$].has(value);
}
add(value) {
const oldSize = this[symbols.$$].size;
this[symbols.$$].add(value);
if (oldSize !== this[symbols.$$].size) {
emitAdd(this[symbols.$$], void 0, value);
emitReplace(this[symbols.$$], symbols.SIZE, oldSize + 1, oldSize);
}
return this;
}
delete(value) {
const oldSize = this[symbols.$$].size;
return this[symbols.$$].delete(value) && emitRemove(this[symbols.$$], void 0, value) && emitReplace(this[symbols.$$], symbols.SIZE, oldSize - 1, oldSize);
}
clear() {
const oldSize = this[symbols.$$].size;
if (oldSize) {
const oldValues = new Set(this[symbols.$$]);
this[symbols.$$].clear();
emitClear(this[symbols.$$], oldValues);
emitReplace(this[symbols.$$], symbols.SIZE, 0, oldSize);
}
}
}
defineMethods(ObservableMap, MapIterators);
defineMethods(ObservableSet, SetIterators);
function defineMethods(Class, overrides) {
Object.defineProperties(Class.prototype, Object.getOwnPropertyDescriptors(overrides));
}
function setProperty(self, key, value) {
const exists = symbols.hasOwn(self, key);
const oldValue = self[key];
self[key] = value;
return exists ? value === oldValue || emitReplace(self, key, value, oldValue) : emitAdd(self, key, value);
}
function deleteProperty(self, key) {
if (!symbols.hasOwn(self, key))
return true;
const oldValue = self[key];
delete self[key];
return emitRemove(self, key, oldValue);
}
const shallowChanges = (target, onChange) => target[symbols.$O].observe(symbols.$O, onChange);
const canMakeObservable = (value) => is.is.object(value) && !is.is.date(value) && !is.is.regExp(value) && !is.is.promise(value) && !is.is.generator(value) && !is.is.generatorFunction(value) && !is.is.asyncFunction(value) && !is.is.weakMap(value) && !is.is.weakSet(value);
class Observer {
constructor() {
this.onChange = null;
}
get nonce() {
let nonce = 0;
this.observed.forEach((slot) => {
nonce += slot.nonce;
});
return nonce;
}
dispose() {
if (this.onChange) {
this.onChange = null;
this.observed.forEach((value) => value.delete(this));
}
}
}
class Observable extends Map {
constructor(source) {
super();
this.source = source;
if (isDev.isDev) {
no.setDebug(this, {
name: this.constructor.name || "Unknown"
});
}
if (source) {
this.proxy = is.is.map(source) ? new ObservableMap(source) : is.is.set(source) ? new ObservableSet(source) : new Proxy(source, source[symbols.$T] || (is.is.array(source) ? ArrayTraps : ObjectTraps));
}
}
has(key) {
const observers = super.get(key);
return !!(observers && observers.size);
}
get(key) {
let observers = super.get(key);
if (!observers) {
observers = new ObservedSlot(this, key);
this.set(key, observers);
}
return observers;
}
observe(key, onChange) {
const observers = this.get(key);
const observer = {
onChange,
dispose() {
observers.delete(observer);
}
};
observers.add(observer);
return observer;
}
}
class ObservedSlot extends Set {
constructor(owner, key) {
super();
this.owner = owner;
this.key = key;
this.nonce = 1;
}
onChange(change) {
if (this.size) {
for (const observer of Array.from(this)) {
if (observer.onChange) {
observer.onChange(change);
}
}
}
}
}
function auto(effect, config) {
const auto2 = new Auto(config);
auto2.run(effect);
return auto2;
}
class Auto {
constructor(config = {}) {
this.dirty = true;
this.nonce = 0;
this.observer = null;
this.sync = !!config.sync;
this.onDirty = config.onDirty || this._onDirty;
this.onError = config.onError || symbols.rethrowError;
this.onCommit = config.onCommit || symbols.noop;
this.onDispose = config.onDispose || symbols.noop;
}
run(compute) {
const observer = this.start(compute);
try {
const result = compute();
this.stop().commit(observer);
this.nonce = observer.nonce;
return result;
} catch (error) {
this.stop().onError(error);
}
}
rerun() {
const {observer} = this;
return observer && no.noto(() => this.run(observer.effect));
}
commit(observer, nonce) {
if (this.observer) {
this.observer.dispose();
}
if (nonce && nonce != observer.nonce) {
this.observer = null;
return false;
}
observer.onChange = this._onChange.bind(this);
observer.observed.forEach((observable) => observable.add(observer));
this.observer = observer;
this.onCommit(observer);
return true;
}
dispose() {
if (this.observer) {
this.observer.dispose();
this.observer = null;
this.onDispose();
}
}
start(effect) {
if (symbols.globals.observe) {
throw Error("Nested tracking is forbidden");
}
const observer = new AutoObserver(effect);
symbols.globals.auto = this;
symbols.globals.observe = (target, key) => {
observer.observed.add(target[symbols.$O].get(key));
};
return observer;
}
stop() {
if (symbols.globals.auto == this) {
symbols.globals.auto = symbols.globals.observe = null;
}
this.dirty = false;
return this;
}
clear() {
if (!this.dirty) {
this.dirty = true;
this.onDirty();
}
}
_onChange(change) {
if (isDev.isDev && no.getDebug(this)) {
no.addDebugAction(this, change);
}
if (this.sync && change.op == "clear" && is.is.function(change.target)) {
change.target();
}
this.clear();
}
_onDirty() {
const {observer, nonce} = this;
const maybeRerun = () => observer.nonce > nonce ? this.rerun() : this.dirty = false;
if (this.sync) {
maybeRerun();
} else {
batch.run(() => observer == this.observer && maybeRerun());
}
}
}
class AutoObserver extends Observer {
constructor(effect) {
super();
this.effect = effect;
this.observed = new Set();
}
}
function derive(run, discard) {
let memo;
const observable = new Observable();
const auto = new Auto({
onDirty() {
if (observable.has(symbols.$O)) {
batch.run(derived);
observable.get(symbols.$O).onChange({
op: "clear",
target: derived,
oldValue: memo
});
}
}
});
const derived = () => {
if (auto.dirty) {
no.noto(() => {
const oldMemo = memo;
const newMemo = run(auto);
if (newMemo !== oldMemo && !(discard && discard(newMemo, oldMemo))) {
emitReplace(derived, null, memo = newMemo, oldMemo);
}
});
}
symbols.observe(derived, symbols.$O);
return memo;
};
derived.auto = auto;
derived.dispose = () => auto.dispose();
symbols.setHidden(derived, symbols.$O, observable);
return derived;
}
function isDerived(value) {
return is.is.function(value) && !!value[symbols.$O];
}
function o(value) {
let state = value && value[symbols.$O];
if (!state || !symbols.getOwnDescriptor(value, symbols.$O)) {
if (!canMakeObservable(value)) {
return value;
}
if (is.is.function(value)) {
return derive((auto) => auto.run(value));
}
if (Object.isFrozen(value)) {
return value;
}
symbols.setHidden(value, symbols.$O, state = new Observable(value));
}
return state.proxy || value;
}
const when = (condition) => new Promise((resolve, reject) => {
const auto = new Auto({
onDirty() {
if (no.noto(() => this.run(condition))) {
this.dispose();
resolve();
}
},
onError(error) {
this.dispose();
reject(error);
}
});
auto.onDirty();
});
function watch(root, arg2, arg3) {
return arg3 ? new Watcher(root, arg3, arg2) : new Watcher(root, arg2);
}
class Watcher extends Observer {
constructor(root, onChange, key) {
super();
this.root = root;
this.key = key;
this.observed = new Set();
this.counts = new Map();
this.watch = (value, key, ctx) => {
if (canWatch(key) && is.is.map(ctx))
this._watch(key);
if (canWatch(value))
this._watch(value);
};
this.unwatch = (value, key, ctx) => {
if (canWatch(key) && is.is.map(ctx))
this._unwatch(key);
if (canWatch(value))
this._unwatch(value);
};
this.onChange = (change) => {
const {op, target, key: key2, value, oldValue} = change;
switch (op) {
case "replace":
if (canWatch(oldValue))
this._unwatch(oldValue);
case "add":
this.watch(value, key2, target);
break;
case "remove":
this.unwatch(value, key2, target);
break;
case "splice":
value.forEach(this.watch);
default:
oldValue.forEach(this.unwatch);
}
onChange(change);
};
if (key) {
this.counts.set(root, 1);
const observable = root[symbols.$O];
this.observed.add(observable.get(key).add(this));
this.watch(root[key], key, root);
} else {
this._watch(root);
}
}
dispose() {
if (this.onChange) {
super.dispose();
this.counts.clear();
}
}
_watch(target) {
const {counts} = this;
const count = counts.get(target) || 0;
counts.set(target, count + 1);
if (!count) {
if (target[symbols.$O]) {
this.observed.add(target[symbols.$O].get(symbols.$O).add(this));
}
if (!target.forEach) {
target = Object.values(target);
}
target.forEach(this.watch);
}
}
_unwatch(target) {
const {counts} = this;
const count = counts.get(target);
if (is.is.undefined(count))
return;
if (count > 1) {
counts.set(target, count - 1);
} else {
counts.delete(target);
if (target[symbols.$O]) {
const value = target[symbols.$O].get(symbols.$O);
this.observed.delete(value);
value.delete(this);
}
if (!target.forEach) {
target = Object.values(target);
}
target.forEach(this.unwatch);
}
}
}
const canWatch = (value) => value && typeof value == "object";
function mountAuto(auto) {
let mounted = false;
let observer;
let nonce = 0;
return (props) => {
if (props.observer) {
observer = props.observer;
if (!mounted) {
nonce = observer.nonce;
} else if (observer != auto.observer) {
auto.commit(observer);
}
} else {
mounted = props.mounted;
if (observer) {
if (!mounted) {
nonce = observer.nonce;
auto.dispose();
} else if (!auto.commit(observer, nonce)) {
auto.clear();
}
}
}
};
}
exports.Auto = Auto;
exports.ObjectTraps = ObjectTraps;
exports.Observable = Observable;
exports.ObservedSlot = ObservedSlot;
exports.Watcher = Watcher;
exports.auto = auto;
exports.batch = batch;
exports.derive = derive;
exports.flushSync = flushSync;
exports.isDerived = isDerived;
exports.mountAuto = mountAuto;
exports.o = o;
exports.shallowChanges = shallowChanges;
exports.watch = watch;
exports.when = when;
//# sourceMappingURL=23f6edd1.js.map