@ontrails/core 0.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/CHANGELOG.md +849 -0
- package/README.md +190 -0
- package/package.json +36 -0
- package/src/activation-provenance.ts +116 -0
- package/src/activation-source-compatibility.ts +430 -0
- package/src/activation-source-derivation.ts +227 -0
- package/src/activation-source.ts +93 -0
- package/src/blob-ref.ts +90 -0
- package/src/branded.ts +135 -0
- package/src/collections.ts +99 -0
- package/src/compose-batch.ts +69 -0
- package/src/compose-schema.ts +36 -0
- package/src/context.ts +66 -0
- package/src/derive.ts +485 -0
- package/src/detours.ts +8 -0
- package/src/diagnostics.ts +21 -0
- package/src/draft.ts +350 -0
- package/src/entity.ts +346 -0
- package/src/error-rendering.ts +87 -0
- package/src/errors.ts +483 -0
- package/src/execute.ts +1577 -0
- package/src/fetch.ts +138 -0
- package/src/fire.ts +1172 -0
- package/src/glob.ts +81 -0
- package/src/guards.ts +37 -0
- package/src/index.ts +704 -0
- package/src/internal/fork-ctx.ts +69 -0
- package/src/layer-field-rendering.ts +193 -0
- package/src/layer.ts +81 -0
- package/src/observe.ts +361 -0
- package/src/path-scope.ts +66 -0
- package/src/path-security.ts +98 -0
- package/src/patterns/bulk.ts +16 -0
- package/src/patterns/change.ts +12 -0
- package/src/patterns/date-range.ts +12 -0
- package/src/patterns/index.ts +8 -0
- package/src/patterns/pagination.ts +22 -0
- package/src/patterns/progress.ts +13 -0
- package/src/patterns/sorting.ts +14 -0
- package/src/patterns/status.ts +11 -0
- package/src/patterns/timestamps.ts +12 -0
- package/src/permits.ts +12 -0
- package/src/queue.ts +163 -0
- package/src/redaction/index.ts +3 -0
- package/src/redaction/patterns.ts +50 -0
- package/src/redaction/redactor.ts +178 -0
- package/src/resilience.ts +234 -0
- package/src/resource-config.ts +804 -0
- package/src/resource.ts +194 -0
- package/src/result.ts +212 -0
- package/src/run.ts +76 -0
- package/src/runtime-builtins.ts +69 -0
- package/src/schedule-runtime.ts +689 -0
- package/src/schedule.ts +326 -0
- package/src/serialization.ts +265 -0
- package/src/sha256.ts +136 -0
- package/src/signal-diagnostics.ts +633 -0
- package/src/signal-ref.ts +111 -0
- package/src/signal.ts +104 -0
- package/src/store/accessor-protocol.ts +56 -0
- package/src/store/index.ts +4 -0
- package/src/structured-examples.ts +248 -0
- package/src/surface-derivation.ts +91 -0
- package/src/surface-filter.ts +101 -0
- package/src/surface-overlay.ts +694 -0
- package/src/surface-versioning.ts +42 -0
- package/src/topo.ts +835 -0
- package/src/tracing.ts +346 -0
- package/src/trail-id-glob.ts +15 -0
- package/src/trail.ts +1351 -0
- package/src/trails/derive-trail.ts +835 -0
- package/src/trails/index.ts +9 -0
- package/src/trails/ingest.ts +152 -0
- package/src/trails-db.ts +212 -0
- package/src/transport-error-map.ts +163 -0
- package/src/type-utils.ts +87 -0
- package/src/types.ts +300 -0
- package/src/validate-established-topo.ts +73 -0
- package/src/validate-topo.ts +725 -0
- package/src/validation.ts +330 -0
- package/src/version-marker.ts +716 -0
- package/src/version-resolution.ts +308 -0
- package/src/version-runtime.ts +120 -0
- package/src/webhook.ts +461 -0
- package/src/workspace.ts +244 -0
- package/src/zod-wrappers.ts +72 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { ErrorCategory } from './errors.js';
|
|
2
|
+
import { isTrailsError } from './errors.js';
|
|
3
|
+
import { createRedactor } from './redaction/index.js';
|
|
4
|
+
|
|
5
|
+
const errorRedactor = createRedactor();
|
|
6
|
+
|
|
7
|
+
export const INTERNAL_ERROR_PUBLIC_MESSAGE = 'Internal server error';
|
|
8
|
+
|
|
9
|
+
export interface ErrorDiagnosticsRendering {
|
|
10
|
+
readonly category?: ErrorCategory | undefined;
|
|
11
|
+
readonly context?: Record<string, unknown> | undefined;
|
|
12
|
+
readonly message: string;
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly retryable?: boolean | undefined;
|
|
15
|
+
readonly stack?: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PublicErrorRendering {
|
|
19
|
+
readonly category: ErrorCategory;
|
|
20
|
+
readonly message: string;
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly retryable: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const redactErrorString = (value: string): string =>
|
|
26
|
+
errorRedactor.redact(value);
|
|
27
|
+
|
|
28
|
+
export const redactErrorContext = (
|
|
29
|
+
context: Record<string, unknown> | undefined
|
|
30
|
+
): Record<string, unknown> | undefined =>
|
|
31
|
+
context === undefined ? undefined : errorRedactor.redactObject(context);
|
|
32
|
+
|
|
33
|
+
export const redactErrorStack = (
|
|
34
|
+
stack: string | undefined
|
|
35
|
+
): string | undefined =>
|
|
36
|
+
stack === undefined ? undefined : redactErrorString(stack);
|
|
37
|
+
|
|
38
|
+
export const renderErrorDiagnostics = (
|
|
39
|
+
error: Error
|
|
40
|
+
): ErrorDiagnosticsRendering => {
|
|
41
|
+
const context = isTrailsError(error)
|
|
42
|
+
? redactErrorContext(error.context)
|
|
43
|
+
: undefined;
|
|
44
|
+
const stack = redactErrorStack(error.stack);
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
...(isTrailsError(error)
|
|
48
|
+
? {
|
|
49
|
+
category: error.category,
|
|
50
|
+
retryable: error.retryable,
|
|
51
|
+
}
|
|
52
|
+
: {}),
|
|
53
|
+
...(context === undefined ? {} : { context }),
|
|
54
|
+
message: redactErrorString(error.message),
|
|
55
|
+
name: error.name || error.constructor.name || 'Error',
|
|
56
|
+
...(stack === undefined ? {} : { stack }),
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Render an error through the shared public redaction policy.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const rendering = renderPublicError(new NotFoundError('missing'));
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export const renderPublicError = (error: Error): PublicErrorRendering => {
|
|
69
|
+
if (isTrailsError(error)) {
|
|
70
|
+
return {
|
|
71
|
+
category: error.category,
|
|
72
|
+
message:
|
|
73
|
+
error.category === 'internal'
|
|
74
|
+
? INTERNAL_ERROR_PUBLIC_MESSAGE
|
|
75
|
+
: redactErrorString(error.message),
|
|
76
|
+
name: error.name,
|
|
77
|
+
retryable: error.retryable,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
category: 'internal',
|
|
83
|
+
message: INTERNAL_ERROR_PUBLIC_MESSAGE,
|
|
84
|
+
name: 'InternalError',
|
|
85
|
+
retryable: false,
|
|
86
|
+
};
|
|
87
|
+
};
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
/* oxlint-disable max-classes-per-file -- error taxonomy requires co-located class definitions */
|
|
2
|
+
/**
|
|
3
|
+
* Error taxonomy for @ontrails/core
|
|
4
|
+
*
|
|
5
|
+
* Provides a structured error hierarchy with category-based mapping
|
|
6
|
+
* to exit codes, HTTP status codes, and JSON-RPC error codes.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Category
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
export const errorCategories = [
|
|
14
|
+
'validation',
|
|
15
|
+
'not_found',
|
|
16
|
+
'conflict',
|
|
17
|
+
'permission',
|
|
18
|
+
'timeout',
|
|
19
|
+
'rate_limit',
|
|
20
|
+
'network',
|
|
21
|
+
'shift',
|
|
22
|
+
'internal',
|
|
23
|
+
'auth',
|
|
24
|
+
'cancelled',
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
export type ErrorCategory = (typeof errorCategories)[number];
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Base class
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
export abstract class TrailsError extends Error {
|
|
34
|
+
abstract readonly category: ErrorCategory;
|
|
35
|
+
abstract readonly retryable: boolean;
|
|
36
|
+
readonly context?: Record<string, unknown> | undefined;
|
|
37
|
+
|
|
38
|
+
constructor(
|
|
39
|
+
message: string,
|
|
40
|
+
options?: { cause?: Error; context?: Record<string, unknown> }
|
|
41
|
+
) {
|
|
42
|
+
super(message, { cause: options?.cause });
|
|
43
|
+
this.name = this.constructor.name;
|
|
44
|
+
this.context = options?.context;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Concrete error classes
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
export class ValidationError extends TrailsError {
|
|
53
|
+
readonly category = 'validation' as const;
|
|
54
|
+
readonly retryable = false as const;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class AmbiguousError extends TrailsError {
|
|
58
|
+
readonly category = 'validation' as const;
|
|
59
|
+
readonly retryable = false as const;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class AssertionError extends TrailsError {
|
|
63
|
+
readonly category = 'internal' as const;
|
|
64
|
+
readonly retryable = false as const;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export class NotFoundError extends TrailsError {
|
|
68
|
+
readonly category = 'not_found' as const;
|
|
69
|
+
readonly retryable = false as const;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export class VersionNotSupportedError extends NotFoundError {
|
|
73
|
+
readonly reason?: string | undefined;
|
|
74
|
+
readonly requested?: number | string | undefined;
|
|
75
|
+
readonly supported?: readonly number[] | undefined;
|
|
76
|
+
readonly trailId?: string | undefined;
|
|
77
|
+
|
|
78
|
+
constructor(
|
|
79
|
+
message: string,
|
|
80
|
+
options?: { cause?: Error; context?: Record<string, unknown> }
|
|
81
|
+
);
|
|
82
|
+
constructor(
|
|
83
|
+
trailId: string,
|
|
84
|
+
requested: number | string,
|
|
85
|
+
supported: readonly number[],
|
|
86
|
+
reason?: string | undefined
|
|
87
|
+
);
|
|
88
|
+
constructor(
|
|
89
|
+
messageOrTrailId: string,
|
|
90
|
+
optionsOrRequested?:
|
|
91
|
+
| { cause?: Error; context?: Record<string, unknown> }
|
|
92
|
+
| number
|
|
93
|
+
| string,
|
|
94
|
+
supported?: readonly number[],
|
|
95
|
+
reason?: string | undefined
|
|
96
|
+
) {
|
|
97
|
+
if (supported === undefined) {
|
|
98
|
+
super(
|
|
99
|
+
messageOrTrailId,
|
|
100
|
+
optionsOrRequested as
|
|
101
|
+
| { cause?: Error; context?: Record<string, unknown> }
|
|
102
|
+
| undefined
|
|
103
|
+
);
|
|
104
|
+
this.name = 'VersionNotSupportedError';
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const requested = optionsOrRequested as number | string;
|
|
109
|
+
const supportedLabel =
|
|
110
|
+
supported.length === 0 ? 'none' : supported.join(', ');
|
|
111
|
+
super(
|
|
112
|
+
`Trail "${messageOrTrailId}" version ${String(requested)} is not supported (supported: ${supportedLabel})`,
|
|
113
|
+
{
|
|
114
|
+
context: {
|
|
115
|
+
...(reason === undefined ? {} : { reason }),
|
|
116
|
+
requested,
|
|
117
|
+
supported,
|
|
118
|
+
trailId: messageOrTrailId,
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
this.name = 'VersionNotSupportedError';
|
|
123
|
+
this.reason = reason;
|
|
124
|
+
this.requested = requested;
|
|
125
|
+
this.supported = supported;
|
|
126
|
+
this.trailId = messageOrTrailId;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export class AlreadyExistsError extends TrailsError {
|
|
131
|
+
readonly category = 'conflict' as const;
|
|
132
|
+
readonly retryable = false as const;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export class ConflictError extends TrailsError {
|
|
136
|
+
readonly category = 'conflict' as const;
|
|
137
|
+
readonly retryable = false as const;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export class PermissionError extends TrailsError {
|
|
141
|
+
readonly category = 'permission' as const;
|
|
142
|
+
readonly retryable = false as const;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export class PermitError extends PermissionError {
|
|
146
|
+
constructor(
|
|
147
|
+
message: string,
|
|
148
|
+
options?: { cause?: Error; context?: Record<string, unknown> }
|
|
149
|
+
) {
|
|
150
|
+
super(message, options);
|
|
151
|
+
this.name = 'PermitError';
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export class TimeoutError extends TrailsError {
|
|
156
|
+
readonly category = 'timeout' as const;
|
|
157
|
+
readonly retryable = true as const;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export class RateLimitError extends TrailsError {
|
|
161
|
+
readonly category = 'rate_limit' as const;
|
|
162
|
+
readonly retryable = true as const;
|
|
163
|
+
readonly retryAfter?: number | undefined;
|
|
164
|
+
|
|
165
|
+
constructor(
|
|
166
|
+
message: string,
|
|
167
|
+
options?: {
|
|
168
|
+
cause?: Error;
|
|
169
|
+
context?: Record<string, unknown>;
|
|
170
|
+
retryAfter?: number;
|
|
171
|
+
}
|
|
172
|
+
) {
|
|
173
|
+
super(message, options);
|
|
174
|
+
this.retryAfter = options?.retryAfter;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export class NetworkError extends TrailsError {
|
|
179
|
+
readonly category = 'network' as const;
|
|
180
|
+
readonly retryable = true as const;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* return Result.err(new WorkspaceShiftError('workspace changed during check'));
|
|
187
|
+
* ```
|
|
188
|
+
*
|
|
189
|
+
* Raised when the observed workspace substrate moves during one run.
|
|
190
|
+
*
|
|
191
|
+
* A shift voids the run's verdict, including passes. Callers can retry on
|
|
192
|
+
* stable ground without changing their request.
|
|
193
|
+
*/
|
|
194
|
+
export class WorkspaceShiftError extends TrailsError {
|
|
195
|
+
readonly category = 'shift' as const;
|
|
196
|
+
readonly retryable = true as const;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export class InternalError extends TrailsError {
|
|
200
|
+
readonly category: ErrorCategory = 'internal';
|
|
201
|
+
readonly retryable = false as const;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export class DerivationError extends TrailsError {
|
|
205
|
+
readonly category = 'internal' as const;
|
|
206
|
+
readonly retryable = false as const;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export class RecoverableCompletionError extends InternalError {}
|
|
210
|
+
|
|
211
|
+
export class AuthError extends TrailsError {
|
|
212
|
+
readonly category = 'auth' as const;
|
|
213
|
+
readonly retryable = false as const;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export class CancelledError extends TrailsError {
|
|
217
|
+
readonly category = 'cancelled' as const;
|
|
218
|
+
readonly retryable = false as const;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Returned when a detour exhausts all recovery attempts.
|
|
223
|
+
*
|
|
224
|
+
* Inherits the wrapped error's category for surface mapping (e.g. a
|
|
225
|
+
* `RetryExhaustedError<ConflictError>` maps to HTTP 409), but always
|
|
226
|
+
* sets `retryable = false` to prevent amplification across `ctx.compose()`
|
|
227
|
+
* boundaries or stacked layers.
|
|
228
|
+
*/
|
|
229
|
+
export class RetryExhaustedError<
|
|
230
|
+
TErr extends TrailsError = TrailsError,
|
|
231
|
+
> extends InternalError {
|
|
232
|
+
readonly category: ErrorCategory;
|
|
233
|
+
readonly cause: TErr;
|
|
234
|
+
|
|
235
|
+
/** Number of recovery attempts made before exhaustion. */
|
|
236
|
+
readonly attempts: number;
|
|
237
|
+
|
|
238
|
+
/** Name of the detour whose recovery was exhausted. */
|
|
239
|
+
readonly detour: string;
|
|
240
|
+
|
|
241
|
+
constructor(
|
|
242
|
+
wrapped: TErr,
|
|
243
|
+
metadata: { readonly attempts: number; readonly detour: string }
|
|
244
|
+
) {
|
|
245
|
+
super(
|
|
246
|
+
`Recovery exhausted after ${metadata.attempts} attempts: ${wrapped.message}`,
|
|
247
|
+
{ cause: wrapped }
|
|
248
|
+
);
|
|
249
|
+
this.cause = wrapped;
|
|
250
|
+
this.attempts = metadata.attempts;
|
|
251
|
+
this.detour = metadata.detour;
|
|
252
|
+
// Dynamic — inherited from wrapped error at construction time.
|
|
253
|
+
this.category = wrapped.category;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// Class registry
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
export type ErrorClassConstructor = new (...args: never[]) => TrailsError;
|
|
262
|
+
|
|
263
|
+
export interface FixedErrorClassRegistryEntry {
|
|
264
|
+
readonly category: ErrorCategory;
|
|
265
|
+
readonly ctor: ErrorClassConstructor;
|
|
266
|
+
readonly name: string;
|
|
267
|
+
readonly retryable: boolean;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export interface DynamicErrorClassRegistryEntry {
|
|
271
|
+
readonly category: 'dynamic';
|
|
272
|
+
readonly ctor: ErrorClassConstructor;
|
|
273
|
+
readonly inheritsCategoryFrom: 'wrapped-error';
|
|
274
|
+
readonly name: string;
|
|
275
|
+
readonly retryable: false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export type ErrorClassRegistryEntry =
|
|
279
|
+
| DynamicErrorClassRegistryEntry
|
|
280
|
+
| FixedErrorClassRegistryEntry;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Authored registry of concrete TrailsError classes.
|
|
284
|
+
*
|
|
285
|
+
* JavaScript cannot enumerate subclasses at runtime, so rule and derivation
|
|
286
|
+
* tooling should walk this owner-held list instead of hardcoding parallel
|
|
287
|
+
* class-name tables. `RetryExhaustedError` is marked dynamic because it
|
|
288
|
+
* inherits its runtime category from the wrapped error rather than always
|
|
289
|
+
* mapping as `internal`.
|
|
290
|
+
*/
|
|
291
|
+
export const errorClasses = [
|
|
292
|
+
{
|
|
293
|
+
category: 'validation',
|
|
294
|
+
ctor: ValidationError,
|
|
295
|
+
name: 'ValidationError',
|
|
296
|
+
retryable: false,
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
category: 'validation',
|
|
300
|
+
ctor: AmbiguousError,
|
|
301
|
+
name: 'AmbiguousError',
|
|
302
|
+
retryable: false,
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
category: 'internal',
|
|
306
|
+
ctor: AssertionError,
|
|
307
|
+
name: 'AssertionError',
|
|
308
|
+
retryable: false,
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
category: 'not_found',
|
|
312
|
+
ctor: NotFoundError,
|
|
313
|
+
name: 'NotFoundError',
|
|
314
|
+
retryable: false,
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
category: 'not_found',
|
|
318
|
+
ctor: VersionNotSupportedError,
|
|
319
|
+
name: 'VersionNotSupportedError',
|
|
320
|
+
retryable: false,
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
category: 'conflict',
|
|
324
|
+
ctor: AlreadyExistsError,
|
|
325
|
+
name: 'AlreadyExistsError',
|
|
326
|
+
retryable: false,
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
category: 'conflict',
|
|
330
|
+
ctor: ConflictError,
|
|
331
|
+
name: 'ConflictError',
|
|
332
|
+
retryable: false,
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
category: 'permission',
|
|
336
|
+
ctor: PermissionError,
|
|
337
|
+
name: 'PermissionError',
|
|
338
|
+
retryable: false,
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
category: 'permission',
|
|
342
|
+
ctor: PermitError,
|
|
343
|
+
name: 'PermitError',
|
|
344
|
+
retryable: false,
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
category: 'timeout',
|
|
348
|
+
ctor: TimeoutError,
|
|
349
|
+
name: 'TimeoutError',
|
|
350
|
+
retryable: true,
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
category: 'rate_limit',
|
|
354
|
+
ctor: RateLimitError,
|
|
355
|
+
name: 'RateLimitError',
|
|
356
|
+
retryable: true,
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
category: 'network',
|
|
360
|
+
ctor: NetworkError,
|
|
361
|
+
name: 'NetworkError',
|
|
362
|
+
retryable: true,
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
category: 'shift',
|
|
366
|
+
ctor: WorkspaceShiftError,
|
|
367
|
+
name: 'WorkspaceShiftError',
|
|
368
|
+
retryable: true,
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
category: 'internal',
|
|
372
|
+
ctor: InternalError,
|
|
373
|
+
name: 'InternalError',
|
|
374
|
+
retryable: false,
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
category: 'internal',
|
|
378
|
+
ctor: DerivationError,
|
|
379
|
+
name: 'DerivationError',
|
|
380
|
+
retryable: false,
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
category: 'internal',
|
|
384
|
+
ctor: RecoverableCompletionError,
|
|
385
|
+
name: 'RecoverableCompletionError',
|
|
386
|
+
retryable: false,
|
|
387
|
+
},
|
|
388
|
+
{ category: 'auth', ctor: AuthError, name: 'AuthError', retryable: false },
|
|
389
|
+
{
|
|
390
|
+
category: 'cancelled',
|
|
391
|
+
ctor: CancelledError,
|
|
392
|
+
name: 'CancelledError',
|
|
393
|
+
retryable: false,
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
category: 'dynamic',
|
|
397
|
+
ctor: RetryExhaustedError,
|
|
398
|
+
inheritsCategoryFrom: 'wrapped-error',
|
|
399
|
+
name: 'RetryExhaustedError',
|
|
400
|
+
retryable: false,
|
|
401
|
+
},
|
|
402
|
+
] as const satisfies readonly ErrorClassRegistryEntry[];
|
|
403
|
+
|
|
404
|
+
// ---------------------------------------------------------------------------
|
|
405
|
+
// Taxonomy maps
|
|
406
|
+
// ---------------------------------------------------------------------------
|
|
407
|
+
|
|
408
|
+
export interface ErrorCategoryCodes {
|
|
409
|
+
readonly exit: number;
|
|
410
|
+
readonly http: number;
|
|
411
|
+
readonly jsonRpc: number;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export const codesByCategory = {
|
|
415
|
+
auth: { exit: 9, http: 401, jsonRpc: -32_600 },
|
|
416
|
+
cancelled: { exit: 130, http: 499, jsonRpc: -32_603 },
|
|
417
|
+
conflict: { exit: 3, http: 409, jsonRpc: -32_603 },
|
|
418
|
+
internal: { exit: 8, http: 500, jsonRpc: -32_603 },
|
|
419
|
+
network: { exit: 7, http: 502, jsonRpc: -32_603 },
|
|
420
|
+
not_found: { exit: 2, http: 404, jsonRpc: -32_601 },
|
|
421
|
+
permission: { exit: 4, http: 403, jsonRpc: -32_600 },
|
|
422
|
+
rate_limit: { exit: 6, http: 429, jsonRpc: -32_603 },
|
|
423
|
+
shift: { exit: 10, http: 503, jsonRpc: -32_603 },
|
|
424
|
+
timeout: { exit: 5, http: 504, jsonRpc: -32_603 },
|
|
425
|
+
validation: { exit: 1, http: 400, jsonRpc: -32_602 },
|
|
426
|
+
} as const satisfies Record<ErrorCategory, ErrorCategoryCodes>;
|
|
427
|
+
|
|
428
|
+
const deriveCodeMap = <TCode extends keyof ErrorCategoryCodes>(
|
|
429
|
+
code: TCode
|
|
430
|
+
): {
|
|
431
|
+
readonly [TCategory in ErrorCategory]: (typeof codesByCategory)[TCategory][TCode];
|
|
432
|
+
} => ({
|
|
433
|
+
auth: codesByCategory.auth[code],
|
|
434
|
+
cancelled: codesByCategory.cancelled[code],
|
|
435
|
+
conflict: codesByCategory.conflict[code],
|
|
436
|
+
internal: codesByCategory.internal[code],
|
|
437
|
+
network: codesByCategory.network[code],
|
|
438
|
+
not_found: codesByCategory.not_found[code],
|
|
439
|
+
permission: codesByCategory.permission[code],
|
|
440
|
+
rate_limit: codesByCategory.rate_limit[code],
|
|
441
|
+
shift: codesByCategory.shift[code],
|
|
442
|
+
timeout: codesByCategory.timeout[code],
|
|
443
|
+
validation: codesByCategory.validation[code],
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
/** @deprecated Prefer `codesByCategory[category].exit`. */
|
|
447
|
+
export const exitCodeMap = deriveCodeMap('exit');
|
|
448
|
+
|
|
449
|
+
/** @deprecated Prefer `codesByCategory[category].http`. */
|
|
450
|
+
export const statusCodeMap = deriveCodeMap('http');
|
|
451
|
+
|
|
452
|
+
/** @deprecated Prefer `codesByCategory[category].jsonRpc`. */
|
|
453
|
+
export const jsonRpcCodeMap = deriveCodeMap('jsonRpc');
|
|
454
|
+
|
|
455
|
+
export const retryableMap: Record<ErrorCategory, boolean> = {
|
|
456
|
+
auth: false,
|
|
457
|
+
cancelled: false,
|
|
458
|
+
conflict: false,
|
|
459
|
+
internal: false,
|
|
460
|
+
network: true,
|
|
461
|
+
not_found: false,
|
|
462
|
+
permission: false,
|
|
463
|
+
rate_limit: true,
|
|
464
|
+
shift: true,
|
|
465
|
+
timeout: true,
|
|
466
|
+
validation: false,
|
|
467
|
+
} as const;
|
|
468
|
+
|
|
469
|
+
// ---------------------------------------------------------------------------
|
|
470
|
+
// Helper functions
|
|
471
|
+
// ---------------------------------------------------------------------------
|
|
472
|
+
|
|
473
|
+
/** Type guard: narrows unknown to TrailsError */
|
|
474
|
+
export const isTrailsError = (error?: unknown): error is TrailsError =>
|
|
475
|
+
error instanceof TrailsError;
|
|
476
|
+
|
|
477
|
+
/** Returns true if the error is retryable (TrailsError with retryable category). */
|
|
478
|
+
export const isRetryable = (error: Error): boolean => {
|
|
479
|
+
if (isTrailsError(error)) {
|
|
480
|
+
return error.retryable;
|
|
481
|
+
}
|
|
482
|
+
return false;
|
|
483
|
+
};
|