stunk 0.8.0 → 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 CHANGED
@@ -1,92 +1,62 @@
1
1
  # Stunk
2
2
 
3
- A lightweight, framework-agnostic state management library using atomic state principles. Stunk breaks down state into manageable "chunks" for easy updates and subscriptions.
4
-
5
- ## Pronunciation and Meaning
3
+ A lightweight, reactive state management library for TypeScript/JavaScript applications. Stunk combines atomic state management with powerful features like middleware, time travel, and async state handling.
6
4
 
7
5
  - **Pronunciation**: _Stunk_ (A playful blend of "state" and "chunk")
8
- - **Meaning**: "Stunk" represents the combination of state management with chunk-based atomic units. The term captures the essence of atomic state management while using "chunk" to refer to these discrete units of state.
9
-
10
- ## What is Stunk?
11
-
12
- Think of your application's state as a big jar of data. In traditional state management, you keep everything in one big jar, and every time you want to change something, you have to dig through the whole jar.
13
6
 
14
7
  **Stunk** is like dividing your jar into many smaller containers, each holding a single piece of state. These smaller containers are called **chunks**. Each **chunk** can be updated and accessed easily, and any part of your app can subscribe to changes in a chunk so it gets updated automatically.
15
8
 
16
9
  ## Features
17
10
 
18
- <!-- - 🎯 Framework agnostic -->
19
-
20
- - 🔄 Reactive updates with efficient subscription system
21
- - 🎯 Granular state selection
22
- - Built-in undo/redo/getHistory/clearHistory
23
- - 🔄 Batch updates for performance and nested batch updates
24
- - 🛠️ Extensible middleware
25
- - 🔍 Full TypeScript support
11
+ - 🚀 **Lightweight and Fast**: No dependencies, minimal overhead
12
+ - 🔄 **Reactive**: Automatic updates when state changes
13
+ - 📦 **Batch Updates**: Group multiple state updates together
14
+ - 🎯 **Atomic State Management**: Break down state into manageable chunks
15
+ - 🎭 **State Selection**: Select and derive specific parts of the state
16
+ - 🔄 **Async Support**: Handle async state with built-in loading and error states
17
+ - 🔌 **Middleware Support**: Extend functionality with custom middleware
18
+ - ⏱️ **Time Travel**: Undo/redo state changes
19
+ - 🔍 **Type-Safe**: Written in TypeScript with full type inference
26
20
 
27
21
  ## Installation
28
22
 
29
23
  ```bash
30
24
  npm install stunk
25
+ # or
26
+ yarn add stunk
27
+ # or
28
+ pnpm install stunk
31
29
  ```
32
30
 
33
- ## Quick Start
34
-
35
- ```typescript
36
- import { chunk, select, batch } from "stunk";
37
-
38
- // Create a state chunk
39
- const counter = chunk(0);
40
- const userChunk = chunk({ name: "Olamide", age: 26 });
41
-
42
- // Select specific state - Selector
43
- const nameSelector = select(userChunk, (user) => user.name);
44
-
45
- // Subscribe to changes
46
- nameSelector.subscribe((name) => console.log("Name:", name));
47
- counter.subscribe((count) => console.log("Counter", counter));
48
-
49
- // Batch updates
50
- batch(() => {
51
- userChunk.set({ name: "Olalekan", age: 27 }); // Doesn't log yet
52
- counter.set(5); // Doesn't log yet
53
- }); // All logs happen here at once
54
- ```
55
-
56
- ## Core Concepts
57
-
58
- ### Chunks
31
+ ## Basic Usage
59
32
 
60
- Basic unit of state with get/set/subscribe functionality:
33
+ A **chunk** is a small container of state. It holds a value, and you can do some stuffs with it:
61
34
 
62
35
  ```typescript
63
- const counter = chunk(0);
64
- counter.subscribe((value) => console.log(value));
65
- counter.set(5);
66
- ```
67
-
68
- ## Unsubscribing
69
-
70
- You can **unsubscribe** from a **chunk**, which means you stop getting notifications when the **value changes**. You can do this by calling the function that's returned when you **subscribe..**
36
+ import { chunk } from "stunk";
71
37
 
