trisgeist 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.json +8 -0
- package/CONTRIBUTING.md +16 -0
- package/LICENSE +13 -0
- package/README.md +166 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/index.ts +1 -0
- package/package.json +31 -0
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Adding a framework binding
|
|
4
|
+
|
|
5
|
+
1. Create `src/<framework>.ts` exporting your hook/adapter (e.g. `src/vue.ts`, `src/svelte.ts`)
|
|
6
|
+
2. Add an export entry in `package.json`:
|
|
7
|
+
```json
|
|
8
|
+
"./<framework>": {
|
|
9
|
+
"types": "./dist/src/<framework>.d.ts",
|
|
10
|
+
"import": "./dist/src/<framework>.js"
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
3. Add the directory to `.npmignore` if you add any framework-specific source folders outside `src/`
|
|
14
|
+
4. Add `src/<framework>.ts` to `tsconfig.json` `include` if it lives outside `src/` (files inside `src/` are already covered)
|
|
15
|
+
|
|
16
|
+
The core (`src/Notifier.ts`) has no framework dependencies — keep it that way.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
2
|
+
Version 2, December 2004
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
|
5
|
+
|
|
6
|
+
Everyone is permitted to copy and distribute verbatim or modified
|
|
7
|
+
copies of this license document, and changing it is allowed as long
|
|
8
|
+
as the name is changed.
|
|
9
|
+
|
|
10
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
11
|
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
12
|
+
|
|
13
|
+
0. You just DO WHAT THE FUCK YOU WANT TO.
|
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# trisgeist
|
|
2
|
+
|
|
3
|
+
small simple global state management library. class-based stores, selector subscriptions, derived state, redux devtools. inspired by flutter's `ChangeNotifier` and riverpod.
|
|
4
|
+
|
|
5
|
+
## install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add trisgeist
|
|
9
|
+
# or
|
|
10
|
+
npm install trisgeist
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## basic usage
|
|
14
|
+
|
|
15
|
+
extend `Notifier`, override `build()` to return your initial state, expose actions as methods.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { Notifier } from "trisgeist";
|
|
19
|
+
|
|
20
|
+
type CounterState = { count: number };
|
|
21
|
+
|
|
22
|
+
class CounterNotifier extends Notifier<CounterState> {
|
|
23
|
+
override build() {
|
|
24
|
+
return { count: 0 };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
increment() {
|
|
28
|
+
this.setState({ count: this.state.count + 1 });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const counterNotifier = new CounterNotifier();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
then in your component:
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { useWatch } from "trisgeist/react";
|
|
39
|
+
import { counterNotifier } from "./counterNotifier";
|
|
40
|
+
|
|
41
|
+
export function Counter() {
|
|
42
|
+
const { count } = useWatch(counterNotifier, (o) => o.state);
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<button onClick={() => counterNotifier.increment()}>
|
|
46
|
+
clicked {count} times
|
|
47
|
+
</button>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
the selector `(o) => o.state.count` means the component only re-renders when `count` changes, not on any unrelated state update.
|
|
53
|
+
|
|
54
|
+
> **note:** `useWatch` lives at `trisgeist/react` rather than the
|
|
55
|
+
> root export. this keeps the door open for other framework bindings
|
|
56
|
+
> (e.g. `trisgeist/vue`) without name collisions.
|
|
57
|
+
|
|
58
|
+
## the rules
|
|
59
|
+
|
|
60
|
+
- **always override `build()`** to return your initial state. don't also
|
|
61
|
+
assign `state = {...}` as a class field — class fields run *after* the
|
|
62
|
+
constructor body, so it'll silently overwrite whatever `build()` returned.
|
|
63
|
+
- **`state` is settable**, but every direct assignment must be followed by
|
|
64
|
+
`notifyListeners()` (or use `setState`/`scheduleNotify`, which call it for
|
|
65
|
+
you). if you set `state` directly and don't notify, subscribers won't update.
|
|
66
|
+
|
|
67
|
+
## derived stores
|
|
68
|
+
|
|
69
|
+
use `watch()` inside `build()` to make a store that recomputes when other stores change — same idea as riverpod's `ref.watch`.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
class FilteredEventsNotifier extends Notifier<Event[]> {
|
|
73
|
+
override build() {
|
|
74
|
+
const events = this.watch(eventsNotifier, (o) => o.state);
|
|
75
|
+
const filters = this.watch(filtersNotifier, (o) => o.state);
|
|
76
|
+
return events.filter((e) =>
|
|
77
|
+
filters.category === "all" || e.category === filters.category
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const filteredEventsNotifier = new FilteredEventsNotifier();
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
when `filtersNotifier` or `eventsNotifier` updates, `build()` reruns automatically. no wiring needed.
|
|
86
|
+
|
|
87
|
+
> **note:** dependencies declared with `watch()` must be static — call
|
|
88
|
+
> `watch()` on the same stores, in the same order, every time `build()` runs.
|
|
89
|
+
> only the first call (during construction) actually registers subscriptions,
|
|
90
|
+
> so conditional `watch()` calls won't be tracked correctly afterward.
|
|
91
|
+
|
|
92
|
+
## debounced updates
|
|
93
|
+
|
|
94
|
+
if you're getting a burst of updates (websocket events, stream data) and don't want a re-render for each one, assign `this.state` directly (no merge) and call `scheduleNotify()` instead of `setState`:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
class MessagesNotifier extends Notifier<Message[]> {
|
|
98
|
+
override debounceDuration = 50; // default is 100ms
|
|
99
|
+
|
|
100
|
+
override build() {
|
|
101
|
+
return [] as Message[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
onMessage(msg: Message) {
|
|
105
|
+
this.state = [...this.state, msg];
|
|
106
|
+
this.scheduleNotify(); // batches into one re-render once the burst settles
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`scheduleNotify()` resets a timer on each call and only fires `notifyListeners()` once things settle. unlike `setState`, it does **not** merge — set `this.state` to the full new value yourself first.
|
|
112
|
+
|
|
113
|
+
## reading from other stores
|
|
114
|
+
|
|
115
|
+
stores are just singletons, import and read from them anywhere:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
class CartNotifier extends Notifier<CartState> {
|
|
119
|
+
checkout() {
|
|
120
|
+
const { currency } = settingsNotifier.state;
|
|
121
|
+
this.setState({ total: calculateTotal(this.state.items, currency) });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## which update method to use
|
|
127
|
+
|
|
128
|
+
| method | when |
|
|
129
|
+
|---|---|
|
|
130
|
+
| `setState(partial)` | shallow merge + notify immediately — use this for almost everything |
|
|
131
|
+
| `this.state = ...` + `notifyListeners()` | full state replacement, then notify |
|
|
132
|
+
| `this.state = ...` + `scheduleNotify()` | full replacement, debounced notify, for burst updates |
|
|
133
|
+
|
|
134
|
+
## devtools
|
|
135
|
+
|
|
136
|
+
connects to [redux devtools](https://github.com/reduxjs/redux-devtools) automatically if you have the extension installed. each store shows up as its own instance in the dropdown. time travel works on source stores — derived ones recompute naturally.
|
|
137
|
+
|
|
138
|
+
no config needed, just install the extension.
|
|
139
|
+
|
|
140
|
+
## react native
|
|
141
|
+
|
|
142
|
+
works fine, `useSyncExternalStore` is react core not web-specific.
|
|
143
|
+
|
|
144
|
+
## api
|
|
145
|
+
|
|
146
|
+
### `Notifier<TState>` — `trisgeist`
|
|
147
|
+
|
|
148
|
+
| member | description |
|
|
149
|
+
|---|---|
|
|
150
|
+
| `state` | current state, settable — direct assignment requires a `notifyListeners()`/`scheduleNotify()` call after |
|
|
151
|
+
| `build()` | **must override.** return initial state here, call `watch()` to declare deps |
|
|
152
|
+
| `setState(partial)` | shallow merge + notify |
|
|
153
|
+
| `notifyListeners()` | manually flush listeners |
|
|
154
|
+
| `scheduleNotify()` | debounced flush |
|
|
155
|
+
| `debounceDuration` | debounce ms, default `100`, override per store |
|
|
156
|
+
| `watch(store, selector)` | declare a static dep inside `build()`, reruns build when it changes |
|
|
157
|
+
| `subscribe(listener, selector)` | raw subscription, returns unsub fn |
|
|
158
|
+
|
|
159
|
+
### `useWatch(store, selector?)` — `trisgeist/react`
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { useWatch } from "trisgeist/react";
|
|
163
|
+
|
|
164
|
+
const state = useWatch(myNotifier); // whole state, re-renders on any change
|
|
165
|
+
const count = useWatch(myNotifier, (o) => o.state.count); // only re-renders when count changes
|
|
166
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Notifier } from "./src/Notifier";
|
package/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Notifier } from "./src/Notifier";
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "trisgeist",
|
|
3
|
+
"license": "WTFPL",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"module": "dist/index.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./react": {
|
|
15
|
+
"types": "./dist/src/react.d.ts",
|
|
16
|
+
"import": "./dist/src/react.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/bun": "latest",
|
|
21
|
+
"@types/react": ">=18.0.0"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"typescript": "^5",
|
|
25
|
+
"react": ">=18.0.0"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "bunx tsc",
|
|
29
|
+
"prepare": "bunx tsc"
|
|
30
|
+
}
|
|
31
|
+
}
|