@flexsurfer/reflex 0.1.23 → 0.1.25
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/README.md +29 -1
- package/dist/index.cjs +33 -5
- package/dist/index.mjs +33 -5
- package/llms.txt +186 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -32,11 +32,39 @@ After many years of building applications with re-frame in the ClojureScript wor
|
|
|
32
32
|
- [Step-by-Step Tutorial](https://reflex.js.org/docs/quick-start.html)
|
|
33
33
|
- [Best Practices](https://reflex.js.org/docs/api-reference.html)
|
|
34
34
|
- [API Reference](https://reflex.js.org/docs/best-practices.html)
|
|
35
|
+
- [AI Context (llms.txt)](./llms.txt) - Compact guide for AI-assisted Reflex project scaffolding and state architecture
|
|
35
36
|
- [re-frame Documentation](https://day8.github.io/re-frame/re-frame/) - The original and comprehensive guide to understanding the philosophy and patterns
|
|
36
37
|
|
|
37
38
|
- Examples
|
|
38
39
|
- [TodoMVC](https://github.com/flexsurfer/reflex/tree/main/examples/todomvc) - Classic todo app implementation showcasing core reflex patterns
|
|
39
|
-
- [Einbürgerungstest](https://github.com/flexsurfer/einburgerungstest/) -
|
|
40
|
+
- [Einbürgerungstest](https://github.com/flexsurfer/einburgerungstest/) - Cross-platform web/mobile app built with reflex ([Live Demo](https://www.ebtest.org/))
|
|
41
|
+
- [StarRupture Planner](https://github.com/flexsurfer/starrupture-planner) - Production planning tool built with reflex ([Live Demo](https://www.starrupture-planner.com/))
|
|
42
|
+
|
|
43
|
+
## 🤖 Using with AI Assistants
|
|
44
|
+
|
|
45
|
+
Reflex ships an [`llms.txt`](./llms.txt) file — a compact, AI-readable guide covering state architecture, event/effect/subscription patterns, and code generation rules. Point your AI tool at it so it generates idiomatic Reflex code from the start.
|
|
46
|
+
|
|
47
|
+
**Claude Code** — add to `CLAUDE.md` in your project root:
|
|
48
|
+
```bash
|
|
49
|
+
curl -o CLAUDE.md https://raw.githubusercontent.com/flexsurfer/reflex/main/llms.txt
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Codex (OpenAI)** — add to `AGENTS.md` in your project root:
|
|
53
|
+
```bash
|
|
54
|
+
curl -o AGENTS.md https://raw.githubusercontent.com/flexsurfer/reflex/main/llms.txt
|
|
55
|
+
```
|
|
56
|
+
Codex reads project instructions from `AGENTS.md`, so this gives it Reflex-specific architecture and code generation rules for your repo.
|
|
57
|
+
|
|
58
|
+
**Cursor** — create `.cursor/rules/reflex.mdc` and paste the contents, or reference via project rules.
|
|
59
|
+
|
|
60
|
+
**GitHub Copilot** — add to `.github/copilot-instructions.md`:
|
|
61
|
+
```bash
|
|
62
|
+
curl -o .github/copilot-instructions.md https://raw.githubusercontent.com/flexsurfer/reflex/main/llms.txt
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**ChatGPT / Claude.ai Projects** — upload `llms.txt` from your `node_modules/@flexsurfer/reflex/` as a project file, or paste the raw URL into the conversation.
|
|
66
|
+
|
|
67
|
+
The file is also included in the npm package, so after installing Reflex you can find it at `node_modules/@flexsurfer/reflex/llms.txt`.
|
|
40
68
|
|
|
41
69
|
## 🤝 Contributing
|
|
42
70
|
|
package/dist/index.cjs
CHANGED
|
@@ -127,12 +127,16 @@ function clearHandlers(kind, id) {
|
|
|
127
127
|
for (const k in kindToIdToHandler) {
|
|
128
128
|
kindToIdToHandler[k] = {};
|
|
129
129
|
}
|
|
130
|
+
clearRootSubSources();
|
|
130
131
|
} else if (id == null) {
|
|
131
132
|
if (!(kind in kindToIdToHandler)) {
|
|
132
133
|
consoleLog("error", `[reflex] Unknown kind: ${kind}`);
|
|
133
134
|
return;
|
|
134
135
|
}
|
|
135
136
|
kindToIdToHandler[kind] = {};
|
|
137
|
+
if (kind === "sub") {
|
|
138
|
+
clearRootSubSources();
|
|
139
|
+
}
|
|
136
140
|
} else {
|
|
137
141
|
if (kindToIdToHandler[kind][id]) {
|
|
138
142
|
delete kindToIdToHandler[kind][id];
|
|
@@ -161,6 +165,16 @@ function clearReactions(id) {
|
|
|
161
165
|
reactionsRegistry.delete(id);
|
|
162
166
|
}
|
|
163
167
|
}
|
|
168
|
+
var rootSubIdBySource = /* @__PURE__ */ new Map();
|
|
169
|
+
function setRootSubSource(subId, sourceKey) {
|
|
170
|
+
rootSubIdBySource.set(sourceKey, subId);
|
|
171
|
+
}
|
|
172
|
+
function getRootSubIdBySource(sourceKey) {
|
|
173
|
+
return rootSubIdBySource.get(sourceKey);
|
|
174
|
+
}
|
|
175
|
+
function clearRootSubSources() {
|
|
176
|
+
rootSubIdBySource.clear();
|
|
177
|
+
}
|
|
164
178
|
function clearSubs() {
|
|
165
179
|
clearReactions();
|
|
166
180
|
clearHandlers("sub");
|
|
@@ -230,7 +244,14 @@ function updateAppDbWithPatches(newDb, patches) {
|
|
|
230
244
|
const pathSegments = patch.path;
|
|
231
245
|
if (pathSegments.length > 0) {
|
|
232
246
|
const rootKey = pathSegments[0];
|
|
233
|
-
|
|
247
|
+
if (typeof rootKey !== "string") {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const subId = getRootSubIdBySource(rootKey);
|
|
251
|
+
if (!subId) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const subVectorKey = JSON.stringify([subId]);
|
|
234
255
|
const reaction = getReaction(subVectorKey);
|
|
235
256
|
if (reaction) {
|
|
236
257
|
if (!reaction.isRoot) {
|
|
@@ -1089,16 +1110,23 @@ var Reaction = class _Reaction {
|
|
|
1089
1110
|
// src/subs.ts
|
|
1090
1111
|
var KIND4 = "sub";
|
|
1091
1112
|
var KIND_DEPS = "subDeps";
|
|
1113
|
+
function registerRootSub(id, sourceKey) {
|
|
1114
|
+
const conflictingSubId = getRootSubIdBySource(sourceKey);
|
|
1115
|
+
if (conflictingSubId && conflictingSubId !== id) {
|
|
1116
|
+
consoleLog("error", `[reflex] Subscription with id '${id}' will be overridden. Root key '${sourceKey}' is already used by subscription '${conflictingSubId}'.`);
|
|
1117
|
+
}
|
|
1118
|
+
setRootSubSource(id, sourceKey);
|
|
1119
|
+
registerHandler(KIND4, id, () => getAppDb()[sourceKey]);
|
|
1120
|
+
registerHandler(KIND_DEPS, id, () => []);
|
|
1121
|
+
}
|
|
1092
1122
|
function regSub(id, computeFn, depsFn, config) {
|
|
1093
1123
|
if (hasHandler(KIND4, id)) {
|
|
1094
1124
|
consoleLog("warn", `[reflex] Overriding. Subscription '${id}' already registered.`);
|
|
1095
1125
|
}
|
|
1096
1126
|
if (!computeFn) {
|
|
1097
|
-
|
|
1098
|
-
registerHandler(KIND_DEPS, id, () => []);
|
|
1127
|
+
registerRootSub(id, id);
|
|
1099
1128
|
} else if (typeof computeFn === "string") {
|
|
1100
|
-
|
|
1101
|
-
registerHandler(KIND_DEPS, id, () => []);
|
|
1129
|
+
registerRootSub(id, computeFn);
|
|
1102
1130
|
} else {
|
|
1103
1131
|
if (!depsFn) {
|
|
1104
1132
|
consoleLog("error", `[reflex] Subscription '${id}' has computeFn but missing depsFn. Computed subscriptions must specify their dependencies.`);
|
package/dist/index.mjs
CHANGED
|
@@ -49,12 +49,16 @@ function clearHandlers(kind, id) {
|
|
|
49
49
|
for (const k in kindToIdToHandler) {
|
|
50
50
|
kindToIdToHandler[k] = {};
|
|
51
51
|
}
|
|
52
|
+
clearRootSubSources();
|
|
52
53
|
} else if (id == null) {
|
|
53
54
|
if (!(kind in kindToIdToHandler)) {
|
|
54
55
|
consoleLog("error", `[reflex] Unknown kind: ${kind}`);
|
|
55
56
|
return;
|
|
56
57
|
}
|
|
57
58
|
kindToIdToHandler[kind] = {};
|
|
59
|
+
if (kind === "sub") {
|
|
60
|
+
clearRootSubSources();
|
|
61
|
+
}
|
|
58
62
|
} else {
|
|
59
63
|
if (kindToIdToHandler[kind][id]) {
|
|
60
64
|
delete kindToIdToHandler[kind][id];
|
|
@@ -83,6 +87,16 @@ function clearReactions(id) {
|
|
|
83
87
|
reactionsRegistry.delete(id);
|
|
84
88
|
}
|
|
85
89
|
}
|
|
90
|
+
var rootSubIdBySource = /* @__PURE__ */ new Map();
|
|
91
|
+
function setRootSubSource(subId, sourceKey) {
|
|
92
|
+
rootSubIdBySource.set(sourceKey, subId);
|
|
93
|
+
}
|
|
94
|
+
function getRootSubIdBySource(sourceKey) {
|
|
95
|
+
return rootSubIdBySource.get(sourceKey);
|
|
96
|
+
}
|
|
97
|
+
function clearRootSubSources() {
|
|
98
|
+
rootSubIdBySource.clear();
|
|
99
|
+
}
|
|
86
100
|
function clearSubs() {
|
|
87
101
|
clearReactions();
|
|
88
102
|
clearHandlers("sub");
|
|
@@ -152,7 +166,14 @@ function updateAppDbWithPatches(newDb, patches) {
|
|
|
152
166
|
const pathSegments = patch.path;
|
|
153
167
|
if (pathSegments.length > 0) {
|
|
154
168
|
const rootKey = pathSegments[0];
|
|
155
|
-
|
|
169
|
+
if (typeof rootKey !== "string") {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const subId = getRootSubIdBySource(rootKey);
|
|
173
|
+
if (!subId) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const subVectorKey = JSON.stringify([subId]);
|
|
156
177
|
const reaction = getReaction(subVectorKey);
|
|
157
178
|
if (reaction) {
|
|
158
179
|
if (!reaction.isRoot) {
|
|
@@ -1011,16 +1032,23 @@ var Reaction = class _Reaction {
|
|
|
1011
1032
|
// src/subs.ts
|
|
1012
1033
|
var KIND4 = "sub";
|
|
1013
1034
|
var KIND_DEPS = "subDeps";
|
|
1035
|
+
function registerRootSub(id, sourceKey) {
|
|
1036
|
+
const conflictingSubId = getRootSubIdBySource(sourceKey);
|
|
1037
|
+
if (conflictingSubId && conflictingSubId !== id) {
|
|
1038
|
+
consoleLog("error", `[reflex] Subscription with id '${id}' will be overridden. Root key '${sourceKey}' is already used by subscription '${conflictingSubId}'.`);
|
|
1039
|
+
}
|
|
1040
|
+
setRootSubSource(id, sourceKey);
|
|
1041
|
+
registerHandler(KIND4, id, () => getAppDb()[sourceKey]);
|
|
1042
|
+
registerHandler(KIND_DEPS, id, () => []);
|
|
1043
|
+
}
|
|
1014
1044
|
function regSub(id, computeFn, depsFn, config) {
|
|
1015
1045
|
if (hasHandler(KIND4, id)) {
|
|
1016
1046
|
consoleLog("warn", `[reflex] Overriding. Subscription '${id}' already registered.`);
|
|
1017
1047
|
}
|
|
1018
1048
|
if (!computeFn) {
|
|
1019
|
-
|
|
1020
|
-
registerHandler(KIND_DEPS, id, () => []);
|
|
1049
|
+
registerRootSub(id, id);
|
|
1021
1050
|
} else if (typeof computeFn === "string") {
|
|
1022
|
-
|
|
1023
|
-
registerHandler(KIND_DEPS, id, () => []);
|
|
1051
|
+
registerRootSub(id, computeFn);
|
|
1024
1052
|
} else {
|
|
1025
1053
|
if (!depsFn) {
|
|
1026
1054
|
consoleLog("error", `[reflex] Subscription '${id}' has computeFn but missing depsFn. Computed subscriptions must specify their dependencies.`);
|
package/llms.txt
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
## 0) Reflex Quick Info
|
|
2
|
+
|
|
3
|
+
- Reflex is a React state-management library inspired by ClojureScript re-frame (event-driven updates, subscriptions for derived data, effects/coeffects for side effects).
|
|
4
|
+
- Install:
|
|
5
|
+
- Runtime: `npm i @flexsurfer/reflex`
|
|
6
|
+
- Devtools (dev only): `npm i -D @flexsurfer/reflex-devtools`
|
|
7
|
+
- Docs:
|
|
8
|
+
- Main docs: [reflex.js.org/docs](https://reflex.js.org/docs)
|
|
9
|
+
- Best practices: [reflex.js.org/docs/best-practices.html](https://reflex.js.org/docs/best-practices.html)
|
|
10
|
+
- Packages: [@flexsurfer/reflex](https://www.npmjs.com/package/@flexsurfer/reflex), [@flexsurfer/reflex-devtools](https://www.npmjs.com/package/@flexsurfer/reflex-devtools)
|
|
11
|
+
|
|
12
|
+
## 1) State Architecture
|
|
13
|
+
|
|
14
|
+
Use this baseline structure:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
src/state/
|
|
18
|
+
db.ts
|
|
19
|
+
event-ids.ts
|
|
20
|
+
events.ts
|
|
21
|
+
effect-ids.ts
|
|
22
|
+
effects.ts
|
|
23
|
+
sub-ids.ts
|
|
24
|
+
subs.ts
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Rules:
|
|
28
|
+
- Keep IDs centralized in `event-ids.ts`, `effect-ids.ts`, `sub-ids.ts`.
|
|
29
|
+
- Keep init in `db.ts` (`initAppDb(...)`).
|
|
30
|
+
- Register events/effects/subscriptions via side-effect imports in app bootstrap (`main.tsx` style).
|
|
31
|
+
- If `events.ts` or `subs.ts` grows too large, split by feature, keep shared/global state separate.
|
|
32
|
+
|
|
33
|
+
## 2) State Shape
|
|
34
|
+
|
|
35
|
+
- Grow horizontally (new top-level feature keys), avoid deep nesting.
|
|
36
|
+
- Normalize entity-like data (`byId` maps + id arrays) for fast lookup and simpler updates.
|
|
37
|
+
- Keep UI state explicit and separate from domain entities.
|
|
38
|
+
- If using `Map`/`Set` in DB, call `enableMapSet()` before `initAppDb`.
|
|
39
|
+
|
|
40
|
+
## 3) Events `regEvent`
|
|
41
|
+
|
|
42
|
+
- Events must be synchronous and focused on state transitions.
|
|
43
|
+
- Events should read all required data from `draftDb` (or via coeffects) — never rely on callers passing subscription-derived state; dispatch calls from views should only carry user intent (IDs, input values, flags).
|
|
44
|
+
- Validate inputs and guard clauses first; return early on invalid state.
|
|
45
|
+
- Mutate only required fields on `draftDb`.
|
|
46
|
+
- Avoid unnecessary object/array recreation (`{...obj}`, `[...arr]`) when no actual change is needed.
|
|
47
|
+
- Never perform async/API/localStorage work directly in events.
|
|
48
|
+
- Return effect tuples for side effects.
|
|
49
|
+
- When sending mutated draft data to effects, always use `current(...)`.
|
|
50
|
+
- Prefer deterministic coeffects (time/id/random/env) instead of direct globals for testability.
|
|
51
|
+
|
|
52
|
+
Event naming:
|
|
53
|
+
- Keep exported constant keys in `UPPER_SNAKE_CASE`.
|
|
54
|
+
- Use namespaced string values: `feature/action` (example: `bases/create`).
|
|
55
|
+
- Keep key and value aligned:
|
|
56
|
+
- `BASES_CREATE` -> `bases/create`
|
|
57
|
+
- `PRODUCTION_PLAN_ADD_BUILDINGS_TO_BASE` -> `production_plan/add_buildings_to_base`
|
|
58
|
+
|
|
59
|
+
## 4) Effects and Coeffects
|
|
60
|
+
|
|
61
|
+
- Put all I/O here: localStorage, HTTP, timers, analytics, navigation.
|
|
62
|
+
- Effects should be small, defensive, and fail-soft (log, do not crash app state flow).
|
|
63
|
+
|
|
64
|
+
## 5) Subscriptions `regSub`
|
|
65
|
+
|
|
66
|
+
- Define root subscriptions first `regSub(id, "pathKey")`.
|
|
67
|
+
- Build derived subscriptions from other subscriptions only.
|
|
68
|
+
- Use parameterized subscriptions for by-id and section-specific queries.
|
|
69
|
+
- Keep subscriptions deterministic and lightweight.
|
|
70
|
+
- Subscriptions must return data shaped and ready for direct view consumption — all filtering, sorting, formatting, and joining should happen in the subscription layer, not in React components.
|
|
71
|
+
- Move heavy computations to events (precompute once, read many); avoid repeated expensive derivations in hot subscriptions.
|
|
72
|
+
|
|
73
|
+
## 6) React Component Contract
|
|
74
|
+
|
|
75
|
+
- Components should only:
|
|
76
|
+
- subscribe to minimal required data
|
|
77
|
+
- dispatch events on user intent (pass only user-provided values — e.g. input text, selected id — never forward subscription data back through dispatch; the event handler should read everything it needs from the DB itself)
|
|
78
|
+
- render UI
|
|
79
|
+
- Never transform, filter, sort, or reshape subscription data inside a component — if the view needs a different shape, create a dedicated subscription that returns it ready to render.
|
|
80
|
+
- Use direct React hooks only for local/ephemeral component concerns:
|
|
81
|
+
- temporary form/input draft state before dispatch
|
|
82
|
+
- UI-only toggles scoped to one component (hover/open/focus)
|
|
83
|
+
- refs, DOM measurement, animation lifecycle
|
|
84
|
+
- Do not mirror Reflex global state in `useState`/`useReducer`.
|
|
85
|
+
- If state is shared, persisted, or business-relevant, keep it in Reflex DB via events/subscriptions.
|
|
86
|
+
- Keep `useEffect` thin in components; business side effects belong in Reflex effects/coeffects.
|
|
87
|
+
- Do not place business rules/validation pipelines in component handlers.
|
|
88
|
+
- Avoid over-subscription (row/item components should not subscribe to full collections).
|
|
89
|
+
|
|
90
|
+
## 7) Test Minimum
|
|
91
|
+
|
|
92
|
+
For every new feature:
|
|
93
|
+
- Event tests: mutation correctness + emitted effect tuples.
|
|
94
|
+
- Subscription tests: derived outputs from fixed state fixtures.
|
|
95
|
+
|
|
96
|
+
## 8) AI Generation Checklist
|
|
97
|
+
|
|
98
|
+
Before finalizing generated code:
|
|
99
|
+
- IDs added/updated in all relevant `*-ids.ts` files.
|
|
100
|
+
- Event namespaced and descriptive.
|
|
101
|
+
- Side effects isolated to effects/coeffects.
|
|
102
|
+
- `current(...)` used when passing draft-derived data to effects.
|
|
103
|
+
- No unnecessary object/array recreation in events.
|
|
104
|
+
- Expensive work not placed in frequently re-run subscriptions.
|
|
105
|
+
- Subscriptions return view-ready data; components do not reshape subscription output.
|
|
106
|
+
- Dispatch calls from components pass only user intent, not subscription data; events read from DB.
|
|
107
|
+
- Tests added for new event/subscription behavior.
|
|
108
|
+
|
|
109
|
+
## 9) Starter Skeleton (copy pattern)
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import {
|
|
113
|
+
initAppDb,
|
|
114
|
+
regSub,
|
|
115
|
+
regEvent,
|
|
116
|
+
regEffect,
|
|
117
|
+
regCoeffect,
|
|
118
|
+
current,
|
|
119
|
+
dispatch,
|
|
120
|
+
useSubscription,
|
|
121
|
+
} from '@flexsurfer/reflex';
|
|
122
|
+
|
|
123
|
+
// event-ids.ts
|
|
124
|
+
export const EVENT_IDS = {
|
|
125
|
+
APP_INIT: 'app/init',
|
|
126
|
+
TODOS_ADD: 'todos/add',
|
|
127
|
+
} as const;
|
|
128
|
+
|
|
129
|
+
// effect-ids.ts
|
|
130
|
+
export const EFFECT_IDS = {
|
|
131
|
+
GET_TODOS: 'storage/get_todos',
|
|
132
|
+
SET_TODOS: 'storage/set_todos',
|
|
133
|
+
} as const;
|
|
134
|
+
|
|
135
|
+
// sub-ids.ts
|
|
136
|
+
export const SUB_IDS = {
|
|
137
|
+
TODOS_LIST: 'todos/list', // root sub
|
|
138
|
+
TODOS_OPEN_COUNT: 'todos/open_count', // computed sub
|
|
139
|
+
} as const;
|
|
140
|
+
|
|
141
|
+
// db.ts
|
|
142
|
+
type Todo = { id: string; text: string; done: boolean };
|
|
143
|
+
initAppDb({ todos: [] as Todo[] });
|
|
144
|
+
|
|
145
|
+
// effects.ts
|
|
146
|
+
regEffect(EFFECT_IDS.SET_TODOS, (todos: Todo[]) => {
|
|
147
|
+
localStorage.setItem('todos', JSON.stringify(todos));
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
regCoeffect(EFFECT_IDS.GET_TODOS, (coeffects) => {
|
|
151
|
+
const raw = localStorage.getItem('todos');
|
|
152
|
+
coeffects.localStoreTodos = raw ? (JSON.parse(raw) as Todo[]) : [];
|
|
153
|
+
return coeffects;
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// events.ts
|
|
157
|
+
regEvent(
|
|
158
|
+
EVENT_IDS.APP_INIT,
|
|
159
|
+
({ draftDb, localStoreTodos }) => {
|
|
160
|
+
draftDb.todos = Array.isArray(localStoreTodos) ? localStoreTodos : [];
|
|
161
|
+
},
|
|
162
|
+
[[EFFECT_IDS.GET_TODOS]]
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
regEvent(EVENT_IDS.TODOS_ADD, ({ draftDb }, text: string) => {
|
|
166
|
+
const clean = text.trim();
|
|
167
|
+
if (!clean) return;
|
|
168
|
+
draftDb.todos.push({ id: `todo_${Date.now()}`, text: clean, done: false });
|
|
169
|
+
return [[EFFECT_IDS.SET_TODOS, current(draftDb.todos)]];
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// subs.ts
|
|
173
|
+
regSub(SUB_IDS.TODOS_LIST, 'todos'); // root sub
|
|
174
|
+
|
|
175
|
+
regSub(
|
|
176
|
+
SUB_IDS.TODOS_OPEN_COUNT, // computed sub from root sub
|
|
177
|
+
(todos: Todo[]) => todos.filter((t) => !t.done).length,
|
|
178
|
+
() => [[SUB_IDS.TODOS_LIST]]
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Usage example (React):
|
|
182
|
+
// const todos = useSubscription([SUB_IDS.TODOS_LIST]);
|
|
183
|
+
// const openCount = useSubscription([SUB_IDS.TODOS_OPEN_COUNT]);
|
|
184
|
+
// dispatch([EVENT_IDS.APP_INIT]);
|
|
185
|
+
// dispatch([EVENT_IDS.TODOS_ADD, 'Buy milk']);
|
|
186
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flexsurfer/reflex",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
25
25
|
"README.md",
|
|
26
|
-
"LICENSE"
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"llms.txt"
|
|
27
28
|
],
|
|
28
29
|
"scripts": {
|
|
29
30
|
"build": "tsup",
|