@phala/cloud 0.1.1 → 0.1.2

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.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  // src/client.ts
2
2
  import { ofetch } from "ofetch";
3
3
  import debug from "debug";
4
+ import mitt from "mitt";
4
5
 
5
- // src/types/client.ts
6
+ // src/utils/errors.ts
6
7
  import { z } from "zod";
7
8
  var ApiErrorSchema = z.object({
8
9
  detail: z.union([
@@ -15,21 +16,34 @@ var ApiErrorSchema = z.object({
15
16
  })
16
17
  ),
17
18
  z.record(z.unknown())
18
- ]),
19
+ ]).optional(),
19
20
  type: z.string().optional(),
20
21
  code: z.string().optional()
21
22
  });
22
- var RequestError = class _RequestError extends Error {
23
- constructor(message, options) {
23
+ var PhalaCloudError = class extends Error {
24
+ constructor(message, data) {
24
25
  super(message);
26
+ this.name = this.constructor.name;
27
+ this.status = data.status;
28
+ this.statusText = data.statusText;
29
+ this.detail = data.detail;
30
+ if (Error.captureStackTrace) {
31
+ Error.captureStackTrace(this, this.constructor);
32
+ }
33
+ }
34
+ };
35
+ var RequestError = class _RequestError extends PhalaCloudError {
36
+ constructor(message, options) {
37
+ super(message, {
38
+ status: options?.status ?? 0,
39
+ statusText: options?.statusText ?? "Unknown Error",
40
+ detail: options?.detail || message
41
+ });
25
42
  this.name = "RequestError";
26
43
  this.isRequestError = true;
27
- this.status = options?.status;
28
- this.statusText = options?.statusText;
29
44
  this.data = options?.data;
30
45
  this.request = options?.request;
31
46
  this.response = options?.response;
32
- this.detail = options?.detail || message;
33
47
  this.code = options?.code;
34
48
  this.type = options?.type;
35
49
  }
@@ -70,9 +84,172 @@ var RequestError = class _RequestError extends Error {
70
84
  });
71
85
  }
72
86
  };
