@honeyhive/control-plane-sdk 1.0.0-rc.1
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/.github/workflows/publish.yaml +173 -0
- package/CHANGELOG.md +6 -0
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/generated/apiTypes.d.ts +410 -0
- package/dist/generated/apiTypes.d.ts.map +1 -0
- package/dist/generated/apiTypes.js +3 -0
- package/dist/generated/apiTypes.js.map +1 -0
- package/dist/generated/client.d.ts +83 -0
- package/dist/generated/client.d.ts.map +1 -0
- package/dist/generated/client.js +134 -0
- package/dist/generated/client.js.map +1 -0
- package/dist/generated/types.d.ts +672 -0
- package/dist/generated/types.d.ts.map +1 -0
- package/dist/generated/types.js +2 -0
- package/dist/generated/types.js.map +1 -0
- package/dist/generated/version.d.ts +2 -0
- package/dist/generated/version.d.ts.map +1 -0
- package/dist/generated/version.js +3 -0
- package/dist/generated/version.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/util.d.ts +131 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +270 -0
- package/dist/util.js.map +1 -0
- package/package.json +28 -0
- package/src/generated/apiTypes.ts +471 -0
- package/src/generated/client.ts +187 -0
- package/src/generated/types.ts +683 -0
- package/src/generated/version.ts +3 -0
- package/src/index.ts +26 -0
- package/src/util.ts +386 -0
package/src/util.ts
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import createClient, { type ClientOptions, type Middleware } from 'openapi-fetch';
|
|
3
|
+
|
|
4
|
+
import { SDK_VERSION } from './generated/version.js';
|
|
5
|
+
|
|
6
|
+
// The control plane's public, API-key entrypoint. This is deliberately not the
|
|
7
|
+
// URL the HoneyHive web app talks to: the app authenticates with a session
|
|
8
|
+
// cookie rather than an API key, so its endpoint is not usable by this client.
|
|
9
|
+
// Self-hosted and per-customer deployments override via the `controlPlaneUrl`
|
|
10
|
+
// option or `HH_CONTROL_PLANE_URL`.
|
|
11
|
+
const DEFAULT_CONTROL_PLANE_URL = 'https://api.cp.us.honeyhive.ai';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Gets an environment variable value, or returns the default value if the
|
|
15
|
+
* environment variable is not set or is the empty string.
|
|
16
|
+
*
|
|
17
|
+
* Empty string is treated the same as unset because `FOO= node script.js`
|
|
18
|
+
* (and `unset FOO; export FOO=`) are common shell patterns for "no value
|
|
19
|
+
* here", and downstream code (`??` chains, URL fallbacks) would otherwise
|
|
20
|
+
* propagate the empty string as if it were a real value.
|
|
21
|
+
*
|
|
22
|
+
* **This is cross-cutting behavior, not specific to URL resolution.** Every
|
|
23
|
+
* caller (currently `HH_CONTROL_PLANE_API_KEY`, `HH_CONTROL_PLANE_URL`,
|
|
24
|
+
* `HH_VERBOSE`) sees the empty-string-as-unset behavior. When adding a new
|
|
25
|
+
* env var via `getEnv('HH_FOO', 'default')`, be aware that `HH_FOO=""`
|
|
26
|
+
* will resolve to `'default'`, not `''`.
|
|
27
|
+
*
|
|
28
|
+
* This function is also isomorphic. If run from a non-Node.js environment,
|
|
29
|
+
* it will return the default value.
|
|
30
|
+
*/
|
|
31
|
+
function getEnv(key: string, defaultValue?: string): string | undefined {
|
|
32
|
+
if (typeof process !== 'undefined' && process.env) {
|
|
33
|
+
const v = process.env[key];
|
|
34
|
+
return v === undefined || v === '' ? defaultValue : v;
|
|
35
|
+
}
|
|
36
|
+
return defaultValue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The prefix of a fine-grained control-plane API key, whose values have the
|
|
41
|
+
* shape `hh_fgcp_<key id>_<key secret>`. In practice this is the only credential
|
|
42
|
+
* the control plane's API accepts, but the SDK does not enforce that — the
|
|
43
|
+
* prefix's job here is masking: it identifies the values whose key id can be
|
|
44
|
+
* shown, and everything else is redacted wholesale.
|
|
45
|
+
*/
|
|
46
|
+
const FGCP_KEY_PREFIX = 'hh_fgcp_';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The shape of the key id segment of a fine-grained key value: exactly 24
|
|
50
|
+
* alphanumeric characters, with `_` and `-` excluded so that an id can never
|
|
51
|
+
* read as two segments.
|
|
52
|
+
*
|
|
53
|
+
* The length and the alphabet are fixed properties of the key format, so a value
|
|
54
|
+
* whose id segment doesn't match this either wasn't issued by HoneyHive or has
|
|
55
|
+
* been altered in transit — either way it is redacted rather than rendered.
|
|
56
|
+
*/
|
|
57
|
+
const FGCP_KEY_ID_PATTERN = /^[A-Za-z0-9]{24}$/;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Returns a display-safe rendering of an API key for verbose logging.
|
|
61
|
+
*
|
|
62
|
+
* Renders `hh_fgcp_<key id>_******` — the key's id and none of its secret. This
|
|
63
|
+
* is character-for-character the masked form HoneyHive displays for that key, so
|
|
64
|
+
* a verbose log line can be matched directly against a key in your account.
|
|
65
|
+
* Masking from the id rather than from the secret is what makes it unique per
|
|
66
|
+
* key and free of secret material.
|
|
67
|
+
*
|
|
68
|
+
* The id is `_`-free by construction, so it is everything up to the first `_`
|
|
69
|
+
* after the prefix.
|
|
70
|
+
*
|
|
71
|
+
* Anything else collapses to 8 fixed-width asterisks, revealing neither its
|
|
72
|
+
* length nor its content. That covers both a value that isn't a fine-grained key
|
|
73
|
+
* at all — the SDK forwards whatever it is given, so this function must assume
|
|
74
|
+
* it may be handed a coarse-grained HoneyHive key or a credential belonging to
|
|
75
|
+
* some other system entirely — and a fine-grained value whose id segment is
|
|
76
|
+
* truncated or mangled, where the characters after the prefix could be secret
|
|
77
|
+
* material rather than an id.
|
|
78
|
+
*
|
|
79
|
+
* Both guards are load-bearing, and the prefix guard especially: without it the
|
|
80
|
+
* unconditional slice below would chop 8 characters off an arbitrary token and
|
|
81
|
+
* any value whose next 24 characters happened to be alphanumerics followed by
|
|
82
|
+
* `_` would render as `hh_fgcp_<24 chars of that token>_******`, echoing part of
|
|
83
|
+
* a foreign credential under our own prefix. So the output is always either
|
|
84
|
+
* exactly the server's mask or fully redacted, never a partial echo.
|
|
85
|
+
*/
|
|
86
|
+
function maskApiKey(apiKey: string): string {
|
|
87
|
+
if (!apiKey.startsWith(FGCP_KEY_PREFIX)) {
|
|
88
|
+
return '********';
|
|
89
|
+
}
|
|
90
|
+
const rest = apiKey.slice(FGCP_KEY_PREFIX.length);
|
|
91
|
+
const separator = rest.indexOf('_');
|
|
92
|
+
// An empty id never matches the pattern, so a value with no separator at all
|
|
93
|
+
// takes the redacted path without a second branch.
|
|
94
|
+
const keyId = separator === -1 ? '' : rest.slice(0, separator);
|
|
95
|
+
if (!FGCP_KEY_ID_PATTERN.test(keyId)) {
|
|
96
|
+
return '********';
|
|
97
|
+
}
|
|
98
|
+
return `${FGCP_KEY_PREFIX}${keyId}_******`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Configuration options for the HoneyHive Control Plane client. They extend the
|
|
103
|
+
* options from openapi-fetch, but replace 'baseUrl' with 'controlPlaneUrl' so
|
|
104
|
+
* the name is unambiguous (the data plane SDK uses 'dataPlaneUrl').
|
|
105
|
+
*/
|
|
106
|
+
export interface ClientConfig extends Omit<ClientOptions, 'baseUrl' | 'headers'> {
|
|
107
|
+
/**
|
|
108
|
+
* A fine-grained control-plane API key (`hh_fgcp_…`), the only credential the
|
|
109
|
+
* control plane's API accepts in practice. Sent as a bearer token as-is; the
|
|
110
|
+
* control plane is what rejects a key it doesn't accept. Defaults to the
|
|
111
|
+
* `HH_CONTROL_PLANE_API_KEY` environment variable.
|
|
112
|
+
*/
|
|
113
|
+
apiKey?: string;
|
|
114
|
+
controlPlaneUrl?: string;
|
|
115
|
+
middleware?: Middleware[];
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* When true, logs the resolved control plane URL, a masked API key, and the
|
|
119
|
+
* SDK package + version via `console.error` on client construction (stderr in
|
|
120
|
+
* Node, devtools in the browser). Useful for confirming which environment,
|
|
121
|
+
* credential, and SDK build the client is configured with. Defaults to
|
|
122
|
+
* true when the `HH_VERBOSE` environment variable is set to `'true'`
|
|
123
|
+
* (case-insensitive).
|
|
124
|
+
*/
|
|
125
|
+
verbose?: boolean;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @internal HoneyHive use only. Overrides the default SDK provenance headers
|
|
129
|
+
* with custom values (e.g. for the CLI or frontend).
|
|
130
|
+
*/
|
|
131
|
+
_internal_provenance?: {
|
|
132
|
+
// Constrain the type to known values to further discourage
|
|
133
|
+
// customers from setting something arbitrary
|
|
134
|
+
package: 'cp-frontend' | '@honeyhive/cli';
|
|
135
|
+
version: string;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Technically speaking headers can be more complicated than this (e.g.
|
|
139
|
+
// arrays), but to keep the implementation simple we constrain headers to how
|
|
140
|
+
// most people use them anyways
|
|
141
|
+
headers?: Record<string, string>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Custom query serializer that delegates to axios.
|
|
146
|
+
*
|
|
147
|
+
* openapi-fetch defaults to "explode" style (`key=a&key=b`), which the HoneyHive
|
|
148
|
+
* API reads as a plain string rather than a one-element array. Axios uses bracket
|
|
149
|
+
* notation (`key[]=a&key[]=b`) and handles nested objects and arrays
|
|
150
|
+
* recursively, which the API parses correctly.
|
|
151
|
+
*/
|
|
152
|
+
function querySerializer(queryParams: Record<string, unknown>): string {
|
|
153
|
+
const uri = axios.getUri({ url: '', params: queryParams });
|
|
154
|
+
return uri.startsWith('?') ? uri.slice(1) : uri;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type -- needs to match openapi-fetch's own createClient<Paths extends {}> signature
|
|
158
|
+
export function createApiClient<Paths extends {}>(
|
|
159
|
+
options: ClientConfig,
|
|
160
|
+
): ReturnType<typeof createClient<Paths>> {
|
|
161
|
+
const { apiKey, controlPlaneUrl, middleware, verbose, _internal_provenance, ...clientOptions } =
|
|
162
|
+
options;
|
|
163
|
+
const resolvedApiKey = apiKey ?? getEnv('HH_CONTROL_PLANE_API_KEY');
|
|
164
|
+
|
|
165
|
+
// Resolution order: option > env var > default. For the option, any
|
|
166
|
+
// non-undefined value wins (so explicit undefined falls back). For the env
|
|
167
|
+
// var, both unset and empty-string fall back, because `getEnv` normalizes
|
|
168
|
+
// empty-string to undefined — see `getEnv` for the rationale. The default
|
|
169
|
+
// targets HoneyHive's managed control plane; self-hosted and per-customer
|
|
170
|
+
// deployments override via the option or `HH_CONTROL_PLANE_URL`.
|
|
171
|
+
const resolvedControlPlaneUrl =
|
|
172
|
+
controlPlaneUrl ?? getEnv('HH_CONTROL_PLANE_URL') ?? DEFAULT_CONTROL_PLANE_URL;
|
|
173
|
+
|
|
174
|
+
const resolvedVerbose = verbose ?? getEnv('HH_VERBOSE')?.toLowerCase() === 'true';
|
|
175
|
+
const provenance = _internal_provenance ?? {
|
|
176
|
+
package: '@honeyhive/control-plane-sdk',
|
|
177
|
+
version: SDK_VERSION,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Log before the missing-key check so verbose users can see what *did*
|
|
181
|
+
// resolve when construction is about to fail.
|
|
182
|
+
if (resolvedVerbose) {
|
|
183
|
+
console.error(`Control plane URL: ${resolvedControlPlaneUrl}`);
|
|
184
|
+
console.error(`API key: ${resolvedApiKey ? maskApiKey(resolvedApiKey) : '(none)'}`);
|
|
185
|
+
console.error(`Package: ${provenance.package} v${provenance.version}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// When middleware is supplied, it is assumed to handle authentication itself
|
|
189
|
+
// (for example by attaching a short-lived token per request), so no key is
|
|
190
|
+
// required. The URL always resolves (option > env > default), so only the key
|
|
191
|
+
// can be missing here.
|
|
192
|
+
if (!resolvedApiKey && !middleware?.length) {
|
|
193
|
+
throw new Error(
|
|
194
|
+
'Missing API key: provide apiKey in options or set the HH_CONTROL_PLANE_API_KEY environment variable',
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const headers: Record<string, string> = {
|
|
199
|
+
'hh-client-package': provenance.package,
|
|
200
|
+
'hh-client-version': provenance.version,
|
|
201
|
+
'hh-client-language': 'typescript',
|
|
202
|
+
};
|
|
203
|
+
if (resolvedApiKey) {
|
|
204
|
+
headers.Authorization = `Bearer ${resolvedApiKey}`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const client = createClient<Paths>({
|
|
208
|
+
...clientOptions,
|
|
209
|
+
querySerializer,
|
|
210
|
+
// Always set (option > env > default). Middleware, when supplied, may still
|
|
211
|
+
// rewrite the request URL per-request, but the resolved value is the default
|
|
212
|
+
// otherwise.
|
|
213
|
+
baseUrl: resolvedControlPlaneUrl,
|
|
214
|
+
headers: {
|
|
215
|
+
...headers,
|
|
216
|
+
...clientOptions.headers,
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
if (middleware?.length) {
|
|
221
|
+
client.use(...middleware);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return client;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Per-request fetch-level options that are orthogonal to the API request
|
|
229
|
+
* payload. These are passed through to the underlying `fetch()` call via
|
|
230
|
+
* openapi-fetch's init spread.
|
|
231
|
+
*
|
|
232
|
+
* Intentionally kept separate from `*Request` types so API-domain interfaces
|
|
233
|
+
* stay serializable and free of DOM/transport concerns.
|
|
234
|
+
*/
|
|
235
|
+
export interface FetchOptions {
|
|
236
|
+
/**
|
|
237
|
+
* An `AbortSignal` to cancel the in-flight HTTP request. When the signal
|
|
238
|
+
* fires, the underlying `fetch()` rejects with an `AbortError` wrapped in
|
|
239
|
+
* a `NetworkError`.
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```ts
|
|
243
|
+
* const controller = new AbortController();
|
|
244
|
+
* setTimeout(() => controller.abort(), 5000);
|
|
245
|
+
* const result = await client.alerts.get(request, { signal: controller.signal });
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
248
|
+
signal?: AbortSignal;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Structural match for both branches of openapi-fetch's FetchResponse union. */
|
|
252
|
+
type FetchResult<T = unknown, E = unknown> =
|
|
253
|
+
| { data: T; error?: undefined; response: Response }
|
|
254
|
+
| { data?: undefined; error: E; response: Response };
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* HoneyHiveError is a base class for all errors thrown by the HoneyHive control
|
|
258
|
+
* plane client.
|
|
259
|
+
*
|
|
260
|
+
* This error is never thrown directly, but is useful for determining if an
|
|
261
|
+
* error is from the client with `err instanceof HoneyHiveError`
|
|
262
|
+
*/
|
|
263
|
+
export class HoneyHiveError extends Error {}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Type guard that returns the payload as ErrorResponse if it matches the
|
|
267
|
+
* canonical shape, or undefined otherwise.
|
|
268
|
+
*/
|
|
269
|
+
function asErrorResponse(e: unknown): ErrorResponse | undefined {
|
|
270
|
+
if (
|
|
271
|
+
typeof e === 'object' &&
|
|
272
|
+
e !== null &&
|
|
273
|
+
'message' in e &&
|
|
274
|
+
typeof e.message === 'string' &&
|
|
275
|
+
'statusCode' in e &&
|
|
276
|
+
typeof e.statusCode === 'number' &&
|
|
277
|
+
'success' in e &&
|
|
278
|
+
typeof e.success === 'boolean' &&
|
|
279
|
+
// `errorCode` is optional here because not every error response carries it
|
|
280
|
+
// yet; it will become required in a future major version.
|
|
281
|
+
('errorCode' in e ? typeof e.errorCode === 'string' : true)
|
|
282
|
+
) {
|
|
283
|
+
return e as ErrorResponse;
|
|
284
|
+
}
|
|
285
|
+
return undefined;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* An error that is thrown when the API call was not successful
|
|
290
|
+
*
|
|
291
|
+
* @property status - The HTTP status code of the response
|
|
292
|
+
* @property response - The Response object from the fetch call. Call
|
|
293
|
+
* `await err.response.text()` to see details of the error.
|
|
294
|
+
*/
|
|
295
|
+
export class ApiError extends HoneyHiveError {
|
|
296
|
+
public readonly status: number;
|
|
297
|
+
public readonly response: Response;
|
|
298
|
+
public readonly error: unknown;
|
|
299
|
+
|
|
300
|
+
constructor(status: number, error: unknown, response: Response) {
|
|
301
|
+
const parsed = asErrorResponse(error);
|
|
302
|
+
const message =
|
|
303
|
+
parsed !== undefined ? `API error ${status}: ${parsed.message}` : `API error ${status}`;
|
|
304
|
+
super(message, { cause: error });
|
|
305
|
+
this.name = 'ApiError';
|
|
306
|
+
this.status = status;
|
|
307
|
+
this.response = response;
|
|
308
|
+
this.error = error;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Returns the parsed error response body with its known type, or `undefined`
|
|
313
|
+
* if the body doesn't match the expected shape.
|
|
314
|
+
*
|
|
315
|
+
* The HoneyHive API returns `{ statusCode, message, success, errorCode }` as
|
|
316
|
+
* JSON for all error responses. However, failures that happen before a request
|
|
317
|
+
* reaches the API (e.g. an HTML 502 from a load balancer, or a generic 404) can
|
|
318
|
+
* arrive in an unrecognized shape, in which case we return undefined.
|
|
319
|
+
*/
|
|
320
|
+
public parseError(): ErrorResponse | undefined {
|
|
321
|
+
return asErrorResponse(this.error);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* The standard error response shape the HoneyHive API returns for all non-2xx
|
|
327
|
+
* responses.
|
|
328
|
+
*/
|
|
329
|
+
export interface ErrorResponse {
|
|
330
|
+
statusCode: number;
|
|
331
|
+
message: string;
|
|
332
|
+
success: boolean;
|
|
333
|
+
errorCode: string;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* An error that is thrown when the API call fails at the network level
|
|
338
|
+
* (e.g. DNS failures, timeouts, connection refused)
|
|
339
|
+
*/
|
|
340
|
+
export class NetworkError extends HoneyHiveError {
|
|
341
|
+
readonly error: unknown;
|
|
342
|
+
|
|
343
|
+
constructor(error: unknown) {
|
|
344
|
+
super(error instanceof Error ? error.message : String(error), { cause: error });
|
|
345
|
+
this.name = 'NetworkError';
|
|
346
|
+
this.error = error;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Narrows a FetchResult to its success branch while also stripping the
|
|
352
|
+
* `undefined` that openapi-fetch adds to `data` across both union members.
|
|
353
|
+
*
|
|
354
|
+
* By declaring `result is { data: T; … }` (without `| undefined`), TypeScript
|
|
355
|
+
* narrows `data` from `T | undefined` to `T` after the guard — no cast needed.
|
|
356
|
+
*/
|
|
357
|
+
function isSuccess<T, E>(
|
|
358
|
+
result: FetchResult<T | undefined, E>,
|
|
359
|
+
): result is { data: T; error?: undefined; response: Response } {
|
|
360
|
+
return result.error === undefined;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Unwraps a fetch result, throwing an ApiError if the result contains an error.
|
|
365
|
+
* This enables a more ergonomic way of consuming the results of API calls.
|
|
366
|
+
*
|
|
367
|
+
* The generic accepts `FetchResult<T | undefined, E>` so that `T` itself is
|
|
368
|
+
* inferred without `undefined`. openapi-fetch's union types `data` as
|
|
369
|
+
* `ResponseType | undefined` across both branches; by absorbing the
|
|
370
|
+
* `undefined` in the parameter, the return type is a clean `Promise<T>`.
|
|
371
|
+
*/
|
|
372
|
+
export async function unwrap<T, E>(promise: Promise<FetchResult<T | undefined, E>>): Promise<T> {
|
|
373
|
+
let result: FetchResult<T | undefined, E>;
|
|
374
|
+
try {
|
|
375
|
+
result = await promise;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
if (error instanceof HoneyHiveError) {
|
|
378
|
+
throw error;
|
|
379
|
+
}
|
|
380
|
+
throw new NetworkError(error);
|
|
381
|
+
}
|
|
382
|
+
if (!isSuccess(result)) {
|
|
383
|
+
throw new ApiError(result.response.status, result.error, result.response);
|
|
384
|
+
}
|
|
385
|
+
return result.data;
|
|
386
|
+
}
|