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.cjs
CHANGED
|
@@ -21,9 +21,83 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
}) : target, mod));
|
|
22
22
|
|
|
23
23
|
//#endregion
|
|
24
|
-
|
|
25
|
-
zod = __toESM(zod);
|
|
24
|
+
const zod = __toESM(require("zod"));
|
|
26
25
|
|
|
26
|
+
//#region lib/auth.ts
|
|
27
|
+
/**
|
|
28
|
+
* No-op authentication provider (no authentication applied).
|
|
29
|
+
* Use this when you don't need authentication.
|
|
30
|
+
*/
|
|
31
|
+
var NoAuth = class {
|
|
32
|
+
async apply() {}
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* API Key authentication provider.
|
|
36
|
+
* Supports both header-based and query parameter-based authentication.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* // Header-based
|
|
41
|
+
* const auth = new ApiKeyAuth({ header: 'X-API-Key', value: 'secret' });
|
|
42
|
+
*
|
|
43
|
+
* // Query parameter-based
|
|
44
|
+
* const auth = new ApiKeyAuth({ query: 'apiKey', value: 'secret' });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
var ApiKeyAuth = class {
|
|
48
|
+
constructor(opts) {
|
|
49
|
+
this.opts = opts;
|
|
50
|
+
if (!opts.header && !opts.query) throw new Error("ApiKeyAuth requires either \"header\" or \"query\" option");
|
|
51
|
+
if (opts.header && opts.query) throw new Error("ApiKeyAuth cannot use both \"header\" and \"query\" options");
|
|
52
|
+
}
|
|
53
|
+
apply({ url, init }) {
|
|
54
|
+
const value = this.opts.value;
|
|
55
|
+
if (this.opts.header) if (init.headers instanceof Headers) init.headers.set(this.opts.header, value);
|
|
56
|
+
else if (Array.isArray(init.headers)) init.headers.push([this.opts.header, value]);
|
|
57
|
+
else init.headers = {
|
|
58
|
+
...init.headers,
|
|
59
|
+
[this.opts.header]: value
|
|
60
|
+
};
|
|
61
|
+
else if (this.opts.query) {
|
|
62
|
+
const u = new URL(url);
|
|
63
|
+
u.searchParams.set(this.opts.query, value);
|
|
64
|
+
init.__urlOverride = u.toString();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Bearer token authentication provider.
|
|
70
|
+
* Supports both static tokens and dynamic token fetching (e.g., for OAuth2 refresh).
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* // Static token
|
|
75
|
+
* const auth = new BearerTokenAuth(() => 'my-token');
|
|
76
|
+
*
|
|
77
|
+
* // Dynamic token with refresh
|
|
78
|
+
* const auth = new BearerTokenAuth(async () => {
|
|
79
|
+
* return await refreshAccessToken();
|
|
80
|
+
* });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
var BearerTokenAuth = class {
|
|
84
|
+
constructor(getToken) {
|
|
85
|
+
this.getToken = getToken;
|
|
86
|
+
}
|
|
87
|
+
async apply({ init }) {
|
|
88
|
+
const token = await this.getToken();
|
|
89
|
+
if (!token) throw new Error("BearerTokenAuth: token is empty or undefined");
|
|
90
|
+
const authHeader = `Bearer ${token}`;
|
|
91
|
+
if (init.headers instanceof Headers) init.headers.set("Authorization", authHeader);
|
|
92
|
+
else if (Array.isArray(init.headers)) init.headers.push(["Authorization", authHeader]);
|
|
93
|
+
else init.headers = {
|
|
94
|
+
...init.headers,
|
|
95
|
+
Authorization: authHeader
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
//#endregion
|
|
27
101
|
//#region lib/types.ts
|
|
28
102
|
const HTTPMethod = {
|
|
29
103
|
GET: "GET",
|
|
@@ -34,6 +108,70 @@ const HTTPMethod = {
|
|
|
34
108
|
HEAD: "HEAD",
|
|
35
109
|
OPTIONS: "OPTIONS"
|
|
36
110
|
};
|
|
111
|
+
const HTTPStatusCode = {
|
|
112
|
+
CONTINUE: 100,
|
|
113
|
+
SWITCHING_PROTOCOLS: 101,
|
|
114
|
+
PROCESSING: 102,
|
|
115
|
+
EARLY_HINTS: 103,
|
|
116
|
+
OK: 200,
|
|
117
|
+
CREATED: 201,
|
|
118
|
+
ACCEPTED: 202,
|
|
119
|
+
NON_AUTHORITATIVE_INFORMATION: 203,
|
|
120
|
+
NO_CONTENT: 204,
|
|
121
|
+
RESET_CONTENT: 205,
|
|
122
|
+
PARTIAL_CONTENT: 206,
|
|
123
|
+
MULTI_STATUS: 207,
|
|
124
|
+
ALREADY_REPORTED: 208,
|
|
125
|
+
IM_USED: 226,
|
|
126
|
+
MULTIPLE_CHOICES: 300,
|
|
127
|
+
MOVED_PERMANENTLY: 301,
|
|
128
|
+
FOUND: 302,
|
|
129
|
+
SEE_OTHER: 303,
|
|
130
|
+
NOT_MODIFIED: 304,
|
|
131
|
+
USE_PROXY: 305,
|
|
132
|
+
TEMPORARY_REDIRECT: 307,
|
|
133
|
+
PERMANENT_REDIRECT: 308,
|
|
134
|
+
BAD_REQUEST: 400,
|
|
135
|
+
UNAUTHORIZED: 401,
|
|
136
|
+
PAYMENT_REQUIRED: 402,
|
|
137
|
+
FORBIDDEN: 403,
|
|
138
|
+
NOT_FOUND: 404,
|
|
139
|
+
METHOD_NOT_ALLOWED: 405,
|
|
140
|
+
NOT_ACCEPTABLE: 406,
|
|
141
|
+
PROXY_AUTHENTICATION_REQUIRED: 407,
|
|
142
|
+
REQUEST_TIMEOUT: 408,
|
|
143
|
+
CONFLICT: 409,
|
|
144
|
+
GONE: 410,
|
|
145
|
+
LENGTH_REQUIRED: 411,
|
|
146
|
+
PRECONDITION_FAILED: 412,
|
|
147
|
+
PAYLOAD_TOO_LARGE: 413,
|
|
148
|
+
URI_TOO_LONG: 414,
|
|
149
|
+
UNSUPPORTED_MEDIA_TYPE: 415,
|
|
150
|
+
RANGE_NOT_SATISFIABLE: 416,
|
|
151
|
+
EXPECTATION_FAILED: 417,
|
|
152
|
+
IM_A_TEAPOT: 418,
|
|
153
|
+
MISDIRECTED_REQUEST: 421,
|
|
154
|
+
UNPROCESSABLE_ENTITY: 422,
|
|
155
|
+
LOCKED: 423,
|
|
156
|
+
FAILED_DEPENDENCY: 424,
|
|
157
|
+
TOO_EARLY: 425,
|
|
158
|
+
UPGRADE_REQUIRED: 426,
|
|
159
|
+
PRECONDITION_REQUIRED: 428,
|
|
160
|
+
TOO_MANY_REQUESTS: 429,
|
|
161
|
+
REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
|
|
162
|
+
UNAVAILABLE_FOR_LEGAL_REASONS: 451,
|
|
163
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
164
|
+
NOT_IMPLEMENTED: 501,
|
|
165
|
+
BAD_GATEWAY: 502,
|
|
166
|
+
SERVICE_UNAVAILABLE: 503,
|
|
167
|
+
GATEWAY_TIMEOUT: 504,
|
|
168
|
+
HTTP_VERSION_NOT_SUPPORTED: 505,
|
|
169
|
+
VARIANT_ALSO_NEGOTIATES: 506,
|
|
170
|
+
INSUFFICIENT_STORAGE: 507,
|
|
171
|
+
LOOP_DETECTED: 508,
|
|
172
|
+
NOT_EXTENDED: 510,
|
|
173
|
+
NETWORK_AUTHENTICATION_REQUIRED: 511
|
|
174
|
+
};
|
|
37
175
|
/**
|
|
38
176
|
* Custom error class for API-related errors.
|
|
39
177
|
* Includes HTTP status codes, response details, and validation errors.
|
|
@@ -92,6 +230,18 @@ var ApiError = class ApiError extends Error {
|
|
|
92
230
|
}
|
|
93
231
|
};
|
|
94
232
|
/**
|
|
233
|
+
* Error thrown when an endpoint receives a response with a status code
|
|
234
|
+
* that has no defined schema in the endpoint configuration.
|
|
235
|
+
*/
|
|
236
|
+
var SchemaDefinitionError = class SchemaDefinitionError extends Error {
|
|
237
|
+
constructor(status) {
|
|
238
|
+
super(`No schema defined for status code ${status}`);
|
|
239
|
+
this.status = status;
|
|
240
|
+
this.name = "SchemaDefinitionError";
|
|
241
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, SchemaDefinitionError);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
95
245
|
* Schema for paginated responses
|
|
96
246
|
*/
|
|
97
247
|
const PaginationSchema = zod.z.object({
|
|
@@ -127,107 +277,9 @@ function toQueryString(q) {
|
|
|
127
277
|
return s ? `?${s}` : "";
|
|
128
278
|
}
|
|
129
279
|
|
|
130
|
-
//#endregion
|
|
131
|
-
//#region lib/auth.ts
|
|
132
|
-
/**
|
|
133
|
-
* No-op authentication provider (no authentication applied).
|
|
134
|
-
* Use this when you don't need authentication.
|
|
135
|
-
*/
|
|
136
|
-
var NoAuth = class {
|
|
137
|
-
async apply() {}
|
|
138
|
-
};
|
|
139
|
-
/**
|
|
140
|
-
* API Key authentication provider.
|
|
141
|
-
* Supports both header-based and query parameter-based authentication.
|
|
142
|
-
*
|
|
143
|
-
* @example
|
|
144
|
-
* ```ts
|
|
145
|
-
* // Header-based
|
|
146
|
-
* const auth = new ApiKeyAuth({ header: 'X-API-Key', value: 'secret' });
|
|
147
|
-
*
|
|
148
|
-
* // Query parameter-based
|
|
149
|
-
* const auth = new ApiKeyAuth({ query: 'apiKey', value: 'secret' });
|
|
150
|
-
* ```
|
|
151
|
-
*/
|
|
152
|
-
var ApiKeyAuth = class {
|
|
153
|
-
constructor(opts) {
|
|
154
|
-
this.opts = opts;
|
|
155
|
-
if (!opts.header && !opts.query) throw new Error("ApiKeyAuth requires either \"header\" or \"query\" option");
|
|
156
|
-
if (opts.header && opts.query) throw new Error("ApiKeyAuth cannot use both \"header\" and \"query\" options");
|
|
157
|
-
}
|
|
158
|
-
apply({ url, init }) {
|
|
159
|
-
if (this.opts.header) init.headers = {
|
|
160
|
-
...init.headers,
|
|
161
|
-
[this.opts.header]: this.opts.value
|
|
162
|
-
};
|
|
163
|
-
else if (this.opts.query) {
|
|
164
|
-
const u = new URL(url);
|
|
165
|
-
u.searchParams.set(this.opts.query, this.opts.value);
|
|
166
|
-
init.__urlOverride = u.toString();
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
/**
|
|
171
|
-
* Bearer token authentication provider.
|
|
172
|
-
* Supports both static tokens and dynamic token fetching (e.g., for OAuth2 refresh).
|
|
173
|
-
*
|
|
174
|
-
* @example
|
|
175
|
-
* ```ts
|
|
176
|
-
* // Static token
|
|
177
|
-
* const auth = new BearerTokenAuth(() => 'my-token');
|
|
178
|
-
*
|
|
179
|
-
* // Dynamic token with refresh
|
|
180
|
-
* const auth = new BearerTokenAuth(async () => {
|
|
181
|
-
* return await refreshAccessToken();
|
|
182
|
-
* });
|
|
183
|
-
* ```
|
|
184
|
-
*/
|
|
185
|
-
var BearerTokenAuth = class {
|
|
186
|
-
constructor(getToken) {
|
|
187
|
-
this.getToken = getToken;
|
|
188
|
-
}
|
|
189
|
-
async apply({ init }) {
|
|
190
|
-
const token = await this.getToken();
|
|
191
|
-
if (!token) throw new Error("BearerTokenAuth: token is empty or undefined");
|
|
192
|
-
init.headers = {
|
|
193
|
-
...init.headers,
|
|
194
|
-
Authorization: `Bearer ${token}`
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
|
|
199
280
|
//#endregion
|
|
200
281
|
//#region lib/validation.ts
|
|
201
282
|
/**
|
|
202
|
-
* Safely parse data with a Zod schema without throwing.
|
|
203
|
-
* Returns a result object with success status and data or error.
|
|
204
|
-
*
|
|
205
|
-
* @param schema - Zod schema to validate against
|
|
206
|
-
* @param data - Data to validate
|
|
207
|
-
* @returns Result object with success flag and data/error
|
|
208
|
-
*
|
|
209
|
-
* @example
|
|
210
|
-
* ```ts
|
|
211
|
-
* const result = safeParse(UserSchema, userData);
|
|
212
|
-
* if (result.success) {
|
|
213
|
-
* console.log(result.data);
|
|
214
|
-
* } else {
|
|
215
|
-
* console.error(result.error);
|
|
216
|
-
* }
|
|
217
|
-
* ```
|
|
218
|
-
*/
|
|
219
|
-
function safeParse(schema, data) {
|
|
220
|
-
const res = schema.safeParse(data);
|
|
221
|
-
if (res.success) return {
|
|
222
|
-
success: true,
|
|
223
|
-
data: res.data
|
|
224
|
-
};
|
|
225
|
-
return {
|
|
226
|
-
success: false,
|
|
227
|
-
error: res.error
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
283
|
* Parse data with a Zod schema, throwing an ApiError on validation failure.
|
|
232
284
|
* Use this when you want to fail fast on invalid data.
|
|
233
285
|
*
|
|
@@ -254,6 +306,48 @@ function parseOrThrow(schema, data) {
|
|
|
254
306
|
return res.data;
|
|
255
307
|
}
|
|
256
308
|
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region lib/endpoint/base-endpoint.ts
|
|
311
|
+
var Endpoint = class {
|
|
312
|
+
constructor(client, config) {
|
|
313
|
+
this.client = client;
|
|
314
|
+
this.config = config;
|
|
315
|
+
}
|
|
316
|
+
async call(params) {
|
|
317
|
+
const { data, query, pathParams, headers, signal } = params;
|
|
318
|
+
if (this.config.request && data !== void 0) {
|
|
319
|
+
const parsed = this.config.request.safeParse(data);
|
|
320
|
+
if (!parsed.success) throw parsed.error;
|
|
321
|
+
}
|
|
322
|
+
if (this.config.query && query !== void 0) {
|
|
323
|
+
const parsed = this.config.query.safeParse(query);
|
|
324
|
+
if (!parsed.success) throw parsed.error;
|
|
325
|
+
}
|
|
326
|
+
if (this.config.pathParams && pathParams !== void 0) {
|
|
327
|
+
const parsed = this.config.pathParams.safeParse(pathParams);
|
|
328
|
+
if (!parsed.success) throw parsed.error;
|
|
329
|
+
}
|
|
330
|
+
if (this.config.request && data === void 0) throw new Error("Missing required request body (data)");
|
|
331
|
+
if (this.config.pathParams && pathParams === void 0) throw new Error("Missing required path parameters (pathParams)");
|
|
332
|
+
let pathStr;
|
|
333
|
+
if (typeof this.config.path === "function") {
|
|
334
|
+
if (!pathParams) throw new Error("Path function requires pathParams");
|
|
335
|
+
pathStr = this.config.path(pathParams);
|
|
336
|
+
} else pathStr = this.config.path;
|
|
337
|
+
const { data: responseData, status } = await this.client.request(this.config.method, pathStr, data, {
|
|
338
|
+
query,
|
|
339
|
+
headers,
|
|
340
|
+
baseUrlKey: this.config.baseUrlKey,
|
|
341
|
+
signal
|
|
342
|
+
});
|
|
343
|
+
const schema = this.config.response;
|
|
344
|
+
if (schema instanceof zod.z.ZodType) return parseOrThrow(schema, responseData);
|
|
345
|
+
const specificSchema = schema[status];
|
|
346
|
+
if (!specificSchema) throw new SchemaDefinitionError(status);
|
|
347
|
+
return parseOrThrow(specificSchema, responseData);
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
257
351
|
//#endregion
|
|
258
352
|
//#region lib/logger.ts
|
|
259
353
|
/**
|
|
@@ -281,7 +375,9 @@ var ConsoleLogger = class {
|
|
|
281
375
|
LogLevel.WARN,
|
|
282
376
|
LogLevel.ERROR
|
|
283
377
|
];
|
|
284
|
-
|
|
378
|
+
const entryLevelIndex = levels.indexOf(entry.level);
|
|
379
|
+
const minLevelIndex = levels.indexOf(this.minLevel);
|
|
380
|
+
if (entryLevelIndex < minLevelIndex) return;
|
|
285
381
|
const output = {
|
|
286
382
|
...entry,
|
|
287
383
|
error: entry.error ? {
|
|
@@ -324,7 +420,7 @@ var LoggerUtil = class {
|
|
|
324
420
|
this.logger.log({
|
|
325
421
|
level: LogLevel.DEBUG,
|
|
326
422
|
message,
|
|
327
|
-
timestamp:
|
|
423
|
+
timestamp: new Date().toISOString(),
|
|
328
424
|
context
|
|
329
425
|
});
|
|
330
426
|
}
|
|
@@ -332,7 +428,7 @@ var LoggerUtil = class {
|
|
|
332
428
|
this.logger.log({
|
|
333
429
|
level: LogLevel.INFO,
|
|
334
430
|
message,
|
|
335
|
-
timestamp:
|
|
431
|
+
timestamp: new Date().toISOString(),
|
|
336
432
|
context
|
|
337
433
|
});
|
|
338
434
|
}
|
|
@@ -340,7 +436,7 @@ var LoggerUtil = class {
|
|
|
340
436
|
this.logger.log({
|
|
341
437
|
level: LogLevel.WARN,
|
|
342
438
|
message,
|
|
343
|
-
timestamp:
|
|
439
|
+
timestamp: new Date().toISOString(),
|
|
344
440
|
context
|
|
345
441
|
});
|
|
346
442
|
}
|
|
@@ -348,7 +444,7 @@ var LoggerUtil = class {
|
|
|
348
444
|
this.logger.log({
|
|
349
445
|
level: LogLevel.ERROR,
|
|
350
446
|
message,
|
|
351
|
-
timestamp:
|
|
447
|
+
timestamp: new Date().toISOString(),
|
|
352
448
|
context,
|
|
353
449
|
error
|
|
354
450
|
});
|
|
@@ -396,15 +492,22 @@ var InMemoryMetricsCollector = class {
|
|
|
396
492
|
maxDurationMs: 0
|
|
397
493
|
};
|
|
398
494
|
const successful = this.metrics.filter((m) => m.success).length;
|
|
399
|
-
|
|
400
|
-
|
|
495
|
+
let sum = 0;
|
|
496
|
+
let min = Infinity;
|
|
497
|
+
let max = -Infinity;
|
|
498
|
+
for (const m of this.metrics) {
|
|
499
|
+
const d = m.durationMs;
|
|
500
|
+
sum += d;
|
|
501
|
+
if (d < min) min = d;
|
|
502
|
+
if (d > max) max = d;
|
|
503
|
+
}
|
|
401
504
|
return {
|
|
402
505
|
total: this.metrics.length,
|
|
403
506
|
successful,
|
|
404
507
|
failed: this.metrics.length - successful,
|
|
405
508
|
avgDurationMs: sum / this.metrics.length,
|
|
406
|
-
minDurationMs:
|
|
407
|
-
maxDurationMs:
|
|
509
|
+
minDurationMs: min === Infinity ? 0 : min,
|
|
510
|
+
maxDurationMs: max === -Infinity ? 0 : max
|
|
408
511
|
};
|
|
409
512
|
}
|
|
410
513
|
/**
|
|
@@ -471,6 +574,10 @@ var HttpClient = class {
|
|
|
471
574
|
jitter: .2,
|
|
472
575
|
retryMethods: ["GET", "HEAD"]
|
|
473
576
|
};
|
|
577
|
+
if (!this.retry.retryStatusCodes) this.retry.retryStatusCodes = Object.keys(HTTPStatusCode).filter((key) => {
|
|
578
|
+
const code = HTTPStatusCode[key];
|
|
579
|
+
return typeof code === "number" && code >= 500;
|
|
580
|
+
});
|
|
474
581
|
if (this.retry.maxRetries < 0) throw new Error("retry.maxRetries must be non-negative");
|
|
475
582
|
if (this.retry.baseDelayMs < 0) throw new Error("retry.baseDelayMs must be non-negative");
|
|
476
583
|
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");
|
|
@@ -550,6 +657,23 @@ var HttpClient = class {
|
|
|
550
657
|
});
|
|
551
658
|
}
|
|
552
659
|
/**
|
|
660
|
+
* Get all configured base URLs.
|
|
661
|
+
*
|
|
662
|
+
* @returns Object mapping base URL keys to their resolved URLs
|
|
663
|
+
*/
|
|
664
|
+
getBaseUrls() {
|
|
665
|
+
return this.baseUrls;
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Get the resolved base URL for a given key.
|
|
669
|
+
*
|
|
670
|
+
* @param key - Base URL key (defaults to 'default' if not provided)
|
|
671
|
+
* @returns Resolved base URL string
|
|
672
|
+
*/
|
|
673
|
+
getBaseUrl(key) {
|
|
674
|
+
return this.resolveBaseUrl(key);
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
553
677
|
* Make an HTTP request with automatic retry, authentication, and validation.
|
|
554
678
|
*
|
|
555
679
|
* @param method - HTTP method (GET, POST, PUT, etc.)
|
|
@@ -569,7 +693,8 @@ var HttpClient = class {
|
|
|
569
693
|
*/
|
|
570
694
|
async request(method, path, body, options) {
|
|
571
695
|
const startTime = Date.now();
|
|
572
|
-
|
|
696
|
+
const base = this.resolveBaseUrl(options?.baseUrlKey);
|
|
697
|
+
let url = `${base}${path}${toQueryString(options?.query)}`;
|
|
573
698
|
this.logger.debug("HTTP request initiated", {
|
|
574
699
|
method,
|
|
575
700
|
path,
|
|
@@ -598,26 +723,16 @@ var HttpClient = class {
|
|
|
598
723
|
const doFetch = async () => {
|
|
599
724
|
let timeoutId;
|
|
600
725
|
if (this.timeoutMs && !options?.signal) timeoutId = setTimeout(() => {
|
|
601
|
-
const timeoutError =
|
|
726
|
+
const timeoutError = new Error("Request timeout");
|
|
602
727
|
timeoutError.name = "TimeoutError";
|
|
603
728
|
controller.abort(timeoutError);
|
|
604
729
|
}, this.timeoutMs);
|
|
605
730
|
try {
|
|
606
731
|
const req = new Request(url, init);
|
|
607
732
|
const res = await this.fetchImpl(req);
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
text = await res.text();
|
|
612
|
-
} catch (readError) {
|
|
613
|
-
text = `Failed to read response: ${readError instanceof Error ? readError.message : String(readError)}`;
|
|
614
|
-
}
|
|
615
|
-
throw new ApiError(`HTTP ${res.status}: ${res.statusText}`, {
|
|
616
|
-
status: res.status,
|
|
617
|
-
details: text
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
const data = (res.headers.get("content-type") || "").includes("json") ? await res.json() : await res.text();
|
|
733
|
+
const status = res.status;
|
|
734
|
+
const contentType = res.headers.get("content-type") || "";
|
|
735
|
+
const data = contentType.includes("json") ? await res.json() : await res.text();
|
|
621
736
|
await this.runAfterHooks(new Request(url, init), res, data);
|
|
622
737
|
const duration = Date.now() - startTime;
|
|
623
738
|
this.logger.info("HTTP request successful", {
|
|
@@ -631,12 +746,12 @@ var HttpClient = class {
|
|
|
631
746
|
path,
|
|
632
747
|
status: res.status,
|
|
633
748
|
durationMs: duration,
|
|
634
|
-
timestamp:
|
|
749
|
+
timestamp: new Date().toISOString(),
|
|
635
750
|
success: true
|
|
636
751
|
});
|
|
637
752
|
return {
|
|
638
753
|
data,
|
|
639
|
-
|
|
754
|
+
status
|
|
640
755
|
};
|
|
641
756
|
} catch (error) {
|
|
642
757
|
const duration = Date.now() - startTime;
|
|
@@ -650,7 +765,7 @@ var HttpClient = class {
|
|
|
650
765
|
path,
|
|
651
766
|
status: error instanceof ApiError ? error.status : void 0,
|
|
652
767
|
durationMs: duration,
|
|
653
|
-
timestamp:
|
|
768
|
+
timestamp: new Date().toISOString(),
|
|
654
769
|
success: false,
|
|
655
770
|
error: error instanceof Error ? error.message : String(error)
|
|
656
771
|
});
|
|
@@ -659,13 +774,15 @@ var HttpClient = class {
|
|
|
659
774
|
if (timeoutId) clearTimeout(timeoutId);
|
|
660
775
|
}
|
|
661
776
|
};
|
|
662
|
-
const canRetry = ({
|
|
777
|
+
const canRetry = ({ error }) => {
|
|
663
778
|
if (error && typeof error === "object" && "name" in error) {
|
|
664
779
|
const errorName = error.name;
|
|
665
780
|
if (errorName === "AbortError" || errorName === "TimeoutError") return false;
|
|
666
781
|
}
|
|
667
|
-
if (error instanceof ApiError && error.status
|
|
668
|
-
|
|
782
|
+
if (error instanceof ApiError && error.status) {
|
|
783
|
+
const retryCodes = this.retry.retryStatusCodes;
|
|
784
|
+
if (retryCodes?.some((codeKey) => HTTPStatusCode[codeKey] === error.status)) return true;
|
|
785
|
+
}
|
|
669
786
|
return false;
|
|
670
787
|
};
|
|
671
788
|
if (!this.retry.retryMethods?.includes(method)) return doFetch();
|
|
@@ -726,109 +843,14 @@ var HttpClient = class {
|
|
|
726
843
|
async delete(path, options) {
|
|
727
844
|
return this.request("DELETE", path, void 0, options);
|
|
728
845
|
}
|
|
729
|
-
};
|
|
730
|
-
|
|
731
|
-
//#endregion
|
|
732
|
-
//#region lib/endpoint/base-endpoint.ts
|
|
733
|
-
/**
|
|
734
|
-
* Generic, strongly-typed endpoint with Zod schemas for request and response validation.
|
|
735
|
-
* Extend this class to create type-safe API endpoints.
|
|
736
|
-
*
|
|
737
|
-
* @template ReqSchema - Zod schema for request validation
|
|
738
|
-
* @template ResSchema - Zod schema for response validation
|
|
739
|
-
* @template PathParams - Type for path parameters (optional)
|
|
740
|
-
* @template QueryParams - Type for query parameters (optional)
|
|
741
|
-
*
|
|
742
|
-
* @example
|
|
743
|
-
* ```ts
|
|
744
|
-
* const UserSchema = z.object({ id: z.number(), name: z.string() });
|
|
745
|
-
* const CreateUserSchema = z.object({ name: z.string() });
|
|
746
|
-
*
|
|
747
|
-
* type UserPathParams = { id: string };
|
|
748
|
-
* type UserQueryParams = { include?: string; limit?: number };
|
|
749
|
-
*
|
|
750
|
-
* class GetUser extends BaseEndpoint<
|
|
751
|
-
* typeof CreateUserSchema,
|
|
752
|
-
* typeof UserSchema,
|
|
753
|
-
* UserPathParams,
|
|
754
|
-
* UserQueryParams
|
|
755
|
-
* > {
|
|
756
|
-
* protected method = 'GET' as const;
|
|
757
|
-
* protected path = (params: UserPathParams) => `/users/${params.id}`;
|
|
758
|
-
*
|
|
759
|
-
* constructor(client: HttpClient) {
|
|
760
|
-
* super(client, {
|
|
761
|
-
* requestSchema: CreateUserSchema,
|
|
762
|
-
* responseSchema: UserSchema
|
|
763
|
-
* });
|
|
764
|
-
* }
|
|
765
|
-
* }
|
|
766
|
-
*
|
|
767
|
-
* // Usage:
|
|
768
|
-
* const user = await endpoint.call({
|
|
769
|
-
* pathParams: { id: '123' },
|
|
770
|
-
* query: { include: 'posts', limit: 10 }
|
|
771
|
-
* });
|
|
772
|
-
* ```
|
|
773
|
-
*/
|
|
774
|
-
var BaseEndpoint = class {
|
|
775
|
-
/** Additional options for the request */
|
|
776
|
-
options;
|
|
777
|
-
/** Optional request schema for validation */
|
|
778
|
-
requestSchema;
|
|
779
|
-
/** Response schema for validation */
|
|
780
|
-
responseSchema;
|
|
781
846
|
/**
|
|
782
|
-
*
|
|
783
|
-
* @param cfg - Configuration with request and response schemas
|
|
784
|
-
*/
|
|
785
|
-
constructor(client, cfg) {
|
|
786
|
-
this.client = client;
|
|
787
|
-
this.requestSchema = cfg.requestSchema;
|
|
788
|
-
this.responseSchema = cfg.responseSchema;
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* Call the endpoint with strong typing derived from schemas.
|
|
792
|
-
* Validates request data before sending and response data after receiving.
|
|
793
|
-
*
|
|
794
|
-
* @param config - Request configuration object containing all parameters
|
|
795
|
-
* @returns Promise resolving to validated response data (typed by ResSchema)
|
|
796
|
-
* @throws {ZodError} If request validation fails
|
|
797
|
-
* @throws {ApiError} If response validation fails or request fails
|
|
847
|
+
* Create a strongly-typed endpoint builder.
|
|
798
848
|
*
|
|
799
|
-
* @
|
|
800
|
-
*
|
|
801
|
-
* const endpoint = new GetUser(client);
|
|
802
|
-
* const user = await endpoint.call({
|
|
803
|
-
* pathParams: { id: '123' },
|
|
804
|
-
* query: { include: 'posts' }
|
|
805
|
-
* });
|
|
806
|
-
* // With additional options:
|
|
807
|
-
* const user = await endpoint.call({
|
|
808
|
-
* data: { name: 'John' },
|
|
809
|
-
* pathParams: { id: '123' },
|
|
810
|
-
* headers: { 'X-Custom': 'value' },
|
|
811
|
-
* query: { include: 'posts' }
|
|
812
|
-
* });
|
|
813
|
-
* ```
|
|
849
|
+
* @param config - Endpoint configuration
|
|
850
|
+
* @returns Endpoint instance
|
|
814
851
|
*/
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
if (this.requestSchema && data !== void 0) {
|
|
818
|
-
const parsed = this.requestSchema.safeParse(data);
|
|
819
|
-
if (!parsed.success) throw parsed.error;
|
|
820
|
-
}
|
|
821
|
-
const pathArgs = pathParams ?? data;
|
|
822
|
-
const path = typeof this.path === "function" ? this.path(pathArgs) : this.path;
|
|
823
|
-
const body = this.method !== "GET" && this.method !== "HEAD" ? data : void 0;
|
|
824
|
-
const queryForRequest = query;
|
|
825
|
-
const { data: responseData } = await this.client.request(this.method, path, body, {
|
|
826
|
-
query: queryForRequest,
|
|
827
|
-
headers,
|
|
828
|
-
baseUrlKey: baseUrlKey ?? this.options?.baseUrlKey,
|
|
829
|
-
signal
|
|
830
|
-
});
|
|
831
|
-
return parseOrThrow(this.responseSchema, responseData);
|
|
852
|
+
createEndpoint(config) {
|
|
853
|
+
return new Endpoint(this, config);
|
|
832
854
|
}
|
|
833
855
|
};
|
|
834
856
|
|
|
@@ -920,13 +942,14 @@ const Envelope = (inner) => zod.z.object({
|
|
|
920
942
|
exports.ApiError = ApiError;
|
|
921
943
|
exports.ApiErrorSchema = ApiErrorSchema;
|
|
922
944
|
exports.ApiKeyAuth = ApiKeyAuth;
|
|
923
|
-
exports.BaseEndpoint = BaseEndpoint;
|
|
924
945
|
exports.BearerTokenAuth = BearerTokenAuth;
|
|
925
946
|
exports.ConsoleLogger = ConsoleLogger;
|
|
926
947
|
exports.ConsoleMetricsCollector = ConsoleMetricsCollector;
|
|
948
|
+
exports.Endpoint = Endpoint;
|
|
927
949
|
exports.Envelope = Envelope;
|
|
928
950
|
exports.ErrorDetail = ErrorDetail;
|
|
929
951
|
exports.HTTPMethod = HTTPMethod;
|
|
952
|
+
exports.HTTPStatusCode = HTTPStatusCode;
|
|
930
953
|
exports.HttpClient = HttpClient;
|
|
931
954
|
exports.Id = Id;
|
|
932
955
|
exports.InMemoryMetricsCollector = InMemoryMetricsCollector;
|
|
@@ -937,8 +960,7 @@ exports.NoAuth = NoAuth;
|
|
|
937
960
|
exports.NoOpLogger = NoOpLogger;
|
|
938
961
|
exports.NoOpMetricsCollector = NoOpMetricsCollector;
|
|
939
962
|
exports.PaginationSchema = PaginationSchema;
|
|
963
|
+
exports.SchemaDefinitionError = SchemaDefinitionError;
|
|
940
964
|
exports.Timestamps = Timestamps;
|
|
941
|
-
exports.parseOrThrow = parseOrThrow;
|
|
942
|
-
exports.safeParse = safeParse;
|
|
943
965
|
exports.toQueryString = toQueryString;
|
|
944
966
|
//# sourceMappingURL=index.cjs.map
|