My Projects
📦 Crate
API

Create

Create a new crate instance with a default state.

Type
new Crate<T>(
    defaultState: T,
) : Crate<T>
Usage
const PlayerCrate = new Crate({
  health: 100,
  walkSpeed: 16,
});

I/O

API for updating and fetching data from the crate instance.

.update()

Update the state of the crate with a partial object.

Keys can either be set to their value type or a mutator function.

Type
Crate.update(
    Dispatch: Partial<ValOrMutator<T>> 
) : void
Usage
const PlayerCrate = new Crate({
    health: 100,
    walkSpeed: 16,
    Jumping: false
})
 
// Mutate both the health and walkspeed.
Crate.update({
    health: (value) => value + 10, // add 10 to health.
    walkSpeed: 20
})
⚠️

As of v0.0.3, the object passed into .update() may be mutated by middleware.

This is intended behavior since cloning object literals is unnecessary, but know that it can cause problems when passing objects by reference.

Example
const defaultState = {
    myVal: 0,
}
 
const crate = new Crate(defaultState)
 
crate.useMiddleware("myVal", (o, n) => n + 5)
 
// ❌ If we pass the object directly, the middleware **will** alter it.
crate.update(defaultState);
 
// ✅ Instead, let's pass a copy of the object:
crate.update({...defaultState});

.get()

Get a deep-copy of the original store, or just a single key's value.

Type
Crate.get(
    Key?: keyof T,
) : T[U]
Usage
const PlayerCrate = new Crate({
  health: 100,
  walkSpeed: 16,
  jumping: false,
});
 
// Mutate both the health and walkspeed.
PlayerCrate.update({
  health: (value) => value + 10, // add 10 to health.
  walkSpeed: 20,
});
 
print(PlayerCrate.get("health")); // 110

Listeners and Mutators

Crates have two types of listeners help you observe and mutate your state.

.useMiddleware()

middleware is invoked before state update to mutate the incoming state before it is dispatched.

Any value returned from it's callback will be pushed to the crate.

⚠️

You should never yeild under any circumstances within middleware.

Type
Crate.useMiddleware(
    Key: keyof T,
    Callback: (oldValue: T[U], newValue: T[U]) => T[U]
) : void
Usage
const playerState = new Crate({
  health: 100,
  maxHealth: 100,
});
 
// Simple middleware that prevents the health from going out of range.
playerState.useMiddleware("health", (oldValue, newValue) => {
  return math.clamp(newValue, 0, playerState.get("maxHealth"));
});

.onUpdate()

onUpdate() is invoked after middleware, and only if the state has changed.

Updates only if with a given Key.

Type With Key
Crate.onUpdate(
    Key: keyof T,
    Callback: (value: T[U]) => void
) : void
Usage With Key
const PlayerCrate = new Crate({
    health: 100,
});
 
PlayerCrate.onUpdate("health", (value) => {
    print(value); // 20
});
 
PlayerCrate.update("health", 20);

Execution Order

When state is changed, it goes through the pipleine in a specific order.

ExecutionModel.ts
const Store = new Crate({
    val: 10,
})
 
// 3. Finally, the onUpdate callbacks are invoked.
Store.onUpdate("val", (value) => {
    print(value) // 21
})
 
// 2. Next, it goes through middleware to be mutated.
Store.useMiddleware("val", (oldValue, newValue) => {
    return newValue + 1
})
 
// 1. First, the value is set
Store.update({
    val: 20,
})

Utilities

If you decide to stop using your crate instance for whatever reason, there are some utilities to ensure it's done properly.

.cleanup()

Clean all internal objects and connections.

⚠️

This is final. Attemtping to call update() after cleanup() will cause an error.

Usage
Crate.cleanup();