11/**
22 * @lessjs /core — Unified Error Architecture (ADR-0053 / SOP-011).
3- *
4- * Four-layer error system:
5- * Layer 1: Typed error hierarchy (LessError → RenderError → ...)
6- * Layer 2: ErrorBoundary component (catch + fallback render)
7- * Layer 3: Error propagation pipeline (SSR accumulation, CSR bubbling, SPA retry)
8- * Layer 4: Error telemetry hook (application-level observer)
9- *
10- * All errors carry: code, severity, phase, recoverable, cause.
113 */
124
13- // ─── Error Severity ──────── ─────────────────────────────────────────
5+ // ─── Well-known error codes ─────────────────────────────────────────
146
15- export type ErrorSeverity = 'error' | 'warning' ;
7+ /** Well-known error code constants for reference. String values are always accepted. */
8+ export const ErrorCode = {
9+ SSR_RENDER_ERROR : 'SSR_RENDER_ERROR' ,
10+ ISLAND_RENDER_ERROR : 'ISLAND_RENDER_ERROR' ,
11+ PROP_VALIDATION_ERROR : 'PROP_VALIDATION_ERROR' ,
12+ NAVIGATION_ERROR : 'NAVIGATION_ERROR' ,
13+ BUILD_ERROR : 'BUILD_ERROR' ,
14+ RENDER_ERROR : 'RENDER_ERROR' ,
15+ BOUNDARY_CAUGHT : 'BOUNDARY_CAUGHT' ,
16+ UNKNOWN : 'UNKNOWN' ,
17+ } as const ;
1618
17- // ─── Error Phase ────────────────────────────────────────────────────
19+ // ─── Types ────── ────────────────────────────────────────────────────
1820
21+ export type ErrorSeverity = 'error' | 'warning' ;
1922export type ErrorPhase =
2023 | 'render'
2124 | 'ssr'
@@ -25,46 +28,45 @@ export type ErrorPhase =
2528 | 'validation'
2629 | 'unknown' ;
2730
28- // ─── Error Codes ────────────────────────────────────────────────────
29-
30- export enum ErrorCode {
31- SSR_RENDER_ERROR = 'SSR_RENDER_ERROR' ,
32- ISLAND_RENDER_ERROR = 'ISLAND_RENDER_ERROR' ,
33- PROP_VALIDATION_ERROR = 'PROP_VALIDATION_ERROR' ,
34- NAVIGATION_ERROR = 'NAVIGATION_ERROR' ,
35- BUILD_ERROR = 'BUILD_ERROR' ,
36- RENDER_ERROR = 'RENDER_ERROR' ,
37- BOUNDARY_CAUGHT = 'BOUNDARY_CAUGHT' ,
38- UNKNOWN = 'UNKNOWN' ,
39- }
40-
4131// ─── Base Error ─────────────────────────────────────────────────────
4232
4333export class LessError extends Error {
44- /** Stable error code from ErrorCode enum */
45- public readonly code : ErrorCode ;
46- /** Error severity */
34+ public readonly code : string ;
4735 public readonly severity : ErrorSeverity ;
48- /** Lifecycle phase where the error originated */
4936 public readonly phase : ErrorPhase ;
50- /** Can the application continue after this error? */
5137 public readonly recoverable : boolean ;
5238
5339 constructor (
5440 message : string ,
55- code : ErrorCode = ErrorCode . UNKNOWN ,
56- severity : ErrorSeverity = 'error' ,
57- phase : ErrorPhase = 'unknown' ,
58- recoverable = false ,
41+ code ?: string ,
42+ /** Backward compat: if number, treated as statusCode (old API) */
43+ severityOrStatus ?: ErrorSeverity | number ,
44+ phaseOrOperational ?: ErrorPhase | boolean ,
45+ recoverable ?: boolean ,
5946 cause ?: Error ,
6047 ) {
48+ let severity : ErrorSeverity ;
49+ let phase : ErrorPhase ;
50+ let rec : boolean ;
51+
52+ if ( typeof severityOrStatus === 'number' ) {
53+ const _statusCode = severityOrStatus ;
54+ severity = 'error' ;
55+ phase = 'render' ;
56+ rec = phaseOrOperational === true ;
57+ } else {
58+ severity = severityOrStatus ?? 'error' ;
59+ phase =
60+ ( typeof phaseOrOperational === 'string' ? phaseOrOperational : 'unknown' ) as ErrorPhase ;
61+ rec = recoverable ?? false ;
62+ }
63+
6164 super ( message , cause ? { cause } : undefined ) ;
6265 this . name = 'LessError' ;
63- this . code = code ;
66+ this . code = code ?? ErrorCode . UNKNOWN ;
6467 this . severity = severity ;
6568 this . phase = phase ;
66- this . recoverable = recoverable ;
67- if ( cause ) this . cause = cause ;
69+ this . recoverable = rec ;
6870 }
6971
7072 toJSON ( ) : Record < string , unknown > {
@@ -80,69 +82,68 @@ export class LessError extends Error {
8082 }
8183}
8284
83- // ─── Render Errors ──────────────────────────────────────────────────
85+ // ─── SsrRenderError (backward compat) ────────────────────────────────
86+
87+ export class SsrRenderError extends LessError {
88+ public readonly componentPath : string ;
89+ public readonly sourceError : Error ;
90+
91+ constructor ( componentPath : string , sourceError : Error ) {
92+ super (
93+ `SSR render failed: ${ componentPath } ` ,
94+ 'SSR_RENDER_ERROR' ,
95+ 'error' ,
96+ 'ssr' as ErrorPhase ,
97+ false ,
98+ sourceError ,
99+ ) ;
100+ this . name = 'SsrRenderError' ;
101+ this . componentPath = componentPath ;
102+ this . sourceError = sourceError ;
103+ }
104+ }
105+
106+ // ─── New ADR-0053 error classes ─────────────────────────────────────
84107
85- /** Base class for all render-pipeline errors */
86108export class RenderError extends LessError {
87109 public readonly componentPath : string ;
110+ public readonly tagName : string ;
88111
89112 constructor (
90113 componentPath : string ,
91114 message : string ,
92- code : ErrorCode = ErrorCode . RENDER_ERROR ,
115+ code = 'RENDER_ERROR' ,
116+ tagName = '' ,
93117 cause ?: Error ,
94118 ) {
95119 super ( message , code , 'error' , 'render' , true , cause ) ;
96120 this . name = 'RenderError' ;
97121 this . componentPath = componentPath ;
98- }
99-
100- override toJSON ( ) : Record < string , unknown > {
101- return {
102- ...super . toJSON ( ) ,
103- componentPath : this . componentPath ,
104- } ;
105- }
106- }
107-
108- /** SSR rendering failed */
109- export class SsrRenderError extends RenderError {
110- public readonly sourceError : Error ;
111-
112- constructor ( componentPath : string , sourceError : Error ) {
113- super (
114- componentPath ,
115- `SSR render failed: ${ componentPath } ` ,
116- ErrorCode . SSR_RENDER_ERROR ,
117- sourceError ,
118- ) ;
119- this . name = 'SsrRenderError' ;
120- this . sourceError = sourceError ;
122+ this . tagName = tagName ;
121123 }
122124}
123125
124- /** Island hydration failed */
125126export class IslandRenderError extends RenderError {
126127 constructor ( componentPath : string , sourceError : Error ) {
127128 super (
128129 componentPath ,
129130 `Island render failed: ${ componentPath } ` ,
130- ErrorCode . ISLAND_RENDER_ERROR ,
131+ 'ISLAND_RENDER_ERROR' ,
132+ '' ,
131133 sourceError ,
132134 ) ;
133135 this . name = 'IslandRenderError' ;
134136 }
135137}
136138
137- /** @prop () validation failed */
138139export class PropValidationError extends LessError {
139140 public readonly propertyName : string ;
140141 public readonly receivedValue : unknown ;
141142
142143 constructor ( propertyName : string , receivedValue : unknown , cause ?: Error ) {
143144 super (
144- `@prop validation failed for "${ propertyName } ": received ${ typeof receivedValue } ` ,
145- ErrorCode . PROP_VALIDATION_ERROR ,
145+ `@prop validation failed for "${ propertyName } "` ,
146+ ' PROP_VALIDATION_ERROR' ,
146147 'warning' ,
147148 'validation' ,
148149 true ,
@@ -154,30 +155,19 @@ export class PropValidationError extends LessError {
154155 }
155156}
156157
157- // ─── Navigation Errors ──────────────────────────────────────────────
158-
159158export class NavigationError extends LessError {
160159 public readonly route : string ;
161160
162161 constructor ( route : string , cause ?: Error ) {
163- super (
164- `Navigation failed for route: ${ route } ` ,
165- ErrorCode . NAVIGATION_ERROR ,
166- 'error' ,
167- 'navigation' ,
168- true ,
169- cause ,
170- ) ;
162+ super ( `Navigation failed: ${ route } ` , 'NAVIGATION_ERROR' , 'error' , 'navigation' , true , cause ) ;
171163 this . name = 'NavigationError' ;
172164 this . route = route ;
173165 }
174166}
175167
176- // ─── Build Errors ───────────────────────────────────────────────────
177-
178168export class BuildError extends LessError {
179169 constructor ( message : string , cause ?: Error ) {
180- super ( message , ErrorCode . BUILD_ERROR , 'error' , 'build' , false , cause ) ;
170+ super ( message , ' BUILD_ERROR' , 'error' , 'build' , false , cause ) ;
181171 this . name = 'BuildError' ;
182172 }
183173}
@@ -192,14 +182,11 @@ export function setErrorTelemetryHook(hook: ErrorTelemetryHook): void {
192182 _telemetryHook = hook ;
193183}
194184
195- /** Report an error through the telemetry pipeline */
196185export function reportError ( error : LessError ) : void {
197186 if ( _telemetryHook ) {
198187 try {
199188 _telemetryHook ( error ) ;
200- } catch {
201- // Telemetry hook must not throw
202- }
189+ } catch { /* must not throw */ }
203190 } else {
204191 console . error ( `[LessJS:${ error . code } ] ${ error . message } ` ) ;
205192 }
@@ -229,10 +216,7 @@ export class SsrErrorContext {
229216 return this . errors . length > 0 ;
230217 }
231218
232- /** Accumulate errors from another context (e.g. child component) */
233219 merge ( other : SsrErrorContext ) : void {
234- for ( const entry of other . errors ) {
235- this . add ( entry ) ;
236- }
220+ for ( const e of other . errors ) this . add ( e ) ;
237221 }
238222}
0 commit comments