zlient 1.0.11 → 2.0.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/README.md +70 -361
- package/dist/auth.d.ts.map +1 -1
- package/dist/endpoint/base-endpoint.d.ts +21 -109
- package/dist/endpoint/base-endpoint.d.ts.map +1 -1
- package/dist/http/http-client.d.ts +16 -7
- package/dist/http/http-client.d.ts.map +1 -1
- package/dist/index.cjs +238 -233
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +235 -229
- package/dist/index.js.map +1 -1
- package/dist/metrics.d.ts.map +1 -1
- package/dist/types.d.ts +78 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +40 -44
package/dist/index.js
CHANGED
|
@@ -1,5 +1,80 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
+
//#region lib/auth.ts
|
|
4
|
+
/**
|
|
5
|
+
* No-op authentication provider (no authentication applied).
|
|
6
|
+
* Use this when you don't need authentication.
|
|
7
|
+
*/
|
|
8
|
+
var NoAuth = class {
|
|
9
|
+
async apply() {}
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* API Key authentication provider.
|
|
13
|
+
* Supports both header-based and query parameter-based authentication.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* // Header-based
|
|
18
|
+
* const auth = new ApiKeyAuth({ header: 'X-API-Key', value: 'secret' });
|
|
19
|
+
*
|
|
20
|
+
* // Query parameter-based
|
|
21
|
+
* const auth = new ApiKeyAuth({ query: 'apiKey', value: 'secret' });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
var ApiKeyAuth = class {
|
|
25
|
+
constructor(opts) {
|
|
26
|
+
this.opts = opts;
|
|
27
|
+
if (!opts.header && !opts.query) throw new Error("ApiKeyAuth requires either \"header\" or \"query\" option");
|
|
28
|
+
if (opts.header && opts.query) throw new Error("ApiKeyAuth cannot use both \"header\" and \"query\" options");
|
|
29
|
+
}
|
|
30
|
+
apply({ url, init }) {
|
|
31
|
+
const value = this.opts.value;
|
|
32
|
+
if (this.opts.header) if (init.headers instanceof Headers) init.headers.set(this.opts.header, value);
|
|
33
|
+
else if (Array.isArray(init.headers)) init.headers.push([this.opts.header, value]);
|
|
34
|
+
else init.headers = {
|
|
35
|
+
...init.headers,
|
|
36
|
+
[this.opts.header]: value
|
|
37
|
+
};
|
|
38
|
+
else if (this.opts.query) {
|
|
39
|
+
const u = new URL(url);
|
|
40
|
+
u.searchParams.set(this.opts.query, value);
|
|
41
|
+
init.__urlOverride = u.toString();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Bearer token authentication provider.
|
|
47
|
+
* Supports both static tokens and dynamic token fetching (e.g., for OAuth2 refresh).
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* // Static token
|
|
52
|
+
* const auth = new BearerTokenAuth(() => 'my-token');
|
|
53
|
+
*
|
|
54
|
+
* // Dynamic token with refresh
|
|
55
|
+
* const auth = new BearerTokenAuth(async () => {
|
|
56
|
+
* return await refreshAccessToken();
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
var BearerTokenAuth = class {
|
|
61
|
+
constructor(getToken) {
|
|
62
|
+
this.getToken = getToken;
|
|
63
|
+
}
|
|
64
|
+
async apply({ init }) {
|
|
65
|
+
const token = await this.getToken();
|
|
66
|
+
if (!token) throw new Error("BearerTokenAuth: token is empty or undefined");
|
|
67
|
+
const authHeader = `Bearer ${token}`;
|
|
68
|
+
if (init.headers instanceof Headers) init.headers.set("Authorization", authHeader);
|
|
69
|
+
else if (Array.isArray(init.headers)) init.headers.push(["Authorization", authHeader]);
|
|
70
|
+
else init.headers = {
|
|
71
|
+
...init.headers,
|
|
72
|
+
Authorization: authHeader
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
//#endregion
|
|
3
78
|
//#region lib/types.ts
|
|
4
79
|
const HTTPMethod = {
|
|
5
80
|
GET: "GET",
|
|
@@ -10,6 +85,70 @@ const HTTPMethod = {
|
|
|
10
85
|
HEAD: "HEAD",
|
|
11
86
|
OPTIONS: "OPTIONS"
|
|
12
87
|
};
|
|
88
|
+
const HTTPStatusCode = {
|
|
89
|
+
CONTINUE: 100,
|
|
90
|
+
SWITCHING_PROTOCOLS: 101,
|
|
91
|
+
PROCESSING: 102,
|
|
92
|
+
EARLY_HINTS: 103,
|
|
93
|
+
OK: 200,
|
|
94
|
+
CREATED: 201,
|
|
95
|
+
ACCEPTED: 202,
|
|
96
|
+
NON_AUTHORITATIVE_INFORMATION: 203,
|
|
97
|
+
NO_CONTENT: 204,
|
|
98
|
+
RESET_CONTENT: 205,
|
|
99
|
+
PARTIAL_CONTENT: 206,
|
|
100
|
+
MULTI_STATUS: 207,
|
|
101
|
+
ALREADY_REPORTED: 208,
|
|
102
|
+
IM_USED: 226,
|
|
103
|
+
MULTIPLE_CHOICES: 300,
|
|
104
|
+
MOVED_PERMANENTLY: 301,
|
|
105
|
+
FOUND: 302,
|
|
106
|
+
SEE_OTHER: 303,
|
|
107
|
+
NOT_MODIFIED: 304,
|
|
108
|
+
USE_PROXY: 305,
|
|
109
|
+
TEMPORARY_REDIRECT: 307,
|
|
110
|
+
PERMANENT_REDIRECT: 308,
|
|
111
|
+
BAD_REQUEST: 400,
|
|
112
|
+
UNAUTHORIZED: 401,
|
|
113
|
+
PAYMENT_REQUIRED: 402,
|
|
114
|
+
FORBIDDEN: 403,
|
|
115
|
+
NOT_FOUND: 404,
|
|
116
|
+
METHOD_NOT_ALLOWED: 405,
|
|
117
|
+
NOT_ACCEPTABLE: 406,
|
|
118
|
+
PROXY_AUTHENTICATION_REQUIRED: 407,
|
|
119
|
+
REQUEST_TIMEOUT: 408,
|
|
120
|
+
CONFLICT: 409,
|
|
121
|
+
GONE: 410,
|
|
122
|
+
LENGTH_REQUIRED: 411,
|
|
123
|
+
PRECONDITION_FAILED: 412,
|
|
124
|
+
PAYLOAD_TOO_LARGE: 413,
|
|
125
|
+
URI_TOO_LONG: 414,
|
|
126
|
+
UNSUPPORTED_MEDIA_TYPE: 415,
|
|
127
|
+
RANGE_NOT_SATISFIABLE: 416,
|
|
128
|
+
EXPECTATION_FAILED: 417,
|
|
129
|
+
IM_A_TEAPOT: 418,
|
|
130
|
+
MISDIRECTED_REQUEST: 421,
|
|
131
|
+
UNPROCESSABLE_ENTITY: 422,
|
|
132
|
+
LOCKED: 423,
|
|
133
|
+
FAILED_DEPENDENCY: 424,
|
|
134
|
+
TOO_EARLY: 425,
|
|
135
|
+
UPGRADE_REQUIRED: 426,
|
|
136
|
+
PRECONDITION_REQUIRED: 428,
|
|
137
|
+
TOO_MANY_REQUESTS: 429,
|
|
138
|
+
REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
|
|
139
|
+
UNAVAILABLE_FOR_LEGAL_REASONS: 451,
|
|
140
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
141
|
+
NOT_IMPLEMENTED: 501,
|
|
142
|
+
BAD_GATEWAY: 502,
|
|
143
|
+
SERVICE_UNAVAILABLE: 503,
|
|
144
|
+
GATEWAY_TIMEOUT: 504,
|
|
145
|
+
HTTP_VERSION_NOT_SUPPORTED: 505,
|
|
146
|
+
VARIANT_ALSO_NEGOTIATES: 506,
|
|
147
|
+
INSUFFICIENT_STORAGE: 507,
|
|
148
|
+
LOOP_DETECTED: 508,
|
|
149
|
+
NOT_EXTENDED: 510,
|
|
150
|
+
NETWORK_AUTHENTICATION_REQUIRED: 511
|
|
151
|
+
};
|
|
13
152
|
/**
|
|
14
153
|
* Custom error class for API-related errors.
|
|
15
154
|
* Includes HTTP status codes, response details, and validation errors.
|
|
@@ -68,6 +207,18 @@ var ApiError = class ApiError extends Error {
|
|
|
68
207
|
}
|
|
69
208
|
};
|
|
70
209
|
/**
|
|
210
|
+
* Error thrown when an endpoint receives a response with a status code
|
|
211
|
+
* that has no defined schema in the endpoint configuration.
|
|
212
|
+
*/
|
|
213
|
+
var SchemaDefinitionError = class SchemaDefinitionError extends Error {
|
|
214
|
+
constructor(status) {
|
|
215
|
+
super(`No schema defined for status code ${status}`);
|
|
216
|
+
this.status = status;
|
|
217
|
+
this.name = "SchemaDefinitionError";
|
|
218
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, SchemaDefinitionError);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
71
222
|
* Schema for paginated responses
|
|
72
223
|
*/
|
|
73
224
|
const PaginationSchema = z.object({
|
|
@@ -103,107 +254,9 @@ function toQueryString(q) {
|
|
|
103
254
|
return s ? `?${s}` : "";
|
|
104
255
|
}
|
|
105
256
|
|
|
106
|
-
//#endregion
|
|
107
|
-
//#region lib/auth.ts
|
|
108
|
-
/**
|
|
109
|
-
* No-op authentication provider (no authentication applied).
|
|
110
|
-
* Use this when you don't need authentication.
|
|
111
|
-
*/
|
|
112
|
-
var NoAuth = class {
|
|
113
|
-
async apply() {}
|
|
114
|
-
};
|
|
115
|
-
/**
|
|
116
|
-
* API Key authentication provider.
|
|
117
|
-
* Supports both header-based and query parameter-based authentication.
|
|
118
|
-
*
|
|
119
|
-
* @example
|
|
120
|
-
* ```ts
|
|
121
|
-
* // Header-based
|
|
122
|
-
* const auth = new ApiKeyAuth({ header: 'X-API-Key', value: 'secret' });
|
|
123
|
-
*
|
|
124
|
-
* // Query parameter-based
|
|
125
|
-
* const auth = new ApiKeyAuth({ query: 'apiKey', value: 'secret' });
|
|
126
|
-
* ```
|
|
127
|
-
*/
|
|
128
|
-
var ApiKeyAuth = class {
|
|
129
|
-
constructor(opts) {
|
|
130
|
-
this.opts = opts;
|
|
131
|
-
if (!opts.header && !opts.query) throw new Error("ApiKeyAuth requires either \"header\" or \"query\" option");
|
|
132
|
-
if (opts.header && opts.query) throw new Error("ApiKeyAuth cannot use both \"header\" and \"query\" options");
|
|
133
|
-
}
|
|
134
|
-
apply({ url, init }) {
|
|
135
|
-
if (this.opts.header) init.headers = {
|
|
136
|
-
...init.headers,
|
|
137
|
-
[this.opts.header]: this.opts.value
|
|
138
|
-
};
|
|
139
|
-
else if (this.opts.query) {
|
|
140
|
-
const u = new URL(url);
|
|
141
|
-
u.searchParams.set(this.opts.query, this.opts.value);
|
|
142
|
-
init.__urlOverride = u.toString();
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
/**
|
|
147
|
-
* Bearer token authentication provider.
|
|
148
|
-
* Supports both static tokens and dynamic token fetching (e.g., for OAuth2 refresh).
|
|
149
|
-
*
|
|
150
|
-
* @example
|
|
151
|
-
* ```ts
|
|
152
|
-
* // Static token
|
|
153
|
-
* const auth = new BearerTokenAuth(() => 'my-token');
|
|
154
|
-
*
|
|
155
|
-
* // Dynamic token with refresh
|
|
156
|
-
* const auth = new BearerTokenAuth(async () => {
|
|
157
|
-
* return await refreshAccessToken();
|
|
158
|
-
* });
|
|
159
|
-
* ```
|
|
160
|
-
*/
|
|
161
|
-
var BearerTokenAuth = class {
|
|
162
|
-
constructor(getToken) {
|
|
163
|
-
this.getToken = getToken;
|
|
164
|
-
}
|
|
165
|
-
async apply({ init }) {
|
|
166
|
-
const token = await this.getToken();
|
|
167
|
-
if (!token) throw new Error("BearerTokenAuth: token is empty or undefined");
|
|
168
|
-
init.headers = {
|
|
169
|
-
...init.headers,
|
|
170
|
-
Authorization: `Bearer ${token}`
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
|
|
175
257
|
//#endregion
|
|
176
258
|
//#region lib/validation.ts
|
|
177
259
|
/**
|
|
178
|
-
* Safely parse data with a Zod schema without throwing.
|
|
179
|
-
* Returns a result object with success status and data or error.
|
|
180
|
-
*
|
|
181
|
-
* @param schema - Zod schema to validate against
|
|
182
|
-
* @param data - Data to validate
|
|
183
|
-
* @returns Result object with success flag and data/error
|
|
184
|
-
*
|
|
185
|
-
* @example
|
|
186
|
-
* ```ts
|
|
187
|
-
* const result = safeParse(UserSchema, userData);
|
|
188
|
-
* if (result.success) {
|
|
189
|
-
* console.log(result.data);
|
|
190
|
-
* } else {
|
|
191
|
-
* console.error(result.error);
|
|
192
|
-
* }
|
|
193
|
-
* ```
|
|
194
|
-
*/
|
|
195
|
-
function safeParse(schema, data) {
|
|
196
|
-
const res = schema.safeParse(data);
|
|
197
|
-
if (res.success) return {
|
|
198
|
-
success: true,
|
|
199
|
-
data: res.data
|
|
200
|
-
};
|
|
201
|
-
return {
|
|
202
|
-
success: false,
|
|
203
|
-
error: res.error
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
260
|
* Parse data with a Zod schema, throwing an ApiError on validation failure.
|
|
208
261
|
* Use this when you want to fail fast on invalid data.
|
|
209
262
|
*
|
|
@@ -230,6 +283,48 @@ function parseOrThrow(schema, data) {
|
|
|
230
283
|
return res.data;
|
|
231
284
|
}
|
|
232
285
|
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region lib/endpoint/base-endpoint.ts
|
|
288
|
+
var Endpoint = class {
|
|
289
|
+
constructor(client, config) {
|
|
290
|
+
this.client = client;
|
|
291
|
+
this.config = config;
|
|
292
|
+
}
|
|
293
|
+
async call(params) {
|
|
294
|
+
const { data, query, pathParams, headers, signal } = params;
|
|
295
|
+
if (this.config.request && data !== void 0) {
|
|
296
|
+
const parsed = this.config.request.safeParse(data);
|
|
297
|
+
if (!parsed.success) throw parsed.error;
|
|
298
|
+
}
|
|
299
|
+
if (this.config.query && query !== void 0) {
|
|
300
|
+
const parsed = this.config.query.safeParse(query);
|
|
301
|
+
if (!parsed.success) throw parsed.error;
|
|
302
|
+
}
|
|
303
|
+
if (this.config.pathParams && pathParams !== void 0) {
|
|
304
|
+
const parsed = this.config.pathParams.safeParse(pathParams);
|
|
305
|
+
if (!parsed.success) throw parsed.error;
|
|
306
|
+
}
|
|
307
|
+
if (this.config.request && data === void 0) throw new Error("Missing required request body (data)");
|
|
308
|
+
if (this.config.pathParams && pathParams === void 0) throw new Error("Missing required path parameters (pathParams)");
|
|
309
|
+
let pathStr;
|
|
310
|
+
if (typeof this.config.path === "function") {
|
|
311
|
+
if (!pathParams) throw new Error("Path function requires pathParams");
|
|
312
|
+
pathStr = this.config.path(pathParams);
|
|
313
|
+
} else pathStr = this.config.path;
|
|
314
|
+
const { data: responseData, status } = await this.client.request(this.config.method, pathStr, data, {
|
|
315
|
+
query,
|
|
316
|
+
headers,
|
|
317
|
+
baseUrlKey: this.config.baseUrlKey,
|
|
318
|
+
signal
|
|
319
|
+
});
|
|
320
|
+
const schema = this.config.response;
|
|
321
|
+
if (schema instanceof z.ZodType) return parseOrThrow(schema, responseData);
|
|
322
|
+
const specificSchema = schema[status];
|
|
323
|
+
if (!specificSchema) throw new SchemaDefinitionError(status);
|
|
324
|
+
return parseOrThrow(specificSchema, responseData);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
233
328
|
//#endregion
|
|
234
329
|
//#region lib/logger.ts
|
|
235
330
|
/**
|
|
@@ -257,7 +352,9 @@ var ConsoleLogger = class {
|
|
|
257
352
|
LogLevel.WARN,
|
|
258
353
|
LogLevel.ERROR
|
|
259
354
|
];
|
|
260
|
-
|
|
355
|
+
const entryLevelIndex = levels.indexOf(entry.level);
|
|
356
|
+
const minLevelIndex = levels.indexOf(this.minLevel);
|
|
357
|
+
if (entryLevelIndex < minLevelIndex) return;
|
|
261
358
|
const output = {
|
|
262
359
|
...entry,
|
|
263
360
|
error: entry.error ? {
|
|
@@ -300,7 +397,7 @@ var LoggerUtil = class {
|
|
|
300
397
|
this.logger.log({
|
|
301
398
|
level: LogLevel.DEBUG,
|
|
302
399
|
message,
|
|
303
|
-
timestamp:
|
|
400
|
+
timestamp: new Date().toISOString(),
|
|
304
401
|
context
|
|
305
402
|
});
|
|
306
403
|
}
|
|
@@ -308,7 +405,7 @@ var LoggerUtil = class {
|
|
|
308
405
|
this.logger.log({
|
|
309
406
|
level: LogLevel.INFO,
|
|
310
407
|
message,
|
|
311
|
-
timestamp:
|
|
408
|
+
timestamp: new Date().toISOString(),
|
|
312
409
|
context
|
|
313
410
|
});
|
|
314
411
|
}
|
|
@@ -316,7 +413,7 @@ var LoggerUtil = class {
|
|
|
316
413
|
this.logger.log({
|
|
317
414
|
level: LogLevel.WARN,
|
|
318
415
|
message,
|
|
319
|
-
timestamp:
|
|
416
|
+
timestamp: new Date().toISOString(),
|
|
320
417
|
context
|
|
321
418
|
});
|
|
322
419
|
}
|
|
@@ -324,7 +421,7 @@ var LoggerUtil = class {
|
|
|
324
421
|
this.logger.log({
|
|
325
422
|
level: LogLevel.ERROR,
|
|
326
423
|
message,
|
|
327
|
-
timestamp:
|
|
424
|
+
timestamp: new Date().toISOString(),
|
|
328
425
|
context,
|
|
329
426
|
error
|
|
330
427
|
});
|
|
@@ -372,15 +469,22 @@ var InMemoryMetricsCollector = class {
|
|
|
372
469
|
maxDurationMs: 0
|
|
373
470
|
};
|
|
374
471
|
const successful = this.metrics.filter((m) => m.success).length;
|
|
375
|
-
|
|
376
|
-
|
|
472
|
+
let sum = 0;
|
|
473
|
+
let min = Infinity;
|
|
474
|
+
let max = -Infinity;
|
|
475
|
+
for (const m of this.metrics) {
|
|
476
|
+
const d = m.durationMs;
|
|
477
|
+
sum += d;
|
|
478
|
+
if (d < min) min = d;
|
|
479
|
+
if (d > max) max = d;
|
|
480
|
+
}
|
|
377
481
|
return {
|
|
378
482
|
total: this.metrics.length,
|
|
379
483
|
successful,
|
|
380
484
|
failed: this.metrics.length - successful,
|
|
381
485
|
avgDurationMs: sum / this.metrics.length,
|
|
382
|
-
minDurationMs:
|
|
383
|
-
maxDurationMs:
|
|
486
|
+
minDurationMs: min === Infinity ? 0 : min,
|
|
487
|
+
maxDurationMs: max === -Infinity ? 0 : max
|
|
384
488
|
};
|
|
385
489
|
}
|
|
386
490
|
/**
|
|
@@ -447,6 +551,10 @@ var HttpClient = class {
|
|
|
447
551
|
jitter: .2,
|
|
448
552
|
retryMethods: ["GET", "HEAD"]
|
|
449
553
|
};
|
|
554
|
+
if (!this.retry.retryStatusCodes) this.retry.retryStatusCodes = Object.keys(HTTPStatusCode).filter((key) => {
|
|
555
|
+
const code = HTTPStatusCode[key];
|
|
556
|
+
return typeof code === "number" && code >= 500;
|
|
557
|
+
});
|
|
450
558
|
if (this.retry.maxRetries < 0) throw new Error("retry.maxRetries must be non-negative");
|
|
451
559
|
if (this.retry.baseDelayMs < 0) throw new Error("retry.baseDelayMs must be non-negative");
|
|
452
560
|
if (this.retry.jitter !== void 0 && (this.retry.jitter < 0 || this.retry.jitter > 1)) throw new Error("retry.jitter must be between 0 and 1");
|
|
@@ -562,7 +670,8 @@ var HttpClient = class {
|
|
|
562
670
|
*/
|
|
563
671
|
async request(method, path, body, options) {
|
|
564
672
|
const startTime = Date.now();
|
|
565
|
-
|
|
673
|
+
const base = this.resolveBaseUrl(options?.baseUrlKey);
|
|
674
|
+
let url = `${base}${path}${toQueryString(options?.query)}`;
|
|
566
675
|
this.logger.debug("HTTP request initiated", {
|
|
567
676
|
method,
|
|
568
677
|
path,
|
|
@@ -591,26 +700,16 @@ var HttpClient = class {
|
|
|
591
700
|
const doFetch = async () => {
|
|
592
701
|
let timeoutId;
|
|
593
702
|
if (this.timeoutMs && !options?.signal) timeoutId = setTimeout(() => {
|
|
594
|
-
const timeoutError =
|
|
703
|
+
const timeoutError = new Error("Request timeout");
|
|
595
704
|
timeoutError.name = "TimeoutError";
|
|
596
705
|
controller.abort(timeoutError);
|
|
597
706
|
}, this.timeoutMs);
|
|
598
707
|
try {
|
|
599
708
|
const req = new Request(url, init);
|
|
600
709
|
const res = await this.fetchImpl(req);
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
text = await res.text();
|
|
605
|
-
} catch (readError) {
|
|
606
|
-
text = `Failed to read response: ${readError instanceof Error ? readError.message : String(readError)}`;
|
|
607
|
-
}
|
|
608
|
-
throw new ApiError(`HTTP ${res.status}: ${res.statusText}`, {
|
|
609
|
-
status: res.status,
|
|
610
|
-
details: text
|
|
611
|
-
});
|
|
612
|
-
}
|
|
613
|
-
const data = (res.headers.get("content-type") || "").includes("json") ? await res.json() : await res.text();
|
|
710
|
+
const status = res.status;
|
|
711
|
+
const contentType = res.headers.get("content-type") || "";
|
|
712
|
+
const data = contentType.includes("json") ? await res.json() : await res.text();
|
|
614
713
|
await this.runAfterHooks(new Request(url, init), res, data);
|
|
615
714
|
const duration = Date.now() - startTime;
|
|
616
715
|
this.logger.info("HTTP request successful", {
|
|
@@ -624,12 +723,12 @@ var HttpClient = class {
|
|
|
624
723
|
path,
|
|
625
724
|
status: res.status,
|
|
626
725
|
durationMs: duration,
|
|
627
|
-
timestamp:
|
|
726
|
+
timestamp: new Date().toISOString(),
|
|
628
727
|
success: true
|
|
629
728
|
});
|
|
630
729
|
return {
|
|
631
730
|
data,
|
|
632
|
-
|
|
731
|
+
status
|
|
633
732
|
};
|
|
634
733
|
} catch (error) {
|
|
635
734
|
const duration = Date.now() - startTime;
|
|
@@ -643,7 +742,7 @@ var HttpClient = class {
|
|
|
643
742
|
path,
|
|
644
743
|
status: error instanceof ApiError ? error.status : void 0,
|
|
645
744
|
durationMs: duration,
|
|
646
|
-
timestamp:
|
|
745
|
+
timestamp: new Date().toISOString(),
|
|
647
746
|
success: false,
|
|
648
747
|
error: error instanceof Error ? error.message : String(error)
|
|
649
748
|
});
|
|
@@ -652,13 +751,15 @@ var HttpClient = class {
|
|
|
652
751
|
if (timeoutId) clearTimeout(timeoutId);
|
|
653
752
|
}
|
|
654
753
|
};
|
|
655
|
-
const canRetry = ({
|
|
754
|
+
const canRetry = ({ error }) => {
|
|
656
755
|
if (error && typeof error === "object" && "name" in error) {
|
|
657
756
|
const errorName = error.name;
|
|
658
757
|
if (errorName === "AbortError" || errorName === "TimeoutError") return false;
|
|
659
758
|
}
|
|
660
|
-
if (error instanceof ApiError && error.status
|
|
661
|
-
|
|
759
|
+
if (error instanceof ApiError && error.status) {
|
|
760
|
+
const retryCodes = this.retry.retryStatusCodes;
|
|
761
|
+
if (retryCodes?.some((codeKey) => HTTPStatusCode[codeKey] === error.status)) return true;
|
|
762
|
+
}
|
|
662
763
|
return false;
|
|
663
764
|
};
|
|
664
765
|
if (!this.retry.retryMethods?.includes(method)) return doFetch();
|
|
@@ -719,109 +820,14 @@ var HttpClient = class {
|
|
|
719
820
|
async delete(path, options) {
|
|
720
821
|
return this.request("DELETE", path, void 0, options);
|
|
721
822
|
}
|
|
722
|
-
};
|
|
723
|
-
|
|
724
|
-
//#endregion
|
|
725
|
-
//#region lib/endpoint/base-endpoint.ts
|
|
726
|
-
/**
|
|
727
|
-
* Generic, strongly-typed endpoint with Zod schemas for request and response validation.
|
|
728
|
-
* Extend this class to create type-safe API endpoints.
|
|
729
|
-
*
|
|
730
|
-
* @template ReqSchema - Zod schema for request validation
|
|
731
|
-
* @template ResSchema - Zod schema for response validation
|
|
732
|
-
* @template PathParams - Type for path parameters (optional)
|
|
733
|
-
* @template QueryParams - Type for query parameters (optional)
|
|
734
|
-
*
|
|
735
|
-
* @example
|
|
736
|
-
* ```ts
|
|
737
|
-
* const UserSchema = z.object({ id: z.number(), name: z.string() });
|
|
738
|
-
* const CreateUserSchema = z.object({ name: z.string() });
|
|
739
|
-
*
|
|
740
|
-
* type UserPathParams = { id: string };
|
|
741
|
-
* type UserQueryParams = { include?: string; limit?: number };
|
|
742
|
-
*
|
|
743
|
-
* class GetUser extends BaseEndpoint<
|
|
744
|
-
* typeof CreateUserSchema,
|
|
745
|
-
* typeof UserSchema,
|
|
746
|
-
* UserPathParams,
|
|
747
|
-
* UserQueryParams
|
|
748
|
-
* > {
|
|
749
|
-
* protected method = 'GET' as const;
|
|
750
|
-
* protected path = (params: UserPathParams) => `/users/${params.id}`;
|
|
751
|
-
*
|
|
752
|
-
* constructor(client: HttpClient) {
|
|
753
|
-
* super(client, {
|
|
754
|
-
* requestSchema: CreateUserSchema,
|
|
755
|
-
* responseSchema: UserSchema
|
|
756
|
-
* });
|
|
757
|
-
* }
|
|
758
|
-
* }
|
|
759
|
-
*
|
|
760
|
-
* // Usage:
|
|
761
|
-
* const user = await endpoint.call({
|
|
762
|
-
* pathParams: { id: '123' },
|
|
763
|
-
* query: { include: 'posts', limit: 10 }
|
|
764
|
-
* });
|
|
765
|
-
* ```
|
|
766
|
-
*/
|
|
767
|
-
var BaseEndpoint = class {
|
|
768
|
-
/** Additional options for the request */
|
|
769
|
-
options;
|
|
770
|
-
/** Optional request schema for validation */
|
|
771
|
-
requestSchema;
|
|
772
|
-
/** Response schema for validation */
|
|
773
|
-
responseSchema;
|
|
774
|
-
/**
|
|
775
|
-
* @param client - HttpClient instance
|
|
776
|
-
* @param cfg - Configuration with request and response schemas
|
|
777
|
-
*/
|
|
778
|
-
constructor(client, cfg) {
|
|
779
|
-
this.client = client;
|
|
780
|
-
this.requestSchema = cfg.requestSchema;
|
|
781
|
-
this.responseSchema = cfg.responseSchema;
|
|
782
|
-
}
|
|
783
823
|
/**
|
|
784
|
-
*
|
|
785
|
-
* Validates request data before sending and response data after receiving.
|
|
786
|
-
*
|
|
787
|
-
* @param config - Request configuration object containing all parameters
|
|
788
|
-
* @returns Promise resolving to validated response data (typed by ResSchema)
|
|
789
|
-
* @throws {ZodError} If request validation fails
|
|
790
|
-
* @throws {ApiError} If response validation fails or request fails
|
|
824
|
+
* Create a strongly-typed endpoint builder.
|
|
791
825
|
*
|
|
792
|
-
* @
|
|
793
|
-
*
|
|
794
|
-
* const endpoint = new GetUser(client);
|
|
795
|
-
* const user = await endpoint.call({
|
|
796
|
-
* pathParams: { id: '123' },
|
|
797
|
-
* query: { include: 'posts' }
|
|
798
|
-
* });
|
|
799
|
-
* // With additional options:
|
|
800
|
-
* const user = await endpoint.call({
|
|
801
|
-
* data: { name: 'John' },
|
|
802
|
-
* pathParams: { id: '123' },
|
|
803
|
-
* headers: { 'X-Custom': 'value' },
|
|
804
|
-
* query: { include: 'posts' }
|
|
805
|
-
* });
|
|
806
|
-
* ```
|
|
826
|
+
* @param config - Endpoint configuration
|
|
827
|
+
* @returns Endpoint instance
|
|
807
828
|
*/
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
if (this.requestSchema && data !== void 0) {
|
|
811
|
-
const parsed = this.requestSchema.safeParse(data);
|
|
812
|
-
if (!parsed.success) throw parsed.error;
|
|
813
|
-
}
|
|
814
|
-
const pathArgs = pathParams ?? data;
|
|
815
|
-
const path = typeof this.path === "function" ? this.path(pathArgs) : this.path;
|
|
816
|
-
const body = this.method !== "GET" && this.method !== "HEAD" ? data : void 0;
|
|
817
|
-
const queryForRequest = query;
|
|
818
|
-
const { data: responseData } = await this.client.request(this.method, path, body, {
|
|
819
|
-
query: queryForRequest,
|
|
820
|
-
headers,
|
|
821
|
-
baseUrlKey: baseUrlKey ?? this.options?.baseUrlKey,
|
|
822
|
-
signal
|
|
823
|
-
});
|
|
824
|
-
return parseOrThrow(this.responseSchema, responseData);
|
|
829
|
+
createEndpoint(config) {
|
|
830
|
+
return new Endpoint(this, config);
|
|
825
831
|
}
|
|
826
832
|
};
|
|
827
833
|
|
|
@@ -910,5 +916,5 @@ const Envelope = (inner) => z.object({
|
|
|
910
916
|
});
|
|
911
917
|
|
|
912
918
|
//#endregion
|
|
913
|
-
export { ApiError, ApiErrorSchema, ApiKeyAuth,
|
|
919
|
+
export { ApiError, ApiErrorSchema, ApiKeyAuth, BearerTokenAuth, ConsoleLogger, ConsoleMetricsCollector, Endpoint, Envelope, ErrorDetail, HTTPMethod, HTTPStatusCode, HttpClient, Id, InMemoryMetricsCollector, LogLevel, LoggerUtil, Meta, NoAuth, NoOpLogger, NoOpMetricsCollector, PaginationSchema, SchemaDefinitionError, Timestamps, toQueryString };
|
|
914
920
|
//# sourceMappingURL=index.js.map
|