@zudojs/errors 1.1.0 → 1.3.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 CHANGED
@@ -47,7 +47,7 @@ try {
47
47
  - `BaseError` with stable `code`, `category`, `severity`, `statusCode`, `expose`, `isOperational`, deep-frozen `metadata` and `cause`
48
48
  - `ErrorCode`, `ErrorCategory` and `ErrorSeverity` enums with guards (`isErrorCode`, ...)
49
49
  - Domain, infrastructure and system error families (access, state, HTTP, database, network, container, adapter, crypto, ...) with factories and type guards
50
- - Base classes for other packages' error families, so their `instanceof` checks match: `TransactionError` (+ 10 subclasses), `MiddlewareLimitExceededError` / `MiddlewareDepthExceededError` / `MiddlewareRateLimitError` / `MiddlewareAbortedError`, `TraversalLimitError`, `HttpMiddlewareError` / `HttpMiddlewarePipelineError` / `HttpRequestGuardError`, `OpenAPIError`, `AuthError`, `OAuthError` (with `ErrorCode.OAUTH_*`), `CqrsError`, `ObservabilityError`, `InvalidConstantError` / `ConstantContextError`, and the `zudojs-cli` errors `CLIValidationError` / `CLIGenerationError` / `CLINotInProjectError` / `CLITemplateError`
50
+ - Base classes for other packages' error families, so their `instanceof` checks match: `TransactionError` (+ 10 subclasses), `MiddlewareLimitExceededError` / `MiddlewareDepthExceededError` / `MiddlewareRateLimitError` / `MiddlewareAbortedError`, `TraversalLimitError`, `HttpMiddlewareError` / `HttpMiddlewarePipelineError` / `HttpRequestGuardError`, `OpenAPIError`, `AuthError`, `OAuthError` (with `ErrorCode.OAUTH_*`), `CqrsError` (+ `CommandFailedError` / `QueryFailedError`, thrown when a failed command or query result is unwrapped), `EventBusStoppedError` / `EventBusDisposedError` (re-exported by `@zudojs/events`), `ObservabilityError`, `InvalidConstantError` / `ConstantContextError`, and the `zudojs-cli` errors `CLIValidationError` / `CLIGenerationError` / `CLINotInProjectError` / `CLITemplateError`
51
51
  - `withMetadata()` copies any error (including subclasses with custom constructors) with extra metadata
52
52
  - Serialization: `toJSON()`/`toLogObject()` for trusted logs (cycle-safe cause chains truncated with `"[MaxDepth]"` after 8 levels across the whole chain; metadata under sensitive keys, sensitive keys in object causes and submitted issue values are redacted, so `JSON.stringify(error)` is safe to log), `serializePublicError` / `ErrorSerializer` / `ErrorHandler.toPublicResult` for untrusted clients (recursive redaction, metadata allow-list)
53
53
  - Metadata utilities: `createErrorMetadata`, `mergeErrorMetadata`, `sanitizeErrorMetadata` (drops unsupported values) and `redactErrorMetadata` (removes secrets)
@@ -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);
@@ -43,6 +43,12 @@ export declare enum ErrorCode {
43
43
  SESSION_EXPIRED = "ERR_SESSION_EXPIRED",
44
44
  TOKEN_INVALID = "ERR_TOKEN_INVALID",
45
45
  TOKEN_EXPIRED = "ERR_TOKEN_EXPIRED",
46
+ /** A token that verified but has been revoked (logout, rotation replay). */
47
+ TOKEN_REVOKED = "ERR_TOKEN_REVOKED",
48
+ /** The account is temporarily locked after repeated failed sign-ins. */
49
+ ACCOUNT_LOCKED = "ERR_ACCOUNT_LOCKED",
50
+ /** The account exists but has been deactivated. */
51
+ ACCOUNT_DEACTIVATED = "ERR_ACCOUNT_DEACTIVATED",
46
52
  CRYPTO = "ERR_CRYPTO",
47
53
  CRYPTO_KEY = "ERR_CRYPTO_KEY",
48
54
  CRYPTO_HASH = "ERR_CRYPTO_HASH",
@@ -84,6 +90,8 @@ export declare enum ErrorCode {
84
90
  QUERY_HANDLER_NOT_FOUND = "ERR_QUERY_HANDLER_NOT_FOUND",
85
91
  INVALID_COMMAND = "ERR_INVALID_COMMAND",
86
92
  INVALID_QUERY = "ERR_INVALID_QUERY",
93
+ COMMAND_FAILED = "ERR_COMMAND_FAILED",
94
+ QUERY_FAILED = "ERR_QUERY_FAILED",
87
95
  EVENT_HANDLER_FAILED = "ERR_EVENT_HANDLER_FAILED",
88
96
  CONFIGURATION_ERROR = "ERR_CONFIGURATION_ERROR",
89
97
  CONTAINER_DUPLICATE_REGISTRATION = "ERR_CONTAINER_DUPLICATE_REGISTRATION",
@@ -114,6 +122,7 @@ export declare enum ErrorCode {
114
122
  EVENT_REGISTRY_DISPOSED = "ERR_EVENT_REGISTRY_DISPOSED",
115
123
  EVENT_BUS_DISPOSED = "ERR_EVENT_BUS_DISPOSED",
116
124
  EVENT_SUBSCRIPTION_CLOSED = "ERR_EVENT_SUBSCRIPTION_CLOSED",
125
+ EVENT_LISTENER_LIMIT_EXCEEDED = "ERR_EVENT_LISTENER_LIMIT_EXCEEDED",
117
126
  EVENT_TIMEOUT = "ERR_EVENT_TIMEOUT",
118
127
  EVENT_SERIALIZATION_FAILED = "ERR_EVENT_SERIALIZATION_FAILED",
119
128
  EVENT_DESERIALIZATION_FAILED = "ERR_EVENT_DESERIALIZATION_FAILED",
@@ -44,6 +44,12 @@ export var ErrorCode;
44
44
  ErrorCode["SESSION_EXPIRED"] = "ERR_SESSION_EXPIRED";
45
45
  ErrorCode["TOKEN_INVALID"] = "ERR_TOKEN_INVALID";
46
46
  ErrorCode["TOKEN_EXPIRED"] = "ERR_TOKEN_EXPIRED";
47
+ /** A token that verified but has been revoked (logout, rotation replay). */
48
+ ErrorCode["TOKEN_REVOKED"] = "ERR_TOKEN_REVOKED";
49
+ /** The account is temporarily locked after repeated failed sign-ins. */
50
+ ErrorCode["ACCOUNT_LOCKED"] = "ERR_ACCOUNT_LOCKED";
51
+ /** The account exists but has been deactivated. */
52
+ ErrorCode["ACCOUNT_DEACTIVATED"] = "ERR_ACCOUNT_DEACTIVATED";
47
53
  ErrorCode["CRYPTO"] = "ERR_CRYPTO";
48
54
  ErrorCode["CRYPTO_KEY"] = "ERR_CRYPTO_KEY";
49
55
  ErrorCode["CRYPTO_HASH"] = "ERR_CRYPTO_HASH";
@@ -86,6 +92,8 @@ export var ErrorCode;
86
92
  ErrorCode["QUERY_HANDLER_NOT_FOUND"] = "ERR_QUERY_HANDLER_NOT_FOUND";
87
93
  ErrorCode["INVALID_COMMAND"] = "ERR_INVALID_COMMAND";
88
94
  ErrorCode["INVALID_QUERY"] = "ERR_INVALID_QUERY";
95
+ ErrorCode["COMMAND_FAILED"] = "ERR_COMMAND_FAILED";
96
+ ErrorCode["QUERY_FAILED"] = "ERR_QUERY_FAILED";
89
97
  ErrorCode["EVENT_HANDLER_FAILED"] = "ERR_EVENT_HANDLER_FAILED";
90
98
  ErrorCode["CONFIGURATION_ERROR"] = "ERR_CONFIGURATION_ERROR";
91
99
  // Container
@@ -120,6 +128,7 @@ export var ErrorCode;
120
128
  ErrorCode["EVENT_REGISTRY_DISPOSED"] = "ERR_EVENT_REGISTRY_DISPOSED";
121
129
  ErrorCode["EVENT_BUS_DISPOSED"] = "ERR_EVENT_BUS_DISPOSED";
122
130
  ErrorCode["EVENT_SUBSCRIPTION_CLOSED"] = "ERR_EVENT_SUBSCRIPTION_CLOSED";
131
+ ErrorCode["EVENT_LISTENER_LIMIT_EXCEEDED"] = "ERR_EVENT_LISTENER_LIMIT_EXCEEDED";
123
132
  ErrorCode["EVENT_TIMEOUT"] = "ERR_EVENT_TIMEOUT";
124
133
  ErrorCode["EVENT_SERIALIZATION_FAILED"] = "ERR_EVENT_SERIALIZATION_FAILED";
125
134
  ErrorCode["EVENT_DESERIALIZATION_FAILED"] = "ERR_EVENT_DESERIALIZATION_FAILED";
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Errors raised when a failed command or query result is unwrapped.
3
+ */
4
+ import { CqrsError } from "./cqrsError.base.js";
5
+ /**
6
+ * Thrown by `unwrapCommandResult` for a result whose status is `"failure"`.
7
+ *
8
+ * `failure` holds the failure payload the result carried, which is also the
9
+ * error's `cause`. Not exposed to clients: the payload is an internal value.
10
+ */
11
+ export declare class CommandFailedError extends CqrsError {
12
+ readonly commandType: string;
13
+ readonly failure: unknown;
14
+ constructor(commandType: string, failure: unknown);
15
+ }
16
+ /**
17
+ * Thrown by `unwrapQueryResult` for a result whose status is `"failure"`.
18
+ *
19
+ * `failure` holds the failure payload the result carried, which is also the
20
+ * error's `cause`. Not exposed to clients: the payload is an internal value.
21
+ */
22
+ export declare class QueryFailedError extends CqrsError {
23
+ readonly queryType: string;
24
+ readonly failure: unknown;
25
+ constructor(queryType: string, failure: unknown);
26
+ }
27
+ //# sourceMappingURL=cqrsError.result.d.ts.map
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Errors raised when a failed command or query result is unwrapped.
3
+ */
4
+ import { ErrorCode } from "../../base/types/errorCode.type.js";
5
+ import { CqrsError } from "./cqrsError.base.js";
6
+ /**
7
+ * Thrown by `unwrapCommandResult` for a result whose status is `"failure"`.
8
+ *
9
+ * `failure` holds the failure payload the result carried, which is also the
10
+ * error's `cause`. Not exposed to clients: the payload is an internal value.
11
+ */
12
+ export class CommandFailedError extends CqrsError {
13
+ commandType;
14
+ failure;
15
+ constructor(commandType, failure) {
16
+ super(`Command "${commandType}" failed.`, {
17
+ code: ErrorCode.COMMAND_FAILED,
18
+ statusCode: 500,
19
+ expose: false,
20
+ isOperational: true,
21
+ cause: failure,
22
+ metadata: { commandType },
23
+ });
24
+ this.commandType = commandType;
25
+ this.failure = failure;
26
+ }
27
+ }
28
+ /**
29
+ * Thrown by `unwrapQueryResult` for a result whose status is `"failure"`.
30
+ *
31
+ * `failure` holds the failure payload the result carried, which is also the
32
+ * error's `cause`. Not exposed to clients: the payload is an internal value.
33
+ */
34
+ export class QueryFailedError extends CqrsError {
35
+ queryType;
36
+ failure;
37
+ constructor(queryType, failure) {
38
+ super(`Query "${queryType}" failed.`, {
39
+ code: ErrorCode.QUERY_FAILED,
40
+ statusCode: 500,
41
+ expose: false,
42
+ isOperational: true,
43
+ cause: failure,
44
+ metadata: { queryType },
45
+ });
46
+ this.queryType = queryType;
47
+ this.failure = failure;
48
+ }
49
+ }
50
+ //# sourceMappingURL=cqrsError.result.js.map
@@ -4,4 +4,5 @@
4
4
  * Command/query bus errors.
5
5
  */
6
6
  export * from "./cqrsError.base.js";
7
+ export * from "./cqrsError.result.js";
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -4,4 +4,5 @@
4
4
  * Command/query bus errors.
5
5
  */
6
6
  export * from "./cqrsError.base.js";
7
+ export * from "./cqrsError.result.js";
7
8
  //# sourceMappingURL=index.js.map
@@ -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, EventBusStoppedError, 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, EventBusStoppedError, 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,30 @@ export declare class EventSubscriptionClosedError extends EventError {
62
62
  export declare class EventBusDisposedError extends EventError {
63
63
  constructor();
64
64
  }
65
+ /**
66
+ * Error thrown when publishing or subscribing on a stopped event bus.
67
+ * Call `start()` to resume.
68
+ */
69
+ export declare class EventBusStoppedError extends EventError {
70
+ readonly operation: string;
71
+ constructor(operation: string);
72
+ }
73
+ /**
74
+ * Error thrown when a pattern exceeds its configured handler limit.
75
+ *
76
+ * The limit exists to catch a subscribe-without-unsubscribe leak. Registering
77
+ * past it is a programming fault rather than bad input, so this is reported as
78
+ * internal and is never exposed to a caller.
79
+ *
80
+ * A registry only raises this when it is configured to enforce the limit;
81
+ * the default is a one-shot warning, which does not interrupt registration.
82
+ */
83
+ export declare class EventListenerLimitExceededError extends EventError {
84
+ readonly pattern: string;
85
+ readonly count: number;
86
+ readonly limit: number;
87
+ constructor(pattern: string, count: number, limit: number);
88
+ }
65
89
  /** Error thrown when an event operation times out. */
66
90
  export declare class EventTimeoutError extends EventError {
67
91
  readonly timeoutMs: number;
@@ -130,6 +130,54 @@ export class EventBusDisposedError extends EventError {
130
130
  });
131
131
  }
132
132
  }
133
+ /**
134
+ * Error thrown when publishing or subscribing on a stopped event bus.
135
+ * Call `start()` to resume.
136
+ */
137
+ export class EventBusStoppedError extends EventError {
138
+ operation;
139
+ constructor(operation) {
140
+ super(`Cannot ${operation} on a stopped event bus. Call start() first.`, {
141
+ code: ErrorCode.LIFECYCLE_STATE,
142
+ statusCode: 500,
143
+ expose: false,
144
+ isOperational: true,
145
+ metadata: { operation },
146
+ });
147
+ this.operation = operation;
148
+ }
149
+ }
150
+ /**
151
+ * Error thrown when a pattern exceeds its configured handler limit.
152
+ *
153
+ * The limit exists to catch a subscribe-without-unsubscribe leak. Registering
154
+ * past it is a programming fault rather than bad input, so this is reported as
155
+ * internal and is never exposed to a caller.
156
+ *
157
+ * A registry only raises this when it is configured to enforce the limit;
158
+ * the default is a one-shot warning, which does not interrupt registration.
159
+ */
160
+ export class EventListenerLimitExceededError extends EventError {
161
+ pattern;
162
+ count;
163
+ limit;
164
+ constructor(pattern, count, limit) {
165
+ assertFiniteNonNegative("count", count);
166
+ assertFiniteNonNegative("limit", limit);
167
+ super(`Possible event handler leak: ${count} handlers registered for ` +
168
+ `"${pattern}" (limit ${limit}). Unsubscribe handlers you no longer ` +
169
+ "need or raise maxHandlersPerPattern / maxListeners.", {
170
+ code: ErrorCode.EVENT_LISTENER_LIMIT_EXCEEDED,
171
+ metadata: { pattern, count, limit },
172
+ statusCode: 500,
173
+ expose: false,
174
+ isOperational: false,
175
+ });
176
+ this.pattern = pattern;
177
+ this.count = count;
178
+ this.limit = limit;
179
+ }
180
+ }
133
181
  /** Error thrown when an event operation times out. */
134
182
  export class EventTimeoutError extends EventError {
135
183
  timeoutMs;
@@ -9,10 +9,19 @@ import { ErrorSeverity } from "../../base/types/errorSeverity.type.js";
9
9
  export interface RPCErrorOptions extends Omit<BaseErrorOptions, "category"> {
10
10
  readonly category?: ErrorCategory;
11
11
  readonly procedureName?: string;
12
+ /** Structured, caller-safe detail sent with the error's wire payload. */
13
+ readonly details?: unknown;
12
14
  }
13
15
  /** Base error for all RPC failures. */
14
16
  export declare class RPCError extends BaseError {
15
17
  readonly procedureName?: string;
18
+ /**
19
+ * Structured, caller-safe detail carried by the wire payload — validation
20
+ * issues, `{ retryAfter }` for a rate limit, or whatever a custom error
21
+ * sent. Set on errors an RPC client rebuilds from a response; `undefined`
22
+ * when the payload had none.
23
+ */
24
+ readonly details?: unknown;
16
25
  constructor(message: string, options?: RPCErrorOptions);
17
26
  toJSON(): {
18
27
  name: string;
@@ -27,6 +36,7 @@ export declare class RPCError extends BaseError {
27
36
  stack?: string;
28
37
  cause?: import("../../base/types/baseError.type.js").SerializedBaseError | unknown;
29
38
  procedureName?: string | undefined;
39
+ details?: {} | null | undefined;
30
40
  };
31
41
  }
32
42
  /** Creates an RPC error. */
@@ -19,6 +19,13 @@ export class RPCError extends BaseError {
19
19
  isOperational: options.isOperational ?? true,
20
20
  });
21
21
  this.procedureName = options.procedureName;
22
+ if (options.details !== undefined) {
23
+ Object.defineProperty(this, "details", {
24
+ value: options.details,
25
+ enumerable: true,
26
+ configurable: true,
27
+ });
28
+ }
22
29
  }
23
30
  toJSON() {
24
31
  return {
@@ -26,6 +33,7 @@ export class RPCError extends BaseError {
26
33
  ...(this.procedureName !== undefined
27
34
  ? { procedureName: this.procedureName }
28
35
  : {}),
36
+ ...(this.details !== undefined ? { details: this.details } : {}),
29
37
  };
30
38
  }
31
39
  }
@@ -5,10 +5,16 @@ import { BaseError } from "../../base/core/baseError.core.js";
5
5
  import type { BaseErrorOptions } from "../../base/types/baseError.type.js";
6
6
  import { ErrorCategory } from "../../base/types/errorCategory.type.js";
7
7
  import { ErrorSeverity } from "../../base/types/errorSeverity.type.js";
8
- /** Options for constructing a SchemaError. */
9
- export interface SchemaErrorOptions extends Omit<BaseErrorOptions, "category"> {
8
+ /**
9
+ * Options for constructing a SchemaError.
10
+ *
11
+ * @typeParam TIssue - Shape of one issue. `@zudojs/errors` cannot know it
12
+ * (the schema package sits above it), so it defaults to `unknown`;
13
+ * `@zudojs/schema` throws `SchemaError<SchemaIssue>`.
14
+ */
15
+ export interface SchemaErrorOptions<TIssue = unknown> extends Omit<BaseErrorOptions, "category"> {
10
16
  readonly category?: ErrorCategory;
11
- readonly issues?: readonly unknown[];
17
+ readonly issues?: readonly TIssue[];
12
18
  }
13
19
  /**
14
20
  * Base error for all schema validation failures.
@@ -19,9 +25,9 @@ export interface SchemaErrorOptions extends Omit<BaseErrorOptions, "category"> {
19
25
  * (`value`, `received`, `input`, `actual`) with a type/size description so
20
26
  * that secrets submitted by a client are never echoed back or logged.
21
27
  */
22
- export declare class SchemaError extends BaseError {
23
- readonly issues: readonly unknown[];
24
- constructor(message: string, options?: SchemaErrorOptions);
28
+ export declare class SchemaError<TIssue = unknown> extends BaseError {
29
+ readonly issues: readonly TIssue[];
30
+ constructor(message: string, options?: SchemaErrorOptions<TIssue>);
25
31
  /** Returns whether any issues were recorded. */
26
32
  hasIssues(): boolean;
27
33
  /** Returns a serialized representation including (redacted) issues. */
@@ -37,11 +43,11 @@ export declare class SchemaError extends BaseError {
37
43
  metadata: Readonly<import("../../index.js").ErrorMetadata>;
38
44
  stack?: string;
39
45
  cause?: import("../../base/types/baseError.type.js").SerializedBaseError | unknown;
40
- issues: readonly unknown[];
46
+ issues: readonly TIssue[];
41
47
  };
42
48
  }
43
49
  /** Creates a schema error. */
44
- export declare function createSchemaError(message: string, options?: SchemaErrorOptions): SchemaError;
50
+ export declare function createSchemaError<TIssue = unknown>(message: string, options?: SchemaErrorOptions<TIssue>): SchemaError<TIssue>;
45
51
  /** Determines whether an unknown value is a SchemaError. */
46
52
  export declare function isSchemaError(value: unknown): value is SchemaError;
47
53
  //# sourceMappingURL=schemaError.base.d.ts.map
@@ -42,14 +42,21 @@ export declare class CircularReferenceError extends SerializationError {
42
42
  /**
43
43
  * Error thrown when maximum serialization depth is exceeded.
44
44
  *
45
- * Over-deep data is a server-side data bug, so this is an internal (500) error.
45
+ * By default over-deep data is a server-side data bug, so this is an
46
+ * internal (500) error. Code that checks UNTRUSTED input (for example
47
+ * `assertDepthWithinLimit` in `@zudojs/validation`) passes
48
+ * `{ statusCode: 400, expose: true }`: too-deep client input is a client
49
+ * error. The message holds only the two numbers, so it is safe to expose.
46
50
  */
47
51
  export declare class SerializationDepthError extends SerializationError {
48
52
  readonly depth: number;
49
53
  readonly maxDepth: number;
50
54
  /** @deprecated Use `maxDepth`. */
51
55
  readonly maxDepthValue: number;
52
- constructor(depth: number, maxDepth: number);
56
+ constructor(depth: number, maxDepth: number, options?: {
57
+ readonly statusCode?: number;
58
+ readonly expose?: boolean;
59
+ });
53
60
  }
54
61
  /** Error thrown when a serialized payload exceeds the size limit. */
55
62
  export declare class SerializationPayloadTooLargeError extends SerializationError {
@@ -74,20 +74,24 @@ export class CircularReferenceError extends SerializationError {
74
74
  /**
75
75
  * Error thrown when maximum serialization depth is exceeded.
76
76
  *
77
- * Over-deep data is a server-side data bug, so this is an internal (500) error.
77
+ * By default over-deep data is a server-side data bug, so this is an
78
+ * internal (500) error. Code that checks UNTRUSTED input (for example
79
+ * `assertDepthWithinLimit` in `@zudojs/validation`) passes
80
+ * `{ statusCode: 400, expose: true }`: too-deep client input is a client
81
+ * error. The message holds only the two numbers, so it is safe to expose.
78
82
  */
79
83
  export class SerializationDepthError extends SerializationError {
80
84
  depth;
81
85
  maxDepth;
82
86
  /** @deprecated Use `maxDepth`. */
83
87
  maxDepthValue;
84
- constructor(depth, maxDepth) {
88
+ constructor(depth, maxDepth, options = {}) {
85
89
  super(`Maximum serialization depth exceeded: ${depth} > ${maxDepth}`, {
86
90
  code: ErrorCode.MAX_DEPTH_EXCEEDED,
87
91
  depth,
88
92
  maxDepth,
89
- statusCode: 500,
90
- expose: false,
93
+ statusCode: options.statusCode ?? 500,
94
+ expose: options.expose ?? false,
91
95
  });
92
96
  this.depth = depth;
93
97
  this.maxDepth = maxDepth;
@@ -20,8 +20,23 @@ export declare class TransactionRollbackError extends TransactionError {
20
20
  constructor(transactionId: string, options?: {
21
21
  readonly cause?: unknown;
22
22
  readonly originalError?: unknown;
23
+ /** Overrides the default "rollback failed" message (for subclasses). */
24
+ readonly message?: string;
23
25
  });
24
26
  }
27
+ /**
28
+ * A commit was refused because the transaction was marked rollback-only;
29
+ * the transaction was rolled back instead.
30
+ *
31
+ * Distinct from a rollback that *failed*. It extends
32
+ * `TransactionRollbackError`, so existing `instanceof` checks and the
33
+ * `ERR_DATABASE_TRANSACTION` code still match, while the message and
34
+ * class say what actually happened. `metadata.originalError` carries the
35
+ * reason passed to `markRollbackOnly`.
36
+ */
37
+ export declare class TransactionRollbackOnlyError extends TransactionRollbackError {
38
+ constructor(transactionId: string, reason?: unknown);
39
+ }
25
40
  /** The underlying adapter threw an error. */
26
41
  export declare class TransactionAdapterError extends TransactionError {
27
42
  constructor(message: string, cause?: unknown);
@@ -35,7 +35,7 @@ export class TransactionCommitError extends TransactionError {
35
35
  /** Transaction rollback failed. */
36
36
  export class TransactionRollbackError extends TransactionError {
37
37
  constructor(transactionId, options) {
38
- super(`Transaction "${transactionId}" rollback failed`, {
38
+ super(options?.message ?? `Transaction "${transactionId}" rollback failed`, {
39
39
  code: ErrorCode.DATABASE_TRANSACTION,
40
40
  cause: options?.cause,
41
41
  metadata: {
@@ -47,6 +47,24 @@ export class TransactionRollbackError extends TransactionError {
47
47
  });
48
48
  }
49
49
  }
50
+ /**
51
+ * A commit was refused because the transaction was marked rollback-only;
52
+ * the transaction was rolled back instead.
53
+ *
54
+ * Distinct from a rollback that *failed*. It extends
55
+ * `TransactionRollbackError`, so existing `instanceof` checks and the
56
+ * `ERR_DATABASE_TRANSACTION` code still match, while the message and
57
+ * class say what actually happened. `metadata.originalError` carries the
58
+ * reason passed to `markRollbackOnly`.
59
+ */
60
+ export class TransactionRollbackOnlyError extends TransactionRollbackError {
61
+ constructor(transactionId, reason) {
62
+ super(transactionId, {
63
+ originalError: reason ?? "marked rollback-only",
64
+ message: `Transaction "${transactionId}" commit refused: transaction marked rollback-only`,
65
+ });
66
+ }
67
+ }
50
68
  /** The underlying adapter threw an error. */
51
69
  export class TransactionAdapterError extends TransactionError {
52
70
  constructor(message, cause) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zudojs/errors",
3
- "version": "1.1.0",
3
+ "version": "1.3.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",
@@ -22,7 +22,7 @@
22
22
  "dependencies": {},
23
23
  "devDependencies": {
24
24
  "typescript": "7.0.2",
25
- "vitest": "^4.1.11"
25
+ "vitest": "^5.0.1"
26
26
  },
27
27
  "engines": {
28
28
  "node": ">=24.0.0"
@@ -40,7 +40,7 @@
40
40
  "errors",
41
41
  "error-handling"
42
42
  ],
43
- "homepage": "https://github.com/oyinlola-tech/zudo#readme",
43
+ "homepage": "https://zudojs.oyinlola.site/docs/packages-errors",
44
44
  "bugs": {
45
45
  "url": "https://github.com/oyinlola-tech/zudo/issues"
46
46
  },