Created
February 28, 2022 08:29
-
-
Save fabiospampinato/4fc3eeeef21eae004f8f53413dda6ec0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// src/batch.ts | |
var Batch = class { | |
constructor() { | |
this.level = 0; | |
this.registerUpdate = (observable2, value) => { | |
if (!this.queue) | |
return; | |
this.queue.set(observable2, value); | |
}; | |
this.wrap = (fn) => { | |
const queuePrev = this.queue; | |
const queueNext = queuePrev || /* @__PURE__ */ new Map(); | |
this.level += 1; | |
this.queue = queueNext; | |
try { | |
fn(); | |
} finally { | |
this.level -= 1; | |
this.queue = queuePrev; | |
if (!this.level) { | |
this.flush(queueNext); | |
} | |
} | |
}; | |
this.flush = (queue) => { | |
queue.forEach((value, observable2) => observable2.set(value)); | |
}; | |
this.has = () => { | |
return !!this.queue; | |
}; | |
} | |
}; | |
var batch_default = new Batch(); | |
// src/constants.ts | |
var NOOP = () => { | |
}; | |
var SYMBOL = Symbol("Observable"); | |
// src/callable.ts | |
var callable = (() => { | |
const self = function() { | |
return this; | |
}; | |
const traps = { | |
get(target, property) { | |
if (property === SYMBOL) | |
return true; | |
const observable2 = target(); | |
return observable2[property].bind(observable2); | |
}, | |
apply(target, thisArg, args) { | |
if (!args.length) | |
return target().get(); | |
return target().set(args[0]); | |
} | |
}; | |
return (observable2) => { | |
return new Proxy(self.bind(observable2), traps); | |
}; | |
})(); | |
var callable_default = callable; | |
// src/utils.ts | |
var cloneDeep = (value) => { | |
return JSON.parse(JSON.stringify(value)); | |
}; | |
var { isArray } = Array; | |
var isFunction = (value) => { | |
return typeof value === "function"; | |
}; | |
var isPrimitive = (value) => { | |
if (value === null) | |
return true; | |
const type = typeof value; | |
return type !== "object" && type !== "function"; | |
}; | |
var isSet = (value) => { | |
return value instanceof Set; | |
}; | |
var isUndefined = (value) => { | |
return value === void 0; | |
}; | |
// src/observer.ts | |
var Observer = class { | |
registerCleanup(cleanup) { | |
if (!this.cleanups) { | |
this.cleanups = cleanup; | |
} else if (isArray(this.cleanups)) { | |
this.cleanups.push(cleanup); | |
} else { | |
this.cleanups = [this.cleanups, cleanup]; | |
} | |
} | |
registerError(error) { | |
if (!this.errors) { | |
this.errors = error; | |
} else if (isArray(this.errors)) { | |
this.errors.push(error); | |
} else { | |
this.errors = [this.errors, error]; | |
} | |
} | |
registerObservable(observable2) { | |
if (!this.observables) { | |
this.observables = observable2; | |
} else if (isArray(this.observables)) { | |
this.observables.push(observable2); | |
} else { | |
this.observables = [this.observables, observable2]; | |
} | |
} | |
registerObserver(observer) { | |
if (!this.observers) { | |
this.observers = observer; | |
} else if (isArray(this.observers)) { | |
this.observers.push(observer); | |
} else { | |
this.observers = [this.observers, observer]; | |
} | |
} | |
registerParent(observer) { | |
this.parent = observer; | |
} | |
registerSelf() { | |
if (!this.observables) { | |
return; | |
} else if (isArray(this.observables)) { | |
context_default.registerObservables(this.observables); | |
} else { | |
context_default.registerObservable(this.observables); | |
} | |
} | |
unregisterObserver(observer) { | |
if (!this.observers) { | |
return; | |
} else if (isArray(this.observers)) { | |
const index = this.observers.indexOf(observer); | |
if (index >= 0) { | |
this.observers.splice(index, 1); | |
} | |
} else { | |
if (this.observers === observer) { | |
delete this.observers; | |
} | |
} | |
} | |
unregisterParent() { | |
delete this.parent; | |
} | |
update() { | |
delete this.dirty; | |
} | |
updateError(error, silent) { | |
const { errors, parent } = this; | |
if (errors) { | |
if (isArray(errors)) { | |
errors.forEach((fn) => fn(error)); | |
} else { | |
errors(error); | |
} | |
return true; | |
} else { | |
if (parent) { | |
if (parent.updateError(error, true)) | |
return true; | |
} | |
if (!silent) { | |
throw error; | |
} | |
return false; | |
} | |
} | |
static unsubscribe(observer) { | |
const { observers, observables, cleanups, errors } = observer; | |
if (observers) { | |
if (isArray(observers)) { | |
for (let i = 0, l = observers.length; i < l; i++) { | |
Observer.unsubscribe(observers[i]); | |
} | |
observers.length = 0; | |
} else { | |
Observer.unsubscribe(observers); | |
delete observer.observers; | |
} | |
} | |
if (observables) { | |
if (isArray(observables)) { | |
for (let i = 0, l = observables.length; i < l; i++) { | |
observables[i].unregisterObserver(observer); | |
} | |
observables.length = 0; | |
} else { | |
observables.unregisterObserver(observer); | |
delete observer.observables; | |
} | |
} | |
if (cleanups) { | |
if (isArray(cleanups)) { | |
for (let i = 0, l = cleanups.length; i < l; i++) { | |
cleanups[i](); | |
} | |
cleanups.length = 0; | |
} else { | |
cleanups(); | |
delete observer.cleanups; | |
} | |
} | |
if (errors) { | |
if (isArray(errors)) { | |
errors.length = 0; | |
} else { | |
delete observer.errors; | |
} | |
} | |
} | |
}; | |
var observer_default = Observer; | |
// src/context.ts | |
var Context = class { | |
constructor() { | |
this.sampling = false; | |
this.registerCleanup = (cleanup) => { | |
if (!this.observer) | |
return; | |
this.observer.registerCleanup(cleanup); | |
}; | |
this.registerError = (error) => { | |
if (!this.observer) | |
return; | |
this.observer.registerError(error); | |
}; | |
this.registerObservable = (observable2) => { | |
if (!this.observer) | |
return; | |
if (this.sampling) | |
return; | |
if (observable2.hasObserver(this.observer)) | |
return; | |
this.observer.registerObservable(observable2); | |
observable2.registerObserver(this.observer); | |
}; | |
this.registerObservables = (observables) => { | |
if (!this.observer) | |
return; | |
if (this.sampling) | |
return; | |
observables.forEach(this.registerObservable); | |
}; | |
this.registerObserver = (observer) => { | |
if (!this.observer) | |
return; | |
this.observer.registerObserver(observer); | |
observer.registerParent(this.observer); | |
}; | |
this.unregisterObserver = (observer) => { | |
if (!this.observer) | |
return; | |
this.observer.unregisterObserver(observer); | |
observer.unregisterParent(); | |
}; | |
this.wrap = (fn) => { | |
const observer = new observer_default(); | |
try { | |
return this.wrapWith(fn, observer, true); | |
} catch (error) { | |
observer.updateError(error); | |
} | |
}; | |
this.wrapVoid = (fn) => { | |
this.wrap(fn); | |
}; | |
this.wrapWith = (fn, observer, disposable, sampling) => { | |
const observerPrev = this.observer; | |
const samplingPrev = this.sampling; | |
this.observer = observer; | |
this.sampling = !!sampling; | |
try { | |
const dispose = observer && disposable ? () => this.dispose(observer) : NOOP; | |
return fn(dispose); | |
} finally { | |
this.observer = observerPrev; | |
this.sampling = samplingPrev; | |
} | |
}; | |
this.wrapWithout = (fn) => { | |
return this.wrapWith(fn); | |
}; | |
this.wrapWithSampling = (fn) => { | |
return this.wrapWith(fn, this.observer, false, true); | |
}; | |
this.dispose = (observer) => { | |
observer_default.unsubscribe(observer); | |
this.observer = void 0; | |
}; | |
} | |
}; | |
var context_default = new Context(); | |
// src/observable.ts | |
var Observable = class { | |
constructor(value, options, parent) { | |
this.value = value; | |
if (options) { | |
if (options.comparator) { | |
this.comparator = options.comparator; | |
} | |
} | |
if (parent) { | |
this.parent = parent; | |
} | |
} | |
hasObserver(observer) { | |
if (!this.observers) { | |
return false; | |
} else if (isSet(this.observers)) { | |
return this.observers.has(observer); | |
} else { | |
return this.observers === observer; | |
} | |
} | |
registerObserver(observer) { | |
if (!this.observers) { | |
this.observers = observer; | |
} else if (isSet(this.observers)) { | |
this.observers.add(observer); | |
} else if (this.observers === observer) { | |
return; | |
} else { | |
this.observers = /* @__PURE__ */ new Set([this.observers, observer]); | |
} | |
} | |
unregisterObserver(observer) { | |
if (!this.observers) { | |
return; | |
} else if (isSet(this.observers)) { | |
this.observers.delete(observer); | |
} else if (this.observers === observer) { | |
this.observers = void 0; | |
} | |
} | |
registerSelf() { | |
if (this.parent) { | |
if (!this.parent.dirty) { | |
this.parent.registerSelf(); | |
} else { | |
this.parent.update(); | |
} | |
} else { | |
context_default.registerObservable(this); | |
} | |
} | |
get() { | |
this.registerSelf(); | |
return this.value; | |
} | |
sample() { | |
return this.value; | |
} | |
set(value) { | |
if (Observable.compare(value, this.value, this.comparator)) { | |
return this.value; | |
} | |
if (batch_default.has()) { | |
batch_default.registerUpdate(this, value); | |
return value; | |
} else { | |
this.value = value; | |
this.emit(); | |
return value; | |
} | |
} | |
produce(fn) { | |
const isValuePrimitive = isPrimitive(this.value); | |
const valueClone = isValuePrimitive ? this.value : cloneDeep(this.value); | |
const valueResult = fn(valueClone); | |
const valueNext = isValuePrimitive || !isUndefined(valueResult) ? valueResult : valueClone; | |
return this.set(valueNext); | |
} | |
update(fn) { | |
const valueNext = fn(this.value); | |
return this.set(valueNext); | |
} | |
emit() { | |
const { observers } = this; | |
if (!observers) | |
return; | |
if (isSet(observers) && !observers.size) | |
return; | |
context_default.wrapWithout(() => { | |
if (isSet(observers)) { | |
const queue = Array.from(observers.values()); | |
for (let i = 0, l = queue.length; i < l; i++) { | |
const observer = queue[i]; | |
observer.dirty = true; | |
} | |
for (let i = 0, l = queue.length; i < l; i++) { | |
const observer = queue[i]; | |
if (!observer.dirty) | |
continue; | |
if (!observers.has(observer)) | |
continue; | |
observer.update(); | |
} | |
} else { | |
observers.update(); | |
} | |
}); | |
} | |
on(fn, options, dependencies) { | |
if (isArray(options)) | |
return this.on(fn, void 0, options); | |
const observable2 = computed_default.wrap(() => { | |
this.get(); | |
if (dependencies) | |
dependencies.forEach((observable3) => observable3()); | |
return context_default.wrapWithSampling(() => fn(this.value)); | |
}, void 0, options); | |
return observable2; | |
} | |
static compare(value, valuePrev, comparator = Object.is) { | |
return comparator(value, valuePrev); | |
} | |
}; | |
var observable_default = Observable; | |
// src/computed.ts | |
var Computed = class extends observer_default { | |
constructor(fn, valueInitial, options) { | |
super(); | |
this.fn = fn; | |
this.observable = new observable_default(valueInitial, options, this); | |
this.update(); | |
} | |
update() { | |
context_default.registerObserver(this); | |
observer_default.unsubscribe(this); | |
delete this.dirty; | |
const valuePrev = this.observable.sample(); | |
try { | |
const valueNext = context_default.wrapWith(() => this.fn(valuePrev), this, true); | |
this.observable.set(valueNext); | |
} catch (error) { | |
this.updateError(error); | |
} | |
} | |
static wrap(fn, value, options) { | |
return callable_default(new Computed(fn, value, options).observable); | |
} | |
}; | |
var computed_default = Computed; | |
// src/disposed.ts | |
var disposed = () => { | |
const value = src_default(false); | |
context_default.registerCleanup(() => { | |
value(true); | |
}); | |
return value; | |
}; | |
var disposed_default = disposed; | |
// src/effect.ts | |
var Effect = class extends observer_default { | |
constructor(fn) { | |
super(); | |
this.fn = fn; | |
this.update(); | |
} | |
isDisposable() { | |
const { observers, observables, cleanups } = this; | |
if (observers) { | |
if (isArray(observers)) { | |
if (observers.length) { | |
return false; | |
} | |
} else { | |
return false; | |
} | |
} | |
if (observables) { | |
if (isArray(observables)) { | |
if (observables.length) { | |
return false; | |
} | |
} else { | |
return false; | |
} | |
} | |
if (cleanups) { | |
if (isArray(cleanups)) { | |
if (cleanups.length) { | |
return false; | |
} | |
} else { | |
return false; | |
} | |
} | |
return true; | |
} | |
update() { | |
context_default.registerObserver(this); | |
observer_default.unsubscribe(this); | |
delete this.dirty; | |
try { | |
const cleanup = context_default.wrapWith(() => this.fn(), this, false); | |
if (cleanup) { | |
this.registerCleanup(cleanup); | |
} else { | |
if (this.isDisposable()) { | |
context_default.unregisterObserver(this); | |
observer_default.unsubscribe(this); | |
} | |
} | |
} catch (error) { | |
this.updateError(error); | |
} | |
} | |
static wrap(fn) { | |
new Effect(fn); | |
} | |
}; | |
var effect_default = Effect; | |
// src/from.ts | |
var from = (fn, options) => { | |
const value = src_default(void 0, options); | |
effect_default.wrap(() => fn(value)); | |
return value; | |
}; | |
var from_default = from; | |
// src/is.ts | |
var is = (value) => { | |
return isFunction(value) && !!value[SYMBOL]; | |
}; | |
var is_default = is; | |
// src/get.ts | |
var get = (value) => { | |
if (is_default(value)) | |
return get(value()); | |
return value; | |
}; | |
var get_default = get; | |
// src/index.ts | |
function observable(value, options) { | |
return callable_default(new observable_default(value, options)); | |
} | |
observable.batch = batch_default.wrap; | |
observable.cleanup = context_default.registerCleanup; | |
observable.computed = computed_default.wrap; | |
observable.disposed = disposed_default; | |
observable.effect = effect_default.wrap; | |
observable.error = context_default.registerError; | |
observable.from = from_default; | |
observable.get = get_default; | |
observable.is = is_default; | |
observable.root = context_default.wrapVoid; | |
observable.sample = context_default.wrapWithSampling; | |
var src_default = observable; | |
export { | |
src_default as default | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment