@but212/atom-effect 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/dist/core/effect/effect.d.ts +61 -0
- package/dist/core/effect/effect.d.ts.map +1 -1
- package/dist/errors/messages.d.ts +79 -0
- package/dist/errors/messages.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +665 -82
- package/dist/index.mjs.map +1 -1
- package/dist/scheduler/batch.d.ts +29 -0
- package/dist/scheduler/batch.d.ts.map +1 -1
- package/dist/scheduler/scheduler.d.ts +130 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -1
- package/dist/tracking/context.d.ts +68 -0
- package/dist/tracking/context.d.ts.map +1 -1
- package/dist/tracking/dependency-manager.d.ts +203 -0
- package/dist/tracking/dependency-manager.d.ts.map +1 -1
- package/dist/tracking/untracked.d.ts +23 -0
- package/dist/tracking/untracked.d.ts.map +1 -1
- package/dist/utils/debug.d.ts +73 -2
- package/dist/utils/debug.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -8,7 +8,7 @@ const S = {
|
|
|
8
8
|
// 0001 - Effect has been disposed
|
|
9
9
|
EXECUTING: 2
|
|
10
10
|
// 0010 - Effect is currently executing
|
|
11
|
-
},
|
|
11
|
+
}, u = {
|
|
12
12
|
DIRTY: 1,
|
|
13
13
|
// 0001 - Needs recomputation
|
|
14
14
|
IDLE: 2,
|
|
@@ -23,7 +23,7 @@ const S = {
|
|
|
23
23
|
// 100000 - Currently recomputing
|
|
24
24
|
HAS_ERROR: 64
|
|
25
25
|
// 1000000 - Has error state
|
|
26
|
-
},
|
|
26
|
+
}, j = {
|
|
27
27
|
/** Maximum number of pooled objects to prevent memory bloat */
|
|
28
28
|
MAX_SIZE: 1e3,
|
|
29
29
|
/** Number of objects to pre-allocate for performance-critical paths */
|
|
@@ -70,7 +70,7 @@ class b extends a {
|
|
|
70
70
|
super(e, t, !1), this.name = "EffectError";
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
class
|
|
73
|
+
class g extends a {
|
|
74
74
|
/**
|
|
75
75
|
* Creates a new SchedulerError
|
|
76
76
|
* @param message - Error message
|
|
@@ -93,36 +93,130 @@ function E(i, e, t) {
|
|
|
93
93
|
function A(i) {
|
|
94
94
|
return i != null && typeof i.then == "function";
|
|
95
95
|
}
|
|
96
|
-
const
|
|
96
|
+
const o = {
|
|
97
|
+
// ─────────────────────────────────────────────────────────────────
|
|
97
98
|
// Computed errors
|
|
99
|
+
// ─────────────────────────────────────────────────────────────────
|
|
100
|
+
/**
|
|
101
|
+
* Error thrown when computed() receives a non-function argument.
|
|
102
|
+
*/
|
|
98
103
|
COMPUTED_MUST_BE_FUNCTION: "Computed function must be a function",
|
|
104
|
+
/**
|
|
105
|
+
* Error thrown when subscribe() receives a non-function listener.
|
|
106
|
+
*/
|
|
99
107
|
COMPUTED_SUBSCRIBER_MUST_BE_FUNCTION: "Subscriber listener must be a function",
|
|
108
|
+
/**
|
|
109
|
+
* Error thrown when accessing a pending async computed without a default value.
|
|
110
|
+
*/
|
|
100
111
|
COMPUTED_ASYNC_PENDING_NO_DEFAULT: "Async computation is pending. No default value provided",
|
|
112
|
+
/**
|
|
113
|
+
* Error thrown when a synchronous computed computation fails.
|
|
114
|
+
*/
|
|
101
115
|
COMPUTED_COMPUTATION_FAILED: "Computed computation failed",
|
|
116
|
+
/**
|
|
117
|
+
* Error thrown when an asynchronous computed computation fails.
|
|
118
|
+
*/
|
|
102
119
|
COMPUTED_ASYNC_COMPUTATION_FAILED: "Async computed computation failed",
|
|
120
|
+
/**
|
|
121
|
+
* Error thrown when subscribing to a dependency fails.
|
|
122
|
+
*/
|
|
103
123
|
COMPUTED_DEPENDENCY_SUBSCRIPTION_FAILED: "Failed to subscribe to dependency",
|
|
124
|
+
// ─────────────────────────────────────────────────────────────────
|
|
104
125
|
// Atom errors
|
|
126
|
+
// ─────────────────────────────────────────────────────────────────
|
|
127
|
+
/**
|
|
128
|
+
* Error thrown when atom.subscribe() receives a non-function listener.
|
|
129
|
+
*/
|
|
105
130
|
ATOM_SUBSCRIBER_MUST_BE_FUNCTION: "Subscription listener must be a function",
|
|
131
|
+
/**
|
|
132
|
+
* Error thrown when the atom subscriber notification process fails.
|
|
133
|
+
*/
|
|
106
134
|
ATOM_SUBSCRIBER_EXECUTION_FAILED: "Error occurred while executing atom subscribers",
|
|
135
|
+
/**
|
|
136
|
+
* Error logged when an individual subscriber throws during notification.
|
|
137
|
+
* @remarks This error is caught and logged to prevent cascading failures.
|
|
138
|
+
*/
|
|
107
139
|
ATOM_INDIVIDUAL_SUBSCRIBER_FAILED: "Error during individual atom subscriber execution",
|
|
140
|
+
// ─────────────────────────────────────────────────────────────────
|
|
108
141
|
// Effect errors
|
|
142
|
+
// ─────────────────────────────────────────────────────────────────
|
|
143
|
+
/**
|
|
144
|
+
* Error thrown when effect() receives a non-function argument.
|
|
145
|
+
*/
|
|
109
146
|
EFFECT_MUST_BE_FUNCTION: "Effect function must be a function",
|
|
147
|
+
/**
|
|
148
|
+
* Error thrown when an effect's execution fails.
|
|
149
|
+
*/
|
|
110
150
|
EFFECT_EXECUTION_FAILED: "Effect execution failed",
|
|
151
|
+
/**
|
|
152
|
+
* Error thrown when an effect's cleanup function fails.
|
|
153
|
+
*/
|
|
111
154
|
EFFECT_CLEANUP_FAILED: "Effect cleanup function execution failed",
|
|
155
|
+
// ─────────────────────────────────────────────────────────────────
|
|
112
156
|
// Debug warnings
|
|
157
|
+
// ─────────────────────────────────────────────────────────────────
|
|
158
|
+
/**
|
|
159
|
+
* Warning message for large dependency graphs.
|
|
160
|
+
*
|
|
161
|
+
* @param count - The number of dependencies detected
|
|
162
|
+
* @returns Formatted warning message with dependency count
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* console.warn(ERROR_MESSAGES.LARGE_DEPENDENCY_GRAPH(150));
|
|
167
|
+
* // Output: "Large dependency graph detected: 150 dependencies"
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
113
170
|
LARGE_DEPENDENCY_GRAPH: (i) => `Large dependency graph detected: ${i} dependencies`,
|
|
171
|
+
/**
|
|
172
|
+
* Warning logged when attempting to unsubscribe a non-existent listener.
|
|
173
|
+
*/
|
|
114
174
|
UNSUBSCRIBE_NON_EXISTENT: "Attempted to unsubscribe a non-existent listener",
|
|
175
|
+
/**
|
|
176
|
+
* Error logged when the onError callback itself throws an error.
|
|
177
|
+
* @remarks This prevents cascading failures from masking the original error.
|
|
178
|
+
*/
|
|
115
179
|
CALLBACK_ERROR_IN_ERROR_HANDLER: "Error occurred during onError callback execution"
|
|
116
180
|
};
|
|
117
181
|
class x {
|
|
118
182
|
constructor() {
|
|
119
183
|
this.queue = /* @__PURE__ */ new Set(), this.isProcessing = !1, this.isBatching = !1, this.batchDepth = 0, this.batchQueue = [], this.batchQueueSize = 0, this.isFlushingSync = !1, this.maxFlushIterations = 1e3;
|
|
120
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Schedules a callback for execution.
|
|
187
|
+
*
|
|
188
|
+
* If batching is active or a sync flush is in progress, the callback
|
|
189
|
+
* is added to the batch queue. Otherwise, it's added to the main queue
|
|
190
|
+
* and a flush is triggered via microtask.
|
|
191
|
+
*
|
|
192
|
+
* @param callback - The function to schedule for execution
|
|
193
|
+
* @throws {SchedulerError} If callback is not a function
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```typescript
|
|
197
|
+
* scheduler.schedule(() => {
|
|
198
|
+
* // This runs in the next microtask (or sync if batching)
|
|
199
|
+
* updateUI();
|
|
200
|
+
* });
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
121
203
|
schedule(e) {
|
|
122
204
|
if (typeof e != "function")
|
|
123
|
-
throw new
|
|
205
|
+
throw new g("Scheduler callback must be a function");
|
|
124
206
|
this.isBatching || this.isFlushingSync ? this.batchQueue[this.batchQueueSize++] = e : (this.queue.add(e), this.isProcessing || this.flush());
|
|
125
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Flushes the queue asynchronously via microtask.
|
|
210
|
+
*
|
|
211
|
+
* Executes all queued callbacks in a microtask, allowing the current
|
|
212
|
+
* synchronous execution to complete first. Errors in individual
|
|
213
|
+
* callbacks are caught and logged without interrupting others.
|
|
214
|
+
*
|
|
215
|
+
* @private
|
|
216
|
+
* @remarks
|
|
217
|
+
* This method is idempotent - calling it multiple times while
|
|
218
|
+
* processing is active has no effect.
|
|
219
|
+
*/
|
|
126
220
|
flush() {
|
|
127
221
|
if (this.isProcessing || this.queue.size === 0) return;
|
|
128
222
|
this.isProcessing = !0;
|
|
@@ -133,12 +227,25 @@ class x {
|
|
|
133
227
|
e[t]?.();
|
|
134
228
|
} catch (s) {
|
|
135
229
|
console.error(
|
|
136
|
-
new
|
|
230
|
+
new g("Error occurred during scheduler execution", s)
|
|
137
231
|
);
|
|
138
232
|
}
|
|
139
233
|
this.isProcessing = !1, this.queue.size > 0 && !this.isBatching && this.flush();
|
|
140
234
|
});
|
|
141
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* Flushes all queued callbacks synchronously.
|
|
238
|
+
*
|
|
239
|
+
* This method is called when a batch ends. It processes all callbacks
|
|
240
|
+
* in the batch queue and main queue synchronously, allowing callbacks
|
|
241
|
+
* to schedule additional callbacks that are processed in the same flush.
|
|
242
|
+
*
|
|
243
|
+
* @private
|
|
244
|
+
* @remarks
|
|
245
|
+
* - Includes infinite loop protection via maxFlushIterations
|
|
246
|
+
* - Errors in callbacks are caught and logged individually
|
|
247
|
+
* - The isFlushingSync flag prevents re-entrancy issues
|
|
248
|
+
*/
|
|
142
249
|
flushSync() {
|
|
143
250
|
this.isFlushingSync = !0;
|
|
144
251
|
try {
|
|
@@ -151,7 +258,7 @@ class x {
|
|
|
151
258
|
for (; this.queue.size > 0; ) {
|
|
152
259
|
if (++e > this.maxFlushIterations) {
|
|
153
260
|
console.error(
|
|
154
|
-
new
|
|
261
|
+
new g(
|
|
155
262
|
`Maximum flush iterations (${this.maxFlushIterations}) exceeded. Possible infinite loop in reactive dependencies. Consider increasing the limit with scheduler.setMaxFlushIterations()`
|
|
156
263
|
)
|
|
157
264
|
), this.queue.clear(), this.batchQueueSize = 0;
|
|
@@ -164,7 +271,7 @@ class x {
|
|
|
164
271
|
t[s]?.();
|
|
165
272
|
} catch (r) {
|
|
166
273
|
console.error(
|
|
167
|
-
new
|
|
274
|
+
new g("Error occurred during batch execution", r)
|
|
168
275
|
);
|
|
169
276
|
}
|
|
170
277
|
if (this.batchQueueSize > 0) {
|
|
@@ -177,33 +284,91 @@ class x {
|
|
|
177
284
|
this.isFlushingSync = !1;
|
|
178
285
|
}
|
|
179
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* Starts a new batch operation.
|
|
289
|
+
*
|
|
290
|
+
* While batching is active, all scheduled callbacks are deferred
|
|
291
|
+
* until endBatch() is called. Batches can be nested - only the
|
|
292
|
+
* outermost endBatch() triggers execution.
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* ```typescript
|
|
296
|
+
* scheduler.startBatch();
|
|
297
|
+
* // All updates here are deferred
|
|
298
|
+
* atom1.value = 'a';
|
|
299
|
+
* atom2.value = 'b';
|
|
300
|
+
* scheduler.endBatch(); // Both updates processed together
|
|
301
|
+
* ```
|
|
302
|
+
*/
|
|
180
303
|
startBatch() {
|
|
181
304
|
this.batchDepth++, this.isBatching = !0;
|
|
182
305
|
}
|
|
306
|
+
/**
|
|
307
|
+
* Ends a batch operation.
|
|
308
|
+
*
|
|
309
|
+
* Decrements the batch depth counter. When depth reaches zero,
|
|
310
|
+
* all queued callbacks are flushed synchronously and batching
|
|
311
|
+
* is disabled.
|
|
312
|
+
*
|
|
313
|
+
* @remarks
|
|
314
|
+
* Safe to call even if startBatch() wasn't called - depth is
|
|
315
|
+
* clamped to zero minimum.
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```typescript
|
|
319
|
+
* scheduler.startBatch();
|
|
320
|
+
* try {
|
|
321
|
+
* // ... batched operations
|
|
322
|
+
* } finally {
|
|
323
|
+
* scheduler.endBatch(); // Always end batch, even on error
|
|
324
|
+
* }
|
|
325
|
+
* ```
|
|
326
|
+
*/
|
|
183
327
|
endBatch() {
|
|
184
328
|
this.batchDepth = Math.max(0, this.batchDepth - 1), this.batchDepth === 0 && (this.flushSync(), this.isBatching = !1);
|
|
185
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* Sets the maximum number of flush iterations allowed.
|
|
332
|
+
*
|
|
333
|
+
* This limit prevents infinite loops when reactive dependencies
|
|
334
|
+
* form cycles. If exceeded, the queue is cleared and an error
|
|
335
|
+
* is logged.
|
|
336
|
+
*
|
|
337
|
+
* @param max - Maximum iterations (must be at least 10)
|
|
338
|
+
* @throws {SchedulerError} If max is less than 10
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* ```typescript
|
|
342
|
+
* // Increase limit for complex dependency graphs
|
|
343
|
+
* scheduler.setMaxFlushIterations(5000);
|
|
344
|
+
* ```
|
|
345
|
+
*/
|
|
186
346
|
setMaxFlushIterations(e) {
|
|
187
347
|
if (e < 10)
|
|
188
|
-
throw new
|
|
348
|
+
throw new g("Max flush iterations must be at least 10");
|
|
189
349
|
this.maxFlushIterations = e;
|
|
190
350
|
}
|
|
191
351
|
}
|
|
192
|
-
const
|
|
193
|
-
function
|
|
352
|
+
const D = new x();
|
|
353
|
+
function G(i) {
|
|
194
354
|
if (typeof i != "function")
|
|
195
355
|
throw new a("Batch callback must be a function");
|
|
196
|
-
|
|
356
|
+
D.startBatch();
|
|
197
357
|
try {
|
|
198
358
|
return i();
|
|
199
359
|
} catch (e) {
|
|
200
360
|
throw new a("Error occurred during batch execution", e);
|
|
201
361
|
} finally {
|
|
202
|
-
|
|
362
|
+
D.endBatch();
|
|
203
363
|
}
|
|
204
364
|
}
|
|
205
365
|
const p = {
|
|
366
|
+
/** @inheritdoc */
|
|
206
367
|
current: null,
|
|
368
|
+
/**
|
|
369
|
+
* @inheritdoc
|
|
370
|
+
* @throws Re-throws any error from the executed function after restoring context
|
|
371
|
+
*/
|
|
207
372
|
run(i, e) {
|
|
208
373
|
const t = this.current;
|
|
209
374
|
this.current = i;
|
|
@@ -213,6 +378,7 @@ const p = {
|
|
|
213
378
|
this.current = t;
|
|
214
379
|
}
|
|
215
380
|
},
|
|
381
|
+
/** @inheritdoc */
|
|
216
382
|
getCurrent() {
|
|
217
383
|
return this.current;
|
|
218
384
|
}
|
|
@@ -221,6 +387,26 @@ class F {
|
|
|
221
387
|
constructor() {
|
|
222
388
|
this.depMap = /* @__PURE__ */ new WeakMap(), this.depRefs = [], this.cleanupThreshold = 100, this.addCount = 0;
|
|
223
389
|
}
|
|
390
|
+
/**
|
|
391
|
+
* Adds a dependency with its associated unsubscribe callback.
|
|
392
|
+
*
|
|
393
|
+
* If the dependency already exists, the new unsubscribe callback is
|
|
394
|
+
* immediately called to prevent duplicate subscriptions.
|
|
395
|
+
*
|
|
396
|
+
* @param dep - The dependency to track (atom, computed, etc.)
|
|
397
|
+
* @param unsubscribe - Callback to invoke when removing the dependency
|
|
398
|
+
*
|
|
399
|
+
* @remarks
|
|
400
|
+
* - Duplicate dependencies are rejected with immediate unsubscribe
|
|
401
|
+
* - Automatic cleanup is triggered every `cleanupThreshold` additions
|
|
402
|
+
* - Time complexity: O(1) for add, O(n) when cleanup triggers
|
|
403
|
+
*
|
|
404
|
+
* @example
|
|
405
|
+
* ```typescript
|
|
406
|
+
* const unsubscribe = atom.subscribe(() => markDirty());
|
|
407
|
+
* manager.addDependency(atom, unsubscribe);
|
|
408
|
+
* ```
|
|
409
|
+
*/
|
|
224
410
|
addDependency(e, t) {
|
|
225
411
|
if (this.depMap.has(e)) {
|
|
226
412
|
t();
|
|
@@ -228,6 +414,25 @@ class F {
|
|
|
228
414
|
}
|
|
229
415
|
this.depMap.set(e, t), this.depRefs.push(new WeakRef(e)), ++this.addCount >= this.cleanupThreshold && (this.cleanup(), this.addCount = 0);
|
|
230
416
|
}
|
|
417
|
+
/**
|
|
418
|
+
* Removes a dependency and calls its unsubscribe callback.
|
|
419
|
+
*
|
|
420
|
+
* @param dep - The dependency to remove
|
|
421
|
+
* @returns `true` if the dependency was found and removed, `false` otherwise
|
|
422
|
+
*
|
|
423
|
+
* @remarks
|
|
424
|
+
* - Unsubscribe errors are caught and logged to prevent cascading failures
|
|
425
|
+
* - The WeakRef entry is not immediately removed (cleaned up lazily)
|
|
426
|
+
* - Time complexity: O(1)
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```typescript
|
|
430
|
+
* const wasRemoved = manager.removeDependency(atom);
|
|
431
|
+
* if (wasRemoved) {
|
|
432
|
+
* console.log('Dependency successfully removed');
|
|
433
|
+
* }
|
|
434
|
+
* ```
|
|
435
|
+
*/
|
|
231
436
|
removeDependency(e) {
|
|
232
437
|
const t = this.depMap.get(e);
|
|
233
438
|
if (t) {
|
|
@@ -240,9 +445,42 @@ class F {
|
|
|
240
445
|
}
|
|
241
446
|
return !1;
|
|
242
447
|
}
|
|
448
|
+
/**
|
|
449
|
+
* Checks if a dependency is currently being tracked.
|
|
450
|
+
*
|
|
451
|
+
* @param dep - The dependency to check
|
|
452
|
+
* @returns `true` if the dependency exists in the manager
|
|
453
|
+
*
|
|
454
|
+
* @remarks
|
|
455
|
+
* Time complexity: O(1)
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* ```typescript
|
|
459
|
+
* if (manager.hasDependency(atom)) {
|
|
460
|
+
* // Dependency is already tracked
|
|
461
|
+
* }
|
|
462
|
+
* ```
|
|
463
|
+
*/
|
|
243
464
|
hasDependency(e) {
|
|
244
465
|
return this.depMap.has(e);
|
|
245
466
|
}
|
|
467
|
+
/**
|
|
468
|
+
* Removes all dependencies and calls their unsubscribe callbacks.
|
|
469
|
+
*
|
|
470
|
+
* This method iterates through all tracked dependencies, calls their
|
|
471
|
+
* unsubscribe callbacks, and clears internal storage.
|
|
472
|
+
*
|
|
473
|
+
* @remarks
|
|
474
|
+
* - Errors during unsubscribe are caught and logged individually
|
|
475
|
+
* - Safe to call multiple times (idempotent after first call)
|
|
476
|
+
* - Time complexity: O(n) where n is the number of dependencies
|
|
477
|
+
*
|
|
478
|
+
* @example
|
|
479
|
+
* ```typescript
|
|
480
|
+
* // Clean up when disposing a computed value
|
|
481
|
+
* manager.unsubscribeAll();
|
|
482
|
+
* ```
|
|
483
|
+
*/
|
|
246
484
|
unsubscribeAll() {
|
|
247
485
|
for (let e = 0; e < this.depRefs.length; e++) {
|
|
248
486
|
const t = this.depRefs[e].deref();
|
|
@@ -260,12 +498,59 @@ class F {
|
|
|
260
498
|
}
|
|
261
499
|
this.depRefs.length = 0, this.addCount = 0;
|
|
262
500
|
}
|
|
501
|
+
/**
|
|
502
|
+
* Removes stale WeakRefs from the internal array.
|
|
503
|
+
*
|
|
504
|
+
* WeakRefs whose targets have been garbage collected are filtered out
|
|
505
|
+
* to prevent unbounded growth of the depRefs array.
|
|
506
|
+
*
|
|
507
|
+
* @remarks
|
|
508
|
+
* - Called automatically every `cleanupThreshold` additions
|
|
509
|
+
* - Can be called manually for immediate cleanup
|
|
510
|
+
* - Time complexity: O(n) where n is the number of WeakRefs
|
|
511
|
+
*
|
|
512
|
+
* @example
|
|
513
|
+
* ```typescript
|
|
514
|
+
* // Force immediate cleanup
|
|
515
|
+
* manager.cleanup();
|
|
516
|
+
* ```
|
|
517
|
+
*/
|
|
263
518
|
cleanup() {
|
|
264
519
|
this.depRefs = this.depRefs.filter((e) => e.deref() !== void 0);
|
|
265
520
|
}
|
|
521
|
+
/**
|
|
522
|
+
* Gets the current number of live dependencies.
|
|
523
|
+
*
|
|
524
|
+
* @returns The count of dependencies that haven't been garbage collected
|
|
525
|
+
*
|
|
526
|
+
* @remarks
|
|
527
|
+
* - Triggers cleanup before counting for accurate results
|
|
528
|
+
* - Time complexity: O(n) due to cleanup
|
|
529
|
+
*
|
|
530
|
+
* @example
|
|
531
|
+
* ```typescript
|
|
532
|
+
* console.log(`Tracking ${manager.count} dependencies`);
|
|
533
|
+
* ```
|
|
534
|
+
*/
|
|
266
535
|
get count() {
|
|
267
536
|
return this.cleanup(), this.depRefs.length;
|
|
268
537
|
}
|
|
538
|
+
/**
|
|
539
|
+
* Gets an array of all live dependencies.
|
|
540
|
+
*
|
|
541
|
+
* @returns Array of dependencies that haven't been garbage collected
|
|
542
|
+
*
|
|
543
|
+
* @remarks
|
|
544
|
+
* - Returns a new array (safe to modify)
|
|
545
|
+
* - Does not trigger cleanup (may include some stale refs)
|
|
546
|
+
* - Time complexity: O(n)
|
|
547
|
+
*
|
|
548
|
+
* @example
|
|
549
|
+
* ```typescript
|
|
550
|
+
* const deps = manager.getDependencies();
|
|
551
|
+
* deps.forEach(dep => console.log(dep));
|
|
552
|
+
* ```
|
|
553
|
+
*/
|
|
269
554
|
getDependencies() {
|
|
270
555
|
const e = [];
|
|
271
556
|
for (let t = 0; t < this.depRefs.length; t++) {
|
|
@@ -274,14 +559,49 @@ class F {
|
|
|
274
559
|
}
|
|
275
560
|
return e;
|
|
276
561
|
}
|
|
562
|
+
/**
|
|
563
|
+
* Gets the internal WeakMap for advanced use cases.
|
|
564
|
+
*
|
|
565
|
+
* @returns The internal dependency -> unsubscribe WeakMap
|
|
566
|
+
*
|
|
567
|
+
* @remarks
|
|
568
|
+
* - Returns the actual internal map (not a copy)
|
|
569
|
+
* - Modifications will affect the manager's state
|
|
570
|
+
* - Use with caution in production code
|
|
571
|
+
*
|
|
572
|
+
* @example
|
|
573
|
+
* ```typescript
|
|
574
|
+
* const map = manager.getDepMap();
|
|
575
|
+
* const unsubscribe = map.get(someDependency);
|
|
576
|
+
* ```
|
|
577
|
+
*/
|
|
277
578
|
getDepMap() {
|
|
278
579
|
return this.depMap;
|
|
279
580
|
}
|
|
581
|
+
/**
|
|
582
|
+
* Sets the threshold for automatic cleanup triggering.
|
|
583
|
+
*
|
|
584
|
+
* @param threshold - Number of additions before cleanup (minimum 1)
|
|
585
|
+
*
|
|
586
|
+
* @remarks
|
|
587
|
+
* - Lower values mean more frequent cleanup (less memory, more CPU)
|
|
588
|
+
* - Higher values mean less frequent cleanup (more memory, less CPU)
|
|
589
|
+
* - Default is 100, suitable for most use cases
|
|
590
|
+
*
|
|
591
|
+
* @example
|
|
592
|
+
* ```typescript
|
|
593
|
+
* // More aggressive cleanup for memory-constrained environments
|
|
594
|
+
* manager.setCleanupThreshold(50);
|
|
595
|
+
*
|
|
596
|
+
* // Less frequent cleanup for performance-critical paths
|
|
597
|
+
* manager.setCleanupThreshold(500);
|
|
598
|
+
* ```
|
|
599
|
+
*/
|
|
280
600
|
setCleanupThreshold(e) {
|
|
281
601
|
this.cleanupThreshold = Math.max(1, e);
|
|
282
602
|
}
|
|
283
603
|
}
|
|
284
|
-
function
|
|
604
|
+
function V(i) {
|
|
285
605
|
if (typeof i != "function")
|
|
286
606
|
throw new a("Untracked callback must be a function");
|
|
287
607
|
const e = p.current;
|
|
@@ -294,43 +614,148 @@ function G(i) {
|
|
|
294
614
|
p.current = e;
|
|
295
615
|
}
|
|
296
616
|
}
|
|
297
|
-
const C = /* @__PURE__ */ Symbol("debugName"), P = /* @__PURE__ */ Symbol("id"), R = /* @__PURE__ */ Symbol("type"), T = /* @__PURE__ */ Symbol("noDefaultValue")
|
|
617
|
+
const C = /* @__PURE__ */ Symbol("debugName"), P = /* @__PURE__ */ Symbol("id"), R = /* @__PURE__ */ Symbol("type"), T = /* @__PURE__ */ Symbol("noDefaultValue");
|
|
618
|
+
function w(i) {
|
|
619
|
+
return i !== null && typeof i == "object" && "dependencies" in i && i.dependencies instanceof Set;
|
|
620
|
+
}
|
|
621
|
+
const h = {
|
|
622
|
+
/**
|
|
623
|
+
* Whether debug mode is enabled.
|
|
624
|
+
*
|
|
625
|
+
* @remarks
|
|
626
|
+
* Automatically set based on `NODE_ENV` environment variable.
|
|
627
|
+
* Only `'development'` enables debug features.
|
|
628
|
+
*/
|
|
298
629
|
enabled: typeof process < "u" && process.env?.NODE_ENV === "development",
|
|
630
|
+
/**
|
|
631
|
+
* Maximum number of dependencies before warning.
|
|
632
|
+
*
|
|
633
|
+
* @see {@link DEBUG_CONFIG.MAX_DEPENDENCIES}
|
|
634
|
+
*/
|
|
299
635
|
maxDependencies: m.MAX_DEPENDENCIES,
|
|
636
|
+
/**
|
|
637
|
+
* Whether to warn about potential infinite loops.
|
|
638
|
+
*
|
|
639
|
+
* @see {@link DEBUG_CONFIG.WARN_INFINITE_LOOP}
|
|
640
|
+
*/
|
|
300
641
|
warnInfiniteLoop: m.WARN_INFINITE_LOOP,
|
|
642
|
+
/**
|
|
643
|
+
* Logs a warning message when condition is true and debug is enabled.
|
|
644
|
+
*
|
|
645
|
+
* @param condition - When true, the warning is logged
|
|
646
|
+
* @param message - The warning message to display
|
|
647
|
+
*
|
|
648
|
+
* @example
|
|
649
|
+
* ```typescript
|
|
650
|
+
* debug.warn(deps.length > 100, 'Large dependency graph detected');
|
|
651
|
+
* ```
|
|
652
|
+
*/
|
|
301
653
|
warn(i, e) {
|
|
302
654
|
this.enabled && i && console.warn(`[Atom Effect] ${e}`);
|
|
303
655
|
},
|
|
656
|
+
/**
|
|
657
|
+
* Checks for circular dependencies in the dependency graph.
|
|
658
|
+
*
|
|
659
|
+
* Detects two types of circular references:
|
|
660
|
+
* 1. **Direct**: A depends on itself (A → A)
|
|
661
|
+
* 2. **Indirect**: A depends on B which depends on A (A → B → A)
|
|
662
|
+
*
|
|
663
|
+
* @param dep - The dependency being added
|
|
664
|
+
* @param current - The current reactive object adding the dependency
|
|
665
|
+
* @param visited - Set of already visited nodes (for recursion)
|
|
666
|
+
*
|
|
667
|
+
* @throws {ComputedError} When a circular dependency is detected
|
|
668
|
+
*
|
|
669
|
+
* @remarks
|
|
670
|
+
* - Direct circular detection runs in all environments
|
|
671
|
+
* - Indirect circular detection only runs in development mode
|
|
672
|
+
* - Uses depth-first traversal with O(n) time complexity
|
|
673
|
+
*
|
|
674
|
+
* @example
|
|
675
|
+
* ```typescript
|
|
676
|
+
* // This will throw for direct circular reference
|
|
677
|
+
* debug.checkCircular(computedA, computedA);
|
|
678
|
+
*
|
|
679
|
+
* // This will throw for indirect circular reference (dev only)
|
|
680
|
+
* // Given: A → B → C → A
|
|
681
|
+
* debug.checkCircular(computedC, computedA);
|
|
682
|
+
* ```
|
|
683
|
+
*/
|
|
304
684
|
checkCircular(i, e, t = /* @__PURE__ */ new Set()) {
|
|
305
685
|
if (i === e)
|
|
306
686
|
throw new d("Direct circular dependency detected");
|
|
307
687
|
if (this.enabled) {
|
|
308
688
|
if (t.has(i))
|
|
309
689
|
throw new d("Indirect circular dependency detected");
|
|
310
|
-
if (t.add(i), i
|
|
311
|
-
const s
|
|
312
|
-
|
|
313
|
-
this.checkCircular(r, e, t);
|
|
314
|
-
}
|
|
690
|
+
if (t.add(i), w(i))
|
|
691
|
+
for (const s of i.dependencies)
|
|
692
|
+
this.checkCircular(s, e, t);
|
|
315
693
|
}
|
|
316
694
|
},
|
|
695
|
+
/**
|
|
696
|
+
* Attaches debug metadata to a reactive object.
|
|
697
|
+
*
|
|
698
|
+
* @param obj - The object to attach metadata to
|
|
699
|
+
* @param type - The type of reactive object ('atom' | 'computed' | 'effect')
|
|
700
|
+
* @param id - The unique identifier for this object
|
|
701
|
+
*
|
|
702
|
+
* @remarks
|
|
703
|
+
* Only attaches metadata when debug mode is enabled.
|
|
704
|
+
* Uses symbol keys to avoid property name collisions.
|
|
705
|
+
*
|
|
706
|
+
* @example
|
|
707
|
+
* ```typescript
|
|
708
|
+
* const atom = createAtomInternal(0);
|
|
709
|
+
* debug.attachDebugInfo(atom, 'atom', 1);
|
|
710
|
+
* // atom[DEBUG_NAME] === 'atom_1'
|
|
711
|
+
* // atom[DEBUG_ID] === 1
|
|
712
|
+
* // atom[DEBUG_TYPE] === 'atom'
|
|
713
|
+
* ```
|
|
714
|
+
*/
|
|
317
715
|
attachDebugInfo(i, e, t) {
|
|
318
|
-
if (!this.enabled)
|
|
716
|
+
if (!this.enabled)
|
|
717
|
+
return;
|
|
319
718
|
const s = i;
|
|
320
719
|
s[C] = `${e}_${t}`, s[P] = t, s[R] = e;
|
|
321
720
|
},
|
|
721
|
+
/**
|
|
722
|
+
* Retrieves the debug display name from a reactive object.
|
|
723
|
+
*
|
|
724
|
+
* @param obj - The object to get the name from
|
|
725
|
+
* @returns The debug name (e.g., 'atom_1') or undefined if not set
|
|
726
|
+
*
|
|
727
|
+
* @example
|
|
728
|
+
* ```typescript
|
|
729
|
+
* const name = debug.getDebugName(myAtom);
|
|
730
|
+
* console.log(`Updating ${name ?? 'unknown'}`);
|
|
731
|
+
* ```
|
|
732
|
+
*/
|
|
322
733
|
getDebugName(i) {
|
|
323
|
-
if (i && typeof i == "object" && C in i)
|
|
734
|
+
if (i !== null && typeof i == "object" && C in i)
|
|
324
735
|
return i[C];
|
|
325
736
|
},
|
|
737
|
+
/**
|
|
738
|
+
* Retrieves the debug type from a reactive object.
|
|
739
|
+
*
|
|
740
|
+
* @param obj - The object to get the type from
|
|
741
|
+
* @returns The type ('atom' | 'computed' | 'effect') or undefined if not set
|
|
742
|
+
*
|
|
743
|
+
* @example
|
|
744
|
+
* ```typescript
|
|
745
|
+
* const type = debug.getDebugType(reactiveObj);
|
|
746
|
+
* if (type === 'computed') {
|
|
747
|
+
* // Handle computed-specific logic
|
|
748
|
+
* }
|
|
749
|
+
* ```
|
|
750
|
+
*/
|
|
326
751
|
getDebugType(i) {
|
|
327
|
-
if (i && typeof i == "object" && R in i)
|
|
752
|
+
if (i !== null && typeof i == "object" && R in i)
|
|
328
753
|
return i[R];
|
|
329
754
|
}
|
|
330
755
|
};
|
|
331
|
-
let
|
|
332
|
-
const I = () =>
|
|
333
|
-
class
|
|
756
|
+
let v = 1;
|
|
757
|
+
const I = () => v++;
|
|
758
|
+
class L {
|
|
334
759
|
/**
|
|
335
760
|
* Creates a new AtomImpl instance.
|
|
336
761
|
*
|
|
@@ -478,7 +903,7 @@ class v {
|
|
|
478
903
|
l && l(e, t);
|
|
479
904
|
} catch (l) {
|
|
480
905
|
console.error(
|
|
481
|
-
new a(
|
|
906
|
+
new a(o.ATOM_INDIVIDUAL_SUBSCRIBER_FAILED, l)
|
|
482
907
|
);
|
|
483
908
|
}
|
|
484
909
|
for (let f = 0; f < M; f++)
|
|
@@ -487,11 +912,11 @@ class v {
|
|
|
487
912
|
l && l.execute();
|
|
488
913
|
} catch (l) {
|
|
489
914
|
console.error(
|
|
490
|
-
new a(
|
|
915
|
+
new a(o.ATOM_INDIVIDUAL_SUBSCRIBER_FAILED, l)
|
|
491
916
|
);
|
|
492
917
|
}
|
|
493
918
|
};
|
|
494
|
-
this._sync && !
|
|
919
|
+
this._sync && !D.isBatching ? r() : D.schedule(r);
|
|
495
920
|
}
|
|
496
921
|
/**
|
|
497
922
|
* Subscribes a listener function to value changes.
|
|
@@ -510,7 +935,7 @@ class v {
|
|
|
510
935
|
*/
|
|
511
936
|
subscribe(e) {
|
|
512
937
|
if (typeof e != "function")
|
|
513
|
-
throw new a(
|
|
938
|
+
throw new a(o.ATOM_SUBSCRIBER_MUST_BE_FUNCTION);
|
|
514
939
|
return this._addFnSub(e);
|
|
515
940
|
}
|
|
516
941
|
/**
|
|
@@ -544,8 +969,8 @@ class v {
|
|
|
544
969
|
return this._fnSubCount + this._objSubCount;
|
|
545
970
|
}
|
|
546
971
|
}
|
|
547
|
-
function
|
|
548
|
-
return new
|
|
972
|
+
function X(i, e = {}) {
|
|
973
|
+
return new L(i, e.sync ?? !1);
|
|
549
974
|
}
|
|
550
975
|
class N {
|
|
551
976
|
constructor() {
|
|
@@ -686,11 +1111,11 @@ class N {
|
|
|
686
1111
|
return this.subscribers ? [...this.subscribers] : [];
|
|
687
1112
|
}
|
|
688
1113
|
}
|
|
689
|
-
class
|
|
1114
|
+
class k {
|
|
690
1115
|
constructor(e, t = {}) {
|
|
691
1116
|
if (this._error = null, this._promiseId = 0, this.MAX_PROMISE_ID = Number.MAX_SAFE_INTEGER - 1, typeof e != "function")
|
|
692
|
-
throw new d(
|
|
693
|
-
this._fn = e, this._stateFlags =
|
|
1117
|
+
throw new d(o.COMPUTED_MUST_BE_FUNCTION);
|
|
1118
|
+
this._fn = e, this._stateFlags = u.DIRTY | u.IDLE, this._value = void 0;
|
|
694
1119
|
const {
|
|
695
1120
|
equal: s = Object.is,
|
|
696
1121
|
defaultValue: r = T,
|
|
@@ -709,14 +1134,14 @@ class L {
|
|
|
709
1134
|
}
|
|
710
1135
|
// === PUBLIC API ===
|
|
711
1136
|
get value() {
|
|
712
|
-
if ((this._stateFlags & (
|
|
1137
|
+
if ((this._stateFlags & (u.RESOLVED | u.DIRTY)) === u.RESOLVED)
|
|
713
1138
|
return this._registerTracking(), this._value;
|
|
714
1139
|
const t = this._computeValue();
|
|
715
1140
|
return this._registerTracking(), t;
|
|
716
1141
|
}
|
|
717
1142
|
subscribe(e) {
|
|
718
1143
|
if (typeof e != "function")
|
|
719
|
-
throw new d(
|
|
1144
|
+
throw new d(o.COMPUTED_SUBSCRIBER_MUST_BE_FUNCTION);
|
|
720
1145
|
return this._functionSubscribers.add(e);
|
|
721
1146
|
}
|
|
722
1147
|
peek() {
|
|
@@ -741,47 +1166,47 @@ class L {
|
|
|
741
1166
|
this._markDirty();
|
|
742
1167
|
}
|
|
743
1168
|
dispose() {
|
|
744
|
-
this._dependencyManager.unsubscribeAll(), this._functionSubscribers.clear(), this._objectSubscribers.clear(), this._stateFlags =
|
|
1169
|
+
this._dependencyManager.unsubscribeAll(), this._functionSubscribers.clear(), this._objectSubscribers.clear(), this._stateFlags = u.DIRTY | u.IDLE, this._error = null, this._value = void 0, this._promiseId = (this._promiseId + 1) % this.MAX_PROMISE_ID;
|
|
745
1170
|
}
|
|
746
1171
|
// === PRIVATE: State Flag Operations (inlined for performance) ===
|
|
747
1172
|
_isDirty() {
|
|
748
|
-
return (this._stateFlags &
|
|
1173
|
+
return (this._stateFlags & u.DIRTY) !== 0;
|
|
749
1174
|
}
|
|
750
1175
|
_setDirty() {
|
|
751
|
-
this._stateFlags |=
|
|
1176
|
+
this._stateFlags |= u.DIRTY;
|
|
752
1177
|
}
|
|
753
1178
|
_clearDirty() {
|
|
754
1179
|
this._stateFlags &= -2;
|
|
755
1180
|
}
|
|
756
1181
|
_isIdle() {
|
|
757
|
-
return (this._stateFlags &
|
|
1182
|
+
return (this._stateFlags & u.IDLE) !== 0;
|
|
758
1183
|
}
|
|
759
1184
|
_setIdle() {
|
|
760
|
-
this._stateFlags |=
|
|
1185
|
+
this._stateFlags |= u.IDLE, this._stateFlags &= -29;
|
|
761
1186
|
}
|
|
762
1187
|
_isPending() {
|
|
763
|
-
return (this._stateFlags &
|
|
1188
|
+
return (this._stateFlags & u.PENDING) !== 0;
|
|
764
1189
|
}
|
|
765
1190
|
_setPending() {
|
|
766
|
-
this._stateFlags |=
|
|
1191
|
+
this._stateFlags |= u.PENDING, this._stateFlags &= -27;
|
|
767
1192
|
}
|
|
768
1193
|
_isResolved() {
|
|
769
|
-
return (this._stateFlags &
|
|
1194
|
+
return (this._stateFlags & u.RESOLVED) !== 0;
|
|
770
1195
|
}
|
|
771
1196
|
_setResolved() {
|
|
772
|
-
this._stateFlags |=
|
|
1197
|
+
this._stateFlags |= u.RESOLVED, this._stateFlags &= -87;
|
|
773
1198
|
}
|
|
774
1199
|
_isRejected() {
|
|
775
|
-
return (this._stateFlags &
|
|
1200
|
+
return (this._stateFlags & u.REJECTED) !== 0;
|
|
776
1201
|
}
|
|
777
1202
|
_setRejected() {
|
|
778
|
-
this._stateFlags |=
|
|
1203
|
+
this._stateFlags |= u.REJECTED | u.HAS_ERROR, this._stateFlags &= -15;
|
|
779
1204
|
}
|
|
780
1205
|
_isRecomputing() {
|
|
781
|
-
return (this._stateFlags &
|
|
1206
|
+
return (this._stateFlags & u.RECOMPUTING) !== 0;
|
|
782
1207
|
}
|
|
783
1208
|
_setRecomputing(e) {
|
|
784
|
-
e ? this._stateFlags |=
|
|
1209
|
+
e ? this._stateFlags |= u.RECOMPUTING : this._stateFlags &= -33;
|
|
785
1210
|
}
|
|
786
1211
|
_getAsyncState() {
|
|
787
1212
|
return this._isPending() ? S.PENDING : this._isResolved() ? S.RESOLVED : this._isRejected() ? S.REJECTED : S.IDLE;
|
|
@@ -830,29 +1255,29 @@ class L {
|
|
|
830
1255
|
this._value = e, this._clearDirty(), this._setResolved(), this._error = null, this._setRecomputing(!1), t && this._notifySubscribers();
|
|
831
1256
|
}
|
|
832
1257
|
_handleAsyncRejection(e) {
|
|
833
|
-
const t = E(e, d,
|
|
1258
|
+
const t = E(e, d, o.COMPUTED_ASYNC_COMPUTATION_FAILED);
|
|
834
1259
|
if (this._error = t, this._setRejected(), this._clearDirty(), this._setRecomputing(!1), this._onError && typeof this._onError == "function")
|
|
835
1260
|
try {
|
|
836
1261
|
this._onError(t);
|
|
837
1262
|
} catch (s) {
|
|
838
|
-
console.error(
|
|
1263
|
+
console.error(o.CALLBACK_ERROR_IN_ERROR_HANDLER, s);
|
|
839
1264
|
}
|
|
840
1265
|
this._notifySubscribers();
|
|
841
1266
|
}
|
|
842
1267
|
_handleComputationError(e) {
|
|
843
|
-
const t = E(e, d,
|
|
1268
|
+
const t = E(e, d, o.COMPUTED_COMPUTATION_FAILED);
|
|
844
1269
|
if (this._error = t, this._setRejected(), this._clearDirty(), this._setRecomputing(!1), this._onError && typeof this._onError == "function")
|
|
845
1270
|
try {
|
|
846
1271
|
this._onError(t);
|
|
847
1272
|
} catch (s) {
|
|
848
|
-
console.error(
|
|
1273
|
+
console.error(o.CALLBACK_ERROR_IN_ERROR_HANDLER, s);
|
|
849
1274
|
}
|
|
850
1275
|
throw t;
|
|
851
1276
|
}
|
|
852
1277
|
_handlePending() {
|
|
853
1278
|
if (this._hasDefaultValue)
|
|
854
1279
|
return this._defaultValue;
|
|
855
|
-
throw new d(
|
|
1280
|
+
throw new d(o.COMPUTED_ASYNC_PENDING_NO_DEFAULT);
|
|
856
1281
|
}
|
|
857
1282
|
_handleRejected() {
|
|
858
1283
|
if (this._error?.recoverable && this._hasDefaultValue)
|
|
@@ -892,7 +1317,7 @@ class L {
|
|
|
892
1317
|
_addDependency(e) {
|
|
893
1318
|
h.checkCircular(e, this);
|
|
894
1319
|
const t = this._dependencyManager.count;
|
|
895
|
-
h.warn(t > h.maxDependencies,
|
|
1320
|
+
h.warn(t > h.maxDependencies, o.LARGE_DEPENDENCY_GRAPH(t));
|
|
896
1321
|
try {
|
|
897
1322
|
const s = e.subscribe(() => this._markDirty());
|
|
898
1323
|
this._dependencyManager.addDependency(e, s);
|
|
@@ -902,7 +1327,7 @@ class L {
|
|
|
902
1327
|
}
|
|
903
1328
|
// === PRIVATE: Subscriber Management ===
|
|
904
1329
|
_markDirty() {
|
|
905
|
-
this._isRecomputing() || this._isDirty() || (this._setDirty(), this._setIdle(), (this._functionSubscribers.hasSubscribers || this._objectSubscribers.hasSubscribers) &&
|
|
1330
|
+
this._isRecomputing() || this._isDirty() || (this._setDirty(), this._setIdle(), (this._functionSubscribers.hasSubscribers || this._objectSubscribers.hasSubscribers) && D.schedule(() => {
|
|
906
1331
|
if (this._isDirty())
|
|
907
1332
|
try {
|
|
908
1333
|
this._recompute();
|
|
@@ -911,7 +1336,7 @@ class L {
|
|
|
911
1336
|
}));
|
|
912
1337
|
}
|
|
913
1338
|
_notifySubscribers() {
|
|
914
|
-
!this._functionSubscribers.hasSubscribers && !this._objectSubscribers.hasSubscribers ||
|
|
1339
|
+
!this._functionSubscribers.hasSubscribers && !this._objectSubscribers.hasSubscribers || D.schedule(() => {
|
|
915
1340
|
this._functionSubscribers.forEachSafe(
|
|
916
1341
|
(e) => e(),
|
|
917
1342
|
(e) => console.error(e)
|
|
@@ -926,13 +1351,13 @@ class L {
|
|
|
926
1351
|
e && (typeof e == "function" ? this._functionSubscribers.add(e) : e.addDependency ? e.addDependency(this) : e.execute && this._objectSubscribers.add(e));
|
|
927
1352
|
}
|
|
928
1353
|
}
|
|
929
|
-
function
|
|
930
|
-
return new
|
|
1354
|
+
function z(i, e = {}) {
|
|
1355
|
+
return new k(i, e);
|
|
931
1356
|
}
|
|
932
1357
|
function O(i) {
|
|
933
1358
|
return i !== null && typeof i == "object" && "value" in i && "subscribe" in i && typeof i.subscribe == "function";
|
|
934
1359
|
}
|
|
935
|
-
function
|
|
1360
|
+
function q(i) {
|
|
936
1361
|
if (h.enabled) {
|
|
937
1362
|
const e = h.getDebugType(i);
|
|
938
1363
|
if (e)
|
|
@@ -940,14 +1365,39 @@ function z(i) {
|
|
|
940
1365
|
}
|
|
941
1366
|
return O(i) && "invalidate" in i && typeof i.invalidate == "function";
|
|
942
1367
|
}
|
|
943
|
-
function
|
|
1368
|
+
function Y(i) {
|
|
944
1369
|
return i !== null && typeof i == "object" && "dispose" in i && "run" in i && typeof i.dispose == "function" && typeof i.run == "function";
|
|
945
1370
|
}
|
|
946
|
-
class
|
|
1371
|
+
class B {
|
|
1372
|
+
/**
|
|
1373
|
+
* Creates a new EffectImpl instance.
|
|
1374
|
+
*
|
|
1375
|
+
* @param fn - The effect function to execute. May return a cleanup function
|
|
1376
|
+
* or a Promise that resolves to a cleanup function.
|
|
1377
|
+
* @param options - Configuration options for the effect
|
|
1378
|
+
* @param options.sync - If true, re-executes synchronously on dependency changes.
|
|
1379
|
+
* Defaults to false (scheduled execution).
|
|
1380
|
+
* @param options.maxExecutionsPerSecond - Maximum executions per second before
|
|
1381
|
+
* infinite loop detection triggers.
|
|
1382
|
+
* Defaults to SCHEDULER_CONFIG.MAX_EXECUTIONS_PER_SECOND.
|
|
1383
|
+
* @param options.trackModifications - If true, tracks and warns about dependencies
|
|
1384
|
+
* that are both read and modified. Defaults to false.
|
|
1385
|
+
*
|
|
1386
|
+
* @example
|
|
1387
|
+
* ```typescript
|
|
1388
|
+
* const impl = new EffectImpl(
|
|
1389
|
+
* () => {
|
|
1390
|
+
* console.log(counter.value);
|
|
1391
|
+
* return () => console.log('cleanup');
|
|
1392
|
+
* },
|
|
1393
|
+
* { sync: true }
|
|
1394
|
+
* );
|
|
1395
|
+
* ```
|
|
1396
|
+
*/
|
|
947
1397
|
constructor(e, t = {}) {
|
|
948
1398
|
this.run = () => {
|
|
949
1399
|
if (this.isDisposed)
|
|
950
|
-
throw new b(
|
|
1400
|
+
throw new b(o.EFFECT_MUST_BE_FUNCTION);
|
|
951
1401
|
this.execute();
|
|
952
1402
|
}, this.dispose = () => {
|
|
953
1403
|
this.isDisposed || (this._setDisposed(), this._safeCleanup(), this._depManager.unsubscribeAll(), this._trackedDeps.size > 0 && (this._trackedDeps.forEach((s) => {
|
|
@@ -962,11 +1412,11 @@ class k {
|
|
|
962
1412
|
}, this.addDependency = (s) => {
|
|
963
1413
|
try {
|
|
964
1414
|
const r = s.subscribe(() => {
|
|
965
|
-
this._sync ? this.execute() :
|
|
1415
|
+
this._sync ? this.execute() : D.schedule(this.execute);
|
|
966
1416
|
});
|
|
967
1417
|
this._depManager.addDependency(s, r), this._trackModifications && O(s) && this._trackModificationsForDep(s);
|
|
968
1418
|
} catch (r) {
|
|
969
|
-
throw E(r, b,
|
|
1419
|
+
throw E(r, b, o.EFFECT_EXECUTION_FAILED);
|
|
970
1420
|
}
|
|
971
1421
|
}, this.execute = () => {
|
|
972
1422
|
if (this.isDisposed || this.isExecuting) return;
|
|
@@ -977,40 +1427,145 @@ class k {
|
|
|
977
1427
|
this._checkLoopWarnings(), A(r) ? r.then((c) => {
|
|
978
1428
|
!this.isDisposed && typeof c == "function" && (this._cleanup = c);
|
|
979
1429
|
}).catch((c) => {
|
|
980
|
-
console.error(E(c, b,
|
|
1430
|
+
console.error(E(c, b, o.EFFECT_EXECUTION_FAILED));
|
|
981
1431
|
}) : this._cleanup = typeof r == "function" ? r : null;
|
|
982
1432
|
} catch (r) {
|
|
983
|
-
console.error(E(r, b,
|
|
1433
|
+
console.error(E(r, b, o.EFFECT_EXECUTION_FAILED)), this._cleanup = null;
|
|
984
1434
|
} finally {
|
|
985
1435
|
this._setExecuting(!1);
|
|
986
1436
|
}
|
|
987
1437
|
}, this._fn = e, this._sync = t.sync ?? !1, this._maxExecutions = t.maxExecutionsPerSecond ?? U.MAX_EXECUTIONS_PER_SECOND, this._trackModifications = t.trackModifications ?? !1, this._id = I(), this._flags = 0, this._cleanup = null, this._depManager = new F(), this._modifiedDeps = /* @__PURE__ */ new Set(), this._originalDescriptors = /* @__PURE__ */ new WeakMap(), this._trackedDeps = /* @__PURE__ */ new Set(), this._historyCapacity = this._maxExecutions + 5, this._history = new Float64Array(this._historyCapacity), this._historyIdx = 0, this._historyCount = 0, this._executionCount = 0, h.attachDebugInfo(this, "effect", this._id);
|
|
988
1438
|
}
|
|
1439
|
+
/**
|
|
1440
|
+
* Indicates whether this effect has been disposed.
|
|
1441
|
+
*
|
|
1442
|
+
* @returns `true` if the effect has been disposed, `false` otherwise
|
|
1443
|
+
*
|
|
1444
|
+
* @remarks
|
|
1445
|
+
* A disposed effect will not execute and cannot be reactivated.
|
|
1446
|
+
* Use this property to check if the effect is still active before
|
|
1447
|
+
* performing operations that depend on it.
|
|
1448
|
+
*
|
|
1449
|
+
* @example
|
|
1450
|
+
* ```typescript
|
|
1451
|
+
* const fx = effect(() => console.log(counter.value));
|
|
1452
|
+
* console.log(fx.isDisposed); // false
|
|
1453
|
+
* fx.dispose();
|
|
1454
|
+
* console.log(fx.isDisposed); // true
|
|
1455
|
+
* ```
|
|
1456
|
+
*/
|
|
989
1457
|
get isDisposed() {
|
|
990
1458
|
return (this._flags & y.DISPOSED) !== 0;
|
|
991
1459
|
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Returns the total number of times this effect has been executed.
|
|
1462
|
+
*
|
|
1463
|
+
* @returns The cumulative execution count since the effect was created
|
|
1464
|
+
*
|
|
1465
|
+
* @remarks
|
|
1466
|
+
* This counter is useful for debugging, testing, and monitoring
|
|
1467
|
+
* effect behavior. It increments on every execution, regardless
|
|
1468
|
+
* of whether the execution succeeds or fails.
|
|
1469
|
+
*
|
|
1470
|
+
* @example
|
|
1471
|
+
* ```typescript
|
|
1472
|
+
* const fx = effect(() => console.log(counter.value));
|
|
1473
|
+
* console.log(fx.executionCount); // 1 (initial execution)
|
|
1474
|
+
* counter.value = 10;
|
|
1475
|
+
* console.log(fx.executionCount); // 2
|
|
1476
|
+
* ```
|
|
1477
|
+
*/
|
|
992
1478
|
get executionCount() {
|
|
993
1479
|
return this._executionCount;
|
|
994
1480
|
}
|
|
1481
|
+
/**
|
|
1482
|
+
* Indicates whether this effect is currently executing.
|
|
1483
|
+
*
|
|
1484
|
+
* @returns `true` if the effect is mid-execution, `false` otherwise
|
|
1485
|
+
*
|
|
1486
|
+
* @remarks
|
|
1487
|
+
* This property is used internally to prevent re-entrant execution
|
|
1488
|
+
* (an effect triggering itself during its own execution). It can
|
|
1489
|
+
* also be useful for debugging to understand the effect's state.
|
|
1490
|
+
*
|
|
1491
|
+
* @example
|
|
1492
|
+
* ```typescript
|
|
1493
|
+
* const fx = effect(() => {
|
|
1494
|
+
* console.log('executing:', fx.isExecuting); // true
|
|
1495
|
+
* });
|
|
1496
|
+
* console.log(fx.isExecuting); // false (after execution completes)
|
|
1497
|
+
* ```
|
|
1498
|
+
*/
|
|
995
1499
|
get isExecuting() {
|
|
996
1500
|
return (this._flags & y.EXECUTING) !== 0;
|
|
997
1501
|
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Sets the disposed flag on this effect.
|
|
1504
|
+
*
|
|
1505
|
+
* @remarks
|
|
1506
|
+
* This is a low-level method that only sets the bit flag.
|
|
1507
|
+
* Use the public `dispose()` method for proper cleanup.
|
|
1508
|
+
*
|
|
1509
|
+
* @internal
|
|
1510
|
+
*/
|
|
998
1511
|
_setDisposed() {
|
|
999
1512
|
this._flags |= y.DISPOSED;
|
|
1000
1513
|
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Sets or clears the executing flag on this effect.
|
|
1516
|
+
*
|
|
1517
|
+
* @param value - `true` to mark as executing, `false` to clear
|
|
1518
|
+
*
|
|
1519
|
+
* @remarks
|
|
1520
|
+
* Uses bitwise operations for efficient flag manipulation.
|
|
1521
|
+
* This flag prevents re-entrant execution of the effect.
|
|
1522
|
+
*
|
|
1523
|
+
* @internal
|
|
1524
|
+
*/
|
|
1001
1525
|
_setExecuting(e) {
|
|
1002
1526
|
e ? this._flags |= y.EXECUTING : this._flags &= -3;
|
|
1003
1527
|
}
|
|
1528
|
+
/**
|
|
1529
|
+
* Safely executes the cleanup function if one exists.
|
|
1530
|
+
*
|
|
1531
|
+
* @remarks
|
|
1532
|
+
* This method:
|
|
1533
|
+
* - Checks if a cleanup function exists and is callable
|
|
1534
|
+
* - Wraps the cleanup call in a try-catch to prevent cleanup errors
|
|
1535
|
+
* from breaking the effect lifecycle
|
|
1536
|
+
* - Logs any cleanup errors to the console
|
|
1537
|
+
* - Clears the cleanup reference after execution
|
|
1538
|
+
*
|
|
1539
|
+
* @internal
|
|
1540
|
+
*/
|
|
1004
1541
|
_safeCleanup() {
|
|
1005
1542
|
if (this._cleanup && typeof this._cleanup == "function") {
|
|
1006
1543
|
try {
|
|
1007
1544
|
this._cleanup();
|
|
1008
1545
|
} catch (e) {
|
|
1009
|
-
console.error(E(e, b,
|
|
1546
|
+
console.error(E(e, b, o.EFFECT_CLEANUP_FAILED));
|
|
1010
1547
|
}
|
|
1011
1548
|
this._cleanup = null;
|
|
1012
1549
|
}
|
|
1013
1550
|
}
|
|
1551
|
+
/**
|
|
1552
|
+
* Records an execution timestamp and checks for infinite loop conditions.
|
|
1553
|
+
*
|
|
1554
|
+
* @param now - The current timestamp in milliseconds (from `Date.now()`)
|
|
1555
|
+
*
|
|
1556
|
+
* @remarks
|
|
1557
|
+
* This method implements a circular buffer to track recent execution
|
|
1558
|
+
* timestamps. If the number of executions within the last second exceeds
|
|
1559
|
+
* `_maxExecutions`, the effect is disposed and an error is thrown (in debug mode)
|
|
1560
|
+
* or logged (in production mode).
|
|
1561
|
+
*
|
|
1562
|
+
* The circular buffer approach provides O(1) insertion and efficient
|
|
1563
|
+
* memory usage for tracking execution history.
|
|
1564
|
+
*
|
|
1565
|
+
* @throws {EffectError} In debug mode, throws when infinite loop is detected
|
|
1566
|
+
*
|
|
1567
|
+
* @internal
|
|
1568
|
+
*/
|
|
1014
1569
|
_recordExecution(e) {
|
|
1015
1570
|
if (this._maxExecutions <= 0) return;
|
|
1016
1571
|
const t = e - 1e3;
|
|
@@ -1024,6 +1579,21 @@ class k {
|
|
|
1024
1579
|
throw n;
|
|
1025
1580
|
}
|
|
1026
1581
|
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Sets up modification tracking for a dependency.
|
|
1584
|
+
*
|
|
1585
|
+
* @param dep - The dependency (atom) to track modifications on
|
|
1586
|
+
*
|
|
1587
|
+
* @remarks
|
|
1588
|
+
* This method intercepts the `value` setter on the dependency to detect
|
|
1589
|
+
* when the effect modifies a dependency it also reads. This pattern
|
|
1590
|
+
* (read-after-write within the same effect) often indicates an infinite loop.
|
|
1591
|
+
*
|
|
1592
|
+
* The original property descriptor is preserved and can be restored
|
|
1593
|
+
* when the effect is disposed.
|
|
1594
|
+
*
|
|
1595
|
+
* @internal
|
|
1596
|
+
*/
|
|
1027
1597
|
_trackModificationsForDep(e) {
|
|
1028
1598
|
const t = Object.getPrototypeOf(e), s = Object.getOwnPropertyDescriptor(t, "value");
|
|
1029
1599
|
if (s?.set && !this._originalDescriptors.has(e)) {
|
|
@@ -1041,6 +1611,19 @@ class k {
|
|
|
1041
1611
|
});
|
|
1042
1612
|
}
|
|
1043
1613
|
}
|
|
1614
|
+
/**
|
|
1615
|
+
* Checks for and warns about potential infinite loop patterns.
|
|
1616
|
+
*
|
|
1617
|
+
* @remarks
|
|
1618
|
+
* When modification tracking is enabled and debug mode is active,
|
|
1619
|
+
* this method checks if any dependencies were both read and modified
|
|
1620
|
+
* during the effect execution. Such patterns often lead to infinite loops.
|
|
1621
|
+
*
|
|
1622
|
+
* Warnings are only emitted in debug mode to avoid performance overhead
|
|
1623
|
+
* in production.
|
|
1624
|
+
*
|
|
1625
|
+
* @internal
|
|
1626
|
+
*/
|
|
1044
1627
|
_checkLoopWarnings() {
|
|
1045
1628
|
if (this._trackModifications && h.enabled) {
|
|
1046
1629
|
const e = this._depManager.getDependencies();
|
|
@@ -1054,10 +1637,10 @@ class k {
|
|
|
1054
1637
|
}
|
|
1055
1638
|
}
|
|
1056
1639
|
}
|
|
1057
|
-
function
|
|
1640
|
+
function Q(i, e = {}) {
|
|
1058
1641
|
if (typeof i != "function")
|
|
1059
|
-
throw new b(
|
|
1060
|
-
const t = new
|
|
1642
|
+
throw new b(o.EFFECT_MUST_BE_FUNCTION);
|
|
1643
|
+
const t = new B(i, e);
|
|
1061
1644
|
return t.execute(), t;
|
|
1062
1645
|
}
|
|
1063
1646
|
export {
|
|
@@ -1067,17 +1650,17 @@ export {
|
|
|
1067
1650
|
m as DEBUG_CONFIG,
|
|
1068
1651
|
h as DEBUG_RUNTIME,
|
|
1069
1652
|
b as EffectError,
|
|
1070
|
-
|
|
1653
|
+
j as POOL_CONFIG,
|
|
1071
1654
|
U as SCHEDULER_CONFIG,
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1655
|
+
g as SchedulerError,
|
|
1656
|
+
X as atom,
|
|
1657
|
+
G as batch,
|
|
1658
|
+
z as computed,
|
|
1659
|
+
Q as effect,
|
|
1077
1660
|
O as isAtom,
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1661
|
+
q as isComputed,
|
|
1662
|
+
Y as isEffect,
|
|
1663
|
+
D as scheduler,
|
|
1664
|
+
V as untracked
|
|
1082
1665
|
};
|
|
1083
1666
|
//# sourceMappingURL=index.mjs.map
|