@zudojs/events 1.0.1 → 1.2.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 +9 -2
- package/dist/eventBus/eventBus.core.js +17 -8
- package/dist/eventBus/eventBus.type.d.ts +6 -1
- package/dist/eventEmitter/eventEmitter.core.js +19 -4
- package/dist/eventEmitter/eventEmitter.parallel.js +3 -3
- package/dist/eventEmitter/eventEmitter.sequential.js +6 -4
- package/dist/eventEmitter/eventEmitter.type.d.ts +6 -1
- package/dist/eventErrors/eventError.base.d.ts +1 -8
- package/dist/eventErrors/eventError.base.js +1 -15
- package/dist/eventErrors/eventWarning.helper.d.ts +33 -0
- package/dist/eventErrors/eventWarning.helper.js +49 -0
- package/dist/eventMiddleware/eventMiddleware.pipeline.d.ts +2 -1
- package/dist/eventMiddleware/eventMiddleware.pipeline.js +38 -56
- package/dist/eventRegistry/eventRegistry.lifecycle.d.ts +3 -1
- package/dist/eventRegistry/eventRegistry.lifecycle.js +7 -1
- package/dist/eventRegistry/eventRegistry.registration.d.ts +1 -0
- package/dist/eventRegistry/eventRegistry.registration.js +18 -6
- package/dist/eventRegistry/eventRegistry.store.js +9 -1
- package/dist/eventRegistry/eventRegistry.type.d.ts +12 -2
- package/dist/eventTypes/eventSnapshot.freeze.d.ts +37 -0
- package/dist/eventTypes/eventSnapshot.freeze.js +117 -0
- package/dist/eventTypes/index.d.ts +1 -0
- package/dist/eventTypes/index.js +1 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Event-driven architecture with event bus, emitter, middleware, and registry for decoupled communication.
|
|
4
4
|
|
|
5
|
+
<!-- zudo-docs:start -->
|
|
6
|
+
|
|
7
|
+
**Documentation:** [zudojs.oyinlola.site/docs/packages-events](https://zudojs.oyinlola.site/docs/packages-events) · **For AI agents:** [Markdown version](https://zudojs.oyinlola.site/docs/packages-events.md), [llms.txt](https://zudojs.oyinlola.site/llms.txt)
|
|
8
|
+
|
|
9
|
+
<!-- zudo-docs:end -->
|
|
10
|
+
|
|
5
11
|
## Installation
|
|
6
12
|
|
|
7
13
|
```bash
|
|
@@ -46,8 +52,9 @@ await bus.publishEvent({
|
|
|
46
52
|
- Handler priorities, one-time handlers, per-handler timeouts
|
|
47
53
|
- Wildcard subscriptions (`"user.*"`, `"*"`)
|
|
48
54
|
- Event registry for typed definitions; handlers registered on the registry are dispatched by the bus
|
|
49
|
-
- Deep-frozen events (`freezeEvents`, on by default) so handlers cannot alter what other handlers see
|
|
50
|
-
-
|
|
55
|
+
- Deep-frozen events (`freezeEvents`, on by default) so handlers cannot alter what other handlers see: handlers receive a frozen *copy* (`createFrozenEventSnapshot`), so the publisher's own objects are never frozen, and Map, Set and Date values (including `event.timestamp`) become read-only variants that throw on mutation. Class instances are passed by reference.
|
|
56
|
+
- A handler unsubscribed by an earlier handler in the same dispatch, or left over after the bus is disposed mid-dispatch, does not run
|
|
57
|
+
- Listener-leak warnings (`maxListeners`, reported through `onWarning` or, by default, `process.emitWarning` with type `ZudojsEventsWarning`) and an `onError` hook for fire-and-forget publishes
|
|
51
58
|
- Typed error classes from `@zudojs/errors` (`EventHandlerError`, `EventMiddlewareError`, `EventDispatchAbortedError`, …)
|
|
52
59
|
|
|
53
60
|
## Lifecycle
|
|
@@ -7,6 +7,7 @@ import { EventErrorMode } from "../eventEmitter/eventEmitter.type.js";
|
|
|
7
7
|
import { EventRegistry } from "../eventRegistry/eventRegistry.store.js";
|
|
8
8
|
import { normalizeRegistryEventType } from "../eventRegistry/eventRegistry.registration.js";
|
|
9
9
|
import { EventBusDisposedError, EventBusStoppedError, EventError, toEventError, } from "../eventErrors/eventError.base.js";
|
|
10
|
+
import { warnObserverError } from "../eventErrors/eventWarning.helper.js";
|
|
10
11
|
import { EventBusState } from "./eventBus.type.js";
|
|
11
12
|
export { EventBusState } from "./eventBus.type.js";
|
|
12
13
|
import { busOn, busOnce, busOnAny, busOff, busUse, registerMiddlewareItem, } from "./eventBus.registration.js";
|
|
@@ -34,6 +35,7 @@ export class EventBus {
|
|
|
34
35
|
this.registry = new EventRegistry({
|
|
35
36
|
...options.registry,
|
|
36
37
|
maxHandlersPerPattern: options.emitter?.maxListeners,
|
|
38
|
+
enforceHandlerLimit: options.emitter?.enforceHandlerLimit,
|
|
37
39
|
onWarning: options.onWarning,
|
|
38
40
|
onError: options.onError
|
|
39
41
|
? (error, context) => options.onError?.(error, {
|
|
@@ -235,16 +237,23 @@ export class EventBus {
|
|
|
235
237
|
catch (error) {
|
|
236
238
|
/**
|
|
237
239
|
* Observers must never be able to break event bus
|
|
238
|
-
* operations; failures go to the onError hook
|
|
240
|
+
* operations; failures go to the onError hook, or to Node's
|
|
241
|
+
* process warning channel when no hook is configured, so a
|
|
242
|
+
* broken observer is never silently discarded.
|
|
239
243
|
*/
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
244
|
+
if (this.options.onError) {
|
|
245
|
+
try {
|
|
246
|
+
this.options.onError(error, {
|
|
247
|
+
source: "observer",
|
|
248
|
+
event: event.event,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
// Ignore failures of the error hook itself.
|
|
253
|
+
}
|
|
245
254
|
}
|
|
246
|
-
|
|
247
|
-
|
|
255
|
+
else {
|
|
256
|
+
warnObserverError(error, "An event bus observer", this.listeners);
|
|
248
257
|
}
|
|
249
258
|
}
|
|
250
259
|
}
|
|
@@ -36,6 +36,11 @@ export interface EventBusOptions {
|
|
|
36
36
|
* emitted (0 disables). Defaults to 100.
|
|
37
37
|
*/
|
|
38
38
|
readonly maxListeners?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Refuse a registration that would exceed `maxListeners` instead of
|
|
41
|
+
* warning about it. Defaults to `false`.
|
|
42
|
+
*/
|
|
43
|
+
readonly enforceHandlerLimit?: boolean;
|
|
39
44
|
};
|
|
40
45
|
readonly registry?: {
|
|
41
46
|
readonly allowDuplicateDefinitions?: boolean;
|
|
@@ -52,7 +57,7 @@ export interface EventBusOptions {
|
|
|
52
57
|
readonly requireRegistration?: boolean;
|
|
53
58
|
readonly middleware?: readonly EventBusMiddlewareItem[];
|
|
54
59
|
/**
|
|
55
|
-
* Receives leak warnings. Defaults to
|
|
60
|
+
* Receives leak warnings. Defaults to `process.emitWarning` (type `ZudojsEventsWarning`).
|
|
56
61
|
*/
|
|
57
62
|
readonly onWarning?: (warning: EventRegistryWarning) => void;
|
|
58
63
|
/**
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* bus and its registry share one set of handlers.
|
|
7
7
|
*/
|
|
8
8
|
import { createEvent, isEvent } from "../eventTypes/eventDefinition.type.js";
|
|
9
|
-
import {
|
|
9
|
+
import { createFrozenEventSnapshot } from "../eventTypes/eventSnapshot.freeze.js";
|
|
10
10
|
import { createEventHandlerContext } from "../eventHandler/eventHandler.core.js";
|
|
11
11
|
import { EventSubscriptionGroup } from "../eventSubscription/eventSubscription.core.js";
|
|
12
12
|
import { EventRegistry } from "../eventRegistry/eventRegistry.store.js";
|
|
@@ -32,6 +32,7 @@ export class EventEmitter {
|
|
|
32
32
|
options.store ??
|
|
33
33
|
new EventRegistry({
|
|
34
34
|
maxHandlersPerPattern: options.maxListeners,
|
|
35
|
+
enforceHandlerLimit: options.enforceHandlerLimit,
|
|
35
36
|
onWarning: options.onWarning,
|
|
36
37
|
});
|
|
37
38
|
}
|
|
@@ -68,8 +69,12 @@ export class EventEmitter {
|
|
|
68
69
|
}
|
|
69
70
|
const mode = options.mode ?? this.options.mode;
|
|
70
71
|
const errorMode = options.errorMode ?? this.options.errorMode;
|
|
71
|
-
const
|
|
72
|
-
|
|
72
|
+
const handlers = this.store.getHandlersForEvent(event);
|
|
73
|
+
// Handlers receive a frozen COPY: freezing the event in place froze
|
|
74
|
+
// the publisher's own objects, even when nobody was subscribed.
|
|
75
|
+
const dispatched = this.options.freezeEvents && handlers.length > 0
|
|
76
|
+
? createFrozenEventSnapshot(event)
|
|
77
|
+
: event;
|
|
73
78
|
const context = createEventHandlerContext(dispatched, {
|
|
74
79
|
signal: options.signal,
|
|
75
80
|
metadata: options.metadata,
|
|
@@ -87,7 +92,17 @@ export class EventEmitter {
|
|
|
87
92
|
};
|
|
88
93
|
}
|
|
89
94
|
const hooks = {
|
|
90
|
-
|
|
95
|
+
// A bus disposed mid-dispatch has no registered handlers left.
|
|
96
|
+
isRegistered: (handlerId) => {
|
|
97
|
+
if (this.disposed)
|
|
98
|
+
return false;
|
|
99
|
+
try {
|
|
100
|
+
return this.store.hasHandler(handlerId);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
},
|
|
91
106
|
removeOnce: (handlerId) => {
|
|
92
107
|
this.store.unregisterHandler(handlerId);
|
|
93
108
|
},
|
|
@@ -22,12 +22,12 @@ export async function emitParallel(handlers, event, context, errorMode, results,
|
|
|
22
22
|
* an overlapping dispatch cannot invoke them a second time.
|
|
23
23
|
*/
|
|
24
24
|
const runnable = handlers.filter((handler) => {
|
|
25
|
-
if (!handler.once) {
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
25
|
if (!hooks.isRegistered(handler.id)) {
|
|
29
26
|
return false;
|
|
30
27
|
}
|
|
28
|
+
if (!handler.once) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
31
|
hooks.removeOnce(handler.id);
|
|
32
32
|
return true;
|
|
33
33
|
});
|
|
@@ -13,11 +13,13 @@ export async function emitSequential(handlers, event, context, errorMode, result
|
|
|
13
13
|
if (context.signal.aborted) {
|
|
14
14
|
throw createAbortError(event, results, errors);
|
|
15
15
|
}
|
|
16
|
+
// Checked for every handler, not only once-handlers: a handler
|
|
17
|
+
// unsubscribed (or a bus disposed) by an earlier handler in this
|
|
18
|
+
// same dispatch must not run.
|
|
19
|
+
if (!hooks.isRegistered(handler.id)) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
16
22
|
if (handler.once) {
|
|
17
|
-
if (!hooks.isRegistered(handler.id)) {
|
|
18
|
-
// Already consumed by an overlapping dispatch.
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
23
|
hooks.removeOnce(handler.id);
|
|
22
24
|
}
|
|
23
25
|
const started = performance.now();
|
|
@@ -42,9 +42,14 @@ export interface EventEmitterOptions {
|
|
|
42
42
|
* Defaults to 100.
|
|
43
43
|
*/
|
|
44
44
|
readonly maxListeners?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Refuse a registration that would exceed `maxListeners` instead of warning
|
|
47
|
+
* about it. Only applies to the private store. Defaults to `false`.
|
|
48
|
+
*/
|
|
49
|
+
readonly enforceHandlerLimit?: boolean;
|
|
45
50
|
/**
|
|
46
51
|
* Receives leak warnings. Only applies to the private store.
|
|
47
|
-
* Defaults to
|
|
52
|
+
* Defaults to `process.emitWarning` (type `ZudojsEventsWarning`).
|
|
48
53
|
*/
|
|
49
54
|
readonly onWarning?: (warning: EventRegistryWarning) => void;
|
|
50
55
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* errors package does not define yet live in this file.
|
|
7
7
|
*/
|
|
8
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";
|
|
9
|
+
export { EventError, createEventError, isEventError, toEventError, EventPublishError, InvalidEventError, EventTypeNotFoundError, EventHandlerError, createEventHandlerError, EventHandlerNotFoundError, DuplicateEventHandlerError, DuplicateEventDefinitionError, EventDefinitionNotFoundError, EventEmitterDisposedError, EventRegistryDisposedError, EventSubscriptionClosedError, EventListenerLimitExceededError, EventTimeoutError, EventMiddlewareError, EventSerializationError, EventDeserializationError, } from "@zudojs/errors";
|
|
10
10
|
/**
|
|
11
11
|
* Options for EventDispatchAbortedError.
|
|
12
12
|
*/
|
|
@@ -45,11 +45,4 @@ export declare class EventBusDisposedError extends EventError {
|
|
|
45
45
|
export declare class EventBusStoppedError extends EventError {
|
|
46
46
|
constructor(operation: string);
|
|
47
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
|
-
}
|
|
55
48
|
//# sourceMappingURL=eventError.base.d.ts.map
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* errors package does not define yet live in this file.
|
|
7
7
|
*/
|
|
8
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";
|
|
9
|
+
export { EventError, createEventError, isEventError, toEventError, EventPublishError, InvalidEventError, EventTypeNotFoundError, EventHandlerError, createEventHandlerError, EventHandlerNotFoundError, DuplicateEventHandlerError, DuplicateEventDefinitionError, EventDefinitionNotFoundError, EventEmitterDisposedError, EventRegistryDisposedError, EventSubscriptionClosedError, EventListenerLimitExceededError, EventTimeoutError, EventMiddlewareError, EventSerializationError, EventDeserializationError, } from "@zudojs/errors";
|
|
10
10
|
/**
|
|
11
11
|
* Error thrown when event dispatch is aborted through an
|
|
12
12
|
* AbortSignal. Carries the partial results and errors gathered
|
|
@@ -52,18 +52,4 @@ export class EventBusStoppedError extends EventError {
|
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
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
|
-
}
|
|
69
55
|
//# sourceMappingURL=eventError.base.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-level warning sink for @zudojs/events.
|
|
3
|
+
*
|
|
4
|
+
* @module eventErrors/eventWarning
|
|
5
|
+
*/
|
|
6
|
+
/** Warning code for a registry or emitter handler-limit breach. */
|
|
7
|
+
export declare const EVENT_HANDLER_LIMIT_WARNING_CODE = "ZUDOJS_EVENTS_HANDLER_LIMIT";
|
|
8
|
+
/** Warning code for a bus or registry observer that threw. */
|
|
9
|
+
export declare const EVENT_OBSERVER_ERROR_WARNING_CODE = "ZUDOJS_EVENTS_OBSERVER_ERROR";
|
|
10
|
+
/**
|
|
11
|
+
* Emits a diagnostic on Node's process warning channel — the same one
|
|
12
|
+
* `EventEmitter` uses for `MaxListenersExceededWarning`, so it honours
|
|
13
|
+
* `--no-warnings` and `process.on("warning")` instead of writing to the
|
|
14
|
+
* console. A no-op where `process.emitWarning` is unavailable.
|
|
15
|
+
*
|
|
16
|
+
* @param message - The warning text, emitted with a package prefix.
|
|
17
|
+
* @param code - The machine-readable warning code.
|
|
18
|
+
*/
|
|
19
|
+
export declare function emitEventsWarning(message: string, code: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* Default sink for an observer that threw: with no `onError` hook
|
|
22
|
+
* configured the failure would otherwise be swallowed entirely.
|
|
23
|
+
*
|
|
24
|
+
* Emitted at most once per scope, the way the handler-limit warning is
|
|
25
|
+
* emitted at most once per pattern, so a broken observer on a busy bus
|
|
26
|
+
* reports itself without flooding the warning channel.
|
|
27
|
+
*
|
|
28
|
+
* @param error - The value the observer threw.
|
|
29
|
+
* @param source - A short description of which observer channel failed.
|
|
30
|
+
* @param scope - The bus or registry the observer belongs to.
|
|
31
|
+
*/
|
|
32
|
+
export declare function warnObserverError(error: unknown, source: string, scope: object): void;
|
|
33
|
+
//# sourceMappingURL=eventWarning.helper.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-level warning sink for @zudojs/events.
|
|
3
|
+
*
|
|
4
|
+
* @module eventErrors/eventWarning
|
|
5
|
+
*/
|
|
6
|
+
/** Warning code for a registry or emitter handler-limit breach. */
|
|
7
|
+
export const EVENT_HANDLER_LIMIT_WARNING_CODE = "ZUDOJS_EVENTS_HANDLER_LIMIT";
|
|
8
|
+
/** Warning code for a bus or registry observer that threw. */
|
|
9
|
+
export const EVENT_OBSERVER_ERROR_WARNING_CODE = "ZUDOJS_EVENTS_OBSERVER_ERROR";
|
|
10
|
+
/**
|
|
11
|
+
* Emits a diagnostic on Node's process warning channel — the same one
|
|
12
|
+
* `EventEmitter` uses for `MaxListenersExceededWarning`, so it honours
|
|
13
|
+
* `--no-warnings` and `process.on("warning")` instead of writing to the
|
|
14
|
+
* console. A no-op where `process.emitWarning` is unavailable.
|
|
15
|
+
*
|
|
16
|
+
* @param message - The warning text, emitted with a package prefix.
|
|
17
|
+
* @param code - The machine-readable warning code.
|
|
18
|
+
*/
|
|
19
|
+
export function emitEventsWarning(message, code) {
|
|
20
|
+
const emit = globalThis.process?.emitWarning;
|
|
21
|
+
emit?.(`[@zudojs/events] ${message}`, {
|
|
22
|
+
type: "ZudojsEventsWarning",
|
|
23
|
+
code,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/** Scopes that have already reported a failing observer. */
|
|
27
|
+
const warnedObserverScopes = new WeakSet();
|
|
28
|
+
/**
|
|
29
|
+
* Default sink for an observer that threw: with no `onError` hook
|
|
30
|
+
* configured the failure would otherwise be swallowed entirely.
|
|
31
|
+
*
|
|
32
|
+
* Emitted at most once per scope, the way the handler-limit warning is
|
|
33
|
+
* emitted at most once per pattern, so a broken observer on a busy bus
|
|
34
|
+
* reports itself without flooding the warning channel.
|
|
35
|
+
*
|
|
36
|
+
* @param error - The value the observer threw.
|
|
37
|
+
* @param source - A short description of which observer channel failed.
|
|
38
|
+
* @param scope - The bus or registry the observer belongs to.
|
|
39
|
+
*/
|
|
40
|
+
export function warnObserverError(error, source, scope) {
|
|
41
|
+
if (warnedObserverScopes.has(scope))
|
|
42
|
+
return;
|
|
43
|
+
warnedObserverScopes.add(scope);
|
|
44
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
45
|
+
emitEventsWarning(`${source} threw and was ignored: ${detail}. ` +
|
|
46
|
+
"Configure the `onError` option to handle observer failures. " +
|
|
47
|
+
"This warning is emitted once.", EVENT_OBSERVER_ERROR_WARNING_CODE);
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=eventWarning.helper.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Event middleware pipeline execution for Zudojs
|
|
2
|
+
* Event middleware pipeline execution for Zudojs, built on `compose` from
|
|
3
|
+
* `@zudojs/middleware`.
|
|
3
4
|
*/
|
|
4
5
|
import type { Event } from "../eventTypes/eventDefinition.type.js";
|
|
5
6
|
import type { EventMiddlewareContext, EventMiddlewareNext, RegisteredEventMiddleware, EventMiddlewarePipelineResult } from "./eventMiddleware.type.js";
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Event middleware pipeline execution for Zudojs
|
|
2
|
+
* Event middleware pipeline execution for Zudojs, built on `compose` from
|
|
3
|
+
* `@zudojs/middleware`.
|
|
3
4
|
*/
|
|
5
|
+
import { compose } from "@zudojs/middleware";
|
|
4
6
|
import { EventDispatchAbortedError, EventMiddlewareError, toEventError, } from "../eventErrors/eventError.base.js";
|
|
5
|
-
import { sortEventMiddleware, executeEventMiddleware
|
|
7
|
+
import { sortEventMiddleware, executeEventMiddleware } from "./eventMiddleware.helper.js";
|
|
6
8
|
/**
|
|
7
9
|
* Executes a middleware pipeline.
|
|
8
10
|
*
|
|
@@ -21,47 +23,37 @@ import { sortEventMiddleware, executeEventMiddleware, } from "./eventMiddleware.
|
|
|
21
23
|
*/
|
|
22
24
|
export async function executeEventMiddlewarePipeline(middleware, context, terminal) {
|
|
23
25
|
const started = performance.now();
|
|
24
|
-
const activeMiddleware = sortEventMiddleware(middleware.filter((item) => item.enabled));
|
|
25
26
|
const executions = [];
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return terminal();
|
|
44
|
-
}
|
|
27
|
+
const stages = sortEventMiddleware(middleware.filter((item) => item.enabled))
|
|
28
|
+
.map((current) => toStage(current, executions));
|
|
29
|
+
const run = compose(stages, async (ctx) => {
|
|
30
|
+
throwIfAborted(ctx);
|
|
31
|
+
return terminal();
|
|
32
|
+
}, { maxDepth: Number.POSITIVE_INFINITY });
|
|
33
|
+
const result = await run(context);
|
|
34
|
+
return { result, executions, duration: performance.now() - started };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Adapts one registered middleware to the shared composer: checks the
|
|
38
|
+
* abort signal, rejects a second next() with an EventMiddlewareError,
|
|
39
|
+
* records the execution, and wraps only the middleware's own errors.
|
|
40
|
+
*/
|
|
41
|
+
function toStage(current, executions) {
|
|
42
|
+
return async (context, advance) => {
|
|
43
|
+
throwIfAborted(context);
|
|
45
44
|
const middlewareStarted = performance.now();
|
|
45
|
+
const eventType = context.event?.type;
|
|
46
|
+
const eventId = context.event?.id;
|
|
46
47
|
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
48
|
let downstreamThrew = false;
|
|
53
49
|
let downstreamError;
|
|
54
50
|
const next = async () => {
|
|
55
51
|
if (nextCalled) {
|
|
56
|
-
throw new EventMiddlewareError(`Middleware "${current.id}" called next() more than once.`, {
|
|
57
|
-
middlewareId: current.id,
|
|
58
|
-
eventType: context.event?.type,
|
|
59
|
-
eventId: context.event?.id,
|
|
60
|
-
});
|
|
52
|
+
throw new EventMiddlewareError(`Middleware "${current.id}" called next() more than once.`, { middlewareId: current.id, eventType, eventId });
|
|
61
53
|
}
|
|
62
54
|
nextCalled = true;
|
|
63
55
|
try {
|
|
64
|
-
return await
|
|
56
|
+
return await advance();
|
|
65
57
|
}
|
|
66
58
|
catch (error) {
|
|
67
59
|
downstreamThrew = true;
|
|
@@ -79,39 +71,29 @@ export async function executeEventMiddlewarePipeline(middleware, context, termin
|
|
|
79
71
|
return result;
|
|
80
72
|
}
|
|
81
73
|
catch (error) {
|
|
82
|
-
if (downstreamThrew && error === downstreamError)
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
if (error instanceof EventMiddlewareError ||
|
|
74
|
+
if ((downstreamThrew && error === downstreamError) ||
|
|
75
|
+
error instanceof EventMiddlewareError ||
|
|
86
76
|
error instanceof EventDispatchAbortedError) {
|
|
87
77
|
throw error;
|
|
88
78
|
}
|
|
89
79
|
throw new EventMiddlewareError(`Event middleware "${current.id}" failed.`, {
|
|
90
80
|
middlewareId: current.id,
|
|
91
|
-
eventType
|
|
92
|
-
eventId
|
|
93
|
-
cause: toEventError(error, {
|
|
94
|
-
eventType: context.event?.type,
|
|
95
|
-
eventId: context.event?.id,
|
|
96
|
-
}),
|
|
81
|
+
eventType,
|
|
82
|
+
eventId,
|
|
83
|
+
cause: toEventError(error, { eventType, eventId }),
|
|
97
84
|
});
|
|
98
85
|
}
|
|
99
86
|
};
|
|
100
|
-
const result = await dispatch(0);
|
|
101
|
-
return {
|
|
102
|
-
result,
|
|
103
|
-
executions,
|
|
104
|
-
duration: performance.now() - started,
|
|
105
|
-
};
|
|
106
87
|
}
|
|
107
88
|
/**
|
|
108
|
-
*
|
|
109
|
-
* aborted signal.
|
|
89
|
+
* Throws the abort error when the pipeline observes an aborted signal.
|
|
110
90
|
*/
|
|
111
|
-
function
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
91
|
+
function throwIfAborted(context) {
|
|
92
|
+
if (context.signal.aborted) {
|
|
93
|
+
throw new EventDispatchAbortedError("Event dispatch was aborted.", {
|
|
94
|
+
eventType: context.event?.type,
|
|
95
|
+
eventId: context.event?.id,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
116
98
|
}
|
|
117
99
|
//# sourceMappingURL=eventMiddleware.pipeline.js.map
|
|
@@ -18,7 +18,9 @@ export declare function registryDispose(disposed: boolean, definitions: Map<Even
|
|
|
18
18
|
* Notifies registry listeners.
|
|
19
19
|
*
|
|
20
20
|
* Observer failures never break registry mutations; they are
|
|
21
|
-
* forwarded to the `onError` hook when one is configured
|
|
21
|
+
* forwarded to the `onError` hook when one is configured, and to
|
|
22
|
+
* Node's process warning channel when one is not, so a broken
|
|
23
|
+
* observer is never silently discarded.
|
|
22
24
|
*/
|
|
23
25
|
export declare function registryNotify(change: EventRegistryChange, listeners: Set<EventRegistryListener>, onError?: (error: unknown, context: EventRegistryErrorContext) => void): void;
|
|
24
26
|
//# sourceMappingURL=eventRegistry.lifecycle.d.ts.map
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Event registry lifecycle methods for Zudojs.
|
|
3
3
|
*/
|
|
4
4
|
import { registryUnregister } from "./eventRegistry.registration.js";
|
|
5
|
+
import { warnObserverError } from "../eventErrors/eventWarning.helper.js";
|
|
5
6
|
/**
|
|
6
7
|
* Clears all handlers and definitions from the registry.
|
|
7
8
|
*
|
|
@@ -34,7 +35,9 @@ export function registryDispose(disposed, definitions, handlers, listeners, ensu
|
|
|
34
35
|
* Notifies registry listeners.
|
|
35
36
|
*
|
|
36
37
|
* Observer failures never break registry mutations; they are
|
|
37
|
-
* forwarded to the `onError` hook when one is configured
|
|
38
|
+
* forwarded to the `onError` hook when one is configured, and to
|
|
39
|
+
* Node's process warning channel when one is not, so a broken
|
|
40
|
+
* observer is never silently discarded.
|
|
38
41
|
*/
|
|
39
42
|
export function registryNotify(change, listeners, onError) {
|
|
40
43
|
for (const listener of listeners) {
|
|
@@ -52,6 +55,9 @@ export function registryNotify(change, listeners, onError) {
|
|
|
52
55
|
*/
|
|
53
56
|
}
|
|
54
57
|
}
|
|
58
|
+
else {
|
|
59
|
+
warnObserverError(error, "An event registry observer", listeners);
|
|
60
|
+
}
|
|
55
61
|
}
|
|
56
62
|
}
|
|
57
63
|
}
|
|
@@ -23,6 +23,7 @@ export declare function registryRegister<TType extends EventType, TPayload>(defi
|
|
|
23
23
|
export declare function registryRegisterHandler<TEvent extends Event = Event>(eventType: EventTypePattern, handler: EventHandlerLike<TEvent>, handlerOptions: Omit<EventHandlerOptions, "eventType">, handlers: Map<string, EventHandlerEntry>, options: {
|
|
24
24
|
onDuplicateHandlerId: DuplicateHandlerIdPolicy;
|
|
25
25
|
maxHandlersPerPattern: number;
|
|
26
|
+
enforceHandlerLimit?: boolean;
|
|
26
27
|
onWarning: (warning: EventRegistryWarning) => void;
|
|
27
28
|
}, ensureActive: () => void, notify: (change: EventRegistryChange) => void, warnedPatterns: Set<string>): EventSubscription;
|
|
28
29
|
/**
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { normalizeEventType } from "../eventTypes/eventType.type.js";
|
|
5
5
|
import { createEventHandler } from "../eventHandler/eventHandler.core.js";
|
|
6
6
|
import { createEventSubscription } from "../eventSubscription/eventSubscription.core.js";
|
|
7
|
-
import { DuplicateEventDefinitionError, DuplicateEventHandlerError, InvalidEventError, } from "../eventErrors/eventError.base.js";
|
|
7
|
+
import { DuplicateEventDefinitionError, DuplicateEventHandlerError, EventListenerLimitExceededError, InvalidEventError, } from "../eventErrors/eventError.base.js";
|
|
8
8
|
import { EventRegistryChangeType } from "./eventRegistry.type.js";
|
|
9
9
|
/**
|
|
10
10
|
* Normalizes an event type for registry lookups, converting
|
|
@@ -104,7 +104,7 @@ export function registryRegisterHandler(eventType, handler, handlerOptions, hand
|
|
|
104
104
|
description: registration.description,
|
|
105
105
|
});
|
|
106
106
|
handlers.set(registration.id, { registration, subscription });
|
|
107
|
-
checkHandlerLimit(registration.eventType, handlers, options.maxHandlersPerPattern, options.onWarning, warnedPatterns);
|
|
107
|
+
checkHandlerLimit(registration.eventType, handlers, options.maxHandlersPerPattern, options.onWarning, warnedPatterns, options.enforceHandlerLimit === true, registration.id);
|
|
108
108
|
notify({
|
|
109
109
|
type: EventRegistryChangeType.HANDLER_REGISTERED,
|
|
110
110
|
eventType: registration.eventType,
|
|
@@ -114,11 +114,19 @@ export function registryRegisterHandler(eventType, handler, handlerOptions, hand
|
|
|
114
114
|
return subscription;
|
|
115
115
|
}
|
|
116
116
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
117
|
+
* Reports a pattern whose handler count has passed the configured limit.
|
|
118
|
+
*
|
|
119
|
+
* Warns once per pattern by default. Under `enforceHandlerLimit` it instead
|
|
120
|
+
* removes the handler just registered and throws
|
|
121
|
+
* {@link EventListenerLimitExceededError}, so a refused registration leaves
|
|
122
|
+
* the registry exactly as it was — and it throws on every breach, not only
|
|
123
|
+
* the first, because each one is a separate fault.
|
|
119
124
|
*/
|
|
120
|
-
function checkHandlerLimit(pattern, handlers, limit, onWarning, warnedPatterns) {
|
|
121
|
-
if (limit <= 0
|
|
125
|
+
function checkHandlerLimit(pattern, handlers, limit, onWarning, warnedPatterns, enforce, registrationId) {
|
|
126
|
+
if (limit <= 0) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (!enforce && warnedPatterns.has(pattern)) {
|
|
122
130
|
return;
|
|
123
131
|
}
|
|
124
132
|
let count = 0;
|
|
@@ -130,6 +138,10 @@ function checkHandlerLimit(pattern, handlers, limit, onWarning, warnedPatterns)
|
|
|
130
138
|
if (count <= limit) {
|
|
131
139
|
return;
|
|
132
140
|
}
|
|
141
|
+
if (enforce) {
|
|
142
|
+
handlers.delete(registrationId);
|
|
143
|
+
throw new EventListenerLimitExceededError(pattern, count, limit);
|
|
144
|
+
}
|
|
133
145
|
warnedPatterns.add(pattern);
|
|
134
146
|
const warning = {
|
|
135
147
|
type: "handler.limit",
|
|
@@ -7,13 +7,20 @@
|
|
|
7
7
|
* higher-level routing belongs to EventBus.
|
|
8
8
|
*/
|
|
9
9
|
import { DuplicateEventDefinitionError, EventDefinitionNotFoundError, DuplicateEventHandlerError, EventHandlerNotFoundError, EventRegistryDisposedError, } from "../eventErrors/eventError.base.js";
|
|
10
|
+
import { EVENT_HANDLER_LIMIT_WARNING_CODE, emitEventsWarning, } from "../eventErrors/eventWarning.helper.js";
|
|
10
11
|
import { DEFAULT_MAX_HANDLERS_PER_PATTERN } from "./eventRegistry.type.js";
|
|
11
12
|
import { normalizeRegistryEventType, registryRegister, registryRegisterHandler, registryUnregister, registryUnregisterHandler, } from "./eventRegistry.registration.js";
|
|
12
13
|
import { getHandlersForEvent, getHandlersForType, getAllDefinitions, getAllHandlers, } from "./eventRegistry.queries.js";
|
|
13
14
|
import { registryClear, registryDispose, registryNotify, } from "./eventRegistry.lifecycle.js";
|
|
14
15
|
export { DuplicateEventDefinitionError, EventDefinitionNotFoundError, DuplicateEventHandlerError, EventHandlerNotFoundError, EventRegistryDisposedError, };
|
|
16
|
+
/**
|
|
17
|
+
* Default leak-warning sink: Node's process warning channel (the same one
|
|
18
|
+
* `EventEmitter` uses for `MaxListenersExceededWarning`), which honours
|
|
19
|
+
* `--no-warnings` and `process.on("warning")`, instead of writing to the
|
|
20
|
+
* console directly. A no-op where `process.emitWarning` is unavailable.
|
|
21
|
+
*/
|
|
15
22
|
function defaultWarning(warning) {
|
|
16
|
-
|
|
23
|
+
emitEventsWarning(warning.message, EVENT_HANDLER_LIMIT_WARNING_CODE);
|
|
17
24
|
}
|
|
18
25
|
/**
|
|
19
26
|
* Main event registry.
|
|
@@ -37,6 +44,7 @@ export class EventRegistry {
|
|
|
37
44
|
onDuplicateHandlerId: options.onDuplicateHandlerId ??
|
|
38
45
|
(options.allowDuplicateHandlerIds ? "replace" : "throw"),
|
|
39
46
|
maxHandlersPerPattern,
|
|
47
|
+
enforceHandlerLimit: options.enforceHandlerLimit ?? false,
|
|
40
48
|
onWarning: options.onWarning ?? defaultWarning,
|
|
41
49
|
onError: options.onError,
|
|
42
50
|
};
|
|
@@ -55,12 +55,22 @@ export interface EventRegistryOptions {
|
|
|
55
55
|
readonly onDuplicateHandlerId?: DuplicateHandlerIdPolicy;
|
|
56
56
|
/**
|
|
57
57
|
* Maximum handlers per event pattern before a leak warning is
|
|
58
|
-
* emitted through `onWarning` (or
|
|
58
|
+
* emitted through `onWarning` (or `process.emitWarning`). Use 0 to
|
|
59
59
|
* disable. Defaults to 100.
|
|
60
60
|
*/
|
|
61
61
|
readonly maxHandlersPerPattern?: number;
|
|
62
62
|
/**
|
|
63
|
-
*
|
|
63
|
+
* Refuse a registration that would exceed `maxHandlersPerPattern` instead of
|
|
64
|
+
* warning about it. Defaults to `false`.
|
|
65
|
+
*
|
|
66
|
+
* The default is a one-shot warning, which reports a suspected leak without
|
|
67
|
+
* interrupting a working application. Set this where a breached limit is a
|
|
68
|
+
* fault you would rather fail on: `register` then throws
|
|
69
|
+
* `EventListenerLimitExceededError` and the handler is not registered.
|
|
70
|
+
*/
|
|
71
|
+
readonly enforceHandlerLimit?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Receives limit warnings. Defaults to `process.emitWarning` (type `ZudojsEventsWarning`).
|
|
64
74
|
*/
|
|
65
75
|
readonly onWarning?: (warning: EventRegistryWarning) => void;
|
|
66
76
|
/**
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Immutable event snapshots.
|
|
3
|
+
*
|
|
4
|
+
* The emitter used to deep-freeze the event IN PLACE, which froze the
|
|
5
|
+
* publisher's own objects (a later `cart.total = 20` threw) and left
|
|
6
|
+
* Map, Set and Date internals mutable, because `Object.freeze` does not
|
|
7
|
+
* reach them. A snapshot is a frozen COPY: the caller's graph is never
|
|
8
|
+
* touched, and Map, Set and Date are copied into read-only variants
|
|
9
|
+
* whose mutators throw.
|
|
10
|
+
*/
|
|
11
|
+
/** A Date whose setters throw. */
|
|
12
|
+
export declare class FrozenEventDate extends Date {
|
|
13
|
+
}
|
|
14
|
+
/** A Map whose mutators throw once construction has finished. */
|
|
15
|
+
export declare class FrozenEventMap<K, V> extends Map<K, V> {
|
|
16
|
+
set(): this;
|
|
17
|
+
delete(): boolean;
|
|
18
|
+
clear(): void;
|
|
19
|
+
}
|
|
20
|
+
/** A Set whose mutators throw once construction has finished. */
|
|
21
|
+
export declare class FrozenEventSet<T> extends Set<T> {
|
|
22
|
+
add(): this;
|
|
23
|
+
delete(): boolean;
|
|
24
|
+
clear(): void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Returns a deeply frozen copy of `value`, leaving `value` untouched.
|
|
28
|
+
*
|
|
29
|
+
* Plain objects, arrays and errors are copied (own properties, including
|
|
30
|
+
* a `__proto__` data key, are defined rather than assigned) and frozen.
|
|
31
|
+
* Map, Set and Date become {@link FrozenEventMap}, {@link FrozenEventSet}
|
|
32
|
+
* and {@link FrozenEventDate}, which still pass `instanceof Map` / `Set`
|
|
33
|
+
* / `Date` but throw on mutation. Class instances and other exotic
|
|
34
|
+
* objects are passed by reference. Cycles are preserved.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createFrozenEventSnapshot<T>(value: T): T;
|
|
37
|
+
//# sourceMappingURL=eventSnapshot.freeze.d.ts.map
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Immutable event snapshots.
|
|
3
|
+
*
|
|
4
|
+
* The emitter used to deep-freeze the event IN PLACE, which froze the
|
|
5
|
+
* publisher's own objects (a later `cart.total = 20` threw) and left
|
|
6
|
+
* Map, Set and Date internals mutable, because `Object.freeze` does not
|
|
7
|
+
* reach them. A snapshot is a frozen COPY: the caller's graph is never
|
|
8
|
+
* touched, and Map, Set and Date are copied into read-only variants
|
|
9
|
+
* whose mutators throw.
|
|
10
|
+
*/
|
|
11
|
+
/** Throws the TypeError every read-only mutator raises. */
|
|
12
|
+
function readOnly(kind) {
|
|
13
|
+
throw new TypeError(`Cannot modify a frozen event ${kind}.`);
|
|
14
|
+
}
|
|
15
|
+
/** A Date whose setters throw. */
|
|
16
|
+
export class FrozenEventDate extends Date {
|
|
17
|
+
}
|
|
18
|
+
for (const key of Object.getOwnPropertyNames(Date.prototype)) {
|
|
19
|
+
if (key.startsWith("set")) {
|
|
20
|
+
Object.defineProperty(FrozenEventDate.prototype, key, {
|
|
21
|
+
value: () => readOnly("Date"),
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** A Map whose mutators throw once construction has finished. */
|
|
28
|
+
export class FrozenEventMap extends Map {
|
|
29
|
+
set() {
|
|
30
|
+
return readOnly("Map");
|
|
31
|
+
}
|
|
32
|
+
delete() {
|
|
33
|
+
return readOnly("Map");
|
|
34
|
+
}
|
|
35
|
+
clear() {
|
|
36
|
+
readOnly("Map");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** A Set whose mutators throw once construction has finished. */
|
|
40
|
+
export class FrozenEventSet extends Set {
|
|
41
|
+
add() {
|
|
42
|
+
return readOnly("Set");
|
|
43
|
+
}
|
|
44
|
+
delete() {
|
|
45
|
+
return readOnly("Set");
|
|
46
|
+
}
|
|
47
|
+
clear() {
|
|
48
|
+
readOnly("Set");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Whether a value is a plain object (literal or null-prototype). */
|
|
52
|
+
function isPlainRecord(value) {
|
|
53
|
+
const proto = Object.getPrototypeOf(value);
|
|
54
|
+
return proto === Object.prototype || proto === null;
|
|
55
|
+
}
|
|
56
|
+
function snapshot(value, seen) {
|
|
57
|
+
if (typeof value !== "object" || value === null)
|
|
58
|
+
return value;
|
|
59
|
+
if (seen.has(value))
|
|
60
|
+
return seen.get(value);
|
|
61
|
+
if (value instanceof Date) {
|
|
62
|
+
const copy = Object.freeze(new FrozenEventDate(value.getTime()));
|
|
63
|
+
seen.set(value, copy);
|
|
64
|
+
return copy;
|
|
65
|
+
}
|
|
66
|
+
if (value instanceof Map) {
|
|
67
|
+
const copy = new FrozenEventMap();
|
|
68
|
+
seen.set(value, copy);
|
|
69
|
+
for (const [key, item] of value) {
|
|
70
|
+
Map.prototype.set.call(copy, key, snapshot(item, seen));
|
|
71
|
+
}
|
|
72
|
+
return Object.freeze(copy);
|
|
73
|
+
}
|
|
74
|
+
if (value instanceof Set) {
|
|
75
|
+
const copy = new FrozenEventSet();
|
|
76
|
+
seen.set(value, copy);
|
|
77
|
+
for (const item of value) {
|
|
78
|
+
Set.prototype.add.call(copy, snapshot(item, seen));
|
|
79
|
+
}
|
|
80
|
+
return Object.freeze(copy);
|
|
81
|
+
}
|
|
82
|
+
const isArray = Array.isArray(value);
|
|
83
|
+
const isError = value instanceof Error;
|
|
84
|
+
// Instances of other classes (and exotic built-ins such as typed
|
|
85
|
+
// arrays, RegExp, URL) cannot be copied faithfully — private fields and
|
|
86
|
+
// internal slots do not survive — so they are passed by reference.
|
|
87
|
+
if (!isArray && !isError && !isPlainRecord(value))
|
|
88
|
+
return value;
|
|
89
|
+
const copy = isArray
|
|
90
|
+
? []
|
|
91
|
+
: Object.create(Object.getPrototypeOf(value));
|
|
92
|
+
seen.set(value, copy);
|
|
93
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
94
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
95
|
+
if (!descriptor)
|
|
96
|
+
continue;
|
|
97
|
+
if ("value" in descriptor) {
|
|
98
|
+
descriptor.value = snapshot(descriptor.value, seen);
|
|
99
|
+
}
|
|
100
|
+
Object.defineProperty(copy, key, descriptor);
|
|
101
|
+
}
|
|
102
|
+
return Object.freeze(copy);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Returns a deeply frozen copy of `value`, leaving `value` untouched.
|
|
106
|
+
*
|
|
107
|
+
* Plain objects, arrays and errors are copied (own properties, including
|
|
108
|
+
* a `__proto__` data key, are defined rather than assigned) and frozen.
|
|
109
|
+
* Map, Set and Date become {@link FrozenEventMap}, {@link FrozenEventSet}
|
|
110
|
+
* and {@link FrozenEventDate}, which still pass `instanceof Map` / `Set`
|
|
111
|
+
* / `Date` but throw on mutation. Class instances and other exotic
|
|
112
|
+
* objects are passed by reference. Cycles are preserved.
|
|
113
|
+
*/
|
|
114
|
+
export function createFrozenEventSnapshot(value) {
|
|
115
|
+
return snapshot(value, new Map());
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=eventSnapshot.freeze.js.map
|
|
@@ -6,4 +6,5 @@
|
|
|
6
6
|
export { type EventId, type EventType, type EventTimestamp, type EventSource, type EventCorrelationId, type EventCausationId, type EventPayload, type Event, type EventInput, type EventDefinition, isEvent, createEventId, createEvent, defineEvent, withEventMetadata, createDerivedEvent, getEventType, getEventPayload, describeEvent, } from "./eventDefinition.type.js";
|
|
7
7
|
export { type ObjectEventPayload, type PrimitiveEventPayload, type JsonEventPayload, type EventPayloadMap, type PayloadOf, type PayloadMap, type EventPayloadFactory, type EventPayloadOptions, isPrimitiveEventPayload, isObjectEventPayload, isJsonEventPayload, createEventPayload, createObjectEventPayload, createJsonEventPayload, cloneEventPayload, deepFreeze, stripUndefinedValues, mergeEventPayloads, staticPayload, definePayloadFactory, validateEventPayload, describeEventPayload, } from "./eventPayload.type.js";
|
|
8
8
|
export { type EventTypeList, type EventTypePattern, type EventTypeOf, type EventUnion, isValidEventType, isValidEventTypePattern, normalizeEventType, normalizeEventTypePattern, tryNormalizeEventType, assertEventType, createEventType, createEventTypePattern, getEventNamespace, getEventAction, getEventTypeSegments, matchesEventType, isSameEventNamespace, isChildEventType, eventMatchesType, filterEventsByType, defineEventTypes, defineEventType, } from "./eventType.type.js";
|
|
9
|
+
export { createFrozenEventSnapshot, FrozenEventDate, FrozenEventMap, FrozenEventSet, } from "./eventSnapshot.freeze.js";
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/eventTypes/index.js
CHANGED
|
@@ -6,4 +6,5 @@
|
|
|
6
6
|
export { isEvent, createEventId, createEvent, defineEvent, withEventMetadata, createDerivedEvent, getEventType, getEventPayload, describeEvent, } from "./eventDefinition.type.js";
|
|
7
7
|
export { isPrimitiveEventPayload, isObjectEventPayload, isJsonEventPayload, createEventPayload, createObjectEventPayload, createJsonEventPayload, cloneEventPayload, deepFreeze, stripUndefinedValues, mergeEventPayloads, staticPayload, definePayloadFactory, validateEventPayload, describeEventPayload, } from "./eventPayload.type.js";
|
|
8
8
|
export { isValidEventType, isValidEventTypePattern, normalizeEventType, normalizeEventTypePattern, tryNormalizeEventType, assertEventType, createEventType, createEventTypePattern, getEventNamespace, getEventAction, getEventTypeSegments, matchesEventType, isSameEventNamespace, isChildEventType, eventMatchesType, filterEventsByType, defineEventTypes, defineEventType, } from "./eventType.type.js";
|
|
9
|
+
export { createFrozenEventSnapshot, FrozenEventDate, FrozenEventMap, FrozenEventSet, } from "./eventSnapshot.freeze.js";
|
|
9
10
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/events",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Event-driven architecture with event bus, emitter, middleware, and registry for decoupled communication.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
],
|
|
21
21
|
"sideEffects": false,
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@zudojs/
|
|
24
|
-
"@zudojs/
|
|
23
|
+
"@zudojs/constants": "1.1.1",
|
|
24
|
+
"@zudojs/errors": "1.2.0",
|
|
25
|
+
"@zudojs/middleware": "1.0.3"
|
|
25
26
|
},
|
|
26
27
|
"engines": {
|
|
27
28
|
"node": ">=24.0.0"
|