1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
|
# ShapeX
Create scalable event-driven applications with ShapeX, inspired by [re-frame](https://github.com/day8/re-frame/). ShapeX
uses zero dependencies and is runtime agnostic, meaning that you can use it in Node, Deno, Bun, browsers, or really
anywhere where JavaScript runs.
## Example application
This is an example application that demonstrates how to use the ShapeX library. It has a single starting point event
called `request`, which returns an updated state, which changes the `counter`. When that state changes, the subscriber
for the `counter` state fires.
```typescript
import {ShapeX} from "shapex";
type AppState = {
counter: number;
};
const app = ShapeX<AppState>({
counter: 1,
});
app.subscribe("$.counter", (state) => {
console.log("counter changed", state);
});
app.subscribe("request", (state) => {
app.setState({
...state,
counter: state.counter + 1
});
});
// Dispatch an event somewhere.
app.dispatch("request");
```
## Installation
```shell
npm install shapex
```
## Documentation
### State
At the core of your application is state. You start by initiating ShapeX with some initial state, like so:
```typescript
import {ShapeX} from "shapex";
type AppState = {
counter: number;
};
const app = ShapeX<AppState>({
counter: 1,
});
```
You can model your `AppState` however you like. It does not have to be called `AppState`.
### Events
Events set things in motion. You can dispatch events like so:
```typescript
app.dispatch("some-topic-name");
```
And, if there's any subscriptions for that topic, those subscriptions will then fire their event listeners.
The above example is an event with no payload, but you can also dispatch events with payload, like so:
```typescript
app.dispatch("some-topic-name", {
hello: "world",
});
```
### Subscriptions
You can listen to events like so:
```typescript
app.subscribe("some-event-name", (state, payload) => {
// do something with the payload
});
```
Each subscription has a callback function (event listener) which gets passed to it the app state and whatever payload
was passed when the event was dispatched. In other words, subscriptions take a `EventListener<TState, TPayload>`
function where `TState` is the app state, `TPayload` is the data sent via the `dispatch` method.
#### State change subscriptions
You can also listen to state changes with subscriptions, which will fire when the listened state changes. You can listen
to state changes like so:
```typescript
app.subscribe("$.counter", (state) => {
// state.counter changed
});
```
Notable difference here is the `$.` prefix in the subscription listener name, which tells ShapeX what state to look for.
Here `$.counter` will look for the root-level `counter` key in state. To look for nested state, simply add a dot (`.`)
followed by the key name, i.e: `$.counter.nestedKey`. Additionally, state change subscriptions do not get any additional
data passed to them, only state, or in other words they are of `EventListener<TState>` type.
#### Subscribe only once
If you want to subscribe to an event or state change only once, you can use the `subscribeOnce` method. This method
works similarly to `subscribe`, but it will automatically unsubscribe after the first event or state change.
```typescript
app.subscribeOnce("$.counter", (state) => {
// This will run only once.
});
```
#### Unsubscribe
If you want to unsubscribe from an event or state change, you can use the `unsubscribe` method. This method takes the
event or state change name as its argument and removes the subscription.
```typescript
app.unsubscribe("some-topic-name");
```
### Updating state
You can update state with the `setState` method:
```typescript
app.subscribe("counter++", (state) => {
app.setState({
...state,
counter: state.counter + 1
});
});
```
#### Get the subscription count
If you want to get the number of subscriptions for a specific event or state change, you can use the `subscriptionCount`
method. This method takes the event or state change name as its argument and returns the number of subscriptions.
```typescript
// State change subscriptions
app.subscriptionCount("$.counter");
// Event subscriptions
app.subscriptionCount("some-event-name");
```
#### Get all subscriptions
If you want to get all subscriptions, you can use the `subscriptions` method. This method returns an array of all the
subscription names.
```typescript
app.subscriptions();
```
#### Get current app state
If you want to get the current state of the app, you can use the `state` method. This method returns the current state
of the app.
```typescript
app.state();
```
|