Document dataverse API
This commit is contained in:
parent
5763f2bca4
commit
599cc101c9
17 changed files with 555 additions and 19 deletions
|
@ -15,7 +15,7 @@
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"postinstall": "husky install",
|
"postinstall": "husky install",
|
||||||
"release": "zx scripts/release.mjs",
|
"release": "zx scripts/release.mjs",
|
||||||
"build:api-docs": "zx scripts/build-api-docs.mjs",
|
"build:api-docs": "zx scripts/build-api-docs.mjs /Users/andrew/Projects/theatre-docs/docs/api",
|
||||||
"lint:all": "eslint . --ext ts,tsx --ignore-path=.gitignore --rulesdir ./devEnv/eslint/rules"
|
"lint:all": "eslint . --ext ts,tsx --ignore-path=.gitignore --rulesdir ./devEnv/eslint/rules"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
|
|
|
@ -19,8 +19,19 @@ enum ValueTypes {
|
||||||
Other,
|
Other,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for objects that can provide a derivation at a certain path.
|
||||||
|
*/
|
||||||
export interface IdentityDerivationProvider {
|
export interface IdentityDerivationProvider {
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
readonly $$isIdentityDerivationProvider: true
|
readonly $$isIdentityDerivationProvider: true
|
||||||
|
/**
|
||||||
|
* Returns a derivation of the value at the provided path.
|
||||||
|
*
|
||||||
|
* @param path The path to create the derivation at.
|
||||||
|
*/
|
||||||
getIdentityDerivation(path: Array<string | number>): IDerivation<unknown>
|
getIdentityDerivation(path: Array<string | number>): IDerivation<unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,12 +110,24 @@ class Scope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps an object whose (sub)properties can be individually tracked.
|
||||||
|
*/
|
||||||
export default class Atom<State extends {}>
|
export default class Atom<State extends {}>
|
||||||
implements IdentityDerivationProvider
|
implements IdentityDerivationProvider
|
||||||
{
|
{
|
||||||
private _currentState: State
|
private _currentState: State
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
readonly $$isIdentityDerivationProvider = true
|
readonly $$isIdentityDerivationProvider = true
|
||||||
private readonly _rootScope: Scope
|
private readonly _rootScope: Scope
|
||||||
|
/**
|
||||||
|
* Convenience property that gives you a pointer to the root of the atom.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Equivalent to `pointer({ root: thisAtom, path: [] })`.
|
||||||
|
*/
|
||||||
readonly pointer: Pointer<State>
|
readonly pointer: Pointer<State>
|
||||||
|
|
||||||
constructor(initialState: State) {
|
constructor(initialState: State) {
|
||||||
|
@ -113,6 +136,11 @@ export default class Atom<State extends {}>
|
||||||
this.pointer = pointer({root: this as $FixMe, path: []})
|
this.pointer = pointer({root: this as $FixMe, path: []})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the state of the atom.
|
||||||
|
*
|
||||||
|
* @param newState The new state of the atom.
|
||||||
|
*/
|
||||||
setState(newState: State) {
|
setState(newState: State) {
|
||||||
const oldState = this._currentState
|
const oldState = this._currentState
|
||||||
this._currentState = newState
|
this._currentState = newState
|
||||||
|
@ -120,14 +148,39 @@ export default class Atom<State extends {}>
|
||||||
this._checkUpdates(this._rootScope, oldState, newState)
|
this._checkUpdates(this._rootScope, oldState, newState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current state of the atom.
|
||||||
|
*/
|
||||||
getState() {
|
getState() {
|
||||||
return this._currentState
|
return this._currentState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the state of the atom at `path`.
|
||||||
|
*/
|
||||||
getIn(path: (string | number)[]): unknown {
|
getIn(path: (string | number)[]): unknown {
|
||||||
return path.length === 0 ? this.getState() : get(this.getState(), path)
|
return path.length === 0 ? this.getState() : get(this.getState(), path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new state object from the current one, where the value at `path`
|
||||||
|
* is replaced by the return value of `reducer`, then sets it.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Doesn't mutate the old state, and preserves referential equality between
|
||||||
|
* values of the old state and the new state where possible.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* someAtom.getIn(['a']) // 1
|
||||||
|
* someAtom.reduceState(['a'], (state) => state + 1);
|
||||||
|
* someAtom.getIn(['a']) // 2
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param path The path to call the reducer at.
|
||||||
|
* @param reducer The function to use for creating the new state.
|
||||||
|
*/
|
||||||
|
// TODO: Why is this a property and not a method?
|
||||||
reduceState: PathBasedReducer<State, State> = (
|
reduceState: PathBasedReducer<State, State> = (
|
||||||
path: $IntentionalAny[],
|
path: $IntentionalAny[],
|
||||||
reducer: $IntentionalAny,
|
reducer: $IntentionalAny,
|
||||||
|
@ -137,6 +190,9 @@ export default class Atom<State extends {}>
|
||||||
return newState
|
return newState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the state of the atom at `path`.
|
||||||
|
*/
|
||||||
setIn(path: $FixMe[], val: $FixMe) {
|
setIn(path: $FixMe[], val: $FixMe) {
|
||||||
return this.reduceState(path, () => val)
|
return this.reduceState(path, () => val)
|
||||||
}
|
}
|
||||||
|
@ -181,6 +237,11 @@ export default class Atom<State extends {}>
|
||||||
return untap
|
return untap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new derivation of the value at the provided path.
|
||||||
|
*
|
||||||
|
* @param path The path to create the derivation at.
|
||||||
|
*/
|
||||||
getIdentityDerivation(path: Array<string | number>): IDerivation<unknown> {
|
getIdentityDerivation(path: Array<string | number>): IDerivation<unknown> {
|
||||||
return new DerivationFromSource<$IntentionalAny>(
|
return new DerivationFromSource<$IntentionalAny>(
|
||||||
(listener) => this._onPathValueChange(path, listener),
|
(listener) => this._onPathValueChange(path, listener),
|
||||||
|
@ -191,6 +252,12 @@ export default class Atom<State extends {}>
|
||||||
|
|
||||||
const identityDerivationWeakMap = new WeakMap<{}, IDerivation<unknown>>()
|
const identityDerivationWeakMap = new WeakMap<{}, IDerivation<unknown>>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a derivation of the value at the provided pointer. Derivations are
|
||||||
|
* cached per pointer.
|
||||||
|
*
|
||||||
|
* @param pointer The pointer to return the derivation at.
|
||||||
|
*/
|
||||||
export const valueDerivation = <P extends PointerType<$IntentionalAny>>(
|
export const valueDerivation = <P extends PointerType<$IntentionalAny>>(
|
||||||
pointer: P,
|
pointer: P,
|
||||||
): IDerivation<P extends PointerType<infer T> ? T : void> => {
|
): IDerivation<P extends PointerType<infer T> ? T : void> => {
|
||||||
|
@ -211,6 +278,7 @@ export const valueDerivation = <P extends PointerType<$IntentionalAny>>(
|
||||||
return derivation as $IntentionalAny
|
return derivation as $IntentionalAny
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Rename it to isIdentityDerivationProvider
|
||||||
function isIdentityChangeProvider(
|
function isIdentityChangeProvider(
|
||||||
val: unknown,
|
val: unknown,
|
||||||
): val is IdentityDerivationProvider {
|
): val is IdentityDerivationProvider {
|
||||||
|
@ -221,6 +289,16 @@ function isIdentityChangeProvider(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience function that returns a plain value from its argument, whether it
|
||||||
|
* is a pointer, a derivation or a plain value itself.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* For pointers, the value is returned by first creating a derivation, so it is
|
||||||
|
* reactive e.g. when used in a `prism`.
|
||||||
|
*
|
||||||
|
* @param pointerOrDerivationOrPlainValue The argument to return a value from.
|
||||||
|
*/
|
||||||
export const val = <P>(
|
export const val = <P>(
|
||||||
pointerOrDerivationOrPlainValue: P,
|
pointerOrDerivationOrPlainValue: P,
|
||||||
): P extends PointerType<infer T>
|
): P extends PointerType<infer T>
|
||||||
|
|
|
@ -1,33 +1,88 @@
|
||||||
import DerivationFromSource from './derivations/DerivationFromSource'
|
import DerivationFromSource from './derivations/DerivationFromSource'
|
||||||
import type {IDerivation} from './derivations/IDerivation'
|
import type {IDerivation} from './derivations/IDerivation'
|
||||||
import Emitter from './utils/Emitter'
|
import Emitter from './utils/Emitter'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common interface for Box types. Boxes wrap a single value.
|
||||||
|
*/
|
||||||
export interface IBox<V> {
|
export interface IBox<V> {
|
||||||
|
/**
|
||||||
|
* Sets the value of the Box.
|
||||||
|
*
|
||||||
|
* @param v The value to update the Box with.
|
||||||
|
*/
|
||||||
|
|
||||||
set(v: V): void
|
set(v: V): void
|
||||||
|
/**
|
||||||
|
* Gets the value of the Box.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Usages of `get()` aren't tracked, they are only for retrieving the value. To track changes, you need to
|
||||||
|
* create a derivation.
|
||||||
|
*
|
||||||
|
* @see derivation
|
||||||
|
*/
|
||||||
get(): V
|
get(): V
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a derivation of the Box that you can use to track changes to it.
|
||||||
|
*/
|
||||||
derivation: IDerivation<V>
|
derivation: IDerivation<V>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a single value.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Derivations created with {@link Box.derivation} update based on strict equality (`===`) of the old value and the new one.
|
||||||
|
* This also means that property-changes of objects won't be tracked, and that for objects, updates will trigger on changes of
|
||||||
|
* reference even if the objects are structurally equal.
|
||||||
|
*/
|
||||||
export default class Box<V> implements IBox<V> {
|
export default class Box<V> implements IBox<V> {
|
||||||
private _publicDerivation: IDerivation<V>
|
private _publicDerivation: IDerivation<V>
|
||||||
private _emitter = new Emitter<V>()
|
private _emitter = new Emitter<V>()
|
||||||
|
|
||||||
constructor(protected _value: V) {
|
/**
|
||||||
|
* @param _value The initial value of the Box.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
protected _value: V,
|
||||||
|
) {
|
||||||
this._publicDerivation = new DerivationFromSource(
|
this._publicDerivation = new DerivationFromSource(
|
||||||
(listener) => this._emitter.tappable.tap(listener),
|
(listener) => this._emitter.tappable.tap(listener),
|
||||||
this.get.bind(this),
|
this.get.bind(this),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of the Box.
|
||||||
|
*
|
||||||
|
* @param v The value to update the Box with.
|
||||||
|
*/
|
||||||
set(v: V) {
|
set(v: V) {
|
||||||
if (v === this._value) return
|
if (v === this._value) return
|
||||||
this._value = v
|
this._value = v
|
||||||
this._emitter.emit(v)
|
this._emitter.emit(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of the Box.
|
||||||
|
*
|
||||||
|
* Note: usages of `get()` aren't tracked, they are only for retrieving the value. To track changes, you need to
|
||||||
|
* create a derivation.
|
||||||
|
*
|
||||||
|
* @see derivation
|
||||||
|
*/
|
||||||
get() {
|
get() {
|
||||||
return this._value
|
return this._value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a derivation of the Box that you can use to track changes to it.
|
||||||
|
*/
|
||||||
get derivation() {
|
get derivation() {
|
||||||
return this._publicDerivation
|
return this._publicDerivation
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,27 @@ import Box from './Box'
|
||||||
import type {$FixMe, $IntentionalAny} from './types'
|
import type {$FixMe, $IntentionalAny} from './types'
|
||||||
import {valueDerivation} from './Atom'
|
import {valueDerivation} from './Atom'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows creating pointer-derivations where the pointer can be switched out.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* This allows reacting not just to value changes at a certain pointer, but changes
|
||||||
|
* to the proxied pointer too.
|
||||||
|
*/
|
||||||
export default class PointerProxy<O extends {}>
|
export default class PointerProxy<O extends {}>
|
||||||
implements IdentityDerivationProvider
|
implements IdentityDerivationProvider
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
readonly $$isIdentityDerivationProvider = true
|
readonly $$isIdentityDerivationProvider = true
|
||||||
private readonly _currentPointerBox: IBox<Pointer<O>>
|
private readonly _currentPointerBox: IBox<Pointer<O>>
|
||||||
|
/**
|
||||||
|
* Convenience pointer pointing to the root of this PointerProxy.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Allows convenient use of {@link valueDerivation} and {@link val}.
|
||||||
|
*/
|
||||||
readonly pointer: Pointer<O>
|
readonly pointer: Pointer<O>
|
||||||
|
|
||||||
constructor(currentPointer: Pointer<O>) {
|
constructor(currentPointer: Pointer<O>) {
|
||||||
|
@ -18,10 +34,19 @@ export default class PointerProxy<O extends {}>
|
||||||
this.pointer = pointer({root: this as $FixMe, path: []})
|
this.pointer = pointer({root: this as $FixMe, path: []})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the underlying pointer.
|
||||||
|
* @param p The pointer to be proxied.
|
||||||
|
*/
|
||||||
setPointer(p: Pointer<O>) {
|
setPointer(p: Pointer<O>) {
|
||||||
this._currentPointerBox.set(p)
|
this._currentPointerBox.set(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a derivation of the value at the provided sub-path of the proxied pointer.
|
||||||
|
*
|
||||||
|
* @param path The path to create the derivation at.
|
||||||
|
*/
|
||||||
getIdentityDerivation(path: Array<string | number>) {
|
getIdentityDerivation(path: Array<string | number>) {
|
||||||
return this._currentPointerBox.derivation.flatMap((p) => {
|
return this._currentPointerBox.derivation.flatMap((p) => {
|
||||||
const subPointer = path.reduce(
|
const subPointer = path.reduce(
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
type ICallback = (t: number) => void
|
type ICallback = (t: number) => void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Ticker class helps schedule callbacks. Scheduled callbacks are executed per tick. Ticks can be triggered by an
|
||||||
|
* external scheduling strategy, e.g. a raf.
|
||||||
|
*/
|
||||||
export default class Ticker {
|
export default class Ticker {
|
||||||
private _scheduledForThisOrNextTick: Set<ICallback>
|
private _scheduledForThisOrNextTick: Set<ICallback>
|
||||||
private _scheduledForNextTick: Set<ICallback>
|
private _scheduledForNextTick: Set<ICallback>
|
||||||
|
@ -15,12 +19,16 @@ export default class Ticker {
|
||||||
/**
|
/**
|
||||||
* Registers for fn to be called either on this tick or the next tick.
|
* Registers for fn to be called either on this tick or the next tick.
|
||||||
*
|
*
|
||||||
* If registerSideEffect() is called while Ticker.tick() is running, the
|
* If `onThisOrNextTick()` is called while `Ticker.tick()` is running, the
|
||||||
* side effect _will_ be called within the running tick. If you don't want this
|
* side effect _will_ be called within the running tick. If you don't want this
|
||||||
* behavior, you can use registerSideEffectForNextTick().
|
* behavior, you can use `onNextTick()`.
|
||||||
*
|
*
|
||||||
* Note that fn will be added to a Set(). Which means, if you call registerSideEffect(fn)
|
* Note that `fn` will be added to a `Set()`. Which means, if you call `onThisOrNextTick(fn)`
|
||||||
* with the same fn twice in a single tick, it'll only run once.
|
* with the same fn twice in a single tick, it'll only run once.
|
||||||
|
*
|
||||||
|
* @param fn The function to be registered.
|
||||||
|
*
|
||||||
|
* @see offThisOrNextTick
|
||||||
*/
|
*/
|
||||||
onThisOrNextTick(fn: ICallback) {
|
onThisOrNextTick(fn: ICallback) {
|
||||||
this._scheduledForThisOrNextTick.add(fn)
|
this._scheduledForThisOrNextTick.add(fn)
|
||||||
|
@ -29,26 +37,55 @@ export default class Ticker {
|
||||||
/**
|
/**
|
||||||
* Registers a side effect to be called on the next tick.
|
* Registers a side effect to be called on the next tick.
|
||||||
*
|
*
|
||||||
* @see Ticker:onThisOrNextTick()
|
* @param fn The function to be registered.
|
||||||
|
*
|
||||||
|
* @see onThisOrNextTick
|
||||||
|
* @see offNextTick
|
||||||
*/
|
*/
|
||||||
onNextTick(fn: ICallback) {
|
onNextTick(fn: ICallback) {
|
||||||
this._scheduledForNextTick.add(fn)
|
this._scheduledForNextTick.add(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* De-registers a fn to be called either on this tick or the next tick.
|
||||||
|
*
|
||||||
|
* @param fn The function to be de-registered.
|
||||||
|
*
|
||||||
|
* @see onThisOrNextTick
|
||||||
|
*/
|
||||||
offThisOrNextTick(fn: ICallback) {
|
offThisOrNextTick(fn: ICallback) {
|
||||||
this._scheduledForThisOrNextTick.delete(fn)
|
this._scheduledForThisOrNextTick.delete(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* De-registers a fn to be called on the next tick.
|
||||||
|
*
|
||||||
|
* @param fn The function to be de-registered.
|
||||||
|
*
|
||||||
|
* @see onNextTick
|
||||||
|
*/
|
||||||
offNextTick(fn: ICallback) {
|
offNextTick(fn: ICallback) {
|
||||||
this._scheduledForNextTick.delete(fn)
|
this._scheduledForNextTick.delete(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time at the start of the current tick if there is a tick in progress, otherwise defaults to
|
||||||
|
* `performance.now()`.
|
||||||
|
*/
|
||||||
get time() {
|
get time() {
|
||||||
if (this._ticking) {
|
if (this._ticking) {
|
||||||
return this._timeAtCurrentTick
|
return this._timeAtCurrentTick
|
||||||
} else return performance.now()
|
} else return performance.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers a tick which starts executing the callbacks scheduled for this tick.
|
||||||
|
*
|
||||||
|
* @param t The time at the tick.
|
||||||
|
*
|
||||||
|
* @see onThisOrNextTick
|
||||||
|
* @see onNextTick
|
||||||
|
*/
|
||||||
tick(t: number = performance.now()) {
|
tick(t: number = performance.now()) {
|
||||||
this._ticking = true
|
this._ticking = true
|
||||||
this._timeAtCurrentTick = t
|
this._timeAtCurrentTick = t
|
||||||
|
|
|
@ -12,58 +12,121 @@ import {
|
||||||
} from './prism/discoveryMechanism'
|
} from './prism/discoveryMechanism'
|
||||||
|
|
||||||
type IDependent = (msgComingFrom: IDerivation<$IntentionalAny>) => void
|
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> {
|
export default abstract class AbstractDerivation<V> implements IDerivation<V> {
|
||||||
|
/**
|
||||||
|
* Whether the object is a derivation.
|
||||||
|
*/
|
||||||
readonly isDerivation: true = true
|
readonly isDerivation: true = true
|
||||||
private _didMarkDependentsAsStale: boolean = false
|
private _didMarkDependentsAsStale: boolean = false
|
||||||
private _isHot: boolean = false
|
private _isHot: boolean = false
|
||||||
|
|
||||||
private _isFresh: boolean = false
|
private _isFresh: boolean = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
protected _lastValue: undefined | V = undefined
|
protected _lastValue: undefined | V = undefined
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
protected _dependents: Set<IDependent> = new Set()
|
protected _dependents: Set<IDependent> = new Set()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
protected _dependencies: Set<IDerivation<$IntentionalAny>> = new Set()
|
protected _dependencies: Set<IDerivation<$IntentionalAny>> = new Set()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
protected abstract _recalculate(): V
|
protected abstract _recalculate(): V
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
protected abstract _reactToDependencyBecomingStale(
|
protected abstract _reactToDependencyBecomingStale(
|
||||||
which: IDerivation<unknown>,
|
which: IDerivation<unknown>,
|
||||||
): void
|
): void
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the derivation is hot.
|
||||||
|
*/
|
||||||
get isHot(): boolean {
|
get isHot(): boolean {
|
||||||
return this._isHot
|
return this._isHot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
protected _addDependency(d: IDerivation<$IntentionalAny>) {
|
protected _addDependency(d: IDerivation<$IntentionalAny>) {
|
||||||
if (this._dependencies.has(d)) return
|
if (this._dependencies.has(d)) return
|
||||||
this._dependencies.add(d)
|
this._dependencies.add(d)
|
||||||
if (this._isHot) d.addDependent(this._internal_markAsStale)
|
if (this._isHot) d.addDependent(this._internal_markAsStale)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
protected _removeDependency(d: IDerivation<$IntentionalAny>) {
|
protected _removeDependency(d: IDerivation<$IntentionalAny>) {
|
||||||
if (!this._dependencies.has(d)) return
|
if (!this._dependencies.has(d)) return
|
||||||
this._dependencies.delete(d)
|
this._dependencies.delete(d)
|
||||||
if (this._isHot) d.removeDependent(this._internal_markAsStale)
|
if (this._isHot) d.removeDependent(this._internal_markAsStale)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a `Tappable` of the changes of this derivation.
|
||||||
|
*/
|
||||||
changes(ticker: Ticker): Tappable<V> {
|
changes(ticker: Ticker): Tappable<V> {
|
||||||
return new DerivationEmitter(this, ticker).tappable()
|
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> {
|
changesWithoutValues(): Tappable<void> {
|
||||||
return new DerivationValuelessEmitter(this).tappable()
|
return new DerivationValuelessEmitter(this).tappable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep the derivation hot, even if there are no tappers (subscribers).
|
||||||
|
*/
|
||||||
keepHot() {
|
keepHot() {
|
||||||
return this.changesWithoutValues().tap(() => {})
|
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 {
|
tapImmediate(ticker: Ticker, fn: (cb: V) => void): VoidFn {
|
||||||
const untap = this.changes(ticker).tap(fn)
|
const untap = this.changes(ticker).tap(fn)
|
||||||
fn(this.getValue())
|
fn(this.getValue())
|
||||||
return untap
|
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) {
|
addDependent(d: IDependent) {
|
||||||
const hadDepsBefore = this._dependents.size > 0
|
const hadDepsBefore = this._dependents.size > 0
|
||||||
this._dependents.add(d)
|
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) {
|
removeDependent(d: IDependent) {
|
||||||
const hadDepsBefore = this._dependents.size > 0
|
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
|
* This is meant to be called by subclasses
|
||||||
*
|
*
|
||||||
* @sealed
|
* @sealed
|
||||||
|
* @internal
|
||||||
*/
|
*/
|
||||||
protected _markAsStale(which: IDerivation<$IntentionalAny>) {
|
protected _markAsStale(which: IDerivation<$IntentionalAny>) {
|
||||||
this._internal_markAsStale(which)
|
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 {
|
getValue(): V {
|
||||||
reportResolutionStart(this)
|
reportResolutionStart(this)
|
||||||
|
|
||||||
|
@ -144,14 +215,41 @@ export default abstract class AbstractDerivation<V> implements IDerivation<V> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
protected _keepHot() {}
|
protected _keepHot() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
protected _becomeCold() {}
|
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> {
|
map<T>(fn: (v: V) => T): IDerivation<T> {
|
||||||
return map(this, fn)
|
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>(
|
flatMap<R>(
|
||||||
fn: (v: V) => R,
|
fn: (v: V) => R,
|
||||||
): IDerivation<R extends IDerivation<infer T> ? T : R> {
|
): IDerivation<R extends IDerivation<infer T> ? T : R> {
|
||||||
|
|
|
@ -1,17 +1,29 @@
|
||||||
import AbstractDerivation from './AbstractDerivation'
|
import AbstractDerivation from './AbstractDerivation'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A derivation whose value never changes.
|
||||||
|
*/
|
||||||
export default class ConstantDerivation<V> extends AbstractDerivation<V> {
|
export default class ConstantDerivation<V> extends AbstractDerivation<V> {
|
||||||
_v: V
|
private readonly _v: V
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param v The value of the derivation.
|
||||||
|
*/
|
||||||
constructor(v: V) {
|
constructor(v: V) {
|
||||||
super()
|
super()
|
||||||
this._v = v
|
this._v = v
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
_recalculate() {
|
_recalculate() {
|
||||||
return this._v
|
return this._v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
_reactToDependencyBecomingStale() {}
|
_reactToDependencyBecomingStale() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@ import Emitter from '../utils/Emitter'
|
||||||
import type {default as Tappable} from '../utils/Tappable'
|
import type {default as Tappable} from '../utils/Tappable'
|
||||||
import type {IDerivation} from './IDerivation'
|
import type {IDerivation} from './IDerivation'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event emitter that emits events on changes to a derivation.
|
||||||
|
*/
|
||||||
export default class DerivationEmitter<V> {
|
export default class DerivationEmitter<V> {
|
||||||
private _derivation: IDerivation<V>
|
private _derivation: IDerivation<V>
|
||||||
private _ticker: Ticker
|
private _ticker: Ticker
|
||||||
|
@ -11,6 +14,10 @@ export default class DerivationEmitter<V> {
|
||||||
private _lastValueRecorded: boolean
|
private _lastValueRecorded: boolean
|
||||||
private _hadTappers: 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) {
|
constructor(derivation: IDerivation<V>, ticker: Ticker) {
|
||||||
this._derivation = derivation
|
this._derivation = derivation
|
||||||
this._ticker = ticker
|
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> {
|
tappable(): Tappable<V> {
|
||||||
return this._emitter.tappable
|
return this._emitter.tappable
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,18 @@ import AbstractDerivation from './AbstractDerivation'
|
||||||
|
|
||||||
const noop = () => {}
|
const noop = () => {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a derivation based on a tappable (subscribable) data source.
|
||||||
|
*/
|
||||||
export default class DerivationFromSource<V> extends AbstractDerivation<V> {
|
export default class DerivationFromSource<V> extends AbstractDerivation<V> {
|
||||||
private _untapFromChanges: () => void
|
private _untapFromChanges: () => void
|
||||||
private _cachedValue: undefined | V
|
private _cachedValue: undefined | V
|
||||||
private _hasCachedValue: boolean
|
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(
|
constructor(
|
||||||
private readonly _tapToSource: (listener: (newValue: V) => void) => VoidFn,
|
private readonly _tapToSource: (listener: (newValue: V) => void) => VoidFn,
|
||||||
private readonly _getValueFromSource: () => V,
|
private readonly _getValueFromSource: () => V,
|
||||||
|
@ -18,6 +25,9 @@ export default class DerivationFromSource<V> extends AbstractDerivation<V> {
|
||||||
this._hasCachedValue = false
|
this._hasCachedValue = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
_recalculate() {
|
_recalculate() {
|
||||||
if (this.isHot) {
|
if (this.isHot) {
|
||||||
if (!this._hasCachedValue) {
|
if (!this._hasCachedValue) {
|
||||||
|
@ -30,6 +40,9 @@ export default class DerivationFromSource<V> extends AbstractDerivation<V> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
_keepHot() {
|
_keepHot() {
|
||||||
this._hasCachedValue = false
|
this._hasCachedValue = false
|
||||||
this._cachedValue = undefined
|
this._cachedValue = undefined
|
||||||
|
@ -41,6 +54,9 @@ export default class DerivationFromSource<V> extends AbstractDerivation<V> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
_becomeCold() {
|
_becomeCold() {
|
||||||
this._untapFromChanges()
|
this._untapFromChanges()
|
||||||
this._untapFromChanges = noop
|
this._untapFromChanges = noop
|
||||||
|
@ -49,5 +65,8 @@ export default class DerivationFromSource<V> extends AbstractDerivation<V> {
|
||||||
this._cachedValue = undefined
|
this._cachedValue = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
_reactToDependencyBecomingStale() {}
|
_reactToDependencyBecomingStale() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,10 @@ import type {default as Tappable} from '../utils/Tappable'
|
||||||
import type {IDerivation} from './IDerivation'
|
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> {
|
export default class DerivationValuelessEmitter<V> {
|
||||||
_derivation: IDerivation<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> {
|
tappable(): Tappable<void> {
|
||||||
return this._emitter.tappable
|
return this._emitter.tappable
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,26 +3,102 @@ import type {$IntentionalAny, VoidFn} from '../types'
|
||||||
import type Tappable from '../utils/Tappable'
|
import type Tappable from '../utils/Tappable'
|
||||||
|
|
||||||
type IDependent = (msgComingFrom: IDerivation<$IntentionalAny>) => void
|
type IDependent = (msgComingFrom: IDerivation<$IntentionalAny>) => void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common interface for derivations.
|
||||||
|
*/
|
||||||
export interface IDerivation<V> {
|
export interface IDerivation<V> {
|
||||||
|
/**
|
||||||
|
* Whether the object is a derivation.
|
||||||
|
*/
|
||||||
isDerivation: true
|
isDerivation: true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the derivation is hot.
|
||||||
|
*/
|
||||||
isHot: boolean
|
isHot: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a `Tappable` of the changes of this derivation.
|
||||||
|
*/
|
||||||
changes(ticker: Ticker): Tappable<V>
|
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>
|
changesWithoutValues(): Tappable<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep the derivation hot, even if there are no tappers (subscribers).
|
||||||
|
*/
|
||||||
keepHot(): VoidFn
|
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
|
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
|
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
|
removeDependent(d: IDependent): void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current value of the derivation. If the value is stale, it causes the derivation to freshen.
|
||||||
|
*/
|
||||||
getValue(): V
|
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>
|
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>(
|
flatMap<R>(
|
||||||
fn: (v: V) => R,
|
fn: (v: V) => R,
|
||||||
): IDerivation<R extends IDerivation<infer T> ? T : R>
|
): IDerivation<R extends IDerivation<infer T> ? T : R>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether `d` is a derivation.
|
||||||
|
*/
|
||||||
export function isDerivation(d: any): d is IDerivation<unknown> {
|
export function isDerivation(d: any): d is IDerivation<unknown> {
|
||||||
return d && d.isDerivation && d.isDerivation === true
|
return d && d.isDerivation && d.isDerivation === true
|
||||||
}
|
}
|
||||||
|
|
|
@ -334,6 +334,12 @@ type IPrismFn = {
|
||||||
inPrism: typeof inPrism
|
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) => {
|
const prism: IPrismFn = (fn) => {
|
||||||
return new PrismDerivation(fn)
|
return new PrismDerivation(fn)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
/**
|
||||||
|
* The animation-optimized FRP library powering the internals of Theatre.js.
|
||||||
|
*
|
||||||
|
* @packageDocumentation
|
||||||
|
*/
|
||||||
|
|
||||||
export type {IdentityDerivationProvider} from './Atom'
|
export type {IdentityDerivationProvider} from './Atom'
|
||||||
export {default as Atom, val, valueDerivation} from './Atom'
|
export {default as Atom, val, valueDerivation} from './Atom'
|
||||||
export {default as Box} from './Box'
|
export {default as Box} from './Box'
|
||||||
|
|
|
@ -22,10 +22,19 @@ export type UnindexablePointer = {
|
||||||
|
|
||||||
const pointerMetaWeakMap = new WeakMap<{}, PointerMeta>()
|
const pointerMetaWeakMap = new WeakMap<{}, PointerMeta>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper type for the type a `Pointer` points to.
|
||||||
|
*/
|
||||||
export type PointerType<O> = {
|
export type PointerType<O> = {
|
||||||
$$__pointer_type: O
|
$$__pointer_type: O
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of {@link Atom} pointers. See {@link pointer|pointer()} for an
|
||||||
|
* explanation of pointers.
|
||||||
|
*
|
||||||
|
* @see Atom
|
||||||
|
*/
|
||||||
export type Pointer<O> = PointerType<O> &
|
export type Pointer<O> = PointerType<O> &
|
||||||
(O extends UnindexableTypesForPointer
|
(O extends UnindexableTypesForPointer
|
||||||
? UnindexablePointer
|
? UnindexablePointer
|
||||||
|
@ -67,6 +76,12 @@ const handler = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the metadata associated with the pointer. Usually the root object and
|
||||||
|
* the path.
|
||||||
|
*
|
||||||
|
* @param p The pointer.
|
||||||
|
*/
|
||||||
export const getPointerMeta = (
|
export const getPointerMeta = (
|
||||||
p: Pointer<$IntentionalAny> | Pointer<{}> | Pointer<unknown>,
|
p: Pointer<$IntentionalAny> | Pointer<{}> | Pointer<unknown>,
|
||||||
): PointerMeta => {
|
): PointerMeta => {
|
||||||
|
@ -77,6 +92,18 @@ export const getPointerMeta = (
|
||||||
return meta
|
return meta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the root object and the path of the pointer.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const {root, path} = getPointerParts(pointer)
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param p The pointer.
|
||||||
|
*
|
||||||
|
* @returns An object with two properties: `root`-the root object or the pointer, and `path`-the path of the pointer. `path` is an array of the property-chain.
|
||||||
|
*/
|
||||||
export const getPointerParts = (
|
export const getPointerParts = (
|
||||||
p: Pointer<$IntentionalAny> | Pointer<{}> | Pointer<unknown>,
|
p: Pointer<$IntentionalAny> | Pointer<{}> | Pointer<unknown>,
|
||||||
): {root: {}; path: PathToProp} => {
|
): {root: {}; path: PathToProp} => {
|
||||||
|
@ -84,25 +111,52 @@ export const getPointerParts = (
|
||||||
return {root, path}
|
return {root, path}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pointer<O>({
|
/**
|
||||||
root,
|
* Creates a pointer to a (nested) property of an {@link Atom}.
|
||||||
path,
|
*
|
||||||
}: {
|
* @remarks
|
||||||
root: {}
|
* Pointers are used to make derivations of properties or nested properties of
|
||||||
path: Array<string | number>
|
* {@link Atom|Atoms}.
|
||||||
}): Pointer<O>
|
*
|
||||||
function pointer(args: {root: {}; path?: Array<string | number>}) {
|
* Pointers also allow easy construction of new pointers pointing to nested members
|
||||||
|
* of the root object, by simply using property chaining. E.g. `somePointer.a.b` will
|
||||||
|
* create a new pointer that has `'a'` and `'b'` added to the path of `somePointer`.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* // Here, sum is a derivation that updates whenever the a or b prop of someAtom does.
|
||||||
|
* const sum = prism(() => {
|
||||||
|
* return val(pointer({root: someAtom, path: ['a']})) + val(pointer({root: someAtom, path: ['b']}));
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Note, atoms have a convenience Atom.pointer property that points to the root,
|
||||||
|
* // which you would normally use in this situation.
|
||||||
|
* const sum = prism(() => {
|
||||||
|
* return val(someAtom.pointer.a) + val(someAtom.pointer.b);
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param args The pointer parameters.
|
||||||
|
* @param args.root The {@link Atom} the pointer applies to.
|
||||||
|
* @param args.path The path to the (nested) property the pointer points to.
|
||||||
|
*
|
||||||
|
* @typeParam O The type of the value being pointed to.
|
||||||
|
*/
|
||||||
|
function pointer<O>(args: {root: {}; path?: Array<string | number>}) {
|
||||||
const meta: PointerMeta = {
|
const meta: PointerMeta = {
|
||||||
root: args.root as $IntentionalAny,
|
root: args.root as $IntentionalAny,
|
||||||
path: args.path ?? [],
|
path: args.path ?? [],
|
||||||
}
|
}
|
||||||
const hiddenObj = {}
|
const hiddenObj = {}
|
||||||
pointerMetaWeakMap.set(hiddenObj, meta)
|
pointerMetaWeakMap.set(hiddenObj, meta)
|
||||||
return new Proxy(hiddenObj, handler) as Pointer<$IntentionalAny>
|
return new Proxy(hiddenObj, handler) as Pointer<O>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default pointer
|
export default pointer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether `p` is a pointer.
|
||||||
|
*/
|
||||||
export const isPointer = (p: $IntentionalAny): p is Pointer<unknown> => {
|
export const isPointer = (p: $IntentionalAny): p is Pointer<unknown> => {
|
||||||
return p && !!getPointerMeta(p)
|
return p && !!getPointerMeta(p)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,19 @@ import Tappable from './Tappable'
|
||||||
type Tapper<V> = (v: V) => void
|
type Tapper<V> = (v: V) => void
|
||||||
type Untap = () => void
|
type Untap = () => void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event emitter. Emit events that others can tap (subscribe to).
|
||||||
|
*/
|
||||||
export default class Emitter<V> {
|
export default class Emitter<V> {
|
||||||
private _tappers: Map<any, (v: V) => void>
|
private _tappers: Map<any, (v: V) => void>
|
||||||
private _lastTapperId: number
|
private _lastTapperId: number
|
||||||
readonly tappable: Tappable<V>
|
|
||||||
private _onNumberOfTappersChangeListener: undefined | ((n: number) => void)
|
private _onNumberOfTappersChangeListener: undefined | ((n: number) => void)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Tappable associated with this emitter. You can use this to tap (subscribe to) events emitted.
|
||||||
|
*/
|
||||||
|
readonly tappable: Tappable<V>
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this._lastTapperId = 0
|
this._lastTapperId = 0
|
||||||
this._tappers = new Map()
|
this._tappers = new Map()
|
||||||
|
@ -39,16 +46,37 @@ export default class Emitter<V> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit a value.
|
||||||
|
*
|
||||||
|
* @param payload The value to be emitted.
|
||||||
|
*/
|
||||||
emit(payload: V) {
|
emit(payload: V) {
|
||||||
this._tappers.forEach((cb) => {
|
this._tappers.forEach((cb) => {
|
||||||
cb(payload)
|
cb(payload)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the emitter has tappers (subscribers).
|
||||||
|
*/
|
||||||
hasTappers() {
|
hasTappers() {
|
||||||
return this._tappers.size !== 0
|
return this._tappers.size !== 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to execute when the number of tappers (subscribers) changes.
|
||||||
|
*
|
||||||
|
* @callback Emitter~numberOfTappersChangeHandler
|
||||||
|
*
|
||||||
|
* @param {number} n The current number of tappers (subscribers).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls callback when the number of tappers (subscribers) changes.
|
||||||
|
*
|
||||||
|
* @param {Emitter~requestCallback} cb The function to be called.
|
||||||
|
*/
|
||||||
onNumberOfTappersChange(cb: (n: number) => void) {
|
onNumberOfTappersChange(cb: (n: number) => void) {
|
||||||
this._onNumberOfTappersChangeListener = cb
|
this._onNumberOfTappersChangeListener = cb
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,9 @@ interface IProps<V> {
|
||||||
|
|
||||||
type Listener<V> = ((v: V) => void) | (() => void)
|
type Listener<V> = ((v: V) => void) | (() => void)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a data-source that can be tapped (subscribed to).
|
||||||
|
*/
|
||||||
export default class Tappable<V> {
|
export default class Tappable<V> {
|
||||||
private _props: IProps<V>
|
private _props: IProps<V>
|
||||||
private _tappers: Map<number, {bivarianceHack(v: V): void}['bivarianceHack']>
|
private _tappers: Map<number, {bivarianceHack(v: V): void}['bivarianceHack']>
|
||||||
|
@ -55,6 +58,11 @@ export default class Tappable<V> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tap (subscribe to) the data source.
|
||||||
|
*
|
||||||
|
* @param cb The callback to be called on a change.
|
||||||
|
*/
|
||||||
tap(cb: Listener<V>): Untap {
|
tap(cb: Listener<V>): Untap {
|
||||||
const tapperId = this._lastTapperId++
|
const tapperId = this._lastTapperId++
|
||||||
this._tappers.set(tapperId, cb)
|
this._tappers.set(tapperId, cb)
|
||||||
|
|
|
@ -54,5 +54,23 @@ import {parse as parseJsonC} from 'jsonc-parser'
|
||||||
(pkg) => $`yarn workspace ${pkg} run build:api-json`,
|
(pkg) => $`yarn workspace ${pkg} run build:api-json`,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
We replace the Pointer name with a similar-looking one so that that shitty
|
||||||
|
api-documenter generates two different pages for Pointer and pointer, instead
|
||||||
|
of overwriting one with the other.
|
||||||
|
|
||||||
|
Apparently any non-english character, including this one will break links,
|
||||||
|
probably due to some overzealous regex. Didn't find any replacement that doesn't
|
||||||
|
change the look of the name AND doesn't break links, however the below code does
|
||||||
|
replace links too, in case we find something in the future. For now, we shouldn't
|
||||||
|
@link to the Pointer type in TSDoc comments.
|
||||||
|
*/
|
||||||
|
const replacement = 'Pointer\u200E'
|
||||||
|
|
||||||
|
fs.writeFileSync('./.temp/api/dataverse.api.json', fs.readFileSync('./.temp/api/dataverse.api.json', {
|
||||||
|
encoding: 'utf-8',
|
||||||
|
}).replaceAll('"name": "Pointer"', `"name": "${replacement}"`).replaceAll('{@link Pointer}', `{@link ${replacement}}`))
|
||||||
|
|
||||||
await $`api-documenter markdown --input-folder ${pathToApiJsonFiles} --output-folder ${outputPath}`
|
await $`api-documenter markdown --input-folder ${pathToApiJsonFiles} --output-folder ${outputPath}`
|
||||||
})()
|
})()
|
||||||
|
|
Loading…
Reference in a new issue