Перейти к содержимому
GitHubXDiscord

Middleware

Стабильный · с 0.1.0

Подключаемый наблюдатель, прицепленный к каждому триггеру в рантайме. Семь хуков срабатывают в предсказуемых точках вокруг диспатча — три из них (onFire, onSkip, onError) часто складываются в полный аудит-трейл; остальные (onBeforeMatch, onActionStart, onActionEnd, onCascade) — для трассировки, метрик и devtools.

Все хуки необязательные. Исключение, выброшенное в хуке, ловится и логируется рантаймом, но не прерывает запуск — middleware не должен быть load-bearing.

import type { Middleware } from '@triggery/core';
type Middleware = {
  readonly name: string;

  onFire?(ctx: FireContext): void | { cancel: true; reason: string };
  onBeforeMatch?(ctx: MatchContext): void;
  onSkip?(ctx: SkipContext): void;
  onActionStart?(ctx: ActionContext): void;
  onActionEnd?(ctx: ActionContext & { durationMs: number; result?: unknown }): void;
  onError?(ctx: ActionContext & { error: unknown }): void;
  onCascade?(ctx: CascadeContext): void;
};

Стабильный id middleware. Обязателен. Полезен в сообщениях об ошибках и в панелях devtools.

Вызывается один раз на каждый fire(eventName, payload) верхнего уровня (или каскадный) перед тем, как сматчится любой триггер. Возврат { cancel: true, reason } прерывает весь диспатч — триггеры не запускаются, и запись в стиле 'overflow' не пишется. Это единственный хук с коротким замыканием.

Поле FireContextОписание
eventNameДиспатчируемое событие.
payloadПолезная нагрузка, непрозрачная для middleware.
cascadeDepth0 для срабатываний верхнего уровня; n внутри каскада.
parentRunId?Установлен, если fire происходит внутри работающего обработчика.
parentTriggerId?То же — id upstream-триггера.
parentContext?Непрозрачная ссылка-связный список для детекта циклов.

Вызывается на каждую пару (event, trigger) сразу после того, как диспатч достал триггер из индекса событий — до любой concurrency-gate или проверки required. Чисто наблюдательный; полезен для «какие триггеры были рассмотрены для этого события» без инструментирования onSkip и onActionStart по отдельности.

Вызывается, когда триггер сматчился, но был пропущен. reason — один из 'disabled', 'required-missing', 'concurrency-take-first', 'aborted', 'cycle', 'overflow' и т. д. Используй для отслеживания «почему мой триггер не запустился».

Вызывается прямо перед тем, как обработчик вызовет действие. actionName и payload — аргументы. Используй для трассировки по действиям.

Вызывается, когда действие вернёт значение (или его промис разрешится). durationMs — wall-clock-время от onActionStart до onActionEnd. Рантайм замеряет это только когда есть хотя бы один слушатель — без миддлвэров с таймингом затрат нет.

Вызывается, когда действие выбрасывает исключение или его промис реджектится. Паря с onActionStart, обрамляет полную попытку. Ошибки также записываются в снепшот инспектора со status: 'errored'.

Вызывается, когда каскадное событие подавлено — kind: 'overflow' (cascadeDepth превысил maxCascadeDepth) или kind: 'cycle' (триггер появляется в своей же цепочке предков).