72
- ### Usage
73
-
74
- ```ts
75
- const count = chunk(0);
76
- const callback = (newValue: number) => console.log("Updated value:", newValue);
38
+ // Create a simple counter
39
+ const counterChunk = chunk(0);
77
40
 
78
- const unsubscribe = count.subscribe(callback);
41
+ // Subscribe to changes
42
+ counterChunk.subscribe((value) => {
43
+ console.log("Counter changed:", value);
44
+ });
79
45
 
80
- count.set(10); // Will log: "Updated value: 10"
46
+ // Update the value
47
+ counterChunk.set(1);
81
48
 
82
- unsubscribe(); // Unsubscribe
49
+ // Get current value
50
+ const value = counterChunk.get(); // 1
83
51
 
84
- count.set(20); // Nothing will happen now, because you unsubscribed
52
+ // Reset to initial value
53
+ counterChunk.reset();
85
54
  ```
86
55
 
87
- ### Deriving New Chunks
56
+ ## Deriving New Chunks
88
57
 
89
- With Stunk, you can create **derived chunks**. This means you can create a new **chunk** based on the value of another **chunk**. When the original **chunk** changes, the **derived chunk** will automatically update.
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.
90
60
 
91
61
  ```typescript
92
62
  const count = chunk(5);
@@ -103,34 +73,51 @@ count.set(10);
103
73
  // "Double count: 20"
104
74
  ```
105
75
 
106
- ### Batch Updates
76
+ ## Batch Updates
107
77
 
108
- Group multiple updates:
78
+ 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.
109
79
 
110
80
  ```typescript
81
+ import { chunk, batch } from "stunk";
82
+
83
+ const nameChunk = chunk("Olamide");
84
+ const ageChunk = chunk(30);
85
+
111
86
  batch(() => {
112
- chunk1.set(newValue1);
113
- chunk2.set(newValue2);
114
- }); // Single notification
87
+ nameChunk.set("AbdulAzeez");
88
+ ageChunk.set(31);
89
+ }); // Only one notification will be sent to subscribers
115
90
 
116
91
  // Nested batches are also supported
117
92
  batch(() => {
118
- chunk1.set("Tunde");
93
+ firstName.set("Olanrewaju");
119
94
  batch(() => {
120
- chunk1.set(26);
95
+ age.set(29);
121
96
  });
122
- });
97
+ }); // Only one notification will be sent to subscribers
123
98
  ```
124
99
 
125
- ### Selectors
100
+ ## State Selection
126
101
 
127
102
  Efficiently access and react to specific state parts:
128
103
 
129
104
  ```typescript
130
- // With selector - more specific, read-only
131
- const userChunk = chunk({ name: "Olamide", score: 100 });
132
- const scoreSelector = select(userChunk, (u) => u.score);
133
- // scoreSelector.set(200); // This would throw an error
105
+ import { chunk, select } from "stunk";
106
+
107
+ const userChunk = chunk({
108
+ name: "Olamide",
109
+ age: 30,
110
+ email: "olamide@example.com",
111
+ });
112
+
113
+ // Select specific properties -readonly
114
+ const nameChunk = select(userChunk, (state) => state.name);
115
+ const ageChunk = select(userChunk, (state) => state.age);
116
+
117
+ nameChunk.subscribe((name) => console.log("Name changed:", name));
118
+ // will only re-render if the selected part change.
119
+
120
+ nameChunk.set("Olamide"); // ❌ this will throw an error, because it is a readonly.
134
121
  ```
135
122
 
136
123
  ## Middleware
@@ -138,46 +125,135 @@ const scoreSelector = select(userChunk, (u) => u.score);
138
125
  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.
139
126
 
140
127
  ```typescript
141
- // You can also create yours and pass it []
128
+ import { chunk } from "stunk";
129
+ import { logger, nonNegativeValidator } from "stunk/middleware";
130
+
131
+ // You can also create yours and pass it chunk as the second param
142
132
 
143
133
  // Use middleware for logging and validation
144
134
  const age = chunk(25, [logger, nonNegativeValidator]);
145
135
 
146
136
  age.set(30); // Logs: "Setting value: 30"
147
- age.set(-5); // Throws an error: "Value must be non-negative!"
137
+ age.set(-5); // Throws an error: "Value must be non-negative!"
148
138
  ```
