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.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");
|
|
@@ -586,7 +693,8 @@ var HttpClient = class {
|
|
|
586
693
|
*/
|
|
587
694
|
async request(method, path, body, options) {
|
|
588
695
|
const startTime = Date.now();
|
|
589
|
-
|
|
696
|
+
const base = this.resolveBaseUrl(options?.baseUrlKey);
|
|
697
|
+
let url = `${base}${path}${toQueryString(options?.query)}`;
|
|
590
698
|
this.logger.debug("HTTP request initiated", {
|
|
591
699
|
method,
|
|
592
700
|
path,
|
|
@@ -615,26 +723,16 @@ var HttpClient = class {
|
|
|
615
723
|
const doFetch = async () => {
|
|
616
724
|
let timeoutId;
|
|
617
725
|
if (this.timeoutMs && !options?.signal) timeoutId = setTimeout(() => {
|
|
618
|
-
const timeoutError =
|
|
726
|
+
const timeoutError = new Error("Request timeout");
|
|
619
727
|
timeoutError.name = "TimeoutError";
|
|
620
728
|
controller.abort(timeoutError);
|
|
621
729
|
}, this.timeoutMs);
|
|
622
730
|
try {
|
|
623
731
|
const req = new Request(url, init);
|
|
624
732
|
const res = await this.fetchImpl(req);
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
text = await res.text();
|
|
629
|
-
} catch (readError) {
|
|
630
|
-
text = `Failed to read response: ${readError instanceof Error ? readError.message : String(readError)}`;
|
|
631
|
-
}
|
|
632
|
-
throw new ApiError(`HTTP ${res.status}: ${res.statusText}`, {
|
|
633
|
-
status: res.status,
|
|
634
|
-
details: text
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
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();
|
|
638
736
|
await this.runAfterHooks(new Request(url, init), res, data);
|
|
639
737
|
const duration = Date.now() - startTime;
|
|
640
738
|
this.logger.info("HTTP request successful", {
|
|
@@ -648,12 +746,12 @@ var HttpClient = class {
|
|
|
648
746
|
path,
|
|
649
747
|
status: res.status,
|
|
650
748
|
durationMs: duration,
|
|
651
|
-
timestamp:
|
|
749
|
+
timestamp: new Date().toISOString(),
|
|
652
750
|
success: true
|
|
653
751
|
});
|
|
654
752
|
return {
|
|
655
753
|
data,
|
|
656
|
-
|
|
754
|
+
status
|
|
657
755
|
};
|
|
658
756
|
} catch (error) {
|
|
659
757
|
const duration = Date.now() - startTime;
|
|
@@ -667,7 +765,7 @@ var HttpClient = class {
|
|
|
667
765
|
path,
|
|
668
766
|
status: error instanceof ApiError ? error.status : void 0,
|
|
669
767
|
durationMs: duration,
|
|
670
|
-
timestamp:
|
|
768
|
+
timestamp: new Date().toISOString(),
|
|
671
769
|
success: false,
|
|
672
770
|
error: error instanceof Error ? error.message : String(error)
|
|
673
771
|
});
|
|
@@ -676,13 +774,15 @@ var HttpClient = class {
|
|
|
676
774
|
if (timeoutId) clearTimeout(timeoutId);
|
|
677
775
|
}
|
|
678
776
|
};
|
|
679
|
-
const canRetry = ({
|
|
777
|
+
const canRetry = ({ error }) => {
|
|
680
778
|
if (error && typeof error === "object" && "name" in error) {
|
|
681
779
|
const errorName = error.name;
|
|
682
780
|
if (errorName === "AbortError" || errorName === "TimeoutError") return false;
|
|
683
781
|
}
|
|
684
|
-
if (error instanceof ApiError && error.status
|
|
685
|
-
|
|
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
|
+
}
|
|
686
786
|
return false;
|
|
687
787
|
};
|
|
688
788
|
if (!this.retry.retryMethods?.includes(method)) return doFetch();
|
|
@@ -743,109 +843,14 @@ var HttpClient = class {
|
|
|
743
843
|
async delete(path, options) {
|
|
744
844
|
return this.request("DELETE", path, void 0, options);
|
|
745
845
|
}
|
|
746
|
-
};
|
|
747
|
-
|
|
748
|
-
//#endregion
|
|
749
|
-
//#region lib/endpoint/base-endpoint.ts
|
|
750
|
-
/**
|
|
751
|
-
* Generic, strongly-typed endpoint with Zod schemas for request and response validation.
|
|
752
|
-
* Extend this class to create type-safe API endpoints.
|
|
753
|
-
*
|
|
754
|
-
* @template ReqSchema - Zod schema for request validation
|
|
755
|
-
* @template ResSchema - Zod schema for response validation
|
|
756
|
-
* @template PathParams - Type for path parameters (optional)
|
|
757
|
-
* @template QueryParams - Type for query parameters (optional)
|
|
758
|
-
*
|
|
759
|
-
* @example
|
|
760
|
-
* ```ts
|
|
761
|
-
* const UserSchema = z.object({ id: z.number(), name: z.string() });
|
|
762
|
-
* const CreateUserSchema = z.object({ name: z.string() });
|
|
763
|
-
*
|
|
764
|
-
* type UserPathParams = { id: string };
|
|
765
|
-
* type UserQueryParams = { include?: string; limit?: number };
|
|
766
|
-
*
|
|
767
|
-
* class GetUser extends BaseEndpoint<
|
|
768
|
-
* typeof CreateUserSchema,
|
|
769
|
-
* typeof UserSchema,
|
|
770
|
-
* UserPathParams,
|
|
771
|
-
* UserQueryParams
|
|
772
|
-
* > {
|
|
773
|
-
* protected method = 'GET' as const;
|
|
774
|
-
* protected path = (params: UserPathParams) => `/users/${params.id}`;
|
|
775
|
-
*
|
|
776
|
-
* constructor(client: HttpClient) {
|
|
777
|
-
* super(client, {
|
|
778
|
-
* requestSchema: CreateUserSchema,
|
|
779
|
-
* responseSchema: UserSchema
|
|
780
|
-
* });
|
|
781
|
-
* }
|
|
782
|
-
* }
|
|
783
|
-
*
|
|
784
|
-
* // Usage:
|
|
785
|
-
* const user = await endpoint.call({
|
|
786
|
-
* pathParams: { id: '123' },
|
|
787
|
-
* query: { include: 'posts', limit: 10 }
|
|
788
|
-
* });
|
|
789
|
-
* ```
|
|
790
|
-
*/
|
|
791
|
-
var BaseEndpoint = class {
|
|
792
|
-
/** Additional options for the request */
|
|
793
|
-
options;
|
|
794
|
-
/** Optional request schema for validation */
|
|
795
|
-
requestSchema;
|
|
796
|
-
/** Response schema for validation */
|
|
797
|
-
responseSchema;
|
|
798
|
-
/**
|
|
799
|
-
* @param client - HttpClient instance
|
|
800
|
-
* @param cfg - Configuration with request and response schemas
|
|
801
|
-
*/
|
|
802
|
-
constructor(client, cfg) {
|
|
803
|
-
this.client = client;
|
|
804
|
-
this.requestSchema = cfg.requestSchema;
|
|
805
|
-
this.responseSchema = cfg.responseSchema;
|
|
806
|
-
}
|
|
807
846
|
/**
|
|
808
|
-
*
|
|
809
|
-
* Validates request data before sending and response data after receiving.
|
|
810
|
-
*
|
|
811
|
-
* @param config - Request configuration object containing all parameters
|
|
812
|
-
* @returns Promise resolving to validated response data (typed by ResSchema)
|
|
813
|
-
* @throws {ZodError} If request validation fails
|
|
814
|
-
* @throws {ApiError} If response validation fails or request fails
|
|
847
|
+
* Create a strongly-typed endpoint builder.
|
|
815
848
|
*
|
|
816
|
-
* @
|
|
817
|
-
*
|
|
818
|
-
* const endpoint = new GetUser(client);
|
|
819
|
-
* const user = await endpoint.call({
|
|
820
|
-
* pathParams: { id: '123' },
|
|
821
|
-
* query: { include: 'posts' }
|
|
822
|
-
* });
|
|
823
|
-
* // With additional options:
|
|
824
|
-
* const user = await endpoint.call({
|
|
825
|
-
* data: { name: 'John' },
|
|
826
|
-
* pathParams: { id: '123' },
|
|
827
|
-
* headers: { 'X-Custom': 'value' },
|
|
828
|
-
* query: { include: 'posts' }
|
|
829
|
-
* });
|
|
830
|
-
* ```
|
|
849
|
+
* @param config - Endpoint configuration
|
|
850
|
+
* @returns Endpoint instance
|
|
831
851
|
*/
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
if (this.requestSchema && data !== void 0) {
|
|
835
|
-
const parsed = this.requestSchema.safeParse(data);
|
|
836
|
-
if (!parsed.success) throw parsed.error;
|
|
837
|
-
}
|
|
838
|
-
const pathArgs = pathParams ?? data;
|
|
839
|
-
const path = typeof this.path === "function" ? this.path(pathArgs) : this.path;
|
|
840
|
-
const body = this.method !== "GET" && this.method !== "HEAD" ? data : void 0;
|
|
841
|
-
const queryForRequest = query;
|
|
842
|
-
const { data: responseData } = await this.client.request(this.method, path, body, {
|
|
843
|
-
query: queryForRequest,
|
|
844
|
-
headers,
|
|
845
|
-
baseUrlKey: baseUrlKey ?? this.options?.baseUrlKey,
|
|
846
|
-
signal
|
|
847
|
-
});
|
|
848
|
-
return parseOrThrow(this.responseSchema, responseData);
|
|
852
|
+
createEndpoint(config) {
|
|
853
|
+
return new Endpoint(this, config);
|
|
849
854
|
}
|
|
850
855
|
};
|
|
851
856
|
|
|
@@ -937,13 +942,14 @@ const Envelope = (inner) => zod.z.object({
|
|
|
937
942
|
exports.ApiError = ApiError;
|
|
938
943
|
exports.ApiErrorSchema = ApiErrorSchema;
|
|
939
944
|
exports.ApiKeyAuth = ApiKeyAuth;
|
|
940
|
-
exports.BaseEndpoint = BaseEndpoint;
|
|
941
945
|
exports.BearerTokenAuth = BearerTokenAuth;
|
|
942
946
|
exports.ConsoleLogger = ConsoleLogger;
|
|
943
947
|
exports.ConsoleMetricsCollector = ConsoleMetricsCollector;
|
|
948
|
+
exports.Endpoint = Endpoint;
|
|
944
949
|
exports.Envelope = Envelope;
|
|
945
950
|
exports.ErrorDetail = ErrorDetail;
|
|
946
951
|
exports.HTTPMethod = HTTPMethod;
|
|
952
|
+
exports.HTTPStatusCode = HTTPStatusCode;
|
|
947
953
|
exports.HttpClient = HttpClient;
|
|
948
954
|
exports.Id = Id;
|
|
949
955
|
exports.InMemoryMetricsCollector = InMemoryMetricsCollector;
|
|
@@ -954,8 +960,7 @@ exports.NoAuth = NoAuth;
|
|
|
954
960
|
exports.NoOpLogger = NoOpLogger;
|
|
955
961
|
exports.NoOpMetricsCollector = NoOpMetricsCollector;
|
|
956
962
|
exports.PaginationSchema = PaginationSchema;
|
|
963
|
+
exports.SchemaDefinitionError = SchemaDefinitionError;
|
|
957
964
|
exports.Timestamps = Timestamps;
|
|
958
|
-
exports.parseOrThrow = parseOrThrow;
|
|
959
|
-
exports.safeParse = safeParse;
|
|
960
965
|
exports.toQueryString = toQueryString;
|
|
961
966
|
//# sourceMappingURL=index.cjs.map
|