@nerdalytics/beacon 1000.2.0 → 1000.2.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/dist/src/index.d.ts +0 -24
- package/dist/src/index.js +0 -143
- package/dist/src/index.min.js +1 -0
- package/package.json +11 -3
package/dist/src/index.d.ts
CHANGED
|
@@ -8,36 +8,12 @@ declare const STATE_ID: unique symbol;
|
|
|
8
8
|
export type State<T> = ReadOnlyState<T> & WriteableState<T> & {
|
|
9
9
|
[STATE_ID]?: symbol;
|
|
10
10
|
};
|
|
11
|
-
/**
|
|
12
|
-
* Creates a reactive state container with the provided initial value.
|
|
13
|
-
*/
|
|
14
11
|
export declare const state: <T>(initialValue: T, equalityFn?: (a: T, b: T) => boolean) => State<T>;
|
|
15
|
-
/**
|
|
16
|
-
* Registers a function to run whenever its reactive dependencies change.
|
|
17
|
-
*/
|
|
18
12
|
export declare const effect: (fn: () => void) => Unsubscribe;
|
|
19
|
-
/**
|
|
20
|
-
* Groups multiple state updates to trigger effects only once at the end.
|
|
21
|
-
*/
|
|
22
13
|
export declare const batch: <T>(fn: () => T) => T;
|
|
23
|
-
/**
|
|
24
|
-
* Creates a read-only computed value that updates when its dependencies change.
|
|
25
|
-
*/
|
|
26
14
|
export declare const derive: <T>(computeFn: () => T) => ReadOnlyState<T>;
|
|
27
|
-
/**
|
|
28
|
-
* Creates an efficient subscription to a subset of a state value.
|
|
29
|
-
*/
|
|
30
15
|
export declare const select: <T, R>(source: ReadOnlyState<T>, selectorFn: (state: T) => R, equalityFn?: (a: R, b: R) => boolean) => ReadOnlyState<R>;
|
|
31
|
-
/**
|
|
32
|
-
* Creates a read-only view of a state, hiding mutation methods.
|
|
33
|
-
*/
|
|
34
16
|
export declare const readonlyState: <T>(state: State<T>) => ReadOnlyState<T>;
|
|
35
|
-
/**
|
|
36
|
-
* Creates a state with access control, returning a tuple of reader and writer.
|
|
37
|
-
*/
|
|
38
17
|
export declare const protectedState: <T>(initialValue: T, equalityFn?: (a: T, b: T) => boolean) => [ReadOnlyState<T>, WriteableState<T>];
|
|
39
|
-
/**
|
|
40
|
-
* Creates a lens for direct updates to nested properties of a state.
|
|
41
|
-
*/
|
|
42
18
|
export declare const lens: <T, K>(source: State<T>, accessor: (state: T) => K) => State<K>;
|
|
43
19
|
export {};
|
package/dist/src/index.js
CHANGED
|
@@ -1,32 +1,10 @@
|
|
|
1
|
-
// Special symbol used for internal tracking
|
|
2
1
|
const STATE_ID = Symbol();
|
|
3
|
-
/**
|
|
4
|
-
* Creates a reactive state container with the provided initial value.
|
|
5
|
-
*/
|
|
6
2
|
export const state = (initialValue, equalityFn = Object.is) => StateImpl.createState(initialValue, equalityFn);
|
|
7
|
-
/**
|
|
8
|
-
* Registers a function to run whenever its reactive dependencies change.
|
|
9
|
-
*/
|
|
10
3
|
export const effect = (fn) => StateImpl.createEffect(fn);
|
|
11
|
-
/**
|
|
12
|
-
* Groups multiple state updates to trigger effects only once at the end.
|
|
13
|
-
*/
|
|
14
4
|
export const batch = (fn) => StateImpl.executeBatch(fn);
|
|
15
|
-
/**
|
|
16
|
-
* Creates a read-only computed value that updates when its dependencies change.
|
|
17
|
-
*/
|
|
18
5
|
export const derive = (computeFn) => StateImpl.createDerive(computeFn);
|
|
19
|
-
/**
|
|
20
|
-
* Creates an efficient subscription to a subset of a state value.
|
|
21
|
-
*/
|
|
22
6
|
export const select = (source, selectorFn, equalityFn = Object.is) => StateImpl.createSelect(source, selectorFn, equalityFn);
|
|
23
|
-
/**
|
|
24
|
-
* Creates a read-only view of a state, hiding mutation methods.
|
|
25
|
-
*/
|
|
26
7
|
export const readonlyState = (state) => () => state();
|
|
27
|
-
/**
|
|
28
|
-
* Creates a state with access control, returning a tuple of reader and writer.
|
|
29
|
-
*/
|
|
30
8
|
export const protectedState = (initialValue, equalityFn = Object.is) => {
|
|
31
9
|
const fullState = state(initialValue, equalityFn);
|
|
32
10
|
return [
|
|
@@ -37,26 +15,18 @@ export const protectedState = (initialValue, equalityFn = Object.is) => {
|
|
|
37
15
|
},
|
|
38
16
|
];
|
|
39
17
|
};
|
|
40
|
-
/**
|
|
41
|
-
* Creates a lens for direct updates to nested properties of a state.
|
|
42
|
-
*/
|
|
43
18
|
export const lens = (source, accessor) => StateImpl.createLens(source, accessor);
|
|
44
19
|
class StateImpl {
|
|
45
|
-
// Static fields track global reactivity state - this centralized approach allows
|
|
46
|
-
// for coordinated updates while maintaining individual state isolation
|
|
47
20
|
static currentSubscriber = null;
|
|
48
21
|
static pendingSubscribers = new Set();
|
|
49
22
|
static isNotifying = false;
|
|
50
23
|
static batchDepth = 0;
|
|
51
24
|
static deferredEffectCreations = [];
|
|
52
25
|
static activeSubscribers = new Set();
|
|
53
|
-
// WeakMaps enable automatic garbage collection when subscribers are no
|
|
54
|
-
// longer referenced, preventing memory leaks in long-running applications
|
|
55
26
|
static stateTracking = new WeakMap();
|
|
56
27
|
static subscriberDependencies = new WeakMap();
|
|
57
28
|
static parentSubscriber = new WeakMap();
|
|
58
29
|
static childSubscribers = new WeakMap();
|
|
59
|
-
// Instance state - each state has unique subscribers and ID
|
|
60
30
|
value;
|
|
61
31
|
subscribers = new Set();
|
|
62
32
|
stateId = Symbol();
|
|
@@ -65,10 +35,6 @@ class StateImpl {
|
|
|
65
35
|
this.value = initialValue;
|
|
66
36
|
this.equalityFn = equalityFn;
|
|
67
37
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Creates a reactive state container with the provided initial value.
|
|
70
|
-
* Implementation of the public 'state' function.
|
|
71
|
-
*/
|
|
72
38
|
static createState = (initialValue, equalityFn = Object.is) => {
|
|
73
39
|
const instance = new StateImpl(initialValue, equalityFn);
|
|
74
40
|
const get = () => instance.get();
|
|
@@ -77,23 +43,16 @@ class StateImpl {
|
|
|
77
43
|
get[STATE_ID] = instance.stateId;
|
|
78
44
|
return get;
|
|
79
45
|
};
|
|
80
|
-
// Auto-tracks dependencies when called within effects, creating a fine-grained
|
|
81
|
-
// reactivity graph that only updates affected components
|
|
82
46
|
get = () => {
|
|
83
47
|
const currentEffect = StateImpl.currentSubscriber;
|
|
84
48
|
if (currentEffect) {
|
|
85
|
-
// Add this effect to subscribers for future notification
|
|
86
49
|
this.subscribers.add(currentEffect);
|
|
87
|
-
// Maintain bidirectional dependency tracking to enable precise cleanup
|
|
88
|
-
// when effects are unsubscribed, preventing memory leaks
|
|
89
50
|
let dependencies = StateImpl.subscriberDependencies.get(currentEffect);
|
|
90
51
|
if (!dependencies) {
|
|
91
52
|
dependencies = new Set();
|
|
92
53
|
StateImpl.subscriberDependencies.set(currentEffect, dependencies);
|
|
93
54
|
}
|
|
94
55
|
dependencies.add(this.subscribers);
|
|
95
|
-
// Track read states to detect direct cyclical dependencies that
|
|
96
|
-
// could cause infinite loops
|
|
97
56
|
let readStates = StateImpl.stateTracking.get(currentEffect);
|
|
98
57
|
if (!readStates) {
|
|
99
58
|
readStates = new Set();
|
|
@@ -103,14 +62,10 @@ class StateImpl {
|
|
|
103
62
|
}
|
|
104
63
|
return this.value;
|
|
105
64
|
};
|
|
106
|
-
// Handles value updates with built-in optimizations and safeguards
|
|
107
65
|
set = (newValue) => {
|
|
108
|
-
// Skip updates for unchanged values to prevent redundant effect executions
|
|
109
66
|
if (this.equalityFn(this.value, newValue)) {
|
|
110
67
|
return;
|
|
111
68
|
}
|
|
112
|
-
// Infinite loop detection prevents direct self-mutation within effects,
|
|
113
|
-
// while allowing nested effect patterns that would otherwise appear cyclical
|
|
114
69
|
const effect = StateImpl.currentSubscriber;
|
|
115
70
|
if (effect) {
|
|
116
71
|
const states = StateImpl.stateTracking.get(effect);
|
|
@@ -119,16 +74,12 @@ class StateImpl {
|
|
|
119
74
|
}
|
|
120
75
|
}
|
|
121
76
|
this.value = newValue;
|
|
122
|
-
// Skip updates when there are no subscribers, avoiding unnecessary processing
|
|
123
77
|
if (this.subscribers.size === 0) {
|
|
124
78
|
return;
|
|
125
79
|
}
|
|
126
|
-
// Queue notifications instead of executing immediately to support batch operations
|
|
127
|
-
// and prevent redundant effect runs
|
|
128
80
|
for (const sub of this.subscribers) {
|
|
129
81
|
StateImpl.pendingSubscribers.add(sub);
|
|
130
82
|
}
|
|
131
|
-
// Immediate execution outside of batches, deferred execution inside batches
|
|
132
83
|
if (StateImpl.batchDepth === 0 && !StateImpl.isNotifying) {
|
|
133
84
|
StateImpl.notifySubscribers();
|
|
134
85
|
}
|
|
@@ -136,27 +87,17 @@ class StateImpl {
|
|
|
136
87
|
update = (fn) => {
|
|
137
88
|
this.set(fn(this.value));
|
|
138
89
|
};
|
|
139
|
-
/**
|
|
140
|
-
* Registers a function to run whenever its reactive dependencies change.
|
|
141
|
-
* Implementation of the public 'effect' function.
|
|
142
|
-
*/
|
|
143
90
|
static createEffect = (fn) => {
|
|
144
91
|
const runEffect = () => {
|
|
145
|
-
// Prevent re-entrance to avoid cascade updates during effect execution
|
|
146
92
|
if (StateImpl.activeSubscribers.has(runEffect)) {
|
|
147
93
|
return;
|
|
148
94
|
}
|
|
149
95
|
StateImpl.activeSubscribers.add(runEffect);
|
|
150
96
|
const parentEffect = StateImpl.currentSubscriber;
|
|
151
97
|
try {
|
|
152
|
-
// Clean existing subscriptions before running to ensure only
|
|
153
|
-
// currently accessed states are tracked as dependencies
|
|
154
98
|
StateImpl.cleanupEffect(runEffect);
|
|
155
|
-
// Set current context for automatic dependency tracking
|
|
156
99
|
StateImpl.currentSubscriber = runEffect;
|
|
157
100
|
StateImpl.stateTracking.set(runEffect, new Set());
|
|
158
|
-
// Track parent-child relationships to handle nested effects correctly
|
|
159
|
-
// and enable hierarchical cleanup later
|
|
160
101
|
if (parentEffect) {
|
|
161
102
|
StateImpl.parentSubscriber.set(runEffect, parentEffect);
|
|
162
103
|
let children = StateImpl.childSubscribers.get(parentEffect);
|
|
@@ -166,22 +107,17 @@ class StateImpl {
|
|
|
166
107
|
}
|
|
167
108
|
children.add(runEffect);
|
|
168
109
|
}
|
|
169
|
-
// Execute the effect function, which will auto-track dependencies
|
|
170
110
|
fn();
|
|
171
111
|
}
|
|
172
112
|
finally {
|
|
173
|
-
// Restore previous context when done
|
|
174
113
|
StateImpl.currentSubscriber = parentEffect;
|
|
175
114
|
StateImpl.activeSubscribers.delete(runEffect);
|
|
176
115
|
}
|
|
177
116
|
};
|
|
178
|
-
// Run immediately unless we're in a batch operation
|
|
179
117
|
if (StateImpl.batchDepth === 0) {
|
|
180
118
|
runEffect();
|
|
181
119
|
}
|
|
182
120
|
else {
|
|
183
|
-
// Still track parent-child relationship even when deferred,
|
|
184
|
-
// ensuring proper hierarchical cleanup later
|
|
185
121
|
if (StateImpl.currentSubscriber) {
|
|
186
122
|
const parent = StateImpl.currentSubscriber;
|
|
187
123
|
StateImpl.parentSubscriber.set(runEffect, parent);
|
|
@@ -192,17 +128,13 @@ class StateImpl {
|
|
|
192
128
|
}
|
|
193
129
|
children.add(runEffect);
|
|
194
130
|
}
|
|
195
|
-
// Queue for execution when batch completes
|
|
196
131
|
StateImpl.deferredEffectCreations.push(runEffect);
|
|
197
132
|
}
|
|
198
|
-
// Return cleanup function to properly disconnect from reactivity graph
|
|
199
133
|
return () => {
|
|
200
|
-
// Remove from dependency tracking to stop future notifications
|
|
201
134
|
StateImpl.cleanupEffect(runEffect);
|
|
202
135
|
StateImpl.pendingSubscribers.delete(runEffect);
|
|
203
136
|
StateImpl.activeSubscribers.delete(runEffect);
|
|
204
137
|
StateImpl.stateTracking.delete(runEffect);
|
|
205
|
-
// Clean up parent-child relationship bidirectionally
|
|
206
138
|
const parent = StateImpl.parentSubscriber.get(runEffect);
|
|
207
139
|
if (parent) {
|
|
208
140
|
const siblings = StateImpl.childSubscribers.get(parent);
|
|
@@ -211,8 +143,6 @@ class StateImpl {
|
|
|
211
143
|
}
|
|
212
144
|
}
|
|
213
145
|
StateImpl.parentSubscriber.delete(runEffect);
|
|
214
|
-
// Recursively clean up child effects to prevent memory leaks in
|
|
215
|
-
// nested effect scenarios
|
|
216
146
|
const children = StateImpl.childSubscribers.get(runEffect);
|
|
217
147
|
if (children) {
|
|
218
148
|
for (const child of children) {
|
|
@@ -223,19 +153,12 @@ class StateImpl {
|
|
|
223
153
|
}
|
|
224
154
|
};
|
|
225
155
|
};
|
|
226
|
-
/**
|
|
227
|
-
* Groups multiple state updates to trigger effects only once at the end.
|
|
228
|
-
* Implementation of the public 'batch' function.
|
|
229
|
-
*/
|
|
230
156
|
static executeBatch = (fn) => {
|
|
231
|
-
// Increment depth counter to handle nested batches correctly
|
|
232
157
|
StateImpl.batchDepth++;
|
|
233
158
|
try {
|
|
234
159
|
return fn();
|
|
235
160
|
}
|
|
236
161
|
catch (error) {
|
|
237
|
-
// Clean up on error to prevent stale subscribers from executing
|
|
238
|
-
// and potentially causing cascading errors
|
|
239
162
|
if (StateImpl.batchDepth === 1) {
|
|
240
163
|
StateImpl.pendingSubscribers.clear();
|
|
241
164
|
StateImpl.deferredEffectCreations.length = 0;
|
|
@@ -244,10 +167,7 @@ class StateImpl {
|
|
|
244
167
|
}
|
|
245
168
|
finally {
|
|
246
169
|
StateImpl.batchDepth--;
|
|
247
|
-
// Only process effects when exiting the outermost batch,
|
|
248
|
-
// maintaining proper execution order while avoiding redundant runs
|
|
249
170
|
if (StateImpl.batchDepth === 0) {
|
|
250
|
-
// Process effects created during the batch
|
|
251
171
|
if (StateImpl.deferredEffectCreations.length > 0) {
|
|
252
172
|
const effectsToRun = [...StateImpl.deferredEffectCreations];
|
|
253
173
|
StateImpl.deferredEffectCreations.length = 0;
|
|
@@ -255,34 +175,24 @@ class StateImpl {
|
|
|
255
175
|
effect();
|
|
256
176
|
}
|
|
257
177
|
}
|
|
258
|
-
// Process state updates that occurred during the batch
|
|
259
178
|
if (StateImpl.pendingSubscribers.size > 0 && !StateImpl.isNotifying) {
|
|
260
179
|
StateImpl.notifySubscribers();
|
|
261
180
|
}
|
|
262
181
|
}
|
|
263
182
|
}
|
|
264
183
|
};
|
|
265
|
-
/**
|
|
266
|
-
* Creates a read-only computed value that updates when its dependencies change.
|
|
267
|
-
* Implementation of the public 'derive' function.
|
|
268
|
-
*/
|
|
269
184
|
static createDerive = (computeFn) => {
|
|
270
185
|
const valueState = StateImpl.createState(undefined);
|
|
271
186
|
let initialized = false;
|
|
272
187
|
let cachedValue;
|
|
273
|
-
// Internal effect automatically tracks dependencies and updates the derived value
|
|
274
188
|
StateImpl.createEffect(() => {
|
|
275
189
|
const newValue = computeFn();
|
|
276
|
-
// Only update if the value actually changed to preserve referential equality
|
|
277
|
-
// and prevent unnecessary downstream updates
|
|
278
190
|
if (!(initialized && Object.is(cachedValue, newValue))) {
|
|
279
191
|
cachedValue = newValue;
|
|
280
192
|
valueState.set(newValue);
|
|
281
193
|
}
|
|
282
194
|
initialized = true;
|
|
283
195
|
});
|
|
284
|
-
// Return function with lazy initialization - ensures value is available
|
|
285
|
-
// even when accessed before its dependencies have had a chance to update
|
|
286
196
|
return () => {
|
|
287
197
|
if (!initialized) {
|
|
288
198
|
cachedValue = computeFn();
|
|
@@ -292,35 +202,25 @@ class StateImpl {
|
|
|
292
202
|
return valueState();
|
|
293
203
|
};
|
|
294
204
|
};
|
|
295
|
-
/**
|
|
296
|
-
* Creates an efficient subscription to a subset of a state value.
|
|
297
|
-
* Implementation of the public 'select' function.
|
|
298
|
-
*/
|
|
299
205
|
static createSelect = (source, selectorFn, equalityFn = Object.is) => {
|
|
300
206
|
let lastSourceValue;
|
|
301
207
|
let lastSelectedValue;
|
|
302
208
|
let initialized = false;
|
|
303
209
|
const valueState = StateImpl.createState(undefined);
|
|
304
|
-
// Internal effect to track the source and update only when needed
|
|
305
210
|
StateImpl.createEffect(() => {
|
|
306
211
|
const sourceValue = source();
|
|
307
|
-
// Skip computation if source reference hasn't changed
|
|
308
212
|
if (initialized && Object.is(lastSourceValue, sourceValue)) {
|
|
309
213
|
return;
|
|
310
214
|
}
|
|
311
215
|
lastSourceValue = sourceValue;
|
|
312
216
|
const newSelectedValue = selectorFn(sourceValue);
|
|
313
|
-
// Use custom equality function to determine if value semantically changed,
|
|
314
|
-
// allowing for deep equality comparisons with complex objects
|
|
315
217
|
if (initialized && lastSelectedValue !== undefined && equalityFn(lastSelectedValue, newSelectedValue)) {
|
|
316
218
|
return;
|
|
317
219
|
}
|
|
318
|
-
// Update cache and notify subscribers due the value has changed
|
|
319
220
|
lastSelectedValue = newSelectedValue;
|
|
320
221
|
valueState.set(newSelectedValue);
|
|
321
222
|
initialized = true;
|
|
322
223
|
});
|
|
323
|
-
// Return function with eager initialization capability
|
|
324
224
|
return () => {
|
|
325
225
|
if (!initialized) {
|
|
326
226
|
lastSourceValue = source();
|
|
@@ -331,12 +231,7 @@ class StateImpl {
|
|
|
331
231
|
return valueState();
|
|
332
232
|
};
|
|
333
233
|
};
|
|
334
|
-
/**
|
|
335
|
-
* Creates a lens for direct updates to nested properties of a state.
|
|
336
|
-
* Implementation of the public 'lens' function.
|
|
337
|
-
*/
|
|
338
234
|
static createLens = (source, accessor) => {
|
|
339
|
-
// Extract the property path once during lens creation
|
|
340
235
|
const extractPath = () => {
|
|
341
236
|
const path = [];
|
|
342
237
|
const proxy = new Proxy({}, {
|
|
@@ -351,17 +246,12 @@ class StateImpl {
|
|
|
351
246
|
accessor(proxy);
|
|
352
247
|
}
|
|
353
248
|
catch {
|
|
354
|
-
// Ignore errors, we're just collecting the path
|
|
355
249
|
}
|
|
356
250
|
return path;
|
|
357
251
|
};
|
|
358
|
-
// Capture the path once
|
|
359
252
|
const path = extractPath();
|
|
360
|
-
// Create a state with the initial value from the source
|
|
361
253
|
const lensState = StateImpl.createState(accessor(source()));
|
|
362
|
-
// Prevent circular updates
|
|
363
254
|
let isUpdating = false;
|
|
364
|
-
// Set up an effect to sync from source to lens
|
|
365
255
|
StateImpl.createEffect(() => {
|
|
366
256
|
if (isUpdating) {
|
|
367
257
|
return;
|
|
@@ -374,7 +264,6 @@ class StateImpl {
|
|
|
374
264
|
isUpdating = false;
|
|
375
265
|
}
|
|
376
266
|
});
|
|
377
|
-
// Override the lens state's set method to update the source
|
|
378
267
|
const originalSet = lensState.set;
|
|
379
268
|
lensState.set = (value) => {
|
|
380
269
|
if (isUpdating) {
|
|
@@ -382,35 +271,25 @@ class StateImpl {
|
|
|
382
271
|
}
|
|
383
272
|
isUpdating = true;
|
|
384
273
|
try {
|
|
385
|
-
// Update lens state
|
|
386
274
|
originalSet(value);
|
|
387
|
-
// Update source by modifying the value at path
|
|
388
275
|
source.update((current) => setValueAtPath(current, path, value));
|
|
389
276
|
}
|
|
390
277
|
finally {
|
|
391
278
|
isUpdating = false;
|
|
392
279
|
}
|
|
393
280
|
};
|
|
394
|
-
// Add update method for completeness
|
|
395
281
|
lensState.update = (fn) => {
|
|
396
282
|
lensState.set(fn(lensState()));
|
|
397
283
|
};
|
|
398
284
|
return lensState;
|
|
399
285
|
};
|
|
400
|
-
// Processes queued subscriber notifications in a controlled, non-reentrant way
|
|
401
286
|
static notifySubscribers = () => {
|
|
402
|
-
// Prevent reentrance to avoid cascading notification loops when
|
|
403
|
-
// effects trigger further state changes
|
|
404
287
|
if (StateImpl.isNotifying) {
|
|
405
288
|
return;
|
|
406
289
|
}
|
|
407
290
|
StateImpl.isNotifying = true;
|
|
408
291
|
try {
|
|
409
|
-
// Process all pending effects in batches for better perf,
|
|
410
|
-
// ensuring topological execution order is maintained
|
|
411
292
|
while (StateImpl.pendingSubscribers.size > 0) {
|
|
412
|
-
// Process in snapshot batches to prevent infinite loops
|
|
413
|
-
// when effects trigger further state changes
|
|
414
293
|
const subscribers = Array.from(StateImpl.pendingSubscribers);
|
|
415
294
|
StateImpl.pendingSubscribers.clear();
|
|
416
295
|
for (const effect of subscribers) {
|
|
@@ -422,11 +301,8 @@ class StateImpl {
|
|
|
422
301
|
StateImpl.isNotifying = false;
|
|
423
302
|
}
|
|
424
303
|
};
|
|
425
|
-
// Removes effect from dependency tracking to prevent memory leaks
|
|
426
304
|
static cleanupEffect = (effect) => {
|
|
427
|
-
// Remove from execution queue to prevent stale updates
|
|
428
305
|
StateImpl.pendingSubscribers.delete(effect);
|
|
429
|
-
// Remove bidirectional dependency references to prevent memory leaks
|
|
430
306
|
const deps = StateImpl.subscriberDependencies.get(effect);
|
|
431
307
|
if (deps) {
|
|
432
308
|
for (const subscribers of deps) {
|
|
@@ -437,72 +313,54 @@ class StateImpl {
|
|
|
437
313
|
}
|
|
438
314
|
};
|
|
439
315
|
}
|
|
440
|
-
// Helper for array updates
|
|
441
316
|
const updateArrayItem = (arr, index, value) => {
|
|
442
317
|
const copy = [...arr];
|
|
443
318
|
copy[index] = value;
|
|
444
319
|
return copy;
|
|
445
320
|
};
|
|
446
|
-
// Helper for single-level updates (optimization)
|
|
447
321
|
const updateShallowProperty = (obj, key, value) => {
|
|
448
322
|
const result = { ...obj };
|
|
449
323
|
result[key] = value;
|
|
450
324
|
return result;
|
|
451
325
|
};
|
|
452
|
-
// Helper to create the appropriate container type
|
|
453
326
|
const createContainer = (key) => {
|
|
454
327
|
const isArrayKey = typeof key === 'number' || !Number.isNaN(Number(key));
|
|
455
328
|
return isArrayKey ? [] : {};
|
|
456
329
|
};
|
|
457
|
-
// Helper for handling array path updates
|
|
458
330
|
const updateArrayPath = (array, pathSegments, value) => {
|
|
459
331
|
const index = Number(pathSegments[0]);
|
|
460
332
|
if (pathSegments.length === 1) {
|
|
461
|
-
// Simple array item update
|
|
462
333
|
return updateArrayItem(array, index, value);
|
|
463
334
|
}
|
|
464
|
-
// Nested path in array
|
|
465
335
|
const copy = [...array];
|
|
466
336
|
const nextPathSegments = pathSegments.slice(1);
|
|
467
337
|
const nextKey = nextPathSegments[0];
|
|
468
|
-
// For null/undefined values in arrays, create appropriate containers
|
|
469
338
|
let nextValue = array[index];
|
|
470
339
|
if (nextValue === undefined || nextValue === null) {
|
|
471
|
-
// Use empty object as default if nextKey is undefined
|
|
472
340
|
nextValue = nextKey !== undefined ? createContainer(nextKey) : {};
|
|
473
341
|
}
|
|
474
342
|
copy[index] = setValueAtPath(nextValue, nextPathSegments, value);
|
|
475
343
|
return copy;
|
|
476
344
|
};
|
|
477
|
-
// Helper for handling object path updates
|
|
478
345
|
const updateObjectPath = (obj, pathSegments, value) => {
|
|
479
|
-
// Ensure we have a valid key
|
|
480
346
|
const currentKey = pathSegments[0];
|
|
481
347
|
if (currentKey === undefined) {
|
|
482
|
-
// This shouldn't happen given our checks in the main function
|
|
483
348
|
return obj;
|
|
484
349
|
}
|
|
485
350
|
if (pathSegments.length === 1) {
|
|
486
|
-
// Simple object property update
|
|
487
351
|
return updateShallowProperty(obj, currentKey, value);
|
|
488
352
|
}
|
|
489
|
-
// Nested path in object
|
|
490
353
|
const nextPathSegments = pathSegments.slice(1);
|
|
491
354
|
const nextKey = nextPathSegments[0];
|
|
492
|
-
// For null/undefined values, create appropriate containers
|
|
493
355
|
let currentValue = obj[currentKey];
|
|
494
356
|
if (currentValue === undefined || currentValue === null) {
|
|
495
|
-
// Use empty object as default if nextKey is undefined
|
|
496
357
|
currentValue = nextKey !== undefined ? createContainer(nextKey) : {};
|
|
497
358
|
}
|
|
498
|
-
// Create new object with updated property
|
|
499
359
|
const result = { ...obj };
|
|
500
360
|
result[currentKey] = setValueAtPath(currentValue, nextPathSegments, value);
|
|
501
361
|
return result;
|
|
502
362
|
};
|
|
503
|
-
// Simplified function to update a nested value at a path
|
|
504
363
|
const setValueAtPath = (obj, pathSegments, value) => {
|
|
505
|
-
// Handle base cases
|
|
506
364
|
if (pathSegments.length === 0) {
|
|
507
365
|
return value;
|
|
508
366
|
}
|
|
@@ -513,7 +371,6 @@ const setValueAtPath = (obj, pathSegments, value) => {
|
|
|
513
371
|
if (currentKey === undefined) {
|
|
514
372
|
return obj;
|
|
515
373
|
}
|
|
516
|
-
// Delegate to specialized handlers based on data type
|
|
517
374
|
if (Array.isArray(obj)) {
|
|
518
375
|
return updateArrayPath(obj, pathSegments, value);
|
|
519
376
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let s=Symbol(),i=(e,t=Object.is)=>u.createState(e,t);var e=e=>u.createEffect(e),t=e=>u.executeBatch(e),r=e=>u.createDerive(e),c=(e,t,r=Object.is)=>u.createSelect(e,t,r);let a=e=>()=>e();var n=(e,t=Object.is)=>{let r=i(e,t);return[()=>a(r)(),{set:e=>r.set(e),update:e=>r.update(e)}]},b=(e,t)=>u.createLens(e,t);class u{static currentSubscriber=null;static pendingSubscribers=new Set;static isNotifying=!1;static batchDepth=0;static deferredEffectCreations=[];static activeSubscribers=new Set;static stateTracking=new WeakMap;static subscriberDependencies=new WeakMap;static parentSubscriber=new WeakMap;static childSubscribers=new WeakMap;value;subscribers=new Set;stateId=Symbol();equalityFn;constructor(e,t=Object.is){this.value=e,this.equalityFn=t}static createState=(e,t=Object.is)=>{let r=new u(e,t);e=()=>r.get();return e.set=e=>r.set(e),e.update=e=>r.update(e),e[s]=r.stateId,e};get=()=>{var r=u.currentSubscriber;if(r){this.subscribers.add(r);let e=u.subscriberDependencies.get(r),t=(e||(e=new Set,u.subscriberDependencies.set(r,e)),e.add(this.subscribers),u.stateTracking.get(r));t||(t=new Set,u.stateTracking.set(r,t)),t.add(this.stateId)}return this.value};set=e=>{if(!this.equalityFn(this.value,e)){var t=u.currentSubscriber;if(t)if(u.stateTracking.get(t)?.has(this.stateId)&&!u.parentSubscriber.get(t))throw new Error("Infinite loop detected: effect() cannot update a state() it depends on!");if(this.value=e,0!==this.subscribers.size){for(var r of this.subscribers)u.pendingSubscribers.add(r);0!==u.batchDepth||u.isNotifying||u.notifySubscribers()}}};update=e=>{this.set(e(this.value))};static createEffect=e=>{let r=()=>{if(!u.activeSubscribers.has(r)){u.activeSubscribers.add(r);var t=u.currentSubscriber;try{if(u.cleanupEffect(r),u.currentSubscriber=r,u.stateTracking.set(r,new Set),t){u.parentSubscriber.set(r,t);let e=u.childSubscribers.get(t);e||(e=new Set,u.childSubscribers.set(t,e)),e.add(r)}e()}finally{u.currentSubscriber=t,u.activeSubscribers.delete(r)}}};if(0===u.batchDepth)r();else{if(u.currentSubscriber){var t=u.currentSubscriber;u.parentSubscriber.set(r,t);let e=u.childSubscribers.get(t);e||(e=new Set,u.childSubscribers.set(t,e)),e.add(r)}u.deferredEffectCreations.push(r)}return()=>{u.cleanupEffect(r),u.pendingSubscribers.delete(r),u.activeSubscribers.delete(r),u.stateTracking.delete(r);var e=u.parentSubscriber.get(r),e=(e&&(e=u.childSubscribers.get(e))&&e.delete(r),u.parentSubscriber.delete(r),u.childSubscribers.get(r));if(e){for(var t of e)u.cleanupEffect(t);e.clear(),u.childSubscribers.delete(r)}}};static executeBatch=e=>{u.batchDepth++;try{return e()}catch(e){throw 1===u.batchDepth&&(u.pendingSubscribers.clear(),u.deferredEffectCreations.length=0),e}finally{if(u.batchDepth--,0===u.batchDepth){if(0<u.deferredEffectCreations.length){var t,e=[...u.deferredEffectCreations];u.deferredEffectCreations.length=0;for(t of e)t()}0<u.pendingSubscribers.size&&!u.isNotifying&&u.notifySubscribers()}}};static createDerive=t=>{let r=u.createState(void 0),s=!1,i;return u.createEffect(()=>{var e=t();s&&Object.is(i,e)||(i=e,r.set(e)),s=!0}),()=>(s||(i=t(),s=!0,r.set(i)),r())};static createSelect=(t,r,s=Object.is)=>{let i,c,a=!1,n=u.createState(void 0);return u.createEffect(()=>{var e=t();a&&Object.is(i,e)||(i=e,e=r(e),a&&void 0!==c&&s(c,e))||(c=e,n.set(e),a=!0)}),()=>(a||(i=t(),c=r(i),n.set(c),a=!0),n())};static createLens=(e,t)=>{let r=(()=>{let r=[],s=new Proxy({},{get:(e,t)=>("string"!=typeof t&&"number"!=typeof t||r.push(t),s)});try{t(s)}catch{}return r})(),s=u.createState(t(e())),i=!1,c=(u.createEffect(()=>{if(!i){i=!0;try{s.set(t(e()))}finally{i=!1}}}),s.set);return s.set=t=>{if(!i){i=!0;try{c(t),e.update(e=>p(e,r,t))}finally{i=!1}}},s.update=e=>{s.set(e(s()))},s};static notifySubscribers=()=>{if(!u.isNotifying){u.isNotifying=!0;try{for(;0<u.pendingSubscribers.size;){var e,t=Array.from(u.pendingSubscribers);u.pendingSubscribers.clear();for(e of t)e()}}finally{u.isNotifying=!1}}};static cleanupEffect=e=>{u.pendingSubscribers.delete(e);var t=u.subscriberDependencies.get(e);if(t){for(var r of t)r.delete(e);t.clear(),u.subscriberDependencies.delete(e)}}}let f=(e,t,r)=>{e=[...e];return e[t]=r,e},l=(e,t,r)=>{e={...e};return e[t]=r,e},d=e=>"number"==typeof e||!Number.isNaN(Number(e))?[]:{},S=(e,t,r)=>{var s=Number(t[0]);if(1===t.length)return f(e,s,r);var i=[...e],t=t.slice(1),c=t[0];let a=e[s];return null==a&&(a=void 0!==c?d(c):{}),i[s]=p(a,t,r),i},h=(e,t,r)=>{var s=t[0];if(void 0===s)return e;if(1===t.length)return l(e,s,r);var t=t.slice(1),i=t[0];let c=e[s];null==c&&(c=void 0!==i?d(i):{});i={...e};return i[s]=p(c,t,r),i},p=(e,t,r)=>0===t.length?r:null==e?p({},t,r):void 0===t[0]?e:(Array.isArray(e)?S:h)(e,t,r);export{i as state,e as effect,t as batch,r as derive,c as select,a as readonlyState,n as protectedState,b as lens};
|
package/package.json
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nerdalytics/beacon",
|
|
3
|
-
"version": "1000.2.
|
|
3
|
+
"version": "1000.2.1",
|
|
4
4
|
"description": "A lightweight reactive state library for Node.js backends. Enables reactive state management with automatic dependency tracking and efficient updates for server-side applications.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "dist/src/index.js",
|
|
6
|
+
"main": "dist/src/index.min.js",
|
|
7
7
|
"types": "dist/src/index.d.ts",
|
|
8
8
|
"files": ["dist/src/index.js", "dist/src/index.d.ts", "src/index.ts", "LICENSE"],
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"typescript": "./src/index.ts",
|
|
12
|
+
"default": "./dist/src/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
9
15
|
"repository": {
|
|
10
16
|
"url": "git+https://github.com/nerdalytics/beacon.git",
|
|
11
17
|
"type": "git"
|
|
@@ -34,6 +40,7 @@
|
|
|
34
40
|
"build": "npm run build:lts",
|
|
35
41
|
"prebuild:lts": "rm -rf dist/",
|
|
36
42
|
"build:lts": "tsc -p tsconfig.lts.json",
|
|
43
|
+
"postbuild:lts": "npx uglify-js --compress --mangle --module --toplevel --v8 --warn --source-map \"content='dist/src/index.js.map'\" --output dist/src/index.min.js dist/src/index.js",
|
|
37
44
|
"prepublishOnly": "npm run build:lts",
|
|
38
45
|
"pretest:lts": "node scripts/run-lts-tests.js",
|
|
39
46
|
"test:lts:20": "node --test dist/tests/**.js",
|
|
@@ -63,7 +70,8 @@
|
|
|
63
70
|
"devDependencies": {
|
|
64
71
|
"@biomejs/biome": "1.9.4",
|
|
65
72
|
"@types/node": "22.14.1",
|
|
66
|
-
"typescript": "5.8.3"
|
|
73
|
+
"typescript": "5.8.3",
|
|
74
|
+
"uglify-js": "3.19.3"
|
|
67
75
|
},
|
|
68
76
|
"engines": {
|
|
69
77
|
"node": ">=20.0.0"
|