@zudojs/events 0.0.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -11
- package/dist/eventBus/eventBus.core.d.ts +28 -2
- package/dist/eventBus/eventBus.core.js +104 -31
- package/dist/eventBus/eventBus.publish.d.ts +23 -5
- package/dist/eventBus/eventBus.publish.js +72 -32
- package/dist/eventBus/eventBus.registration.d.ts +24 -13
- package/dist/eventBus/eventBus.registration.js +31 -28
- package/dist/eventBus/eventBus.type.d.ts +75 -3
- package/dist/eventBus/eventBus.type.js +9 -0
- package/dist/eventBus/index.d.ts +2 -0
- package/dist/eventBus/index.js +2 -0
- package/dist/eventEmitter/eventEmitter.abort.d.ts +11 -2
- package/dist/eventEmitter/eventEmitter.abort.js +20 -5
- package/dist/eventEmitter/eventEmitter.core.d.ts +14 -3
- package/dist/eventEmitter/eventEmitter.core.js +63 -55
- package/dist/eventEmitter/eventEmitter.parallel.d.ts +7 -1
- package/dist/eventEmitter/eventEmitter.parallel.js +31 -13
- package/dist/eventEmitter/eventEmitter.sequential.d.ts +17 -1
- package/dist/eventEmitter/eventEmitter.sequential.js +19 -12
- package/dist/eventEmitter/eventEmitter.type.d.ts +60 -0
- package/dist/eventErrors/eventError.base.d.ts +50 -3
- package/dist/eventErrors/eventError.base.js +64 -3
- package/dist/eventHandler/eventHandler.core.d.ts +19 -0
- package/dist/eventHandler/eventHandler.core.js +67 -4
- package/dist/eventMiddleware/eventMiddleware.builder.js +5 -11
- package/dist/eventMiddleware/eventMiddleware.helper.js +0 -1
- package/dist/eventMiddleware/eventMiddleware.pipeline.d.ts +5 -0
- package/dist/eventMiddleware/eventMiddleware.pipeline.js +33 -9
- package/dist/eventRegistry/eventRegistry.lifecycle.d.ts +10 -5
- package/dist/eventRegistry/eventRegistry.lifecycle.js +23 -10
- package/dist/eventRegistry/eventRegistry.queries.d.ts +16 -7
- package/dist/eventRegistry/eventRegistry.queries.js +22 -13
- package/dist/eventRegistry/eventRegistry.registration.d.ts +14 -7
- package/dist/eventRegistry/eventRegistry.registration.js +120 -41
- package/dist/eventRegistry/eventRegistry.store.d.ts +12 -5
- package/dist/eventRegistry/eventRegistry.store.js +37 -12
- package/dist/eventRegistry/eventRegistry.type.d.ts +87 -2
- package/dist/eventRegistry/eventRegistry.type.js +5 -0
- package/dist/eventSubscription/eventSubscription.core.d.ts +5 -0
- package/dist/eventSubscription/eventSubscription.core.js +17 -3
- package/dist/eventTypes/eventDefinition.type.d.ts +11 -4
- package/dist/eventTypes/eventDefinition.type.js +49 -17
- package/dist/eventTypes/eventPayload.type.d.ts +5 -0
- package/dist/eventTypes/eventPayload.type.js +56 -15
- package/dist/eventTypes/eventType.type.d.ts +22 -5
- package/dist/eventTypes/eventType.type.js +33 -11
- package/dist/eventTypes/index.d.ts +2 -2
- package/dist/eventTypes/index.js +2 -2
- package/package.json +15 -7
- package/dist/.tsbuildinfo +0 -1
- package/dist/eventBus/eventBus.core.d.ts.map +0 -1
- package/dist/eventBus/eventBus.core.js.map +0 -1
- package/dist/eventBus/eventBus.factory.d.ts.map +0 -1
- package/dist/eventBus/eventBus.factory.js.map +0 -1
- package/dist/eventBus/eventBus.publish.d.ts.map +0 -1
- package/dist/eventBus/eventBus.publish.js.map +0 -1
- package/dist/eventBus/eventBus.registration.d.ts.map +0 -1
- package/dist/eventBus/eventBus.registration.js.map +0 -1
- package/dist/eventBus/eventBus.type.d.ts.map +0 -1
- package/dist/eventBus/eventBus.type.js.map +0 -1
- package/dist/eventBus/index.d.ts.map +0 -1
- package/dist/eventBus/index.js.map +0 -1
- package/dist/eventEmitter/eventEmitter.abort.d.ts.map +0 -1
- package/dist/eventEmitter/eventEmitter.abort.js.map +0 -1
- package/dist/eventEmitter/eventEmitter.core.d.ts.map +0 -1
- package/dist/eventEmitter/eventEmitter.core.js.map +0 -1
- package/dist/eventEmitter/eventEmitter.parallel.d.ts.map +0 -1
- package/dist/eventEmitter/eventEmitter.parallel.js.map +0 -1
- package/dist/eventEmitter/eventEmitter.sequential.d.ts.map +0 -1
- package/dist/eventEmitter/eventEmitter.sequential.js.map +0 -1
- package/dist/eventEmitter/eventEmitter.type.d.ts.map +0 -1
- package/dist/eventEmitter/eventEmitter.type.js.map +0 -1
- package/dist/eventEmitter/index.d.ts.map +0 -1
- package/dist/eventEmitter/index.js.map +0 -1
- package/dist/eventErrors/eventError.base.d.ts.map +0 -1
- package/dist/eventErrors/eventError.base.js.map +0 -1
- package/dist/eventErrors/index.d.ts.map +0 -1
- package/dist/eventErrors/index.js.map +0 -1
- package/dist/eventHandler/eventHandler.core.d.ts.map +0 -1
- package/dist/eventHandler/eventHandler.core.js.map +0 -1
- package/dist/eventHandler/index.d.ts.map +0 -1
- package/dist/eventHandler/index.js.map +0 -1
- package/dist/eventMiddleware/eventMiddleware.builder.d.ts.map +0 -1
- package/dist/eventMiddleware/eventMiddleware.builder.js.map +0 -1
- package/dist/eventMiddleware/eventMiddleware.helper.d.ts.map +0 -1
- package/dist/eventMiddleware/eventMiddleware.helper.js.map +0 -1
- package/dist/eventMiddleware/eventMiddleware.pipeline.d.ts.map +0 -1
- package/dist/eventMiddleware/eventMiddleware.pipeline.js.map +0 -1
- package/dist/eventMiddleware/eventMiddleware.type.d.ts.map +0 -1
- package/dist/eventMiddleware/eventMiddleware.type.js.map +0 -1
- package/dist/eventMiddleware/index.d.ts.map +0 -1
- package/dist/eventMiddleware/index.js.map +0 -1
- package/dist/eventRegistry/eventRegistry.lifecycle.d.ts.map +0 -1
- package/dist/eventRegistry/eventRegistry.lifecycle.js.map +0 -1
- package/dist/eventRegistry/eventRegistry.queries.d.ts.map +0 -1
- package/dist/eventRegistry/eventRegistry.queries.js.map +0 -1
- package/dist/eventRegistry/eventRegistry.registration.d.ts.map +0 -1
- package/dist/eventRegistry/eventRegistry.registration.js.map +0 -1
- package/dist/eventRegistry/eventRegistry.store.d.ts.map +0 -1
- package/dist/eventRegistry/eventRegistry.store.js.map +0 -1
- package/dist/eventRegistry/eventRegistry.type.d.ts.map +0 -1
- package/dist/eventRegistry/eventRegistry.type.js.map +0 -1
- package/dist/eventRegistry/index.d.ts.map +0 -1
- package/dist/eventRegistry/index.js.map +0 -1
- package/dist/eventSubscription/eventSubscription.core.d.ts.map +0 -1
- package/dist/eventSubscription/eventSubscription.core.js.map +0 -1
- package/dist/eventSubscription/index.d.ts.map +0 -1
- package/dist/eventSubscription/index.js.map +0 -1
- package/dist/eventTypes/eventDefinition.type.d.ts.map +0 -1
- package/dist/eventTypes/eventDefinition.type.js.map +0 -1
- package/dist/eventTypes/eventPayload.type.d.ts.map +0 -1
- package/dist/eventTypes/eventPayload.type.js.map +0 -1
- package/dist/eventTypes/eventType.type.d.ts.map +0 -1
- package/dist/eventTypes/eventType.type.js.map +0 -1
- package/dist/eventTypes/index.d.ts.map +0 -1
- package/dist/eventTypes/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -1,42 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sequential event handler dispatch for Zudojs.
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { executeRegisteredEventHandler } from "../eventHandler/eventHandler.core.js";
|
|
5
|
+
import { createEventHandlerError } from "../eventErrors/eventError.base.js";
|
|
5
6
|
import { EventErrorMode } from "./eventEmitter.type.js";
|
|
6
7
|
import { createAbortError } from "./eventEmitter.abort.js";
|
|
7
8
|
/**
|
|
8
9
|
* Executes handlers sequentially.
|
|
9
10
|
*/
|
|
10
|
-
export async function emitSequential(handlers, event, context, errorMode, results, errors,
|
|
11
|
+
export async function emitSequential(handlers, event, context, errorMode, results, errors, hooks) {
|
|
11
12
|
for (const handler of handlers) {
|
|
12
13
|
if (context.signal.aborted) {
|
|
13
|
-
throw createAbortError();
|
|
14
|
+
throw createAbortError(event, results, errors);
|
|
15
|
+
}
|
|
16
|
+
if (handler.once) {
|
|
17
|
+
if (!hooks.isRegistered(handler.id)) {
|
|
18
|
+
// Already consumed by an overlapping dispatch.
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
hooks.removeOnce(handler.id);
|
|
14
22
|
}
|
|
15
23
|
const started = performance.now();
|
|
16
24
|
try {
|
|
17
|
-
const result = await
|
|
25
|
+
const result = await executeRegisteredEventHandler(handler, event, context);
|
|
18
26
|
results.push({
|
|
19
27
|
handlerId: handler.id,
|
|
20
28
|
eventId: event.id,
|
|
29
|
+
ok: true,
|
|
21
30
|
result,
|
|
22
31
|
duration: performance.now() - started,
|
|
23
32
|
});
|
|
24
|
-
if (handler.once) {
|
|
25
|
-
onOnceHandler(handler.id);
|
|
26
|
-
}
|
|
27
33
|
}
|
|
28
34
|
catch (error) {
|
|
29
|
-
|
|
35
|
+
results.push({
|
|
30
36
|
handlerId: handler.id,
|
|
31
37
|
eventId: event.id,
|
|
38
|
+
ok: false,
|
|
32
39
|
result: undefined,
|
|
33
40
|
duration: performance.now() - started,
|
|
34
41
|
error,
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
errors.push(
|
|
42
|
+
});
|
|
43
|
+
const wrapped = createEventHandlerError(handler.id, event.type, event.id, error);
|
|
44
|
+
errors.push(wrapped);
|
|
38
45
|
if (errorMode === EventErrorMode.THROW) {
|
|
39
|
-
throw
|
|
46
|
+
throw wrapped;
|
|
40
47
|
}
|
|
41
48
|
}
|
|
42
49
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import type { Event } from "../eventTypes/eventDefinition.type.js";
|
|
5
5
|
import type { RegisteredEventHandler } from "../eventHandler/eventHandler.core.js";
|
|
6
6
|
import type { EventSubscription } from "../eventSubscription/eventSubscription.core.js";
|
|
7
|
+
import type { EventHandlerStore, EventRegistryWarning } from "../eventRegistry/eventRegistry.type.js";
|
|
7
8
|
export declare enum EventEmitterMode {
|
|
8
9
|
SEQUENTIAL = "sequential",
|
|
9
10
|
PARALLEL = "parallel"
|
|
@@ -13,9 +14,39 @@ export declare enum EventErrorMode {
|
|
|
13
14
|
CONTINUE = "continue"
|
|
14
15
|
}
|
|
15
16
|
export interface EventEmitterOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Dispatch mode. Defaults to SEQUENTIAL.
|
|
19
|
+
*/
|
|
16
20
|
readonly mode?: EventEmitterMode;
|
|
21
|
+
/**
|
|
22
|
+
* Error mode. Defaults to THROW.
|
|
23
|
+
*/
|
|
17
24
|
readonly errorMode?: EventErrorMode;
|
|
25
|
+
/**
|
|
26
|
+
* Deep-freeze events (including their payload) before they are
|
|
27
|
+
* handed to handlers so no handler can alter what other handlers
|
|
28
|
+
* see. Defaults to true. Note that freezing mutates the payload
|
|
29
|
+
* object passed in; clone it first if you need to keep it
|
|
30
|
+
* mutable elsewhere.
|
|
31
|
+
*/
|
|
18
32
|
readonly freezeEvents?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Handler store to use. When omitted the emitter creates a
|
|
35
|
+
* private EventRegistry. EventBus passes its registry so that
|
|
36
|
+
* handlers registered on either side are dispatched.
|
|
37
|
+
*/
|
|
38
|
+
readonly store?: EventHandlerStore;
|
|
39
|
+
/**
|
|
40
|
+
* Maximum handlers per event pattern before a leak warning is
|
|
41
|
+
* emitted (0 disables). Only applies to the private store.
|
|
42
|
+
* Defaults to 100.
|
|
43
|
+
*/
|
|
44
|
+
readonly maxListeners?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Receives leak warnings. Only applies to the private store.
|
|
47
|
+
* Defaults to console.warn.
|
|
48
|
+
*/
|
|
49
|
+
readonly onWarning?: (warning: EventRegistryWarning) => void;
|
|
19
50
|
}
|
|
20
51
|
export interface EmitOptions {
|
|
21
52
|
readonly mode?: EventEmitterMode;
|
|
@@ -26,16 +57,45 @@ export interface EmitOptions {
|
|
|
26
57
|
export interface EventHandlerExecutionResult {
|
|
27
58
|
readonly handlerId: string;
|
|
28
59
|
readonly eventId: string;
|
|
60
|
+
/**
|
|
61
|
+
* True when the handler settled without throwing.
|
|
62
|
+
*/
|
|
63
|
+
readonly ok: boolean;
|
|
29
64
|
readonly result: unknown;
|
|
30
65
|
readonly duration: number;
|
|
66
|
+
/**
|
|
67
|
+
* The raw value thrown by the handler when `ok` is false.
|
|
68
|
+
*/
|
|
31
69
|
readonly error?: unknown;
|
|
32
70
|
}
|
|
33
71
|
export interface EventEmitResult<TEvent extends Event = Event> {
|
|
34
72
|
readonly event: TEvent;
|
|
73
|
+
/**
|
|
74
|
+
* True when at least one handler completed successfully.
|
|
75
|
+
*/
|
|
35
76
|
readonly handled: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* One entry per invoked handler, in invocation order.
|
|
79
|
+
*/
|
|
36
80
|
readonly results: readonly EventHandlerExecutionResult[];
|
|
81
|
+
/**
|
|
82
|
+
* Handler failures wrapped as EventHandlerError (cause holds the
|
|
83
|
+
* raw thrown value).
|
|
84
|
+
*/
|
|
37
85
|
readonly errors: readonly unknown[];
|
|
86
|
+
/**
|
|
87
|
+
* Number of handlers that completed successfully.
|
|
88
|
+
*/
|
|
89
|
+
readonly succeeded: number;
|
|
90
|
+
/**
|
|
91
|
+
* Number of handlers that threw.
|
|
92
|
+
*/
|
|
93
|
+
readonly failed: number;
|
|
38
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* @deprecated Handlers are stored in an EventRegistry; this shape
|
|
97
|
+
* is kept for type compatibility only.
|
|
98
|
+
*/
|
|
39
99
|
export interface EmitterListener {
|
|
40
100
|
readonly registration: RegisteredEventHandler;
|
|
41
101
|
readonly subscription: EventSubscription;
|
|
@@ -1,8 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @zudojs/events/eventErrors/eventError.base
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Event error types are centralized in @zudojs/errors and
|
|
5
|
+
* re-exported here. A few event-bus specific errors that the
|
|
6
|
+
* errors package does not define yet live in this file.
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
|
+
import { EventError, EventDispatchAbortedError as BaseEventDispatchAbortedError } from "@zudojs/errors";
|
|
9
|
+
export { EventError, createEventError, isEventError, toEventError, EventPublishError, InvalidEventError, EventTypeNotFoundError, EventHandlerError, createEventHandlerError, EventHandlerNotFoundError, DuplicateEventHandlerError, DuplicateEventDefinitionError, EventDefinitionNotFoundError, EventEmitterDisposedError, EventRegistryDisposedError, EventSubscriptionClosedError, EventTimeoutError, EventMiddlewareError, EventSerializationError, EventDeserializationError, } from "@zudojs/errors";
|
|
10
|
+
/**
|
|
11
|
+
* Options for EventDispatchAbortedError.
|
|
12
|
+
*/
|
|
13
|
+
export interface EventDispatchAbortedErrorOptions {
|
|
14
|
+
readonly eventType?: string;
|
|
15
|
+
readonly eventId?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Execution results collected before the abort was observed.
|
|
18
|
+
*/
|
|
19
|
+
readonly results?: readonly unknown[];
|
|
20
|
+
/**
|
|
21
|
+
* Handler errors collected before the abort was observed.
|
|
22
|
+
*/
|
|
23
|
+
readonly errors?: readonly unknown[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Error thrown when event dispatch is aborted through an
|
|
27
|
+
* AbortSignal. Carries the partial results and errors gathered
|
|
28
|
+
* before the abort was observed so callers can see what ran.
|
|
29
|
+
*/
|
|
30
|
+
export declare class EventDispatchAbortedError extends BaseEventDispatchAbortedError {
|
|
31
|
+
readonly results: readonly unknown[];
|
|
32
|
+
readonly errors: readonly unknown[];
|
|
33
|
+
constructor(message?: string, options?: EventDispatchAbortedErrorOptions);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Error thrown when an EventBus is used after dispose().
|
|
37
|
+
*/
|
|
38
|
+
export declare class EventBusDisposedError extends EventError {
|
|
39
|
+
constructor();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Error thrown when publishing or subscribing on a stopped
|
|
43
|
+
* EventBus. Call start() to resume.
|
|
44
|
+
*/
|
|
45
|
+
export declare class EventBusStoppedError extends EventError {
|
|
46
|
+
constructor(operation: string);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Error thrown when the listener limit of an emitter or registry
|
|
50
|
+
* is exceeded and the limit is configured to be enforced.
|
|
51
|
+
*/
|
|
52
|
+
export declare class EventListenerLimitExceededError extends EventError {
|
|
53
|
+
constructor(pattern: string, limit: number);
|
|
54
|
+
}
|
|
8
55
|
//# sourceMappingURL=eventError.base.d.ts.map
|
|
@@ -1,8 +1,69 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @zudojs/events/eventErrors/eventError.base
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Event error types are centralized in @zudojs/errors and
|
|
5
|
+
* re-exported here. A few event-bus specific errors that the
|
|
6
|
+
* errors package does not define yet live in this file.
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
|
+
import { ErrorCode, EventError, EventDispatchAbortedError as BaseEventDispatchAbortedError, } from "@zudojs/errors";
|
|
9
|
+
export { EventError, createEventError, isEventError, toEventError, EventPublishError, InvalidEventError, EventTypeNotFoundError, EventHandlerError, createEventHandlerError, EventHandlerNotFoundError, DuplicateEventHandlerError, DuplicateEventDefinitionError, EventDefinitionNotFoundError, EventEmitterDisposedError, EventRegistryDisposedError, EventSubscriptionClosedError, EventTimeoutError, EventMiddlewareError, EventSerializationError, EventDeserializationError, } from "@zudojs/errors";
|
|
10
|
+
/**
|
|
11
|
+
* Error thrown when event dispatch is aborted through an
|
|
12
|
+
* AbortSignal. Carries the partial results and errors gathered
|
|
13
|
+
* before the abort was observed so callers can see what ran.
|
|
14
|
+
*/
|
|
15
|
+
export class EventDispatchAbortedError extends BaseEventDispatchAbortedError {
|
|
16
|
+
results;
|
|
17
|
+
errors;
|
|
18
|
+
constructor(message = "Event dispatch was aborted.", options = {}) {
|
|
19
|
+
super(message, {
|
|
20
|
+
eventType: options.eventType,
|
|
21
|
+
eventId: options.eventId,
|
|
22
|
+
});
|
|
23
|
+
this.results = Object.freeze([...(options.results ?? [])]);
|
|
24
|
+
this.errors = Object.freeze([...(options.errors ?? [])]);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Error thrown when an EventBus is used after dispose().
|
|
29
|
+
*/
|
|
30
|
+
export class EventBusDisposedError extends EventError {
|
|
31
|
+
constructor() {
|
|
32
|
+
super("Event bus has already been disposed.", {
|
|
33
|
+
code: ErrorCode.LIFECYCLE_DISPOSED,
|
|
34
|
+
statusCode: 500,
|
|
35
|
+
expose: false,
|
|
36
|
+
isOperational: false,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Error thrown when publishing or subscribing on a stopped
|
|
42
|
+
* EventBus. Call start() to resume.
|
|
43
|
+
*/
|
|
44
|
+
export class EventBusStoppedError extends EventError {
|
|
45
|
+
constructor(operation) {
|
|
46
|
+
super(`Cannot ${operation} on a stopped event bus. Call start() first.`, {
|
|
47
|
+
code: ErrorCode.LIFECYCLE_STATE,
|
|
48
|
+
statusCode: 500,
|
|
49
|
+
expose: false,
|
|
50
|
+
isOperational: true,
|
|
51
|
+
metadata: { operation },
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Error thrown when the listener limit of an emitter or registry
|
|
57
|
+
* is exceeded and the limit is configured to be enforced.
|
|
58
|
+
*/
|
|
59
|
+
export class EventListenerLimitExceededError extends EventError {
|
|
60
|
+
constructor(pattern, limit) {
|
|
61
|
+
super(`Listener limit of ${limit} exceeded for event pattern "${pattern}".`, {
|
|
62
|
+
code: ErrorCode.LIFECYCLE_STATE,
|
|
63
|
+
statusCode: 500,
|
|
64
|
+
expose: false,
|
|
65
|
+
metadata: { pattern, limit },
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
8
69
|
//# sourceMappingURL=eventError.base.js.map
|
|
@@ -94,6 +94,14 @@ export interface EventHandlerOptions {
|
|
|
94
94
|
* Whether the handler should only execute once.
|
|
95
95
|
*/
|
|
96
96
|
readonly once?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Optional execution timeout in milliseconds.
|
|
99
|
+
*
|
|
100
|
+
* When the handler does not settle within this time the
|
|
101
|
+
* execution fails with EventTimeoutError. Defaults to no
|
|
102
|
+
* timeout.
|
|
103
|
+
*/
|
|
104
|
+
readonly timeoutMs?: number;
|
|
97
105
|
}
|
|
98
106
|
/**
|
|
99
107
|
* Complete registered event handler.
|
|
@@ -123,6 +131,10 @@ export interface RegisteredEventHandler<TEvent extends Event = Event> {
|
|
|
123
131
|
* Optional description.
|
|
124
132
|
*/
|
|
125
133
|
readonly description?: string;
|
|
134
|
+
/**
|
|
135
|
+
* Optional execution timeout in milliseconds.
|
|
136
|
+
*/
|
|
137
|
+
readonly timeoutMs?: number;
|
|
126
138
|
/**
|
|
127
139
|
* Actual handler.
|
|
128
140
|
*/
|
|
@@ -157,6 +169,13 @@ export declare function isEventHandler(value: unknown): value is EventHandlerLik
|
|
|
157
169
|
* represented as a function or an object.
|
|
158
170
|
*/
|
|
159
171
|
export declare function executeEventHandler<TEvent extends Event>(handler: EventHandlerLike<TEvent>, event: TEvent, context: EventHandlerContext<TEvent>): Promise<EventHandlerResult>;
|
|
172
|
+
/**
|
|
173
|
+
* Executes a registered handler, applying its timeout when one
|
|
174
|
+
* is configured. A timed-out execution rejects with
|
|
175
|
+
* EventTimeoutError; the underlying handler keeps running but its
|
|
176
|
+
* eventual result is ignored.
|
|
177
|
+
*/
|
|
178
|
+
export declare function executeRegisteredEventHandler<TEvent extends Event>(registration: RegisteredEventHandler<TEvent>, event: TEvent, context: EventHandlerContext<TEvent>): Promise<EventHandlerResult>;
|
|
160
179
|
/**
|
|
161
180
|
* Determines whether a registered handler should process
|
|
162
181
|
* a given event.
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Handlers are responsible for processing events.
|
|
5
5
|
* They do not own event registration or event dispatching.
|
|
6
6
|
*/
|
|
7
|
-
import { matchesEventType } from "../eventTypes/eventType.type.js";
|
|
7
|
+
import { isValidEventTypePattern, matchesEventType, normalizeEventTypePattern, } from "../eventTypes/eventType.type.js";
|
|
8
|
+
import { EventTimeoutError } from "../eventErrors/eventError.base.js";
|
|
8
9
|
/**
|
|
9
10
|
* Generates a handler identifier.
|
|
10
11
|
*/
|
|
@@ -37,17 +38,43 @@ export function createEventHandlerContext(event, options = {}) {
|
|
|
37
38
|
* Creates a registered event handler.
|
|
38
39
|
*/
|
|
39
40
|
export function createEventHandler(handler, options = {}) {
|
|
40
|
-
const eventType = options.eventType ?? "*";
|
|
41
41
|
if (!isValidHandler(handler)) {
|
|
42
42
|
throw new TypeError("Invalid event handler.");
|
|
43
43
|
}
|
|
44
|
+
const rawPattern = options.eventType ?? "*";
|
|
45
|
+
let eventType;
|
|
46
|
+
try {
|
|
47
|
+
eventType = normalizeEventTypePattern(rawPattern);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
throw new TypeError(`Invalid event type pattern "${String(rawPattern)}".`);
|
|
51
|
+
}
|
|
52
|
+
if (!isValidEventTypePattern(eventType)) {
|
|
53
|
+
throw new TypeError(`Invalid event type pattern "${String(rawPattern)}".`);
|
|
54
|
+
}
|
|
55
|
+
const priority = options.priority ?? 0;
|
|
56
|
+
if (typeof priority !== "number" || !Number.isFinite(priority)) {
|
|
57
|
+
throw new RangeError("Event handler priority must be a finite number.");
|
|
58
|
+
}
|
|
59
|
+
if (options.id !== undefined &&
|
|
60
|
+
(typeof options.id !== "string" || options.id.length === 0)) {
|
|
61
|
+
throw new TypeError("Event handler id must be a non-empty string.");
|
|
62
|
+
}
|
|
63
|
+
const timeoutMs = options.timeoutMs;
|
|
64
|
+
if (timeoutMs !== undefined &&
|
|
65
|
+
(typeof timeoutMs !== "number" ||
|
|
66
|
+
!Number.isFinite(timeoutMs) ||
|
|
67
|
+
timeoutMs <= 0)) {
|
|
68
|
+
throw new RangeError("Event handler timeoutMs must be a positive finite number.");
|
|
69
|
+
}
|
|
44
70
|
return Object.freeze({
|
|
45
71
|
id: options.id ?? createEventHandlerId(),
|
|
46
72
|
eventType,
|
|
47
|
-
priority
|
|
73
|
+
priority,
|
|
48
74
|
enabled: options.enabled ?? true,
|
|
49
75
|
once: options.once ?? false,
|
|
50
76
|
description: options.description,
|
|
77
|
+
timeoutMs,
|
|
51
78
|
handler,
|
|
52
79
|
});
|
|
53
80
|
}
|
|
@@ -87,6 +114,38 @@ export async function executeEventHandler(handler, event, context) {
|
|
|
87
114
|
}
|
|
88
115
|
return handler.handle(event, context);
|
|
89
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Executes a registered handler, applying its timeout when one
|
|
119
|
+
* is configured. A timed-out execution rejects with
|
|
120
|
+
* EventTimeoutError; the underlying handler keeps running but its
|
|
121
|
+
* eventual result is ignored.
|
|
122
|
+
*/
|
|
123
|
+
export async function executeRegisteredEventHandler(registration, event, context) {
|
|
124
|
+
const timeoutMs = registration.timeoutMs;
|
|
125
|
+
if (timeoutMs === undefined) {
|
|
126
|
+
return executeEventHandler(registration.handler, event, context);
|
|
127
|
+
}
|
|
128
|
+
let timer;
|
|
129
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
130
|
+
timer = setTimeout(() => {
|
|
131
|
+
reject(new EventTimeoutError(timeoutMs, {
|
|
132
|
+
eventType: event.type,
|
|
133
|
+
eventId: event.id,
|
|
134
|
+
}));
|
|
135
|
+
}, timeoutMs);
|
|
136
|
+
});
|
|
137
|
+
try {
|
|
138
|
+
return await Promise.race([
|
|
139
|
+
executeEventHandler(registration.handler, event, context),
|
|
140
|
+
timeout,
|
|
141
|
+
]);
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
if (timer !== undefined) {
|
|
145
|
+
clearTimeout(timer);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
90
149
|
/**
|
|
91
150
|
* Determines whether a registered handler should process
|
|
92
151
|
* a given event.
|
|
@@ -104,7 +163,11 @@ export function handlerMatchesEvent(handler, event) {
|
|
|
104
163
|
* Registration order is preserved for equal priorities.
|
|
105
164
|
*/
|
|
106
165
|
export function sortEventHandlers(handlers) {
|
|
107
|
-
return [...handlers].sort((first, second) =>
|
|
166
|
+
return [...handlers].sort((first, second) => {
|
|
167
|
+
const a = Number.isFinite(first.priority) ? first.priority : 0;
|
|
168
|
+
const b = Number.isFinite(second.priority) ? second.priority : 0;
|
|
169
|
+
return b - a;
|
|
170
|
+
});
|
|
108
171
|
}
|
|
109
172
|
/**
|
|
110
173
|
* Filters handlers that can process an event.
|
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Event middleware builder functions for Zudojs.
|
|
3
3
|
*/
|
|
4
|
-
import { EventMiddlewareError } from "../eventErrors/eventError.base.js";
|
|
4
|
+
import { EventDispatchAbortedError, EventMiddlewareError, } from "../eventErrors/eventError.base.js";
|
|
5
5
|
import { createEventMiddleware } from "./eventMiddleware.helper.js";
|
|
6
|
-
/**
|
|
7
|
-
* Creates an AbortError without relying on a runtime-specific
|
|
8
|
-
* DOMException implementation.
|
|
9
|
-
*/
|
|
10
|
-
function createAbortError() {
|
|
11
|
-
return new EventMiddlewareError("Event middleware execution was aborted.", {
|
|
12
|
-
cause: new Error("AbortSignal was aborted."),
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
6
|
/**
|
|
16
7
|
* Creates a middleware that executes before downstream
|
|
17
8
|
* middleware and handlers.
|
|
@@ -102,7 +93,10 @@ export function disableEventMiddleware(middleware) {
|
|
|
102
93
|
export function abortableEventMiddleware(options = {}) {
|
|
103
94
|
return beforeEvent(async (context) => {
|
|
104
95
|
if (context.signal.aborted) {
|
|
105
|
-
throw
|
|
96
|
+
throw new EventDispatchAbortedError("Event dispatch was aborted.", {
|
|
97
|
+
eventType: context.event?.type,
|
|
98
|
+
eventId: context.event?.id,
|
|
99
|
+
});
|
|
106
100
|
}
|
|
107
101
|
}, options);
|
|
108
102
|
}
|
|
@@ -13,6 +13,11 @@ import type { EventMiddlewareContext, EventMiddlewareNext, RegisteredEventMiddle
|
|
|
13
13
|
* → handler
|
|
14
14
|
* ← middleware B
|
|
15
15
|
* ← middleware A
|
|
16
|
+
*
|
|
17
|
+
* Only errors thrown by a middleware function itself are wrapped
|
|
18
|
+
* in EventMiddlewareError. Errors coming back through next() —
|
|
19
|
+
* handler failures, aborts, downstream middleware errors — are
|
|
20
|
+
* re-thrown untouched so callers can discriminate them.
|
|
16
21
|
*/
|
|
17
22
|
export declare function executeEventMiddlewarePipeline<TEvent extends Event, TResult>(middleware: readonly RegisteredEventMiddleware<TEvent, TResult>[], context: EventMiddlewareContext<TEvent>, terminal: EventMiddlewareNext<TResult>): Promise<EventMiddlewarePipelineResult<TResult>>;
|
|
18
23
|
//# sourceMappingURL=eventMiddleware.pipeline.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Event middleware pipeline execution for Zudojs.
|
|
3
3
|
*/
|
|
4
|
-
import { EventMiddlewareError, toEventError, } from "../eventErrors/eventError.base.js";
|
|
4
|
+
import { EventDispatchAbortedError, EventMiddlewareError, toEventError, } from "../eventErrors/eventError.base.js";
|
|
5
5
|
import { sortEventMiddleware, executeEventMiddleware, } from "./eventMiddleware.helper.js";
|
|
6
6
|
/**
|
|
7
7
|
* Executes a middleware pipeline.
|
|
@@ -13,6 +13,11 @@ import { sortEventMiddleware, executeEventMiddleware, } from "./eventMiddleware.
|
|
|
13
13
|
* → handler
|
|
14
14
|
* ← middleware B
|
|
15
15
|
* ← middleware A
|
|
16
|
+
*
|
|
17
|
+
* Only errors thrown by a middleware function itself are wrapped
|
|
18
|
+
* in EventMiddlewareError. Errors coming back through next() —
|
|
19
|
+
* handler failures, aborts, downstream middleware errors — are
|
|
20
|
+
* re-thrown untouched so callers can discriminate them.
|
|
16
21
|
*/
|
|
17
22
|
export async function executeEventMiddlewarePipeline(middleware, context, terminal) {
|
|
18
23
|
const started = performance.now();
|
|
@@ -21,7 +26,7 @@ export async function executeEventMiddlewarePipeline(middleware, context, termin
|
|
|
21
26
|
let index = -1;
|
|
22
27
|
const dispatch = async (currentIndex) => {
|
|
23
28
|
if (context.signal.aborted) {
|
|
24
|
-
throw createAbortError();
|
|
29
|
+
throw createAbortError(context);
|
|
25
30
|
}
|
|
26
31
|
if (currentIndex === activeMiddleware.length) {
|
|
27
32
|
return terminal();
|
|
@@ -39,6 +44,13 @@ export async function executeEventMiddlewarePipeline(middleware, context, termin
|
|
|
39
44
|
}
|
|
40
45
|
const middlewareStarted = performance.now();
|
|
41
46
|
let nextCalled = false;
|
|
47
|
+
/**
|
|
48
|
+
* Errors that surfaced through next() belong to downstream
|
|
49
|
+
* code, not to this middleware; they must pass through
|
|
50
|
+
* unwrapped.
|
|
51
|
+
*/
|
|
52
|
+
let downstreamThrew = false;
|
|
53
|
+
let downstreamError;
|
|
42
54
|
const next = async () => {
|
|
43
55
|
if (nextCalled) {
|
|
44
56
|
throw new EventMiddlewareError(`Middleware "${current.id}" called next() more than once.`, {
|
|
@@ -48,7 +60,14 @@ export async function executeEventMiddlewarePipeline(middleware, context, termin
|
|
|
48
60
|
});
|
|
49
61
|
}
|
|
50
62
|
nextCalled = true;
|
|
51
|
-
|
|
63
|
+
try {
|
|
64
|
+
return await dispatch(currentIndex + 1);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
downstreamThrew = true;
|
|
68
|
+
downstreamError = error;
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
52
71
|
};
|
|
53
72
|
try {
|
|
54
73
|
const result = await executeEventMiddleware(current.middleware, context, next);
|
|
@@ -60,7 +79,11 @@ export async function executeEventMiddlewarePipeline(middleware, context, termin
|
|
|
60
79
|
return result;
|
|
61
80
|
}
|
|
62
81
|
catch (error) {
|
|
63
|
-
if (error
|
|
82
|
+
if (downstreamThrew && error === downstreamError) {
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
if (error instanceof EventMiddlewareError ||
|
|
86
|
+
error instanceof EventDispatchAbortedError) {
|
|
64
87
|
throw error;
|
|
65
88
|
}
|
|
66
89
|
throw new EventMiddlewareError(`Event middleware "${current.id}" failed.`, {
|
|
@@ -82,12 +105,13 @@ export async function executeEventMiddlewarePipeline(middleware, context, termin
|
|
|
82
105
|
};
|
|
83
106
|
}
|
|
84
107
|
/**
|
|
85
|
-
* Creates
|
|
86
|
-
*
|
|
108
|
+
* Creates the abort error thrown when the pipeline observes an
|
|
109
|
+
* aborted signal.
|
|
87
110
|
*/
|
|
88
|
-
function createAbortError() {
|
|
89
|
-
return new
|
|
90
|
-
|
|
111
|
+
function createAbortError(context) {
|
|
112
|
+
return new EventDispatchAbortedError("Event dispatch was aborted.", {
|
|
113
|
+
eventType: context.event?.type,
|
|
114
|
+
eventId: context.event?.id,
|
|
91
115
|
});
|
|
92
116
|
}
|
|
93
117
|
//# sourceMappingURL=eventMiddleware.pipeline.js.map
|
|
@@ -2,18 +2,23 @@
|
|
|
2
2
|
* Event registry lifecycle methods for Zudojs.
|
|
3
3
|
*/
|
|
4
4
|
import type { EventType } from "../eventTypes/eventDefinition.type.js";
|
|
5
|
-
import type {
|
|
6
|
-
import type { EventRegistryChange, EventRegistryListener, RegisteredEventDefinition } from "./eventRegistry.type.js";
|
|
5
|
+
import type { EventHandlerEntry, EventRegistryChange, EventRegistryErrorContext, EventRegistryListener, RegisteredEventDefinition } from "./eventRegistry.type.js";
|
|
7
6
|
/**
|
|
8
7
|
* Clears all handlers and definitions from the registry.
|
|
8
|
+
*
|
|
9
|
+
* Every handler subscription is cancelled, so subscriptions held
|
|
10
|
+
* by callers report `active: false` afterwards.
|
|
9
11
|
*/
|
|
10
|
-
export declare function registryClear(definitions: Map<EventType, RegisteredEventDefinition>, handlers: Map<string,
|
|
12
|
+
export declare function registryClear(definitions: Map<EventType, RegisteredEventDefinition>, handlers: Map<string, EventHandlerEntry>, ensureActive: () => void, notify: (change: EventRegistryChange) => void): void;
|
|
11
13
|
/**
|
|
12
14
|
* Disposes the registry.
|
|
13
15
|
*/
|
|
14
|
-
export declare function registryDispose(disposed: boolean, definitions: Map<EventType, RegisteredEventDefinition>, handlers: Map<string,
|
|
16
|
+
export declare function registryDispose(disposed: boolean, definitions: Map<EventType, RegisteredEventDefinition>, handlers: Map<string, EventHandlerEntry>, listeners: Set<EventRegistryListener>, ensureActive: () => void, notify: (change: EventRegistryChange) => void): void;
|
|
15
17
|
/**
|
|
16
18
|
* Notifies registry listeners.
|
|
19
|
+
*
|
|
20
|
+
* Observer failures never break registry mutations; they are
|
|
21
|
+
* forwarded to the `onError` hook when one is configured.
|
|
17
22
|
*/
|
|
18
|
-
export declare function registryNotify(change: EventRegistryChange, listeners: Set<EventRegistryListener
|
|
23
|
+
export declare function registryNotify(change: EventRegistryChange, listeners: Set<EventRegistryListener>, onError?: (error: unknown, context: EventRegistryErrorContext) => void): void;
|
|
19
24
|
//# sourceMappingURL=eventRegistry.lifecycle.d.ts.map
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Event registry lifecycle methods for Zudojs.
|
|
3
3
|
*/
|
|
4
|
-
import { registryUnregister
|
|
4
|
+
import { registryUnregister } from "./eventRegistry.registration.js";
|
|
5
5
|
/**
|
|
6
6
|
* Clears all handlers and definitions from the registry.
|
|
7
|
+
*
|
|
8
|
+
* Every handler subscription is cancelled, so subscriptions held
|
|
9
|
+
* by callers report `active: false` afterwards.
|
|
7
10
|
*/
|
|
8
11
|
export function registryClear(definitions, handlers, ensureActive, notify) {
|
|
9
12
|
ensureActive();
|
|
10
|
-
const
|
|
11
|
-
for (const
|
|
12
|
-
|
|
13
|
+
const entries = [...handlers.values()];
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
entry.subscription.unsubscribe();
|
|
13
16
|
}
|
|
17
|
+
handlers.clear();
|
|
14
18
|
const eventTypes = [...definitions.keys()];
|
|
15
19
|
for (const eventType of eventTypes) {
|
|
16
20
|
registryUnregister(eventType, definitions, ensureActive, notify);
|
|
@@ -28,17 +32,26 @@ export function registryDispose(disposed, definitions, handlers, listeners, ensu
|
|
|
28
32
|
}
|
|
29
33
|
/**
|
|
30
34
|
* Notifies registry listeners.
|
|
35
|
+
*
|
|
36
|
+
* Observer failures never break registry mutations; they are
|
|
37
|
+
* forwarded to the `onError` hook when one is configured.
|
|
31
38
|
*/
|
|
32
|
-
export function registryNotify(change, listeners) {
|
|
39
|
+
export function registryNotify(change, listeners, onError) {
|
|
33
40
|
for (const listener of listeners) {
|
|
34
41
|
try {
|
|
35
42
|
listener(change);
|
|
36
43
|
}
|
|
37
|
-
catch {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (onError) {
|
|
46
|
+
try {
|
|
47
|
+
onError(error, { source: "observer", change });
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
/**
|
|
51
|
+
* A failing error hook must not break the mutation either.
|
|
52
|
+
*/
|
|
53
|
+
}
|
|
54
|
+
}
|
|
42
55
|
}
|
|
43
56
|
}
|
|
44
57
|
}
|