···11+/**
22+ * bitmask flags for computation states
33+ */
14const enum Flags {
55+ // context flags
26 RUNNING = 1 << 0,
37 DIRTY = 1 << 1,
48 MAYBE_DIRTY = 1 << 2,
···10141115type Computation = Computed<any> | Effect<any>;
12161313-/** @internal currently evaluating listener */
1717+/**
1818+ * @internal
1919+ * the currently evaluating computation
2020+ */
1421export let eval_listener: Computation | undefined;
1515-/** @internal currently evaluating effect */
1616-export let eval_effect: Effect<any> | undefined;
2222+/**
2323+ * the currently evaluating effect
2424+ */
2525+let eval_effect: Effect<any> | undefined;
17261818-/** pointer for checking existing dependencies in a context */
2727+/**
2828+ * pointer for the currently evaluating computation's sources, used for tracking
2929+ */
1930let eval_sources_index: number = 0;
2020-/** array of new dependencies to said context */
3131+/**
3232+ * array of new dependencies for the currently evaluating computation
3333+ */
2134let eval_untracked_sources: Signal[] | undefined;
22352323-/** effect scheduled for batching */
3636+/**
3737+ * the effect that is scheduled for batching
3838+ */
2439let batched_effect: Effect<any> | undefined;
2525-/** current batch depth */
4040+/**
4141+ * the current batch depth, used for nested batches
4242+ */
2643let batch_depth: number = 0;
2727-/** current batch iteration */
4444+/**
4545+ * the current batch iteration, used to prevent infinite loops
4646+ */
2847let batch_iteration: number = 0;
29483049/**
3131- * Writing to a signal ticks this write clock forward and stores the new epoch
3232- * into that signal, this is used for contexts to know if they're stale by
3333- * comparing the epoch between it and its dependencies.
5050+ * a global clock that is incremented every time a signal is written to.
5151+ * this is used to determine if a computation is stale.
3452 */
3553let write_clock = 0;
36543755/**
3838- * Running a context ticks this read clock forward and stores the new epoch
3939- * into that context, this is used for duplicate-checking to see if the signal
4040- * has already been read by the context.
4141- *
4242- * Oversubscription isn't a big deal at all so really this isn't actually
4343- * needed, a duplicate dependency wouldn't cause any harm as we have a flag for
4444- * checking whether the context has been already been notified.
5656+ * a global clock that is incremented every time a computation is run.
5757+ * this is used to track dependencies.
4558 */
4659let read_clock = 0;
4760···116129 let dependencies = eval_listener!._dependencies;
117130118131 if (eval_untracked_sources) {
119119- // We have new dependencies, so let's unsubscribe from stale dependencies.
132132+ // we have new dependencies, so let's unsubscribe from stale dependencies.
120133 prune_old_dependencies();
121134122135 if (eval_sources_index > 0) {
123123- // We have existing dependencies still depended on, so let's expand the
136136+ // we have existing dependencies still depended on, so let's expand the
124137 // existing array to make room for our new dependencies.
125138 const ilen = eval_untracked_sources.length;
126139127140 dependencies.length = eval_sources_index + ilen;
128141129129- // Override anything after the pointer with our new dependencies, this is
142142+ // override anything after the pointer with our new dependencies, this is
130143 // fine since we're no longer subscribed to them.
131144 for (let i = 0; i < ilen; i++) {
132145 dependencies[eval_sources_index + i] = eval_untracked_sources[i];
133146 }
134147 } else {
135135- // There isn't any existing dependencies, so just replace the existing
148148+ // there isn't any existing dependencies, so just replace the existing
136149 // array with the new one.
137150 dependencies = eval_listener!._dependencies = eval_untracked_sources;
138151 }
139152140140- // Now we subscribe to the new dependencies, but only if we're currently
153153+ // now we subscribe to the new dependencies, but only if we're currently
141154 // configured as tracking.
142155 if (eval_listener!._flags & Flags.TRACKING) {
143156 for (let i = eval_sources_index, ilen = dependencies.length; i < ilen; i++) {
···147160 }
148161 }
149162 } else if (eval_sources_index < eval_listener!._dependencies.length) {
150150- // We don't have new dependencies, but the index pointer isn't pointing to
163163+ // we don't have new dependencies, but the index pointer isn't pointing to
151164 // the end of the array, so we need to clean up the rest.
152165 prune_old_dependencies();
153166 dependencies.length = eval_sources_index;
···178191179192 try {
180193 for (let i = 0; i < cleanups.length; i++) {
181181- (0, cleanups[i])();
194194+ cleanups[i]!();
182195 }
183196 } catch (err) {
184184- // Failed to clean, so let's dispose of this effect.
197197+ // failed to clean, so let's dispose of this effect.
185198 effect._flags = (effect._flags & ~Flags.RUNNING) | Flags.DISPOSED;
186199 dispose_effect(effect, false);
187200···211224}
212225213226export class Signal<T = unknown> {
214214- /** @internal Stored time of the write clock */
227227+ /**
228228+ * @internal
229229+ * the last time this signal was written to.
230230+ * this is a timestamp from the global `write_clock`.
231231+ */
215232 _epoch = -1;
216216- /** @internal Stored time of the read clock, used to detect dupe-reads */
233233+ /**
234234+ * @internal
235235+ * the last time this signal was read by a computation.
236236+ * this is a timestamp from the global `read_clock`.
237237+ */
217238 _access_epoch = -1;
218218- /** @internal Contexts depending on it */
239239+ /**
240240+ * @internal
241241+ * the computations that depend on this signal.
242242+ */
219243 _dependants: Computation[] = [];
220244221221- /** @internal stored value */
245245+ /**
246246+ * @internal
247247+ * the current value of the signal.
248248+ */
222249 _value: T;
223250224251 constructor(value: T) {
···227254228255 /**
229256 * @internal
230230- * Contexts will call this function to check if perhaps its dependencies are
231231- * stale, this is mostly needed for computed signals though, for a signal,
232232- * there's nothing new to be had once this method is called.
257257+ * this method is called by computations to check if they are stale.
258258+ * for a signal, this always returns false.
233259 */
234260 _refresh(): boolean {
235235- // Returning `true` indicates that the dependant is now stale as a result,
236236- // e.g. computation has returned a different value. This just prevents the
261261+ // returning `true` indicates that the dependant is now stale as a result,
262262+ // e.g. computation has returned a different value. this just prevents the
237263 // need to check the epoch again so the epoch still needs to be incremented.
238264 return false;
239265 }
240266241241- /** @internal */
267267+ /**
268268+ * @internal
269269+ * subscribes a computation to this signal.
270270+ * @param target the computation to subscribe
271271+ */
242272 _subscribe(target: Computation): void {
243273 this._dependants.push(target);
244274 }
245275246246- /** @internal */
276276+ /**
277277+ * @internal
278278+ * unsubscribes a computation from this signal.
279279+ * @param target the computation to unsubscribe
280280+ */
247281 _unsubscribe(target: Computation): void {
248282 const dependants = this._dependants;
249283 const index = dependants.indexOf(target);
···252286 }
253287254288 /**
255255- * Retrieves the value without being tracked as a dependant.
289289+ * retrieves the value of the signal without creating a dependency.
290290+ * @returns the current value
256291 */
257292 peek(): T {
258293 return this._value;
259294 }
260295261296 /**
262262- * Retrieves the value, tracks the currently-running effect as a dependant.
297297+ * retrieves the value of the signal and creates a dependency if inside a computation.
263298 */
264299 get value(): T {
265265- // Check if we're running under a context
300300+ // check if we're running under a context
266301 if (eval_listener !== undefined && eval_listener._context_epoch !== this._access_epoch) {
267267- // Store the read epoch, we don't need to recheck the dependencies of
302302+ // store the read epoch, we don't need to recheck the dependencies of
268303 // this effect.
269304 this._access_epoch = eval_listener._context_epoch;
270305271271- // Dependency tracking is simple: does the index pointer point to us?
306306+ // dependency tracking is simple: does the index pointer point to us?
272307 //
273273- // - If so, then we're already depended on and we can increment the
308308+ // - if so, then we're already depended on and we can increment the
274309 // pointer for the next signal.
275310 //
276276- // - If not, then we need to create a new dependency array and stop
311311+ // - if not, then we need to create a new dependency array and stop
277312 // incrementing the pointer, now that pointer acts as a dividing line
278313 // between signals that are still being depended, and are no longer
279279- // depended upon. The new dependencies can be concatenated afterwards.
314314+ // depended upon. the new dependencies can be concatenated afterwards.
280315281316 if (eval_untracked_sources !== undefined) {
282317 eval_untracked_sources.push(this);
···289324290325 return this._value;
291326 }
327327+292328 set value(next: T) {
293329 if (this._value !== next) {
294294- // Tick the write clock forward
330330+ // tick the write clock forward
295331 this._epoch = ++write_clock;
296332 this._value = next;
297333···303339 for (let i = 0, ilen = dependants.length; i < ilen; i++) {
304340 const dep = dependants[i];
305341306306- // Source signal dependants are guaranteed to be dirty.
342342+ // source signal dependants are guaranteed to be dirty.
307343 dep._notify(Flags.DIRTY);
308344 }
309345···317353 readonly value: T;
318354}
319355320320-/** @internal */
321356export class Computed<T = unknown> extends Signal<T> {
322357 /**
323358 * @internal
324324- * Signals it's depending on
359359+ * the signals that this computation depends on.
325360 */
326361 _dependencies: Signal[] = [];
327362 /**
328363 * @internal
329329- * Context flags
364364+ * the current state of the computation.
330365 */
331366 _flags: number = Flags.DIRTY;
367367+332368 /**
333369 * @internal
334334- * This is mostly used for computed signals that currently aren't being
335335- * subscribed to, if nothing in the realm has changed, then it shouldn't be
336336- * possible for this computed signal to be stale from its dependencies.
370370+ * the last time this computation was updated.
371371+ * this is a timestamp from the global `write_clock`.
337372 */
338373 _realm_write_epoch = -1;
339374 /**
340375 * @internal
341341- * Stored time of the read clock
376376+ * the last time this computation was run.
377377+ * this is a timestamp from the global `read_clock`.
342378 */
343379 _context_epoch = -1;
344380345381 /**
346382 * @internal
347347- * Compute function used to retrieve the value for this computed signal
383383+ * the function that computes the value of this signal.
348384 */
349385 _compute: (prev: T) => T;
350386···354390 this._compute = compute;
355391 }
356392357357- /** @internal */
393393+ /**
394394+ * @internal
395395+ * re-runs the computation if it's stale and updates its value.
396396+ * @returns `true` if the value changed, `false` otherwise.
397397+ */
358398 override _refresh(): boolean {
359359- // Retrieve the current flags, make sure to unset NOTIFIED now that we're
399399+ // retrieve the current flags, make sure to unset NOTIFIED now that we're
360400 // running this refresh.
361401 const flags = (this._flags &= ~Flags.NOTIFIED);
362402363403 if (
364364- // If our stored time matches current time then nothing in the realm has changed
404404+ // if our stored time matches current time then nothing in the realm has changed
365405 this._realm_write_epoch === write_clock ||
366366- // If we're tracking, we can make use of DIRTY and MAYBE_DIRTY
406406+ // if we're tracking, we can make use of DIRTY and MAYBE_DIRTY
367407 (flags & (Flags.TRACKING | Flags.DIRTY | Flags.MAYBE_DIRTY)) === Flags.TRACKING ||
368368- // Prevent self-referential checks
408408+ // prevent self-referential checks
369409 flags & Flags.RUNNING
370410 ) {
371411 return false;
···404444 this._epoch = this._realm_write_epoch = ++write_clock;
405445 }
406446 } catch (err) {
407407- // Always mark errors as stale
447447+ // always mark errors as stale
408448 stale = true;
409449410450 this._value = err as T;
···422462 return stale;
423463 }
424464425425- /** @internal */
465465+ /**
466466+ * @internal
467467+ * subscribes a computation to this signal.
468468+ * @param target the computation to subscribe
469469+ */
426470 override _subscribe(target: Computation): void {
427427- // Subscribe to our sources now that we have someone subscribing on us
471471+ // subscribe to our sources now that we have someone subscribing on us
428472 if (this._dependants.length < 1) {
429473 const dependencies = this._dependencies;
430474 this._flags |= Flags.TRACKING;
···438482 super._subscribe(target);
439483 }
440484441441- /** @internal */
485485+ /**
486486+ * @internal
487487+ * unsubscribes a computation from this signal.
488488+ * @param target the computation to unsubscribe
489489+ */
442490 override _unsubscribe(target: Computation): void {
443491 super._unsubscribe(target);
444492445445- // Unsubscribe from our sources since there's no one subscribing to us
493493+ // unsubscribe from our sources since there's no one subscribing to us
446494 if (this._dependants.length < 1) {
447495 const dependencies = this._dependencies;
448496 this._flags &= ~Flags.TRACKING;
···454502 }
455503 }
456504457457- /** @internal */
505505+ /**
506506+ * @internal
507507+ * notifies all dependents that this signal has changed.
508508+ * @param flag the type of change
509509+ */
458510 _notify(flag: Flags.DIRTY | Flags.MAYBE_DIRTY): void {
459511 if (!(this._flags & (Flags.NOTIFIED | Flags.RUNNING))) {
460512 const dependants = this._dependants;
···464516 for (let i = 0, ilen = dependants.length; i < ilen; i++) {
465517 const dep = dependants[i];
466518467467- // Computed signal dependants aren't guaranteed to be dirty.
519519+ // computed signal dependants aren't guaranteed to be dirty.
468520 dep._notify(Flags.MAYBE_DIRTY);
469521 }
470522 }
471523 }
472524473525 /**
474474- * Retrieves the value without being tracked as a dependant
526526+ * retrieves the value of the signal without creating a dependency.
527527+ * this will re-run the computation if it's stale.
528528+ * @returns the current value
475529 */
476530 override peek(): T {
477531 this._refresh();
···484538 }
485539486540 /**
487487- * Retrieves the value, tracks the currently-running effect as a dependant
541541+ * retrieves the value of the signal and creates a dependency if inside a computation.
542542+ * this will re-run the computation if it's stale.
488543 */
489544 override get value(): T {
490545 this._refresh();
···497552 }
498553}
499554500500-/** @internal */
555555+/**
556556+ * @internal
557557+ * a side-effect that is run when a signal it depends on changes.
558558+ * @template T the type of the value
559559+ */
501560export class Effect<T = void> {
502502- /** @internal Stored time of the write clock */
561561+ /**
562562+ * @internal
563563+ * the last time this effect was run.
564564+ * this is a timestamp from the global `write_clock`.
565565+ */
503566 _epoch = -1;
504504- /** @internal Stored time of the read clock */
567567+ /**
568568+ * @internal
569569+ * the last time this effect was run.
570570+ * this is a timestamp from the global `read_clock`.
571571+ */
505572 _context_epoch = -1;
506506- /** @internal Signals it's depending on */
573573+ /**
574574+ * @internal
575575+ * the signals that this effect depends on.
576576+ */
507577 _dependencies: Signal[] = [];
508508- /** @internal Context flags */
578578+ /**
579579+ * @internal
580580+ * the current state of the effect.
581581+ */
509582 _flags = Flags.TRACKING;
510583511511- /** @internal Registered cleanup functions */
584584+ /**
585585+ * @internal
586586+ * the cleanup functions for this effect.
587587+ */
512588 _cleanups?: (() => void)[];
513513- /** @internal Compute function for this effect */
589589+ /**
590590+ * @internal
591591+ * the function that runs the effect.
592592+ */
514593 _compute: () => unknown;
515515- /** @internal Stored value from this compute */
594594+ /**
595595+ * @internal
596596+ * the value returned by the effect.
597597+ */
516598 _value!: T;
517599518518- /** @internal Batched effects are queued by a linked list on itself */
600600+ /**
601601+ * @internal
602602+ * the next effect in the batch queue.
603603+ */
519604 _next_batched_effect: Effect | undefined;
520605521606 constructor(compute: () => unknown) {
522607 this._compute = compute;
523608 }
524609525525- /** @internal */
610610+ /**
611611+ * @internal
612612+ * starts the effect.
613613+ * @returns a function to finish the effect
614614+ */
526615 _start(): (() => void) | undefined {
527616 const flags = this._flags;
528617···564653 };
565654 }
566655567567- /** @internal */
656656+ /**
657657+ * @internal
658658+ * re-runs the effect if it's stale.
659659+ */
568660 _refresh(): void {
569661 const finish = this._start();
570662···579671 }
580672 }
581673582582- /** @internal */
674674+ /**
675675+ * @internal
676676+ * notifies the effect that one of its dependencies has changed.
677677+ * @param flag the type of change
678678+ */
583679 _notify(flag: Flags.DIRTY | Flags.MAYBE_DIRTY): void {
584680 if (!(this._flags & (Flags.NOTIFIED | Flags.RUNNING))) {
585681 this._flags |= flag | Flags.NOTIFIED;
···588684 }
589685 }
590686591591- /** @internal */
687687+ /**
688688+ * disposes the effect.
689689+ */
592690 _dispose(): void {
593691 if (!((this._flags |= Flags.DISPOSED) & Flags.RUNNING)) {
594692 dispose_effect(this, true);
···597695}
598696599697/**
600600- * Create a new signal, a container that can change value and subscribed to at
601601- * any given time.
698698+ * creates a new signal.
699699+ * @param value the initial value of the signal
700700+ * @returns a new signal
602701 */
603702export function signal<T>(): Signal<T | undefined>;
604703export function signal<T>(value: T): Signal<T>;
···609708type NoInfer<T extends any> = [T][T extends any ? 0 : never];
610709611710/**
612612- * The compute function itself.
711711+ * the function that computes the value of a `computed` signal.
712712+ * @template Prev the type of the previous value
713713+ * @template Next the type of the next value
613714 */
614715export type ComputedFunction<Prev, Next extends Prev = Prev> = (v: Prev) => Next;
615716616717/**
617617- * Create derivations of signals.
718718+ * creates a new computed signal.
719719+ * @param fn the function that computes the value of the signal
720720+ * @param value the initial value of the signal
721721+ * @returns a new computed signal
618722 */
619723export function computed<Next extends Prev, Prev = Next>(
620724 fn: ComputedFunction<undefined | NoInfer<Prev>, Next>,
···632736}
633737634738/**
635635- * Run side-effects that get rerun when one of its signal dependencies change.
739739+ * run a side-effect that is re-run when one of its dependencies changes.
740740+ * @param fn the function to run
741741+ * @returns a function to dispose the effect
636742 */
637743export function effect(fn: () => unknown): () => void {
638744 const instance = new Effect(fn);
···653759}
654760655761/**
656656- * register a cleanup function that is called when the effect is disposed
762762+ * registers a cleanup function that is called when the current effect is disposed.
657763 * @param fn the cleanup function
658764 * @param failSilently whether to warn if called outside of an effect
659765 */
···665771 eval_effect._cleanups.push(fn);
666772 }
667773 } else if (!failSilently) {
668668- console.warn(`onCleanup was called outside of an effect`);
774774+ console.warn('onCleanup was called outside of an effect, this is a no-op.');
669775 }
670776}
671777672778/**
673673- * Read signal values without being tracked as a dependency
779779+ * runs a function without creating dependencies.
780780+ * @param callback the function to run
781781+ * @returns the value returned by the callback
674782 */
675783export function untrack<T>(callback: () => T): T {
676784 if (eval_listener === undefined) {
···688796}
689797690798/**
691691- * Combines multiple signal writes into one single update that gets triggered at
692692- * the end of the callback
799799+ * combines multiple signal writes into a single update.
800800+ * the update is triggered at the end of the callback.
801801+ * @param callback the function to run
693802 */
694803export function batch(callback: () => void): void {
695804 if (batch_depth > 0) {
+11-11
store.ts
···11/**
22 * @module
33- * Creates a reactive proxy of an object.
44- * Loosely based off Svelte's and Solid's reactivity proxy implementation.
33+ * creates a reactive proxy of an object.
44+ * loosely based off Svelte's and Solid's reactivity proxy implementation.
55 */
6677import { batch, eval_listener, type Signal, signal } from './mod.ts';
···194194}
195195196196/**
197197- * Retrieve the original object from a reactive proxy
198198- * @param value Any value
199199- * @returns If a reactive proxy, the original object, otherwise returned as-is.
197197+ * retrieves the original object from a reactive proxy.
198198+ * @param value the value to unwrap
199199+ * @returns the unwrapped value
200200 */
201201export function unwrap<T>(value: T): T {
202202 if (typeof value === 'object' && value !== null) {
···208208}
209209210210/**
211211- * Creates a shallow reactive proxy of an object
212212- * @param value Any value
213213- * @returns If passed an object, a reactive proxy of that object, otherwise returned as-is.
211211+ * creates a shallow reactive proxy of an object.
212212+ * @param value the value to make reactive
213213+ * @returns a shallow reactive proxy
214214 */
215215export function shallowReactive<T extends object>(value: T): T {
216216 if (typeof value === 'object' && value !== null && is_wrappable(value)) {
···230230}
231231232232/**
233233- * Creates a deep reactive proxy of an object
234234- * @param value Any value
235235- * @returns If passed an object, a reactive proxy of that object, otherwise returned as-is.
233233+ * creates a deep reactive proxy of an object.
234234+ * @param value the value to make reactive
235235+ * @returns a deep reactive proxy
236236 */
237237export function reactive<T extends object>(value: T): T {
238238 if (typeof value === 'object' && value !== null && is_wrappable(value)) {