turbowatch
Version:
Extremely fast file change detector and task orchestrator for Node.js.
180 lines (158 loc) • 5.5 kB
text/typescript
// cspell:words idirname imatch iname wholename
import { type FileWatchingBackend } from './backends/FileWatchingBackend';
import { type Logger } from 'roarr';
import { type ProcessOutput } from 'zx';
/* eslint-disable @typescript-eslint/sort-type-union-intersection-members */
export type Expression =
// Evaluates as true if all of the grouped expressions also evaluated as true.
// https://facebook.github.io/watchman/docs/expr/allof.html
| ['allof', ...Expression[]]
// Evaluates as true if any of the grouped expressions also evaluated as true.
// https://facebook.github.io/watchman/docs/expr/anyof.html
| ['anyof', ...Expression[]]
// Evaluates as true if a given file has a matching parent directory.
// https://facebook.github.io/watchman/docs/expr/dirname.html
| ['dirname' | 'idirname', string]
// Evaluates as true if a glob matches against the basename of the file.
// https://facebook.github.io/watchman/docs/expr/match.html
| ['match' | 'imatch', string]
| ['match' | 'imatch', string, 'basename' | 'wholename']
// Evaluates as true if the sub-expression evaluated as false, i.e. inverts the sub-expression.
// https://facebook.github.io/watchman/docs/expr/not.html
| ['not', Expression];
/* eslint-enable @typescript-eslint/sort-type-union-intersection-members */
type JsonValue =
| JsonObject
| JsonValue[]
| boolean
| number
| string
| readonly JsonValue[]
| null
| undefined;
export type JsonObject = {
[k: string]: JsonValue;
};
type File = {
name: string;
};
/**
* @property attempt Attempt number (starting with 0) indicating if trigger was retried.
* @property files Describes the list of files that changed.
* @property first Identifies if this is the first event.
* @property signal Instance of AbortSignal used to signal when the routine should be aborted.
* @property spawn Instance of zx bound to AbortSignal.
*/
export type ChangeEvent = {
abortSignal?: AbortSignal;
attempt: number;
files: readonly File[];
first: boolean;
log: Logger;
spawn: (
pieces: TemplateStringsArray,
...args: any[]
) => Promise<ProcessOutput>;
taskId: string;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type OnChangeEventHandler = (event: ChangeEvent) => Promise<any>;
export type TeardownEvent = {
spawn: (
pieces: TemplateStringsArray,
...args: any[]
) => Promise<ProcessOutput>;
};
type OnTeardownEventHandler = (event: TeardownEvent) => Promise<void>;
/**
* @property factor The exponential factor to use. Default is 2.
* @property maxTimeout The maximum number of milliseconds between two retries. Default is Infinity.
* @property minTimeout The number of milliseconds before starting the first retry. Default is 1000.
* @property retries The maximum amount of times to retry the operation. Default is 10. Seting this to 1 means do it once, then retry it once.
*/
type Retry = {
factor?: number;
maxTimeout?: number;
minTimeout?: number;
retries: number;
};
type Debounce = {
wait: number;
};
export type Throttle = {
delay: number;
};
/**
* @property expression watchman expression, e.g. https://facebook.github.io/watchman/docs/expr/allof.html
* @property interruptible Sends abort signal to an ongoing routine, if any. Otherwise, waits for routine to finish. (default: true)
* @property initialRun Indicates whether onChange is run when the script is first initiated.
* @property name Name of the trigger. Used for debugging. Must match /^[a-z0-9-_]+$/ pattern and must be unique.
* @property onChange Routine that is executed when file changes are detected.
* @property persistent Label a task as persistent if it is a long-running process, such as a dev server or --watch mode.
*/
type TriggerInput = {
expression: Expression;
initialRun?: boolean;
interruptible?: boolean;
name: string;
onChange: OnChangeEventHandler;
onTeardown?: OnTeardownEventHandler;
persistent?: boolean;
retry?: Retry;
throttleOutput?: Throttle;
};
export type Trigger = {
abortSignal?: AbortSignal;
cwd?: string;
expression: Expression;
id: string;
initialRun: boolean;
interruptible: boolean;
name: string;
onChange: OnChangeEventHandler;
onTeardown?: OnTeardownEventHandler;
persistent: boolean;
retry: Retry;
throttleOutput: Throttle;
};
export type WatcherConstructable = new (project: string) => FileWatchingBackend;
/**
* @property project absolute path to the directory to watch
*/
export type TurbowatchConfigurationInput = {
readonly Watcher?: WatcherConstructable;
readonly cwd?: string;
readonly debounce?: Debounce;
readonly project: string;
readonly triggers: readonly TriggerInput[];
};
export type TurbowatchConfiguration = {
readonly Watcher: WatcherConstructable;
readonly cwd?: string;
readonly debounce: Debounce;
readonly project: string;
readonly triggers: readonly TriggerInput[];
};
export type FileChangeEvent = {
filename: string;
};
/**
* @property queued Indicates that a follow action has been queued.
*/
export type ActiveTask = {
abortController: AbortController | null;
id: string;
promise: Promise<unknown>;
queued: boolean;
};
export type Subscription = {
activeTask: ActiveTask | null;
expression: Expression;
initialRun: boolean;
persistent: boolean;
teardown: () => Promise<void>;
trigger: (events: readonly FileChangeEvent[]) => Promise<void>;
};
export type TurbowatchController = {
shutdown: () => Promise<void>;
};