87
+ var ValidationError = class extends PhalaCloudError {
88
+ constructor(message, data) {
89
+ super(message, data);
90
+ this.isValidationError = true;
91
+ this.validationErrors = data.validationErrors;
92
+ }
93
+ };
94
+ var AuthError = class extends PhalaCloudError {
95
+ constructor() {
96
+ super(...arguments);
97
+ this.isAuthError = true;
98
+ }
99
+ };
100
+ var BusinessError = class extends PhalaCloudError {
101
+ constructor() {
102
+ super(...arguments);
103
+ this.isBusinessError = true;
104
+ }
105
+ };
106
+ var ServerError = class extends PhalaCloudError {
107
+ constructor() {
108
+ super(...arguments);
109
+ this.isServerError = true;
110
+ }
111
+ };
112
+ var UnknownError = class extends PhalaCloudError {
113
+ constructor() {
114
+ super(...arguments);
115
+ this.isUnknownError = true;
116
+ }
117
+ };
118
+ function extractFieldPath(loc) {
119
+ const filtered = loc.filter((part) => {
120
+ if (typeof part === "string") {
121
+ return !["body", "query", "path", "header"].includes(part);
122
+ }
123
+ return true;
124
+ });
125
+ return filtered.length > 0 ? filtered.join(".") : "unknown";
126
+ }
127
+ function parseValidationErrors(detail) {
128
+ if (!Array.isArray(detail)) {
129
+ return {
130
+ errors: [],
131
+ message: typeof detail === "string" ? detail : "Validation error"
132
+ };
133
+ }
134
+ const errors = detail.map((item) => ({
135
+ field: extractFieldPath(item.loc),
136
+ message: item.msg,
137
+ type: item.type,
138
+ context: item.ctx
139
+ }));
140
+ const count = errors.length;
141
+ const message = count === 1 ? `Validation failed: ${errors[0].message}` : `Validation failed (${count} issue${count > 1 ? "s" : ""})`;
142
+ return { errors, message };
143
+ }
144
+ function categorizeErrorType(status) {
145
+ if (status === 422) {
146
+ return "validation";
147
+ }
148
+ if (status === 401) {
149
+ return "auth";
150
+ }
151
+ if (status === 403) {
152
+ return "auth";
153
+ }
154
+ if (status >= 400 && status < 500) {
155
+ return "business";
156
+ }
157
+ if (status >= 500) {
158
+ return "server";
159
+ }
160
+ return "unknown";
161
+ }
162
+ function extractPrimaryMessage(status, detail, defaultMessage) {
163
+ if (status === 422 && Array.isArray(detail)) {
164
+ const { message } = parseValidationErrors(detail);
165
+ return message;
166
+ }
167
+ if (typeof detail === "string") {
168
+ return detail;
169
+ }
170
+ if (detail && typeof detail === "object" && "message" in detail) {
171
+ const msg = detail.message;
172
+ if (typeof msg === "string") {
173
+ return msg;
174
+ }
175
+ }
176
+ return defaultMessage;
177
+ }
178
+ function parseApiError(requestError) {
179
+ const status = requestError.status ?? 0;
180
+ const statusText = requestError.statusText ?? "Unknown Error";
181
+ const detail = requestError.detail;
182
+ const errorType = categorizeErrorType(status);
183
+ const message = extractPrimaryMessage(status, detail, requestError.message);
184
+ const commonData = {
185
+ status,
186
+ statusText,
187
+ detail
188
+ };
189
+ if (errorType === "validation" && Array.isArray(detail)) {
190
+ const { errors } = parseValidationErrors(detail);
191
+ return new ValidationError(message, {
192
+ ...commonData,
193
+ validationErrors: errors
194
+ });
195
+ }
196
+ if (errorType === "auth") {
197
+ return new AuthError(message, commonData);
198
+ }
199
+ if (errorType === "business") {
200
+ return new BusinessError(message, commonData);
201
+ }
202
+ if (errorType === "server") {
203
+ return new ServerError(message, commonData);
204
+ }
205
+ return new UnknownError(message, commonData);
206
+ }
207
+ function getValidationFields(error) {
208
+ if (error instanceof ValidationError) {
209
+ return error.validationErrors.map((e) => e.field);
210
+ }
211
+ return [];
212
+ }
213
+ function formatValidationErrors(errors, options) {
214
+ const { numbered = true, indent = 2, showFields = true } = options ?? {};
215
+ const indentStr = " ".repeat(indent);
216
+ return errors.map((error, index) => {
217
+ const prefix = numbered ? `${index + 1}. ` : "\u2022 ";
218
+ const field = showFields ? `${error.field}: ` : "";
219
+ return `${indentStr}${prefix}${field}${error.message}`;
220
+ }).join("\n");
221
+ }
222
+ function formatErrorMessage(error, options) {
223
+ const { showFields = true, showType = false } = options ?? {};
224
+ const parts = [];
225
+ if (showType) {
226
+ parts.push(`[${error.constructor.name.toUpperCase()}]`);
227
+ }
228
+ parts.push(error.message);
229
+ if (error instanceof ValidationError && error.validationErrors.length > 0) {
230
+ parts.push("");
231
+ parts.push(formatValidationErrors(error.validationErrors, { showFields }));
232
+ }
233
+ return parts.join("\n");
234
+ }
235
+ function getErrorMessage(error) {
236
+ if (typeof error.detail === "string") {
237
+ return error.detail;
238
+ }
239
+ if (Array.isArray(error.detail)) {
240
+ if (error.detail.length > 0) {
241
+ return error.detail[0]?.msg || "Validation error";
242
+ }
243
+ return "Validation error";
244
+ }
245
+ if (typeof error.detail === "object" && error.detail !== null) {
246
+ return JSON.stringify(error.detail);
247
+ }
248
+ return "Unknown error occurred";
249
+ }
73
250
 
74
251
  // src/client.ts
75
- var SUPPORTED_API_VERSIONS = ["2025-05-31"];
252
+ var SUPPORTED_API_VERSIONS = ["2025-05-31", "2025-10-28"];
76
253
  var logger = debug("phala::api-client");
