@oxyhq/core 19.1.1 → 20.0.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/LICENSE +202 -0
- package/NOTICE +15 -0
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/HttpService.js +23 -18
- package/dist/cjs/i18n/accountCategoryLabels.js +44 -0
- package/dist/cjs/i18n/accountRoleLabels.js +27 -0
- package/dist/cjs/i18n/reputationCategoryLabels.js +20 -0
- package/dist/cjs/i18n/trustTierLabels.js +19 -0
- package/dist/cjs/index.js +19 -9
- package/dist/cjs/mixins/OxyServices.followGraph.js +17 -0
- package/dist/cjs/session/accountProjection.js +31 -6
- package/dist/cjs/utils/errorUtils.js +65 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +24 -19
- package/dist/esm/i18n/accountCategoryLabels.js +37 -0
- package/dist/esm/i18n/accountRoleLabels.js +20 -0
- package/dist/esm/i18n/reputationCategoryLabels.js +13 -0
- package/dist/esm/i18n/trustTierLabels.js +12 -0
- package/dist/esm/index.js +11 -8
- package/dist/esm/mixins/OxyServices.followGraph.js +17 -0
- package/dist/esm/session/accountProjection.js +30 -6
- package/dist/esm/utils/errorUtils.js +63 -1
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/i18n/accountCategoryLabels.d.ts +34 -0
- package/dist/types/i18n/accountRoleLabels.d.ts +10 -0
- package/dist/types/i18n/reputationCategoryLabels.d.ts +10 -0
- package/dist/types/i18n/trustTierLabels.d.ts +9 -0
- package/dist/types/index.d.ts +7 -2
- package/dist/types/mixins/OxyServices.followGraph.d.ts +13 -0
- package/dist/types/session/accountProjection.d.ts +20 -4
- package/dist/types/utils/errorUtils.d.ts +67 -0
- package/package.json +8 -10
- package/src/HttpService.ts +29 -22
- package/src/__tests__/parseHttpErrorBody.test.ts +116 -0
- package/src/__tests__/serverValueImportsDeclared.test.ts +120 -0
- package/src/i18n/__tests__/accountCategoryLabels.test.ts +62 -0
- package/src/i18n/__tests__/accountRoleLabels.test.ts +54 -0
- package/src/i18n/__tests__/reputationCategoryLabels.test.ts +56 -0
- package/src/i18n/__tests__/trustTierLabels.test.ts +47 -0
- package/src/i18n/accountCategoryLabels.ts +44 -0
- package/src/i18n/accountRoleLabels.ts +26 -0
- package/src/i18n/reputationCategoryLabels.ts +20 -0
- package/src/i18n/trustTierLabels.ts +18 -0
- package/src/index.ts +13 -6
- package/src/mixins/OxyServices.followGraph.ts +24 -0
- package/src/mixins/__tests__/followGraph.test.ts +19 -0
- package/src/session/__tests__/accountProjection.test.ts +98 -0
- package/src/session/accountProjection.ts +37 -6
- package/src/utils/errorUtils.ts +116 -5
package/src/utils/errorUtils.ts
CHANGED
|
@@ -36,6 +36,110 @@ export const ErrorCodes = {
|
|
|
36
36
|
CONNECTION_FAILED: 'CONNECTION_FAILED'
|
|
37
37
|
} as const;
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* The `Error` shape the SDK rejects with when an HTTP request fails.
|
|
41
|
+
*
|
|
42
|
+
* `HttpService` throws this for every non-2xx response, and
|
|
43
|
+
* `OxyServices.handleError` (the wrapper the mixin methods rethrow through)
|
|
44
|
+
* preserves `message`, `status`, `code` and `details`. `response` only survives
|
|
45
|
+
* on the raw `HttpService`/`makeRequest` path, so treat it as optional.
|
|
46
|
+
*
|
|
47
|
+
* Narrow a caught value with {@link isHttpRequestError} instead of asserting.
|
|
48
|
+
*/
|
|
49
|
+
export interface HttpRequestError extends Error {
|
|
50
|
+
/** HTTP status of the failed response. */
|
|
51
|
+
status: number;
|
|
52
|
+
/** Machine-readable code the server sent, when it sent one. */
|
|
53
|
+
code?: string;
|
|
54
|
+
/** Structured error detail the server sent, when it sent an object. */
|
|
55
|
+
details?: Record<string, unknown>;
|
|
56
|
+
/**
|
|
57
|
+
* Present on errors thrown directly by `HttpService`. `data` is the parsed
|
|
58
|
+
* JSON error body verbatim — the escape hatch for any server field the SDK
|
|
59
|
+
* does not lift onto `code`/`details`.
|
|
60
|
+
*/
|
|
61
|
+
response?: {
|
|
62
|
+
status: number;
|
|
63
|
+
statusText: string;
|
|
64
|
+
data?: unknown;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Narrow a caught value to {@link HttpRequestError}.
|
|
70
|
+
*
|
|
71
|
+
* Returns `false` for a plain {@link ApiError} object (those are objects, not
|
|
72
|
+
* `Error`s) — run an arbitrary thrown value through {@link handleHttpError}
|
|
73
|
+
* first if you need one normalized.
|
|
74
|
+
*/
|
|
75
|
+
export function isHttpRequestError(value: unknown): value is HttpRequestError {
|
|
76
|
+
if (!(value instanceof Error)) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return typeof (value as Partial<HttpRequestError>).status === 'number';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The fields {@link parseHttpErrorBody} lifts off a parsed error response body.
|
|
84
|
+
*/
|
|
85
|
+
export interface ParsedHttpErrorBody {
|
|
86
|
+
message?: string;
|
|
87
|
+
code?: string;
|
|
88
|
+
details?: Record<string, unknown>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const isPlainRecord = (value: unknown): value is Record<string, unknown> =>
|
|
92
|
+
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
93
|
+
|
|
94
|
+
const nonEmptyString = (value: unknown): string | undefined =>
|
|
95
|
+
typeof value === 'string' && value.trim().length > 0 ? value : undefined;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Extract `message` / `code` / `details` from a parsed HTTP error response body.
|
|
99
|
+
*
|
|
100
|
+
* Handles every error envelope in use across the Oxy ecosystem:
|
|
101
|
+
*
|
|
102
|
+
* - `{ error: { code, message, details? } }` — nested envelope (CrowdSource and
|
|
103
|
+
* other Oxy services). Never stringify the nested object: `new Error(obj)`
|
|
104
|
+
* yields the literal message `"[object Object]"`.
|
|
105
|
+
* - `{ error: '<CODE>', message, details? }` — oxy-api's canonical shape
|
|
106
|
+
* (`ApiError.toJSON`), where the top-level `error` field IS the code.
|
|
107
|
+
* - `{ error: '<CODE>', error_description }` — RFC 6749 §5.2 / RFC 6750 §3, the
|
|
108
|
+
* OAuth token and userinfo endpoints. `error_description` is the human text
|
|
109
|
+
* and `error` is the machine code, so both survive.
|
|
110
|
+
* - `{ message, code }` — e.g. the API's CSRF rejections.
|
|
111
|
+
* - `{ error: '<human message>' }` — legacy hand-rolled routes. With no sibling
|
|
112
|
+
* `message`/`error_description` the string is the message, not a code: a bare
|
|
113
|
+
* `error` string is not machine-readable enough to promote to `code`.
|
|
114
|
+
*
|
|
115
|
+
* Anything else — a non-object body (`null`, `[]`, `"str"`, `42`), or an object
|
|
116
|
+
* carrying none of these fields — yields an empty result, leaving the caller on
|
|
117
|
+
* its status-based fallback message. Total function: never throws.
|
|
118
|
+
*/
|
|
119
|
+
export function parseHttpErrorBody(body: unknown): ParsedHttpErrorBody {
|
|
120
|
+
if (!isPlainRecord(body)) {
|
|
121
|
+
return {};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const nested = isPlainRecord(body.error) ? body.error : undefined;
|
|
125
|
+
const errorString = nonEmptyString(body.error);
|
|
126
|
+
// A sibling that proves the top-level `error` is a CODE rather than prose.
|
|
127
|
+
const siblingMessage = nonEmptyString(body.message) ?? nonEmptyString(body.error_description);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
message: siblingMessage ?? (nested ? nonEmptyString(nested.message) : errorString),
|
|
131
|
+
code:
|
|
132
|
+
(nested ? nonEmptyString(nested.code) : undefined) ??
|
|
133
|
+
nonEmptyString(body.code) ??
|
|
134
|
+
(siblingMessage ? errorString : undefined),
|
|
135
|
+
details: isPlainRecord(body.details)
|
|
136
|
+
? body.details
|
|
137
|
+
: nested && isPlainRecord(nested.details)
|
|
138
|
+
? nested.details
|
|
139
|
+
: undefined,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
39
143
|
/**
|
|
40
144
|
* Create a standardized API error
|
|
41
145
|
*/
|
|
@@ -98,21 +202,28 @@ export function handleHttpError(error: unknown): ApiError {
|
|
|
98
202
|
|
|
99
203
|
// Handle fetch Response errors - check if it has response property with status
|
|
100
204
|
if (error && typeof error === 'object' && 'response' in error) {
|
|
101
|
-
const fetchError = error as {
|
|
102
|
-
response?: {
|
|
103
|
-
status: number;
|
|
205
|
+
const fetchError = error as {
|
|
206
|
+
response?: {
|
|
207
|
+
status: number;
|
|
104
208
|
statusText?: string;
|
|
105
209
|
};
|
|
106
210
|
status?: number;
|
|
107
211
|
message?: string;
|
|
212
|
+
details?: unknown;
|
|
108
213
|
};
|
|
109
|
-
|
|
214
|
+
|
|
110
215
|
const status = fetchError.response?.status || fetchError.status;
|
|
111
216
|
if (status) {
|
|
217
|
+
// `details` is carried through when present: a body may ship structured
|
|
218
|
+
// detail without a machine-readable `code` (which is what routes the
|
|
219
|
+
// error to the already-an-ApiError branch above), and dropping it here
|
|
220
|
+
// would make it unreachable to every caller that rethrows via
|
|
221
|
+
// `OxyServices.handleError`.
|
|
112
222
|
return createApiError(
|
|
113
223
|
fetchError.message || `HTTP ${status} error`,
|
|
114
224
|
getErrorCodeFromStatus(status),
|
|
115
|
-
status
|
|
225
|
+
status,
|
|
226
|
+
isPlainRecord(fetchError.details) ? fetchError.details : undefined
|
|
116
227
|
);
|
|
117
228
|
}
|
|
118
229
|
}
|