zlient 1.0.10 → 2.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/dist/auth.d.ts.map +1 -1
- package/dist/endpoint/base-endpoint.d.ts +22 -110
- package/dist/endpoint/base-endpoint.d.ts.map +1 -1
- package/dist/http/http-client.d.ts +29 -7
- package/dist/http/http-client.d.ts.map +1 -1
- package/dist/index.cjs +255 -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 +252 -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 +41 -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");
|
|
@@ -526,6 +634,23 @@ var HttpClient = class {
|
|
|
526
634
|
});
|
|
527
635
|
}
|
|
528
636
|
/**
|
|
637
|
+
* Get all configured base URLs.
|
|
638
|
+
*
|
|
639
|
+
* @returns Object mapping base URL keys to their resolved URLs
|
|
640
|
+
*/
|
|
641
|
+
getBaseUrls() {
|
|
642
|
+
return this.baseUrls;
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Get the resolved base URL for a given key.
|
|
646
|
+
*
|
|
647
|
+
* @param key - Base URL key (defaults to 'default' if not provided)
|
|
648
|
+
* @returns Resolved base URL string
|
|
649
|
+
*/
|
|
650
|
+
getBaseUrl(key) {
|
|
651
|
+
return this.resolveBaseUrl(key);
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
529
654
|
* Make an HTTP request with automatic retry, authentication, and validation.
|
|
530
655
|
*
|
|
531
656
|
* @param method - HTTP method (GET, POST, PUT, etc.)
|
|
@@ -545,7 +670,8 @@ var HttpClient = class {
|
|
|
545
670
|
*/
|
|
546
671
|
async request(method, path, body, options) {
|
|
547
672
|
const startTime = Date.now();
|
|
548
|
-
|
|
673
|
+
const base = this.resolveBaseUrl(options?.baseUrlKey);
|
|
674
|
+
let url = `${base}${path}${toQueryString(options?.query)}`;
|
|
549
675
|
this.logger.debug("HTTP request initiated", {
|
|
550
676
|
method,
|
|
551
677
|
path,
|
|
@@ -574,26 +700,16 @@ var HttpClient = class {
|
|
|
574
700
|
const doFetch = async () => {
|
|
575
701
|
let timeoutId;
|
|
576
702
|
if (this.timeoutMs && !options?.signal) timeoutId = setTimeout(() => {
|
|
577
|
-
const timeoutError =
|
|
703
|
+
const timeoutError = new Error("Request timeout");
|
|
578
704
|
timeoutError.name = "TimeoutError";
|
|
579
705
|
controller.abort(timeoutError);
|
|
580
706
|
}, this.timeoutMs);
|
|
581
707
|
try {
|
|
582
708
|
const req = new Request(url, init);
|
|
583
709
|
const res = await this.fetchImpl(req);
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
text = await res.text();
|
|
588
|
-
} catch (readError) {
|
|
589
|
-
text = `Failed to read response: ${readError instanceof Error ? readError.message : String(readError)}`;
|
|
590
|
-
}
|
|
591
|
-
throw new ApiError(`HTTP ${res.status}: ${res.statusText}`, {
|
|
592
|
-
status: res.status,
|
|
593
|
-
details: text
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
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();
|
|
597
713
|
await this.runAfterHooks(new Request(url, init), res, data);
|
|
598
714
|
const duration = Date.now() - startTime;
|
|
599
715
|
this.logger.info("HTTP request successful", {
|
|
@@ -607,12 +723,12 @@ var HttpClient = class {
|
|
|
607
723
|
path,
|
|
608
724
|
status: res.status,
|
|
609
725
|
durationMs: duration,
|
|
610
|
-
timestamp:
|
|
726
|
+
timestamp: new Date().toISOString(),
|
|
611
727
|
success: true
|
|
612
728
|
});
|
|
613
729
|
return {
|
|
614
730
|
data,
|
|
615
|
-
|
|
731
|
+
status
|
|
616
732
|
};
|
|
617
733
|
} catch (error) {
|
|
618
734
|
const duration = Date.now() - startTime;
|
|
@@ -626,7 +742,7 @@ var HttpClient = class {
|
|
|
626
742
|
path,
|
|
627
743
|
status: error instanceof ApiError ? error.status : void 0,
|
|
628
744
|
durationMs: duration,
|
|
629
|
-
timestamp:
|
|
745
|
+
timestamp: new Date().toISOString(),
|
|
630
746
|
success: false,
|
|
631
747
|
error: error instanceof Error ? error.message : String(error)
|
|
632
748
|
});
|
|
@@ -635,13 +751,15 @@ var HttpClient = class {
|
|
|
635
751
|
if (timeoutId) clearTimeout(timeoutId);
|
|
636
752
|
}
|
|
637
753
|
};
|
|
638
|
-
const canRetry = ({
|
|
754
|
+
const canRetry = ({ error }) => {
|
|
639
755
|
if (error && typeof error === "object" && "name" in error) {
|
|
640
756
|
const errorName = error.name;
|
|
641
757
|
if (errorName === "AbortError" || errorName === "TimeoutError") return false;
|
|
642
758
|
}
|
|
643
|
-
if (error instanceof ApiError && error.status
|
|
644
|
-
|
|
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
|
+
}
|
|
645
763
|
return false;
|
|
646
764
|
};
|
|
647
765
|
if (!this.retry.retryMethods?.includes(method)) return doFetch();
|
|
@@ -702,109 +820,14 @@ var HttpClient = class {
|
|
|
702
820
|
async delete(path, options) {
|
|
703
821
|
return this.request("DELETE", path, void 0, options);
|
|
704
822
|
}
|
|
705
|
-
};
|
|
706
|
-
|
|
707
|
-
//#endregion
|
|
708
|
-
//#region lib/endpoint/base-endpoint.ts
|
|
709
|
-
/**
|
|
710
|
-
* Generic, strongly-typed endpoint with Zod schemas for request and response validation.
|
|
711
|
-
* Extend this class to create type-safe API endpoints.
|
|
712
|
-
*
|
|
713
|
-
* @template ReqSchema - Zod schema for request validation
|
|
714
|
-
* @template ResSchema - Zod schema for response validation
|
|
715
|
-
* @template PathParams - Type for path parameters (optional)
|
|
716
|
-
* @template QueryParams - Type for query parameters (optional)
|
|
717
|
-
*
|
|
718
|
-
* @example
|
|
719
|
-
* ```ts
|
|
720
|
-
* const UserSchema = z.object({ id: z.number(), name: z.string() });
|
|
721
|
-
* const CreateUserSchema = z.object({ name: z.string() });
|
|
722
|
-
*
|
|
723
|
-
* type UserPathParams = { id: string };
|
|
724
|
-
* type UserQueryParams = { include?: string; limit?: number };
|
|
725
|
-
*
|
|
726
|
-
* class GetUser extends BaseEndpoint<
|
|
727
|
-
* typeof CreateUserSchema,
|
|
728
|
-
* typeof UserSchema,
|
|
729
|
-
* UserPathParams,
|
|
730
|
-
* UserQueryParams
|
|
731
|
-
* > {
|
|
732
|
-
* protected method = 'GET' as const;
|
|
733
|
-
* protected path = (params: UserPathParams) => `/users/${params.id}`;
|
|
734
|
-
*
|
|
735
|
-
* constructor(client: HttpClient) {
|
|
736
|
-
* super(client, {
|
|
737
|
-
* requestSchema: CreateUserSchema,
|
|
738
|
-
* responseSchema: UserSchema
|
|
739
|
-
* });
|
|
740
|
-
* }
|
|
741
|
-
* }
|
|
742
|
-
*
|
|
743
|
-
* // Usage:
|
|
744
|
-
* const user = await endpoint.call({
|
|
745
|
-
* pathParams: { id: '123' },
|
|
746
|
-
* query: { include: 'posts', limit: 10 }
|
|
747
|
-
* });
|
|
748
|
-
* ```
|
|
749
|
-
*/
|
|
750
|
-
var BaseEndpoint = class {
|
|
751
|
-
/** Additional options for the request */
|
|
752
|
-
options;
|
|
753
|
-
/** Optional request schema for validation */
|
|
754
|
-
requestSchema;
|
|
755
|
-
/** Response schema for validation */
|
|
756
|
-
responseSchema;
|
|
757
823
|
/**
|
|
758
|
-
*
|
|
759
|
-
* @param cfg - Configuration with request and response schemas
|
|
760
|
-
*/
|
|
761
|
-
constructor(client, cfg) {
|
|
762
|
-
this.client = client;
|
|
763
|
-
this.requestSchema = cfg.requestSchema;
|
|
764
|
-
this.responseSchema = cfg.responseSchema;
|
|
765
|
-
}
|
|
766
|
-
/**
|
|
767
|
-
* Call the endpoint with strong typing derived from schemas.
|
|
768
|
-
* Validates request data before sending and response data after receiving.
|
|
769
|
-
*
|
|
770
|
-
* @param config - Request configuration object containing all parameters
|
|
771
|
-
* @returns Promise resolving to validated response data (typed by ResSchema)
|
|
772
|
-
* @throws {ZodError} If request validation fails
|
|
773
|
-
* @throws {ApiError} If response validation fails or request fails
|
|
824
|
+
* Create a strongly-typed endpoint builder.
|
|
774
825
|
*
|
|
775
|
-
* @
|
|
776
|
-
*
|
|
777
|
-
* const endpoint = new GetUser(client);
|
|
778
|
-
* const user = await endpoint.call({
|
|
779
|
-
* pathParams: { id: '123' },
|
|
780
|
-
* query: { include: 'posts' }
|
|
781
|
-
* });
|
|
782
|
-
* // With additional options:
|
|
783
|
-
* const user = await endpoint.call({
|
|
784
|
-
* data: { name: 'John' },
|
|
785
|
-
* pathParams: { id: '123' },
|
|
786
|
-
* headers: { 'X-Custom': 'value' },
|
|
787
|
-
* query: { include: 'posts' }
|
|
788
|
-
* });
|
|
789
|
-
* ```
|
|
826
|
+
* @param config - Endpoint configuration
|
|
827
|
+
* @returns Endpoint instance
|
|
790
828
|
*/
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
if (this.requestSchema && data !== void 0) {
|
|
794
|
-
const parsed = this.requestSchema.safeParse(data);
|
|
795
|
-
if (!parsed.success) throw parsed.error;
|
|
796
|
-
}
|
|
797
|
-
const pathArgs = pathParams ?? data;
|
|
798
|
-
const path = typeof this.path === "function" ? this.path(pathArgs) : this.path;
|
|
799
|
-
const body = this.method !== "GET" && this.method !== "HEAD" ? data : void 0;
|
|
800
|
-
const queryForRequest = query;
|
|
801
|
-
const { data: responseData } = await this.client.request(this.method, path, body, {
|
|
802
|
-
query: queryForRequest,
|
|
803
|
-
headers,
|
|
804
|
-
baseUrlKey: baseUrlKey ?? this.options?.baseUrlKey,
|
|
805
|
-
signal
|
|
806
|
-
});
|
|
807
|
-
return parseOrThrow(this.responseSchema, responseData);
|
|
829
|
+
createEndpoint(config) {
|
|
830
|
+
return new Endpoint(this, config);
|
|
808
831
|
}
|
|
809
832
|
};
|
|
810
833
|
|
|
@@ -893,5 +916,5 @@ const Envelope = (inner) => z.object({
|
|
|
893
916
|
});
|
|
894
917
|
|
|
895
918
|
//#endregion
|
|
896
|
-
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 };
|
|
897
920
|
//# sourceMappingURL=index.js.map
|