@danypops/vehicle-core 0.11.0 → 0.12.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.
@@ -14,13 +14,7 @@ export interface VehicleFailure {
14
14
  readonly recovery?: VehicleRecovery;
15
15
  readonly details?: JsonValue;
16
16
  readonly operationId?: string;
17
- /**
18
- * The underlying cause's own message, when this failure wraps an unexpected error (e.g. a
19
- * handler throwing something other than a VehicleError) -- bounded, never a full stack trace.
20
- * Without this, a caller sees only a generic template like "x handler failed" and has no way
21
- * to tell what actually went wrong or whether the underlying operation may have partially
22
- * applied.
23
- */
17
+ /** The underlying cause's own message, bounded (never a full stack trace). Only set when the throw site opts into exposeCause. */
24
18
  readonly causeMessage?: string;
25
19
  }
26
20
  export interface VehicleErrorOptions {
@@ -31,16 +25,27 @@ export interface VehicleErrorOptions {
31
25
  readonly details?: JsonValue;
32
26
  readonly operationId?: string;
33
27
  readonly cause?: unknown;
34
- /**
35
- * Secure by default (false): `cause` is always attached to the real in-process Error chain
36
- * (for server-side logging/observability), but its message crosses the wire via
37
- * toFailure().causeMessage only when the throw site explicitly opts in here -- an arbitrary
38
- * cause's message could contain a credential, an internal path, or other detail the thrower
39
- * never reviewed for wire-safety. Set true only when the cause is known-safe to show (e.g. a
40
- * validation library's own message intended for the caller).
41
- */
28
+ /** Includes cause's message in toFailure().causeMessage. Default false -- an arbitrary cause could carry a credential or internal detail. */
42
29
  readonly exposeCause?: boolean;
43
30
  }
31
+ export type VehicleErrorClass = abstract new (...args: never[]) => Error;
32
+ export interface VehicleErrorClassMapping {
33
+ readonly errorClass: VehicleErrorClass;
34
+ readonly category: VehicleFailureCategory;
35
+ readonly code?: string;
36
+ }
37
+ export interface VehicleErrorPredicateMapping {
38
+ readonly matches: (error: unknown) => boolean;
39
+ readonly category: VehicleFailureCategory;
40
+ readonly code?: string;
41
+ }
42
+ export type VehicleErrorMapping = VehicleErrorClassMapping | VehicleErrorPredicateMapping;
43
+ export interface DefineErrorMappingOptions {
44
+ readonly fallbackCategory?: VehicleFailureCategory;
45
+ readonly fallbackCode?: string;
46
+ }
47
+ /** Maps reviewed domain errors into wire-safe Vehicle errors while preserving already-mapped failures. */
48
+ export declare function defineErrorMapping(rules: readonly VehicleErrorMapping[], options?: DefineErrorMappingOptions): <T>(run: () => T | Promise<T>) => Promise<T>;
44
49
  export declare class VehicleError extends Error {
45
50
  readonly code: string;
46
51
  readonly category: VehicleFailureCategory;
@@ -1,3 +1,21 @@
1
+ /** Maps reviewed domain errors into wire-safe Vehicle errors while preserving already-mapped failures. */
2
+ export function defineErrorMapping(rules, options = {}) {
3
+ return async (run) => {
4
+ try {
5
+ return await run();
6
+ }
7
+ catch (error) {
8
+ if (error instanceof VehicleError)
9
+ throw error;
10
+ const rule = rules.find((candidate) => "errorClass" in candidate ? error instanceof candidate.errorClass : candidate.matches(error));
11
+ const message = error instanceof Error ? error.message : String(error);
12
+ throw new VehicleError(rule?.code ?? options.fallbackCode ?? "operation-rejected", message, {
13
+ category: rule?.category ?? options.fallbackCategory ?? "validation",
14
+ cause: error,
15
+ });
16
+ }
17
+ };
18
+ }
1
19
  export class VehicleError extends Error {
2
20
  code;
3
21
  category;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danypops/vehicle-core",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Vehicle's runtime-neutral wire contract: operation descriptors, schema codecs, failure shapes. Zero runtime dependencies, zero Bun-specific code -- the one thing every Vehicle client and server package depends on.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -45,13 +45,7 @@ export interface VehicleFailure {
45
45
  readonly recovery?: VehicleRecovery;
46
46
  readonly details?: JsonValue;
47
47
  readonly operationId?: string;
48
- /**
49
- * The underlying cause's own message, when this failure wraps an unexpected error (e.g. a
50
- * handler throwing something other than a VehicleError) -- bounded, never a full stack trace.
51
- * Without this, a caller sees only a generic template like "x handler failed" and has no way
52
- * to tell what actually went wrong or whether the underlying operation may have partially
53
- * applied.
54
- */
48
+ /** The underlying cause's own message, bounded (never a full stack trace). Only set when the throw site opts into exposeCause. */
55
49
  readonly causeMessage?: string;
56
50
  }
57
51
 
@@ -63,17 +57,53 @@ export interface VehicleErrorOptions {
63
57
  readonly details?: JsonValue;
64
58
  readonly operationId?: string;
65
59
  readonly cause?: unknown;
66
- /**
67
- * Secure by default (false): `cause` is always attached to the real in-process Error chain
68
- * (for server-side logging/observability), but its message crosses the wire via
69
- * toFailure().causeMessage only when the throw site explicitly opts in here -- an arbitrary
70
- * cause's message could contain a credential, an internal path, or other detail the thrower
71
- * never reviewed for wire-safety. Set true only when the cause is known-safe to show (e.g. a
72
- * validation library's own message intended for the caller).
73
- */
60
+ /** Includes cause's message in toFailure().causeMessage. Default false -- an arbitrary cause could carry a credential or internal detail. */
74
61
  readonly exposeCause?: boolean;
75
62
  }
76
63
 
64
+ export type VehicleErrorClass = abstract new (...args: never[]) => Error;
65
+
66
+ export interface VehicleErrorClassMapping {
67
+ readonly errorClass: VehicleErrorClass;
68
+ readonly category: VehicleFailureCategory;
69
+ readonly code?: string;
70
+ }
71
+
72
+ export interface VehicleErrorPredicateMapping {
73
+ readonly matches: (error: unknown) => boolean;
74
+ readonly category: VehicleFailureCategory;
75
+ readonly code?: string;
76
+ }
77
+
78
+ export type VehicleErrorMapping = VehicleErrorClassMapping | VehicleErrorPredicateMapping;
79
+
80
+ export interface DefineErrorMappingOptions {
81
+ readonly fallbackCategory?: VehicleFailureCategory;
82
+ readonly fallbackCode?: string;
83
+ }
84
+
85
+ /** Maps reviewed domain errors into wire-safe Vehicle errors while preserving already-mapped failures. */
86
+ export function defineErrorMapping(
87
+ rules: readonly VehicleErrorMapping[],
88
+ options: DefineErrorMappingOptions = {},
89
+ ): <T>(run: () => T | Promise<T>) => Promise<T> {
90
+ return async <T>(run: () => T | Promise<T>): Promise<T> => {
91
+ try {
92
+ return await run();
93
+ } catch (error) {
94
+ if (error instanceof VehicleError) throw error;
95
+ const rule = rules.find((candidate) =>
96
+ "errorClass" in candidate ? error instanceof candidate.errorClass : candidate.matches(error),
97
+ );
98
+ const message = error instanceof Error ? error.message : String(error);
99
+ throw new VehicleError(rule?.code ?? options.fallbackCode ?? "operation-rejected", message, {
100
+ category: rule?.category ?? options.fallbackCategory ?? "validation",
101
+ cause: error,
102
+ });
103
+ }
104
+ };
105
+ }
106
+
77
107
  export class VehicleError extends Error {
78
108
  readonly category: VehicleFailureCategory;
79
109
  readonly retryable: boolean;