@zudojs/errors 1.1.0 → 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.
@@ -10,11 +10,16 @@
10
10
  * Redacts sensitive keys inside a record without changing its shape:
11
11
  * primitives, dates and class instances are kept, plain objects and arrays
12
12
  * are walked, cycles stop at `"[Circular]"`, prototype keys are dropped.
13
+ *
14
+ * The walk is depth-bounded exactly as metadata cloning is: a subtree past
15
+ * {@link MAX_METADATA_DEPTH} becomes `"[MaxDepth]"`. Without that bound a
16
+ * parsed request body attached as a cause could overflow the stack *inside*
17
+ * the serializer, turning a logged error into an uncaught `RangeError`.
13
18
  */
14
- export declare function redactCauseFields(fields: Record<string, unknown>, pattern: RegExp | undefined, seen?: WeakSet<object>): Record<string, unknown>;
19
+ export declare function redactCauseFields(fields: Record<string, unknown>, pattern: RegExp | undefined, seen?: WeakSet<object>, depth?: number): Record<string, unknown>;
15
20
  /**
16
21
  * Redacts one cause value: arrays and plain objects are walked, every other
17
- * value is returned unchanged.
22
+ * value is returned unchanged. Walking stops at {@link MAX_METADATA_DEPTH}.
18
23
  */
19
- export declare function redactCauseValue(value: unknown, pattern: RegExp | undefined, seen?: WeakSet<object>): unknown;
24
+ export declare function redactCauseValue(value: unknown, pattern: RegExp | undefined, seen?: WeakSet<object>, depth?: number): unknown;
20
25
  //# sourceMappingURL=errorCause.redact.d.ts.map
@@ -6,7 +6,9 @@
6
6
  *
7
7
  * @module base/core/errorCause.redact
8
8
  */
9
- import { isForbiddenMetadataKey, isSensitiveMetadataKey, REDACTED_METADATA_VALUE, } from "./errorMetadata.core.js";
9
+ import { isForbiddenMetadataKey, isSensitiveMetadataKey, MAX_METADATA_DEPTH, REDACTED_METADATA_VALUE, } from "./errorMetadata.core.js";
10
+ /** Marker substituted for a cause subtree deeper than {@link MAX_METADATA_DEPTH}. */
11
+ const MAX_DEPTH_VALUE = "[MaxDepth]";
10
12
  /** Plain objects (Object.prototype or null prototype) are walked; anything else is kept as-is. */
