Document dataverse API
This commit is contained in:
parent
5763f2bca4
commit
599cc101c9
17 changed files with 555 additions and 19 deletions
|
@ -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> {
|
||||
|
|
|
@ -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() {}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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() {}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue