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

consistate

v0.1.0

Published

Consistate: Transactional, Reactive, and Asynchronous State Management and Rendering for JavaScript

Downloads

8

Readme

Consistate: Transactional, Reactive, and Asynchronous State Management and Rendering for JavaScript

About

Open source project of Nezaboodka Software

Demo: https://nezaboodka.gitlab.io/consistate-demo

Inspired by: MobX, Nezaboodka, Excel

Consistate is a JavaScript state management library, which combines the power of reactive, transactional, and asynchronous programming models to simplify and improve productivity of Web UI development. Consistate pushes changes from state (data model) to corresponding UI elements for re-rendering in a seamless, consistent, real-time, and fine-grained way. This is achieved by three basic concepts: state, transaction, and reaction.

State is a set of regular JavaScript objects, which are treated as primary source of data for an application.

Transaction is a unit of work, possibly asynchronous, that makes changes to application state. The changes are made in an isolated data snapshot and become visible only when transaction is successfully completed and committed.

Reaction is a function, which is called on completion of a transaction that changed application state. UI rendering function is a good example of reaction. The result of reaction function is treated as derived value from application state and is cached. Subsequent calls of reaction function return cached result until the application state is changed by any other transaction. When it happens, the cached result is invalidated and the reaction function is executed again to obtain a new result.

Consistate takes full care of tracking dependencies between state (observables) and dependant reactions (observers), and provides fine-grained re-execution of reactions, either immediately or lazily. Execution of dependant reactions is fully consistent and takes place only when all state changes are successfully committed. With Consistate, you no longer need to create data change events in any objects, subscribe to these events in other objects, and manually maintain switching from previous state to new state.

Here is an example in TypeScript, which briefly illustrates the concept and its integration with React.

import { state, transaction, reaction } from "consistate";
import fetch from "node-fetch";

@state // treat all fields of the Model class as state
class Model {
  url: string = "https://gitlab.com/nezaboodka/consistate";
  content: string = "Consistate: Transactional, Reactive, and Asynchronous State Management";
  timestamp: Date = Date.now();

  @transaction // wrap method to run in transactional way
  async load(url: string): Promise<void> {
    // All the changes are made in a separate snapshot, which
    // becomes visible only when transaction is committed. 
    this.url = url;
    this.content = await fetch(url);
    this.timestamp = Date.now();
  } // transaction is completed, dependant reactions are re-executed
}

class View extends ConsistateReactComponent<Model> { // see ConsistateReactComponent below
  @reaction // wrap method to track and react to changes in its dependecies
  render(): ReactNode {
    const m: Model = this.props; // just a shortcut
    return (
      <div>
        <h1>{m.url}</h2>       
        <div>{m.content}</div>
      </div>
    );
    // render is subscribed to m.url and m.content, but not m.timestamp
  }
}

In the example above the result of the method render is cached and reused by React rendering system to avoid redundant generation of the target HTML. When the title and content fields of the data model are changed (by some other code), the existing cached HTML is invalidated and then automatically recomputed by re-executing the render method.

Consistate automatically executes reaction functions upon changes of object properties that were accessed during function execution. To do so, Consistate intercepts property getters and setters of the accessed JavaScript objects. Property getters are used to track what properties (observables) are used by a given reaction function (observer). When some of the properties are changed, the corresponding reaction function is automatically re-executed.

Multiple object properties can be changed in a transactional way - all at once with full respect to the all-or-nothing principle (atomicity, consistency, and isolation). To do so, separate data snapshot is automatically maintained for each transaction. The snapshot is logical and doesn't create full copy of all the data. Intermediate state is visible only inside transaction itself, but is not visible outside of the transaction until it is committed. Compensating actions are not needed in case of transaction failure, because all the changes made by transaction in its logical snapshot are simply discarded. In case transaction is successfully committed, affected reaction functions are invalidated and re-executed in a proper order at the end of the transaction (only when all data changes are committed).

Asynchronous operations (promises) are supported as first class citizens during transaction execution. Transaction may consist of a set of asynchronous operations that are confirmed on completion of all of them. Moreover, any asynchronous operation may spawn other asynchronous operations, which prolong transaction execution until the whole chain of asynchronous operations is fully completed. And in this case, reactions are executed only at the end of entire transaction, thus preventing intermediate inconsistent state being leaked to UI.

Here is an example of integration of Consistate and React:

import { reaction, reactionCacheOf, dismiss } from "consistate";
import * as React from "react";

class ConsistateReactComponent<P> extends React.Component<P> {
  @reaction
  autoUpdate(): void {
    // This method is automatically re-executed when
    // cached value of this.render is invalidated.
    if (Reaction.get(this.render).isInvalidated)
      this.forceUpdate();
  }

  componentDidMount(): void {
    // Mark this.autoUpdate to be re-executed automatically
    // upon invalidation due to changes of its dependencies.
    Reaction.get(this.autoUpdate).latency = 0; // react immediately
    this.autoUpdate(); // first run to identify initial dependencies
  }

  shouldComponentUpdate(nextProps: Readonly<P>): boolean {
    // Update component either if this.render is invalidated
    // or if props are different from the current ones.
    let r = Reaction.get(this.render);
    return r.isInvalidated || r.invalidate(diff(this.props, nextProps));
  }

  componentWillUnmount(): void {
    Reaction.dismissAll(this); // cleanup
  }
}

Differentiators

  • Consistency, clarity, and simplicity are the first priorities
  • Reactions, transactional actions, and asynchronous operations are first-class citizens
  • Undo/redo functionality is built-in and provided out of the box
  • It's minimalistic library, not a framework

API (TypeScript)

// Decorators

export function state(target, prop?): any; // class or field
export function stateless(target, prop): any; // field only
export function transaction(target, prop, pd): any; // method only
export function separateTransaction(target, prop, pd): any; // method only
export function reaction(target, prop, pd): any; // method only

// Transaction

export type F<T> = (...args: any[]) => T;

export class Transaction {
  constructor(hint: string);
  run<T>(func: F<T>, ...args: any[]): T;
  wrap<T>(func: F<T>): F<T>;
  commit(): void;
  seal(): Transaction; // t1.seal().whenFinished().then(fulfill, reject)
  discard(error?: any): Transaction; // t1.seal().whenFinished().then(...)
  finished(): boolean;
  whenFinished(): Promise<void>;
  static run<T>(func: F<T>, ...args: any[]): T;
  static runAs<T>(hint: string, root: boolean, func: F<T>, ...args: any[]): T;
  static get current(): Transaction;
  static debug: number = 0; // 0 = off, 1 = brief, 2 = normal, 3 = noisy, 4 = crazy
}

// Reaction

export abstract class Reaction {
  latency: number;
  monitor: Monitor | undefined;
  readonly cachedResult: any;
  readonly cachedAsyncResult: any;
  readonly cachedError: any;
  readonly invalidator: string | undefined;
  invalidate(invalidator: string | undefined): boolean;
  readonly isInvalidated: boolean;
  static get(method: Function): Reaction;
  static dismissAll(...objects: object[]): Transaction;
}

// Monitor

@state
export class Monitor {
  readonly isIdle: boolean;
  readonly workerCount: number;
  constructor(name: string);
}