11
13
  function isPlainRecord(value) {
12
14
  if (value === null || typeof value !== "object")
@@ -18,8 +20,13 @@ function isPlainRecord(value) {
18
20
  * Redacts sensitive keys inside a record without changing its shape:
19
21
  * primitives, dates and class instances are kept, plain objects and arrays
20
22
  * are walked, cycles stop at `"[Circular]"`, prototype keys are dropped.
23
+ *
24
+ * The walk is depth-bounded exactly as metadata cloning is: a subtree past
25
+ * {@link MAX_METADATA_DEPTH} becomes `"[MaxDepth]"`. Without that bound a
26
+ * parsed request body attached as a cause could overflow the stack *inside*
27
+ * the serializer, turning a logged error into an uncaught `RangeError`.
21
28
  */
22
- export function redactCauseFields(fields, pattern, seen = new WeakSet()) {
29
+ export function redactCauseFields(fields, pattern, seen = new WeakSet(), depth = 0) {
23
30
  const result = {};
24
31
  for (const key of Object.keys(fields)) {
25
32
  if (isForbiddenMetadataKey(key))
@@ -29,21 +36,23 @@ export function redactCauseFields(fields, pattern, seen = new WeakSet()) {
29
36
  : isSensitiveMetadataKey(key, pattern);
30
37
  result[key] = sensitive
31
38
  ? REDACTED_METADATA_VALUE
32
- : redactCauseValue(fields[key], pattern, seen);
39
+ : redactCauseValue(fields[key], pattern, seen, depth + 1);
33
40
  }
34
41
  return result;
35
42
  }
36
43
  /**
37
44
  * Redacts one cause value: arrays and plain objects are walked, every other
38
- * value is returned unchanged.
45
+ * value is returned unchanged. Walking stops at {@link MAX_METADATA_DEPTH}.
39
46
  */
40
- export function redactCauseValue(value, pattern, seen = new WeakSet()) {
47
+ export function redactCauseValue(value, pattern, seen = new WeakSet(), depth = 0) {
41
48
  if (Array.isArray(value)) {
49
+ if (depth > MAX_METADATA_DEPTH)
50
+ return MAX_DEPTH_VALUE;
42
51
  if (seen.has(value))
43
52
  return "[Circular]";
44
53
  seen.add(value);
45
54
  try {
46
- return value.map((entry) => redactCauseValue(entry, pattern, seen));
55
+ return value.map((entry) => redactCauseValue(entry, pattern, seen, depth + 1));
47
56
  }
48
57
  finally {
49
58
  seen.delete(value);
@@ -51,11 +60,13 @@ export function redactCauseValue(value, pattern, seen = new WeakSet()) {
51
60
  }
52
61
  if (!isPlainRecord(value))
53
62
  return value;
63
+ if (depth > MAX_METADATA_DEPTH)
64
+ return MAX_DEPTH_VALUE;
54
65
  if (seen.has(value))
55
66
  return "[Circular]";
56
67
  seen.add(value);
57
68
  try {
58
- return redactCauseFields(value, pattern, seen);
69
+ return redactCauseFields(value, pattern, seen, depth);
59
70
  }
60
71
  finally {
61
72
  seen.delete(value);
@@ -114,6 +114,7 @@ export declare enum ErrorCode {
114
114
  EVENT_REGISTRY_DISPOSED = "ERR_EVENT_REGISTRY_DISPOSED",
115
115
  EVENT_BUS_DISPOSED = "ERR_EVENT_BUS_DISPOSED",
116
116
  EVENT_SUBSCRIPTION_CLOSED = "ERR_EVENT_SUBSCRIPTION_CLOSED",
117
+ EVENT_LISTENER_LIMIT_EXCEEDED = "ERR_EVENT_LISTENER_LIMIT_EXCEEDED",
117
118
  EVENT_TIMEOUT = "ERR_EVENT_TIMEOUT",
118
119
  EVENT_SERIALIZATION_FAILED = "ERR_EVENT_SERIALIZATION_FAILED",
119
120
  EVENT_DESERIALIZATION_FAILED = "ERR_EVENT_DESERIALIZATION_FAILED",
@@ -120,6 +120,7 @@ export var ErrorCode;
120
120
  ErrorCode["EVENT_REGISTRY_DISPOSED"] = "ERR_EVENT_REGISTRY_DISPOSED";
121
121
  ErrorCode["EVENT_BUS_DISPOSED"] = "ERR_EVENT_BUS_DISPOSED";
122
122
  ErrorCode["EVENT_SUBSCRIPTION_CLOSED"] = "ERR_EVENT_SUBSCRIPTION_CLOSED";
123
+ ErrorCode["EVENT_LISTENER_LIMIT_EXCEEDED"] = "ERR_EVENT_LISTENER_LIMIT_EXCEEDED";
123
124
  ErrorCode["EVENT_TIMEOUT"] = "ERR_EVENT_TIMEOUT";
124
125
  ErrorCode["EVENT_SERIALIZATION_FAILED"] = "ERR_EVENT_SERIALIZATION_FAILED";
125
126
  ErrorCode["EVENT_DESERIALIZATION_FAILED"] = "ERR_EVENT_DESERIALIZATION_FAILED";
@@ -4,6 +4,6 @@
4
4
  export { EventError, createEventError, isEventError, toEventError, } from "./eventError.base.js";
5
5
  export type { EventErrorOptions } from "./eventError.base.js";
6
6
  export { EventHandlerError, createEventHandlerError, EventHandlerNotFoundError, DuplicateEventHandlerError, EventMiddlewareError, } from "./eventError.handler.js";
7
- export { EventPublishError, InvalidEventError, EventTypeNotFoundError, DuplicateEventDefinitionError, EventDefinitionNotFoundError, EventDispatchAbortedError, EventEmitterDisposedError, EventRegistryDisposedError, EventSubscriptionClosedError, EventBusDisposedError, EventTimeoutError, } from "./eventError.lifecycle.js";
7
+ export { EventPublishError, InvalidEventError, EventTypeNotFoundError, DuplicateEventDefinitionError, EventDefinitionNotFoundError, EventDispatchAbortedError, EventEmitterDisposedError, EventRegistryDisposedError, EventSubscriptionClosedError, EventBusDisposedError, EventListenerLimitExceededError, EventTimeoutError, } from "./eventError.lifecycle.js";
8
8
  export { EventSerializationError, EventDeserializationError, } from "./eventError.serialization.js";
9
9
  //# sourceMappingURL=event.error.d.ts.map
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export { EventError, createEventError, isEventError, toEventError, } from "./eventError.base.js";
5
5
  export { EventHandlerError, createEventHandlerError, EventHandlerNotFoundError, DuplicateEventHandlerError, EventMiddlewareError, } from "./eventError.handler.js";
6
- export { EventPublishError, InvalidEventError, EventTypeNotFoundError, DuplicateEventDefinitionError, EventDefinitionNotFoundError, EventDispatchAbortedError, EventEmitterDisposedError, EventRegistryDisposedError, EventSubscriptionClosedError, EventBusDisposedError, EventTimeoutError, } from "./eventError.lifecycle.js";
6
+ export { EventPublishError, InvalidEventError, EventTypeNotFoundError, DuplicateEventDefinitionError, EventDefinitionNotFoundError, EventDispatchAbortedError, EventEmitterDisposedError, EventRegistryDisposedError, EventSubscriptionClosedError, EventBusDisposedError, EventListenerLimitExceededError, EventTimeoutError, } from "./eventError.lifecycle.js";
7
7
  export { EventSerializationError, EventDeserializationError, } from "./eventError.serialization.js";
8
8
  //# sourceMappingURL=event.error.js.map
@@ -62,6 +62,22 @@ export declare class EventSubscriptionClosedError extends EventError {
62
62
  export declare class EventBusDisposedError extends EventError {
63
63
  constructor();
64
64
  }
65
+ /**
66
+ * Error thrown when a pattern exceeds its configured handler limit.
67
+ *
68
+ * The limit exists to catch a subscribe-without-unsubscribe leak. Registering
69
+ * past it is a programming fault rather than bad input, so this is reported as
70
+ * internal and is never exposed to a caller.
71
+ *
72
+ * A registry only raises this when it is configured to enforce the limit;
73
+ * the default is a one-shot warning, which does not interrupt registration.
74
+ */
75
+ export declare class EventListenerLimitExceededError extends EventError {
76
+ readonly pattern: string;
77
+ readonly count: number;
78
+ readonly limit: number;
79
+ constructor(pattern: string, count: number, limit: number);
80
+ }
65
81
  /** Error thrown when an event operation times out. */
66
82
  export declare class EventTimeoutError extends EventError {
67
83
  readonly timeoutMs: number;
@@ -130,6 +130,37 @@ export class EventBusDisposedError extends EventError {
130
130
  });
131
131
  }
132
132
  }
133
+ /**
134
+ * Error thrown when a pattern exceeds its configured handler limit.
135
+ *
136
+ * The limit exists to catch a subscribe-without-unsubscribe leak. Registering
137
+ * past it is a programming fault rather than bad input, so this is reported as
138
+ * internal and is never exposed to a caller.
139
+ *
140
+ * A registry only raises this when it is configured to enforce the limit;
141
+ * the default is a one-shot warning, which does not interrupt registration.
142
+ */
143
+ export class EventListenerLimitExceededError extends EventError {
144
+ pattern;
145
+ count;
146
+ limit;
147
+ constructor(pattern, count, limit) {
148
+ assertFiniteNonNegative("count", count);
149
+ assertFiniteNonNegative("limit", limit);
150
+ super(`Possible event handler leak: ${count} handlers registered for ` +
151
+ `"${pattern}" (limit ${limit}). Unsubscribe handlers you no longer ` +
152
+ "need or raise maxHandlersPerPattern / maxListeners.", {
153
+ code: ErrorCode.EVENT_LISTENER_LIMIT_EXCEEDED,
154
+ metadata: { pattern, count, limit },
155
+ statusCode: 500,
156
+ expose: false,
157
+ isOperational: false,
158
+ });
159
+ this.pattern = pattern;
160
+ this.count = count;
161
+ this.limit = limit;
162
+ }
163
+ }
133
164
  /** Error thrown when an event operation times out. */
134
165
  export class EventTimeoutError extends EventError {
135
166
  timeoutMs;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zudojs/errors",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Shared error base class, error codes, and error handling utilities for the Zudojs framework.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",