type ObjectKey: number | string | symbol

No description.

type FilterFunction: (obj: object, path: ObjectKey[]) => boolean

No description.

type Endpoint: Record<ObjectKey, any> | Map<ObjectKey, any>

No description.

type Target<KV>: {
[Key in keyof KV]: KV[Key]
}

No description.

type Configuration<KV extends Record<ObjectKey, any>>: Options<KV> | [, Options<KV>]

Central configuration for registering properties.

If given an array, properties will be registered separately for each configuration, each pointing to the next by linking their endpoints and targets. This results in multiple intermediate sequential data storages, propagated from last to first.

Example

const storage = new ReactiveStorage([
  { getter: ({ val }) => Math.round(val / 50) * 50 },
  { getter: ({ val }) => Math.round(val / 5) * 5 },
]);
storage.registerRecursive('value', 62);

console.log(storage.targets[1].value) // 60 // Second getter turns 62 into 60 console.log(storage.targets[0].value) // 50 // Second getter turns 62 into 60

See also OptionsWhole

type Options<KV extends Record<ObjectKey, any>>: {
[Prop in keyof OptionsWhole<KV>]: Prop extends "depth"
? number | Options<KV> : OptionsWhole<KV>[Prop]
?
}

No description.

class ReactiveStorage<KV extends Record<ObjectKey, any>>

ReactiveStorage<KV extends Record<ObjectKey, any>>(
config: Configuration<KV> = {}
): ReactiveStorage<KV>

No description.

readonly endpoint: Endpoint

The endpoint holding the actual data of the registered properties.

See also OptionsWhole.endpoint

readonly target: Target<KV>

The first access point for registered properties. Always the first element of targets.

See also OptionsWhole.target

readonly targets: Target<KV>[]

All access points for registered properties in sequential order. This is only relevant if having defined multiple configurations, and such, multiple intermediate storage points. Otherwise target can be used as well.

See also OptionsWhole.targets

readonly config: OptionsWhole<KV>[]

No description.

static readonly Filter: {
objectLiteralOrArray: (obj: object) => boolean,
any: () => true
}

See also Filter

has(key: ObjectKey): boolean

Check for existence of a registered property on target.

delete(key: ObjectKey): boolean

Delete target and endpoint entry of a registered property.

register<K extends string | number | symbol>(
key: K | K[],
initialValue?: KV[K]
): ReactiveStorage<KV>

Register one or multiple reactive properties according to the current instance's configuration (config) and the given initial value, if any.

Parameters

  • key: The property name to register on target.

  • initialValue: The initial value that will be assigned after registering.

Returns The current ReactiveStorage instance for easy chaining.

Register all property keys and symbols of the given object with their respective values according to the current instance's configuration (config).

Parameters

  • object: The object the keys and symbols of will be registered.

Returns The current ReactiveStorage instance for easy chaining.

static register<
KV extends Record<K, V>,
K extends ObjectKey,
V extends unknown
>(
key: K | K[],
initialValue?: V,
config: Configuration<KV> = {}
): RegistrationData<KV>

Register a reactive property one or multiple targets that point to an endpoint. If left unspecified, target and/or endpoint will be a new object that can be obtained using the returned data.

Parameters

  • key: The property name to register.

  • initialValue: The initial value that will be assigned after registering.

static registerFrom<KV extends Record<ObjectKey, any>>(
object: KV,
config: Configuration<KV>
): RegistrationData<KV>

Register all property keys and symbols of the given object with their respective values. If left unspecified, target and/or endpoint will be a new object that can be obtained using the returned data.

Parameters

  • object: The object the keys and symbols of will be registered.

static registerRecursive<
KV extends Record<K, V>,
K extends ObjectKey,
V extends unknown
>(
key: K | K[],
initialValue?: V,
config: Configuration<KV> = {}
): RegistrationData<KV>

Same as register but register properties infinitely deep. Values (both the initial value and values assigned at a later point in time) will be recursively traversed and registered, limited by Options.deepFilter.

Shorthand for register with the the deepest configured Options.depth set to Infinity.

Parameters

  • key: The property name to register.

  • initialValue: The initial value that will be assigned after registering.

static registerRecursiveFrom<
KV extends Record<ObjectKey, any>
>(
object: KV,
config: Configuration<KV>
): RegistrationData<KV>

Same as registerFrom but register all properties within the given object infinitely deep. Values (both the initial values and values assigned at a later point in time) will be recursively traversed and registered, limited by Options.deepFilter.

Parameters

  • object: The object the keys and symbols of will be registered.

interface RegistrationData<KV extends Record<ObjectKey, any>>

The endpoint holding the actual data of the registered properties.

See also OptionsWhole.endpoint

The first access point for registered properties. Always the first element of targets.

See also OptionsWhole.target

All access points for registered properties in sequential order. This is only relevant if having defined multiple configurations, and such, multiple intermediate storage points. Otherwise target can be used as well.

See also OptionsWhole.targets

interface GetterEvent

Options.getter event argument.

val: any

Value that was fetched, from the underlying endpoint.

Key path of the property in question, starting with the registered key.

Example

const storage = new ReactiveStorage({
  getter: ({ path }) => { console.log("GET:", path) },
});
storage.registerRecursive('value', { first: { second: 3 } });

// <Logs from the initial assignment...>

