Skip to content

Instantly share code, notes, and snippets.

@aleclarson
Created July 11, 2023 18:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aleclarson/5d4928641f07ac57b0bfec45e1d34adb to your computer and use it in GitHub Desktop.
Save aleclarson/5d4928641f07ac57b0bfec45e1d34adb to your computer and use it in GitHub Desktop.
var e=e=>"function"==typeof e,t=()=>{},s=DEV&&"undefined"!=typeof __OBSERVABLE_HOOKS__&&__OBSERVABLE_HOOKS__,r=e=>e._value,i=1,h=1,o=r,n=Symbol.for("alien:refType"),u=class{constructor(e){this._value=e,this._version=0,this._observers=new Set}get _depth(){return 0}_isObserved(e,t){DEV&&s&&s.isObserved(this,e,t),t?this._observers.add(e):this._observers.delete(e)}get[n](){return"ReadonlyRef"}get value(){return o(this)}get version(){return this._version}peek(){return this._value}},d=class extends u{get[n](){return"Ref"}get value(){return super.value}set value(e){const t=this._value;e!==t&&(this._value=e,this._version=i,this._observers.forEach((s=>{s.observe(this,e,t)})),h===i&&O()&&(h++,Promise.resolve().then(y)))}set(t){return e(t)&&(t=t(this._value)),this.value=t,t}},a=Object.getOwnPropertyDescriptor(d.prototype,"value");Object.defineProperties(d.prototype,{0:a,1:{get(){return this.set.bind(this)}},[Symbol.iterator]:{value:function*(){yield this[0],yield this[1]}}});var v=Symbol("empty"),l=class extends u{constructor(e){super(v),this.compute=e,this._refs=null,this._observer=null}get _depth(){return this._observer?.depth??0}_isObserved(e,t){super._isObserved(e,t),t?this._observer||this._setupObserver():(this._value=v,this._observer?.dispose(),this._observer=null,this._refs=null)}_setupObserver(){const e=this._observer=new c(f);this._refs=e.refs,e.onUpdate=a.set.bind(this),e.update(this.compute)}_update(){this._refs=new Set;const e=o;let t;o=e=>(this._refs.add(e),e._value);try{this._version=i,this._value=this.compute()}catch(e){t=e}if(o=e,DEV&&s&&s.didUpdate(this,t,this._value),t)throw t;return this._value}get[n](){return"ComputedRef"}get value(){if(this._observer)return super.value;if(o!==r)return this._setupObserver(),super.value;if(this._value===v)return this._update();for(const e of this._refs)if(e._version>this._version)return this._update();return this._value}},_=e=>e,p=1,c=class{constructor(e=b){this.queue=e,this.id=p++,this.refs=new Set,this.version=0,this.depth=0,this.nextCompute=t,this.onUpdate=_}update(e){const t=new Set(this.refs);this.refs.clear(),this.depth=0;const r=o;let i,h;o=e=>(e._isObserved(this,!0),t.delete(e),this.refs.add(e),this.depth=Math.max(this.depth,e._depth+1),e._value);try{h=(this.nextCompute=e)()}catch(e){i=e}if(o=r,t.forEach((e=>{e._isObserved(this,!1)})),DEV&&s&&s.didUpdate(this,i,h),i)throw i;return this.onUpdate(h)}observe(e,t,r){DEV&&s&&s.observe(this,e,t,r),this.version!==i&&(this.version=i,this.queue.add(this))}dispose(){this.queue.delete(this),this.refs.forEach((e=>{e._isObserved(this,!1)}))}get destructor(){return this.dispose.bind(this)}};var f=new Set,b=new Set;function O(){return f.size+b.size>0}function y(){i=h;const e=DEV&&Error("Cycle detected");let t,r;const n=o;o=e=>(e._isObserved(t,!0),r.delete(e),t.refs.add(e),t.depth=Math.max(t.depth,e._depth+1),e._value);for(let i=0;O();i++){if(i>100)throw DEV&&e||Error("Cycle detected");const h=e=>{let i,h;r=new Set(e.refs),e.refs.clear(),e.depth=0,t=e;try{h=e.nextCompute(),e.onUpdate(h)}catch(e){console.error(i=e)}r.forEach((t=>{t._isObserved(e,!1)})),DEV&&s&&s.didUpdate(e,i,h)},o=[...f].sort(((e,t)=>t.depth-e.depth));f.clear(),o.forEach(h);const n=[...b].sort(((e,t)=>e.id-t.id));b.clear(),n.forEach(h)}o=n}var E=e=>new d(e),w=e=>new l(e);function m(t,s){const r=new c;if(e(t))r.update(t);else{const e=t,i=s;r.update((()=>{o(e)})),r.observe=(e,t,s)=>{r.onUpdate=i.bind(null,t,s,e)}}return r}function S(e){return!!e&&void 0!==e[n]}function g(e){const t=o;o=r;try{return e()}finally{o=t}}export{l as ComputedRef,c as Observer,u as ReadonlyRef,d as Ref,w as computed,S as isRef,m as observe,g as peek,E as ref};
// project/index.ts
var isFunction = (value) => typeof value === "function";
var noop = () => {
};
var hooks = DEV && typeof __OBSERVABLE_HOOKS__ !== "undefined" && __OBSERVABLE_HOOKS__;
var unseenAccess = (ref2) => ref2._value;
var currentVersion = 1;
var nextVersion = 1;
var access = unseenAccess;
var kRefType = Symbol.for("alien:refType");
var ReadonlyRef = class {
constructor(_value) {
this._value = _value;
this._version = 0;
this._observers = /* @__PURE__ */ new Set();
}
get _depth() {
return 0;
}
_isObserved(observer, isObserved) {
if (DEV && hooks) {
hooks.isObserved(this, observer, isObserved);
}
if (isObserved) {
this._observers.add(observer);
} else {
this._observers.delete(observer);
}
}
get [kRefType]() {
return "ReadonlyRef";
}
get value() {
return access(this);
}
get version() {
return this._version;
}
peek() {
return this._value;
}
};
var Ref = class extends ReadonlyRef {
get [kRefType]() {
return "Ref";
}
get value() {
return super.value;
}
set value(newValue) {
const oldValue = this._value;
if (newValue !== oldValue) {
this._value = newValue;
this._version = currentVersion;
this._observers.forEach((observer) => {
observer.observe(this, newValue, oldValue);
});
onObservedUpdate();
}
}
set(arg) {
if (isFunction(arg)) {
arg = arg(this._value);
}
this.value = arg;
return arg;
}
};
var valueProperty = Object.getOwnPropertyDescriptor(Ref.prototype, "value");
Object.defineProperties(Ref.prototype, {
0: valueProperty,
1: {
get() {
return this.set.bind(this);
}
},
[Symbol.iterator]: {
value: function* () {
yield this[0];
yield this[1];
}
}
});
var emptySymbol = Symbol("empty");
var ComputedRef = class extends ReadonlyRef {
constructor(compute) {
super(emptySymbol);
this.compute = compute;
this._refs = null;
this._observer = null;
}
get _depth() {
return this._observer?.depth ?? 0;
}
_isObserved(observer, isObserved) {
super._isObserved(observer, isObserved);
if (!isObserved) {
this._value = emptySymbol;
this._observer?.dispose();
this._observer = null;
this._refs = null;
} else if (!this._observer) {
this._setupObserver();
}
}
_setupObserver() {
const observer = this._observer = new Observer(computeQueue);
this._refs = observer.refs;
observer.onUpdate = valueProperty.set.bind(this);
observer.update(this.compute);
}
_update() {
this._refs = /* @__PURE__ */ new Set();
const parentAccess = access;
access = (ref2) => {
this._refs.add(ref2);
return ref2._value;
};
let error;
try {
this._version = currentVersion;
this._value = this.compute();
} catch (e) {
error = e;
}
access = parentAccess;
if (DEV && hooks) {
hooks.didUpdate(this, error, this._value);
}
if (error)
throw error;
return this._value;
}
get [kRefType]() {
return "ComputedRef";
}
get value() {
if (this._observer) {
return super.value;
}
if (access !== unseenAccess) {
this._setupObserver();
return super.value;
}
if (this._value === emptySymbol) {
return this._update();
}
for (const ref2 of this._refs) {
if (ref2._version > this._version) {
return this._update();
}
}
return this._value;
}
};
var passThrough = (result) => result;
var nextObserverId = 1;
var Observer = class {
constructor(queue = updateQueue) {
this.queue = queue;
this.id = nextObserverId++;
this.refs = /* @__PURE__ */ new Set();
this.version = 0;
this.depth = 0;
this.nextCompute = noop;
this.onUpdate = passThrough;
}
update(compute) {
const oldRefs = new Set(this.refs);
this.refs.clear();
this.depth = 0;
const parentAccess = access;
access = (ref2) => {
ref2._isObserved(this, true);
oldRefs.delete(ref2);
this.refs.add(ref2);
this.depth = Math.max(this.depth, ref2._depth + 1);
return ref2._value;
};
let error;
let result;
try {
result = (this.nextCompute = compute)();
} catch (e) {
error = e;
}
access = parentAccess;
oldRefs.forEach((ref2) => {
ref2._isObserved(this, false);
});
if (DEV && hooks) {
hooks.didUpdate(this, error, result);
}
if (error)
throw error;
return this.onUpdate(result);
}
observe(ref2, newValue, oldValue) {
if (DEV && hooks) {
hooks.observe(this, ref2, newValue, oldValue);
}
if (this.version !== currentVersion) {
this.version = currentVersion;
this.queue.add(this);
}
}
dispose() {
this.queue.delete(this);
this.refs.forEach((ref2) => {
ref2._isObserved(this, false);
});
}
get destructor() {
return this.dispose.bind(this);
}
};
function onObservedUpdate() {
if (nextVersion === currentVersion && hasQueuedObservers()) {
nextVersion++;
Promise.resolve().then(computeNextVersion);
}
}
var computeQueue = /* @__PURE__ */ new Set();
var updateQueue = /* @__PURE__ */ new Set();
function hasQueuedObservers() {
return computeQueue.size + updateQueue.size > 0;
}
function computeNextVersion() {
currentVersion = nextVersion;
const devError = DEV && Error("Cycle detected");
let currentObserver;
let oldRefs;
const parentAccess = access;
access = (ref2) => {
ref2._isObserved(currentObserver, true);
oldRefs.delete(ref2);
currentObserver.refs.add(ref2);
currentObserver.depth = Math.max(currentObserver.depth, ref2._depth + 1);
return ref2._value;
};
for (let loops = 0; hasQueuedObservers(); loops++) {
if (loops > 100) {
throw DEV && devError || Error("Cycle detected");
}
const update = (observer) => {
oldRefs = new Set(observer.refs);
observer.refs.clear();
observer.depth = 0;
let error;
let result;
currentObserver = observer;
try {
result = observer.nextCompute();
observer.onUpdate(result);
} catch (e) {
console.error(error = e);
}
oldRefs.forEach((ref2) => {
ref2._isObserved(observer, false);
});
if (DEV && hooks) {
hooks.didUpdate(observer, error, result);
}
};
const computedRefs = [...computeQueue].sort((a, b) => b.depth - a.depth);
computeQueue.clear();
computedRefs.forEach(update);
const updatedObservers = [...updateQueue].sort((a, b) => a.id - b.id);
updateQueue.clear();
updatedObservers.forEach(update);
}
access = parentAccess;
}
var ref = (value) => new Ref(value);
var computed = (compute) => new ComputedRef(compute);
function observe(arg1, arg2) {
const observer = new Observer();
if (isFunction(arg1)) {
observer.update(arg1);
} else {
const ref2 = arg1, onChange = arg2;
observer.update(() => {
access(ref2);
});
observer.observe = (ref3, newValue, oldValue) => {
observer.onUpdate = onChange.bind(null, newValue, oldValue, ref3);
};
}
return observer;
}
function isRef(value) {
return !!value && value[kRefType] !== void 0;
}
function peek(compute) {
const parentAccess = access;
access = unseenAccess;
try {
return compute();
} finally {
access = parentAccess;
}
}
export {
ComputedRef,
Observer,
ReadonlyRef,
Ref,
computed,
isRef,
observe,
peek,
ref
};
const isFunction = value => typeof value === 'function'
const noop = () => {}
// Debugging hooks are declared globally.
declare const __OBSERVABLE_HOOKS__: {
/**
* A ref was changed and the given observer was notified.
*/
observe(
observer: Observer,
ref: ReadonlyRef<any>,
newValue: any,
oldValue: any
): void
/**
* A ref was observed or unobserved.
*/
isObserved(
ref: ReadonlyRef<any>,
observer: Observer,
isObserved: boolean
): void
/**
* An update was completed or threw an error.
*/
didUpdate(observer: Observer | ComputedRef, error: any, result: any): void
}
const hooks = (DEV &&
typeof __OBSERVABLE_HOOKS__ !== 'undefined' &&
__OBSERVABLE_HOOKS__) as typeof __OBSERVABLE_HOOKS__
type InternalRef<T> = Ref<T> & {
_value: T
_version: number
_observers: Set<Observer>
_depth: number
_isObserved: (observer: Observer, isObserved: boolean) => void
}
const unseenAccess = (ref: InternalRef<any>) => ref._value
let currentVersion = 1
let nextVersion = 1
let access = unseenAccess
const kRefType = Symbol.for('alien:refType')
export class ReadonlyRef<T = any> {
protected _version = 0
protected _observers = new Set<Observer>()
protected get _depth() {
return 0
}
constructor(protected _value: T) {}
/**
* In addition to adding/removing an observer, computed refs use this method
* to switch between eager and lazy computation mode.
*/
protected _isObserved(observer: Observer, isObserved: boolean) {
if (DEV && hooks) {
hooks.isObserved(this, observer, isObserved)
}
if (isObserved) {
this._observers.add(observer)
} else {
this._observers.delete(observer)
}
}
get [kRefType]() {
return 'ReadonlyRef'
}
get value() {
return access(this as any)
}
get version() {
return this._version
}
peek() {
return this._value
}
}
export class Ref<T = any> extends ReadonlyRef<T> {
get [kRefType]() {
return 'Ref'
}
get value() {
return super.value
}
set value(newValue: T) {
const oldValue = this._value
if (newValue !== oldValue) {
this._value = newValue
this._version = currentVersion
this._observers.forEach(observer => {
observer.observe(this, newValue, oldValue)
})
onObservedUpdate()
}
}
set(arg: T | ((value: T) => T)) {
if (isFunction(arg)) {
arg = arg(this._value)
}
this.value = arg
return arg
}
}
export interface Ref<T> {
0: T
1: (arg: T | ((value: T) => T)) => T
}
const valueProperty = Object.getOwnPropertyDescriptor(Ref.prototype, 'value')!
Object.defineProperties(Ref.prototype, {
0: valueProperty,
1: {
get(this: Ref) {
return this.set.bind(this)
},
},
[Symbol.iterator]: {
value: function* () {
yield this[0]
yield this[1]
},
},
})
const emptySymbol: any = Symbol('empty')
export class ComputedRef<T = any> extends ReadonlyRef<T> {
protected _refs: Set<InternalRef<any>> | null = null
protected _observer: Observer | null = null
protected get _depth() {
return this._observer?.depth ?? 0
}
constructor(protected compute: () => T) {
super(emptySymbol)
}
protected _isObserved(observer: Observer, isObserved: boolean) {
super._isObserved(observer, isObserved)
// Destroy our own observer once the ref is no longer observed.
if (!isObserved) {
this._value = emptySymbol
this._observer?.dispose()
this._observer = null
this._refs = null
}
// Create our own observer once the ref is observed.
else if (!this._observer) {
this._setupObserver()
}
}
protected _setupObserver() {
const observer = (this._observer = new Observer(computeQueue))
this._refs = observer.refs
observer.onUpdate = valueProperty.set!.bind(this)
observer.update(this.compute)
}
protected _update() {
this._refs = new Set<InternalRef<any>>()
const parentAccess = access
access = ref => {
this._refs!.add(ref)
return ref._value
}
let error: any
try {
this._version = currentVersion
this._value = this.compute()
} catch (e) {
error = e
}
access = parentAccess
if (DEV && hooks) {
hooks.didUpdate(this, error, this._value)
}
if (error) throw error
return this._value
}
get [kRefType]() {
return 'ComputedRef'
}
get value() {
if (this._observer) {
return super.value
}
if (access !== unseenAccess) {
this._setupObserver()
return super.value
}
if (this._value === emptySymbol) {
return this._update()
}
// Check if a dependency has changed since last access.
for (const ref of this._refs!) {
if (ref._version > this._version) {
return this._update()
}
}
return this._value
}
}
const passThrough = (result: any) => result
let nextObserverId = 1
export class Observer {
readonly id = nextObserverId++
refs = new Set<InternalRef<any>>()
version = 0
depth = 0
nextCompute: () => any = noop
onUpdate = passThrough
constructor(readonly queue = updateQueue) {}
update<T>(compute: () => T) {
const oldRefs = new Set(this.refs)
this.refs.clear()
this.depth = 0
const parentAccess = access
access = ref => {
ref._isObserved(this, true)
oldRefs.delete(ref)
this.refs.add(ref)
this.depth = Math.max(this.depth, ref._depth + 1)
return ref._value
}
let error: any
let result: any
try {
result = (this.nextCompute = compute)()
} catch (e) {
error = e
}
access = parentAccess
oldRefs.forEach(ref => {
ref._isObserved(this, false)
})
if (DEV && hooks) {
hooks.didUpdate(this, error, result)
}
if (error) throw error
return this.onUpdate(result)
}
/** Called when a ref has a new value. */
observe(ref: ReadonlyRef<any>, newValue: any, oldValue: any) {
if (DEV && hooks) {
hooks.observe(this, ref, newValue, oldValue)
}
if (this.version !== currentVersion) {
this.version = currentVersion
this.queue.add(this)
}
}
dispose() {
this.queue.delete(this)
this.refs.forEach(ref => {
ref._isObserved(this, false)
})
}
/**
* Returns a bound `dispose` method.
*/
get destructor() {
return this.dispose.bind(this)
}
}
function onObservedUpdate() {
if (nextVersion === currentVersion && hasQueuedObservers()) {
nextVersion++
Promise.resolve().then(computeNextVersion)
}
}
// Computed refs always run before plain observers.
const computeQueue = new Set<Observer>()
const updateQueue = new Set<Observer>()
function hasQueuedObservers() {
return computeQueue.size + updateQueue.size > 0
}
function computeNextVersion() {
currentVersion = nextVersion
// Capture the stack trace before the infinite loop.
const devError = DEV && Error('Cycle detected')
let currentObserver: Observer
let oldRefs: Set<InternalRef<any>>
const parentAccess = access
access = (ref: InternalRef<any>) => {
ref._isObserved(currentObserver, true)
oldRefs.delete(ref)
currentObserver.refs.add(ref)
currentObserver.depth = Math.max(currentObserver.depth, ref._depth + 1)
return ref._value
}
for (let loops = 0; hasQueuedObservers(); loops++) {
if (loops > 100) {
throw (DEV && devError) || Error('Cycle detected')
}
const update = (observer: Observer) => {
oldRefs = new Set(observer.refs)
observer.refs.clear()
observer.depth = 0
let error: any
let result: any
currentObserver = observer
try {
result = observer.nextCompute()
observer.onUpdate(result)
} catch (e) {
console.error((error = e))
}
oldRefs.forEach(ref => {
ref._isObserved(observer, false)
})
if (DEV && hooks) {
hooks.didUpdate(observer, error, result)
}
}
// Computed refs run in order of depth.
const computedRefs = [...computeQueue].sort((a, b) => b.depth - a.depth)
computeQueue.clear()
computedRefs.forEach(update)
// Plain observers run in order of creation.
const updatedObservers = [...updateQueue].sort((a, b) => a.id - b.id)
updateQueue.clear()
updatedObservers.forEach(update)
}
access = parentAccess
}
//
// Convenience functions
//
export const ref = <T>(value: T) => new Ref(value)
export const computed = <T>(compute: () => T) => new ComputedRef(compute)
/** Observe any refs accessed in the compute function. */
export function observe(compute: () => void): Observer
/** Observe a single ref. */
export function observe<T>(
ref: ReadonlyRef<T>,
compute: (newValue: T, oldValue: T, ref: ReadonlyRef<T>) => void
): Observer
/** @internal */
export function observe(
arg1: ReadonlyRef | (() => void),
arg2?: (newValue: any, oldValue: any, ref: ReadonlyRef) => void
) {
const observer = new Observer()
if (isFunction(arg1)) {
observer.update(arg1)
} else {
const ref = arg1,
onChange = arg2!
observer.update(() => {
access(ref as any)
})
observer.observe = (ref, newValue, oldValue) => {
// Capture the old value for the onChange callback.
observer.onUpdate = onChange.bind(null, newValue, oldValue, ref)
}
}
return observer
}
export function isRef<T = any>(value: any): value is ReadonlyRef<T> {
return !!value && value[kRefType] !== undefined
}
/**
* Like `ref.peek()` but applies to all access within the given `compute`
* callback.
*/
export function peek<T>(compute: () => T) {
const parentAccess = access
access = unseenAccess
try {
return compute()
} finally {
access = parentAccess
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment