Socket
Socket
Sign inDemoInstall

aurelia-store

Package Overview
Dependencies
Maintainers
1
Versions
66
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

aurelia-store - npm Package Compare versions

Comparing version 0.24.2 to 0.25.0

2

dist/amd/store.d.ts

@@ -31,4 +31,6 @@ import { Observable } from "rxjs";

unregisterMiddleware(reducer: Middleware<T>): void;
isMiddlewareRegistered(middleware: Middleware<T>): boolean;
registerAction(name: string, reducer: Reducer<T>): void;
unregisterAction(reducer: Reducer<T>): void;
isActionRegistered(reducer: Reducer<T> | string): boolean;
dispatch(reducer: Reducer<T> | string, ...params: any[]): Promise<void>;

@@ -35,0 +37,0 @@ private handleQueue();

@@ -75,2 +75,5 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {

};
Store.prototype.isMiddlewareRegistered = function (middleware) {
return this.middlewares.has(middleware);
};
Store.prototype.registerAction = function (name, reducer) {

@@ -87,2 +90,8 @@ if (reducer.length === 0) {

};
Store.prototype.isActionRegistered = function (reducer) {
if (typeof reducer === "string") {
return Array.from(this.actions).find(function (action) { return action[1].name === reducer; }) !== undefined;
}
return this.actions.has(reducer);
};
Store.prototype.dispatch = function (reducer) {

@@ -89,0 +98,0 @@ var _this = this;

@@ -31,4 +31,6 @@ import { Observable } from "rxjs";

unregisterMiddleware(reducer: Middleware<T>): void;
isMiddlewareRegistered(middleware: Middleware<T>): boolean;
registerAction(name: string, reducer: Reducer<T>): void;
unregisterAction(reducer: Reducer<T>): void;
isActionRegistered(reducer: Reducer<T> | string): boolean;
dispatch(reducer: Reducer<T> | string, ...params: any[]): Promise<void>;

@@ -35,0 +37,0 @@ private handleQueue();

@@ -79,2 +79,5 @@ "use strict";

};
Store.prototype.isMiddlewareRegistered = function (middleware) {
return this.middlewares.has(middleware);
};
Store.prototype.registerAction = function (name, reducer) {

@@ -91,2 +94,8 @@ if (reducer.length === 0) {

};
Store.prototype.isActionRegistered = function (reducer) {
if (typeof reducer === "string") {
return Array.from(this.actions).find(function (action) { return action[1].name === reducer; }) !== undefined;
}
return this.actions.has(reducer);
};
Store.prototype.dispatch = function (reducer) {

@@ -93,0 +102,0 @@ var _this = this;

@@ -31,4 +31,6 @@ import { Observable } from "rxjs";

unregisterMiddleware(reducer: Middleware<T>): void;
isMiddlewareRegistered(middleware: Middleware<T>): boolean;
registerAction(name: string, reducer: Reducer<T>): void;
unregisterAction(reducer: Reducer<T>): void;
isActionRegistered(reducer: Reducer<T> | string): boolean;
dispatch(reducer: Reducer<T> | string, ...params: any[]): Promise<void>;

@@ -35,0 +37,0 @@ private handleQueue();

@@ -77,2 +77,5 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {

};
Store.prototype.isMiddlewareRegistered = function (middleware) {
return this.middlewares.has(middleware);
};
Store.prototype.registerAction = function (name, reducer) {

@@ -89,2 +92,8 @@ if (reducer.length === 0) {

};
Store.prototype.isActionRegistered = function (reducer) {
if (typeof reducer === "string") {
return Array.from(this.actions).find(function (action) { return action[1].name === reducer; }) !== undefined;
}
return this.actions.has(reducer);
};
Store.prototype.dispatch = function (reducer) {

@@ -91,0 +100,0 @@ var _this = this;

@@ -31,4 +31,6 @@ import { Observable } from "rxjs";

unregisterMiddleware(reducer: Middleware<T>): void;
isMiddlewareRegistered(middleware: Middleware<T>): boolean;
registerAction(name: string, reducer: Reducer<T>): void;
unregisterAction(reducer: Reducer<T>): void;
isActionRegistered(reducer: Reducer<T> | string): boolean;
dispatch(reducer: Reducer<T> | string, ...params: any[]): Promise<void>;

@@ -35,0 +37,0 @@ private handleQueue();

@@ -42,2 +42,5 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {

}
isMiddlewareRegistered(middleware) {
return this.middlewares.has(middleware);
}
registerAction(name, reducer) {

@@ -54,2 +57,8 @@ if (reducer.length === 0) {

}
isActionRegistered(reducer) {
if (typeof reducer === "string") {
return Array.from(this.actions).find((action) => action[1].name === reducer) !== undefined;
}
return this.actions.has(reducer);
}
dispatch(reducer, ...params) {

@@ -56,0 +65,0 @@ let action;

@@ -31,4 +31,6 @@ import { Observable } from "rxjs";

unregisterMiddleware(reducer: Middleware<T>): void;
isMiddlewareRegistered(middleware: Middleware<T>): boolean;
registerAction(name: string, reducer: Reducer<T>): void;
unregisterAction(reducer: Reducer<T>): void;
isActionRegistered(reducer: Reducer<T> | string): boolean;
dispatch(reducer: Reducer<T> | string, ...params: any[]): Promise<void>;

@@ -35,0 +37,0 @@ private handleQueue();

@@ -77,2 +77,5 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {

};
Store.prototype.isMiddlewareRegistered = function (middleware) {
return this.middlewares.has(middleware);
};
Store.prototype.registerAction = function (name, reducer) {

@@ -89,2 +92,8 @@ if (reducer.length === 0) {

};
Store.prototype.isActionRegistered = function (reducer) {
if (typeof reducer === "string") {
return Array.from(this.actions).find(function (action) { return action[1].name === reducer; }) !== undefined;
}
return this.actions.has(reducer);
};
Store.prototype.dispatch = function (reducer) {

@@ -91,0 +100,0 @@ var _this = this;

@@ -31,4 +31,6 @@ import { Observable } from "rxjs";

unregisterMiddleware(reducer: Middleware<T>): void;
isMiddlewareRegistered(middleware: Middleware<T>): boolean;
registerAction(name: string, reducer: Reducer<T>): void;
unregisterAction(reducer: Reducer<T>): void;
isActionRegistered(reducer: Reducer<T> | string): boolean;
dispatch(reducer: Reducer<T> | string, ...params: any[]): Promise<void>;

@@ -35,0 +37,0 @@ private handleQueue();

@@ -106,2 +106,5 @@ System.register(["rxjs", "aurelia-framework", "./history", "./middleware", "./logging"], function (exports_1, context_1) {

};
Store.prototype.isMiddlewareRegistered = function (middleware) {
return this.middlewares.has(middleware);
};
Store.prototype.registerAction = function (name, reducer) {

@@ -118,2 +121,8 @@ if (reducer.length === 0) {

};
Store.prototype.isActionRegistered = function (reducer) {
if (typeof reducer === "string") {
return Array.from(this.actions).find(function (action) { return action[1].name === reducer; }) !== undefined;
}
return this.actions.has(reducer);
};
Store.prototype.dispatch = function (reducer) {

@@ -120,0 +129,0 @@ var _this = this;

313

doc/article/en-US/aurelia-store-plugin.md

@@ -8,3 +8,3 @@ ---

This article covers the Store plugin for Aurelia. It is built on top of two core features of [RxJS](http://reactivex.io/rxjs/), namely Observables and the BehaviorSubject. You're not forced to delve into the reactive universe, in fact, you'll barely notice it at the begin but certainly can benefit a lot when using it wisely.
This article covers the Store plugin for Aurelia. It is built on top of two core features of [RxJS](http://reactivex.io/rxjs/), namely Observables and the BehaviorSubject. You don't have to delve into the reactive universe - in fact, you'll barely notice it at the beginning. But you can certainly benefit from having some knowledge about RxJS in order to use it wisely.

@@ -15,3 +15,3 @@ Various examples, demonstrating individual pieces of the plugin, can be found in the [samples repository](https://github.com/zewa666/aurelia-store-examples).

Currently, a lot of modern development approaches leverage a single store, which acts as a central basis of your app. The idea is that it holds all data, that makes up your application. The content of your store is your application's state. If you will, the app state is a snapshot of data at a specific moment in time. You modify that by using Actions, which are the only way to manipulate the global state, and create the next app state.
A lot of modern development approaches leverage a single store, which acts as a central basis of your app. The idea is that it holds all data that makes up your application. The content of your store is your application's state. If you will, the app state is a snapshot of all data at a specific moment in time. You modify that by using Actions, which are the only way to manipulate the global state, and create the next app state.

@@ -24,15 +24,15 @@ Contrast this to classic service-oriented approaches, where data is split amongst several service entities. What turns out to be a simpler approach, in the beginning, especially combined with a powerful IoC Container, can become a problem once the size of the app grows. Not only do you start to get increased complexity and inter-dependency of your services, but keeping track of who modified what and how to notify every component about a change can become tricky.

As mentioned in the intro this plugin uses RxJS as a foundation. The main idea is having a [BehaviorSubject](http://reactivex.io/rxjs/manual/overview.html#behaviorsubject) `store._state` which will store the current state, at the begin the initial state, and emit new states as they come. Since having access to the BehaviorSubject would allow consumers to directly emit the `next` value, instead of a front-facing `state` property, being an Observable which connects to the BehaviorSubject, is exposed. This way consumers only have access to streamed values but cannot directly manipulate the streaming queue.
As mentioned in the intro this plugin uses RxJS as a foundation. The main idea is having a [BehaviorSubject](http://reactivex.io/rxjs/manual/overview.html#behaviorsubject) `store._state` which will store the current state. It starts with some initial state and emits new states as they come. The BehaviorSubject cannot be accessed directly - it is defined as private - but instead a public Observable named `state` should be consumed. This way consumers only have reading access to streamed values but cannot directly manipulate the streaming queue through the Subjects `next` method.
But besides these core features, RxJS itself can be described as [*Lodash/Underscore for events*](http://reactivex.io/rxjs/manual/overview.html#introduction). As such all of the operators and objects can be used to manipulate the state in whatever way necessary. As an example [pluck](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-pluck) can be used to pierce into a sub-section of the state, whereas methods like [skip]()http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-skip and [take](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-take) are great ways to unit test the stream of states over time.
But besides these core features, RxJS itself can be described as *[Lodash/Underscore for events](http://reactivex.io/rxjs/manual/overview.html#introduction)*. As such all of the operators and objects can be used to manipulate the state in whatever way necessary. As an example [pluck](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-pluck) can be used to pierce into a sub-section of the state, whereas methods like [skip](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-skip) and [take](http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-take) are great ways to unit test the stream of states over time.
The main reason for using RxJS though is that observables are delivered over time. This promotes a reactive approach to how you'd design your application. Instead of pursuing an imperative approach like **a click on button A** should **trigger a re-rendering on component B**, we follow an [Observer Pattern](https://en.wikipedia.org/wiki/Observer_pattern) where **component B observes a global state** and **acts on changes**, which are **triggered through actions by button A**.
Broken down on the concepts of Aurelia, as depicted in the following chart, this means that a ViewModel subscribes to the single store and sets up a state subscription. The view directly binds to properties of the state. Actions can be dispatched and trigger the next state emit. Now the initial subscription receives the next state and changes the bound variable, Aurelia automatically figures out what changed and triggers a re-render. The next dispatch will then trigger the next cycle and so on. This way the system behaves in a cyclic, reactive way and sees state changes as requests for a re-rendering.
Broken down to the concepts of Aurelia, as depicted in the following chart, this means that a ViewModel subscribes to the single store and sets up a state subscription. The view directly binds to properties of the state. Actions can be dispatched and trigger the next state emit. Now the initial subscription receives the next state and changes the bound variable, Aurelia automatically figures out what changed and triggers a re-render. The next dispatch will then trigger the next cycle and so on. This way the system behaves in a cyclic, reactive way and sees state changes as requests for a re-rendering.
![Chart workflow](./images/chart_store_workflow.png)
![Chart workflow](images/chart_store_workflow.png)
A fundamental benefit of that approach is, that you as a developer do not need to think of signaling individual components about changes, but rather they will all listen and react to changes by themselves if the respective part of the state gets modified. Think of it as an event dispatch, where multiple recipients can listen for and perform changes but with the benefit of a formalized global state. As such, all you need to focus on is the state and the rest will be handled automatically.
A fundamental benefit of that approach is that you as a developer do not need to think of signaling individual components about changes, but rather they will all listen and react to changes by themselves if the respective part of the state gets modified. Think of it as an event dispatch, where multiple recipients can listen for and perform changes but with the benefit of a formalized global state. As such, all you need to focus on is the state and the rest will be handled automatically.
Another benefit is the async nature of the subscription. No matter whether the action is a synchronous operation, like changing the title of your page, an Ajax request to fetch the latest products or a long-running web-socket for your next chat application. Whenever you dispatch the next action, listeners will react to these changes.
Another benefit is the async nature of the subscription. No matter whether the action is a synchronous operation, like changing the title of your page, an Ajax request to fetch the latest products or a long-running web-socket for your next chat application: whenever you dispatch the next action, listeners will react to these changes.

@@ -48,2 +48,3 @@ ## Getting Started

or using [Yarn](https://yarnpkg.com)
```Shell

@@ -53,2 +54,5 @@ yarn add aurelia-store

> Info
> If you're not using RxJS at all so far it is recommended to run `npm install rxjs` which should install the latest available rxjs (currently 6.x.x) version as a project dependency.
If your app is based on the Aurelia CLI and the build is based on RequireJS or SystemJS, you can setup the plugin using the following automatic dependency import:

@@ -62,3 +66,3 @@

```json
```JSON
...

@@ -73,4 +77,9 @@ "dependencies": [

"name": "rxjs",
"path": "../node_modules/rxjs",
"main": false
"main": "./index.js",
"path": "../node_modules/rxjs"
},
{
"name": "rxjs/operators",
"main": "./index.js",
"path": "../node_modules/rxjs/operators"
}

@@ -80,59 +89,16 @@ ]

## Granular (patched) RxJS imports
> Info
> With the recent release of RxJS v.6, which this plugin depends on, quite a lot has changed. There are new ways to import dependencies and ways to keep compatibility with previous API versions. Take a look at the [following upgrade instructions](https://github.com/ReactiveX/rxjs/blob/master/docs_app/content/guide/v6/migration.md) for further details. In case you're using a classic Require.js based Aurelia CLI project setup, make sure to [configure rxjs-compat](https://www.npmjs.com/package/rxjs-compat) in aurelia.json as a dependency and use it as the main include file. If you do on the other hand already use the newest APIs you'll have to adjust your `aurelia.json` or do a fresh new `au import aurelia-store` to get the rxjs dependencies properly auto-setup.
> With the recent release of RxJS v.6 quite a lot has changed. There are new ways to import dependencies and ways to keep compatibility with previous API versions. Take a look at the [following upgrade instructions](https://github.com/ReactiveX/rxjs/blob/master/MIGRATION.md) for further details. In case you're using a classic Require.js based Aurelia CLI project setup, make sure to [configure rxjs-compat](https://www.npmjs.com/package/rxjs-compat) in aurelia.json as a dependency and use it as the main include file. If you do on the other already use the newest APIs you'll have to adjust your `aurelia.json` or do a fresh new `au import aurelia-store` to get the rxjs dependencies properly auto-setup.
**This section is only valid for RxJS versions <= 5.x.x**
Looking at the above dependency configuration for Aurelia CLI you'll note the use of `"main": false`, which tells the loader not to use any default file and not start importing things right away. The reason for this is that importing the whole RxJS library would net result in additional ~250kb for your app, where you'd most of the time need only a minimum subset. Patched imports enable to bundle only things directly referenced.
What you need to make sure of when requesting features from RxJS though is that you do not import the full library itself anywhere. This applies to other bundlers such as Webpack as well. That can happen through one of the following statements:
<code-listing heading="Imports triggering a full RxJS bundle">
<source-code lang="TypeScript">
import * as rx from 'rxjs';
import { Observable, ... } from 'rxjs';
import 'rxjs';
import 'rxjs/Rx';
</source-code>
</code-listing>
So try to avoid these and instead only import operators and observable features as needed like in the following way:
<code-listing heading="Imports triggering a full RxJS bundle">
<source-code lang="TypeScript">
// RxJS v >= 6.x.x
// Imports the Observable constructor, creation methods, types and schedulers
import { Observable, pipe, of, from, fromEvent, ... } from 'rxjs';
// Imports of pipeable operators
import { map, filter, scan } from 'rxjs/operators';
// RxJS v <= 5.x.x
// Imports the Observable constructor
import { Observable } from 'rxjs/Observable';
// Imports only the map operator
import "rxjs/add/operator/map";
// Imports and patches the static method of
import "rxjs/add/observable/of";
</source-code>
</code-listing>
> Additional information and tips & tricks about size-sensitive bundling with Aurelia CLI can be found [here](http://pragmatic-coder.net/aurelia-cli-and-rxjs-size-sensitive-bundles/)
## What is the State?
A typical application consists of multiple components, which render various data. Besides actual data though, your components also contain the various statuses, like an active state for a toggle button, but also high-level states like the selected theme or current page.
The contained component state is a good thing and should stay with the component, as long as only that single instance cares about it. The moment you reference the internal state from another component though, you're going to need a different approach like service classes. Another related topic is the inter-component communication where both services but also pub-sub mechanisms like the EventAggregator may be used.
A typical application consists of multiple components, which render various data. Besides actual data though, your components also contain various statuses. An "active" status for a toggle button for example, but also high-level statuses like the selected theme or the current page.
State that is contained within the component is a good thing, as long as only that single component instance cares about it. But the moment you reference that state from another component, you're going to need a way to communicate about that state, like service classes. Pub-sub mechanisms like the EventAggregator are another example of inter-component communication.
In contrast to that, the Store plugin operates on a single overall application state. Think of it as a large object containing all the sub-states reflecting your applications condition at a specific moment in time. This state object needs only to contain serializable properties. With that you gain the benefit of having snapshots of your app, which allow all kinds of cool features like time-traveling, save/reload and so on.
In contrast to that, the Store plugin operates on a single overall application state. Think of it as a large object containing all the sub-states reflecting your applications condition at a specific moment in time. This state object needs to only contain serializable properties. With that you gain the benefit of having snapshots of your app, which allow all kinds of cool features like time-traveling, save/reload and so on.
How much you put into your state is up to you, but a good rule of thumb is that as soon as two different areas of your application consume the same data or affect component states you should store them.
How much you put into your state is up to you, but a good rule of thumb is that as soon as two different areas of your application consume the same data or affect the same component state you should store them.
Your app will typically start with a beginning state, called initial state, which later on is manipulated throughout the app's lifecycle. As mentioned it can be pretty much anything like shown in below example. Whether you prefer TypeScript or pure JavaScript is up to you, but having a typed state, allows for easier refactoring and better autocompletion support.
Your app will typically start with an initial state, which is then manipulated throughout the app's lifecycle. As mentioned this state can be pretty much anything, like shown in the example below. Whether you prefer TypeScript or pure JavaScript is up to you, but having a typed state allows for easier refactoring and better autocompletion support.

@@ -151,6 +117,6 @@ <code-listing heading="Defining the State entity and initialState">

</source-code>
</code-listing>
<code-listing heading="Defining the initialState">
<source-code lang="JavaScript">
// there is no need for a dedicated entity in JavaScript
// state.js

@@ -165,3 +131,3 @@ export const initialState = {

In order to tell Aurelia how to use the plugin, we need to register it. This is done in your apps `main` file, specifically the `configure` method. We'll have to register the Store using our previously defined State entity:
In order to tell Aurelia how to use the plugin, we need to register it. This is done in your app's `main` file, specifically the `configure` method. We'll have to register the Store using our previously defined State entity:

@@ -187,4 +153,2 @@ <code-listing heading="Registering the plugin">

</source-code>
</code-listing>
<code-listing heading="Registering the plugin">
<source-code lang="JavaScript">

@@ -214,3 +178,3 @@

As explained in the beginning, the Aurelia Store plugin provides a public observable called `state` which will stream the apps states over time. So in order to consume it, we first need to inject the store via dependency injection into the constructor. Next, inside the `bind` lifecycle method we are subscribing to the store's `state` property. Inside the *next-handler* we'll have the actually streamed state and may assign it to the components local state property. Last but not least let's not forget to dispose the subscription ones the component becomes unbound. This happens by calling the subscriptions `unsubscribe` method.
As explained in the beginning, the Aurelia Store plugin provides a public observable called `state` which will stream the app's states over time. So in order to consume it, we first need to inject the store via dependency injection into the constructor. Next, inside the `bind` lifecycle method we are subscribing to the store's `state` property. Inside the subscription's *handler* we'll have the actually streamed state and may assign it to the component's local state property. And let's not forget to dispose of the subscription once the component becomes unbound. This happens by calling the subscription's `unsubscribe` method.

@@ -245,4 +209,2 @@ <code-listing heading="Injecting the store and creating a subscription">

</source-code>
</code-listing>
<code-listing heading="Injecting the store and creating a subscription">
<source-code lang="JavaScript">

@@ -273,3 +235,4 @@

> Note that in the TypeScript version we didn't have to type-cast the state variable provided to the next handler since the initial store was injected using the `State` entity as a generic provider.
> Info
> Note that in the TypeScript version we didn't have to type-cast the state variable provided to the subscription handler since the initial store was injected using the `State` entity as a generic provider.

@@ -291,3 +254,3 @@ With that in place the state can be consumed as usual directly from within your template:

Since you've subscribed to the state, every new one that arrives will again be assigned to the components `state` property and the UI automatically re-rendered, based on the details that changed.
Since you've subscribed to the state, every new version of the state that arrives will again be assigned to the component's `state` property and the UI automatically re-rendered, based on the details that changed.

@@ -298,6 +261,6 @@ ## Subscribing with the connectTo decorator

But instead of handling subscriptions and disposal of those by yourself, you may prefer to use the `connectTo` decorator.
What it does is to connect your store's state automatically to a class property called `state`. It does so by overriding by default the
`bind` and `unbind` life-cycle method for a proper setup and teardown of the state subscription, which will be stored in a property called `_stateSubscription`.
What it does is to connect your store's state automatically to a class property called `state`. It does so by overriding the
`bind` and `unbind` life-cycle methods for a proper setup and teardown of the state subscription, which will be stored in a property called `_stateSubscription`.
Above ViewModel example could look the following using the connectTo decorator:
The above ViewModel example could look like this using the `connectTo` decorator:

@@ -308,8 +271,6 @@ <code-listing heading="Using the connectTo decorator">

// app.ts
import { autoinject } from "aurelia-dependency-injection";
import { Store, connectTo } from "aurelia-store";
import { connectTo } from "aurelia-store";
import { State } from "./state";
@autoinject()
@connectTo()

@@ -319,21 +280,13 @@ export class App {

public state: State;
private subscription: Subscription;
constructor(private store: Store<State>) {}
}
</source-code>
</code-listing>
<code-listing heading="Using the connectTo decorator">
<source-code lang="JavaScript">
// app.js
import { inject } from "aurelia-dependency-injection";
import { Store, connectTo } from "aurelia-store";
import { connectTo } from "aurelia-store";
import { State } from "./state";
@inject(Store)
@connectTo()
export class App {
constructor(store) {}
}

@@ -343,2 +296,3 @@ </source-code>

> Info
> Notice how we've declared the public state property of type `State` in the TS version. The sole reason for that is to have proper type hinting during compile time.

@@ -359,4 +313,2 @@

</source-code>
</code-listing>
<code-listing heading="Sub-state selection">
<source-code lang="JavaScript">

@@ -390,4 +342,2 @@

</source-code>
</code-listing>
<code-listing heading="Defining the selector and target">
<source-code lang="JavaScript">

@@ -426,4 +376,2 @@

</source-code>
</code-listing>
<code-listing heading="Overriding the default setup and teardown methods">
<source-code lang="JavaScript">

@@ -446,6 +394,8 @@

> Info
> The provided action names for setup and teardown don't necessarily have to be one of the official [lifecycle methods](http://aurelia.io/docs/fundamentals/components#the-component-lifecycle) but should be used as these get called automatically by Aurelia at the proper time.
Last but not least you can also define a callback to be called with the next state once a state change happens
Last but not least you can also define a callback to be called with the next state once a state change happens.
<code-listing heading="Define an onChanged handler">

@@ -469,4 +419,2 @@ <source-code lang="TypeScript">

</source-code>
</code-listing>
<code-listing heading="Define an onChanged handler">
<source-code lang="JavaScript">

@@ -491,3 +439,4 @@

> Your onChanged handler will be called before the target property is changed. This way you have access to both the current and previous state.
> Info
> Your `onChanged` handler will be called before the target property is changed. This way you have access to both the current and previous state.

@@ -499,10 +448,10 @@ Next, let's find out how to produce state changes.

Actions are the primary way to create a new state. They are essentially functions which take the current state and optionally one or more arguments.
Their job is to create the next state and return it. By doing so they should not mutate the passed in current state but instead use immutable functions to create
either a proper clone. The reason for that is that each state represents a unique snapshot of your app in time. By modifying it, you'd alter the state and wouldn't be able to properly compare the old and new state. Further implications by that would be that advanced features such as time-traveling through states wouldn't work anymore.
Their job is to create the next state and return it. By doing so they should not mutate the passed-in current state but instead use immutable functions to create a proper clone. The reason for that is that each state represents a unique snapshot of your app in time. By modifying it, you'd alter the state and wouldn't be able to properly compare the old and new state. It would also mean that advanced features such as time-traveling through states wouldn't work properly anymore.
So keep in mind ... don't mutate your state.
> In case you're not a fan of functional approaches take a look at libraries like [Immer.js](https://github.com/mweststrate/immer), and the [Aurelia store example](https://github.com/zewa666/aurelia-store-examples#immer) using it, to act like you'd mutate the object but secretly get a proper clone.
> Info
> In case you're not a fan of functional approaches take a look at libraries like [Immer.js](https://github.com/mweststrate/immer), and the [Aurelia store example](https://github.com/zewa666/aurelia-store-examples#immer) using it: this lets you act like you're mutating the state object but secretly you're getting a proper clone.
Continuing with above framework example, an action to add an additional framework would look like the following.
You create a shallow clone of the state by using Object.assign. By saying shallow it means that the actual `frameworks` array in the new state will just reference the original one. So in order to fix that we can use the array spread syntax plus the new `frameworkName` to create a fresh new array.
Continuing with above framework example, let's say we want to make an action to add an additional framework to the list.
You can create a "shallow" clone of the state by using `Object.assign`. By saying shallow we mean that the actual `frameworks` array in the new state object will just be a reference to the original one. So in order to fix that we can use the array spread syntax with the name of the new framework to create a fresh array.

@@ -519,4 +468,2 @@ <code-listing heading="A simple action">

</source-code>
</code-listing>
<code-listing heading="A simple action">
<source-code lang="JavaScript">

@@ -533,3 +480,3 @@

Next, we need to register the created action with the store. That is done by calling the stores `registerAction` method. By doing so we can provide a name which will be used for all kinds of error-handlers, logs, and even Redux DevTools. As a second argument, we pass the action itself.
Next, we need to register the created action with the store. That is done by calling the store's `registerAction` method. By doing so we can provide a name which will be used for all kinds of error-handlers, logs, and even Redux DevTools. As a second argument, we pass the action itself.

@@ -549,3 +496,2 @@ <code-listing heading="Registering an action">

public state: State;
private subscription: Subscription;

@@ -559,4 +505,2 @@ constructor(private store: Store<State>) {

</source-code>
</code-listing>
<code-listing heading="Registering an action">
<source-code lang="JavaScript">

@@ -581,3 +525,3 @@

You can unregister actions whenever needed by using the stores `unregisterAction` method
You can unregister actions whenever needed by using the store's `unregisterAction` method

@@ -603,4 +547,2 @@ <code-listing heading="Unregistering an action">

</source-code>
</code-listing>
<code-listing heading="Unregistering an action">
<source-code lang="JavaScript">

@@ -625,5 +567,5 @@

Previously we mentioned that an action should return a state. What we didn't mention is that they are also able to return a promise which will eventually resolve with the new state.
Previously we mentioned that an action should return a state. What we didn't mention is that actions can also return a promise which will eventually resolve with the new state.
From above example, imagine we'd have to validate the given name, which happens in an async manner.
From the above example, imagine we'd have to validate the given name, which happens in an async manner.

@@ -654,4 +596,2 @@ <code-listing heading="An async action">

</source-code>
</code-listing>
<code-listing heading="An async action">
<source-code lang="JavaScript">

@@ -682,3 +622,4 @@

> You're not forced to use async/await but it's highly recommended to use it for better readability wherever you can
> Info
> You dont't have to use async/await but it's highly recommended to use it for better readability whenever you can.

@@ -688,3 +629,3 @@ ## Dispatching actions

So far we've just created an action and registered it by several means. Now let's look at how we can actually execute one of them to trigger the next state change.
We can use the store method `dispatch` to exactly do that. In below example, the function `dispatchDemo`, can be called with an argument `nextFramework`.
We can use the store method `dispatch` to do exactly that. In the example below, the function `dispatchDemo` can be called with an argument `nextFramework`.
Inside we call `store.dispatch`, passing it the action itself and all subsequent parameters required.

@@ -707,3 +648,2 @@ Alternatively we can also provide the previously registered name instead.

public state: State;
private subscription: Subscription;

@@ -722,4 +662,2 @@ constructor(private store: Store<State>) {

</source-code>
</code-listing>
<code-listing heading="Dispatching an action">
<source-code lang="JavaScript">

@@ -750,16 +688,16 @@

Now keep in mind that an action might be async, or really any middleware is, you'll learn more about them later, as such if you're depending on the state being updated right after it, make sure to `await` the call to `dispatch`.
Now keep in mind that an action might be async (really any middleware might be - you'll learn more about middleware later), and as such if you depend on the state being updated right after dispatching an action, make sure to `await` the call to `dispatch`.
The choice whether you call by the actual actions function or it's previously registered name is up to you. It might
The choice whether you dispatch using the actual action function or its previously registered name is up to you. It might
be less work just forwarding a string. That way you don't need to import the action from wherever you want to dispatch
it. On the other hand exactly this is a helpful mechanism to make sure your app survives a refactoring session. Imagine
you'd rename the registration name and not all the places you're dispatching. A long debugging night might be just around the corner ;)
it. On the other hand using the actual function is a helpful mechanism to make sure your app survives a refactoring session. Imagine you'd rename the action's name but forgot to update all the places that dispatch it. A long night of debugging might be just around the corner ;)
> Dispatching non-registered actions will result in an error
> Info
> Dispatching non-registered actions will result in an error.
## Using the dispatchify higher order function
Perhaps you don't want or can't obtain a reference to the store but still would like to dispatch your actions.
This is especially useful if you don't want your child-elements to have any knowledge of the actual logic and just receive actions via attributes. Childs then can call this method directly via the template.
In order to do so, you can leverage the higher order function `dispatchify`. What it does is returning a wrapped new function which will obtain the store by itself and forward the provided arguments directly to `store.dispatch`.
Perhaps you don't want to or can't obtain a reference to the store, but you'd still like to dispatch your actions.
This is especially useful if you don't want your child-elements to have any knowledge of the actual logic and just receive actions via attributes. Children can then call this method directly in the template.
In order to do so, you can leverage the higher order function `dispatchify`. What it does is return a wrapped new function which will obtain the store by itself and forward the provided arguments directly to `store.dispatch`.

@@ -770,3 +708,3 @@ <code-listing heading="Forwarding a dispatchable function as argument to child-elements">

// framework-list.ts
import { useView } from "aurelia-framework";
import { inlineView } from "aurelia-framework";
import { dispatchify } from "aurelia-store";

@@ -781,3 +719,3 @@

@useView(`
@inlineView(`
<template>

@@ -793,5 +731,5 @@ <require from="./framework-item"></require>

// framework-item.ts
import { bindable, useView } from "aurelia-framework";
import { bindable, inlineView } from "aurelia-framework";
@useView(`
@inlineView(`
<template>

@@ -807,8 +745,6 @@ New framework name:

</source-code>
</code-listing>
<code-listing heading="Forwarding a dispatchable function as argument to child-elements">
<source-code lang="JavaScript">
// framework-list.js
import { useView } from "aurelia-framework";
import { inlineView } from "aurelia-framework";
import { dispatchify } from "aurelia-store";

@@ -823,3 +759,3 @@

@useView(`
@inlineView(`
<template>

@@ -837,5 +773,5 @@ <require from="./framework-item"></require>

// framework-item.js
import { bindable, useView } from "aurelia-framework";
import { bindable, inlineView } from "aurelia-framework";
@useView(`
@inlineView(`
<template>

@@ -855,3 +791,2 @@ New framework name:

## Recording a navigable history of the stream of states

@@ -885,4 +820,2 @@

</source-code>
</code-listing>
<code-listing heading="Registering the plugin with history support">
<source-code lang="JavaScript">

@@ -913,3 +846,3 @@

Now when you subscribe to new state changes, instead of a simple State you'll get a StateHistory<State> object returned, which looks like the following:
Now when you subscribe to new state changes, instead of a simple State you'll get a `StateHistory<State>` object returned, which looks like the following:

@@ -946,4 +879,2 @@ ```typescript

</source-code>
</code-listing>
<code-listing heading="Subscribing to the state history">
<source-code lang="JavaScript">

@@ -972,3 +903,3 @@

## Making our app history aware
## Making our app history-aware

@@ -978,3 +909,3 @@ Now keep in mind that every action will receive a `StateHistory<T>` as input and should return a new `StateHistory<T>`.

<code-listing heading="A StateHistory aware action">
<code-listing heading="A StateHistory-aware action">
<source-code lang="TypeScript">

@@ -987,5 +918,2 @@

const demoAction = (currentState: StateHistory<State>, frameworkName: string) => {
const newState = Object.assign({}, state);
newState.frameworks = [...newState.frameworks, frameworkName];
return nextStateHistory(currentState, {

@@ -1010,4 +938,2 @@ frameworks: [...frameworks, frameworkName]

</source-code>
</code-listing>
<code-listing heading="A StateHistory aware action">
<source-code lang="JavaScript">

@@ -1019,5 +945,2 @@

const demoAction = (currentState, frameworkName) => {
const newState = Object.assign({}, state);
newState.frameworks = [...newState.frameworks, frameworkName];
return nextStateHistory(currentState, {

@@ -1046,2 +969,3 @@ frameworks: [...frameworks, frameworkName]

### Navigating through history
Having a history of states is great to do state time-travelling. That means defining either a past or future state as the new present. You can do it manually as described in the full-fledged example above and switching states between the properties `past`, `present` and `future`, or you can import the pre-registered action `jump` and pass it either a positive number for traveling into the future or a negative for travelling to past states.

@@ -1080,4 +1004,2 @@

</source-code>
</code-listing>
<code-listing heading="A StateHistory aware action">
<source-code lang="JavaScript">

@@ -1148,4 +1070,2 @@

</source-code>
</code-listing>
<code-listing heading="Registering the plugin with history overflow limits">
<source-code lang="JavaScript">

@@ -1178,13 +1098,14 @@

## Handling side effects with middlewares
## Handling side-effects with middleware
Aurelia Store uses a concept of middlewares to handle side-effects. Concept-wise they are similar to Express.js middlewares in that they allow to perform side-effects or manipulate request data. As such they are registered functions, which execute before or after each dispatched action.
Aurelia Store uses a concept of middleware to handle side-effects. Conceptually they are similar to [Express.js](https://expressjs.com/) middlewares in that they allow to perform side-effects or manipulate request data. As such they are registered functions, which execute before or after each dispatched action.
A middleware is similar to an action, with the difference that it may return void as well. Middlewares can be executed before the dispatched action, thus potentially manipulating the current state which will be passed to the action, or afterward, modifying the returned value from the action. If they don't return the previous value will be passed as input. Either way, the middleware reducer can be sync as well as async.
A middleware is similar to an action, with the difference that it may return void as well. Middlewares can be executed before the dispatched action, thus potentially manipulating the current state which will be passed to the action, or afterward, modifying the returned value from the action. If they don't return anything the previous value will be passed as output. Either way, the middleware can be sync as well as async.
> Warning
> As soon as you have one async middleware registered, essentially all action dispatches will be async as well.
![Chart workflow](./images/middlewares.png)
![Chart workflow](images/middlewares.png)
Middlewares are registered using `store.registerMiddleware` with the middlewares function and the placement `before` or `after`. Unregisteration can be done using `store.unregisterMiddleware`
Middlewares are registered using `store.registerMiddleware` with the middleware's function and the placement `before` or `after`. Unregistration can be done using `store.unregisterMiddleware`

@@ -1203,4 +1124,2 @@ <code-listing heading="Registering a middleware">

</source-code>
</code-listing>
<code-listing heading="Registering a middleware">
<source-code lang="JavaScript">

@@ -1217,2 +1136,4 @@

> Info
> You can call the `store.registerMiddleware` function whenever you want. This means middlewares don't have to be defined upfront at the apps configuration time but whenever needed. The same applies to `store.unregisterMiddleware`.

@@ -1234,4 +1155,2 @@ ## Accessing the original (unmodified) state in a middleware

</source-code>
</code-listing>
<code-listing heading="Accessing the original state">
<source-code lang="JavaScript">

@@ -1251,3 +1170,3 @@

Some middlewares require additional configurations in order to work as expected. Above we've looked at a `customLogMiddleware` middleware, which console logs the newly created state. Now if we wanted to control the log type to let's say output to `console.debug` we can make use of middleware settings.
Some middlewares require additional configurations in order to work as expected. Above we've looked at a `customLogMiddleware` middleware, which logs the newly created state to the console. Now if we wanted to control the log type to, let's say, output to `console.debug` we can make use of middleware settings.
These are passed in as the third argument to the middleware function and are registered with `registerMiddlware`.

@@ -1266,4 +1185,2 @@

</source-code>
</code-listing>
<code-listing heading="Passing settings to middlewares">
<source-code lang="JavaScript">

@@ -1279,6 +1196,6 @@

## Calling action reference for middlewares
## Reference to the calling action for middlewares
Last but not least the optional forth argument passed into a middleware is the calling action, meaning the action that is dispatched.
In here you get an object containing the actions `name` and the provided `params`. This is useful when you, for instance, want only certain actions to pass or be canceled under certain circumstances.
Last but not least the optional forth argument passed into a middleware is the calling action, meaning the action that is being dispatched.
In here you get an object containing the action's `name` and the provided `params`. This is useful when you, for instance, want only certain actions to pass or be canceled under certain circumstances.

@@ -1299,4 +1216,2 @@ <code-listing heading="Reference to the calling action in middlewares">

</source-code>
</code-listing>
<code-listing heading="Reference to the calling action in middlewares">
<source-code lang="JavaScript">

@@ -1319,3 +1234,3 @@

By default errors thrown by middlewares will be swallowed in order to guarantee continues states. If you would like to stop state propagation you need to pass in the `propagateError` option set to `true`:
By default errors thrown by middlewares will be swallowed in order to guarantee continued states. If you would like to stop state propagation you need to set the `propagateError` option of the plugin to `true`:

@@ -1344,4 +1259,2 @@ <code-listing heading="Registering the plugin with active error propagation">

</source-code>
</code-listing>
<code-listing heading="Registering the plugin with active error propagation">
<source-code lang="JavaScript">

@@ -1377,3 +1290,3 @@

From the previous explanations of the inner workings of middlewares, you've come to learn about the loggingMiddlware. Instead of rebuilding it you can simply import it from Aurelia Store and register it. Remember that you can pass in a settings object to define the logType, which is also defined as string enum in typescript.
From the previous explanations of the inner workings of middlewares, you've come to learn about the `loggingMiddleware`. Instead of rebuilding it you can simply import it from Aurelia Store and register it. Remember that you can pass in a settings object to define the `logType`, which is also defined as a string enum in TypeScript.

@@ -1391,4 +1304,2 @@ <code-listing heading="Registering the Logging middleware">

</source-code>
</code-listing>
<code-listing heading="Registering the Logging middleware">
<source-code lang="JavaScript">

@@ -1409,5 +1320,5 @@

The `localStorageMiddleware` stores your most recent emitted state in the [window.localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). This is useful when creating apps which should survive a full page refresh. Generally, it makes the most sense to place the middleware at the end of the queue to get the latest available value stored in localStorage.
The `localStorageMiddleware` stores your most recent emitted state in the [window.localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). This is useful when creating apps which should survive a full page refresh. Generally, it makes the most sense to place the middleware at the end of the queue to get the latest available value stored in `localStorage`.
In order to make use of it all, you need to do is to register it as usual. By default, the storage key will be `aurelia-store-state`. You can additionally provide a storage-key via the settings to be used instead.
In order to make use of it all, all you need to do is to register the middleware as usual. By default, the storage key will be `aurelia-store-state`. You can additionally provide a storage-key via the settings to be used instead.

@@ -1425,4 +1336,2 @@ <code-listing heading="Registering the LocalStorage middleware">

</source-code>
</code-listing>
<code-listing heading="Registering the LocalStorage middleware">
<source-code lang="JavaScript">

@@ -1454,4 +1363,2 @@

</source-code>
</code-listing>
<code-listing heading="Dispatching the localStorage rehydration action">
<source-code lang="JavaScript">

@@ -1470,7 +1377,10 @@

> Info
> Keep in mind that the store starts with an `initialState`. If the `localStorage` middleware is registered at the app's start, most likely the next refresh will immediately overwrite your `localStorage` and negate the effect of restoring data from previous runs. In order to avoid that, make sure to register the middleware just after the initial state has loaded.
## Execution order
If multiple actions are dispatched, they will get queued and executed one after another in order to make sure that each dispatch starts with an up to date state.
If multiple actions are dispatched, they will get queued and executed one after another in order to make sure that each dispatch starts with an up-to-date state.
If either your actions or middlewares return a sync or async value of `false` it will cause the Aurelia Store plugin to interrupt the execution and not emit the next state. Use this behavior in order to avoid unnecessary states.
If either your actions or middlewares return a sync or async value of `false` it will cause the Aurelia Store plugin to interrupt the execution and not emit the next state. Use this behavior in order to avoid unnecessary state updates.

@@ -1491,4 +1401,2 @@

</source-code>
</code-listing>
<code-listing heading="Tracking performance data">
<source-code lang="JavaScript">

@@ -1504,7 +1412,7 @@

Measures will only be logged for successful next states, so if an action or middleware aborts due to returning `false` or throwing an error, nothing gets logged.
Measures will only be logged for successful state updates, so if an action or middleware aborts due to returning `false` or throwing an error, nothing gets logged.
## Debugging with the Redux DevTools extension
If you've ever worked with Redux then you know for sure about the [Redux Devtools browser extension](https://github.com/zalmoxisus/redux-devtools-extension). It's a fantastic way to record and replay the states of your applications walkthrough. For each step, you get detailed information about your state at that time. This can tremendously help to debug states and replicate issues more easily.
If you've ever worked with Redux then you know for sure about the [Redux Devtools browser extension](https://github.com/zalmoxisus/redux-devtools-extension). It's a fantastic way to record and replay the states of your application's walkthrough. For each step, you get detailed information about your state at that time. This can help tremendously to debug states and replicate issues more easily.

@@ -1516,3 +1424,3 @@ There are tons of [great articles](https://codeburst.io/redux-devtools-for-dummies-74566c597d7) to get you started. Head over to [DevTools browser extension page](https://github.com/zalmoxisus/redux-devtools-extension) for instructions on how to install the extension, start your Aurelia Store plugin project and see how it works.

For various features, Aurelia store does create log statements if turned on. E.g the dispatch info of the currently dispatched action will log on info level by default. Combining multiple of those features might be very distracting in your console window. As such you can define the log level to be used per feature in the plugins setup options. In the following example, we'd like to have the `logLevel` for the dispatchAction info set to `debug` instead of the default `info` level.
For various features, Aurelia Store can log information if logging is turned on. E.g. the dispatch info of the currently dispatched action will log on info level by default. Combining the logging of many of these features might be very distracting in your console window. As such you can define the log level to be used per feature in the plugin's setup options. In the following example, we'd like to have the `logLevel` for the `dispatchAction` info set to `debug` instead of the default `info` level.

@@ -1533,4 +1441,2 @@ <code-listing heading="Dispatch logs to console.debug">

</source-code>
</code-listing>
<code-listing heading="Dispatch logs to console.debug">
<source-code lang="JavaScript">

@@ -1554,18 +1460,21 @@

There are a lot of other state management libraries out there, so you might ask yourself why you should favor Aurelia Store instead. As always Aurelia doesn't want to force you into a certain direction. There are good reasons to stick with something you're already familiar or using in another project.
Let's look at the differences with few of the well-known alternatives.
Let's look at the differences with a few of the well-known alternatives.
### Differences to Redux
Doubtlessly [Redux](https://redux.js.org/) is one of the most favorite state management libraries out there in the eco system. With its solid principles of being a predictable state container and thus working towards consistently behaving apps, it's a common choice amongst React developers. A lot of that is given by the focus of immutable states and the predictability that brings with itself. Yet Redux is not solely bound to a framework and can be used with everything else, [including Aurelia](https://www.sitepoint.com/managing-state-aurelia-with-redux/) as well. There are even plugins to help you [get started](https://github.com/steelsojka/aurelia-redux-plugin).
Aurelia Store shares a lot of fundamental design choices from Redux yet drastically differentiates in two points. For one it's the reduction of boilerplate code. There is no necessity to split Actions and Reducers, along with separate action constants. Plain functions are all that is needed. Secondly, handling async state calculations is simplified by treating the apps state as a stream of states. RxJS as such is a major differentiator, which slowly is also finding it's place in the [Redux eco-system](https://github.com/redux-observable/redux-observable).
Doubtlessly [Redux](https://redux.js.org/) is one of the most favored state management libraries out there in the ecosystem. With its solid principles of being a predictable state container and thus working towards consistently behaving apps, it's a common choice amongst React developers. A lot of that is motivated by the focus on immutable states and the predictability that this brings in itself. Yet Redux is not solely bound to React and can be used with everything else, [including Aurelia](https://www.sitepoint.com/managing-state-aurelia-with-redux/). There are even plugins to help you [get started](https://github.com/steelsojka/aurelia-redux-plugin).
Aurelia Store shares a lot of fundamental design choices from Redux, yet drastically differentiates itself in two points. For one it's the reduction of boilerplate code. There is no necessity to split Actions and Reducers, along with separate action constants. Plain functions are all that is needed. Secondly, handling async state calculations is simplified by treating the apps state as a stream of states. RxJS as such is a major differentiator, which is also slowly finding its place in the [Redux eco-system](https://github.com/redux-observable/redux-observable).
### Differences to MobX
[MobX](https://github.com/mobxjs/mobx) came up as a more lightweight alternative to Redux. With it's focus on observing properties for changes and that way manipulating the apps state, it addresses the issue of reducing boilerplate and not forcing the user into a strict functional programming style. MobX, same as Redux is not tied specifically to a framework - although they offer React bindings out of the box - yet it is not really a great fit for Aurelia. The primary reason for that is that observing property changes is actually one of the main selling points of Aurelia.
[MobX](https://github.com/mobxjs/mobx) came up as a more lightweight alternative to Redux. With its focus on observing properties for changes and that way manipulating the app's state, it addresses the issue of reducing boilerplate and not forcing the user into a strict functional programming style. MobX, similar to Redux, is not tied specifically to a framework - although they offer React bindings out of the box - yet it is not really a great fit for Aurelia. The primary reason for this is that observing property changes is actually one of the main selling points of Aurelia.
Same applies to computed values resembling Aurelia's `computedFrom` and reactions, being pretty much the same as `propertyChanged` handlers.
Essentially all that MobX brings to the table, might be implemented with vanilla Aurelia plus a global state service.
Essentially all that MobX brings to the table might be implemented with vanilla Aurelia plus a global state service.
### Differences to VueX
The last well-known alternative is [VueX](https://github.com/vuejs/vuex), state management library designed specifically for use with the [Vue framework](https://vuejs.org/v2/guide/). On the surface, VueX is relatively similar to MobX with some specific twists to how it handles internal changes, being `mutations`, although developers [seem to disagree](https://twitter.com/youyuxi/status/736939734900047874?lang=de) about that. Mutations very much translate function wise to Redux reducers, with the difference that they make use of Vue's change tracking and thus nicely fit into the framework itself. Modules, on the other hand, are another way to group your actions.
Aurelia Store in that regards is pretty similar to VueX. It makes use of Aurelia's dependency injection, logging and platform abstractions, but aside from that is still a plain simple TypeScript class and could be re-used for any other purpose. One of the biggest differentiators is that Aurelia Store does not force any specific style. Whether you prefer a class-based approach, using the `connectTo` decorator, or heavy function based composition, the underlying architecture of a private BehaviorSubject and a public Observable is flexible enough to adapt to your needs.
The last well-known alternative is [VueX](https://github.com/vuejs/vuex), a state management library designed specifically for use with the [Vue framework](https://vuejs.org/v2/guide/). On the surface, VueX is relatively similar to MobX with some specific twists to how it handles internal changes, being `mutations`, although developers [seem to disagree](https://twitter.com/youyuxi/status/736939734900047874) about that. Mutations very much translate function-wise to Redux reducers, with the difference that they make use of Vue's change tracking and thus nicely fit into the framework itself. Modules, on the other hand, are another way to group your actions.
Aurelia Store is pretty similar to VueX in that regard. It makes use of Aurelia's dependency injection, logging and platform abstractions, but aside from that is still a plain simple TypeScript class and could be re-used for any other purpose. One of the biggest differentiators is that Aurelia Store does not force any specific style. Whether you prefer a class-based approach, using the `connectTo` decorator, or heavy function based composition, the underlying architecture of a private `BehaviorSubject` and a public `Observable` is flexible enough to adapt to your needs.
{
"name": "aurelia-store",
"version": "0.24.2",
"version": "0.25.0",
"description": "Aurelia single state store based on RxJS",

@@ -17,2 +17,3 @@ "keywords": [

"test": "cross-env jest",
"test-ci": "cross-env jest && cat ./test/coverage-jest/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
"test-watch": "concurrently \"tsc --watch\" \"jest --watch\"",

@@ -115,2 +116,3 @@ "build:amd": "cross-env tsc --outDir dist/amd --module amd",

"copyfiles": "^2.0.0",
"coveralls": "^3.0.1",
"cross-env": "^5.1.5",

@@ -117,0 +119,0 @@ "husky": "^0.14.3",

@@ -7,2 +7,3 @@ # aurelia-store

[![CircleCI](https://circleci.com/gh/aurelia/store.svg?style=shield)](https://circleci.com/gh/aurelia/store)
[![Coverage Status](https://coveralls.io/repos/github/aurelia/store/badge.svg?branch=master)](https://coveralls.io/github/aurelia/store?branch=master)

@@ -12,3 +13,3 @@ This library is part of the [Aurelia](http://www.aurelia.io/) platform and contains a plugin that provides a single state store based on RxJS.

You can find complete documentation on setup and usage in the official [Aurelia Developer Hub](http://aurelia.io/hub.html#/doc/article/aurelia/store/latest/aurelia-store-plugin)
You can find complete documentation on setup and usage in the official [Aurelia Developer Hub](https://aurelia.io/docs/plugins/store)

@@ -15,0 +16,0 @@ > To keep up to date on [Aurelia](http://www.aurelia.io/), please visit and subscribe to [the official blog](http://blog.aurelia.io/) and [our email list](http://eepurl.com/ces50j). We also invite you to [follow us on twitter](https://twitter.com/aureliaeffect). If you have questions look around our [Discourse forums](https://discourse.aurelia.io/), chat in our [community on Gitter](https://gitter.im/aurelia/discuss) or use [stack overflow](http://stackoverflow.com/search?q=aurelia). Documentation can be found [in our developer hub](http://aurelia.io/docs). If you would like to have deeper insight into our development process, please install the [ZenHub](https://zenhub.io) Chrome or Firefox Extension and visit any of our repository's boards.

@@ -73,2 +73,6 @@ import { BehaviorSubject, Observable } from "rxjs";

public isMiddlewareRegistered(middleware: Middleware<T>) {
return this.middlewares.has(middleware);
}
public registerAction(name: string, reducer: Reducer<T>) {

@@ -88,2 +92,10 @@ if (reducer.length === 0) {

public isActionRegistered(reducer: Reducer<T> | string) {
if (typeof reducer === "string") {
return Array.from(this.actions).find((action) => action[1].name === reducer) !== undefined;
}
return this.actions.has(reducer);
}
public dispatch(reducer: Reducer<T> | string, ...params: any[]) {

@@ -90,0 +102,0 @@ let action: Reducer<T>;

@@ -111,2 +111,12 @@ import { PLATFORM } from "aurelia-pal";

it("should allow checking for registered middlewares", () => {
const store = createStoreWithState(initialState);
const testMiddleware = (currentState: TestState): false => {
return false;
}
store.registerMiddleware(testMiddleware, MiddlewarePlacement.Before);
expect(store.isMiddlewareRegistered(testMiddleware)).toBe(true);
});
it("should have a reference to the calling action name and its parameters", async () => {

@@ -330,2 +340,17 @@ const store = createStoreWithStateAndOptions<TestState>(initialState, { propagateError: true });

it("should interrupt queue action if after placed middleware returns sync false", async () => {
const errorMsg = "Failed on purpose";
const store = createStoreWithStateAndOptions(initialState, {});
const nextSpy = spyOn((store as any)._state, "next").and.callThrough();
const syncFalseMiddleware = (currentState: TestState): false => {
return false;
}
store.registerMiddleware(syncFalseMiddleware, MiddlewarePlacement.After);
store.registerAction("IncrementAction", incrementAction);
await store.dispatch(incrementAction);
expect(nextSpy).toHaveBeenCalledTimes(0);
});
it("should interrupt queue action if middleware returns async false", async () => {

@@ -332,0 +357,0 @@ const errorMsg = "Failed on purpose";

@@ -7,4 +7,72 @@ import { skip, delay } from "rxjs/operators";

} from "./helpers";
import { Store } from "../../src/store";
import * as PAL from "aurelia-pal";
class DevToolsMock {
public subscriptions = [];
public init = jest.fn();
public subscribe = jest.fn().mockImplementation((cb: (message: any) => void) => this.subscriptions.push(cb));
}
function createDevToolsMock() {
PAL.PLATFORM.global.devToolsExtension = {};
PAL.PLATFORM.global.__REDUX_DEVTOOLS_EXTENSION__ = {
connect: () => new DevToolsMock()
}
}
describe("redux devtools", () => {
beforeEach(() => {
delete PAL.PLATFORM.global.devToolsExtension;
});
it("should setup devtools on construction", () => {
const spy = jest.spyOn(Store.prototype as any, "setupDevTools");
const store = new Store<testState>({ foo: "bar " });
expect(spy).toHaveBeenCalled();
spy.mockReset();
spy.mockRestore();
});
it("should init devtools if available", () => {
createDevToolsMock();
const store = new Store<testState>({ foo: "bar " });
expect((store as any).devToolsAvailable).toBe(true);
expect((store as any).devTools.init).toHaveBeenCalled();
});
it("should receive time-travel notification from devtools", () => {
createDevToolsMock();
const store = new Store<testState>({ foo: "bar " });
const devtools = ((store as any).devTools as DevToolsMock);
const expectedStateChange = "from-redux-devtools";
expect(devtools.subscriptions.length).toBe(1);
devtools.subscriptions[0]({ state: JSON.stringify({ foo: expectedStateChange }) });
expect(devtools.subscribe).toHaveBeenCalled();
});
it("should update state when receiving DISPATCH message", (done) => {
createDevToolsMock();
const store = new Store<testState>({ foo: "bar " });
const devtools = ((store as any).devTools as DevToolsMock);
const expectedStateChange = "from-redux-devtools";
expect(devtools.subscriptions.length).toBe(1);
devtools.subscriptions[0]({ type: "DISPATCH", state: JSON.stringify({ foo: expectedStateChange }) });
store.state.subscribe((timeTravelledState) => {
expect(timeTravelledState.foo).toBe(expectedStateChange);
done();
});
});
it("should update Redux DevTools", done => {

@@ -34,2 +102,27 @@ const { store } = createTestStore();

});
it("should send the newly dispatched actions to the devtools if available", done => {
const { store } = createTestStore();
(store as any).devToolsAvailable = true;
const spy = jest.fn();
(store as any).devTools = { send: spy };
const modifiedState = { foo: "bert" };
const fakeAction = (currentState: testState) => {
return Object.assign({}, currentState, modifiedState);
};
store.registerAction("FakeAction", fakeAction);
store.dispatch(fakeAction);
store.state.pipe(
skip(1),
delay(1)
).subscribe(() => {
expect(spy).toHaveBeenCalled();
spy.mockReset();
done();
});
});
});

@@ -99,2 +99,18 @@ import { Container } from "aurelia-framework";

it("should allow checking for already registered functions via Reducer", () => {
const { store } = createTestStore();
const fakeAction = (currentState: testState) => currentState;
store.registerAction("FakeAction", fakeAction);
expect(store.isActionRegistered(fakeAction)).toBe(true);
});
it("should allow checking for already registered functions via previously registered name", () => {
const { store } = createTestStore();
const fakeAction = (currentState: testState) => currentState;
store.registerAction("FakeAction", fakeAction);
expect(store.isActionRegistered("FakeAction")).toBe(true);
});
it("should accept reducers taking multiple parameters", done => {

@@ -101,0 +117,0 @@ const { store } = createTestStore();

@@ -30,2 +30,25 @@ import {

it("should reject with error if step fails", async () => {
expect.assertions(4);
const { store } = createTestStore();
const actionA = (currentState: testState) => Promise.resolve({ foo: "A" });
const actionB = (currentState: testState) => Promise.resolve({ foo: "B" });
const actionC = (currentState: testState) => Promise.resolve({ foo: "C" });
store.registerAction("Action A", actionA);
store.registerAction("Action B", actionB);
store.registerAction("Action C", actionC);
await executeSteps(
store,
false,
() => store.dispatch(actionA),
(res) => { expect(res.foo).toBe("A"); store.dispatch(actionB); },
(res) => { expect(res.foo).toBe("B"); store.dispatch(actionC); throw Error("on purpose"); },
(res) => expect(res.foo).toBe("C")
).catch((e: Error) => {
expect(e.message).toBe("on purpose");
});
});
it("should provide console information during executeSteps", async () => {

@@ -32,0 +55,0 @@ expect.assertions(6);

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc