@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.
Files changed (2) hide show
  1. package/README.md +108 -94
  2. package/package.json +27 -3
package/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # effect-runtime
2
2
 
3
- `@clagradi/effect-runtime` is a tiny React 18+ primitive that makes async effects safer than raw `useEffect`.
3
+ [![npm version](https://img.shields.io/npm/v/@clagradi/effect-runtime.svg)](https://www.npmjs.com/package/@clagradi/effect-runtime)
4
+ [![CI](https://github.com/clagradi/UseEffectState/actions/workflows/ci.yml/badge.svg)](https://github.com/clagradi/UseEffectState/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
- signal: AbortSignal;
18
- runId: number;
19
- isActive(): boolean;
20
- commit(fn: () => void): void;
21
- onCleanup(fn: () => void): void;
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
- (scope: EffectTaskScope) =>
26
- void | (() => void) | Promise<void | (() => void)>;
37
+ (scope: EffectTaskScope) =>
38
+ void | (() => void) | Promise<void | (() => void)>;
27
39
 
28
40
  export function useEffectTask(
29
- task: EffectTask,
30
- deps: any[],
31
- options?: { layout?: boolean; onError?: (err: unknown) => void; debugName?: string }
41
+ task: EffectTask,
42
+ deps: any[],
43
+ options?: { layout?: boolean; onError?: (err: unknown) => void; debugName?: string }
32
44
  ): void;
33
45
  ```
34
46
 
35
- ## Quickstart
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
- ### Example 1: fetch with `signal` + anti-race `commit`
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
- const [name, setName] = useState('loading...');
65
+ const [name, setName] = useState('loading...');
45
66
 
46
- useEffectTask(
47
- async ({ signal, commit }) => {
48
- const response = await fetch(`/api/users/${userId}`, { signal });
49
- const user = (await response.json()) as { name: string };
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
- commit(() => {
52
- setName(user.name);
53
- });
54
- },
55
- [userId]
56
- );
72
+ commit(() => {
73
+ setName(user.name);
74
+ });
75
+ },
76
+ [userId]
77
+ );
57
78
 
58
- return <div>{name}</div>;
79
+ return <div>{name}</div>;
59
80
  }
60
81
  ```
61
82
 
62
- ### Example 2: interval with `useEvent`
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
- const [count, setCount] = useState(0);
70
- const onTick = useEvent(() => setCount((value) => value + 1));
90
+ const [count, setCount] = useState(0);
91
+ const onTick = useEvent(() => setCount((value) => value + 1));
71
92
 
72
- useEffectTask(({ onCleanup }) => {
73
- const handle = setInterval(() => onTick(), 1000);
74
- onCleanup(() => clearInterval(handle));
75
- }, []);
93
+ useEffectTask(({ onCleanup }) => {
94
+ const handle = setInterval(() => onTick(), 1000);
95
+ onCleanup(() => clearInterval(handle));
96
+ }, []);
76
97
 
77
- return <span>{count}</span>;
98
+ return <span>{count}</span>;
78
99
  }
79
100
  ```
80
101
 
81
- ### Example 3: subscription with `onCleanup`
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
- useEffectTask(({ onCleanup }) => {
91
- const sub = subscribeToRoom(roomId, (msg) => {
92
- console.log(msg);
93
- });
111
+ useEffectTask(({ onCleanup }) => {
112
+ const sub = subscribeToRoom(roomId, (msg) => {
113
+ console.log(msg);
114
+ });
94
115
 
95
- onCleanup(() => sub.unsubscribe());
96
- }, [roomId]);
116
+ onCleanup(() => sub.unsubscribe());
117
+ }, [roomId]);
97
118
 
98
- return null;
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
- - This is not a data-fetching cache layer (not React Query/SWR).
113
- - No SSR orchestration/caching features.
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 package checks:
130
+ 1. Run checks:
118
131
 
119
- ```bash
120
- npm run typecheck
121
- npm run test
122
- npm run build
123
- ```
132
+ ```bash
133
+ npm run typecheck
134
+ npm run test
135
+ npm run build
136
+ ```
124
137
 
125
- 2. Create a package tarball:
138
+ 2. Pack the library:
126
139
 
127
- ```bash
128
- npm pack
129
- ```
140
+ ```bash
141
+ npm pack
142
+ # optional sanity check only
143
+ npm run pack:check
144
+ ```
130
145
 
131
- 3. Install locally in a separate app (use either method):
146
+ 3. Install in a separate consumer app:
132
147
 
133
- ```bash
134
- # from repo folder path
135
- npm i /absolute/path/to/UseEffectState
148
+ ```bash
149
+ # from local folder
150
+ npm i /absolute/path/to/effect-runtime
136
151
 
137
- # from generated tarball
138
- npm i /absolute/path/to/UseEffectState/clagradi-effect-runtime-0.1.0.tgz
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 the consumer app:
156
+ 4. Verify in consumer app:
142
157
 
143
- - `import { useEffectTask, useEvent } from '@clagradi/effect-runtime'` resolves.
144
- - TypeScript picks up package types from `dist/index.d.ts` (run `npm run typecheck`).
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 availability:
163
+ 1. Check package name status:
149
164
 
150
- ```bash
151
- npm view @clagradi/effect-runtime
152
- # 404 => available
153
- ```
165
+ ```bash
166
+ npm view @clagradi/effect-runtime
167
+ ```
154
168
 
155
- 2. Login:
169
+ 2. Bump version for the next release:
156
170
 
157
- ```bash
158
- npm login
159
- ```
171
+ ```bash
172
+ npm version patch # or minor / major
173
+ ```
160
174
 
161
- 3. Publish the package:
175
+ 3. Publish manually:
162
176
 
163
- ```bash
164
- npm publish --access public
165
- ```
177
+ ```bash
178
+ npm login
179
+ npm publish --access public
180
+ ```
166
181
 
167
- Scoped packages default to private. Always pass `--access public` for public releases.
182
+ 4. Or publish via CI on tag push:
168
183
 
169
- 4. Versioning:
184
+ ```bash
185
+ git push --follow-tags
186
+ ```
170
187
 
171
- ```bash
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
- 5. Tag/release strategy:
190
+ CI publish workflow runs on tags matching `v*` and publishes with provenance:
178
191
 
179
- - CI publish runs when pushing a tag matching `v*` (for example `v0.1.0`) or when a GitHub Release is published.
180
- - The publish workflow uses `npm publish --provenance --access public` with `NODE_AUTH_TOKEN=${{ secrets.NPM_TOKEN }}`.
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.0",
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": ["dist"],
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"