@pellux/goodvibes-errors 0.33.37 → 0.34.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/dist/index.d.ts +169 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +226 -13
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -24,8 +24,106 @@ export type ErrorSource = DaemonErrorSource | 'contract';
|
|
|
24
24
|
* }
|
|
25
25
|
*/
|
|
26
26
|
export type SDKErrorKind = 'auth' | 'config' | 'contract' | 'network' | 'not-found' | 'protocol' | 'rate-limit' | 'service' | 'internal' | 'tool' | 'validation' | 'unknown';
|
|
27
|
+
/**
|
|
28
|
+
* Exhaustive string-literal union of the canonical error codes produced by the
|
|
29
|
+
* GoodVibes SDK. Use this type when you need to pattern-match on `err.code`
|
|
30
|
+
* without losing exhaustiveness checking.
|
|
31
|
+
*
|
|
32
|
+
* The `code` field on {@link GoodVibesSdkError} is typed as
|
|
33
|
+
* `SDKErrorCode | (string & {})` so that:
|
|
34
|
+
* - SDK-produced errors surface as one of the known literals (IDE autocomplete
|
|
35
|
+
* and exhaustive switches work).
|
|
36
|
+
* - Caller-supplied arbitrary string codes still type-check without casting.
|
|
37
|
+
*
|
|
38
|
+
* ### Consumer pattern
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { isErrorCode, SDKErrorCodes } from '@pellux/goodvibes-errors';
|
|
41
|
+
*
|
|
42
|
+
* catch (err) {
|
|
43
|
+
* if (err instanceof GoodVibesSdkError) {
|
|
44
|
+
* if (isErrorCode(err, SDKErrorCodes.RATE_LIMITED)) {
|
|
45
|
+
* await delay(err.retryAfterMs ?? 1000);
|
|
46
|
+
* } else if (isErrorCode(err, SDKErrorCodes.AUTH_REQUIRED)) {
|
|
47
|
+
* await refreshToken();
|
|
48
|
+
* } else if (isErrorCode(err, SDKErrorCodes.TOKEN_EXPIRED)) {
|
|
49
|
+
* await refreshToken();
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export type SDKErrorCode = 'AUTH_REQUIRED' | 'TOKEN_EXPIRED' | 'PERMISSION_DENIED' | 'PAYMENT_REQUIRED' | 'RATE_LIMITED' | 'NETWORK_UNREACHABLE' | 'TIMEOUT' | 'CANCELLED' | 'NOT_FOUND' | 'CONFLICT' | 'VALIDATION_FAILED' | 'AGENT_TIMEOUT' | 'AGENT_FAILED' | 'TOOL_EXEC_FAILED' | 'SERVICE_UNAVAILABLE' | 'CONTRACT_MISMATCH' | 'PROTOCOL_ERROR' | 'INTERNAL_ERROR' | 'SDK_CONFIGURATION_ERROR' | 'SDK_CONTRACT_ERROR' | 'SDK_HTTP_STATUS_ERROR' | 'UNKNOWN';
|
|
56
|
+
/**
|
|
57
|
+
* Runtime-accessible const object mirroring the {@link SDKErrorCode} union.
|
|
58
|
+
* Prefer referencing these constants over raw string literals for refactor safety.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* import { SDKErrorCodes } from '@pellux/goodvibes-errors';
|
|
62
|
+
*
|
|
63
|
+
* if (err.code === SDKErrorCodes.RATE_LIMITED) {
|
|
64
|
+
* await delay(err.retryAfterMs ?? 1000);
|
|
65
|
+
* }
|
|
66
|
+
*/
|
|
67
|
+
export declare const SDKErrorCodes: {
|
|
68
|
+
readonly AUTH_REQUIRED: "AUTH_REQUIRED";
|
|
69
|
+
readonly TOKEN_EXPIRED: "TOKEN_EXPIRED";
|
|
70
|
+
readonly PERMISSION_DENIED: "PERMISSION_DENIED";
|
|
71
|
+
readonly PAYMENT_REQUIRED: "PAYMENT_REQUIRED";
|
|
72
|
+
readonly RATE_LIMITED: "RATE_LIMITED";
|
|
73
|
+
readonly NETWORK_UNREACHABLE: "NETWORK_UNREACHABLE";
|
|
74
|
+
readonly TIMEOUT: "TIMEOUT";
|
|
75
|
+
readonly CANCELLED: "CANCELLED";
|
|
76
|
+
readonly NOT_FOUND: "NOT_FOUND";
|
|
77
|
+
readonly CONFLICT: "CONFLICT";
|
|
78
|
+
readonly VALIDATION_FAILED: "VALIDATION_FAILED";
|
|
79
|
+
readonly AGENT_TIMEOUT: "AGENT_TIMEOUT";
|
|
80
|
+
readonly AGENT_FAILED: "AGENT_FAILED";
|
|
81
|
+
readonly TOOL_EXEC_FAILED: "TOOL_EXEC_FAILED";
|
|
82
|
+
readonly SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE";
|
|
83
|
+
readonly CONTRACT_MISMATCH: "CONTRACT_MISMATCH";
|
|
84
|
+
readonly PROTOCOL_ERROR: "PROTOCOL_ERROR";
|
|
85
|
+
readonly INTERNAL_ERROR: "INTERNAL_ERROR";
|
|
86
|
+
readonly SDK_CONFIGURATION_ERROR: "SDK_CONFIGURATION_ERROR";
|
|
87
|
+
readonly SDK_CONTRACT_ERROR: "SDK_CONTRACT_ERROR";
|
|
88
|
+
readonly SDK_HTTP_STATUS_ERROR: "SDK_HTTP_STATUS_ERROR";
|
|
89
|
+
readonly UNKNOWN: "UNKNOWN";
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Returns `true` when `err.code` equals the given {@link SDKErrorCode},
|
|
93
|
+
* narrowing the type of `err.code` to the specific literal.
|
|
94
|
+
*
|
|
95
|
+
* Works with any object that has a `code?: string` field — not limited to
|
|
96
|
+
* {@link GoodVibesSdkError} subclasses.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* import { isErrorCode, SDKErrorCodes, GoodVibesSdkError } from '@pellux/goodvibes-errors';
|
|
100
|
+
*
|
|
101
|
+
* if (err instanceof GoodVibesSdkError && isErrorCode(err, SDKErrorCodes.RATE_LIMITED)) {
|
|
102
|
+
* console.log('retry after', err.retryAfterMs);
|
|
103
|
+
* }
|
|
104
|
+
*
|
|
105
|
+
* @param err - Any object with an optional `code` string field.
|
|
106
|
+
* @param code - The {@link SDKErrorCode} literal to match against.
|
|
107
|
+
*/
|
|
108
|
+
export declare function isErrorCode<C extends SDKErrorCode>(err: {
|
|
109
|
+
readonly code?: SDKErrorCode | (string & {}) | undefined;
|
|
110
|
+
}, code: C): err is {
|
|
111
|
+
readonly code: C;
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
* Returns `true` when `value` is a known {@link SDKErrorCode} string.
|
|
115
|
+
* Useful for discriminating structured errors received over the wire.
|
|
116
|
+
*
|
|
117
|
+
* @param value - The string to test.
|
|
118
|
+
*/
|
|
119
|
+
export declare function isKnownErrorCode(value: string): value is SDKErrorCode;
|
|
27
120
|
export interface GoodVibesSdkErrorOptions {
|
|
28
|
-
|
|
121
|
+
/**
|
|
122
|
+
* A typed error code for programmatic matching. May be an {@link SDKErrorCode}
|
|
123
|
+
* literal or any custom string for caller-supplied codes.
|
|
124
|
+
* When omitted, the SDK infers a code from `category` or `status`.
|
|
125
|
+
*/
|
|
126
|
+
readonly code?: SDKErrorCode | (string & {}) | undefined;
|
|
29
127
|
readonly category?: ErrorCategory | undefined;
|
|
30
128
|
readonly source?: ErrorSource | undefined;
|
|
31
129
|
readonly recoverable?: boolean | undefined;
|
|
@@ -47,10 +145,29 @@ export declare const RETRYABLE_STATUS_CODES: readonly number[];
|
|
|
47
145
|
/**
|
|
48
146
|
* Base error class for all errors thrown by the GoodVibes SDK.
|
|
49
147
|
*
|
|
50
|
-
* Every error carries a structured `category` and `
|
|
148
|
+
* Every error carries a structured `category`, `source`, and `code` that allow
|
|
51
149
|
* callers to handle specific failure modes without string-matching messages.
|
|
52
150
|
*
|
|
53
|
-
*
|
|
151
|
+
* The `code` field is typed as `SDKErrorCode | (string & {})` — SDK-produced
|
|
152
|
+
* errors always carry a known {@link SDKErrorCode}, while caller-supplied codes
|
|
153
|
+
* remain valid arbitrary strings.
|
|
154
|
+
*
|
|
155
|
+
* ### Narrowing by code
|
|
156
|
+
* ```ts
|
|
157
|
+
* import { GoodVibesSdkError, isErrorCode, SDKErrorCodes } from '@pellux/goodvibes-errors';
|
|
158
|
+
*
|
|
159
|
+
* catch (err) {
|
|
160
|
+
* if (err instanceof GoodVibesSdkError) {
|
|
161
|
+
* if (isErrorCode(err, SDKErrorCodes.RATE_LIMITED)) {
|
|
162
|
+
* await delay(err.retryAfterMs ?? 1000);
|
|
163
|
+
* } else if (isErrorCode(err, SDKErrorCodes.TOKEN_EXPIRED)) {
|
|
164
|
+
* await refreshToken();
|
|
165
|
+
* }
|
|
166
|
+
* }
|
|
167
|
+
* }
|
|
168
|
+
* ```
|
|
169
|
+
*
|
|
170
|
+
* ### Narrowing by kind
|
|
54
171
|
* ```ts
|
|
55
172
|
* import { GoodVibesSdkError, HttpStatusError, ConfigurationError } from '@pellux/goodvibes-errors';
|
|
56
173
|
*
|
|
@@ -69,7 +186,18 @@ export declare const RETRYABLE_STATUS_CODES: readonly number[];
|
|
|
69
186
|
*/
|
|
70
187
|
export declare class GoodVibesSdkError extends Error {
|
|
71
188
|
readonly kind: SDKErrorKind;
|
|
72
|
-
|
|
189
|
+
/**
|
|
190
|
+
* Typed error code for programmatic matching. SDK-produced errors always set
|
|
191
|
+
* a {@link SDKErrorCode}; caller-supplied codes may be any string.
|
|
192
|
+
*
|
|
193
|
+
* **Note:** `code` and `category` are inferred independently and can diverge.
|
|
194
|
+
* For example, `new GoodVibesSdkError('…', { status: 409 })` yields
|
|
195
|
+
* `code === 'CONFLICT'` (from `inferCodeFromStatus`) while
|
|
196
|
+
* `category === 'unknown'` (because `inferCategory` intentionally returns
|
|
197
|
+
* `'unknown'` for 409 — the caller must supply `category` explicitly to get
|
|
198
|
+
* a meaningful category for conflict-style errors).
|
|
199
|
+
*/
|
|
200
|
+
readonly code: SDKErrorCode | (string & {});
|
|
73
201
|
readonly category: ErrorCategory;
|
|
74
202
|
readonly source: ErrorSource;
|
|
75
203
|
readonly recoverable: boolean;
|
|
@@ -95,7 +223,7 @@ export declare class GoodVibesSdkError extends Error {
|
|
|
95
223
|
* implementation available, or calling a mutation on a read-only auth resolver).
|
|
96
224
|
*
|
|
97
225
|
* Always non-recoverable (`recoverable: false`).
|
|
98
|
-
* Category: `'config'`. Kind: `'config'`.
|
|
226
|
+
* Category: `'config'`. Kind: `'config'`. Code: `'SDK_CONFIGURATION_ERROR'`.
|
|
99
227
|
*
|
|
100
228
|
* @example
|
|
101
229
|
* import { ConfigurationError } from '@pellux/goodvibes-errors';
|
|
@@ -124,7 +252,7 @@ export declare class ConfigurationError extends GoodVibesSdkError {
|
|
|
124
252
|
* (unexpected shape, missing required fields, etc.).
|
|
125
253
|
*
|
|
126
254
|
* Always non-recoverable (`recoverable: false`).
|
|
127
|
-
* Category: `'contract'`. Kind: `'contract'`.
|
|
255
|
+
* Category: `'contract'`. Kind: `'contract'`. Code: `'SDK_CONTRACT_ERROR'`.
|
|
128
256
|
*
|
|
129
257
|
* @example
|
|
130
258
|
* import { ContractError } from '@pellux/goodvibes-errors';
|
|
@@ -158,6 +286,17 @@ export declare class ContractError extends GoodVibesSdkError {
|
|
|
158
286
|
* - `5xx` → `'service'`
|
|
159
287
|
* - Any other status (or when constructed without a `status`) → `'unknown'`
|
|
160
288
|
*
|
|
289
|
+
* The `code` field is inferred from `status` automatically:
|
|
290
|
+
* - `400` → `'VALIDATION_FAILED'`
|
|
291
|
+
* - `401` → `'AUTH_REQUIRED'`
|
|
292
|
+
* - `402` → `'PAYMENT_REQUIRED'`
|
|
293
|
+
* - `403` → `'PERMISSION_DENIED'`
|
|
294
|
+
* - `404` → `'NOT_FOUND'`
|
|
295
|
+
* - `408` → `'TIMEOUT'`
|
|
296
|
+
* - `409` → `'CONFLICT'`
|
|
297
|
+
* - `429` → `'RATE_LIMITED'`
|
|
298
|
+
* - `5xx` → `'SERVICE_UNAVAILABLE'`
|
|
299
|
+
*
|
|
161
300
|
* When constructed without a `status` argument (e.g. as a typed
|
|
162
301
|
* wrapper around a structured daemon error that provides its own `category`),
|
|
163
302
|
* the category defaults to `'unknown'`. Callers relying on category-based
|
|
@@ -185,15 +324,36 @@ export declare class ContractError extends GoodVibesSdkError {
|
|
|
185
324
|
*/
|
|
186
325
|
export declare class HttpStatusError extends GoodVibesSdkError {
|
|
187
326
|
/**
|
|
188
|
-
* Brand contract
|
|
327
|
+
* Brand contract: `instanceof HttpStatusError` relies on a dedicated Symbol
|
|
328
|
+
* brand stamped in the constructor, enabling cross-realm identity checks that
|
|
329
|
+
* are independent of the `code` field.
|
|
330
|
+
*
|
|
189
331
|
* A `GoodVibesSdkError` constructed directly with `code: 'SDK_HTTP_STATUS_ERROR'`
|
|
190
|
-
* will pass `instanceof HttpStatusError`
|
|
191
|
-
*
|
|
332
|
+
* will also pass `instanceof HttpStatusError` for backward compatibility with
|
|
333
|
+
* callers that serialise/deserialise errors by code. Callers that need strict
|
|
334
|
+
* prototype checking should use
|
|
192
335
|
* `Object.getPrototypeOf(err) === HttpStatusError.prototype` instead.
|
|
193
336
|
*/
|
|
194
337
|
static [Symbol.hasInstance](value: unknown): boolean;
|
|
195
338
|
constructor(message: string, options?: GoodVibesSdkErrorOptions);
|
|
196
339
|
}
|
|
197
340
|
export declare function isStructuredDaemonErrorBody(value: unknown): value is StructuredDaemonErrorBody;
|
|
341
|
+
/**
|
|
342
|
+
* Creates an {@link HttpStatusError} from an HTTP response.
|
|
343
|
+
*
|
|
344
|
+
* When `body` is a {@link StructuredDaemonErrorBody}, its fields are used
|
|
345
|
+
* directly (including any explicit `code`). When the body is unstructured,
|
|
346
|
+
* the `code` is inferred from `status` via status-based inference (`inferCodeFromStatus`).
|
|
347
|
+
*
|
|
348
|
+
* The structured body path respects the body-supplied `code` over status
|
|
349
|
+
* inference, preserving full backward compatibility for callers that supply
|
|
350
|
+
* custom codes in the daemon response.
|
|
351
|
+
*
|
|
352
|
+
* @param status - HTTP status code.
|
|
353
|
+
* @param url - Request URL.
|
|
354
|
+
* @param method - HTTP method.
|
|
355
|
+
* @param body - Parsed response body (may be structured or unstructured).
|
|
356
|
+
* @param fallbackHint - Human-readable hint when the body provides none.
|
|
357
|
+
*/
|
|
198
358
|
export declare function createHttpStatusError(status: number, url: string, method: string, body: unknown, fallbackHint?: string): HttpStatusError;
|
|
199
359
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,iBAAiB,EACjB,yBAAyB,EAC1B,MAAM,4BAA4B,CAAC;AAEpC,YAAY,EACV,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEjE;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,mBAAmB,GAAG,UAAU,CAAC;AAE7D,MAAM,MAAM,WAAW,GAAG,iBAAiB,GAAG,UAAU,CAAC;AAEzD;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,QAAQ,GACR,UAAU,GACV,SAAS,GACT,WAAW,GACX,UAAU,GACV,YAAY,GACZ,SAAS,GACT,UAAU,GACV,MAAM,GACN,YAAY,GACZ,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,73 @@
|
|
|
1
1
|
export { DaemonErrorCategory } from './daemon-error-contract.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runtime-accessible const object mirroring the {@link SDKErrorCode} union.
|
|
4
|
+
* Prefer referencing these constants over raw string literals for refactor safety.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* import { SDKErrorCodes } from '@pellux/goodvibes-errors';
|
|
8
|
+
*
|
|
9
|
+
* if (err.code === SDKErrorCodes.RATE_LIMITED) {
|
|
10
|
+
* await delay(err.retryAfterMs ?? 1000);
|
|
11
|
+
* }
|
|
12
|
+
*/
|
|
13
|
+
export const SDKErrorCodes = {
|
|
14
|
+
AUTH_REQUIRED: 'AUTH_REQUIRED',
|
|
15
|
+
TOKEN_EXPIRED: 'TOKEN_EXPIRED',
|
|
16
|
+
PERMISSION_DENIED: 'PERMISSION_DENIED',
|
|
17
|
+
PAYMENT_REQUIRED: 'PAYMENT_REQUIRED',
|
|
18
|
+
RATE_LIMITED: 'RATE_LIMITED',
|
|
19
|
+
NETWORK_UNREACHABLE: 'NETWORK_UNREACHABLE',
|
|
20
|
+
TIMEOUT: 'TIMEOUT',
|
|
21
|
+
CANCELLED: 'CANCELLED',
|
|
22
|
+
NOT_FOUND: 'NOT_FOUND',
|
|
23
|
+
CONFLICT: 'CONFLICT',
|
|
24
|
+
VALIDATION_FAILED: 'VALIDATION_FAILED',
|
|
25
|
+
AGENT_TIMEOUT: 'AGENT_TIMEOUT',
|
|
26
|
+
AGENT_FAILED: 'AGENT_FAILED',
|
|
27
|
+
TOOL_EXEC_FAILED: 'TOOL_EXEC_FAILED',
|
|
28
|
+
SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',
|
|
29
|
+
CONTRACT_MISMATCH: 'CONTRACT_MISMATCH',
|
|
30
|
+
PROTOCOL_ERROR: 'PROTOCOL_ERROR',
|
|
31
|
+
INTERNAL_ERROR: 'INTERNAL_ERROR',
|
|
32
|
+
SDK_CONFIGURATION_ERROR: 'SDK_CONFIGURATION_ERROR',
|
|
33
|
+
SDK_CONTRACT_ERROR: 'SDK_CONTRACT_ERROR',
|
|
34
|
+
SDK_HTTP_STATUS_ERROR: 'SDK_HTTP_STATUS_ERROR',
|
|
35
|
+
UNKNOWN: 'UNKNOWN',
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The set of known {@link SDKErrorCode} values for runtime membership tests.
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
const SDK_ERROR_CODE_SET = new Set(Object.values(SDKErrorCodes));
|
|
42
|
+
/**
|
|
43
|
+
* Returns `true` when `err.code` equals the given {@link SDKErrorCode},
|
|
44
|
+
* narrowing the type of `err.code` to the specific literal.
|
|
45
|
+
*
|
|
46
|
+
* Works with any object that has a `code?: string` field — not limited to
|
|
47
|
+
* {@link GoodVibesSdkError} subclasses.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* import { isErrorCode, SDKErrorCodes, GoodVibesSdkError } from '@pellux/goodvibes-errors';
|
|
51
|
+
*
|
|
52
|
+
* if (err instanceof GoodVibesSdkError && isErrorCode(err, SDKErrorCodes.RATE_LIMITED)) {
|
|
53
|
+
* console.log('retry after', err.retryAfterMs);
|
|
54
|
+
* }
|
|
55
|
+
*
|
|
56
|
+
* @param err - Any object with an optional `code` string field.
|
|
57
|
+
* @param code - The {@link SDKErrorCode} literal to match against.
|
|
58
|
+
*/
|
|
59
|
+
export function isErrorCode(err, code) {
|
|
60
|
+
return err.code === code;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Returns `true` when `value` is a known {@link SDKErrorCode} string.
|
|
64
|
+
* Useful for discriminating structured errors received over the wire.
|
|
65
|
+
*
|
|
66
|
+
* @param value - The string to test.
|
|
67
|
+
*/
|
|
68
|
+
export function isKnownErrorCode(value) {
|
|
69
|
+
return SDK_ERROR_CODE_SET.has(value);
|
|
70
|
+
}
|
|
2
71
|
function inferKind(category) {
|
|
3
72
|
switch (category) {
|
|
4
73
|
case 'authentication':
|
|
@@ -32,6 +101,33 @@ function inferKind(category) {
|
|
|
32
101
|
return 'unknown';
|
|
33
102
|
}
|
|
34
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Infers the canonical {@link SDKErrorCode} from an {@link ErrorCategory}.
|
|
106
|
+
* Used internally to populate `code` when no explicit code is provided.
|
|
107
|
+
* @internal
|
|
108
|
+
*/
|
|
109
|
+
function inferCodeFromCategory(category) {
|
|
110
|
+
switch (category) {
|
|
111
|
+
case 'authentication': return 'AUTH_REQUIRED';
|
|
112
|
+
case 'authorization': return 'PERMISSION_DENIED';
|
|
113
|
+
case 'billing': return 'PAYMENT_REQUIRED';
|
|
114
|
+
case 'permission': return 'PERMISSION_DENIED';
|
|
115
|
+
case 'config': return 'SDK_CONFIGURATION_ERROR';
|
|
116
|
+
case 'contract': return 'CONTRACT_MISMATCH';
|
|
117
|
+
case 'network': return 'NETWORK_UNREACHABLE';
|
|
118
|
+
case 'timeout': return 'TIMEOUT';
|
|
119
|
+
case 'not_found': return 'NOT_FOUND';
|
|
120
|
+
case 'rate_limit': return 'RATE_LIMITED';
|
|
121
|
+
case 'protocol': return 'PROTOCOL_ERROR';
|
|
122
|
+
case 'internal': return 'INTERNAL_ERROR';
|
|
123
|
+
case 'service': return 'SERVICE_UNAVAILABLE';
|
|
124
|
+
case 'bad_request': return 'VALIDATION_FAILED';
|
|
125
|
+
case 'tool': return 'TOOL_EXEC_FAILED';
|
|
126
|
+
case 'unknown':
|
|
127
|
+
default:
|
|
128
|
+
return 'UNKNOWN';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
35
131
|
export const RETRYABLE_STATUS_CODES = [408, 429, 500, 502, 503, 504];
|
|
36
132
|
function inferCategory(status) {
|
|
37
133
|
if (status === 400)
|
|
@@ -46,12 +142,37 @@ function inferCategory(status) {
|
|
|
46
142
|
return 'not_found';
|
|
47
143
|
if (status === 408)
|
|
48
144
|
return 'timeout';
|
|
145
|
+
if (status === 409)
|
|
146
|
+
return 'unknown'; // 409 Conflict — caller must supply category explicitly
|
|
49
147
|
if (status === 429)
|
|
50
148
|
return 'rate_limit';
|
|
51
149
|
if (status !== undefined && status >= 500)
|
|
52
150
|
return 'service';
|
|
53
151
|
return 'unknown';
|
|
54
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Infers the canonical {@link SDKErrorCode} for HTTP status codes that have a
|
|
155
|
+
* direct 1-to-1 mapping, used when no explicit code is present in the response
|
|
156
|
+
* body. Returns `undefined` when the code should fall through to category-based
|
|
157
|
+
* inference (e.g. for structured-body responses that supply their own category).
|
|
158
|
+
* @internal
|
|
159
|
+
*/
|
|
160
|
+
function inferCodeFromStatus(status) {
|
|
161
|
+
switch (status) {
|
|
162
|
+
case 400: return 'VALIDATION_FAILED';
|
|
163
|
+
case 401: return 'AUTH_REQUIRED';
|
|
164
|
+
case 402: return 'PAYMENT_REQUIRED';
|
|
165
|
+
case 403: return 'PERMISSION_DENIED';
|
|
166
|
+
case 404: return 'NOT_FOUND';
|
|
167
|
+
case 408: return 'TIMEOUT';
|
|
168
|
+
case 409: return 'CONFLICT';
|
|
169
|
+
case 429: return 'RATE_LIMITED';
|
|
170
|
+
default:
|
|
171
|
+
if (status >= 500)
|
|
172
|
+
return 'SERVICE_UNAVAILABLE';
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
55
176
|
const ERROR_CATEGORIES = new Set([
|
|
56
177
|
'authentication',
|
|
57
178
|
'authorization',
|
|
@@ -71,6 +192,7 @@ const ERROR_CATEGORIES = new Set([
|
|
|
71
192
|
'unknown',
|
|
72
193
|
]);
|
|
73
194
|
const GOODVIBES_SDK_ERROR_BRAND = Symbol.for('pellux.goodvibes.sdk.error');
|
|
195
|
+
const HTTP_STATUS_ERROR_BRAND = Symbol.for('pellux.goodvibes.sdk.http-status-error');
|
|
74
196
|
function readErrorCategory(value) {
|
|
75
197
|
return typeof value === 'string' && ERROR_CATEGORIES.has(value)
|
|
76
198
|
? value
|
|
@@ -97,10 +219,29 @@ function inferCategoryFromCause(cause, seen = new Set(), depth = 0) {
|
|
|
97
219
|
/**
|
|
98
220
|
* Base error class for all errors thrown by the GoodVibes SDK.
|
|
99
221
|
*
|
|
100
|
-
* Every error carries a structured `category` and `
|
|
222
|
+
* Every error carries a structured `category`, `source`, and `code` that allow
|
|
101
223
|
* callers to handle specific failure modes without string-matching messages.
|
|
102
224
|
*
|
|
103
|
-
*
|
|
225
|
+
* The `code` field is typed as `SDKErrorCode | (string & {})` — SDK-produced
|
|
226
|
+
* errors always carry a known {@link SDKErrorCode}, while caller-supplied codes
|
|
227
|
+
* remain valid arbitrary strings.
|
|
228
|
+
*
|
|
229
|
+
* ### Narrowing by code
|
|
230
|
+
* ```ts
|
|
231
|
+
* import { GoodVibesSdkError, isErrorCode, SDKErrorCodes } from '@pellux/goodvibes-errors';
|
|
232
|
+
*
|
|
233
|
+
* catch (err) {
|
|
234
|
+
* if (err instanceof GoodVibesSdkError) {
|
|
235
|
+
* if (isErrorCode(err, SDKErrorCodes.RATE_LIMITED)) {
|
|
236
|
+
* await delay(err.retryAfterMs ?? 1000);
|
|
237
|
+
* } else if (isErrorCode(err, SDKErrorCodes.TOKEN_EXPIRED)) {
|
|
238
|
+
* await refreshToken();
|
|
239
|
+
* }
|
|
240
|
+
* }
|
|
241
|
+
* }
|
|
242
|
+
* ```
|
|
243
|
+
*
|
|
244
|
+
* ### Narrowing by kind
|
|
104
245
|
* ```ts
|
|
105
246
|
* import { GoodVibesSdkError, HttpStatusError, ConfigurationError } from '@pellux/goodvibes-errors';
|
|
106
247
|
*
|
|
@@ -119,6 +260,17 @@ function inferCategoryFromCause(cause, seen = new Set(), depth = 0) {
|
|
|
119
260
|
*/
|
|
120
261
|
export class GoodVibesSdkError extends Error {
|
|
121
262
|
kind;
|
|
263
|
+
/**
|
|
264
|
+
* Typed error code for programmatic matching. SDK-produced errors always set
|
|
265
|
+
* a {@link SDKErrorCode}; caller-supplied codes may be any string.
|
|
266
|
+
*
|
|
267
|
+
* **Note:** `code` and `category` are inferred independently and can diverge.
|
|
268
|
+
* For example, `new GoodVibesSdkError('…', { status: 409 })` yields
|
|
269
|
+
* `code === 'CONFLICT'` (from `inferCodeFromStatus`) while
|
|
270
|
+
* `category === 'unknown'` (because `inferCategory` intentionally returns
|
|
271
|
+
* `'unknown'` for 409 — the caller must supply `category` explicitly to get
|
|
272
|
+
* a meaningful category for conflict-style errors).
|
|
273
|
+
*/
|
|
122
274
|
code;
|
|
123
275
|
category;
|
|
124
276
|
source;
|
|
@@ -158,7 +310,11 @@ export class GoodVibesSdkError extends Error {
|
|
|
158
310
|
enumerable: false,
|
|
159
311
|
configurable: false,
|
|
160
312
|
});
|
|
161
|
-
|
|
313
|
+
// Infer code from status first (most specific), then from category.
|
|
314
|
+
// Explicit caller-supplied code always wins.
|
|
315
|
+
this.code = options.code
|
|
316
|
+
?? (options.status !== undefined ? inferCodeFromStatus(options.status) : undefined)
|
|
317
|
+
?? inferCodeFromCategory(category);
|
|
162
318
|
this.category = category;
|
|
163
319
|
this.kind = inferKind(this.category);
|
|
164
320
|
this.source = options.source ?? 'unknown';
|
|
@@ -238,7 +394,7 @@ function omitUndefined(record) {
|
|
|
238
394
|
* implementation available, or calling a mutation on a read-only auth resolver).
|
|
239
395
|
*
|
|
240
396
|
* Always non-recoverable (`recoverable: false`).
|
|
241
|
-
* Category: `'config'`. Kind: `'config'`.
|
|
397
|
+
* Category: `'config'`. Kind: `'config'`. Code: `'SDK_CONFIGURATION_ERROR'`.
|
|
242
398
|
*
|
|
243
399
|
* @example
|
|
244
400
|
* import { ConfigurationError } from '@pellux/goodvibes-errors';
|
|
@@ -285,7 +441,7 @@ export class ConfigurationError extends GoodVibesSdkError {
|
|
|
285
441
|
* (unexpected shape, missing required fields, etc.).
|
|
286
442
|
*
|
|
287
443
|
* Always non-recoverable (`recoverable: false`).
|
|
288
|
-
* Category: `'contract'`. Kind: `'contract'`.
|
|
444
|
+
* Category: `'contract'`. Kind: `'contract'`. Code: `'SDK_CONTRACT_ERROR'`.
|
|
289
445
|
*
|
|
290
446
|
* @example
|
|
291
447
|
* import { ContractError } from '@pellux/goodvibes-errors';
|
|
@@ -337,6 +493,17 @@ export class ContractError extends GoodVibesSdkError {
|
|
|
337
493
|
* - `5xx` → `'service'`
|
|
338
494
|
* - Any other status (or when constructed without a `status`) → `'unknown'`
|
|
339
495
|
*
|
|
496
|
+
* The `code` field is inferred from `status` automatically:
|
|
497
|
+
* - `400` → `'VALIDATION_FAILED'`
|
|
498
|
+
* - `401` → `'AUTH_REQUIRED'`
|
|
499
|
+
* - `402` → `'PAYMENT_REQUIRED'`
|
|
500
|
+
* - `403` → `'PERMISSION_DENIED'`
|
|
501
|
+
* - `404` → `'NOT_FOUND'`
|
|
502
|
+
* - `408` → `'TIMEOUT'`
|
|
503
|
+
* - `409` → `'CONFLICT'`
|
|
504
|
+
* - `429` → `'RATE_LIMITED'`
|
|
505
|
+
* - `5xx` → `'SERVICE_UNAVAILABLE'`
|
|
506
|
+
*
|
|
340
507
|
* When constructed without a `status` argument (e.g. as a typed
|
|
341
508
|
* wrapper around a structured daemon error that provides its own `category`),
|
|
342
509
|
* the category defaults to `'unknown'`. Callers relying on category-based
|
|
@@ -364,10 +531,14 @@ export class ContractError extends GoodVibesSdkError {
|
|
|
364
531
|
*/
|
|
365
532
|
export class HttpStatusError extends GoodVibesSdkError {
|
|
366
533
|
/**
|
|
367
|
-
* Brand contract
|
|
534
|
+
* Brand contract: `instanceof HttpStatusError` relies on a dedicated Symbol
|
|
535
|
+
* brand stamped in the constructor, enabling cross-realm identity checks that
|
|
536
|
+
* are independent of the `code` field.
|
|
537
|
+
*
|
|
368
538
|
* A `GoodVibesSdkError` constructed directly with `code: 'SDK_HTTP_STATUS_ERROR'`
|
|
369
|
-
* will pass `instanceof HttpStatusError`
|
|
370
|
-
*
|
|
539
|
+
* will also pass `instanceof HttpStatusError` for backward compatibility with
|
|
540
|
+
* callers that serialise/deserialise errors by code. Callers that need strict
|
|
541
|
+
* prototype checking should use
|
|
371
542
|
* `Object.getPrototypeOf(err) === HttpStatusError.prototype` instead.
|
|
372
543
|
*/
|
|
373
544
|
static [Symbol.hasInstance](value) {
|
|
@@ -376,26 +547,65 @@ export class HttpStatusError extends GoodVibesSdkError {
|
|
|
376
547
|
&& value !== null
|
|
377
548
|
&& this.prototype.isPrototypeOf(value);
|
|
378
549
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
550
|
+
if (!GoodVibesSdkError[Symbol.hasInstance](value))
|
|
551
|
+
return false;
|
|
552
|
+
const record = value;
|
|
553
|
+
// Primary: dedicated brand symbol (set in constructor — works in same realm).
|
|
554
|
+
if (record[HTTP_STATUS_ERROR_BRAND] === true)
|
|
555
|
+
return true;
|
|
556
|
+
// Fallback: code-based brand for cross-realm / serialised-error compat.
|
|
557
|
+
return record.code === 'SDK_HTTP_STATUS_ERROR';
|
|
383
558
|
}
|
|
384
559
|
constructor(message, options = {}) {
|
|
385
560
|
super(message, {
|
|
386
561
|
...options,
|
|
562
|
+
// Default code retains 'SDK_HTTP_STATUS_ERROR' when no explicit code is
|
|
563
|
+
// supplied. Use createHttpStatusError() for status-specific semantic codes.
|
|
387
564
|
code: options.code ?? 'SDK_HTTP_STATUS_ERROR',
|
|
388
565
|
source: options.source ?? 'transport',
|
|
389
566
|
});
|
|
567
|
+
// Stamp the brand AFTER super() so the symbol is always present on instances
|
|
568
|
+
// produced by this constructor (regardless of which code was stored).
|
|
569
|
+
Object.defineProperty(this, HTTP_STATUS_ERROR_BRAND, {
|
|
570
|
+
value: true,
|
|
571
|
+
enumerable: false,
|
|
572
|
+
configurable: false,
|
|
573
|
+
});
|
|
390
574
|
}
|
|
391
575
|
}
|
|
392
576
|
export function isStructuredDaemonErrorBody(value) {
|
|
393
577
|
return typeof value === 'object' && value !== null && typeof value.error === 'string';
|
|
394
578
|
}
|
|
579
|
+
/**
|
|
580
|
+
* Creates an {@link HttpStatusError} from an HTTP response.
|
|
581
|
+
*
|
|
582
|
+
* When `body` is a {@link StructuredDaemonErrorBody}, its fields are used
|
|
583
|
+
* directly (including any explicit `code`). When the body is unstructured,
|
|
584
|
+
* the `code` is inferred from `status` via status-based inference (`inferCodeFromStatus`).
|
|
585
|
+
*
|
|
586
|
+
* The structured body path respects the body-supplied `code` over status
|
|
587
|
+
* inference, preserving full backward compatibility for callers that supply
|
|
588
|
+
* custom codes in the daemon response.
|
|
589
|
+
*
|
|
590
|
+
* @param status - HTTP status code.
|
|
591
|
+
* @param url - Request URL.
|
|
592
|
+
* @param method - HTTP method.
|
|
593
|
+
* @param body - Parsed response body (may be structured or unstructured).
|
|
594
|
+
* @param fallbackHint - Human-readable hint when the body provides none.
|
|
595
|
+
*/
|
|
395
596
|
export function createHttpStatusError(status, url, method, body, fallbackHint) {
|
|
396
597
|
if (isStructuredDaemonErrorBody(body)) {
|
|
598
|
+
// Code precedence (highest to lowest):
|
|
599
|
+
// 1. Explicit code in the body (daemon-supplied)
|
|
600
|
+
// 2. Category-derived code when body supplies a category (category is
|
|
601
|
+
// more semantically specific than the HTTP status in this case)
|
|
602
|
+
// 3. Status-derived code as final fallback
|
|
603
|
+
const structuredCode = body.code
|
|
604
|
+
?? (body.category !== undefined ? inferCodeFromCategory(body.category) : undefined)
|
|
605
|
+
?? inferCodeFromStatus(status)
|
|
606
|
+
?? 'UNKNOWN';
|
|
397
607
|
return new HttpStatusError(body.error, {
|
|
398
|
-
code:
|
|
608
|
+
code: structuredCode,
|
|
399
609
|
category: body.category,
|
|
400
610
|
source: body.source ?? 'transport',
|
|
401
611
|
recoverable: body.recoverable,
|
|
@@ -417,6 +627,9 @@ export function createHttpStatusError(status, url, method, body, fallbackHint) {
|
|
|
417
627
|
? body.trim()
|
|
418
628
|
: `Request failed with status ${status}`;
|
|
419
629
|
return new HttpStatusError(message, {
|
|
630
|
+
// Explicitly inject the status-inferred code so HttpStatusError's own
|
|
631
|
+
// default ('SDK_HTTP_STATUS_ERROR') does not suppress the specific code.
|
|
632
|
+
code: inferCodeFromStatus(status) ?? 'SDK_HTTP_STATUS_ERROR',
|
|
420
633
|
status,
|
|
421
634
|
url,
|
|
422
635
|
method,
|