UNPKG

wana

Version:

Easy observable state for React

799 lines (783 loc) 21.3 kB
'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