149
139
 
150
- ### History (Undo/Redo) - Time Travel
140
+ ## Time Travel (Middleware)
151
141
 
152
142
  ```typescript
153
- const counter = withHistory(chunk(0));
143
+ import { chunk } from "stunk";
144
+ import { withHistory } from "stunk/midddleware";
145
+
146
+ const counterChunk = withHistory(chunk(0));
147
+
148
+ counterChunk.set(1);
149
+ counterChunk.set(2);
154
150
 
155
- counter.set(10);
156
- counter.set(20);
151
+ counterChunk.undo(); // Goes back to 1
152
+ counterChunk.undo(); // Goes back to 0
157
153
 
158
- console.log(counter.get()); // 20
154
+ counterChunk.redo(); // Goes forward to 1
159
155
 
160
- counter.undo(); // Go back one step
161
- console.log(counter.get()); // 10
156
+ counterChunk.canUndo(); // Returns `true` if there is a previous state to revert to..
157
+ counterChunk.canRedo(); // Returns `true` if there is a next state to move to.
162
158
 
163
- counter.redo(); // Go forward one step
164
- console.log(counter.get()); // 20
159
+ counterChunk.getHistory(); // Returns an array of all the values in the history.
160
+
161
+ counterChunk.clearHistory(); // Clears the history, keeping only the current value.
165
162
  ```
166
163
 
167
164
  **Example: Limiting History Size (Optional)**
168
165
  You can specify a max history size to prevent excessive memory usage.
169
166
 
170
167
  ```ts
171
- const counter = withHistory(chunk(0), { maxHistory: 5 }); // Only keeps the last 5 changes -- default is 100.
168
+ const counter = withHistory(chunk(0), { maxHistory: 5 });
169
+ // Only keeps the last 5 changes -- default is 100.
172
170
  ```
173
171
 
174
172
  This prevents the history from growing indefinitely and ensures efficient memory usage.
175
173
 
174
+ ## State Persistence
175
+
176
+ Stunk provides a persistence middleware to automatically save state changes to storage (localStorage, sessionStorage, etc).
177
+
178
+ ```typescript
179
+ import { chunk } from "stunk";
180
+ import { withPersistence } from "stunk/middleware";
181
+
182
+ const counterChunk = withPersistence(chunk({ count: 0 }), {
183
+ key: "counter-state",
184
+ });
185
+
186
+ // State automatically persists to localStorage
187
+ counterChunk.set({ count: 1 });
188
+ ```
189
+
190
+ ## Async State
191
+
192
+ ```typescript
193
+ import { asyncChunk } from "stunk";
194
+
195
+ type User = {
196
+ id: number;
197
+ name: string;
198
+ email: string;
199
+ };
200
+
201
+ const user = asyncChunk<User>(async () => {
202
+ const response = await fetch("/api/user");
203
+ return response.json(); // TypeScript expects this to return User;
204
+ });
205
+
206
+ // Now userChunk is typed as AsyncChunk<User>, which means:
207
+ user.subscribe((state) => {
208
+ if (state.data) {
209
+ // state.data is typed as User | null
210
+ console.log(state.data.name); // TypeScript knows 'name' exists
211
+ console.log(state.data.age); // ❌ TypeScript Error: Property 'age' does not exist
212
+ }
213
+ });
214
+
215
+ user.subscribe(({ loading, error, data }) => {
216
+ if (loading) console.log("Loading...");
217
+ if (error) console.log("Error:", error);
218
+ if (data) console.log("User:", data);
219
+ });
220
+
221
+ // Reload data
222
+ await user.reload();
223
+
224
+ // Optimistic update
225
+ user.mutate((currentData) => ({
226
+ ...currentData,
227
+ name: "Fola",
228
+ }));
229
+
230
+ // The mutate function also enforces the User type
231
+ user.mutate(currentUser => ({
232
+ id: currentUser?.id ?? 0,
233
+ name: "Olamide",
234
+ email: "olamide@gmail.com"
235
+ age: 70 // ❌ TypeScript Error: Object literal may only specify known properties
236
+ }));
237
+ ```
238
+
176
239
  ## API Reference
