The interface provided by rule, and other rule.factory() functions.

interface RuleFactory {
    detached(fn: (() => OptionalCleanup)): DisposeFn;
    factory(scheduleFn: SchedulerFn): RuleFactory;
    if(condition: (() => any), action: (() => OptionalCleanup)): DisposeFn;
    method: GenericMethodDecorator<((...args: any[]) => OptionalCleanup)>;
    setScheduler(scheduleFn?: SchedulerFn): void;
    stop: DisposeFn;
    (fn: (() => OptionalCleanup)): DisposeFn;
}
  • Subscribe a function to run every time certain values change.

    The function is run asynchronously, first after being created, then again after there are changes in any of the value()s or cached() functions it read during its previous run.

    The created subscription is tied to the currently-active job (which may be another rule). So when that job is ended or restarted, the rule will be terminated automatically. You can also terminate it early by calling the "stop" function that is returned by rule(), or by calling rule.stop() from within the rule function.

    Note: this function will throw an error if called without an active job. If you need a standalone rule, use rule.detached().

    Parameters

    • fn: (() => OptionalCleanup)

      The function that will be run each time its dependencies change. The function will be run in a restarted job each time, with any resources used by the previous run being cleaned up. The function is called with no arguments, and should return a cleanup function or void.

    Returns DisposeFn

    A function that can be called to terminate the rule.

Properties

method: GenericMethodDecorator<((...args: any[]) => OptionalCleanup)>

Decorate a method or function to behave as a rule, e.g.

const animate = rule.factory(requestAnimationFrame);

class Draggable {
⁣@animate.method
trackPosition(handleTop: number, handleLeft: number) {
const {clientX, clientY} = lastMouseEvent();
this.element.style.top = `${clientY - handleTop}px`;
this.element.style.left = `${clientX - handleLeft}px`;
}
}

// Start running the method in an animation frame for every change to
// lastMouseEvent, until the current job ends:
someDraggable.trackPosition(top, left);

or:

const logger = rule.method((formatString, signal) => { log(formatString, signal()); });

Each time it's (explicitly) called, the decorated method will start a new rule, which will repeatedly run the method body (with the original arguments and this) whenever its dependencies change, according to the schedule defined by the rule factory. (So e.g. @rule.method will update on the microtask after a change, etc.)

The decorated method will always return a DisposeFn to let you explicitly stop the rule before the current job end. But if the original method body doesn't return a dispose function of its own, TypeScript will consider the method to return void, unless you explicitly declare its return type to be DisposeFn | void.

Also note that since rule methods can accept arbitrary parameters, they do not receive a stop parameter, and must therefore use rule.stop() if they wish to terminate themselves.

Type declaration

stop: DisposeFn

A function that will stop the currently-executing rule. (Accessing this attribute will throw an error if no rule is currently running.)

Note: this returns the stop for the current rule regardless of its scheduler, so you can access rule.stop even if the rule was created with a different scheduler.

Methods

  • Return a rule factory for the given scheduling function, that you can then use to make rules that run in a specific time frame.

    // `animate` will now create rules that run during animation fames
    const animate = rule.factory(requestAnimationFrame);

    animate(() => {
    // ... do stuff in an animation frame when signals used here change
    })

    (In addition to being callable, the returned function is also a RuleFactory, and thus has a .method decorator, .if() method, and so on.)

    Parameters

    • scheduleFn: SchedulerFn

      A single-argument scheduling function (such as requestAnimationFrame, setImmediate, or queueMicrotask). The rule scheduler will call it from time to time with a single callback. The scheduling function should then arrange for that callback to be invoked once at some future point, when it is the desired time for all pending rules on that scheduler to run.

    Returns RuleFactory

    A RuleFactory, like rule. If called with the same scheduling function more than once, it returns the same factory.

  • Observe a condition and apply an action.

    For a given RuleFactory r (such as rule), r.if(condition, action) is roughly equivalent to r(() => { if (condition()) return action(); }), except that the rule is only rerun if the action's dependencies change, or the truthiness of condition() changes. It will not be re-run if only the dependencies of condition() have changed, without affecting its truthiness.

    This behavior can be important for rules that nest other rules, have cleanups, fire off tasks, etc., as it may be wasteful to constantly tear them down and set them back up if the enabling condition is a calculation with frequently-changing dependencies.

    Parameters

    Returns DisposeFn

    Remarks

    This is just a shortcut for wrapping condition as a signal that converts it to boolean. So if you already have a boolean signal, you can get the same effect with just if (condition()) { ... }.

  • Change the scheduler used for the currently-executing rule. Throws an error if no rule is running.

    Parameters

    • Optional scheduleFn: SchedulerFn

      Optional: The scheduling function to use; the default microtask scheduler will be used if none is given or the given value is falsy.

    Returns void

    Remarks

    It's best to only use scheduling functions that were created outside the current rule, otherwise you'll be creating a new scheduling queue object on every run of the rule. (These will get garbage collected with the scheduling functions, but you'll be creating more memory pressure and using more GC time if the rule runs frequently.)