import { createRuntimefunction createRuntime(options?: RuntimeOptions): Runtime, type Middleware
type Middleware = {
    readonly name: string;
    onFire?(ctx: FireContext): void | {
        cancel: true;
        reason: string;
    };
    onBeforeMatch?(ctx: MatchContext): void;
    onSkip?(ctx: SkipContext): void;
    onActionStart?(ctx: ActionContext): void;
    onActionEnd?(ctx: ActionContext & {
        durationMs: number;
        result?: unknown;
    }): void;
    onError?(ctx: ActionContext & {
        error: unknown;
    }): void;
    onCascade?(ctx: CascadeContext): void;
}
} from '@triggery/core';
const tracingconst tracing: Middleware: Middleware
type Middleware = {
    readonly name: string;
    onFire?(ctx: FireContext): void | {
        cancel: true;
        reason: string;
    };
    onBeforeMatch?(ctx: MatchContext): void;
    onSkip?(ctx: SkipContext): void;
    onActionStart?(ctx: ActionContext): void;
    onActionEnd?(ctx: ActionContext & {
        durationMs: number;
        result?: unknown;
    }): void;
    onError?(ctx: ActionContext & {
        error: unknown;
    }): void;
    onCascade?(ctx: CascadeContext): void;
}
= {
namename: string: 'tracing', onFire
onFire?(ctx: FireContext): void | {
    cancel: true;
    reason: string;
}
({ eventNameeventName: string, cascadeDepthcascadeDepth: number }) {
consolevar console: Console.logConsole.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
(`[fire] ${eventNameeventName: string}${cascadeDepthcascadeDepth: number ? ` (cascade ${cascadeDepthcascadeDepth: number})` : ''}`);
}, onActionEnd
onActionEnd?(ctx: ActionContext & {
    durationMs: number;
    result?: unknown;
}): void
({ triggerIdtriggerId: string, actionNameactionName: string, durationMsdurationMs: number }) {
consolevar console: Console.logConsole.log(...data: any[]): void
The **`console.log()`** static method outputs a message to the console. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static)
(` ${triggerIdtriggerId: string}.${actionNameactionName: string} ${durationMsdurationMs: number.toFixedNumber.toFixed(fractionDigits?: number): string
Returns a string representing a number in fixed-point notation.
@paramfractionDigits Number of digits after the decimal point. Must be in the range 0 - 20, inclusive.
(2)}ms`);
}, }; const runtimeconst runtime: Runtime = createRuntimefunction createRuntime(options?: RuntimeOptions): Runtime({ middlewaremiddleware?: readonly Middleware[] | undefined
Global middleware applied to every trigger in this runtime.
: [tracingconst tracing: Middleware] });
import { createRuntimefunction createRuntime(options?: RuntimeOptions): Runtime, type Middleware
type Middleware = {
    readonly name: string;
    onFire?(ctx: FireContext): void | {
        cancel: true;
        reason: string;
    };
    onBeforeMatch?(ctx: MatchContext): void;
    onSkip?(ctx: SkipContext): void;
    onActionStart?(ctx: ActionContext): void;
    onActionEnd?(ctx: ActionContext & {
        durationMs: number;
        result?: unknown;
    }): void;
    onError?(ctx: ActionContext & {
        error: unknown;
    }): void;
    onCascade?(ctx: CascadeContext): void;
}
} from '@triggery/core';
const auditconst audit: Middleware: Middleware
type Middleware = {
    readonly name: string;
    onFire?(ctx: FireContext): void | {
        cancel: true;
        reason: string;
    };
    onBeforeMatch?(ctx: MatchContext): void;
    onSkip?(ctx: SkipContext): void;
    onActionStart?(ctx: ActionContext): void;
    onActionEnd?(ctx: ActionContext & {
        durationMs: number;
        result?: unknown;
    }): void;
    onError?(ctx: ActionContext & {
        error: unknown;
    }): void;
    onCascade?(ctx: CascadeContext): void;
}
= {
namename: string: 'audit', onError
onError?(ctx: ActionContext & {
    error: unknown;
}): void
({ triggerIdtriggerId: string, actionNameactionName: string, errorerror: unknown }) {
void fetchfunction fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch)
('/api/log', {
methodRequestInit.method?: string | undefined
A string to set request's method.
: 'POST',
bodyRequestInit.body?: BodyInit | null | undefined
A BodyInit object or null to set request's body.
: JSONvar JSON: JSON
An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format.
.stringifyJSON.stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string (+1 overload)
Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
@paramvalue A JavaScript value, usually an object or array, to be converted.@paramreplacer A function that transforms the results.@paramspace Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read.@throws{TypeError} If a circular reference or a BigInt value is found.
({
triggerIdtriggerId: string, actionNameactionName: string, messagemessage: string: errorerror: unknown instanceof Errorvar Error: ErrorConstructor ? errorerror: Error.messageError.message: string : String
var String: StringConstructor
(value?: any) => string
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
(errorerror: unknown),
stackstack: string | undefined: errorerror: unknown instanceof Errorvar Error: ErrorConstructor ? errorerror: Error.stackError.stack?: string | undefined : undefinedvar undefined, }), }); }, }; const runtimeconst runtime: Runtime = createRuntimefunction createRuntime(options?: RuntimeOptions): Runtime({ middlewaremiddleware?: readonly Middleware[] | undefined
Global middleware applied to every trigger in this runtime.
: [auditconst audit: Middleware] });
import { createRuntimefunction createRuntime(options?: RuntimeOptions): Runtime, type Middleware
type Middleware = {
    readonly name: string;
    onFire?(ctx: FireContext): void | {
        cancel: true;
        reason: string;
    };
    onBeforeMatch?(ctx: MatchContext): void;
    onSkip?(ctx: SkipContext): void;
    onActionStart?(ctx: ActionContext): void;
    onActionEnd?(ctx: ActionContext & {
        durationMs: number;
        result?: unknown;
    }): void;
    onError?(ctx: ActionContext & {
        error: unknown;
    }): void;
    onCascade?(ctx: CascadeContext): void;
}
} from '@triggery/core';
const featureFlagconst featureFlag: Middleware: Middleware
type Middleware = {
    readonly name: string;
    onFire?(ctx: FireContext): void | {
        cancel: true;
        reason: string;
    };
    onBeforeMatch?(ctx: MatchContext): void;
    onSkip?(ctx: SkipContext): void;
    onActionStart?(ctx: ActionContext): void;
    onActionEnd?(ctx: ActionContext & {
        durationMs: number;
        result?: unknown;
    }): void;
    onError?(ctx: ActionContext & {
        error: unknown;
    }): void;
    onCascade?(ctx: CascadeContext): void;
}
= {
namename: string: 'feature-flag', onFire
onFire?(ctx: FireContext): void | {
    cancel: true;
    reason: string;
}
({ eventNameeventName: string }) {
if (eventNameeventName: string === 'beta:experiment' && globalThismodule globalThis.localStoragevar localStorage: Storage
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/localStorage)
?.getItemStorage.getItem(key: string): string | null
The **`getItem()`** method of the Storage interface, when passed a key name, will return that key's value, or `null` if the key does not exist, in the given `Storage` object. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Storage/getItem)
('beta') !== 'on') {
return { cancelcancel: true: true, reasonreason: string: 'beta-disabled' }; } }, }; const runtimeconst runtime: Runtime = createRuntimefunction createRuntime(options?: RuntimeOptions): Runtime({ middlewaremiddleware?: readonly Middleware[] | undefined
Global middleware applied to every trigger in this runtime.
: [featureFlagconst featureFlag: Middleware] });
import { createRuntimefunction createRuntime(options?: RuntimeOptions): Runtime, type Middleware
type Middleware = {
    readonly name: string;
    onFire?(ctx: FireContext): void | {
        cancel: true;
        reason: string;
    };
    onBeforeMatch?(ctx: MatchContext): void;
    onSkip?(ctx: SkipContext): void;
    onActionStart?(ctx: ActionContext): void;
    onActionEnd?(ctx: ActionContext & {
        durationMs: number;
        result?: unknown;
    }): void;
    onError?(ctx: ActionContext & {
        error: unknown;
    }): void;
    onCascade?(ctx: CascadeContext): void;
}
} from '@triggery/core';
const skipCounterconst skipCounter: Map<string, number> = new Map
var Map: MapConstructor
new <string, number>(iterable?: Iterable<readonly [string, number]> | null | undefined) => Map<string, number> (+3 overloads)
<string, number>();
const counterconst counter: Middleware: Middleware
type Middleware = {
    readonly name: string;
    onFire?(ctx: FireContext): void | {
        cancel: true;
        reason: string;
    };
    onBeforeMatch?(ctx: MatchContext): void;
    onSkip?(ctx: SkipContext): void;
    onActionStart?(ctx: ActionContext): void;
    onActionEnd?(ctx: ActionContext & {
        durationMs: number;
        result?: unknown;
    }): void;
    onError?(ctx: ActionContext & {
        error: unknown;
    }): void;
    onCascade?(ctx: CascadeContext): void;
}
= {
namename: string: 'skip-counter', onSkiponSkip?(ctx: SkipContext): void({ triggerIdtriggerId: string, reasonreason: string }) { const keyconst key: string = `${triggerIdtriggerId: string}:${reasonreason: string}`; skipCounterconst skipCounter: Map<string, number>.setMap<string, number>.set(key: string, value: number): Map<string, number>
Adds a new element with a specified key and value to the Map. If an element with the same key already exists, the element will be updated.
(keyconst key: string, (skipCounterconst skipCounter: Map<string, number>.getMap<string, number>.get(key: string): number | undefined
Returns a specified element from the Map object. If the value that is associated to the provided key is an object, then you will get a reference to that object and any change made to that object will effectively modify it inside the Map.
@returnsReturns the element associated with the specified key. If no element is associated with the specified key, undefined is returned.
(keyconst key: string) ?? 0) + 1);
}, }; const runtimeconst runtime: Runtime = createRuntimefunction createRuntime(options?: RuntimeOptions): Runtime({ middlewaremiddleware?: readonly Middleware[] | undefined
Global middleware applied to every trigger in this runtime.
: [counterconst counter: Middleware] });
import { createRuntimefunction createRuntime(options?: RuntimeOptions): Runtime, type Middleware
type Middleware = {
    readonly name: string;
    onFire?(ctx: FireContext): void | {
        cancel: true;
        reason: string;
    };
    onBeforeMatch?(ctx: MatchContext): void;
    onSkip?(ctx: SkipContext): void;
    onActionStart?(ctx: ActionContext): void;
    onActionEnd?(ctx: ActionContext & {
        durationMs: number;
        result?: unknown;
    }): void;
    onError?(ctx: ActionContext & {
        error: unknown;
    }): void;
    onCascade?(ctx: CascadeContext): void;
}
} from '@triggery/core';
const guardconst guard: Middleware: Middleware
type Middleware = {
    readonly name: string;
    onFire?(ctx: FireContext): void | {
        cancel: true;
        reason: string;
    };
    onBeforeMatch?(ctx: MatchContext): void;
    onSkip?(ctx: SkipContext): void;
    onActionStart?(ctx: ActionContext): void;
    onActionEnd?(ctx: ActionContext & {
        durationMs: number;
        result?: unknown;
    }): void;
    onError?(ctx: ActionContext & {
        error: unknown;
    }): void;
    onCascade?(ctx: CascadeContext): void;
}
= {
namename: string: 'cycle-guard', onCascadeonCascade?(ctx: CascadeContext): void({ kindkind: "overflow" | "cycle", parentTriggerIdparentTriggerId: string, newEventNamenewEventName: string }) { if (kindkind: "overflow" | "cycle" === 'cycle') { consolevar console: Console.warnConsole.warn(...data: any[]): void
The **`console.warn()`** static method outputs a warning message to the console at the 'warning' log level. [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/warn_static)
(`Cycle: ${parentTriggerIdparentTriggerId: string}${newEventNamenewEventName: string} would loop back`);
} }, }; const runtimeconst runtime: Runtime = createRuntimefunction createRuntime(options?: RuntimeOptions): Runtime({ middlewaremiddleware?: readonly Middleware[] | undefined
Global middleware applied to every trigger in this runtime.
: [guardconst guard: Middleware] });