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.
@@ -0,0 +1,8 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(bunx tsc *)",
5
+ "Bash(npm search *)"
6
+ ]
7
+ }
8
+ }
@@ -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
+ ```
@@ -0,0 +1,2 @@
1
+ export { Notifier } from "./src/Notifier";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -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
+ }