177
240
 
178
- ### `chunk<T>(initialValue: T, middleware?: Middleware<T>[])`
241
+ ### Core
242
+
243
+ - `chunk<T>(initialValue: T): Chunk<T>`
244
+ - `batch(fn: () => void): void`
245
+ - `select<T, S>(sourceChunk: Chunk<T>, selector: (state: T) => S): Chunk<S>`
246
+ <!-- - `asyncChunk<T>(fetcher: () => Promise<T>, options?): AsyncChunk<T>` -->
247
+
248
+ ### History
249
+
250
+ - `withHistory<T>(chunk: Chunk<T>, options: { maxHistory?: number }): ChunkWithHistory<T>`
251
+
252
+ ### Persistance
253
+
254
+ - `withPersistence<T>(baseChunk: Chunk<T>,options: PersistOptions<T>): Chunk<T>`
179
255
 
180
- Creates a new state chunk.
256
+ ### Types
181
257
 
182
258
  ```typescript
183
259
  interface Chunk<T> {
@@ -185,62 +261,50 @@ interface Chunk<T> {
185
261
  set(value: T): void;
186
262
  subscribe(callback: (value: T) => void): () => void;
187
263
  derive<D>(fn: (value: T) => D): Chunk<D>;
264
+ reset(): void;
188
265
  destroy(): void;
189
266
  }
190
267
  ```
191
268
 
192
- ### `select<T, S>(sourceChunk: Chunk<T>, selector: (value: T) => S)`
193
-
194
- Creates an optimized selector.
195
-
196
269
  ```typescript
197
- // Returns a read-only chunk that updates only when selected value changes
198
- const selector = select(userChunk, (user) => user.name);
270
+ interface AsyncState<T> {
271
+ loading: boolean;
272
+ error: Error | null;
273
+ data: T | null;
274
+ }
199
275
  ```
200
276
 
201
- ### `batch(callback: () => void)`
202
-
203
- Batches multiple updates.
204
-
205
277
  ```typescript
206
- batch(() => {
207
- // Multiple updates here
208
- });
209
-
210
- batch(() => {
211
- // Multiple updates here
212
- batch(() => {
213
- // Nested upddates here
214
- });
215
- });
278
+ interface AsyncChunk<T> extends Chunk<AsyncState<T>> {
279
+ reload(): Promise<void>;
280
+ mutate(mutator: (currentData: T | null) => T): void;
281
+ }
216
282
  ```
217
283
 
218
- ### `withHistory<T>(chunk: Chunk<T>, options?: { maxHistory?: number })`
219
-
220
- Adds undo/redo capabilities.
221
-
222
284
  ```typescript
223
285
  interface ChunkWithHistory<T> extends Chunk<T> {
224
- undo(): void; // Reverts to the previous state (if available).
225
- redo(): void; // Moves forward to the next state (if available).
226
- canUndo(): boolean; // Returns `true` if there are past states available.
227
- canRedo(): boolean; // Returns `true` if there are future states available.
228
- getHistory(): T[]; // Returns an `array` of all past states.
229
- clearHistory(): void; // Clears all stored history and keeps only the current state.
286
+ undo: () => void;
287
+ redo: () => void;
288
+ canUndo: () => boolean;
289
+ canRedo: () => boolean;
290
+ getHistory: () => T[];
291
+ clearHistory: () => void;
230
292
  }
231
293
  ```
232
294
 
233
- ### `Middleware<T>`
234
-
235
- Custom state processing:
236
-
237
295
  ```typescript
238
- type Middleware<T> = (value: T, next: (newValue: T) => void) => void;
296
+ interface PersistOptions<T> {
297
+ key: string; // Storage key
298
+ storage?: Storage; // Storage mechanism (default: localStorage)
299
+ serialize?: (value: T) => string; // Custom serializer
300
+ deserialize?: (value: string) => T; // Custom deserializer
301
+ }
239
302
  ```
240
303
 
