jotai-solid-api 1.0.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/README.md +169 -0
- package/dist/index.d.ts +861 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1470 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# jotai-solid-api
|
|
2
|
+
|
|
3
|
+
Solid-style reactive primitives on top of `jotai/vanilla`, plus a React component wrapper so app code can stay hook-free.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install jotai-solid-api jotai react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## API
|
|
12
|
+
|
|
13
|
+
- `component(setup, { memo?, displayName? })`
|
|
14
|
+
- `createSignal(initial)`
|
|
15
|
+
- `fromSolidSignal(signal)` / `toSolidSignal(signal)`
|
|
16
|
+
- `fromSignal(signal)` / `toSignal(signal)`
|
|
17
|
+
- `createMemo(compute)`
|
|
18
|
+
- `createEffect(effect)`
|
|
19
|
+
- `createLayoutEffect(effect)`
|
|
20
|
+
- `createComputed(compute)`
|
|
21
|
+
- `onMount(callback)`
|
|
22
|
+
- `onCleanup(cleanup)` / `cleanup(cleanup)`
|
|
23
|
+
- `batch(fn)`
|
|
24
|
+
- `createSelector(source, equals?)`
|
|
25
|
+
- `resolveMaybeAccessor(value)` / `toValue(value)`
|
|
26
|
+
- `isAccessor(value)`
|
|
27
|
+
- `createResource(fetcher)`
|
|
28
|
+
- `createResource(source, fetcher)`
|
|
29
|
+
- `createAsync(compute)`
|
|
30
|
+
- `use(accessorOrPromise)`
|
|
31
|
+
- `createStore(initial)` / `setStore(next)`
|
|
32
|
+
- `createMutable(initial)` / `createMutableStore(initial)`
|
|
33
|
+
- `createReactiveArray(initial)` / `createArrayStore(initial)`
|
|
34
|
+
- `createProjection(source, initialize, mutate)`
|
|
35
|
+
- `createArrayProjection(source, { key, map, update })`
|
|
36
|
+
- `createLinkedSignal(derive)` / `linkedSignal(derive)`
|
|
37
|
+
- `lazy(loader)` / `Suspense`
|
|
38
|
+
- `untrack(fn)`
|
|
39
|
+
- control flow: `Show`, `For`, `Index`, `Switch`, `Match`
|
|
40
|
+
|
|
41
|
+
### Aliases
|
|
42
|
+
|
|
43
|
+
- Primitives: `signal`, `memo`, `effect`, `layoutEffect`, `computed`, `mount`
|
|
44
|
+
- Async: `resource`, `asyncSignal`
|
|
45
|
+
- Stores/projections: `store`, `sotre`, `mutable`, `projection`, `arrayProjection`
|
|
46
|
+
- Components: `defineComponent`
|
|
47
|
+
|
|
48
|
+
## Example
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import {
|
|
52
|
+
component,
|
|
53
|
+
createEffect,
|
|
54
|
+
createLayoutEffect,
|
|
55
|
+
createMemo,
|
|
56
|
+
createMutable,
|
|
57
|
+
createLinkedSignal,
|
|
58
|
+
createArrayProjection,
|
|
59
|
+
createProjection,
|
|
60
|
+
createReactiveArray,
|
|
61
|
+
createResource,
|
|
62
|
+
createSignal,
|
|
63
|
+
createStore,
|
|
64
|
+
For,
|
|
65
|
+
Match,
|
|
66
|
+
Suspense,
|
|
67
|
+
lazy,
|
|
68
|
+
Show,
|
|
69
|
+
Switch,
|
|
70
|
+
} from "jotai-solid-api";
|
|
71
|
+
|
|
72
|
+
type CounterProps = { step?: number };
|
|
73
|
+
|
|
74
|
+
export const Counter = component<CounterProps>((props) => {
|
|
75
|
+
const [count, setCount] = createSignal(0);
|
|
76
|
+
const doubled = createMemo(() => count() * 2);
|
|
77
|
+
const [store, setStore] = createStore({ filter: "all" as "all" | "active" });
|
|
78
|
+
const mutable = createMutable({ clicks: 0, nested: { enabled: true } });
|
|
79
|
+
const items = createReactiveArray<string>(["a", "b"]);
|
|
80
|
+
const projectedUsers = createArrayProjection(users.latest, {
|
|
81
|
+
key: (user) => user.id,
|
|
82
|
+
map: (user) => ({ ...user, selected: false }),
|
|
83
|
+
update: (target, user) => {
|
|
84
|
+
target.name = user.name;
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
const selected = createLinkedSignal(() => projectedUsers[0]?.id ?? null);
|
|
88
|
+
const [users] = createResource(async () => {
|
|
89
|
+
const response = await fetch("/api/users");
|
|
90
|
+
return (await response.json()) as Array<{ id: string; name: string }>;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
createLayoutEffect(() => {
|
|
94
|
+
console.log("layout count", count());
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
createEffect(() => {
|
|
98
|
+
console.log("count", count(), "step", props().step ?? 1);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return () => (
|
|
102
|
+
<div>
|
|
103
|
+
<button onClick={() => setCount((n) => n + (props().step ?? 1))}>
|
|
104
|
+
{count()} / {doubled()}
|
|
105
|
+
</button>
|
|
106
|
+
<button onClick={() => setStore({ filter: store.filter === "all" ? "active" : "all" })}>
|
|
107
|
+
filter: {store.filter}
|
|
108
|
+
</button>
|
|
109
|
+
<button onClick={() => { mutable.clicks += 1; items.push(String(mutable.clicks)); }}>
|
|
110
|
+
mutable clicks: {mutable.clicks}
|
|
111
|
+
</button>
|
|
112
|
+
|
|
113
|
+
<Show when={users.loading()} fallback={<p>Loading users...</p>}>
|
|
114
|
+
<For each={projectedUsers} fallback={<p>No users</p>}>
|
|
115
|
+
{(user) => (
|
|
116
|
+
<p onClick={() => selected.set(user.id)}>
|
|
117
|
+
{user.name} {selected.value() === user.id ? "(selected)" : ""}
|
|
118
|
+
</p>
|
|
119
|
+
)}
|
|
120
|
+
</For>
|
|
121
|
+
</Show>
|
|
122
|
+
|
|
123
|
+
<Switch fallback={<p>Ready</p>}>
|
|
124
|
+
<Match when={users.error()}>{(err) => <p>Failed: {String(err)}</p>}</Match>
|
|
125
|
+
<Match when={users.loading()}>
|
|
126
|
+
<p>Fetching...</p>
|
|
127
|
+
</Match>
|
|
128
|
+
</Switch>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}, { memo: true });
|
|
132
|
+
|
|
133
|
+
const LazyPanel = lazy(async () => import("./Panel"));
|
|
134
|
+
|
|
135
|
+
export const App = component(() => {
|
|
136
|
+
return () => (
|
|
137
|
+
<Suspense fallback={<p>Loading panel...</p>}>
|
|
138
|
+
<LazyPanel />
|
|
139
|
+
</Suspense>
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Notes
|
|
145
|
+
|
|
146
|
+
- `setup` runs once per component instance, and should return a render function (`() => ReactNode`).
|
|
147
|
+
- Reads inside the render function are tracked and trigger rerenders.
|
|
148
|
+
- Reads inside `createMemo` and effects are tracked and rerun when dependencies change.
|
|
149
|
+
- `createStore` is immutable-by-default (`setStore` updates), while `createMutable` and `createReactiveArray` allow direct mutation.
|
|
150
|
+
- React `lazy`/`Suspense` already work as-is; this package also re-exports compatible helpers so usage style stays consistent.
|
|
151
|
+
- `createProjection` keeps a stable mutable reference and applies granular mutations, useful for large list projections.
|
|
152
|
+
- `createArrayProjection` gives keyed move/insert/remove updates for projected arrays without full replacement.
|
|
153
|
+
- `createLinkedSignal` is a writable derived signal: user overrides persist until the source derivation changes.
|
|
154
|
+
- This gives Solid-like authoring ergonomics, but still follows React rendering semantics.
|
|
155
|
+
|
|
156
|
+
## Releasing
|
|
157
|
+
|
|
158
|
+
- Local bump helpers:
|
|
159
|
+
- `npm run release:patch`
|
|
160
|
+
- `npm run release:minor`
|
|
161
|
+
- `npm run release:major`
|
|
162
|
+
- Release PR automation: `.github/workflows/release-please.yml`
|
|
163
|
+
- Uses `googleapis/release-please-action` on `main`/`master` pushes.
|
|
164
|
+
- Opens/updates a release PR from Conventional Commit history.
|
|
165
|
+
- Merging that PR creates the GitHub release + tag.
|
|
166
|
+
- Publish automation: `.github/workflows/publish-npm.yml`
|
|
167
|
+
- Runs when a GitHub release is published.
|
|
168
|
+
- Checks out the release tag, runs tests/build, and publishes to npm.
|
|
169
|
+
- Uses repository secret `NPM_TOKEN` (mapped to `NODE_AUTH_TOKEN`).
|