Document dataverse API

This commit is contained in:
Andrew Prifer 2022-01-19 13:06:13 +01:00 committed by Aria
parent 5763f2bca4
commit 599cc101c9
17 changed files with 555 additions and 19 deletions

View file

@ -12,58 +12,121 @@ import {
} from './prism/discoveryMechanism'
type IDependent = (msgComingFrom: IDerivation<$IntentionalAny>) => void
/**
* Represents a derivation whose changes can be tracked. To be used as the base class for all derivations.
*/
export default abstract class AbstractDerivation<V> implements IDerivation<V> {
/**
* Whether the object is a derivation.
*/
readonly isDerivation: true = true
private _didMarkDependentsAsStale: boolean = false
private _isHot: boolean = false
private _isFresh: boolean = false
/**
* @internal
*/
protected _lastValue: undefined | V = undefined
/**
* @internal
*/
protected _dependents: Set<IDependent> = new Set()
/**
* @internal
*/
protected _dependencies: Set<IDerivation<$IntentionalAny>> = new Set()
/**
* @internal
*/
protected abstract _recalculate(): V
/**
* @internal
*/
protected abstract _reactToDependencyBecomingStale(
which: IDerivation<unknown>,
): void
constructor() {}
/**
* Whether the derivation is hot.
*/
get isHot(): boolean {
return this._isHot
}
/**
* @internal
*/
protected _addDependency(d: IDerivation<$IntentionalAny>) {
if (this._dependencies.has(d)) return
this._dependencies.add(d)
if (this._isHot) d.addDependent(this._internal_markAsStale)
}
/**
* @internal
*/
protected _removeDependency(d: IDerivation<$IntentionalAny>) {
if (!this._dependencies.has(d)) return
this._dependencies.delete(d)
if (this._isHot) d.removeDependent(this._internal_markAsStale)
}
/**
* Returns a `Tappable` of the changes of this derivation.
*/
changes(ticker: Ticker): Tappable<V> {
return new DerivationEmitter(this, ticker).tappable()
}
/**
* Like {@link AbstractDerivation.changes} but with a different performance model. `changesWithoutValues` returns a `Tappable` that
* updates every time the derivation is updated, even if the value didn't change, and the callback is called without
* the value. The advantage of this is that you have control over when the derivation is freshened, it won't
* automatically be kept fresh.
*/
changesWithoutValues(): Tappable<void> {
return new DerivationValuelessEmitter(this).tappable()
}
/**
* Keep the derivation hot, even if there are no tappers (subscribers).
*/
keepHot() {
return this.changesWithoutValues().tap(() => {})
}
/**
* Convenience method that taps (subscribes to) the derivation using `this.changes(ticker).tap(fn)` and immediately calls
* the callback with the current value.
*
* @param ticker The ticker to use for batching.
* @param fn The callback to call on update.
*
* @see changes
*/
tapImmediate(ticker: Ticker, fn: (cb: V) => void): VoidFn {
const untap = this.changes(ticker).tap(fn)
fn(this.getValue())
return untap
}
/**
* Add a derivation as a dependent of this derivation.
*
* @param d The derivation to be made a dependent of this derivation.
*
* @see removeDependent
*/
// TODO: document this better, what are dependents?
addDependent(d: IDependent) {
const hadDepsBefore = this._dependents.size > 0
this._dependents.add(d)
@ -74,7 +137,11 @@ export default abstract class AbstractDerivation<V> implements IDerivation<V> {
}
/**
* @sealed
* Remove a derivation as a dependent of this derivation.
*
* @param d The derivation to be removed from as a dependent of this derivation.
*
* @see addDependent
*/
removeDependent(d: IDependent) {
const hadDepsBefore = this._dependents.size > 0
@ -89,6 +156,7 @@ export default abstract class AbstractDerivation<V> implements IDerivation<V> {
* This is meant to be called by subclasses
*
* @sealed
* @internal
*/
protected _markAsStale(which: IDerivation<$IntentionalAny>) {
this._internal_markAsStale(which)
@ -107,6 +175,9 @@ export default abstract class AbstractDerivation<V> implements IDerivation<V> {
})
}
/**
* Gets the current value of the derivation. If the value is stale, it causes the derivation to freshen.
*/
getValue(): V {
reportResolutionStart(this)
@ -144,14 +215,41 @@ export default abstract class AbstractDerivation<V> implements IDerivation<V> {
}
}
/**
* @internal
*/
protected _keepHot() {}
/**
* @internal
*/
protected _becomeCold() {}
/**
* Creates a new derivation from this derivation using the provided mapping function. The new derivation's value will be
* `fn(thisDerivation.getValue())`.
*
* @param fn The mapping function to use. Note: it accepts a plain value, not a derivation.
*/
map<T>(fn: (v: V) => T): IDerivation<T> {
return map(this, fn)
}
/**
* Same as {@link AbstractDerivation.map}, but the mapping function can also return a derivation, in which case the derivation returned
* by `flatMap` takes the value of that derivation.
*
* @example
* ```ts
* // Simply using map() here would return the inner derivation when we call getValue()
* new Box(3).derivation.map((value) => new Box(value).derivation).getValue()
*
* // Using flatMap() eliminates the inner derivation
* new Box(3).derivation.flatMap((value) => new Box(value).derivation).getValue()
* ```
*
* @param fn The mapping function to use. Note: it accepts a plain value, not a derivation.
*/
flatMap<R>(
fn: (v: V) => R,
): IDerivation<R extends IDerivation<infer T> ? T : R> {

View file

@ -1,17 +1,29 @@
import AbstractDerivation from './AbstractDerivation'
/**
* A derivation whose value never changes.
*/
export default class ConstantDerivation<V> extends AbstractDerivation<V> {
_v: V
private readonly _v: V
/**
* @param v The value of the derivation.
*/
constructor(v: V) {
super()
this._v = v
return this
}
/**
* @internal
*/
_recalculate() {
return this._v
}
/**
* @internal
*/
_reactToDependencyBecomingStale() {}
}

View file

@ -3,6 +3,9 @@ import Emitter from '../utils/Emitter'
import type {default as Tappable} from '../utils/Tappable'
import type {IDerivation} from './IDerivation'
/**
* An event emitter that emits events on changes to a derivation.
*/
export default class DerivationEmitter<V> {
private _derivation: IDerivation<V>
private _ticker: Ticker
@ -11,6 +14,10 @@ export default class DerivationEmitter<V> {
private _lastValueRecorded: boolean
private _hadTappers: boolean
/**
* @param derivation The derivation to emit events for.
* @param ticker The ticker to use to batch events.
*/
constructor(derivation: IDerivation<V>, ticker: Ticker) {
this._derivation = derivation
this._ticker = ticker
@ -40,6 +47,9 @@ export default class DerivationEmitter<V> {
}
}
/**
* The tappable associated with the emitter. You can use it to tap (subscribe to) the underlying derivation.
*/
tappable(): Tappable<V> {
return this._emitter.tappable
}

View file

@ -3,11 +3,18 @@ import AbstractDerivation from './AbstractDerivation'
const noop = () => {}
/**
* Represents a derivation based on a tappable (subscribable) data source.
*/
export default class DerivationFromSource<V> extends AbstractDerivation<V> {
private _untapFromChanges: () => void
private _cachedValue: undefined | V
private _hasCachedValue: boolean
/**
* @param _tapToSource A function that takes a listener and subscribes it to the underlying data source.
* @param _getValueFromSource A function that returns the current value of the data source.
*/
constructor(
private readonly _tapToSource: (listener: (newValue: V) => void) => VoidFn,
private readonly _getValueFromSource: () => V,
@ -18,6 +25,9 @@ export default class DerivationFromSource<V> extends AbstractDerivation<V> {
this._hasCachedValue = false
}
/**
* @internal
*/
_recalculate() {
if (this.isHot) {
if (!this._hasCachedValue) {
@ -30,6 +40,9 @@ export default class DerivationFromSource<V> extends AbstractDerivation<V> {
}
}
/**
* @internal
*/
_keepHot() {
this._hasCachedValue = false
this._cachedValue = undefined
@ -41,6 +54,9 @@ export default class DerivationFromSource<V> extends AbstractDerivation<V> {
})
}
/**
* @internal
*/
_becomeCold() {
this._untapFromChanges()
this._untapFromChanges = noop
@ -49,5 +65,8 @@ export default class DerivationFromSource<V> extends AbstractDerivation<V> {
this._cachedValue = undefined
}
/**
* @internal
*/
_reactToDependencyBecomingStale() {}
}

View file

@ -3,7 +3,10 @@ import type {default as Tappable} from '../utils/Tappable'
import type {IDerivation} from './IDerivation'
/**
* Just like DerivationEmitter, except it doesn't emit the value and doesn't need a ticker
* Like DerivationEmitter, but with a different performance model. DerivationValuelessEmitter emits every time the
* derivation is updated, even if the value didn't change, and tappers are called without the value. The advantage of
* this is that you have control over when the underlying derivation is freshened, it won't automatically be freshened
* by the emitter.
*/
export default class DerivationValuelessEmitter<V> {
_derivation: IDerivation<V>
@ -39,6 +42,9 @@ export default class DerivationValuelessEmitter<V> {
}
}
/**
* The tappable associated with the emitter. You can use it to tap (subscribe to) the underlying derivation.
*/
tappable(): Tappable<void> {
return this._emitter.tappable
}

View file

@ -3,26 +3,102 @@ import type {$IntentionalAny, VoidFn} from '../types'
import type Tappable from '../utils/Tappable'
type IDependent = (msgComingFrom: IDerivation<$IntentionalAny>) => void
/**
* Common interface for derivations.
*/
export interface IDerivation<V> {
/**
* Whether the object is a derivation.
*/
isDerivation: true
/**
* Whether the derivation is hot.
*/
isHot: boolean
/**
* Returns a `Tappable` of the changes of this derivation.
*/
changes(ticker: Ticker): Tappable<V>
/**
* Like {@link changes} but with a different performance model. `changesWithoutValues` returns a {@link Tappable} that
* updates every time the derivation is updated, even if the value didn't change, and the callback is called without
* the value. The advantage of this is that you have control over when the derivation is freshened, it won't
* automatically be kept fresh.
*/
changesWithoutValues(): Tappable<void>
/**
* Keep the derivation hot, even if there are no tappers (subscribers).
*/
keepHot(): VoidFn
/**
* Convenience method that taps (subscribes to) the derivation using `this.changes(ticker).tap(fn)` and immediately calls
* the callback with the current value.
*
* @param ticker The ticker to use for batching.
* @param fn The callback to call on update.
*
* @see changes
*/
tapImmediate(ticker: Ticker, fn: (cb: V) => void): VoidFn
/**
* Add a derivation as a dependent of this derivation.
*
* @param d The derivation to be made a dependent of this derivation.
*
* @see removeDependent
*/
addDependent(d: IDependent): void
/**
* Remove a derivation as a dependent of this derivation.
*
* @param d The derivation to be removed from as a dependent of this derivation.
*
* @see addDependent
*/
removeDependent(d: IDependent): void
/**
* Gets the current value of the derivation. If the value is stale, it causes the derivation to freshen.
*/
getValue(): V
/**
* Creates a new derivation from this derivation using the provided mapping function. The new derivation's value will be
* `fn(thisDerivation.getValue())`.
*
* @param fn The mapping function to use. Note: it accepts a plain value, not a derivation.
*/
map<T>(fn: (v: V) => T): IDerivation<T>
/**
* Same as {@link IDerivation.map}, but the mapping function can also return a derivation, in which case the derivation returned
* by `flatMap` takes the value of that derivation.
*
* @example
* // Simply using `map()` here would return the inner derivation when we call `getValue()`
* new Box(3).derivation.map((value) => new Box(value).derivation).getValue()
*
* // Using `flatMap()` eliminates the inner derivation
* new Box(3).derivation.flatMap((value) => new Box(value).derivation).getValue()
*
* @param fn The mapping function to use. Note: it accepts a plain value, not a derivation.
*/
flatMap<R>(
fn: (v: V) => R,
): IDerivation<R extends IDerivation<infer T> ? T : R>
}
/**
* Returns whether `d` is a derivation.
*/
export function isDerivation(d: any): d is IDerivation<unknown> {
return d && d.isDerivation && d.isDerivation === true
}

View file

@ -334,6 +334,12 @@ type IPrismFn = {
inPrism: typeof inPrism
}
/**
* Creates a derivation from the passed function that adds all derivations referenced
* in it as dependencies, and reruns the function when these change.
*
* @param fn The function to rerun when the derivations referenced in it change.
*/
const prism: IPrismFn = (fn) => {
return new PrismDerivation(fn)
}