241
- - value: The value that is about to be set to the chunk.
242
- - next(value): A function you must call with the processed (or unaltered) value to continue the chain of middleware and eventually update the chunk's state.
304
+ ## Contributing
305
+
306
+ Contributions are welcome! Please feel free to submit a Pull Request.
243
307
 
244
308
  ## License
245
309
 
246
- MIT
310
+ This is licence under MIT
@@ -1 +1,46 @@
1
- "use strict";
1
+ import { chunk } from "./core";
2
+ export function asyncChunk(fetcher, options = {}) {
3
+ const { initialData = null, onError, retryCount = 0, retryDelay = 1000, } = options;
4
+ const initialState = {
5
+ loading: true,
6
+ error: null,
7
+ data: initialData,
8
+ };
9
+ const baseChunk = chunk(initialState);
10
+ const fetchData = async (retries = retryCount) => {
11
+ baseChunk.set({ ...baseChunk.get(), loading: true, error: null });
12
+ try {
13
+ const data = await fetcher();
14
+ baseChunk.set({ loading: false, error: null, data });
15
+ }
16
+ catch (error) {
17
+ if (retries > 0) {
18
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
19
+ return fetchData(retries - 1);
20
+ }
21
+ const errorObj = error instanceof Error ? error : new Error(String(error));
22
+ baseChunk.set({ loading: false, error: errorObj, data: baseChunk.get().data });
23
+ if (onError) {
24
+ onError(errorObj);
25
+ }
26
+ }
27
+ };
28
+ // Initial fetch
29
+ fetchData();
30
+ const asyncChunkInstance = {
31
+ ...baseChunk,
32
+ reload: async () => {
33
+ await fetchData();
34
+ },
35
+ mutate: (mutator) => {
36
+ const currentState = baseChunk.get();
37
+ const newData = mutator(currentState.data);
38
+ baseChunk.set({ ...currentState, data: newData });
39
+ },
40
+ reset: () => {
41
+ baseChunk.set(initialState);
42
+ fetchData();
43
+ },
44
+ };
45
+ return asyncChunkInstance;
46
+ }
@@ -0,0 +1,63 @@
1
+ import { isChunk } from "../utils";
2
+ import { chunk } from "./core";
3
+ export function computed(computeFn) {
4
+ // Track the currently executing computed function
5
+ let currentComputation = null;
6
+ // Set to track dependencies
7
+ const dependencies = new Set();
8
+ const trackingProxy = new Proxy({}, {
9
+ get(_, prop) {
10
+ if (currentComputation && prop === 'value') {
11
+ const chunkValue = this[prop];
12
+ if (isChunk(chunkValue)) {
13
+ dependencies.add(chunkValue);
14
+ return chunkValue.get();
15
+ }
16
+ }
17
+ return this[prop];
18
+ },
19
+ });
20
+ // Initial computation
21
+ let cachedValue;
22
+ let isDirty = true;
23
+ const computeValue = () => {
24
+ if (!isDirty)
25
+ return cachedValue;
26
+ // Reset dependencies
27
+ dependencies.clear();
28
+ // Set the current computation context
29
+ currentComputation = computeFn;
30
+ try {
31
+ // Compute with tracking
32
+ cachedValue = computeFn.call(trackingProxy);
33
+ isDirty = false;
34
+ }
35
+ finally {
36
+ // Clear the current computation context
37
+ currentComputation = null;
38
+ }
39
+ return cachedValue;
40
+ };
41
+ // Create the computed chunk
42
+ const computedChunk = chunk(computeValue());
43
+ // Subscribe to all detected dependencies
44
+ dependencies.forEach(dep => {
45
+ dep.subscribe(() => {
46
+ isDirty = true;
47
+ computedChunk.set(computeValue());
48
+ });
49
+ });
50
+ return {
51
+ ...computedChunk,
52
+ get: () => {
53
+ if (isDirty) {
54
+ return computeValue();
55
+ }
56
+ return cachedValue;
57
+ },
58
+ // Prevent direct setting
59
+ set: () => {
60
+ throw new Error('Cannot directly set a computed value');
61
+ }
62
+ };
63
+ }
package/dist/core/core.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { processMiddleware } from "../utils";
1
2
  let batchDepth = 0;
