Skip to content

Commit

Permalink
Merge pull request #7 from zuriscript:feature/wip-rework
Browse files Browse the repository at this point in the history
Lots of new features
  • Loading branch information
zuriscript authored Aug 16, 2023
2 parents 0ff9e4b + 62b6d04 commit 4fc1a63
Show file tree
Hide file tree
Showing 59 changed files with 2,901 additions and 768 deletions.
6 changes: 5 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"root": true,
"ignorePatterns": ["packages/**/*"],
"plugins": [
"tree-shaking"
],
"overrides": [
{
"files": ["*.ts"],
Expand All @@ -27,7 +30,8 @@
"prefix": "lib",
"style": "kebab-case"
}
]
],
"tree-shaking/no-side-effects-in-initialization": 2
}
},
{
Expand Down
29 changes: 3 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,13 @@ signalstory is a flexible and powerful TypeScript library designed specifically

- **Signal-Based State Management:** Utilizes a signal-based approach to state management, as everything, except side effects, is completely signal-based. This eliminates the need for asynchronous observables in components and templates, making the state management process more streamlined.
- **Tailored for Angular:** Is specifically tailored to Angular applications, ensuring seamless integration and optimal performance within the Angular framework.
- **Event Handling:** Supports event handling, enabling communication and interaction between different stores. Events can be used for inter-store communication or for triggering side effects.
- **Event Handling:** Supports event handling, enabling decoupled communication and interaction between different stores as well as providing the possibility to react synchronously to events.
- **Open Architecture:** Offers an open architecture, allowing you to choose between different store implementations. You can use plain repository-based stores or even Redux-like stores depending on your needs and preferences.
- **Flexible Side Effect Execution:** Side effects can be implemented in different ways in signalstory. You have the option to include side effects directly as part of the store, use service-based side effects, or execute effects imperatively on the store based on your specific requirements.
- **Automatic Persistence to Local Storage:** Provides automatic persistence of store state to local storage. Any changes made to the store are automatically synchronized with local storage, ensuring that the state is preserved across page reloads. Initialization of the store can also be directly performed from the persisted state in local storage.
- **State History:** With signalstory, you can enable store history to track state changes over time and perform undo and redo commands.

> **Planned Features and Enhancements:**
>
> - Integration with Redux DevTools: Compatibility with Redux DevTools is in the making, allowing you to leverage the powerful debugging capabilities they provide.
> - Advanced Middleware Support: signalstory is being enhanced to support more advanced middleware options, enabling you to customize and extend its functionality.
> - Time-Travel Debugging: It is planned to introduce time-travel debugging capabilities, empowering you to navigate and inspect the state at different points in time.
> - Many more ideas...
- **Immutability:**In contrast to native signals, immutability becomes a choice, safeguarding your state against accidental mutations and offering more predictability and simplified debugging.
- **Redux Devtools:** Dive deep into the history of state changes, visualize the flow of actions, and effortlessly debug your application using the Redux Devtools

## Installation

Expand All @@ -37,24 +32,6 @@ Install the library using npm:
npm install signalstory
```

## Configuration Options

The Library provides various configuration options that allow you to customize its behaviour. Here are the available configuration options:

| Option | Type | Description |
| ------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | `string` | (Optional) The name of the store. If not provided, the constructor's name will be used as the default value. |
| `initialState` | `any` | (Required) The initial state of the store |
| `enableEffectsAndQueries` | `boolean` | (Optional) Specifies whether to enable effects in the store. When enabled, effects and effect-handler can be registered and executed. Defaults to `false`. |
| `enableEvents` | `boolean` | (Optional) Specifies whether to enable events in the store. When enabled, a mediator can be used to pass events from publisher to subscriber using handler registration. Defaults to `false`. |
| `enableLogging` | `boolean` | (Optional) Specifies whether to enable logging for store actions. When enabled, actions will be logged to the console. Defaults to `false`. |
| `enableStateHistory` | `boolean` | (Optional) Specifies whether to enable state history tracking. When enabled, the store will keep track of state changes and provide a history. Defaults to `false`. |
| `enableLocalStorageSync` | `boolean` | (Optional) Specifies whether to enable automatic synchronization of the store state with local storage. Defaults to `false`. |

To configure the signalstory, simply pass an object with the desired configuration options to the constructor when creating a new instance of the store.

Here's an improved usage section based on the provided sample app code:

## Usage

To demonstrate the usage of signalstory, let's create a very simple counter application.
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/building-blocks/_category_.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"label": "Building Blocks",
"position": 5,
"position": 6,
"link": {
"type": "generated-index",
"description": "The signalstory library is built upon four fundamental building blocks: Queries, Commands, Events, and Effects. These building blocks provide a structured approach to manage state and handle interactions within your application."
Expand Down
11 changes: 8 additions & 3 deletions docs/docs/building-blocks/effect.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,12 @@ Decoupled effects allow for a flexible and decoupled approach to handle side eff

```typescript
import { HttpClient } from '@angular/common/http';
import { Store, createEffect, createEvent } from 'signalstory';
import {
Store,
createEffect,
createEvent,
publishStoreEvent,
} from 'signalstory';

// Create events
export const userLoadedSuccess = createEvent<User>('User loaded successfully');
Expand All @@ -183,12 +188,12 @@ export const fetchUser = createEffect(
.pipe(
tap(user => {
// highlight-start
store.publish(userLoadedSuccess, user);
publishStoreEvent(userLoadedSuccess, user);
// highlight-end
}),
catchError(error => {
// highlight-start
store.publish(userLoadedFailure, error);
publishStoreEvent(userLoadedFailure, error);
// highlight-end
return of(error);
})
Expand Down
30 changes: 18 additions & 12 deletions docs/docs/building-blocks/event.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,15 @@ const myEventWithNoPayload = createEvent('My Event and nothing more');

## Event Handlers

`Event handlers`, on the other hand, are functions that are executed when a specific event is published. They define the behavior or actions to be taken in response to the occurrence of an event. An event handler is always registered in the context of a specific store and hence defines how a certain store reacts to a specific event.
`Event handlers` are functions that are executed when a specific event is published. They define the behavior or actions to be taken in response to the occurrence of an event. An event handler is always registered in the context of a specific store and hence defines how a certain store reacts to a specific event.
To register an event handler, you need to specify the event you want to handle and provide the corresponding handler function.

:::info

Event handlers are invoked **synchronously** and are intended for specific use cases. Events should capture meaningful incidents that happened (e.g. http calls, user interaction, cross cutting state changes) and that stores need to respond to effectively. Keep in mind that infinite circular updates can occur if further events are pubblished within a handler which transitively invokes the handler again.

:::

```typescript
import { Store } from 'signalstory';

Expand All @@ -40,30 +46,30 @@ export class BooksStore extends Store<Book[]> {
});

this.registerHandler(
booksLoadedFailure,
this.handleBooksLoadedFailureEvent.bind(this)
googleBooksLoadedFailure,
this.handleGoogleBooksLoadedFailureEvent
);
}

private handleBooksLoadedFailureEvent(_: StoreEvent<never>) {
this.set([], 'Reset Books');
private handleGoogleBooksLoadedFailureEvent(
store: this,
_: StoreEvent<never>
) {
store.setBooks([]);
}
}
```

## Publishing Events

Events can be published using the `publish` method available in the store. Publishing an event triggers the execution of all registered event handlers for that particular event.
Events can be published using the `publishStoreEvent` method. Publishing an event triggers the execution of all registered event handlers synchronously for that particular event.

```typescript
import { createEvent, publishStoreEvent } from 'signalstory';

const myEvent = createEvent<number>('My Event');

// Any store works here
// You can also publish events in a store command
// Or as result of an effect
store.publish(myEvent, 5);
publishStoreEvent(myEvent, 5);
```

## Replay

When registering a new event handler, you can optionally specify the `withReplay` parameter which defaults to false. By using replay, all already published events are replayed only for this specific store at the moment of registration. This is useful for dynamic or lazily initialized stores.
17 changes: 10 additions & 7 deletions docs/docs/building-blocks/query.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ We can leave it to the consumer, i.e. a component or a service where the data is

### Using a query object

You can use the provided `createQuery` function to create a query object, which can be passed to any store that has been initialized with an injector context (`enableEffectsAndQueries`). A query object consist of an array of stores involved in the query and a function taking those stores as argument. Note, that you may not use the `computed` function in query objects, as this is done for you by the stores implementation.

It actually doesn't matter which store runs the query object. It can be any store that has `enableEffectsAndQueries` enabled. The other stores are not required to have this setting enabled.
You can use the provided `createQuery` function to create a query object, which can be passed to any store (except for dynamic stores that were created outside of an [injection context](https://angular.io/guide/dependency-injection-context)). A query object consist of an array of stores involved in the query and a function taking those stores as argument. Note, that you may not use the `computed` function in query objects, as this is done for you by the stores implementation.

The benefit of using this approach is, that we now have an independent query which can be declared and exported centrally and be reused anywhere.

Expand All @@ -77,7 +75,7 @@ import { Store, createQuery } from 'signalstory';
@Injectable({ providedIn: 'root' })
class CounterStore extends Store<number> {
constructor() {
super({ initialState: 7, enableEffectsAndQueries: true });
super({ initialState: 7 });
}
}

Expand Down Expand Up @@ -112,14 +110,19 @@ export const counterAndWordWithParamQuery = createQuery(
export class AppComponent {
constructor(counterStore: CounterStore) {
// highlight-start
console.log(counterStore.runQuery(counterAndWordQuery)); // prints Magnificent7
console.log(counterStore.runQuery(counterAndWordWithParamQuery, '-movie')); // prints Magnificent7-movie
console.log(counterStore.runQuery(counterAndWordQuery)()); // prints Magnificent7
console.log(
counterStore.runQuery(counterAndWordWithParamQuery, '-movie')()
); // prints Magnificent7-movie
// highlight-end
}
}
```

:::tip
:::info
Note that creating the query does not directly produce the signal itself but rather serves as a "recipe" for building it. The actual signal is generated once the query object is provided to the `runQuery` function of any store. Hence, to take advantage of memoization, it is advisable to store the resulting signal in a variable and reuse it as much as possible, rather than invoking `runQuery` repeatedly.
:::

:::tip
Read about [computed signals](https://angular.io/guide/signals#computed-signals) in the official docs to unlock the full power of multi store queries. You may even specify to use the state value of a certain store without tracking, hence, a change of that stores value would **not** force the computed query to be reevaluated. [Read more](https://angular.io/guide/signals#reading-without-tracking-dependencies)
:::
29 changes: 13 additions & 16 deletions docs/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,17 @@ sidebar_position: 4

# Configuration

The store configuration allows you to customize the behavior of your store. It provides various options to tailor the store's functionality according to your specific requirements. The configuration is applied by passing it to the constructor. Since signalstory is a multi store state management library, each store can have its own configuration.
The store configuration allows you to customize the behavior of your store. The configuration is applied by passing it to the constructor. Since signalstory is a multi store state management library, each store can have its own configuration.

Here are the available configuration options:

| Option | Description | Default Value | Required |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------- | -------- |
| `initialState` | Specifies the initial state of the store. | - | Yes |
| `name` | Sets the name of the store. This option is optional and defaults to the constructor name. | Class name | No |
| `enableStateHistory` | Enables the state history feature in signalstory, allowing you to track, undo and redo the store's state changes over time. | `false` | No |
| `enableLogging` | Enables logging for the store's actions and events, providing detailed information about the store's operations. | `false` | No |
| `enableEvents` | Enables decoupled inter-store and effect communication using event, by providing an internal mediator | `false` | No |
| `enableEffectsAndQueries` | Enables the use of effects and queries in the store, by providing an injection context | `false` | No |
| `enableLocalStorageSync` | Enables local storage persistence for the store's state, allowing the state to be stored and retrieved from local storage. | `false` | No |

To configure a store, simply provide the desired values for the configuration options when creating an instance of the store. For example:
| Option | Description | Default Value | Required |
| --------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------- | -------- |
| `initialState` | Specifies the initial state of the store. | - | Yes |
| `name` | Sets the name of the store. This option is optional and defaults to the constructor name. | Class name | No |
| `injector` | Optional DI injector that can be passed for effects and query objects. Only useful for dynamic stores not registered in DI. | `null` | No |
| `enableLogging` | Enables logging for the store's actions and events, providing detailed information about the store's operations. | `false` | No |
| `plugins` | A list of plugins to use with the store. | `[]` | No |

```typescript

Expand All @@ -27,11 +23,12 @@ class MyStore extends Store<MyState> {
super({
initialState: { ... },
name: 'My Store',
enableStateHistory: true,
enableLogging: true,
enableEvents: true,
enableEffectsAndQueries: true,
enableLocalStorageSync: true,
plugins: [
useDevtools(),
useStoreHistory(),
useStorePersistence(),
],
});
}
}
Expand Down
Loading

0 comments on commit 4fc1a63

Please sign in to comment.