Skip to content

Commit 378567e

Browse files
author
Dev Agrawal
committed
added events package
1 parent 7433e48 commit 378567e

File tree

8 files changed

+741
-0
lines changed

8 files changed

+741
-0
lines changed

packages/events/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Solid Primitives Working Group
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/events/README.md

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
<p>
2+
<img width="100%" src="https://assets.solidjs.com/banner?type=Primitives&background=tiles&project=events" alt="Solid Primitives events">
3+
</p>
4+
5+
# @solid-primitives/events
6+
7+
[![turborepo](https://img.shields.io/badge/built%20with-turborepo-cc00ff.svg?style=for-the-badge&logo=turborepo)](https://turborepo.org/)
8+
[![size](https://img.shields.io/bundlephobia/minzip/@solid-primitives/events?style=for-the-badge&label=size)](https://bundlephobia.com/package/@solid-primitives/events)
9+
[![version](https://img.shields.io/npm/v/@solid-primitives/events?style=for-the-badge)](https://www.npmjs.com/package/@solid-primitives/events)
10+
[![stage](https://img.shields.io/endpoint?style=for-the-badge&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-0.json)](https://github.com/solidjs-community/solid-primitives#contribution-process)
11+
12+
A set of primitives for declarative event composition and state derivation for solidjs. You can think of it as a much simpler version of Rxjs that integrates well with Solidjs.
13+
14+
[Here is an implementation of the Strello demo that uses `solid-events`](https://github.com/devagrawal09/strello/pull/1/files).
15+
16+
## Contents
17+
- [@solid-primitives/events](#solid-primitivesevents)
18+
- [Contents](#contents)
19+
- [Installatiom](#installatiom)
20+
- [`createEvent`](#createevent)
21+
- [Tranformation](#tranformation)
22+
- [Disposal](#disposal)
23+
- [Halting](#halting)
24+
- [Async Events](#async-events)
25+
- [`createSubject`](#createsubject)
26+
- [`createAsyncSubject`](#createasyncsubject)
27+
- [`createSubjectStore`](#createsubjectstore)
28+
- [`createTopic`](#createtopic)
29+
- [`createPartition`](#createpartition)
30+
- [Use Cases](#use-cases)
31+
32+
## Installatiom
33+
34+
```bash
35+
npm install solid-events
36+
```
37+
or
38+
```bash
39+
pnpm install solid-events
40+
```
41+
or
42+
```bash
43+
bun install solid-events
44+
```
45+
46+
47+
## `createEvent`
48+
49+
Returns an event handler and an event emitter. The handler can execute a callback when the event is emitted.
50+
51+
```ts
52+
const [onEvent, emitEvent] = createEvent()
53+
54+
onEvent(payload => console.log(`Event emitted:`, payload))
55+
56+
...
57+
58+
emitEvent(`Hello World!`)
59+
// logs "Event emitted: Hello World!"
60+
```
61+
62+
### Tranformation
63+
64+
The handler can return a new handler with the value returned from the callback. This allows chaining transformations.
65+
66+
```ts
67+
const [onIncrement, emitIncrement] = createEvent()
68+
69+
const onMessage = onIncrement((delta) => `Increment by ${delta}`)
70+
71+
onMessage(message => console.log(`Message emitted:`, message))
72+
73+
...
74+
75+
emitIncrement(2)
76+
// logs "Message emitted: Increment by 2"
77+
```
78+
79+
### Disposal
80+
Handlers that are called inside a component are automatically cleaned up with the component, so no manual bookeeping is necesarry.
81+
82+
```tsx
83+
function Counter() {
84+
const [onIncrement, emitIncrement] = createEvent()
85+
86+
const onMessage = onIncrement((delta) => `Increment by ${delta}`)
87+
88+
onMessage(message => console.log(`Message emitted:`, message))
89+
90+
return <div>....</div>
91+
}
92+
```
93+
Calling `onIncrement` and `onMessage` registers a stateful subscription. The lifecycle of these subscriptions are tied to their owner components. This ensures there's no memory leaks.
94+
95+
### Halting
96+
97+
Event propogation can be stopped at any point using `halt()`
98+
99+
```ts
100+
const [onIncrement, emitIncrement] = createEvent()
101+
102+
const onValidIncrement = onIncrement(delta => delta < 1 ? halt() : delta)
103+
const onMessage = onValidIncrement((delta) => `Increment by ${delta}`)
104+
105+
onMessage(message => console.log(`Message emitted:`, message))
106+
107+
...
108+
109+
emitIncrement(2)
110+
// logs "Message emitted: Increment by 2"
111+
112+
...
113+
114+
emitIncrement(0)
115+
// Doesn't log anything
116+
```
117+
118+
`halt()` returns a `never`, so typescript correctly infers the return type of the handler.
119+
120+
### Async Events
121+
122+
If you return a promise from an event callback, the resulting event will wait to emit until the promise resolves. In other words, promises are automatically flattened by events.
123+
124+
```ts
125+
async function createBoard(boardData) {
126+
"use server"
127+
const boardId = await db.boards.create(boardData)
128+
return boardId
129+
}
130+
131+
const [onCreateBoard, emitCreateBoard] = createEvent()
132+
133+
const onBoardCreated = onCreateBoard(boardData => createBoard(boardData))
134+
135+
onBoardCreated(boardId => navigate(`/board/${boardId}`))
136+
```
137+
138+
## `createSubject`
139+
140+
Events can be used to derive state using Subjects. A Subject is a signal that can be derived from event handlers.
141+
142+
```ts
143+
const [onIncrement, emitIncrement] = createEvent()
144+
const [onReset, emitReset] = createEvent()
145+
146+
const onMessage = onIncrement((delta) => `Increment by ${delta}`)
147+
onMessage(message => console.log(`Message emitted:`, message))
148+
149+
const count = createSubject(
150+
0,
151+
onIncrement(delta => currentCount => currentCount + delta),
152+
onReset(() => 0)
153+
)
154+
155+
createEffect(() => console.log(`count`, count()))
156+
157+
...
158+
159+
emitIncrement(2)
160+
// logs "Message emitted: Increment by 2"
161+
// logs "count 2"
162+
163+
emitReset()
164+
// logs "count 0"
165+
```
166+
167+
To update the value of a subject, event handlers can return a value (like `onReset`), or a function that transforms the current value (like `onIncrement`).
168+
169+
`createSubject` can also accept a signal as the first input instead of a static value. The subject's value resets whenever the source signal updates.
170+
171+
```tsx
172+
function Counter(props) {
173+
const [onIncrement, emitIncrement] = createEvent()
174+
const [onReset, emitReset] = createEvent()
175+
176+
const count = createSubject(
177+
() => props.count,
178+
onIncrement(delta => currentCount => currentCount + delta),
179+
onReset(() => 0)
180+
)
181+
182+
return <div>...</div>
183+
}
184+
```
185+
186+
`createSubject` has some compound variations to complete use cases.
187+
188+
### `createAsyncSubject`
189+
190+
This subject accepts a reactive async function as the first argument similar to `createAsync`, and resets whenever the function reruns.
191+
192+
```ts
193+
const getBoards = cache(async () => {
194+
"use server";
195+
// fetch from database
196+
}, "get-boards");
197+
198+
export default function HomePage() {
199+
const [onDeleteBoard, emitDeleteBoard] = createEvent<number>();
200+
201+
const boards = createAsyncSubject(
202+
() => getBoards(),
203+
onDeleteBoard(
204+
(boardId) => (boards) => boards.filter((board) => board.id !== boardId)
205+
)
206+
);
207+
208+
...
209+
}
210+
```
211+
212+
### `createSubjectStore`
213+
214+
This subject is a store instead of a regular signal. Event handlers can mutate the current state of the board directly. Uses `produce` under the hood.
215+
216+
```ts
217+
const boardStore = createSubjectStore(
218+
() => boardData(),
219+
onCreateNote((createdNote) => (board) => {
220+
const index = board.notes.findIndex((n) => n.id === note.id);
221+
if (index === -1) board.notes.push(note);
222+
}),
223+
onDeleteNote(([id]) => (board) => {
224+
const index = board.notes.findIndex((n) => n.id === id);
225+
if (index !== -1) board.notes.splice(index, 1);
226+
})
227+
...
228+
)
229+
```
230+
Similar to `createSubject`, the first argument can be a signal that resets the value of the store. When this signal updates, the store is updated using `reconcile`.
231+
232+
## `createTopic`
233+
234+
A topic combines multiple events into one. This is simply a more convenient way to merge events than manually iterating through them.
235+
236+
```ts
237+
const [onIncrement, emitIncrement] = createEvent()
238+
const [onDecrement, emitDecrement] = createEvent()
239+
240+
const onMessage = createTopic(
241+
onIncrement(() => `Increment by ${delta}`),
242+
onDecrement(() => `Decrement by ${delta}`)
243+
);
244+
onMessage(message => console.log(`Message emitted:`, message))
245+
246+
...
247+
248+
emitIncrement(2)
249+
// logs "Message emitted: Increment by 2"
250+
251+
emitDecrement(1)
252+
// logs "Message emitted: Decrement by 1"
253+
```
254+
255+
## `createPartition`
256+
257+
A partition splits an event based on a conditional. This is simply a more convenient way to conditionally split events than using `halt()`.
258+
259+
```ts
260+
const [onIncrement, emitIncrement] = createEvent()
261+
262+
const [onValidIncrement, onInvalidIncrement] = createPartition(
263+
onIncrement,
264+
delta => delta > 0
265+
)
266+
267+
onValidIncrement(delta => console.log(`Valid increment by ${delta}`))
268+
269+
onInvalidIncrement(delta => console.log(`Please use a number greater than 0`))
270+
271+
...
272+
273+
emitIncrement(2)
274+
// logs "Valid increment by 2"
275+
276+
emitIncrement(0)
277+
// logs "Please use a number greater than 0"
278+
279+
```
280+
281+
## Use Cases

0 commit comments

Comments
 (0)