npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@boninger-works/state

v10.1.0

Published

Provides classes for defining and accessing complex state models in a type-safe manner.

Downloads

85

Readme

State

Build Status

Library for defining, updating, and accessing data models in a reactive and type-safe manner.

Because JavaScript is not a strongly-typed language, this library is intended to be used with TypeScript.

Installation

npm install @boninger-works/state

Usage

Add core imports.

import { State, IState, IStateReadOnly }
	from "@boninger-works/state/library/core";

Instantiating

Define an interface to represent the state.

interface IOrder {
	items: string[];
	shipping: {
		strategy: "ShipItemsTogether" | "ShipItemsImmediately";
		speed: "OneDay" | "TwoDay" | "Standard";
	};
	offerCode?: string;
	giftWrap?: {
		giftReceipt: boolean;
		personalMessage: string;
	};
}

Create the generic state instance based the interface.

const state = State.create<IOrder>({
	items: [],
	shipping: {
		strategy: "ShipItemsTogether",
		speed: "Standard"
	}
});

Subscribing

Subscribe to the entire state

const observable = state.observe();
observable.subscribe(order => {
	console.log(order, "ENTIRE_STATE");
});

Subscribe to part of the state. The "items" string below is strongly typed (not a magic string!).

const observable = state.to("items").observe();
observable.subscribe(items => {
	console.log(items, "ONLY_ITEMS");
});

Subscribe to a nested part of the state. Note that the to() method can be chained to continue to define the desired path.

const observable = state.to("shipping").to("speed").observe();
observable.subscribe(shipSpeed => {
	console.log(shipSpeed, "ONLY_SHIP_SPEED");
});

Handling Undefined

Keys provide return types that are aware of possibly incomplete paths. This occurs if an ancestor of the desired property can potentially be undefined or null. As a result, the keys type will reflect this by adding undefined as part of the generic type.

Without TypeScript's strictNullChecks compiler option enabled, this effect is not visible because anything can always be undefined or null.

const path = state.to("giftWrap").to("giftReceipt");
const observable = path.observe();
observable.subscribe(giftReceipt => {
	console.log(giftReceipt, "GIFT_RECEIPT_OR_UNDEFINED");
});

However, it can be desirable to deal with only defined values. In this case, the defined() operator can be used to filter the observable created from the keys.

Add operator imports.

import { defined }
	from "@boninger-works/state/library/operators";
const path = state.to("giftWrap").to("giftReceipt");
const observable = path.observe().pipe(defined());
observable.subscribe(giftReceipt => {
	console.log(giftReceipt, "GIFT_RECEIPT");
});

Getting

Get the state. Returns the current version of the state.

const order = state.get();
console.log(order);

Updating

Update the entire state.

state.set({
	items: [],
	shipping: {
		strategy: "ShipItemsImmediately"
		speed: "OneDay"
	}
});

Update part of the state.

state.to("items").set([ 
	"popularBook",
	"goodMovie",
	"sweetMusic"
]);

Batch update the state.

state.batch(batch => {
	batch.to("offerCode").set("EVERYTHING4FREE");
	batch.to("giftWrap").set({
		giftReceipt: true,
		personalMessage: "Here is something special for you!"	
	});
});

Transforming

Add transform imports.

import { 
	push, unshift, pop, shift, filter, insert, remove,
	increment, decrement, add, subtract, multiply, divide,
	set, unset
}
from "@boninger-works/state/library/transforms";

Tranform part of the state. In this case, add an item to the array of items.

state.to("items").transform(push("tastyTreat"));

Opting Out of Type-Safety

If the need arises, keys can be created in a non-type-safe manner. Note that the keysUnsafe method takes a single array argument, which is different from the keys method, which builds type-safe keys.

const keysUnsafe = state.keysUnsafe(["one", "two", "three"]);
const observableUnsafe = state.path(keysUnsafe).observe();
observable.subscribe(anything => {
	console.log(anything, "ANYTHING");
});

Remarks

Emissions

When the observe() method is used to subscribe to part of a state, the returned observable is automatically configured in the following ways:

  1. It will immediately emit the current value for the defined path.
  2. It will emit any future changes to that value.

The observable is configured to act only on the specific slice of the state that was requested. For instance, if items is subscribed to in the above examples, then updates to the shipping property of the state will not emit for the items subscriber because the value of items has not changed.

Batching

When batching updates, the state batcher is immediately updated with each call to the set() or transform() method, but the value is not set and emitted for the state being acted on until the very end. Batching can be used to solve one of the following two problems:

  1. Small frequent updates are causing too many emissions to subscribers.
  2. Individual updates would make a state emit an object which is not correct until other updates are also applied.

Batch operations can also be emitted on demand in the middle of the batch using the emit() method.

The current value of the batch state can be retrieved with get() during the batch operation.

Read Only

The IStateReadOnly interface provides read-only state functionality. This is available through the readOnly property on IState. It is preferable to use the IStateReadOnly stored on the readOnly property because it has better protection from unwanted writes than IState itself.

Immutability

The intent of the state is always to avoid mutating references that are stored within it. Methods provided by this library will always create shallow copies of reference types to avoid mutations while avoiding unnecessary memory allocations for references that do not need modification. However, it is also important to understand that reference types retrieved from the state do not have any inherent protection. Enforcing or respecting immutability of these references is the responsibility of the the implementer in this case, because the state cannot control the generic type that is provided to it.