77
254
  function formatHeaders(headers) {
78
255
  return Object.entries(headers).map(([key, value]) => ` -H "${key}: ${value}"`).join("\n");
@@ -98,18 +275,14 @@ function formatResponse(status, statusText, headers, body) {
98
275
  }
99
276
  var Client = class {
100
277
  constructor(config = {}) {
278
+ this.emitter = mitt();
101
279
  const resolvedConfig = {
102
280
  ...config,
103
281
  apiKey: config.apiKey || process?.env?.PHALA_CLOUD_API_KEY,
104
282
  baseURL: config.baseURL || process?.env?.PHALA_CLOUD_API_PREFIX || "https://cloud-api.phala.network/api/v1"
105
283
  };
106
- const version = resolvedConfig.version && SUPPORTED_API_VERSIONS.includes(resolvedConfig.version) ? resolvedConfig.version : SUPPORTED_API_VERSIONS[0];
284
+ const version = resolvedConfig.version && SUPPORTED_API_VERSIONS.includes(resolvedConfig.version) ? resolvedConfig.version : SUPPORTED_API_VERSIONS[SUPPORTED_API_VERSIONS.length - 1];
107
285
  this.config = resolvedConfig;
108
- if (!resolvedConfig.useCookieAuth && !resolvedConfig.apiKey) {
109
- throw new Error(
110
- "API key is required. Provide it via config.apiKey or set PHALA_CLOUD_API_KEY environment variable."
111
- );
112
- }
113
286
  const { apiKey, baseURL, timeout, headers, useCookieAuth, onResponseError, ...fetchOptions } = resolvedConfig;
114
287
  const requestHeaders = {
115
288
  "X-Phala-Version": version,
@@ -192,75 +365,137 @@ var Client = class {
192
365
  get raw() {
193
366
  return this.fetchInstance;
194
367
  }
368
+ on(type, handler) {
369
+ this.emitter.on(type, handler);
370
+ }
371
+ off(type, handler) {
372
+ this.emitter.off(type, handler);
373
+ }
374
+ once(type, handler) {
375
+ const wrappedHandler = (event) => {
376
+ handler(event);
377
+ this.emitter.off(type, wrappedHandler);
378
+ };
379
+ this.emitter.on(type, wrappedHandler);
380
+ }
195
381
  // ===== Direct methods (throw on error) =====
196
382
  /**
197
- * Perform GET request (throws on error)
383
+ * Perform GET request (throws PhalaCloudError on error)
198
384
  */
199
385
  async get(request, options) {
200
- return this.fetchInstance(request, {
201
- ...options,
202
- method: "GET"
203
- });
386
+ try {
387
+ return await this.fetchInstance(request, {
388
+ ...options,
389
+ method: "GET"
390
+ });
391
+ } catch (error) {
392
+ const requestError = this.convertToRequestError(error);
393
+ const phalaCloudError = this.emitError(requestError);
394
+ throw phalaCloudError;
395
+ }
204
396
  }
205
397
  /**
206
- * Perform POST request (throws on error)
398
+ * Perform POST request (throws PhalaCloudError on error)
207
399
  */
208
400
  async post(request, body, options) {
209
- return this.fetchInstance(request, {
210
- ...options,
211
- method: "POST",
212
- body
213
- });
401
+ try {
402
+ return await this.fetchInstance(request, {
403
+ ...options,
404
+ method: "POST",
405
+ body
406
+ });
407
+ } catch (error) {
408
+ const requestError = this.convertToRequestError(error);
409
+ const phalaCloudError = this.emitError(requestError);
410
+ throw phalaCloudError;
411
+ }
214
412
  }
215
413
  /**
216
- * Perform PUT request (throws on error)
414
+ * Perform PUT request (throws PhalaCloudError on error)
217
415
  */
218
416
  async put(request, body, options) {
219
- return this.fetchInstance(request, {
220
- ...options,
221
- method: "PUT",
222
- body
223
- });
417
+ try {
418
+ return await this.fetchInstance(request, {
419
+ ...options,
420
+ method: "PUT",
421
+ body
422
+ });
423
+ } catch (error) {
424
+ const requestError = this.convertToRequestError(error);
425
+ const phalaCloudError = this.emitError(requestError);
426
+ throw phalaCloudError;
427
+ }
224
428
  }
225
429
  /**
226
- * Perform PATCH request (throws on error)
430
+ * Perform PATCH request (throws PhalaCloudError on error)
227
431
  */
228
432
  async patch(request, body, options) {
229
- return this.fetchInstance(request, {
230
- ...options,
231
- method: "PATCH",
232
- body
233
- });
433
+ try {
434
+ return await this.fetchInstance(request, {
435
+ ...options,
436
+ method: "PATCH",
437
+ body
438
+ });
439
+ } catch (error) {
440
+ const requestError = this.convertToRequestError(error);
441
+ const phalaCloudError = this.emitError(requestError);
442
+ throw phalaCloudError;
443
+ }
234
444
  }
235
445
  /**
236
- * Perform DELETE request (throws on error)
446
+ * Perform DELETE request (throws PhalaCloudError on error)
237
447
  */
238
448
  async delete(request, options) {
239
- return this.fetchInstance(request, {
240
- ...options,
241
- method: "DELETE"
242
- });
449
+ try {
450
+ return await this.fetchInstance(request, {
451
+ ...options,
452
+ method: "DELETE"
453
+ });
454
+ } catch (error) {
455
+ const requestError = this.convertToRequestError(error);
456
+ const phalaCloudError = this.emitError(requestError);
457
+ throw phalaCloudError;
458
+ }
243
459
  }
244
460
  // ===== Safe methods (return SafeResult) =====
461
+ /**
462
+ * Convert any error to RequestError
463
+ */
464
+ convertToRequestError(error) {
465
+ if (error && typeof error === "object" && "data" in error) {
466
+ return RequestError.fromFetchError(error);
467
+ }
468
+ if (error instanceof Error) {
469
+ return RequestError.fromError(error);
470
+ }
471
+ return new RequestError("Unknown error occurred", {
472
+ detail: "Unknown error occurred"
473
+ });
474
+ }
475
+ /**
476
+ * Broadcast error to event listeners (fire-and-forget)
477
+ * @param requestError - The request error to handle
478
+ * @returns PhalaCloudError instance to throw immediately
479
+ */
480
+ emitError(requestError) {
481
+ const phalaCloudError = parseApiError(requestError);
482
+ this.emitter.emit("error", phalaCloudError);
483
+ return phalaCloudError;
484
+ }
245
485
  /**
246
486
  * Safe wrapper for any request method (zod-style result)
487
+ * Returns PhalaCloudError (all errors extend this base class)
247
488
  */
248
489
  async safeRequest(fn) {
249
490
  try {
250
491
  const data = await fn();
251
492
  return { success: true, data };
252
493
  } catch (error) {
253
- if (error && typeof error === "object" && "data" in error) {
254
- const requestError2 = RequestError.fromFetchError(error);
255
- return { success: false, error: requestError2 };
494
+ if (error instanceof PhalaCloudError) {
495
+ return { success: false, error };
256
496
  }
257
- if (error instanceof Error) {
258
- const requestError2 = RequestError.fromError(error);
259
- return { success: false, error: requestError2 };
260
- }
261
- const requestError = new RequestError("Unknown error occurred", {
262
- detail: "Unknown error occurred"
263
- });
497
+ const requestError = this.convertToRequestError(error);
498
+ this.emitError(requestError);
264
499
  return { success: false, error: requestError };
265
500
  }
266
501
  }
@@ -383,7 +618,7 @@ function defineSimpleAction(schema, fn) {
383
618
  const data = await fn(client);
384
619
  return { success: true, data };
385
620
  } catch (error) {
386
- if (error && typeof error === "object" && "isRequestError" in error) {
621
+ if (error && typeof error === "object" && "status" in error) {
387
622
  return { success: false, error };
388
623
  }
389
624
  if (error && typeof error === "object" && "issues" in error) {
@@ -440,7 +675,7 @@ function defineAction(schema, fn) {
440
675
  const data = await fn(client, params);
441
676
  return { success: true, data };
442
677
  } catch (error) {
443
- if (error && typeof error === "object" && "isRequestError" in error) {
678
+ if (error && typeof error === "object" && "status" in error) {
444
679
  return { success: false, error };
445
680
  }
446
681
  if (error && typeof error === "object" && "issues" in error) {
@@ -1025,12 +1260,13 @@ var ProvisionCvmComposeFileUpdateRequestSchema = z13.object({
1025
1260
  }).refine(
1026
1261
  (data) => !!(data.id || data.uuid || data.app_id || data.instance_id),
1027
1262
  "One of id, uuid, app_id, or instance_id must be provided"
1028
- ).transform((data) => ({
1029
- cvmId: data.id || data.uuid || data.app_id || data.instance_id,
1030
- request: data.app_compose,
1031
- update_env_vars: data.update_env_vars,
1032
- _raw: data
1033
- }));
1263
+ ).transform((data) => {
1264
+ return {
1265
+ cvmId: data.id || data.uuid || data.app_id || data.instance_id,
1266
+ request: { ...data.app_compose, update_env_vars: data.update_env_vars },
1267
+ _raw: data
1268
+ };
1269
+ });
1034
1270
  var ProvisionCvmComposeFileUpdateResultSchema = z13.object({
1035
1271
  app_id: z13.string().nullable(),
1036
1272
  device_id: z13.string().nullable(),
@@ -1458,23 +1694,6 @@ import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
1458
1694
  // src/utils/index.ts
1459
1695
  import { encryptEnvVars } from "@phala/dstack-sdk/encrypt-env-vars";
1460
1696
 
1461
- // src/utils/get_error_message.ts
1462
- function getErrorMessage(error) {
1463
- if (typeof error.detail === "string") {
1464
- return error.detail;
1465
- }
1466
- if (Array.isArray(error.detail)) {
1467
- if (error.detail.length > 0) {
1468
- return error.detail[0]?.msg || "Validation error";
1469
- }
1470
- return "Validation error";
1471
- }
1472
- if (typeof error.detail === "object" && error.detail !== null) {
1473
- return JSON.stringify(error.detail);
1474
- }
1475
- return "Unknown error occurred";
1476
- }
1477
-
1478
1697
  // src/utils/as-hex.ts
1479
1698
  import { isHex } from "viem";
1480
1699
  function asHex(value) {
@@ -2727,7 +2946,9 @@ import { verifyEnvEncryptPublicKey } from "@phala/dstack-sdk/verify-env-encrypt-
2727
2946
  export {
2728
2947
  AddComposeHashSchema,
2729
2948
  ApiErrorSchema,
2949
+ AuthError,
2730
2950
  AvailableNodesSchema,
2951
+ BusinessError,
2731
2952
  CommitCvmComposeFileUpdateRequestSchema,
2732
2953
  CommitCvmComposeFileUpdateSchema,
2733
2954
  CommitCvmProvisionRequestSchema,
@@ -2769,6 +2990,7 @@ export {
2769
2990
  NetworkError,
2770
2991
  PaginatedInstanceTypesSchema,
2771
2992
  PaginationMetadataSchema,
2993
+ PhalaCloudError,
2772
2994
  ProvisionCvmComposeFileUpdateRequestSchema,
2773
2995
  ProvisionCvmComposeFileUpdateResultSchema,
2774
2996
  ProvisionCvmRequestSchema,
@@ -2776,13 +2998,16 @@ export {
2776
2998
  RequestError,
2777
2999
  RestartCvmRequestSchema,
2778
3000
  SUPPORTED_CHAINS,
3001
+ ServerError,
2779
3002
  ShutdownCvmRequestSchema,
2780
3003
  StartCvmRequestSchema,
2781
3004
  StopCvmRequestSchema,
2782
3005
  TransactionError,
3006
+ UnknownError,
2783
3007
  UpdateCvmResourcesRequestSchema,
2784
3008
  UpdateCvmVisibilityRequestSchema,
2785
3009
  VMSchema,
3010
+ ValidationError,
2786
3011
  VmInfoSchema,
2787
3012
  WalletError,
2788
3013
  WorkspaceResponseSchema,
@@ -2811,6 +3036,8 @@ export {
2811
3036
  executeTransaction,
2812
3037
  executeTransactionWithRetry,
2813
3038
  extractNetworkClients,
3039
+ formatErrorMessage,
3040
+ formatValidationErrors,
2814
3041
  getAppEnvEncryptPubKey,
2815
3042
  getAvailableNodes,
2816
3043
  getComposeHash2 as getComposeHash,
@@ -2826,9 +3053,11 @@ export {
2826
3053
  getErrorMessage,
2827
3054
  getKmsInfo,
2828
3055
  getKmsList,
3056
+ getValidationFields,
2829
3057
  getWorkspace,
2830
3058
  listInstanceTypes,
2831
3059
  listWorkspaces,
3060
+ parseApiError,
2832
3061
  parseEnv,
2833
3062
  parseEnvVars,
2834
3063
  preprocessAppCompose,
@@ -1,46 +1,10 @@
1
1
  import { z } from "zod";
2
- import type { FetchError, FetchOptions, FetchRequest } from "ofetch";
3
- /**
4
- * API Error Response Schema
5
- */
6
- export declare const ApiErrorSchema: z.ZodObject<{
7
- detail: z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodObject<{
8
- msg: z.ZodString;
9
- type: z.ZodOptional<z.ZodString>;
10
- ctx: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
11
- }, "strip", z.ZodTypeAny, {
12
- msg: string;
13
- type?: string | undefined;
14
- ctx?: Record<string, unknown> | undefined;
15
- }, {
16
- msg: string;
17
- type?: string | undefined;
18
- ctx?: Record<string, unknown> | undefined;
19
- }>, "many">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
20
- type: z.ZodOptional<z.ZodString>;
21
- code: z.ZodOptional<z.ZodString>;
22
- }, "strip", z.ZodTypeAny, {
23
- detail: string | Record<string, unknown> | {
24
- msg: string;
25
- type?: string | undefined;
26
- ctx?: Record<string, unknown> | undefined;
27
- }[];
28
- code?: string | undefined;
29
- type?: string | undefined;
30
- }, {
31
- detail: string | Record<string, unknown> | {
32
- msg: string;
33
- type?: string | undefined;
34
- ctx?: Record<string, unknown> | undefined;
35
- }[];
36
- code?: string | undefined;
37
- type?: string | undefined;
38
- }>;
39
- export type ApiError = z.infer<typeof ApiErrorSchema>;
2
+ import type { FetchOptions, FetchRequest } from "ofetch";
3
+ import type { PhalaCloudError } from "../utils/errors";
40
4
  /**
41
5
  * Enhanced error type that includes both HTTP and validation errors
42
6
  */
43
- export type SafeError = RequestError | z.ZodError;
7
+ export type SafeError = PhalaCloudError | z.ZodError;
44
8
  /**
45
9
  * Result type for safe operations, similar to zod's SafeParseResult
46
10
  * Enhanced to handle both HTTP and validation errors by default
@@ -54,49 +18,6 @@ export type SafeResult<T, E = SafeError> = {
54
18
  data?: never;
55
19
  error: E;
56
20
  };
57
- /**
58
- * Base error class for HTTP requests
59
- * Compatible with ApiError interface for Result type compatibility
60
- */
61
- export declare class RequestError extends Error implements ApiError {
62
- readonly name = "RequestError";
63
- readonly isRequestError: true;
64
- readonly status?: number | undefined;
65
- readonly statusText?: string | undefined;
66
- readonly data?: unknown;
67
- readonly request?: FetchRequest | undefined;
68
- readonly response?: Response | undefined;
69
- readonly detail: string | Record<string, unknown> | Array<{
70
- msg: string;
71
- type?: string;
72
- ctx?: Record<string, unknown>;
73
- }>;
74
- readonly code?: string | undefined;
75
- readonly type?: string | undefined;
76
- constructor(message: string, options?: {
77
- status?: number | undefined;
78
- statusText?: string | undefined;
79
- data?: unknown;
80
- request?: FetchRequest | undefined;
81
- response?: Response | undefined;
82
- cause?: unknown;
83
- detail?: string | Record<string, unknown> | Array<{
84
- msg: string;
85
- type?: string;
86
- ctx?: Record<string, unknown>;
87
- }>;
88
- code?: string | undefined;
89
- type?: string | undefined;
90
- });
91
- /**
92
- * Create RequestError from FetchError
93
- */
94
- static fromFetchError(error: FetchError): RequestError;
95
- /**
96
- * Create RequestError from generic Error
97
- */
98
- static fromError(error: Error, request?: FetchRequest): RequestError;
99
- }
100
21
  /**
101
22
  * Client configuration - extends FetchOptions and adds predefined API-specific options
102
23
  *