@clagradi/effect-runtime 0.1.0 → 0.1.1
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 +108 -94
- package/package.json +27 -3
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# effect-runtime
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@clagradi/effect-runtime)
|
|
4
|
+
[](https://github.com/clagradi/UseEffectState/actions/workflows/ci.yml)
|
|
5
|
+
[](https://github.com/clagradi/UseEffectState/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
`@clagradi/effect-runtime` is a small React 18+ primitive for safer async effects.
|
|
4
8
|
|
|
5
9
|
## Install
|
|
6
10
|
|
|
@@ -8,77 +12,94 @@
|
|
|
8
12
|
npm i @clagradi/effect-runtime
|
|
9
13
|
```
|
|
10
14
|
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @clagradi/effect-runtime
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
yarn add @clagradi/effect-runtime
|
|
21
|
+
```
|
|
22
|
+
|
|
11
23
|
## API
|
|
12
24
|
|
|
13
25
|
```ts
|
|
14
26
|
export function useEvent<T extends (...args: any[]) => any>(fn: T): T;
|
|
15
27
|
|
|
16
28
|
type EffectTaskScope = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
signal: AbortSignal;
|
|
30
|
+
runId: number;
|
|
31
|
+
isActive(): boolean;
|
|
32
|
+
commit(fn: () => void): void;
|
|
33
|
+
onCleanup(fn: () => void): void;
|
|
22
34
|
};
|
|
23
35
|
|
|
24
36
|
type EffectTask =
|
|
25
|
-
|
|
26
|
-
|
|
37
|
+
(scope: EffectTaskScope) =>
|
|
38
|
+
void | (() => void) | Promise<void | (() => void)>;
|
|
27
39
|
|
|
28
40
|
export function useEffectTask(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
task: EffectTask,
|
|
42
|
+
deps: any[],
|
|
43
|
+
options?: { layout?: boolean; onError?: (err: unknown) => void; debugName?: string }
|
|
32
44
|
): void;
|
|
33
45
|
```
|
|
34
46
|
|
|
35
|
-
##
|
|
47
|
+
## Why better than useEffect
|
|
48
|
+
|
|
49
|
+
- Per-run `AbortController` (auto abort on rerun/unmount)
|
|
50
|
+
- `commit()` anti-race guard for stale async completions
|
|
51
|
+
- Late async cleanup is still executed after dispose
|
|
52
|
+
- `onCleanup()` supports multiple cleanups with LIFO order
|
|
53
|
+
- `useEvent` gives stable callbacks without stale closures
|
|
54
|
+
- StrictMode-safe effect lifecycle behavior
|
|
55
|
+
|
|
56
|
+
## Examples
|
|
36
57
|
|
|
37
|
-
###
|
|
58
|
+
### 1) Fetch with `signal` + anti-race `commit`
|
|
38
59
|
|
|
39
60
|
```tsx
|
|
40
61
|
import { useState } from 'react';
|
|
41
62
|
import { useEffectTask } from '@clagradi/effect-runtime';
|
|
42
63
|
|
|
43
64
|
export function UserCard({ userId }: { userId: string }) {
|
|
44
|
-
|
|
65
|
+
const [name, setName] = useState('loading...');
|
|
45
66
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
useEffectTask(
|
|
68
|
+
async ({ signal, commit }) => {
|
|
69
|
+
const response = await fetch(`/api/users/${userId}`, { signal });
|
|
70
|
+
const user = (await response.json()) as { name: string };
|
|
50
71
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
72
|
+
commit(() => {
|
|
73
|
+
setName(user.name);
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
[userId]
|
|
77
|
+
);
|
|
57
78
|
|
|
58
|
-
|
|
79
|
+
return <div>{name}</div>;
|
|
59
80
|
}
|
|
60
81
|
```
|
|
61
82
|
|
|
62
|
-
###
|
|
83
|
+
### 2) Interval with `useEvent`
|
|
63
84
|
|
|
64
85
|
```tsx
|
|
65
86
|
import { useState } from 'react';
|
|
66
87
|
import { useEffectTask, useEvent } from '@clagradi/effect-runtime';
|
|
67
88
|
|
|
68
89
|
export function Counter() {
|
|
69
|
-
|
|
70
|
-
|
|
90
|
+
const [count, setCount] = useState(0);
|
|
91
|
+
const onTick = useEvent(() => setCount((value) => value + 1));
|
|
71
92
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
93
|
+
useEffectTask(({ onCleanup }) => {
|
|
94
|
+
const handle = setInterval(() => onTick(), 1000);
|
|
95
|
+
onCleanup(() => clearInterval(handle));
|
|
96
|
+
}, []);
|
|
76
97
|
|
|
77
|
-
|
|
98
|
+
return <span>{count}</span>;
|
|
78
99
|
}
|
|
79
100
|
```
|
|
80
101
|
|
|
81
|
-
###
|
|
102
|
+
### 3) Subscription with `onCleanup`
|
|
82
103
|
|
|
83
104
|
```tsx
|
|
84
105
|
import { useEffectTask } from '@clagradi/effect-runtime';
|
|
@@ -87,94 +108,87 @@ type Subscription = { unsubscribe: () => void };
|
|
|
87
108
|
declare function subscribeToRoom(roomId: string, cb: (msg: string) => void): Subscription;
|
|
88
109
|
|
|
89
110
|
export function RoomFeed({ roomId }: { roomId: string }) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
111
|
+
useEffectTask(({ onCleanup }) => {
|
|
112
|
+
const sub = subscribeToRoom(roomId, (msg) => {
|
|
113
|
+
console.log(msg);
|
|
114
|
+
});
|
|
94
115
|
|
|
95
|
-
|
|
96
|
-
|
|
116
|
+
onCleanup(() => sub.unsubscribe());
|
|
117
|
+
}, [roomId]);
|
|
97
118
|
|
|
98
|
-
|
|
119
|
+
return null;
|
|
99
120
|
}
|
|
100
121
|
```
|
|
101
122
|
|
|
102
|
-
## Why better than useEffect
|
|
103
|
-
|
|
104
|
-
- Per-run `AbortController` (auto-abort on rerun/unmount)
|
|
105
|
-
- `commit()` anti-race guard for stale async completions
|
|
106
|
-
- Multiple cleanups with LIFO execution via `onCleanup()`
|
|
107
|
-
- Late async cleanup is never lost (executes immediately if resolved after dispose)
|
|
108
|
-
- `useEvent` gives stable identity + latest closure
|
|
109
|
-
|
|
110
123
|
## Caveats
|
|
111
124
|
|
|
112
|
-
-
|
|
113
|
-
- No SSR orchestration
|
|
125
|
+
- Not a data-fetching cache (this is not React Query/SWR).
|
|
126
|
+
- No caching/SSR orchestration features.
|
|
114
127
|
|
|
115
128
|
## Release checklist
|
|
116
129
|
|
|
117
|
-
1. Run
|
|
130
|
+
1. Run checks:
|
|
118
131
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
132
|
+
```bash
|
|
133
|
+
npm run typecheck
|
|
134
|
+
npm run test
|
|
135
|
+
npm run build
|
|
136
|
+
```
|
|
124
137
|
|
|
125
|
-
2.
|
|
138
|
+
2. Pack the library:
|
|
126
139
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
140
|
+
```bash
|
|
141
|
+
npm pack
|
|
142
|
+
# optional sanity check only
|
|
143
|
+
npm run pack:check
|
|
144
|
+
```
|
|
130
145
|
|
|
131
|
-
3. Install
|
|
146
|
+
3. Install in a separate consumer app:
|
|
132
147
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
148
|
+
```bash
|
|
149
|
+
# from local folder
|
|
150
|
+
npm i /absolute/path/to/effect-runtime
|
|
136
151
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
152
|
+
# from package tarball
|
|
153
|
+
npm i /absolute/path/to/effect-runtime/clagradi-effect-runtime-<version>.tgz
|
|
154
|
+
```
|
|
140
155
|
|
|
141
|
-
4. Verify in
|
|
156
|
+
4. Verify in consumer app:
|
|
142
157
|
|
|
143
|
-
|
|
144
|
-
|
|
158
|
+
- `import { useEffectTask, useEvent } from '@clagradi/effect-runtime'` resolves.
|
|
159
|
+
- TypeScript sees package types from `dist/index.d.ts`.
|
|
145
160
|
|
|
146
161
|
## Publishing to npm
|
|
147
162
|
|
|
148
|
-
1. Check package name
|
|
163
|
+
1. Check package name status:
|
|
149
164
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
```
|
|
165
|
+
```bash
|
|
166
|
+
npm view @clagradi/effect-runtime
|
|
167
|
+
```
|
|
154
168
|
|
|
155
|
-
2.
|
|
169
|
+
2. Bump version for the next release:
|
|
156
170
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
171
|
+
```bash
|
|
172
|
+
npm version patch # or minor / major
|
|
173
|
+
```
|
|
160
174
|
|
|
161
|
-
3. Publish
|
|
175
|
+
3. Publish manually:
|
|
162
176
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
177
|
+
```bash
|
|
178
|
+
npm login
|
|
179
|
+
npm publish --access public
|
|
180
|
+
```
|
|
166
181
|
|
|
167
|
-
|
|
182
|
+
4. Or publish via CI on tag push:
|
|
168
183
|
|
|
169
|
-
|
|
184
|
+
```bash
|
|
185
|
+
git push --follow-tags
|
|
186
|
+
```
|
|
170
187
|
|
|
171
|
-
|
|
172
|
-
npm version patch # or minor / major
|
|
173
|
-
git push
|
|
174
|
-
git push --tags
|
|
175
|
-
```
|
|
188
|
+
Scoped packages default to private. Always pass `--access public` for public publish.
|
|
176
189
|
|
|
177
|
-
|
|
190
|
+
CI publish workflow runs on tags matching `v*` and publishes with provenance:
|
|
178
191
|
|
|
179
|
-
|
|
180
|
-
|
|
192
|
+
```bash
|
|
193
|
+
npm publish --provenance --access public
|
|
194
|
+
```
|
package/package.json
CHANGED
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clagradi/effect-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Better async effect primitive for React 18+",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/clagradi/UseEffectState.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/clagradi/UseEffectState/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/clagradi/UseEffectState#readme",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"react",
|
|
16
|
+
"hooks",
|
|
17
|
+
"useeffect",
|
|
18
|
+
"async",
|
|
19
|
+
"abortcontroller",
|
|
20
|
+
"effect",
|
|
21
|
+
"concurrency"
|
|
22
|
+
],
|
|
6
23
|
"main": "dist/index.cjs",
|
|
7
24
|
"module": "dist/index.js",
|
|
8
25
|
"types": "dist/index.d.ts",
|
|
@@ -13,14 +30,21 @@
|
|
|
13
30
|
"require": "./dist/index.cjs"
|
|
14
31
|
}
|
|
15
32
|
},
|
|
16
|
-
"files": [
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
17
36
|
"sideEffects": false,
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public",
|
|
39
|
+
"provenance": true
|
|
40
|
+
},
|
|
18
41
|
"scripts": {
|
|
19
42
|
"build": "tsup",
|
|
43
|
+
"pack:check": "npm pack --dry-run",
|
|
20
44
|
"test": "vitest run",
|
|
21
45
|
"test:watch": "vitest",
|
|
22
46
|
"typecheck": "tsc --noEmit",
|
|
23
|
-
"prepublishOnly": "npm run typecheck && npm run test && npm run build"
|
|
47
|
+
"prepublishOnly": "npm run typecheck && npm run test && npm run build && npm run pack:check"
|
|
24
48
|
},
|
|
25
49
|
"peerDependencies": {
|
|
26
50
|
"react": ">=18"
|