storage.data.value.first.second; // "GET: ['value']" // "GET: ['value', 'first']" // "GET: ['value', 'first', 'second']"

interface SetterEvent implements PostSetterEvent

Options.setter event argument.

initial: boolean

Whether this call is propagated by the initial registration action.

prevVal: any

Previous value.

Key path of the property in question, starting with the registered key.

Example

const storage = new ReactiveStorage({
  setter: ({ path }) => { console.log("SET:", path) }
});
storage.registerRecursive('value', { first: { second: 3 } });

// <Logs from the initial assignment...>

storage.data.value.first.second = 4 // "SET: ['value', 'first', 'second']"

val: any

Value to be set.

set: (val: any) => void

Default setter that can be used to set a value different from the passed one to the expected endpoint. When using it, you should prevent the default value from being set by returning true.

interface PostSetterEvent

Options.postSetter event argument.

val: any

Value that was set.

initial: boolean

Whether this call is propagated by the initial registration action.

prevVal: any

Previous value.

Key path of the property in question, starting with the registered key.

Example

const storage = new ReactiveStorage({
  setter: ({ path }) => { console.log("SET:", path) }
});
storage.registerRecursive('value', { first: { second: 3 } });

// <Logs from the initial assignment...>

storage.data.value.first.second = 4 // "SET: ['value', 'first', 'second']"

interface OptionsWhole<KV extends Record<ObjectKey, any>>

The endpoint that the registered property points to which holds the actual data, so an object that the configured setter and getter will deposit the value to and fetch the value from, respectively.

Default

{}

An object that represents the access point for the registered properties. Values are deposited at the specified endpoint.

Default

{}

Decide whether to deeply register an object covered by depth. This is useful to mitigate registering properties within any object (class instances, DOM nodes, etc.) in favor of simpler objects – especially when making use of an infinite depth.

Be careful when changing this, especially when there is user input involved! Unrestricted recursion may lead to a significant overload or even an infinite loop when (accidentally) assigning huge objects like a DOM node.

Just like all other configuration options, this will be passed down into any depth unless overridden.

Remarks

Filter provides some useful filter functions.

Example

The first layer will deeply register any object, depth 1 and below accept only object literals or arrays.

ReactiveStorage.registerRecursive('value', 420, {
  setter: val => { console.log("SET", val) },
  depthFilter: Filter.any,
  depth: {
    depthFilter: Filter.objectLiteralOrArray
  }
});

Default

Filter.objectLiteralOrArray

enumerable?: boolean

Whether registered properties should be enumerable inside target. Corresponds to PropertyDescriptor.enumerable.

Default

true
depth?: number | Omit<OptionsWhole<Record<ObjectKey, any>>, "target">

Whether and how keys inside any object or array value should be registered such that they go through additional layers of getters and setters. If a value is reassigned, it is re-registered with the same configuration until the given depth. The immediate registered value is depth 0.

If given registration options, the registered key configuration will assume these options in their layer. Can be nested infinitely deep.

If given a number, keys will be registered recursively up until the given depth, assuming the options present in the given scope. Can be Infinity.

Example (Different options from layer 2 onwards)

const storage = new ReactiveStorage({
  setter: val => { console.log("First layer:", val) },
  depth: {
    setter: val => { console.log("Further layer:", val) },
    depth: Infinity
  }
});
storage.register('recursive', { first: { second: 3 } });

// <Logs from the initial assignment...>

storage.data.recursive = { first2: { second2: 69 } }; // "First layer: { first2: { second2: 69 } }" // "Further layer: { second2: 69 }" // "Further layer: 69"

storage.data.recursive.first2 = 70; // "Further layer: 70"

Example (Re-register a deep value)

Even though the initial hierarchy is temporarily deleted by assigning a primitive value of "4", another reassigned value is simply re-registered with the initial configuration (note that since the depth is merely set to 1, any potential value inside second2 will not be reactive).

const storage = new ReactiveStorage({
  setter: val => { console.log("SET", val) },
  depth: 1
});
storage.register('value', { first: { second: 3 } });

// "SET { first: { second: 3 } }" // "SET { second: 3 }"

storage.data.value = 4 // "SET 4"

storage.data.value = { first2: { second2: { third2: 'foobar' } } } // "SET { first2: { second2: { third2: 'foobar' } } }" // "SET second2: { third2: 'foobar' } }"

Default

0
postSetter?: (event: PostSetterEvent) => void

Called after a value has been set.

setter?: (event: SetterEvent) => boolean | void

Called before a value is set.

Return true to prevent the value from being set to the default endpoint. This can be useful to filter specific values or when setting them manually, in which case the passed SetterEvent.set is useful.

getter?: (event: GetterEvent) => any

Called anytime a value is fetched.

Remarks

This is potentially called a lot since, as per the getter/setter hierarchy, any deep value set needs to get the respective value from a layer above.

Example

const storage = new ReactiveStorage({
  getter: val => { console.log("GET", val) },
  depth: Infinity
});
storage.register('value', { first: { second: 3 } });

storage.data.value.first.second = 8 // "GET { first: { second: 3 } }" // "GET { second: 8 }" // "GET 8"

variable Filter

Provides some useful filter functions for use in Options.depthFilter.

Also exposed via ReactiveStorage.Filter.

Variable does not seem to have an implementation. Please open an issue or pull request :)