stunk 1.4.7 → 2.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 CHANGED
@@ -28,507 +28,16 @@ yarn add stunk
28
28
  pnpm install stunk
29
29
  ```
30
30
 
31
- ## Basic Usage
31
+ Read Docs:
32
32
 
33
- A **chunk** is a small container of state. It holds a value, and you can do some stuffs with it:
34
-
35
- ```typescript
36
- import { chunk } from "stunk";
37
-
38
- // Create a simple counter
39
- const counterChunk = chunk(0);
40
-
41
- // Subscribe to changes
42
- counterChunk.subscribe((value) => {
43
- console.log("Counter changed:", value);
44
- });
45
-
46
- // Update the value
47
- counterChunk.set(1);
48
-
49
- // Get current value
50
- const value = counterChunk.get(); // 1
51
-
52
- // Reset to initial value
53
- counterChunk.reset();
54
- ```
55
-
56
- ## Deriving New Chunks
57
-
58
- With **Stunk**, you can create **derived chunks**. This means you can create a new **chunk** based on the value of another **chunk**.
59
- When the original **chunk** changes, the **derived chunk** will automatically update.
60
-
61
- ```typescript
62
- const count = chunk(5);
63
-
64
- // Create a derived chunk that doubles the count
65
- const doubleCount = count.derive((value) => value * 2);
66
-
67
- count.subscribe((newValue) => console.log("Count:", newValue));
68
- doubleCount.subscribe((newValue) => console.log("Double count:", newValue));
69
-
70
- count.set(10);
71
- // Will log:
72
- // "Count: 10"
73
- // "Double count: 20"
74
- ```
75
-
76
- ## State Selection
77
-
78
- Efficiently access and react to specific state parts:
79
-
80
- ```typescript
81
- import { chunk, select } from "stunk";
82
-
83
- const userChunk = chunk({
84
- name: "Olamide",
85
- age: 30,
86
- email: "olamide@example.com",
87
- });
88
-
89
- // Select specific properties -readonly
90
- const nameChunk = select(userChunk, (state) => state.name);
91
- const ageChunk = select(userChunk, (state) => state.age);
92
-
93
- nameChunk.subscribe((name) => console.log("Name changed:", name));
94
- // will only re-render if the selected part change.
95
-
96
- nameChunk.set("Olamide"); // ❌ this will throw an error, because it is a readonly.
97
- ```
98
-
99
- ## Batch Updates
100
-
101
- Batch Update group multiple **state changes** together and notify **subscribers** only once at the end of the `batch`. This is particularly useful for **optimizing performance** when you need to **update multiple** chunks at the same time.
102
-
103
- ```typescript
104
- import { chunk, batch } from "stunk";
105
-
106
- const nameChunk = chunk("Olamide");
107
- const ageChunk = chunk(30);
108
-
109
- batch(() => {
110
- nameChunk.set("AbdulAzeez");
111
- ageChunk.set(31);
112
- }); // Only one notification will be sent to subscribers
113
-
114
- // Nested batches are also supported
115
- batch(() => {
116
- firstName.set("Olanrewaju");
117
- batch(() => {
118
- age.set(29);
119
- });
120
- }); // Only one notification will be sent to subscribers
121
- ```
122
-
123
- ## Computed
124
-
125
- Computed Chunks in Stunk allow you to create state derived from other chunks in a reactive way. Unlike derived chunks, computed chunks can depend on multiple sources, and they automatically recalculate when any of the source chunks change.
126
-
127
- - Multiple Dependencies: Can depend on multiple chunks.
128
- - Memoization: Only recalculates when dependencies change.
129
- - Type-Safe: Fully typed in TypeScript for safe data handling.
130
- - Reactive: Automatically updates subscribers when any dependency changes.
131
-
132
- ```typescript
133
- import { chunk, computed } from "stunk";
134
-
135
- const firstNameChunk = chunk("John");
136
- const lastNameChunk = chunk("Doe");
137
- const ageChunk = chunk(30);
138
- // Create a computed chunk that depends on multiple sources
139
-
140
- const fullInfoChunk = computed(
141
- [firstNameChunk, lastNameChunk, ageChunk],
142
- (firstName, lastName, age) => ({
143
- fullName: `${firstName} ${lastName}`,
144
- isAdult: age >= 18,
145
- })
146
- );
147
-
148
- firstNameChunk.set("Ola");
149
- ageChunk.set(10);
150
-
151
- console.log(fullInfoChunk.get());
152
- // ✅ { fullName: "Ola Doe", isAdult: true }
153
- ```
154
-
155
- `computed` chunks are ideal for scenarios where state depends on multiple sources or needs complex calculations. They ensure your application remains performant and maintainable.
156
-
157
- ## Advanced Examples
158
-
159
- Form Validation Example
160
-
161
- ```typescript
162
- // With derive - single field validation
163
- const emailChunk = chunk("user@example.com");
164
- const isValidEmailChunk = emailChunk.derive((email) =>
165
- /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
166
- );
167
-
168
- // With computed - full form validation
169
- const usernameChunk = chunk("john");
170
- const emailChunk = chunk("user@example.com");
171
- const passwordChunk = chunk("pass123");
172
- const confirmPasswordChunk = chunk("pass123");
173
-
174
- const formValidationChunk = computed(
175
- [usernameChunk, emailChunk, passwordChunk, confirmPasswordChunk],
176
- (username, email, password, confirmPass) => ({
177
- isUsernameValid: username.length >= 3,
178
- isEmailValid: /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
179
- isPasswordValid: password.length >= 6,
180
- doPasswordsMatch: password === confirmPass,
181
- isFormValid:
182
- username.length >= 3 &&
183
- /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email) &&
184
- password.length >= 6 &&
185
- password === confirmPass,
186
- })
187
- );
188
-
189
- console.log(formValidationChunk.get());
190
- ```
191
-
192
- Data Filtering Example
193
-
194
- ```typescript
195
- // With derive - simple filter
196
- const postsChunk = chunk([
197
- { id: 1, title: "Post 1", published: true },
198
- { id: 2, title: "Post 2", published: false },
199
- ]);
200
-
201
- const publishedPostsChunk = postsChunk.derive((posts) =>
202
- posts.filter((post) => post.published)
203
- );
204
-
205
- // With computed - complex filtering with multiple conditions
206
- const postsChunk = chunk([
207
- { id: 1, title: "Post 1", category: "tech", date: "2024-01-01" },
208
- ]);
209
- const categoryFilterChunk = chunk("tech");
210
- const dateRangeChunk = chunk({ start: "2024-01-01", end: "2024-02-01" });
211
- const searchTermChunk = chunk("");
212
-
213
- const filteredPostsChunk = computed(
214
- [postsChunk, categoryFilterChunk, dateRangeChunk, searchTermChunk],
215
- (posts, category, dateRange, searchTerm) =>
216
- posts.filter(
217
- (post) =>
218
- (!category || post.category === category) &&
219
- (!dateRange ||
220
- (post.date >= dateRange.start && post.date <= dateRange.end)) &&
221
- (!searchTerm ||
222
- post.title.toLowerCase().includes(searchTerm.toLowerCase()))
223
- )
224
- );
225
- ```
226
-
227
- ## Middleware
228
-
229
- Middleware allows you to customize how values are set in a **chunk**. For example, you can add **logging**, **validation**, or any custom behavior when a chunk's value changes.
230
-
231
- ```typescript
232
- import { chunk } from "stunk";
233
- import { logger, nonNegativeValidator } from "stunk/middleware";
234
-
235
- // You can also create yours and pass it chunk as the second param
236
-
237
- // Use middleware for logging and validation
238
- const age = chunk(25, [logger, nonNegativeValidator]);
239
-
240
- age.set(30); // Logs: "Setting value: 30"
241
- age.set(-5); // ❌ Throws an error: "Value must be non-negative!"
242
- ```
243
-
244
- ## Time Travel (Middleware)
245
-
246
- The `withHistory` middleware extends a chunk to support undo and redo functionality. This allows you to navigate back and forth between previous states, making it useful for implementing features like undo/redo, form history, and state time travel.
247
-
248
- ```typescript
249
- import { chunk } from "stunk";
250
- import { withHistory } from "stunk/midddleware";
251
-
252
- const counterChunk = withHistory(chunk(0));
253
-
254
- counterChunk.set(1);
255
- counterChunk.set(2);
256
-
257
- counterChunk.undo(); // Goes back to 1
258
- counterChunk.undo(); // Goes back to 0
259
-
260
- counterChunk.redo(); // Goes forward to 1
261
-
262
- counterChunk.canUndo(); // Returns `true` if there is a previous state to revert to..
263
- counterChunk.canRedo(); // Returns `true` if there is a next state to move to.
264
-
265
- counterChunk.getHistory(); // Returns an array of all the values in the history.
266
-
267
- counterChunk.clearHistory(); // Clears the history, keeping only the current value.
268
- ```
269
-
270
- **Example: Limiting History Size (Optional)**
271
- You can specify a max history size to prevent excessive memory usage.
272
-
273
- ```ts
274
- const counter = withHistory(chunk(0), { maxHistory: 5 });
275
- // Only keeps the last 5 changes -- default is 100.
276
- ```
277
-
278
- This prevents the history from growing indefinitely and ensures efficient memory usage.
279
-
280
- ## State Persistence (Middleware)
281
-
282
- Stunk provides a persistence middleware to automatically save state changes to storage (localStorage, sessionStorage, etc).
283
-
284
- ```typescript
285
- import { chunk } from "stunk";
286
- import { withPersistence } from "stunk/middleware";
287
-
288
- const counterChunk = withPersistence(chunk({ count: 0 }), {
289
- key: "counter-state",
290
- });
291
-
292
- // State automatically persists to localStorage
293
- counterChunk.set({ count: 1 });
294
- ```
295
-
296
- Using Different Storage
297
-
298
- ```typescript
299
- // Use sessionStorage instead of localStorage
300
- const sessionStorageChunk = withPersistence(baseChunk, {
301
- key: "counter",
302
- storage: sessionStorage,
303
- });
304
- ```
305
-
306
- Custom Serialization
307
-
308
- ```typescript
309
- // Add custom serialization/deserialization
310
- const encryptedChunk = withPersistence(baseChunk, {
311
- key: "encrypted-data",
312
- serialize: (value) => encrypt(JSON.stringify(value)),
313
- deserialize: (value) => JSON.parse(decrypt(value)),
314
- });
315
- ```
316
-
317
- ## Once
318
-
319
- `Once` utility is a function that ensures a given piece of code or a function is executed only once, no matter how many times it's called. It's typically used to optimize performance by preventing redundant calculations or event handlers from running multiple times.
320
-
321
- How It Works:
322
-
323
- - It wraps a function and tracks whether it has been called.
324
- - On the first call, it executes the function and saves the result.
325
- - On subsequent calls, it simply returns the saved result without executing the function again.
326
-
327
- ```typescript
328
- const numbersChunk = chunk([1, 2, 3, 4, 5]);
329
-
330
- const expensiveCalculation = once(() => {
331
- console.log("Expensive calculation running...");
332
- return numbersChunk.get().reduce((sum, num) => sum + num, 0);
333
- });
334
-
335
- // Derived chunk using the once utility
336
- const totalChunk = numbersChunk.derive(() => expensiveCalculation());
337
-
338
- totalChunk.subscribe((total) => {
339
- console.log("Total:", total);
340
- });
341
-
342
- // Even if numbersChunk updates, the expensive calculation runs only once
343
- numbersChunk.set([10, 20, 30, 40, 50]);
344
- ```
345
-
346
- ## Async State
347
-
348
- Async Chunks in Stunk are designed to manage asynchronous state seamlessly. They handle loading, error, and data states automatically, making it easier to work with APIs and other asynchronous operations.
349
-
350
- Key Features
351
-
352
- - Built-in Loading and Error States: Automatically manages loading, error, and data properties.
353
-
354
- - Type-Safe: Fully typed in TypeScript, ensuring safe data handling.
355
-
356
- - Optimistic Updates: Update state optimistically and revert if needed.
357
-
358
- ```typescript
359
- import { asyncChunk } from "stunk";
360
-
361
- type User = {
362
- id: number;
363
- name: string;
364
- email: string;
365
- };
366
-
367
- // Create an Async Chunk
368
- const user = asyncChunk<User>(async () => {
369
- const response = await fetch("/api/user");
370
- return response.json(); // TypeScript expects this to return User;
371
- });
372
-
373
- // Now userChunk is typed as AsyncChunk<User>, which means:
374
- user.subscribe((state) => {
375
- if (state.data) {
376
- // state.data is typed as User | null
377
- console.log(state.data.name); // TypeScript knows 'name' exists
378
- console.log(state.data.age); // ❌ TypeScript Error: Property 'age' does not exist
379
- }
380
- });
381
-
382
- // Subscribe to state changes
383
- user.subscribe(({ loading, error, data }) => {
384
- if (loading) console.log("Loading...");
385
- if (error) console.log("Error:", error);
386
- if (data) console.log("User:", data);
387
- });
388
- ```
389
-
390
- **Reloading Data**
391
-
392
- ```typescript
393
- // Reload data
394
- await user.reload();
395
- ```
396
-
397
- **Optimistic Updates**
398
-
399
- ```typescript
400
-
401
- // Optimistic update
402
- user.mutate((currentData) => ({
403
- ...currentData,
404
- name: "Fola",
405
- }));
406
-
407
- // The mutate function also enforces the User type
408
- user.mutate(currentUser => ({
409
- id: currentUser?.id ?? 0,
410
- name: "Olamide",
411
- email: "olamide@gmail.com"
412
- age: 70 // ❌ TypeScript Error: Object literal may only specify known properties
413
- }));
414
- ```
415
-
416
- ## Combine Async Chunk
417
-
418
- `combineAsyncChunks` utility is used for managing multiple related async chunks.
419
-
420
- - Maintains reactivity through the entire chain
421
- - Preserves previous data during reloading
422
- - Proper error propagation
423
-
424
- ```typescript
425
- // Basic fetch
426
- const userChunk = asyncChunk(async () => {
427
- const response = await fetch("/api/user");
428
- return response.json();
429
- });
430
-
431
- // With options
432
- const postsChunk = asyncChunk(
433
- async () => {
434
- const response = await fetch("/api/posts");
435
- return response.json();
436
- },
437
- {
438
- initialData: [],
439
- retryCount: 3,
440
- retryDelay: 2000,
441
- onError: (error) => console.error("Failed to fetch posts:", error),
442
- }
443
- );
444
-
445
- // Combining chunks
446
- const profileChunk = combineAsyncChunks({
447
- user: userChunk,
448
- posts: postsChunk,
449
- });
450
-
451
- // Reactive updates
452
- profileChunk.subscribe(({ loading, error, data }) => {
453
- if (loading) {
454
- showLoadingSpinner();
455
- } else if (error) {
456
- showError(error);
457
- } else {
458
- updateUI(data);
459
- }
460
- });
461
- ```
462
-
463
- ## API Reference
464
-
465
- ### Core
466
-
467
- - `chunk<T>(initialValue: T): Chunk<T>`
468
- - `batch(fn: () => void): void`
469
- - `select<T, S>(sourceChunk: Chunk<T>, selector: (state: T) => S): Chunk<S>`
470
- <!-- - `asyncChunk<T>(fetcher: () => Promise<T>, options?): AsyncChunk<T>` -->
471
-
472
- ### History
473
-
474
- - `withHistory<T>(chunk: Chunk<T>, options: { maxHistory?: number }): ChunkWithHistory<T>`
475
-
476
- ### Persistance
477
-
478
- - `withPersistence<T>(baseChunk: Chunk<T>,options: PersistOptions<T>): Chunk<T>`
479
-
480
- ### Types
481
-
482
- ```typescript
483
- interface Chunk<T> {
484
- get(): T;
485
- set(value: T): void;
486
- subscribe(callback: (value: T) => void): () => void;
487
- derive<D>(fn: (value: T) => D): Chunk<D>;
488
- reset(): void;
489
- destroy(): void;
490
- }
491
- ```
492
-
493
- ```typescript
494
- interface AsyncState<T> {
495
- loading: boolean;
496
- error: Error | null;
497
- data: T | null;
498
- }
499
- ```
500
-
501
- ```typescript
502
- interface AsyncChunk<T> extends Chunk<AsyncState<T>> {
503
- reload(): Promise<void>;
504
- mutate(mutator: (currentData: T | null) => T): void;
505
- }
506
- ```
507
-
508
- ```typescript
509
- interface ChunkWithHistory<T> extends Chunk<T> {
510
- undo: () => void;
511
- redo: () => void;
512
- canUndo: () => boolean;
513
- canRedo: () => boolean;
514
- getHistory: () => T[];
515
- clearHistory: () => void;
516
- }
517
- ```
518
-
519
- ```typescript
520
- interface PersistOptions<T> {
521
- key: string; // Storage key
522
- storage?: Storage; // Storage mechanism (default: localStorage)
523
- serialize?: (value: T) => string; // Custom serializer
524
- deserialize?: (value: string) => T; // Custom deserializer
525
- }
526
- ```
33
+ [Stunk](https://stunk.vercel.app/)
527
34
 
528
35
  ## Contributing
529
36
 
530
37
  Contributions are welcome! Please feel free to submit a Pull Request.
531
38
 
39
+ [Pull Request](https://github.com/I-am-abdulazeez/stunk/pulls)
40
+
532
41
  ## License
533
42
 
534
43
  This is licence under MIT
@@ -3,10 +3,8 @@ export type Middleware<T> = (value: T, next: (newValue: T) => void) => void;
3
3
  export interface Chunk<T> {
4
4
  /** Get the current value of the chunk. */
5
5
  get: () => T;
6
- /** Set a new value for the chunk. */
7
- set: (value: T) => void;
8
- /** Update existing value efficiently */
9
- update: (updater: (currentValue: T) => T) => void;
6
+ /** Set a new value for the chunk & Update existing value efficiently. */
7
+ set: (newValueOrUpdater: T | ((currentValue: T) => T)) => void;
10
8
  /** Subscribe to changes in the chunk. Returns an unsubscribe function. */
11
9
  subscribe: (callback: Subscriber<T>) => () => void;
12
10
  /** Create a derived chunk based on this chunk's value. */
package/dist/core/core.js CHANGED
@@ -39,19 +39,17 @@ export function chunk(initialValue, middleware = []) {
39
39
  }
40
40
  };
41
41
  const get = () => value;
42
- const set = (newValue) => {
43
- const processedValue = processMiddleware(newValue, middleware);
44
- if (processedValue !== value) {
45
- value = processedValue;
46
- notifySubscribers();
42
+ const set = (newValueOrUpdater) => {
43
+ let newValue;
44
+ if (typeof newValueOrUpdater === 'function') {
45
+ // Handle updater function
46
+ newValue = newValueOrUpdater(value);
47
47
  }
48
- };
49
- const update = (updater) => {
50
- if (typeof updater !== 'function') {
51
- throw new Error("Updater must be a function");
48
+ else {
49
+ // Handle direct value assignment
50
+ newValue = newValueOrUpdater;
52
51
  }
53
- const newValue = updater(value);
54
- const processedValue = processMiddleware(newValue);
52
+ const processedValue = processMiddleware(newValue, middleware);
55
53
  if (processedValue !== value) {
56
54
  value = processedValue;
57
55
  notifySubscribers();
@@ -92,5 +90,5 @@ export function chunk(initialValue, middleware = []) {
92
90
  });
93
91
  return derivedChunk;
94
92
  };
95
- return { get, set, update, subscribe, derive, reset, destroy };
93
+ return { get, set, subscribe, derive, reset, destroy };
96
94
  }
@@ -5,12 +5,22 @@ export function withHistory(baseChunk, options = {}) {
5
5
  let isHistoryAction = false;
6
6
  const historyChunk = {
7
7
  ...baseChunk,
8
- set: (newValue) => {
8
+ set: (newValueOrUpdater) => {
9
9
  if (isHistoryAction) {
10
- baseChunk.set(newValue);
10
+ baseChunk.set(newValueOrUpdater);
11
11
  return;
12
12
  }
13
- // Remove any future history when setting a new value
13
+ // Process the value or updater function
14
+ let newValue;
15
+ if (typeof newValueOrUpdater === 'function') {
16
+ // Get current value and apply the updater function
17
+ const currentValue = baseChunk.get();
18
+ newValue = newValueOrUpdater(currentValue);
19
+ }
20
+ else {
21
+ // Use directly as the new value
22
+ newValue = newValueOrUpdater;
23
+ }
14
24
  history.splice(currentIndex + 1);
15
25
  history.push(newValue);
16
26
  // Limit history size
@@ -3,4 +3,4 @@ import type { Chunk } from "../../core/core";
3
3
  * A lightweight hook that subscribes to a chunk and returns its current value, along with setters, selector, reset and destroy.
4
4
  * Ensures reactivity and prevents unnecessary re-renders.
5
5
  */
6
- export declare function useChunk<T, S = T>(chunk: Chunk<T>, selector?: (value: T) => S): readonly [S, (value: T) => void, (updater: (currentValue: T) => T) => void, () => void, () => void];
6
+ export declare function useChunk<T, S = T>(chunk: Chunk<T>, selector?: (value: T) => S): readonly [S, (valueOrUpdater: T | ((currentValue: T) => T)) => void, () => void, () => void];
@@ -13,11 +13,8 @@ export function useChunk(chunk, selector) {
13
13
  });
14
14
  return () => unsubscribe();
15
15
  }, [selectedChunk]);
16
- const set = useCallback((value) => {
17
- chunk.set(value);
18
- }, [chunk]);
19
- const update = useCallback((updater) => {
20
- chunk.update(updater);
16
+ const set = useCallback((valueOrUpdater) => {
17
+ chunk.set(valueOrUpdater);
21
18
  }, [chunk]);
22
19
  const reset = useCallback(() => {
23
20
  chunk.reset();
@@ -25,5 +22,5 @@ export function useChunk(chunk, selector) {
25
22
  const destroy = useCallback(() => {
26
23
  chunk.destroy();
27
24
  }, [chunk]);
28
- return [state, set, update, reset, destroy];
25
+ return [state, set, reset, destroy];
29
26
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stunk",
3
- "version": "1.4.7",
3
+ "version": "2.0.0",
4
4
  "description": "Stunk is a lightweight, framework-agnostic state management library for JavaScript and TypeScript. It uses chunk-based state units for efficient updates, reactivity, and performance optimization in React, Vue, Svelte, and Vanilla JS/TS applications.",
5
5
  "repository": {
6
6
  "type": "git",
package/src/core/core.ts CHANGED
@@ -6,10 +6,8 @@ export type Middleware<T> = (value: T, next: (newValue: T) => void) => void;
6
6
  export interface Chunk<T> {
7
7
  /** Get the current value of the chunk. */
8
8
  get: () => T;
9
- /** Set a new value for the chunk. */
10
- set: (value: T) => void;
11
- /** Update existing value efficiently */
12
- update: (updater: (currentValue: T) => T) => void;
9
+ /** Set a new value for the chunk & Update existing value efficiently. */
10
+ set: (newValueOrUpdater: T | ((currentValue: T) => T)) => void;
13
11
  /** Subscribe to changes in the chunk. Returns an unsubscribe function. */
14
12
  subscribe: (callback: Subscriber<T>) => () => void;
15
13
  /** Create a derived chunk based on this chunk's value. */
@@ -64,22 +62,18 @@ export function chunk<T>(initialValue: T, middleware: Middleware<T>[] = []): Chu
64
62
 
65
63
  const get = () => value;
66
64
 
67
- const set = (newValue: T) => {
68
- const processedValue = processMiddleware(newValue, middleware);
65
+ const set = (newValueOrUpdater: T | ((currentValue: T) => T)) => {
66
+ let newValue: T;
69
67
 
70
- if (processedValue !== value) {
71
- value = processedValue as T & {};
72
- notifySubscribers();
73
- }
74
- };
75
-
76
- const update = (updater: (currentValue: T) => T) => {
77
- if (typeof updater !== 'function') {
78
- throw new Error("Updater must be a function");
68
+ if (typeof newValueOrUpdater === 'function') {
69
+ // Handle updater function
70
+ newValue = (newValueOrUpdater as (currentValue: T) => T)(value);
71
+ } else {
72
+ // Handle direct value assignment
73
+ newValue = newValueOrUpdater;
79
74
  }
80
75
 
81
- const newValue = updater(value);
82
- const processedValue = processMiddleware(newValue);
76
+ const processedValue = processMiddleware(newValue, middleware);
83
77
 
84
78
  if (processedValue !== value) {
85
79
  value = processedValue as T & {};
@@ -130,5 +124,5 @@ export function chunk<T>(initialValue: T, middleware: Middleware<T>[] = []): Chu
130
124
  return derivedChunk;
131
125
  };
132
126
 
133
- return { get, set, update, subscribe, derive, reset, destroy };
127
+ return { get, set, subscribe, derive, reset, destroy };
134
128
  }
@@ -39,13 +39,22 @@ export function withHistory<T>(
39
39
  const historyChunk: ChunkWithHistory<T> = {
40
40
  ...baseChunk,
41
41
 
42
- set: (newValue: T) => {
42
+ set: (newValueOrUpdater: T | ((currentValue: T) => T)) => {
43
43
  if (isHistoryAction) {
44
- baseChunk.set(newValue);
44
+ baseChunk.set(newValueOrUpdater);
45
45
  return;
46
46
  }
47
47
 
48
- // Remove any future history when setting a new value
48
+ // Process the value or updater function
49
+ let newValue: T;
50
+ if (typeof newValueOrUpdater === 'function') {
51
+ // Get current value and apply the updater function
52
+ const currentValue = baseChunk.get();
53
+ newValue = (newValueOrUpdater as ((currentValue: T) => T))(currentValue);
54
+ } else {
55
+ // Use directly as the new value
56
+ newValue = newValueOrUpdater;
57
+ }
49
58
  history.splice(currentIndex + 1);
50
59
  history.push(newValue);
51
60
 
@@ -24,17 +24,10 @@ export function useChunk<T, S = T>(
24
24
  return () => unsubscribe();
25
25
  }, [selectedChunk]);
26
26
 
27
- const set = useCallback((value: T) => {
28
- chunk.set(value);
27
+ const set = useCallback((valueOrUpdater: T | ((currentValue: T) => T)) => {
28
+ chunk.set(valueOrUpdater);
29
29
  }, [chunk]);
30
30
 
31
- const update = useCallback(
32
- (updater: (currentValue: T) => T) => {
33
- chunk.update(updater);
34
- },
35
- [chunk]
36
- );
37
-
38
31
  const reset = useCallback(() => {
39
32
  chunk.reset();
40
33
  }, [chunk]);
@@ -43,5 +36,5 @@ export function useChunk<T, S = T>(
43
36
  chunk.destroy();
44
37
  }, [chunk]);
45
38
 
46
- return [state, set, update, reset, destroy] as const;
39
+ return [state, set, reset, destroy] as const;
47
40
  }
@@ -5,6 +5,8 @@ test("Chunk should get and set values correctly", () => {
5
5
  expect(chunky.get()).toBe(0);
6
6
  chunky.set(10);
7
7
  expect(chunky.get()).toBe(10);
8
+ chunky.set((prev) => prev + 1);
9
+ expect(chunky.get()).toBe(11);
8
10
  });
9
11
 
10
12
  test("Chunk should notify subscribers on value change", () => {
@@ -3,7 +3,7 @@ import { chunk } from '../src/core/core';
3
3
  describe('chunk update', () => {
4
4
  it('should update value using updater function', () => {
5
5
  const store = chunk(5);
6
- store.update(value => value + 1);
6
+ store.set(value => value + 1);
7
7
  expect(store.get()).toBe(6);
8
8
  });
9
9
 
@@ -30,18 +30,18 @@ describe('chunk update', () => {
30
30
  subscriber.mockReset();
31
31
 
32
32
  // Update to same value
33
- store.update(value => value);
33
+ store.set(value => value);
34
34
  expect(subscriber).not.toHaveBeenCalled();
35
35
 
36
36
  // Update to new value
37
- store.update(value => value + 1);
37
+ store.set(value => value + 1);
38
38
  expect(subscriber).toHaveBeenCalledWith(6);
39
39
  expect(subscriber).toHaveBeenCalledTimes(1);
40
40
  });
41
41
 
42
42
  it('should handle complex update logic', () => {
43
43
  const store = chunk(5);
44
- store.update(value => {
44
+ store.set(value => {
45
45
  if (value > 3) {
46
46
  return value * 2;
47
47
  }
@@ -58,7 +58,7 @@ describe('chunk update', () => {
58
58
 
59
59
  const store = chunk<User>({ name: 'John', age: 30 });
60
60
 
61
- store.update(user => ({
61
+ store.set(user => ({
62
62
  ...user,
63
63
  age: user.age + 1
64
64
  }));
package/tsconfig.json CHANGED
@@ -8,8 +8,8 @@
8
8
  "declarationDir": "./dist",
9
9
  "baseUrl": "./src",
10
10
  "paths": {
11
- "stunk/middleware": ["dist/middleware"],
12
- "stunk/react": ["dist/use-react"]
11
+ "stunk/middleware": ["middleware"],
12
+ "stunk/react": ["use-react"]
13
13
  },
14
14
  "typeRoots": ["./node_modules/@types", "./types"],
15
15
  "strict": true,