2
3
  const batchQueue = new Set();
3
4
  export function batch(callback) {
@@ -43,7 +44,6 @@ export function chunk(initialValue, middleware = []) {
43
44
  let value = initialValue;
44
45
  const subscribers = new Set();
45
46
  let isDirty = false;
46
- const get = () => value;
47
47
  const notifySubscribers = () => {
48
48
  if (batchDepth > 0) {
49
49
  if (!isDirty) {
@@ -60,30 +60,22 @@ export function chunk(initialValue, middleware = []) {
60
60
  subscribers.forEach((subscriber) => subscriber(value));
61
61
  }
62
62
  };
63
+ const get = () => value;
63
64
  const set = (newValue) => {
64
- if (newValue === null || newValue === undefined) {
65
- throw new Error("Value cannot be null or undefined.");
65
+ const processedValue = processMiddleware(newValue, middleware);
66
+ if (processedValue !== value) {
67
+ value = processedValue;
68
+ notifySubscribers();
66
69
  }
67
- let currentValue = newValue;
68
- let index = 0;
69
- while (index < middleware.length) {
70
- const currentMiddleware = middleware[index];
71
- let nextCalled = false;
72
- let nextValue = null;
73
- currentMiddleware(currentValue, (val) => {
74
- nextCalled = true;
75
- nextValue = val;
76
- });
77
- if (!nextCalled)
78
- break;
79
- if (nextValue === null || nextValue === undefined) {
80
- throw new Error("Value cannot be null or undefined.");
81
- }
82
- currentValue = nextValue;
83
- index++;
70
+ };
71
+ const update = (updater) => {
72
+ if (typeof updater !== 'function') {
73
+ throw new Error("Updater must be a function");
84
74
  }
85
- if (currentValue !== value) {
86
- value = currentValue;
75
+ const newValue = updater(value);
76
+ const processedValue = processMiddleware(newValue);
77
+ if (processedValue !== value) {
78
+ value = processedValue;
87
79
  notifySubscribers();
88
80
  }
89
81
  };
@@ -96,9 +88,7 @@ export function chunk(initialValue, middleware = []) {
96
88
  }
97
89
  subscribers.add(callback);
98
90
  callback(value);
99
- return () => {
100
- subscribers.delete(callback);
101
- };
91
+ return () => subscribers.delete(callback);
102
92
  };
103
93
  const reset = () => {
104
94
  value = initialValue;
@@ -124,5 +114,5 @@ export function chunk(initialValue, middleware = []) {
124
114
  });
125
115
  return derivedChunk;
126
116
  };
127
- return { get, set, subscribe, derive, reset, destroy };
117
+ return { get, set, update, subscribe, derive, reset, destroy };
128
118
  }
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
- export { chunk } from './core/core';
1
+ export { chunk, batch, select } from './core/core';
2
+ export { asyncChunk } from './core/asyncChunk';
2
3
  export * from "./middleware";
@@ -1,3 +1,6 @@
1
+ // Middleware passed to chunks
1
2
  export { logger } from "./logger";
2
3
  export { nonNegativeValidator } from "./validator";
4
+ // Middleware used with chunks
3
5
  export { withHistory } from "./history";
6
+ export { withPersistence } from './persistence';
@@ -0,0 +1,25 @@
1
+ export function withPersistence(baseChunk, options) {
2
+ const { key, storage = localStorage, serialize = JSON.stringify, deserialize = JSON.parse, } = options;
3
+ // Try to load initial state from storage
4
+ try {
5
+ const savedChunk = storage.getItem(key);
6
+ if (savedChunk) {
7
+ const parsed = deserialize(savedChunk);
8
+ baseChunk.set(parsed);
9
+ }
10
+ }
11
+ catch (error) {
12
+ console.error('Failed to load persisted state:', error);
13
+ }
14
+ // Save to storage
15
+ baseChunk.subscribe((newValue) => {
16
+ try {
17
+ const serialized = serialize(newValue);
18
+ storage.setItem(key, serialized);
19
+ }
20
+ catch (error) {
21
+ console.log('Failed to persist chunk', error);
22
+ }
23
+ });
24
+ return baseChunk;
25
+ }