fast reactive signals jsr.io/@mary/signals
typescript jsr
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: clean up jsdoc comments

Mary b3fff573 14ec805a

+212 -103
+201 -92
mod.ts
··· 1 + /** 2 + * bitmask flags for computation states 3 + */ 1 4 const enum Flags { 5 + // context flags 2 6 RUNNING = 1 << 0, 3 7 DIRTY = 1 << 1, 4 8 MAYBE_DIRTY = 1 << 2, ··· 10 14 11 15 type Computation = Computed<any> | Effect<any>; 12 16 13 - /** @internal currently evaluating listener */ 17 + /** 18 + * @internal 19 + * the currently evaluating computation 20 + */ 14 21 export let eval_listener: Computation | undefined; 15 - /** @internal currently evaluating effect */ 16 - export let eval_effect: Effect<any> | undefined; 22 + /** 23 + * the currently evaluating effect 24 + */ 25 + let eval_effect: Effect<any> | undefined; 17 26 18 - /** pointer for checking existing dependencies in a context */ 27 + /** 28 + * pointer for the currently evaluating computation's sources, used for tracking 29 + */ 19 30 let eval_sources_index: number = 0; 20 - /** array of new dependencies to said context */ 31 + /** 32 + * array of new dependencies for the currently evaluating computation 33 + */ 21 34 let eval_untracked_sources: Signal[] | undefined; 22 35 23 - /** effect scheduled for batching */ 36 + /** 37 + * the effect that is scheduled for batching 38 + */ 24 39 let batched_effect: Effect<any> | undefined; 25 - /** current batch depth */ 40 + /** 41 + * the current batch depth, used for nested batches 42 + */ 26 43 let batch_depth: number = 0; 27 - /** current batch iteration */ 44 + /** 45 + * the current batch iteration, used to prevent infinite loops 46 + */ 28 47 let batch_iteration: number = 0; 29 48 30 49 /** 31 - * Writing to a signal ticks this write clock forward and stores the new epoch 32 - * into that signal, this is used for contexts to know if they're stale by 33 - * comparing the epoch between it and its dependencies. 50 + * a global clock that is incremented every time a signal is written to. 51 + * this is used to determine if a computation is stale. 34 52 */ 35 53 let write_clock = 0; 36 54 37 55 /** 38 - * Running a context ticks this read clock forward and stores the new epoch 39 - * into that context, this is used for duplicate-checking to see if the signal 40 - * has already been read by the context. 41 - * 42 - * Oversubscription isn't a big deal at all so really this isn't actually 43 - * needed, a duplicate dependency wouldn't cause any harm as we have a flag for 44 - * checking whether the context has been already been notified. 56 + * a global clock that is incremented every time a computation is run. 57 + * this is used to track dependencies. 45 58 */ 46 59 let read_clock = 0; 47 60 ··· 116 129 let dependencies = eval_listener!._dependencies; 117 130 118 131 if (eval_untracked_sources) { 119 - // We have new dependencies, so let's unsubscribe from stale dependencies. 132 + // we have new dependencies, so let's unsubscribe from stale dependencies. 120 133 prune_old_dependencies(); 121 134 122 135 if (eval_sources_index > 0) { 123 - // We have existing dependencies still depended on, so let's expand the 136 + // we have existing dependencies still depended on, so let's expand the 124 137 // existing array to make room for our new dependencies. 125 138 const ilen = eval_untracked_sources.length; 126 139 127 140 dependencies.length = eval_sources_index + ilen; 128 141 129 - // Override anything after the pointer with our new dependencies, this is 142 + // override anything after the pointer with our new dependencies, this is 130 143 // fine since we're no longer subscribed to them. 131 144 for (let i = 0; i < ilen; i++) { 132 145 dependencies[eval_sources_index + i] = eval_untracked_sources[i]; 133 146 } 134 147 } else { 135 - // There isn't any existing dependencies, so just replace the existing 148 + // there isn't any existing dependencies, so just replace the existing 136 149 // array with the new one. 137 150 dependencies = eval_listener!._dependencies = eval_untracked_sources; 138 151 } 139 152 140 - // Now we subscribe to the new dependencies, but only if we're currently 153 + // now we subscribe to the new dependencies, but only if we're currently 141 154 // configured as tracking. 142 155 if (eval_listener!._flags & Flags.TRACKING) { 143 156 for (let i = eval_sources_index, ilen = dependencies.length; i < ilen; i++) { ··· 147 160 } 148 161 } 149 162 } else if (eval_sources_index < eval_listener!._dependencies.length) { 150 - // We don't have new dependencies, but the index pointer isn't pointing to 163 + // we don't have new dependencies, but the index pointer isn't pointing to 151 164 // the end of the array, so we need to clean up the rest. 152 165 prune_old_dependencies(); 153 166 dependencies.length = eval_sources_index; ··· 178 191 179 192 try { 180 193 for (let i = 0; i < cleanups.length; i++) { 181 - (0, cleanups[i])(); 194 + cleanups[i]!(); 182 195 } 183 196 } catch (err) { 184 - // Failed to clean, so let's dispose of this effect. 197 + // failed to clean, so let's dispose of this effect. 185 198 effect._flags = (effect._flags & ~Flags.RUNNING) | Flags.DISPOSED; 186 199 dispose_effect(effect, false); 187 200 ··· 211 224 } 212 225 213 226 export class Signal<T = unknown> { 214 - /** @internal Stored time of the write clock */ 227 + /** 228 + * @internal 229 + * the last time this signal was written to. 230 + * this is a timestamp from the global `write_clock`. 231 + */ 215 232 _epoch = -1; 216 - /** @internal Stored time of the read clock, used to detect dupe-reads */ 233 + /** 234 + * @internal 235 + * the last time this signal was read by a computation. 236 + * this is a timestamp from the global `read_clock`. 237 + */ 217 238 _access_epoch = -1; 218 - /** @internal Contexts depending on it */ 239 + /** 240 + * @internal 241 + * the computations that depend on this signal. 242 + */ 219 243 _dependants: Computation[] = []; 220 244 221 - /** @internal stored value */ 245 + /** 246 + * @internal 247 + * the current value of the signal. 248 + */ 222 249 _value: T; 223 250 224 251 constructor(value: T) { ··· 227 254 228 255 /** 229 256 * @internal 230 - * Contexts will call this function to check if perhaps its dependencies are 231 - * stale, this is mostly needed for computed signals though, for a signal, 232 - * there's nothing new to be had once this method is called. 257 + * this method is called by computations to check if they are stale. 258 + * for a signal, this always returns false. 233 259 */ 234 260 _refresh(): boolean { 235 - // Returning `true` indicates that the dependant is now stale as a result, 236 - // e.g. computation has returned a different value. This just prevents the 261 + // returning `true` indicates that the dependant is now stale as a result, 262 + // e.g. computation has returned a different value. this just prevents the 237 263 // need to check the epoch again so the epoch still needs to be incremented. 238 264 return false; 239 265 } 240 266 241 - /** @internal */ 267 + /** 268 + * @internal 269 + * subscribes a computation to this signal. 270 + * @param target the computation to subscribe 271 + */ 242 272 _subscribe(target: Computation): void { 243 273 this._dependants.push(target); 244 274 } 245 275 246 - /** @internal */ 276 + /** 277 + * @internal 278 + * unsubscribes a computation from this signal. 279 + * @param target the computation to unsubscribe 280 + */ 247 281 _unsubscribe(target: Computation): void { 248 282 const dependants = this._dependants; 249 283 const index = dependants.indexOf(target); ··· 252 286 } 253 287 254 288 /** 255 - * Retrieves the value without being tracked as a dependant. 289 + * retrieves the value of the signal without creating a dependency. 290 + * @returns the current value 256 291 */ 257 292 peek(): T { 258 293 return this._value; 259 294 } 260 295 261 296 /** 262 - * Retrieves the value, tracks the currently-running effect as a dependant. 297 + * retrieves the value of the signal and creates a dependency if inside a computation. 263 298 */ 264 299 get value(): T { 265 - // Check if we're running under a context 300 + // check if we're running under a context 266 301 if (eval_listener !== undefined && eval_listener._context_epoch !== this._access_epoch) { 267 - // Store the read epoch, we don't need to recheck the dependencies of 302 + // store the read epoch, we don't need to recheck the dependencies of 268 303 // this effect. 269 304 this._access_epoch = eval_listener._context_epoch; 270 305 271 - // Dependency tracking is simple: does the index pointer point to us? 306 + // dependency tracking is simple: does the index pointer point to us? 272 307 // 273 - // - If so, then we're already depended on and we can increment the 308 + // - if so, then we're already depended on and we can increment the 274 309 // pointer for the next signal. 275 310 // 276 - // - If not, then we need to create a new dependency array and stop 311 + // - if not, then we need to create a new dependency array and stop 277 312 // incrementing the pointer, now that pointer acts as a dividing line 278 313 // between signals that are still being depended, and are no longer 279 - // depended upon. The new dependencies can be concatenated afterwards. 314 + // depended upon. the new dependencies can be concatenated afterwards. 280 315 281 316 if (eval_untracked_sources !== undefined) { 282 317 eval_untracked_sources.push(this); ··· 289 324 290 325 return this._value; 291 326 } 327 + 292 328 set value(next: T) { 293 329 if (this._value !== next) { 294 - // Tick the write clock forward 330 + // tick the write clock forward 295 331 this._epoch = ++write_clock; 296 332 this._value = next; 297 333 ··· 303 339 for (let i = 0, ilen = dependants.length; i < ilen; i++) { 304 340 const dep = dependants[i]; 305 341 306 - // Source signal dependants are guaranteed to be dirty. 342 + // source signal dependants are guaranteed to be dirty. 307 343 dep._notify(Flags.DIRTY); 308 344 } 309 345 ··· 317 353 readonly value: T; 318 354 } 319 355 320 - /** @internal */ 321 356 export class Computed<T = unknown> extends Signal<T> { 322 357 /** 323 358 * @internal 324 - * Signals it's depending on 359 + * the signals that this computation depends on. 325 360 */ 326 361 _dependencies: Signal[] = []; 327 362 /** 328 363 * @internal 329 - * Context flags 364 + * the current state of the computation. 330 365 */ 331 366 _flags: number = Flags.DIRTY; 367 + 332 368 /** 333 369 * @internal 334 - * This is mostly used for computed signals that currently aren't being 335 - * subscribed to, if nothing in the realm has changed, then it shouldn't be 336 - * possible for this computed signal to be stale from its dependencies. 370 + * the last time this computation was updated. 371 + * this is a timestamp from the global `write_clock`. 337 372 */ 338 373 _realm_write_epoch = -1; 339 374 /** 340 375 * @internal 341 - * Stored time of the read clock 376 + * the last time this computation was run. 377 + * this is a timestamp from the global `read_clock`. 342 378 */ 343 379 _context_epoch = -1; 344 380 345 381 /** 346 382 * @internal 347 - * Compute function used to retrieve the value for this computed signal 383 + * the function that computes the value of this signal. 348 384 */ 349 385 _compute: (prev: T) => T; 350 386 ··· 354 390 this._compute = compute; 355 391 } 356 392 357 - /** @internal */ 393 + /** 394 + * @internal 395 + * re-runs the computation if it's stale and updates its value. 396 + * @returns `true` if the value changed, `false` otherwise. 397 + */ 358 398 override _refresh(): boolean { 359 - // Retrieve the current flags, make sure to unset NOTIFIED now that we're 399 + // retrieve the current flags, make sure to unset NOTIFIED now that we're 360 400 // running this refresh. 361 401 const flags = (this._flags &= ~Flags.NOTIFIED); 362 402 363 403 if ( 364 - // If our stored time matches current time then nothing in the realm has changed 404 + // if our stored time matches current time then nothing in the realm has changed 365 405 this._realm_write_epoch === write_clock || 366 - // If we're tracking, we can make use of DIRTY and MAYBE_DIRTY 406 + // if we're tracking, we can make use of DIRTY and MAYBE_DIRTY 367 407 (flags & (Flags.TRACKING | Flags.DIRTY | Flags.MAYBE_DIRTY)) === Flags.TRACKING || 368 - // Prevent self-referential checks 408 + // prevent self-referential checks 369 409 flags & Flags.RUNNING 370 410 ) { 371 411 return false; ··· 404 444 this._epoch = this._realm_write_epoch = ++write_clock; 405 445 } 406 446 } catch (err) { 407 - // Always mark errors as stale 447 + // always mark errors as stale 408 448 stale = true; 409 449 410 450 this._value = err as T; ··· 422 462 return stale; 423 463 } 424 464 425 - /** @internal */ 465 + /** 466 + * @internal 467 + * subscribes a computation to this signal. 468 + * @param target the computation to subscribe 469 + */ 426 470 override _subscribe(target: Computation): void { 427 - // Subscribe to our sources now that we have someone subscribing on us 471 + // subscribe to our sources now that we have someone subscribing on us 428 472 if (this._dependants.length < 1) { 429 473 const dependencies = this._dependencies; 430 474 this._flags |= Flags.TRACKING; ··· 438 482 super._subscribe(target); 439 483 } 440 484 441 - /** @internal */ 485 + /** 486 + * @internal 487 + * unsubscribes a computation from this signal. 488 + * @param target the computation to unsubscribe 489 + */ 442 490 override _unsubscribe(target: Computation): void { 443 491 super._unsubscribe(target); 444 492 445 - // Unsubscribe from our sources since there's no one subscribing to us 493 + // unsubscribe from our sources since there's no one subscribing to us 446 494 if (this._dependants.length < 1) { 447 495 const dependencies = this._dependencies; 448 496 this._flags &= ~Flags.TRACKING; ··· 454 502 } 455 503 } 456 504 457 - /** @internal */ 505 + /** 506 + * @internal 507 + * notifies all dependents that this signal has changed. 508 + * @param flag the type of change 509 + */ 458 510 _notify(flag: Flags.DIRTY | Flags.MAYBE_DIRTY): void { 459 511 if (!(this._flags & (Flags.NOTIFIED | Flags.RUNNING))) { 460 512 const dependants = this._dependants; ··· 464 516 for (let i = 0, ilen = dependants.length; i < ilen; i++) { 465 517 const dep = dependants[i]; 466 518 467 - // Computed signal dependants aren't guaranteed to be dirty. 519 + // computed signal dependants aren't guaranteed to be dirty. 468 520 dep._notify(Flags.MAYBE_DIRTY); 469 521 } 470 522 } 471 523 } 472 524 473 525 /** 474 - * Retrieves the value without being tracked as a dependant 526 + * retrieves the value of the signal without creating a dependency. 527 + * this will re-run the computation if it's stale. 528 + * @returns the current value 475 529 */ 476 530 override peek(): T { 477 531 this._refresh(); ··· 484 538 } 485 539 486 540 /** 487 - * Retrieves the value, tracks the currently-running effect as a dependant 541 + * retrieves the value of the signal and creates a dependency if inside a computation. 542 + * this will re-run the computation if it's stale. 488 543 */ 489 544 override get value(): T { 490 545 this._refresh(); ··· 497 552 } 498 553 } 499 554 500 - /** @internal */ 555 + /** 556 + * @internal 557 + * a side-effect that is run when a signal it depends on changes. 558 + * @template T the type of the value 559 + */ 501 560 export class Effect<T = void> { 502 - /** @internal Stored time of the write clock */ 561 + /** 562 + * @internal 563 + * the last time this effect was run. 564 + * this is a timestamp from the global `write_clock`. 565 + */ 503 566 _epoch = -1; 504 - /** @internal Stored time of the read clock */ 567 + /** 568 + * @internal 569 + * the last time this effect was run. 570 + * this is a timestamp from the global `read_clock`. 571 + */ 505 572 _context_epoch = -1; 506 - /** @internal Signals it's depending on */ 573 + /** 574 + * @internal 575 + * the signals that this effect depends on. 576 + */ 507 577 _dependencies: Signal[] = []; 508 - /** @internal Context flags */ 578 + /** 579 + * @internal 580 + * the current state of the effect. 581 + */ 509 582 _flags = Flags.TRACKING; 510 583 511 - /** @internal Registered cleanup functions */ 584 + /** 585 + * @internal 586 + * the cleanup functions for this effect. 587 + */ 512 588 _cleanups?: (() => void)[]; 513 - /** @internal Compute function for this effect */ 589 + /** 590 + * @internal 591 + * the function that runs the effect. 592 + */ 514 593 _compute: () => unknown; 515 - /** @internal Stored value from this compute */ 594 + /** 595 + * @internal 596 + * the value returned by the effect. 597 + */ 516 598 _value!: T; 517 599 518 - /** @internal Batched effects are queued by a linked list on itself */ 600 + /** 601 + * @internal 602 + * the next effect in the batch queue. 603 + */ 519 604 _next_batched_effect: Effect | undefined; 520 605 521 606 constructor(compute: () => unknown) { 522 607 this._compute = compute; 523 608 } 524 609 525 - /** @internal */ 610 + /** 611 + * @internal 612 + * starts the effect. 613 + * @returns a function to finish the effect 614 + */ 526 615 _start(): (() => void) | undefined { 527 616 const flags = this._flags; 528 617 ··· 564 653 }; 565 654 } 566 655 567 - /** @internal */ 656 + /** 657 + * @internal 658 + * re-runs the effect if it's stale. 659 + */ 568 660 _refresh(): void { 569 661 const finish = this._start(); 570 662 ··· 579 671 } 580 672 } 581 673 582 - /** @internal */ 674 + /** 675 + * @internal 676 + * notifies the effect that one of its dependencies has changed. 677 + * @param flag the type of change 678 + */ 583 679 _notify(flag: Flags.DIRTY | Flags.MAYBE_DIRTY): void { 584 680 if (!(this._flags & (Flags.NOTIFIED | Flags.RUNNING))) { 585 681 this._flags |= flag | Flags.NOTIFIED; ··· 588 684 } 589 685 } 590 686 591 - /** @internal */ 687 + /** 688 + * disposes the effect. 689 + */ 592 690 _dispose(): void { 593 691 if (!((this._flags |= Flags.DISPOSED) & Flags.RUNNING)) { 594 692 dispose_effect(this, true); ··· 597 695 } 598 696 599 697 /** 600 - * Create a new signal, a container that can change value and subscribed to at 601 - * any given time. 698 + * creates a new signal. 699 + * @param value the initial value of the signal 700 + * @returns a new signal 602 701 */ 603 702 export function signal<T>(): Signal<T | undefined>; 604 703 export function signal<T>(value: T): Signal<T>; ··· 609 708 type NoInfer<T extends any> = [T][T extends any ? 0 : never]; 610 709 611 710 /** 612 - * The compute function itself. 711 + * the function that computes the value of a `computed` signal. 712 + * @template Prev the type of the previous value 713 + * @template Next the type of the next value 613 714 */ 614 715 export type ComputedFunction<Prev, Next extends Prev = Prev> = (v: Prev) => Next; 615 716 616 717 /** 617 - * Create derivations of signals. 718 + * creates a new computed signal. 719 + * @param fn the function that computes the value of the signal 720 + * @param value the initial value of the signal 721 + * @returns a new computed signal 618 722 */ 619 723 export function computed<Next extends Prev, Prev = Next>( 620 724 fn: ComputedFunction<undefined | NoInfer<Prev>, Next>, ··· 632 736 } 633 737 634 738 /** 635 - * Run side-effects that get rerun when one of its signal dependencies change. 739 + * run a side-effect that is re-run when one of its dependencies changes. 740 + * @param fn the function to run 741 + * @returns a function to dispose the effect 636 742 */ 637 743 export function effect(fn: () => unknown): () => void { 638 744 const instance = new Effect(fn); ··· 653 759 } 654 760 655 761 /** 656 - * register a cleanup function that is called when the effect is disposed 762 + * registers a cleanup function that is called when the current effect is disposed. 657 763 * @param fn the cleanup function 658 764 * @param failSilently whether to warn if called outside of an effect 659 765 */ ··· 665 771 eval_effect._cleanups.push(fn); 666 772 } 667 773 } else if (!failSilently) { 668 - console.warn(`onCleanup was called outside of an effect`); 774 + console.warn('onCleanup was called outside of an effect, this is a no-op.'); 669 775 } 670 776 } 671 777 672 778 /** 673 - * Read signal values without being tracked as a dependency 779 + * runs a function without creating dependencies. 780 + * @param callback the function to run 781 + * @returns the value returned by the callback 674 782 */ 675 783 export function untrack<T>(callback: () => T): T { 676 784 if (eval_listener === undefined) { ··· 688 796 } 689 797 690 798 /** 691 - * Combines multiple signal writes into one single update that gets triggered at 692 - * the end of the callback 799 + * combines multiple signal writes into a single update. 800 + * the update is triggered at the end of the callback. 801 + * @param callback the function to run 693 802 */ 694 803 export function batch(callback: () => void): void { 695 804 if (batch_depth > 0) {
+11 -11
store.ts
··· 1 1 /** 2 2 * @module 3 - * Creates a reactive proxy of an object. 4 - * Loosely based off Svelte's and Solid's reactivity proxy implementation. 3 + * creates a reactive proxy of an object. 4 + * loosely based off Svelte's and Solid's reactivity proxy implementation. 5 5 */ 6 6 7 7 import { batch, eval_listener, type Signal, signal } from './mod.ts'; ··· 194 194 } 195 195 196 196 /** 197 - * Retrieve the original object from a reactive proxy 198 - * @param value Any value 199 - * @returns If a reactive proxy, the original object, otherwise returned as-is. 197 + * retrieves the original object from a reactive proxy. 198 + * @param value the value to unwrap 199 + * @returns the unwrapped value 200 200 */ 201 201 export function unwrap<T>(value: T): T { 202 202 if (typeof value === 'object' && value !== null) { ··· 208 208 } 209 209 210 210 /** 211 - * Creates a shallow reactive proxy of an object 212 - * @param value Any value 213 - * @returns If passed an object, a reactive proxy of that object, otherwise returned as-is. 211 + * creates a shallow reactive proxy of an object. 212 + * @param value the value to make reactive 213 + * @returns a shallow reactive proxy 214 214 */ 215 215 export function shallowReactive<T extends object>(value: T): T { 216 216 if (typeof value === 'object' && value !== null && is_wrappable(value)) { ··· 230 230 } 231 231 232 232 /** 233 - * Creates a deep reactive proxy of an object 234 - * @param value Any value 235 - * @returns If passed an object, a reactive proxy of that object, otherwise returned as-is. 233 + * creates a deep reactive proxy of an object. 234 + * @param value the value to make reactive 235 + * @returns a deep reactive proxy 236 236 */ 237 237 export function reactive<T extends object>(value: T): T { 238 238 if (typeof value === 'object' && value !== null && is_wrappable(value)) {