Security News
GitHub Removes Malicious Pull Requests Targeting Open Source Repositories
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
@figliolia/galena
Advanced tools
A performant state management library supporting mutable state, batched updates, middleware and a rich development API
Lightning fast platform agnostic state! Galena is a one-stop-shop for creating reactive state that supports your global application or individually isolated features. Galena operates on the premise of pub-sub and mutability allowing for significantly higher performance over more traditional state management utilities.
In Galena, your state architecture is a composition of reactive units that can be declared at any point in your application lifecycle. Your units of state can exist in isolation (similar to React Contexts) or as part of one or more global application states (similar to Redux). Galena offers a global application state solution that supports lazy initialization of state, performant updates, and a rich development API.
npm install --save @figliolia/galena
# or
yarn add @figliolia/galena
Creating a "global" application state begins with initializing a Galena
instance. Your Galena
instances act as a container from which units of state can be composed:
// AppState.ts
import { Galena, Logger, Profiler } from "@figliolia/galena";
import type { Middleware } from "@figliolia/galena";
const middleware: Middleware[] = [];
if(process.env.NODE_ENV === "development") {
middleware.push(new Logger(), new Profiler())
}
// Initialize Galena State!
export const AppState = new Galena(middleware);
Now that we have our AppState
instance, let's compose a unit of state for it!
// NavigationState.ts
import { AppState } from "./AppState.ts";
// Compose a unit of state attached to your Galena Instance
export const NavigationState = AppState.composeState("navigation", {
// initial state
currentRoute: "/",
userID: "123",
permittedRoutes: ["**/*"]
});
Creating units of state using AppState.composeState()
will scope your new unit of state to your Galena
instance. You can then access, subscribe, and update your state using using your Galena
instance or the unit of State
returned from AppState.composeState()
:
// BusinessLogic.ts
import { AppState } from "./AppState.ts";
const subscription = AppState.subscribe("navigation", navigationState => {
// React to changes to Navigation state
});
// Set Navigation State!
AppState.update("navigation", state => {
state.currentRoute = "/contact-us";
});
// Clean up subscriptions
AppState.unsubscribe("navigation", subscription);
// BusinessLogic.ts
import { NavigationState } from "./NavigationState";
const subscription = NavigationState.subscribe(state => {
// React to changes to Navigation state
});
// Set Navigation State!
NavigationState.update(state => {
state.currentRoute = "/contact-us";
});
// Clean up subscriptions
NavigationState.unsubscribe(subscription);
Running mutations on individual units of state will automatically update your Galena
instance's state! Your Galena
instance will internally track changes to each unit of state that it composes.
You may also create units of state that are not connected to a "global" Galena
instance. To promote flexibility for developers to organize their state however they wish, Galena
exports its State
object for usage directly:
import { State } from "@figliolia/galena";
// Create Your Isolated Unit of State
const FeatureState = new State("myFeature", {
// initial state
list: [1, 2, 3];
});
const subscription = FeatureState.subscribe((state) => {
// React to FeatureState changes!
});
FeatureState.update((state) => {
// Update feature state!
state.list.push(state.list.length);
});
// Clean up subscriptions
FeatureState.unsubscribe(subscription);
The API for isolated units of State is the same as the API for units connected to a Galena
instance. When using Galena
it's totally normal to have one or more "global" states along side any number of island states for isolate-able features. The composition patterns that can be used for your application state are virtually limitless!
Instances of Galena
behave as a container for one or more units of State
. Your Galena
instance is designed to replicate the "global" application state pattern, but without the overhead of
In Galena
, your "global" application state exists in the form of operable sub-structures that can be individually subscribed to and mutated. This means, mutating one piece of your State
does not effect other units of your State
. This allows for the relief of several performance bottlenecks that are common in state management libraries that offer "global" application states. In Galena
, you get the performance of island architecture with the option to also have a predictable global application state.
import { Galena, Logger, Profiler } from "@figliolia/galena";
import type { State } from "@figliolia/galena";
const AppState = new Galena(/* middleware */ [new Logger(), new Profiler()]);
/**
* Compose State
*
* Creates a unit of `State` connected to your `Galena` instance.
* Returns a unit of `State`
*/
AppState.composeState("nameOfState" /* unique name */, /* initial state */, /* Optional Model */);
/**
* Get
*
* Returns a connected unit of `State` by name
*/
AppState.get("nameOfState");
/**
* Update
*
* Mutates a unit of state by name. This method by default
* uses internal batching in order to optimize the dispatching
* of state changes to consumers. This method is the most
* performant way to mutate state in Galena!
*/
AppState.update("nameOfState", (state) => {});
/**
* Background Update
*
* Runs a higher-priority mutation on a unit of state. This method
* will bypass batching in favor of a scheduled propagation of
* changes to subscribers of your state. This method is great for
* prioritizing state updates driven by frequent user-input such
* as typing into a form or game logic.
*/
AppState.backgroundUpdate("nameOfState", (state) => {});
/**
* Priority Update
*
* Runs a highest-priority mutation on a unit of state. This method
* bypasses all batching and scheduling optimizations in Galena.
* When using `priorityUpdate()` your state changes are immediately
* propagated to your state subscribers ahead of all scheduled and
* batched updates. This method is great for usage with external
* scheduling mechanisms such as `requestAnimationFrame`, `intervals`,
* and/or `timeouts`
*/
AppState.priorityUpdate("nameOfState", (state) => {});
/**
* Subscribe
*
* Registers a subscription on a unit of state
*/
const subscription = AppState.subscribe("nameOfState", (state) => {});
/**
* Unsubscribe
*
* Closes an open subscription given a subscription ID
* returned by `new Galena().subscribe()`
*/
AppState.unsubscribe(subscription);
/**
* Subscribe All
*
* Registers a global subscription on each State registered to
* your Galena instance. Your callback will be invoked any
* time a unit of state is updated
*/
const subscription = AppState.subscribeAll(appState => {});
/**
* Unsubscribe All
*
* Closes an open global subscription by subscription ID
*/
AppState.unsubscribeAll(subscription);
While instances of Galena
behave as a container for units of state, the State
interface serves as the unit itself. The State
interface has a predictable API designed to make composing your states simple and effective. Whether you compose your state using a "global" state or island architecture, the underlying API for your units of state look like the following:
import { State, Logger, Profiler } from "@figliolia/galena";
const MyState = new State(/* a unique name */ "myState", /* initial state */);
/**
* Get State
*
* Returns the current state
*/
MyState.getState();
/**
* Update
*
* Mutates the unit of state using the callback provided. This method
* by default uses internal task batching in order to optimize
* dispatching state changes to consumers. This method is the most
* performant way to mutate state in Galena!
*/
MyState.update((currentState, initialState) => {
currentState.someValue = "new value!"
});
/**
* Background Update
*
* Runs a higher-priority mutation on your state. This method
* will bypass batching in favor of a scheduled propagation of
* changes to subscribers of your state. This method is great for
* prioritizing state updates driven by frequent user-input such
* as typing into a form or game logic.
*/
MyState.backgroundUpdate((currentState, initialState) => {
currentState.someValue = "new value!"
});
/**
* Priority Update
*
* Runs a highest-priority mutation on your state. This method
* bypasses all batching and scheduling optimizations in Galena.
* When using `priorityUpdate()` your state changes are immediately
* propagated to your state subscribers ahead of all scheduled and
* batched updates. This method is great for usage with external
* scheduling mechanisms such as `requestAnimationFrame`, `intervals`,
* and/or `timeouts`
*/
MyState.priorityUpdate((currentState, initialState) => {
currentState.someValue = "new value!"
});
/**
* Reset
*
* Resets the current state back to its initial state
*/
MyState.reset();
/**
* Register Middleware
*
* Applies any number of Middleware instances to your State
*/
MyState.registerMiddleware(/* Middleware */ new Logger(), new Profiler());
/**
* Subscribe
*
* Given a callback, invokes the callback each time your state
* changes. Returns a subscription ID
*/
const subscription = MyState.subscribe(state => {});
/**
* Unsubscribe
*
* Closes an open subscription given a subscription ID
* returned by `new State().subscribe()`
*/
MyState.unsubscribe(subscription);
/**
* Mutation (protected)
*
* This method can be used to wrap arbitrary functions that
* when invoked will:
* 1. Notify your subscriptions with the latest state
* 2. Execute any registered middleware (such as loggers or
* profiling tools)
*
* Using this method, developers can trigger or extend `Galena`'s
* internals for dispatching events and mutating state to create
* proprietary processes for an individual unit of state.
*
* In order to access this method, you'll need to extend `State`
* using:
*
* ```typescript
* class MyState extends State {
* proprietaryMutation = this.mutation((...anyArgs) => {
* // any logic
* });
* }
* ```
*/
MyState.mutation(/* callback */);
Galena supports developers creating enhancements for their usage of Galena
. Out of the box Galena
comes with middleware for Logging and Profiling that can be used for making development with Galena
more intuitive. To opt into Galena
's built-in middleware, simply pass them to your Galena
instance when calling new Galena()
Galena comes with a redux-style state transition logger that prints to the console each time state updates. The Logger will log the previous state, the current state, and tell you which unit of State
has changed.
import { Galena, Logger } from "@figliolia/galena";
// Enable logging!
const AppState = new Galena([new Logger()]);
Galena also comes with a Profiler that can track the duration of all state transitions. When a state transition exceeds 16ms, a warning is printed to the console notifying the developer of a potential bottleneck in his or her application. By default the Profiler will log each time a state transition exceeds one full frame (16ms). This threshold can be adjusted by calling new Profiler(/* any number of milliseconds */)
import { Galena, Profiler } from "@figliolia/galena";
const AppState = new Galena([new Profiler()]);
Similar to a lot of stateful tools, Galena
also exposes an API for creating your own Middleware. With it, you can do a lot of cool things for both development and productions environments. Let's first look at how to use middleware in Galena
, then we'll walk through creating our own!
When applying middleware in Galena
, you may choose to apply your middleware to all of your application state or just some of it. To apply middleware to each of your units of State
, you can simply initialize Galena
with the middleware that you enjoy using:
import { Galena, Profiler, Logger } from "@figliolia/galena";
export const AppState = new Galena([new Profiler(), new Logger()]);
Using this method, whenever you create a new unit of state using AppState.composeState()
, your Profiler
and Logger
will automatically register themselves on your new unit of State.
Alternatively, you may also choose to register a middleware on only some of your state:
import { Galena, Profiler, Logger } from "@figliolia/galena";
// Let's add logging to all of our units of State
export const AppState = new Galena([new Logger()]);
// Lets create an arbitrary unit of state and add profiling to it
export const FrequentlyUpdatedState = AppState.composeState("complexState", {
bigData: new Array(10000).fill("")
});
// Profiling is applied only to this unit of state
FrequentlyUpdatedState.registerMiddleware(new Profiler());
Galena's middleware architecture operates on fixed set of events that are triggered each time a state mutation takes place. When state is mutated in your application, the Middleware
's onBeforeUpdate()
and onUpdate()
methods are called. Using these events, you can compose logic for auditing and enhancing your state updates! Let's take a look at a real world example.
Let's say we have an application that does not use typescript and we want to achieve type-safety for a unit of our application state. To achieve this, we'll create a middleware that validates changes to our state in real time.
In the example below, we'll create a unit of state holding unique identifiers for users that are connections of the current user:
export const CurrentUserState = AppState.composeState("currentUser", {
userID: 1,
username: "currentUser",
connectedUsers: ["2", "3", "4", "5"]
});
Next, let's create our own custom middleware for ensuring that all entries in the connectedUsers
array are strings:
import { Middleware } from "@figliolia/galena";
// Let's extend the Middleware class from the Galena library
export class ConnectedUsersMiddleware extends Middleware {
// A cache for the length of the array we want to audit
private totalArrayElements: number | null = null;
// On each update, let's cache the length of the array
override onBeforeUpdate({ state }: State) {
this.totalArrayElements = state.connectedUsers.length;
}
// When an update to state occurs let's check if the length
// of the connectedUsers array has changed
override onUpdate({ state }: State) {
const connectedUsers = state.connectedUsers
if(
this.totalArrayElements === null ||
connectedUsers.length === this.totalArrayElements
) {
return;
}
// If the length of user connections has changed, let's validate that
// the new connection is, in fact, a string.
const newConnection = connectedUsers[connectedUsers.length - 1];
if(typeof newConnection !== "string") {
// If we find anything other than a string, let's log or throw an error
console.error(`A ${typeof newConnection} was added to the current user's connection array! This can create a bug in production!`)
}
}
}
Next let's bring this middleware into our application!
import { State } from "@figliolia/galena";
import { ConnectedUsersMiddleware } from "./ConnectedUsersMiddleware";
export const CurrentUserState = AppState.composeState("currentUser", {
userID: 1,
username: "currentUser",
connectedUsers: ["2", "3", "4", "5"]
});
if(process.env.NODE_ENV !== "production") {
// Let's apply our custom middleware in development environments
CurrentUserState.registerMiddleware(new ConnectedUsersMiddleware());
}
The Galena
library is designed to promote extension of its features. In doing so, it's possible to achieve a very strong Model/Controller layer for your applications. I'm going to demonstrate a few techniques for not only utilizing Galena
as is, but building proprietary Models and Controllers for your applications.
Galena's State
interface is designed to be an out-of-the-box solution for housing any portion of your application's state. There are benefits however, to extending its functionality to compose proprietary models for your units of state:
// UserModel.ts
import { State } from "@figliolia/galena";
// Let's extend the `State` class for a hypothetical
// user schema
export class UserModel extends State<{
userID: string;
username: string;
connectedUsers: string[];
}> {
public addConnection(userID: string) {
this.update(state => {
state.connectedUsers.push(userID);
});
}
public updateUsername(username: string) {
this.update(state => {
state.username = username;
});
}
}
Next, let's use our Model!
// AppState.ts
import { Galena, State } from "@figliolia/galena";
import { UserModel } from "./UserModel";
export const AppState = new Galena(/* middleware */);
// Let's apply our UserModel to AppState
export const UserState = AppState.composeState("currentUser", {
userID: 1,
username: "currentUser",
connectedUsers: ["2", "3", "4", "5"]
}, UserModel); // Specify the UserModel here so that our new unit is created using the `UserModel` instead of `State`
Now that we have our current user in our Galena State, we can create subscriptions and updates!
import { AppState } from "./AppState";
const subscriptionID = AppState.subscribe("currentUser", state => {
// React to changes to the current user!
});
// The UserModel's custom methods are available on your Galena state!
AppState.get("currentUser").updateUsername("awesomeUser");
AppState.get("currentUser").addConnection("6");
Using this extension pattern, each unit of State
can exist as it's own model with abstractions for proprietary mutations and business logic. Although slightly more complex on the surface, this pattern in very large applications will reduce the complexity of state management significantly. It'll also replicate what one might find at the persistence layer of server-side code - where persisted data structures are often modeled along side their mutation logic when interacting with a database or GQL Resolver. Because of this, the extension pattern's syntactical uniformity may be beneficial for teams that lean fullstack instead of frontend/backend!
In several areas of the Galena
readme, there are references to performance and the relief of bottlenecks. In this section I'd like to share some hard numbers relating to areas of Galena
's architecture.
In Galena, state mutations can occur in-place (O(1)
space). While you can use immutable data structures if you like, it's not required when using this library - by design. This is because even the most basic immutable state updates are about 4-5x slower than mutable state updates. This 4-5x
balloons even larger the more your state grows. When building Galena
, I wanted to remove the notion of immutability wherever possible.
To further promote efficient state mutations, Galena
's composition architecture allows units of state to be operable without effecting adjacent units of state. This means you can can safely make extremely frequent updates to your state and be sure that your updates are scoped to specific units - and not your entire application state. This optimization extends to subscriptions as well. The only subscriptions that will ever be triggered when state changes, are the ones directly bound to the unit that is changing.
Using 2 identical applications, I've profiled the performance of Galena vs. Redux using 10,000 state updates and 10 connected React Components that'll rerender on each state change. The results looked like the following:
33.6ms
to complete 10,000 state updates5.5ms
to complete 10,000 state updatesAs the application scales with more state updates and connected components, the spread between Galena
and Redux grows even further. Although I don't believe most applications will ever require 10,000 immediate state updates (unless building a game-like experience), Galena
does relieve the bottle-necks of popular state management utilities quite well.
Galena
provides bindings for React through react-galena. This package provides factories for generating HOC's and hooks from your Galena instances and units of State!
FAQs
A performant state management library supporting mutable state, batched updates, middleware and a rich development API
The npm package @figliolia/galena receives a total of 6 weekly downloads. As such, @figliolia/galena popularity was classified as not popular.
We found that @figliolia/galena demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.