zlient 3.3.4 → 4.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 +12 -12
- package/dist/auth.d.ts.map +1 -1
- package/dist/endpoint-utils.d.ts +36 -0
- package/dist/endpoint-utils.d.ts.map +1 -0
- package/dist/event-emitter.d.ts +12 -0
- package/dist/event-emitter.d.ts.map +1 -0
- package/dist/http/http-client.d.ts +35 -105
- package/dist/http/http-client.d.ts.map +1 -1
- package/dist/http/http-endpoint.d.ts +2 -2
- package/dist/http/http-endpoint.d.ts.map +1 -1
- package/dist/http/request-utils.d.ts +23 -0
- package/dist/http/request-utils.d.ts.map +1 -0
- package/dist/index.cjs +529 -419
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +528 -420
- package/dist/index.js.map +1 -1
- package/dist/realtime-utils.d.ts +4 -0
- package/dist/realtime-utils.d.ts.map +1 -0
- package/dist/sse/sse-client.d.ts +14 -8
- package/dist/sse/sse-client.d.ts.map +1 -1
- package/dist/sse/sse-endpoint.d.ts.map +1 -1
- package/dist/types.d.ts +24 -12
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts +10 -2
- package/dist/validation.d.ts.map +1 -1
- package/dist/ws/ws-client.d.ts +9 -6
- package/dist/ws/ws-client.d.ts.map +1 -1
- package/dist/ws/ws-endpoint.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Use this when you don't need authentication.
|
|
5
5
|
*/
|
|
6
6
|
var NoAuth = class {
|
|
7
|
-
|
|
7
|
+
apply(_ctx) {}
|
|
8
8
|
};
|
|
9
9
|
/**
|
|
10
10
|
* API Key authentication provider.
|
|
@@ -25,18 +25,18 @@ var ApiKeyAuth = class {
|
|
|
25
25
|
if (!opts.header && !opts.query) throw new Error("ApiKeyAuth requires either \"header\" or \"query\" option");
|
|
26
26
|
if (opts.header && opts.query) throw new Error("ApiKeyAuth cannot use both \"header\" and \"query\" options");
|
|
27
27
|
}
|
|
28
|
-
apply(
|
|
28
|
+
apply(ctx) {
|
|
29
29
|
const value = this.opts.value;
|
|
30
|
-
if (this.opts.header) if (init.headers instanceof Headers) init.headers.set(this.opts.header, value);
|
|
31
|
-
else if (Array.isArray(init.headers)) init.headers.push([this.opts.header, value]);
|
|
32
|
-
else init.headers = {
|
|
33
|
-
...init.headers,
|
|
30
|
+
if (this.opts.header) if (ctx.init.headers instanceof Headers) ctx.init.headers.set(this.opts.header, value);
|
|
31
|
+
else if (Array.isArray(ctx.init.headers)) ctx.init.headers.push([this.opts.header, value]);
|
|
32
|
+
else ctx.init.headers = {
|
|
33
|
+
...ctx.init.headers,
|
|
34
34
|
[this.opts.header]: value
|
|
35
35
|
};
|
|
36
36
|
else if (this.opts.query) {
|
|
37
|
-
const u = new URL(url);
|
|
37
|
+
const u = new URL(ctx.url);
|
|
38
38
|
u.searchParams.set(this.opts.query, value);
|
|
39
|
-
|
|
39
|
+
ctx.url = u.toString();
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
};
|
|
@@ -59,14 +59,14 @@ var BearerTokenAuth = class {
|
|
|
59
59
|
constructor(getToken) {
|
|
60
60
|
this.getToken = getToken;
|
|
61
61
|
}
|
|
62
|
-
async apply(
|
|
62
|
+
async apply(ctx) {
|
|
63
63
|
const token = await this.getToken();
|
|
64
64
|
if (!token) throw new Error("BearerTokenAuth: token is empty or undefined");
|
|
65
65
|
const authHeader = `Bearer ${token}`;
|
|
66
|
-
if (init.headers instanceof Headers) init.headers.set("Authorization", authHeader);
|
|
67
|
-
else if (Array.isArray(init.headers)) init.headers.push(["Authorization", authHeader]);
|
|
68
|
-
else init.headers = {
|
|
69
|
-
...init.headers,
|
|
66
|
+
if (ctx.init.headers instanceof Headers) ctx.init.headers.set("Authorization", authHeader);
|
|
67
|
+
else if (Array.isArray(ctx.init.headers)) ctx.init.headers.push(["Authorization", authHeader]);
|
|
68
|
+
else ctx.init.headers = {
|
|
69
|
+
...ctx.init.headers,
|
|
70
70
|
Authorization: authHeader
|
|
71
71
|
};
|
|
72
72
|
}
|
|
@@ -319,6 +319,9 @@ const HTTPStatusCode = {
|
|
|
319
319
|
NOT_EXTENDED: 510,
|
|
320
320
|
NETWORK_AUTHENTICATION_REQUIRED: 511
|
|
321
321
|
};
|
|
322
|
+
function captureStackTrace(targetObject, constructorOpt) {
|
|
323
|
+
Error.captureStackTrace?.(targetObject, constructorOpt);
|
|
324
|
+
}
|
|
322
325
|
/**
|
|
323
326
|
* Custom error class for API-related errors.
|
|
324
327
|
* Includes HTTP status codes, response details, and validation errors.
|
|
@@ -336,10 +339,12 @@ var ApiError = class ApiError extends Error {
|
|
|
336
339
|
super(message);
|
|
337
340
|
this.name = "ApiError";
|
|
338
341
|
this.status = options?.status;
|
|
342
|
+
this.method = options?.method;
|
|
343
|
+
this.url = options?.url;
|
|
339
344
|
this.details = options?.details;
|
|
340
345
|
this.cause = options?.cause;
|
|
341
346
|
this.validationIssues = options?.validationIssues;
|
|
342
|
-
|
|
347
|
+
captureStackTrace(this, ApiError);
|
|
343
348
|
}
|
|
344
349
|
/**
|
|
345
350
|
* Check if this is a validation error (has validationIssues)
|
|
@@ -367,6 +372,8 @@ var ApiError = class ApiError extends Error {
|
|
|
367
372
|
name: this.name,
|
|
368
373
|
message: this.message,
|
|
369
374
|
status: this.status,
|
|
375
|
+
method: this.method,
|
|
376
|
+
url: this.url,
|
|
370
377
|
details: this.details,
|
|
371
378
|
validationIssues: this.validationIssues,
|
|
372
379
|
stack: this.stack
|
|
@@ -382,7 +389,7 @@ var SchemaDefinitionError = class SchemaDefinitionError extends Error {
|
|
|
382
389
|
super(`No schema defined for status code ${status}`);
|
|
383
390
|
this.status = status;
|
|
384
391
|
this.name = "SchemaDefinitionError";
|
|
385
|
-
|
|
392
|
+
captureStackTrace(this, SchemaDefinitionError);
|
|
386
393
|
}
|
|
387
394
|
};
|
|
388
395
|
/**
|
|
@@ -404,15 +411,34 @@ function toQueryString(q) {
|
|
|
404
411
|
const s = q.toString();
|
|
405
412
|
return s ? `?${s}` : "";
|
|
406
413
|
}
|
|
414
|
+
if (typeof q !== "object") throw new TypeError("Query parameters must be a URLSearchParams instance or an object");
|
|
407
415
|
const params = new URLSearchParams();
|
|
408
416
|
Object.entries(q).forEach(([k, v]) => {
|
|
409
|
-
if (v !== void 0) params.append(k, String(v));
|
|
417
|
+
if (v !== void 0 && v !== null) params.append(k, String(v));
|
|
410
418
|
});
|
|
411
419
|
const s = params.toString();
|
|
412
420
|
return s ? `?${s}` : "";
|
|
413
421
|
}
|
|
422
|
+
function toRequestQuery(q) {
|
|
423
|
+
if (q === void 0 || q instanceof URLSearchParams) return q;
|
|
424
|
+
if (q !== null && typeof q === "object") return q;
|
|
425
|
+
throw new TypeError("Query parameters must be a URLSearchParams instance or an object");
|
|
426
|
+
}
|
|
414
427
|
//#endregion
|
|
415
428
|
//#region lib/validation.ts
|
|
429
|
+
function formatPath(path) {
|
|
430
|
+
if (!path || path.length === 0) return "";
|
|
431
|
+
return path.map((segment) => {
|
|
432
|
+
const key = typeof segment === "object" && segment !== null ? segment.key : segment;
|
|
433
|
+
return typeof key === "symbol" ? key.toString() : String(key);
|
|
434
|
+
}).join(".");
|
|
435
|
+
}
|
|
436
|
+
function formatValidationIssues(issues, maxIssues = 3) {
|
|
437
|
+
return issues.slice(0, maxIssues).map((issue) => {
|
|
438
|
+
const path = formatPath(issue.path);
|
|
439
|
+
return path ? `${path}: ${issue.message}` : issue.message;
|
|
440
|
+
}).join("; ");
|
|
441
|
+
}
|
|
416
442
|
/**
|
|
417
443
|
* Safely parse/validate data with any Standard Schema-compatible library (Zod, Valibot, ArkType, etc.).
|
|
418
444
|
* Returns a result object with success status and data or issues.
|
|
@@ -475,9 +501,19 @@ async function safeParse(schema, data) {
|
|
|
475
501
|
* }
|
|
476
502
|
* ```
|
|
477
503
|
*/
|
|
478
|
-
async function parseOrThrow(schema, data) {
|
|
504
|
+
async function parseOrThrow(schema, data, context = {}) {
|
|
479
505
|
const result = await schema["~standard"].validate(data);
|
|
480
|
-
if (result.issues)
|
|
506
|
+
if (result.issues) {
|
|
507
|
+
const messages = formatValidationIssues(result.issues);
|
|
508
|
+
const issueCount = result.issues.length > 3 ? ` (${result.issues.length} issues total)` : "";
|
|
509
|
+
throw new ApiError(`${context.label ? `${context.label} validation failed` : "Validation failed"}: ${messages}${issueCount}`, {
|
|
510
|
+
status: context.status,
|
|
511
|
+
method: context.method,
|
|
512
|
+
url: context.url,
|
|
513
|
+
details: context.details,
|
|
514
|
+
validationIssues: result.issues
|
|
515
|
+
});
|
|
516
|
+
}
|
|
481
517
|
return result.value;
|
|
482
518
|
}
|
|
483
519
|
/**
|
|
@@ -494,47 +530,132 @@ function isStandardSchema(value) {
|
|
|
494
530
|
return "~standard" in schema && typeof schema["~standard"] === "object" && schema["~standard"] !== null && schema["~standard"].version === 1 && typeof schema["~standard"].validate === "function";
|
|
495
531
|
}
|
|
496
532
|
//#endregion
|
|
533
|
+
//#region lib/endpoint-utils.ts
|
|
534
|
+
function validateRequiredHeaders(mustHeaderKeys, headers) {
|
|
535
|
+
if (!mustHeaderKeys || mustHeaderKeys.length === 0) return;
|
|
536
|
+
const missing = mustHeaderKeys.filter((key) => !headers || !(key in headers));
|
|
537
|
+
if (missing.length > 0) throw new ApiError(`Missing required header(s): ${missing.join(", ")}`, { details: { missingHeaders: missing } });
|
|
538
|
+
}
|
|
539
|
+
async function parseEndpointValue(schema, value, skipValidation, label) {
|
|
540
|
+
if (skipValidation || !schema || value === void 0) return value;
|
|
541
|
+
return parseOrThrow(schema, value, { label });
|
|
542
|
+
}
|
|
543
|
+
function assertRequiredEndpointValue(schema, value, missingParam, message) {
|
|
544
|
+
if (!schema || value !== void 0) return;
|
|
545
|
+
throw new ApiError(message, { details: { missingParam } });
|
|
546
|
+
}
|
|
547
|
+
function resolveEndpointPath(path, rawPathParams, parsedPathParams) {
|
|
548
|
+
if (typeof path !== "function") return path;
|
|
549
|
+
if (!rawPathParams) throw new ApiError("Path function requires pathParams", { details: { missingParam: "pathParams" } });
|
|
550
|
+
return path(parsedPathParams);
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Shared validation pipeline used by HTTP, SSE, and WebSocket endpoints.
|
|
554
|
+
* Validates request body, query params, and path params; asserts required
|
|
555
|
+
* fields are present; and resolves the final path string.
|
|
556
|
+
*/
|
|
557
|
+
async function validateEndpointParams(config, raw, labels = {}) {
|
|
558
|
+
const skip = config.advanced?.skipRequestValidation ?? false;
|
|
559
|
+
const parsedData = await parseEndpointValue(config.request, raw.data, skip, labels.request ?? "Request body");
|
|
560
|
+
const parsedQuery = await parseEndpointValue(config.query, raw.query, skip, labels.query ?? "Query parameters");
|
|
561
|
+
const parsedPathParams = await parseEndpointValue(config.pathParams, raw.pathParams, skip, labels.path ?? "Path parameters");
|
|
562
|
+
assertRequiredEndpointValue(config.request, raw.data, "data", "Missing required request body (data)");
|
|
563
|
+
assertRequiredEndpointValue(config.pathParams, raw.pathParams, "pathParams", "Missing required path parameters (pathParams)");
|
|
564
|
+
return {
|
|
565
|
+
parsedData,
|
|
566
|
+
parsedQuery,
|
|
567
|
+
parsedPathParams,
|
|
568
|
+
pathStr: resolveEndpointPath(config.path, raw.pathParams, parsedPathParams)
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
//#endregion
|
|
572
|
+
//#region lib/event-emitter.ts
|
|
573
|
+
/**
|
|
574
|
+
* Minimal typed event emitter used internally by SSEConnectionImpl and WSConnectionImpl.
|
|
575
|
+
* Not part of the public API.
|
|
576
|
+
*/
|
|
577
|
+
var EventEmitter = class {
|
|
578
|
+
constructor() {
|
|
579
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
580
|
+
}
|
|
581
|
+
on(event, handler) {
|
|
582
|
+
let set = this.handlers.get(event);
|
|
583
|
+
if (!set) {
|
|
584
|
+
set = /* @__PURE__ */ new Set();
|
|
585
|
+
this.handlers.set(event, set);
|
|
586
|
+
}
|
|
587
|
+
set.add(handler);
|
|
588
|
+
}
|
|
589
|
+
off(event, handler) {
|
|
590
|
+
this.handlers.get(event)?.delete(handler);
|
|
591
|
+
}
|
|
592
|
+
emit(event, ...args) {
|
|
593
|
+
const set = this.handlers.get(event);
|
|
594
|
+
if (set) set.forEach((handler) => handler(...args));
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
//#endregion
|
|
598
|
+
//#region lib/realtime-utils.ts
|
|
599
|
+
function looksLikeJsonValue(value) {
|
|
600
|
+
return value.startsWith("{") && value.endsWith("}") || value.startsWith("[") && value.endsWith("]") || value.startsWith("\"") && value.endsWith("\"");
|
|
601
|
+
}
|
|
602
|
+
function parseRealtimeData(data) {
|
|
603
|
+
if (typeof data !== "string" || data.length === 0 || !looksLikeJsonValue(data)) return data;
|
|
604
|
+
try {
|
|
605
|
+
return JSON.parse(data);
|
|
606
|
+
} catch {
|
|
607
|
+
return data;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function serializeRealtimeData(data) {
|
|
611
|
+
if (data != null && typeof data === "object") return JSON.stringify(data);
|
|
612
|
+
if (typeof data === "string") return data;
|
|
613
|
+
return String(data);
|
|
614
|
+
}
|
|
615
|
+
//#endregion
|
|
497
616
|
//#region lib/sse/sse-client.ts
|
|
498
|
-
var SSEConnectionImpl = class {
|
|
617
|
+
var SSEConnectionImpl = class extends EventEmitter {
|
|
499
618
|
constructor(url, responseSchema, options = {}) {
|
|
619
|
+
super();
|
|
500
620
|
this.url = url;
|
|
501
621
|
this.responseSchema = responseSchema;
|
|
502
622
|
this.options = options;
|
|
503
623
|
this.abortController = new AbortController();
|
|
504
|
-
this.handlers = /* @__PURE__ */ new Map();
|
|
505
624
|
this._readyState = 0;
|
|
506
625
|
this.start();
|
|
507
626
|
}
|
|
508
627
|
async start() {
|
|
509
628
|
try {
|
|
510
|
-
|
|
511
|
-
|
|
629
|
+
const { method = "GET", data, withCredentials, signal, auth, logger } = this.options;
|
|
630
|
+
const requestHeaders = {
|
|
631
|
+
Accept: "text/event-stream",
|
|
632
|
+
...this.options.headers
|
|
633
|
+
};
|
|
512
634
|
const init = {
|
|
513
635
|
method,
|
|
514
|
-
headers:
|
|
515
|
-
Accept: "text/event-stream",
|
|
516
|
-
...headers
|
|
517
|
-
},
|
|
636
|
+
headers: requestHeaders,
|
|
518
637
|
signal: signal || this.abortController.signal
|
|
519
638
|
};
|
|
639
|
+
let url = this.url;
|
|
520
640
|
if (auth) {
|
|
521
|
-
|
|
641
|
+
const ctx = {
|
|
522
642
|
url,
|
|
523
643
|
init
|
|
524
|
-
}
|
|
525
|
-
|
|
644
|
+
};
|
|
645
|
+
await auth.apply(ctx);
|
|
646
|
+
url = ctx.url;
|
|
526
647
|
}
|
|
527
648
|
if (withCredentials) init.credentials = "include";
|
|
528
|
-
if (data) {
|
|
649
|
+
if (data != null) {
|
|
529
650
|
init.body = typeof data === "object" ? JSON.stringify(data) : String(data);
|
|
530
|
-
if (!
|
|
651
|
+
if (!("Content-Type" in requestHeaders)) requestHeaders["Content-Type"] = "application/json";
|
|
531
652
|
}
|
|
532
653
|
if (logger) logger.debug("SSE connection initiated", {
|
|
533
654
|
method,
|
|
534
655
|
url,
|
|
535
|
-
hasData:
|
|
656
|
+
hasData: data != null
|
|
536
657
|
});
|
|
537
|
-
const response = await fetch(url, init);
|
|
658
|
+
const response = await (this.options.fetch ?? globalThis.fetch.bind(globalThis))(url, init);
|
|
538
659
|
if (!response.ok) {
|
|
539
660
|
if (logger) logger.error(`SSE request failed with status ${response.status}`, /* @__PURE__ */ new Error("SSE Error"), {
|
|
540
661
|
url,
|
|
@@ -546,80 +667,7 @@ var SSEConnectionImpl = class {
|
|
|
546
667
|
this.emit("open", { type: "open" });
|
|
547
668
|
const reader = response.body?.getReader();
|
|
548
669
|
if (!reader) throw new Error("Response body is not readable");
|
|
549
|
-
|
|
550
|
-
let lineBuffer = "";
|
|
551
|
-
let eventData = "";
|
|
552
|
-
let eventName = "message";
|
|
553
|
-
let lastCharWasCR = false;
|
|
554
|
-
const processLine = (line) => {
|
|
555
|
-
if (line === "") {
|
|
556
|
-
if (eventData) {
|
|
557
|
-
this.handleEvent(eventName, eventData.endsWith("\n") ? eventData.slice(0, -1) : eventData);
|
|
558
|
-
eventData = "";
|
|
559
|
-
}
|
|
560
|
-
eventName = "message";
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
if (line.startsWith(":")) return;
|
|
564
|
-
const colonIndex = line.indexOf(":");
|
|
565
|
-
let field;
|
|
566
|
-
let value;
|
|
567
|
-
if (colonIndex === -1) {
|
|
568
|
-
field = line;
|
|
569
|
-
value = "";
|
|
570
|
-
} else {
|
|
571
|
-
field = line.slice(0, colonIndex);
|
|
572
|
-
value = line.slice(colonIndex + 1);
|
|
573
|
-
if (value.startsWith(" ")) value = value.slice(1);
|
|
574
|
-
}
|
|
575
|
-
if (field === "event") eventName = value;
|
|
576
|
-
else if (field === "data") eventData += value + "\n";
|
|
577
|
-
};
|
|
578
|
-
while (true) {
|
|
579
|
-
const { done, value } = await reader.read();
|
|
580
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
581
|
-
for (let i = 0; i < chunk.length; i++) {
|
|
582
|
-
const char = chunk[i];
|
|
583
|
-
if (char === "\n") if (lastCharWasCR) lastCharWasCR = false;
|
|
584
|
-
else {
|
|
585
|
-
processLine(lineBuffer);
|
|
586
|
-
lineBuffer = "";
|
|
587
|
-
}
|
|
588
|
-
else if (char === "\r") {
|
|
589
|
-
processLine(lineBuffer);
|
|
590
|
-
lineBuffer = "";
|
|
591
|
-
lastCharWasCR = true;
|
|
592
|
-
} else {
|
|
593
|
-
if (lastCharWasCR) lastCharWasCR = false;
|
|
594
|
-
lineBuffer += char;
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
if (done) {
|
|
598
|
-
const finalChunk = decoder.decode();
|
|
599
|
-
for (const char of finalChunk) if (char === "\n") if (lastCharWasCR) lastCharWasCR = false;
|
|
600
|
-
else {
|
|
601
|
-
processLine(lineBuffer);
|
|
602
|
-
lineBuffer = "";
|
|
603
|
-
}
|
|
604
|
-
else if (char === "\r") {
|
|
605
|
-
processLine(lineBuffer);
|
|
606
|
-
lineBuffer = "";
|
|
607
|
-
lastCharWasCR = true;
|
|
608
|
-
} else {
|
|
609
|
-
if (lastCharWasCR) lastCharWasCR = false;
|
|
610
|
-
lineBuffer += char;
|
|
611
|
-
}
|
|
612
|
-
if (lineBuffer) {
|
|
613
|
-
processLine(lineBuffer);
|
|
614
|
-
lineBuffer = "";
|
|
615
|
-
}
|
|
616
|
-
if (eventData) {
|
|
617
|
-
this.handleEvent(eventName, eventData.endsWith("\n") ? eventData.slice(0, -1) : eventData);
|
|
618
|
-
eventData = "";
|
|
619
|
-
}
|
|
620
|
-
break;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
670
|
+
await this.readStream(reader);
|
|
623
671
|
} catch (error) {
|
|
624
672
|
if (error.name === "AbortError") return;
|
|
625
673
|
this._readyState = 2;
|
|
@@ -628,12 +676,78 @@ var SSEConnectionImpl = class {
|
|
|
628
676
|
this._readyState = 2;
|
|
629
677
|
}
|
|
630
678
|
}
|
|
679
|
+
/**
|
|
680
|
+
* Reads the SSE stream according to the spec:
|
|
681
|
+
* - Lines are separated by LF, CR, or CRLF
|
|
682
|
+
* - Empty line dispatches the accumulated event
|
|
683
|
+
* - Lines starting with ':' are comments
|
|
684
|
+
* - 'event' field sets the event name; 'data' field accumulates the payload
|
|
685
|
+
*/
|
|
686
|
+
async readStream(reader) {
|
|
687
|
+
const decoder = new TextDecoder();
|
|
688
|
+
let lineBuffer = "";
|
|
689
|
+
let eventData = "";
|
|
690
|
+
let eventName = "message";
|
|
691
|
+
let lastCharWasCR = false;
|
|
692
|
+
const processLine = (line) => {
|
|
693
|
+
if (line === "") {
|
|
694
|
+
if (eventData) {
|
|
695
|
+
this.handleEvent(eventName, eventData.endsWith("\n") ? eventData.slice(0, -1) : eventData);
|
|
696
|
+
eventData = "";
|
|
697
|
+
}
|
|
698
|
+
eventName = "message";
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
if (line.startsWith(":")) return;
|
|
702
|
+
const colonIndex = line.indexOf(":");
|
|
703
|
+
let field;
|
|
704
|
+
let value;
|
|
705
|
+
if (colonIndex === -1) {
|
|
706
|
+
field = line;
|
|
707
|
+
value = "";
|
|
708
|
+
} else {
|
|
709
|
+
field = line.slice(0, colonIndex);
|
|
710
|
+
value = line.slice(colonIndex + 1);
|
|
711
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
712
|
+
}
|
|
713
|
+
if (field === "event") eventName = value;
|
|
714
|
+
else if (field === "data") eventData += value + "\n";
|
|
715
|
+
};
|
|
716
|
+
const processChunk = (chunk) => {
|
|
717
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
718
|
+
const char = chunk[i];
|
|
719
|
+
if (char === "\n") if (lastCharWasCR) lastCharWasCR = false;
|
|
720
|
+
else {
|
|
721
|
+
processLine(lineBuffer);
|
|
722
|
+
lineBuffer = "";
|
|
723
|
+
}
|
|
724
|
+
else if (char === "\r") {
|
|
725
|
+
processLine(lineBuffer);
|
|
726
|
+
lineBuffer = "";
|
|
727
|
+
lastCharWasCR = true;
|
|
728
|
+
} else {
|
|
729
|
+
lastCharWasCR = false;
|
|
730
|
+
lineBuffer += char;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
while (true) {
|
|
735
|
+
const { done, value } = await reader.read();
|
|
736
|
+
processChunk(decoder.decode(value, { stream: true }));
|
|
737
|
+
if (done) {
|
|
738
|
+
processChunk(decoder.decode());
|
|
739
|
+
if (lineBuffer) {
|
|
740
|
+
processLine(lineBuffer);
|
|
741
|
+
lineBuffer = "";
|
|
742
|
+
}
|
|
743
|
+
if (eventData) this.handleEvent(eventName, eventData.endsWith("\n") ? eventData.slice(0, -1) : eventData);
|
|
744
|
+
break;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
631
748
|
async handleEvent(event, data) {
|
|
632
|
-
let parsedData = data;
|
|
749
|
+
let parsedData = parseRealtimeData(data);
|
|
633
750
|
try {
|
|
634
|
-
if (typeof data === "string" && data.length > 0) try {
|
|
635
|
-
if (data.startsWith("{") && data.endsWith("}") || data.startsWith("[") && data.endsWith("]") || data.startsWith("\"") && data.endsWith("\"")) parsedData = JSON.parse(data);
|
|
636
|
-
} catch {}
|
|
637
751
|
const schema = this.getSchema(event);
|
|
638
752
|
if (!this.options.skipResponseValidation && schema) parsedData = await parseOrThrow(schema, parsedData);
|
|
639
753
|
this.emit(event, parsedData);
|
|
@@ -643,24 +757,9 @@ var SSEConnectionImpl = class {
|
|
|
643
757
|
}
|
|
644
758
|
getSchema(event) {
|
|
645
759
|
if (!this.responseSchema) return void 0;
|
|
646
|
-
if ("~standard" in this.responseSchema)
|
|
647
|
-
if (event === "message") return this.responseSchema;
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
760
|
+
if ("~standard" in this.responseSchema) return event === "message" ? this.responseSchema : void 0;
|
|
650
761
|
return this.responseSchema[event];
|
|
651
762
|
}
|
|
652
|
-
on(event, handler) {
|
|
653
|
-
if (!this.handlers.has(event)) this.handlers.set(event, /* @__PURE__ */ new Set());
|
|
654
|
-
this.handlers.get(event).add(handler);
|
|
655
|
-
}
|
|
656
|
-
off(event, handler) {
|
|
657
|
-
const handlers = this.handlers.get(event);
|
|
658
|
-
if (handlers) handlers.delete(handler);
|
|
659
|
-
}
|
|
660
|
-
emit(event, ...args) {
|
|
661
|
-
const handlers = this.handlers.get(event);
|
|
662
|
-
if (handlers) handlers.forEach((handler) => handler(...args));
|
|
663
|
-
}
|
|
664
763
|
close() {
|
|
665
764
|
this.abortController.abort();
|
|
666
765
|
this._readyState = 2;
|
|
@@ -679,18 +778,16 @@ var SSEEndpointImpl = class {
|
|
|
679
778
|
createCall() {
|
|
680
779
|
return async (params) => {
|
|
681
780
|
const { query, pathParams, data, headers, signal } = params || {};
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
} else pathStr = this.config.path;
|
|
693
|
-
return new SSEConnectionImpl(`${this.client.getBaseUrl(this.config.advanced?.baseUrlKey || "default")}${pathStr}${toQueryString(query)}`, this.config.response, {
|
|
781
|
+
const { parsedQuery, pathStr } = await validateEndpointParams(this.config, {
|
|
782
|
+
data,
|
|
783
|
+
query,
|
|
784
|
+
pathParams
|
|
785
|
+
}, {
|
|
786
|
+
request: "SSE request body",
|
|
787
|
+
query: "SSE query parameters",
|
|
788
|
+
path: "SSE path parameters"
|
|
789
|
+
});
|
|
790
|
+
return new SSEConnectionImpl(`${this.client.getBaseUrl(this.config.advanced?.baseUrlKey || "default")}${pathStr}${toQueryString(toRequestQuery(parsedQuery))}`, this.config.response, {
|
|
694
791
|
skipResponseValidation: this.config.advanced?.skipResponseValidation,
|
|
695
792
|
withCredentials: this.config.advanced?.withCredentials,
|
|
696
793
|
method: this.config.method,
|
|
@@ -702,31 +799,29 @@ var SSEEndpointImpl = class {
|
|
|
702
799
|
},
|
|
703
800
|
signal,
|
|
704
801
|
auth: this.config.advanced?.skipAuth ? void 0 : this.client.getAuth(),
|
|
705
|
-
logger: this.client.getLogger()
|
|
802
|
+
logger: this.client.getLogger(),
|
|
803
|
+
fetch: this.client.getFetch()
|
|
706
804
|
});
|
|
707
805
|
};
|
|
708
806
|
}
|
|
709
807
|
};
|
|
710
808
|
//#endregion
|
|
711
809
|
//#region lib/ws/ws-client.ts
|
|
712
|
-
var WSConnectionImpl = class {
|
|
810
|
+
var WSConnectionImpl = class extends EventEmitter {
|
|
713
811
|
constructor(url, sendSchema, receiveSchema, skipRequestValidation = false, skipResponseValidation = false, protocols) {
|
|
812
|
+
super();
|
|
714
813
|
this.sendSchema = sendSchema;
|
|
715
814
|
this.receiveSchema = receiveSchema;
|
|
716
815
|
this.skipRequestValidation = skipRequestValidation;
|
|
717
816
|
this.skipResponseValidation = skipResponseValidation;
|
|
718
|
-
this.handlers = /* @__PURE__ */ new Map();
|
|
719
817
|
if (typeof WebSocket === "undefined") throw new Error("WebSocket is not defined. Ensure you are in a supported environment.");
|
|
720
818
|
this.ws = new WebSocket(url, protocols);
|
|
721
819
|
this.ws.onopen = () => this.emit("open");
|
|
722
820
|
this.ws.onclose = (event) => this.emit("close", event);
|
|
723
821
|
this.ws.onerror = (event) => this.emit("error", event);
|
|
724
822
|
this.ws.onmessage = async (event) => {
|
|
725
|
-
let data = event.data;
|
|
823
|
+
let data = parseRealtimeData(event.data);
|
|
726
824
|
try {
|
|
727
|
-
if (typeof data === "string") try {
|
|
728
|
-
data = JSON.parse(data);
|
|
729
|
-
} catch {}
|
|
730
825
|
if (!this.skipResponseValidation && this.receiveSchema) data = await parseOrThrow(this.receiveSchema, data);
|
|
731
826
|
this.emit("message", data);
|
|
732
827
|
} catch (error) {
|
|
@@ -736,20 +831,13 @@ var WSConnectionImpl = class {
|
|
|
736
831
|
}
|
|
737
832
|
async send(data) {
|
|
738
833
|
if (!this.skipRequestValidation && this.sendSchema) await parseOrThrow(this.sendSchema, data);
|
|
739
|
-
|
|
740
|
-
this.ws.send(message);
|
|
834
|
+
this.ws.send(serializeRealtimeData(data));
|
|
741
835
|
}
|
|
742
836
|
on(event, handler) {
|
|
743
|
-
|
|
744
|
-
this.handlers.get(event).add(handler);
|
|
837
|
+
super.on(event, handler);
|
|
745
838
|
}
|
|
746
839
|
off(event, handler) {
|
|
747
|
-
|
|
748
|
-
if (handlers) handlers.delete(handler);
|
|
749
|
-
}
|
|
750
|
-
emit(event, ...args) {
|
|
751
|
-
const handlers = this.handlers.get(event);
|
|
752
|
-
if (handlers) handlers.forEach((handler) => handler(...args));
|
|
840
|
+
super.off(event, handler);
|
|
753
841
|
}
|
|
754
842
|
close(code, reason) {
|
|
755
843
|
this.ws.close(code, reason);
|
|
@@ -766,14 +854,16 @@ var WSEndpointImpl = class {
|
|
|
766
854
|
this.config = config;
|
|
767
855
|
}
|
|
768
856
|
createCall() {
|
|
769
|
-
return (params) => {
|
|
857
|
+
return async (params) => {
|
|
770
858
|
const { query, pathParams, protocols } = params || {};
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
859
|
+
const { parsedQuery, pathStr } = await validateEndpointParams(this.config, {
|
|
860
|
+
query,
|
|
861
|
+
pathParams
|
|
862
|
+
}, {
|
|
863
|
+
query: "WebSocket query parameters",
|
|
864
|
+
path: "WebSocket path parameters"
|
|
865
|
+
});
|
|
866
|
+
return new WSConnectionImpl(`${this.client.getBaseUrl(this.config.advanced?.baseUrlKey || "default").replace(/^http/, "ws")}${pathStr}${toQueryString(toRequestQuery(parsedQuery))}`, this.config.send, this.config.receive, this.config.advanced?.skipRequestValidation, this.config.advanced?.skipResponseValidation, protocols);
|
|
777
867
|
};
|
|
778
868
|
}
|
|
779
869
|
};
|
|
@@ -787,42 +877,185 @@ var EndpointImpl = class {
|
|
|
787
877
|
async call(params) {
|
|
788
878
|
const { data, query, pathParams, signal } = params;
|
|
789
879
|
const headers = "headers" in params ? params.headers : void 0;
|
|
790
|
-
|
|
791
|
-
const
|
|
792
|
-
|
|
793
|
-
const missingHeaders = this.config.mustHeaderKeys.filter((key) => !headers || !(key in headers));
|
|
794
|
-
if (missingHeaders.length > 0) throw new Error(`Missing required header(s): ${missingHeaders.join(", ")}`);
|
|
795
|
-
}
|
|
796
|
-
if (!skipRequestValidation && this.config.request && data !== void 0) await parseOrThrow(this.config.request, data);
|
|
797
|
-
if (!skipRequestValidation && this.config.query && query !== void 0) await parseOrThrow(this.config.query, query);
|
|
798
|
-
if (!skipRequestValidation && this.config.pathParams && pathParams !== void 0) await parseOrThrow(this.config.pathParams, pathParams);
|
|
799
|
-
if (this.config.request && data === void 0) throw new Error("Missing required request body (data)");
|
|
800
|
-
if (this.config.pathParams && pathParams === void 0) throw new Error("Missing required path parameters (pathParams)");
|
|
801
|
-
let pathStr;
|
|
802
|
-
if (typeof this.config.path === "function") {
|
|
803
|
-
if (!pathParams) throw new Error("Path function requires pathParams");
|
|
804
|
-
pathStr = this.config.path(pathParams);
|
|
805
|
-
} else pathStr = this.config.path;
|
|
806
|
-
const { data: responseData, status } = await this.client.request(this.config.method, pathStr, data, {
|
|
880
|
+
validateRequiredHeaders(this.config.mustHeaderKeys, headers);
|
|
881
|
+
const { parsedQuery, pathStr } = await validateEndpointParams(this.config, {
|
|
882
|
+
data,
|
|
807
883
|
query,
|
|
884
|
+
pathParams
|
|
885
|
+
});
|
|
886
|
+
const { data: responseData, status } = await this.client.request(this.config.method, pathStr, data, {
|
|
887
|
+
query: toRequestQuery(parsedQuery),
|
|
808
888
|
headers,
|
|
809
889
|
baseUrlKey: this.config.advanced?.baseUrlKey,
|
|
810
890
|
skipAuth: this.config.advanced?.skipAuth,
|
|
811
891
|
skipRetry: this.config.advanced?.skipRetry,
|
|
812
892
|
signal
|
|
813
893
|
});
|
|
894
|
+
if (this.config.advanced?.skipResponseValidation) return responseData;
|
|
814
895
|
const schema = this.config.response;
|
|
815
|
-
if (
|
|
816
|
-
|
|
896
|
+
if (isStandardSchema(schema)) return await parseOrThrow(schema, responseData, {
|
|
897
|
+
label: `Response body for status ${status}`,
|
|
898
|
+
status
|
|
899
|
+
});
|
|
817
900
|
const specificSchema = schema[status];
|
|
818
901
|
if (!specificSchema) throw new SchemaDefinitionError(status);
|
|
819
|
-
return await parseOrThrow(specificSchema, responseData
|
|
902
|
+
return await parseOrThrow(specificSchema, responseData, {
|
|
903
|
+
label: `Response body for status ${status}`,
|
|
904
|
+
status
|
|
905
|
+
});
|
|
820
906
|
}
|
|
821
907
|
};
|
|
822
908
|
//#endregion
|
|
909
|
+
//#region lib/http/request-utils.ts
|
|
910
|
+
function serializeRequestBody(body, headers) {
|
|
911
|
+
if (body == null) return void 0;
|
|
912
|
+
if (body instanceof FormData) {
|
|
913
|
+
delete headers["Content-Type"];
|
|
914
|
+
return body;
|
|
915
|
+
}
|
|
916
|
+
if (body instanceof Blob || body instanceof ArrayBuffer) return body;
|
|
917
|
+
if (headers["Content-Type"]?.includes("json")) return JSON.stringify(body);
|
|
918
|
+
return String(body);
|
|
919
|
+
}
|
|
920
|
+
function isBinaryContentType(contentType) {
|
|
921
|
+
return contentType.includes("application/octet-stream") || contentType.includes("application/pdf") || contentType.includes("image/") || contentType.includes("video/") || contentType.includes("audio/") || contentType.startsWith("application/zip") || contentType.startsWith("application/x-");
|
|
922
|
+
}
|
|
923
|
+
async function parseResponseData(res, method, url) {
|
|
924
|
+
if (res.status === 204 || res.status === 205) return void 0;
|
|
925
|
+
const contentType = res.headers.get("content-type") || "";
|
|
926
|
+
try {
|
|
927
|
+
if (contentType.includes("json")) {
|
|
928
|
+
const text = await res.text();
|
|
929
|
+
return text ? JSON.parse(text) : void 0;
|
|
930
|
+
}
|
|
931
|
+
if (isBinaryContentType(contentType)) return await res.blob();
|
|
932
|
+
return await res.text();
|
|
933
|
+
} catch (error) {
|
|
934
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
935
|
+
throw new ApiError(`Failed to parse response body from ${method} ${url} (status ${res.status}): ${reason}`, {
|
|
936
|
+
status: res.status,
|
|
937
|
+
method,
|
|
938
|
+
url,
|
|
939
|
+
cause: error,
|
|
940
|
+
details: { contentType }
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
function getResponseMessage(details) {
|
|
945
|
+
if (typeof details === "string") return details.trim() || void 0;
|
|
946
|
+
if (!details || typeof details !== "object") return void 0;
|
|
947
|
+
const record = details;
|
|
948
|
+
for (const key of [
|
|
949
|
+
"message",
|
|
950
|
+
"error",
|
|
951
|
+
"title",
|
|
952
|
+
"detail"
|
|
953
|
+
]) {
|
|
954
|
+
const value = record[key];
|
|
955
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
function createStatusError(method, url, res, details) {
|
|
959
|
+
const statusText = res.statusText ? ` ${res.statusText}` : "";
|
|
960
|
+
const responseMessage = getResponseMessage(details);
|
|
961
|
+
const suffix = responseMessage ? `: ${responseMessage}` : "";
|
|
962
|
+
return new ApiError(`Request failed: ${method} ${url} returned ${res.status}${statusText}${suffix}`, {
|
|
963
|
+
status: res.status,
|
|
964
|
+
method,
|
|
965
|
+
url,
|
|
966
|
+
details
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
function createNetworkError(method, url, error) {
|
|
970
|
+
if (error instanceof ApiError) return error;
|
|
971
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
972
|
+
const name = error instanceof Error ? error.name : "Error";
|
|
973
|
+
return new ApiError(`${name === "AbortError" || name === "TimeoutError" ? "Request aborted" : "Network request failed"}: ${method} ${url}: ${reason}`, {
|
|
974
|
+
method,
|
|
975
|
+
url,
|
|
976
|
+
cause: error,
|
|
977
|
+
details: error && typeof error === "object" ? { name } : void 0
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
function isRetryableResponse(retryPolicy, method, status, retryAttempt, skipRetry) {
|
|
981
|
+
return !skipRetry && retryPolicy.maxAttempts > 0 && retryAttempt < retryPolicy.maxAttempts && !!retryPolicy.retryStatusCodes?.includes(status) && !!retryPolicy.retryMethods?.includes(method);
|
|
982
|
+
}
|
|
983
|
+
function getRetryDelay(retryPolicy, retryAttempt, response) {
|
|
984
|
+
const exponentialDelay = retryPolicy.baseDelayMs * 2 ** (retryAttempt - 1);
|
|
985
|
+
if (!retryPolicy.respectRetryAfter) return {
|
|
986
|
+
delay: exponentialDelay,
|
|
987
|
+
usedRetryAfter: false
|
|
988
|
+
};
|
|
989
|
+
const retryAfterHeader = response.headers.get("Retry-After") || response.headers.get("retry-after");
|
|
990
|
+
if (retryAfterHeader) {
|
|
991
|
+
const parsed = parseInt(retryAfterHeader, 10);
|
|
992
|
+
if (Number.isFinite(parsed) && parsed > 0) return {
|
|
993
|
+
delay: parsed * 1e3,
|
|
994
|
+
usedRetryAfter: true
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
return {
|
|
998
|
+
delay: exponentialDelay,
|
|
999
|
+
usedRetryAfter: false
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
function recordSuccessfulRequest(logger, metrics, method, path, url, status, durationMs) {
|
|
1003
|
+
logger.info("HTTP request successful", {
|
|
1004
|
+
method,
|
|
1005
|
+
url,
|
|
1006
|
+
status,
|
|
1007
|
+
durationMs
|
|
1008
|
+
});
|
|
1009
|
+
metrics.collect({
|
|
1010
|
+
method,
|
|
1011
|
+
path,
|
|
1012
|
+
status,
|
|
1013
|
+
durationMs,
|
|
1014
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1015
|
+
success: true
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
function recordFailedRequest(logger, metrics, method, path, url, durationMs, error) {
|
|
1019
|
+
logger.error("HTTP request failed", error, {
|
|
1020
|
+
method,
|
|
1021
|
+
url,
|
|
1022
|
+
durationMs
|
|
1023
|
+
});
|
|
1024
|
+
metrics.collect({
|
|
1025
|
+
method,
|
|
1026
|
+
path,
|
|
1027
|
+
status: error instanceof ApiError ? error.status : void 0,
|
|
1028
|
+
durationMs,
|
|
1029
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1030
|
+
success: false,
|
|
1031
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Re-applies auth to an existing AuthContext, cloning headers first so that
|
|
1036
|
+
* a fresh token overwrites the stale one from the previous attempt.
|
|
1037
|
+
*/
|
|
1038
|
+
async function reapplyAuth(auth, ctx) {
|
|
1039
|
+
const originalHeaders = ctx.init.headers;
|
|
1040
|
+
const clonedHeaders = typeof originalHeaders === "object" && !(originalHeaders instanceof Headers) && !Array.isArray(originalHeaders) ? { ...originalHeaders } : originalHeaders;
|
|
1041
|
+
ctx.init = {
|
|
1042
|
+
...ctx.init,
|
|
1043
|
+
headers: clonedHeaders
|
|
1044
|
+
};
|
|
1045
|
+
await auth.apply(ctx);
|
|
1046
|
+
}
|
|
1047
|
+
function startRequestTimeout(timeoutMs, hasExternalSignal, controller) {
|
|
1048
|
+
if (!timeoutMs || hasExternalSignal) return void 0;
|
|
1049
|
+
return setTimeout(() => {
|
|
1050
|
+
const timeoutError = /* @__PURE__ */ new Error("Request timeout");
|
|
1051
|
+
timeoutError.name = "TimeoutError";
|
|
1052
|
+
controller.abort(timeoutError);
|
|
1053
|
+
}, timeoutMs);
|
|
1054
|
+
}
|
|
1055
|
+
//#endregion
|
|
823
1056
|
//#region lib/http/http-client.ts
|
|
824
1057
|
/**
|
|
825
|
-
* HTTP client with built-in authentication, and interceptors.
|
|
1058
|
+
* HTTP client with built-in authentication, retry, and interceptors.
|
|
826
1059
|
* Supports multiple base URLs, type-safe requests, and comprehensive error handling.
|
|
827
1060
|
*
|
|
828
1061
|
* @example
|
|
@@ -832,17 +1065,9 @@ var EndpointImpl = class {
|
|
|
832
1065
|
* headers: { 'Content-Type': 'application/json' },
|
|
833
1066
|
* timeout: { requestTimeoutMs: 30000 }
|
|
834
1067
|
* });
|
|
835
|
-
*
|
|
836
|
-
* const { data } = await client.request('GET', '/users', undefined, { query: { page: 1 } });
|
|
837
1068
|
* ```
|
|
838
1069
|
*/
|
|
839
1070
|
var HttpClient = class {
|
|
840
|
-
/**
|
|
841
|
-
* Creates a new HTTP client instance.
|
|
842
|
-
*
|
|
843
|
-
* @param opts - Client configuration options
|
|
844
|
-
* @throws {Error} If no fetch implementation is available
|
|
845
|
-
*/
|
|
846
1071
|
constructor(opts) {
|
|
847
1072
|
this.fetchImpl = opts.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
848
1073
|
if (!this.fetchImpl) throw new Error("No fetch implementation found. Pass one via options.fetch.");
|
|
@@ -859,20 +1084,12 @@ var HttpClient = class {
|
|
|
859
1084
|
if (this.retryPolicy.baseDelayMs < 0) throw new Error("retry.baseDelayMs must be non-negative");
|
|
860
1085
|
this.timeoutMs = opts.timeout?.requestTimeoutMs;
|
|
861
1086
|
if (this.timeoutMs !== void 0 && this.timeoutMs < 0) throw new Error("timeout.requestTimeoutMs must be non-negative");
|
|
862
|
-
this.auth = opts
|
|
1087
|
+
this.auth = opts.auth ?? new NoAuth();
|
|
863
1088
|
this.logger = new LoggerUtil(opts.logger ?? new NoOpLogger());
|
|
864
1089
|
this.metrics = opts.metrics ?? new NoOpMetricsCollector();
|
|
865
1090
|
this.onUnauthenticated = opts.onUnauthenticated;
|
|
866
1091
|
}
|
|
867
|
-
/**
|
|
868
|
-
* Set or update the authentication provider.
|
|
869
|
-
*
|
|
870
|
-
* @param auth - Authentication provider instance
|
|
871
|
-
* @example
|
|
872
|
-
* ```ts
|
|
873
|
-
* client.setAuth(new BearerTokenAuth(() => getToken()));
|
|
874
|
-
* ```
|
|
875
|
-
*/
|
|
1092
|
+
/** Set or update the authentication provider at runtime. */
|
|
876
1093
|
setAuth(auth) {
|
|
877
1094
|
this.auth = auth;
|
|
878
1095
|
}
|
|
@@ -885,20 +1102,12 @@ var HttpClient = class {
|
|
|
885
1102
|
}
|
|
886
1103
|
return url.replace(/\/$/, "");
|
|
887
1104
|
}
|
|
888
|
-
/**
|
|
889
|
-
* Run all registered before-request hooks.
|
|
890
|
-
* @private
|
|
891
|
-
*/
|
|
892
1105
|
async runBeforeHooks(url, init) {
|
|
893
1106
|
for (const h of this.interceptors.beforeRequest ?? []) await h({
|
|
894
1107
|
url,
|
|
895
1108
|
init
|
|
896
1109
|
});
|
|
897
1110
|
}
|
|
898
|
-
/**
|
|
899
|
-
* Run all registered after-response hooks.
|
|
900
|
-
* @private
|
|
901
|
-
*/
|
|
902
1111
|
async runAfterHooks(req, res, parsed) {
|
|
903
1112
|
for (const h of this.interceptors.afterResponse ?? []) await h({
|
|
904
1113
|
request: req,
|
|
@@ -907,19 +1116,40 @@ var HttpClient = class {
|
|
|
907
1116
|
});
|
|
908
1117
|
}
|
|
909
1118
|
/**
|
|
910
|
-
*
|
|
911
|
-
*
|
|
912
|
-
* @returns Object mapping base URL keys to their resolved URLs
|
|
1119
|
+
* Returns retry info if the response should be retried, or null if not.
|
|
913
1120
|
*/
|
|
914
|
-
|
|
915
|
-
|
|
1121
|
+
async getRetryInfo(res, url, method, status, attempt, skipRetry) {
|
|
1122
|
+
if (!isRetryableResponse(this.retryPolicy, method, status, attempt, skipRetry)) return null;
|
|
1123
|
+
if (this.retryPolicy.shouldRetry) {
|
|
1124
|
+
if (!await this.retryPolicy.shouldRetry({
|
|
1125
|
+
url,
|
|
1126
|
+
method,
|
|
1127
|
+
status,
|
|
1128
|
+
attempt,
|
|
1129
|
+
response: res.clone()
|
|
1130
|
+
})) return null;
|
|
1131
|
+
}
|
|
1132
|
+
return getRetryDelay(this.retryPolicy, attempt + 1, res);
|
|
1133
|
+
}
|
|
1134
|
+
logRetry(method, url, status, attempt, info) {
|
|
1135
|
+
const suffix = info.usedRetryAfter ? "due to Retry-After header" : `attempt ${attempt}`;
|
|
1136
|
+
this.logger.warn(`Request failed with status ${status}. Retrying ${suffix} after ${info.delay}ms...`, {
|
|
1137
|
+
method,
|
|
1138
|
+
url,
|
|
1139
|
+
status,
|
|
1140
|
+
retryAttempt: attempt
|
|
1141
|
+
});
|
|
916
1142
|
}
|
|
917
1143
|
/**
|
|
918
|
-
*
|
|
919
|
-
*
|
|
920
|
-
* @param key - Base URL key (defaults to 'default' if not provided)
|
|
921
|
-
* @returns Resolved base URL string
|
|
1144
|
+
* Checks whether the 401 response should trigger a token refresh + retry.
|
|
922
1145
|
*/
|
|
1146
|
+
async shouldRefresh(res, refreshAttempted) {
|
|
1147
|
+
if (res.status !== 401 || !this.onUnauthenticated || refreshAttempted) return false;
|
|
1148
|
+
return this.onUnauthenticated(res.clone());
|
|
1149
|
+
}
|
|
1150
|
+
getBaseUrls() {
|
|
1151
|
+
return this.baseUrls;
|
|
1152
|
+
}
|
|
923
1153
|
getBaseUrl(key) {
|
|
924
1154
|
return this.resolveBaseUrl(key);
|
|
925
1155
|
}
|
|
@@ -935,257 +1165,120 @@ var HttpClient = class {
|
|
|
935
1165
|
getLogger() {
|
|
936
1166
|
return this.logger;
|
|
937
1167
|
}
|
|
1168
|
+
/** @internal */
|
|
1169
|
+
getFetch() {
|
|
1170
|
+
return this.fetchImpl;
|
|
1171
|
+
}
|
|
938
1172
|
/**
|
|
939
1173
|
* Make an HTTP request with automatic retry, authentication, and validation.
|
|
940
1174
|
*
|
|
941
|
-
* @param method - HTTP method (GET, POST, PUT, etc.)
|
|
942
|
-
* @param path - Request path (will be appended to base URL)
|
|
943
|
-
* @param body - Request body (will be JSON.stringify'd if Content-Type is json)
|
|
944
|
-
* @param options - Additional request options (headers, query params, etc.)
|
|
945
|
-
* @returns Promise resolving to response data and Response object
|
|
946
|
-
* @throws {ApiError} If request fails or response validation fails
|
|
947
|
-
*
|
|
948
1175
|
* @example
|
|
949
1176
|
* ```ts
|
|
950
|
-
* const { data
|
|
951
|
-
* query: { page: 1
|
|
1177
|
+
* const { data } = await client.request('GET', '/users', undefined, {
|
|
1178
|
+
* query: { page: 1 },
|
|
952
1179
|
* headers: { 'X-Custom': 'value' }
|
|
953
1180
|
* });
|
|
954
1181
|
* ```
|
|
955
1182
|
*/
|
|
956
1183
|
async request(method, path, body, options) {
|
|
957
1184
|
const startTime = Date.now();
|
|
958
|
-
|
|
959
|
-
this.logger.debug("HTTP request initiated", {
|
|
960
|
-
method,
|
|
961
|
-
path,
|
|
962
|
-
baseUrlKey: options?.baseUrlKey,
|
|
963
|
-
hasBody: body !== void 0
|
|
964
|
-
});
|
|
1185
|
+
const base = this.resolveBaseUrl(options?.baseUrlKey);
|
|
965
1186
|
const headers = {
|
|
966
1187
|
...this.headers,
|
|
967
1188
|
...options?.headers ?? {}
|
|
968
1189
|
};
|
|
969
1190
|
const controller = new AbortController();
|
|
970
1191
|
const signal = options?.signal ?? controller.signal;
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
else requestBody = String(body);
|
|
1192
|
+
this.logger.debug("HTTP request initiated", {
|
|
1193
|
+
method,
|
|
1194
|
+
path,
|
|
1195
|
+
baseUrlKey: options?.baseUrlKey,
|
|
1196
|
+
hasBody: body !== void 0
|
|
1197
|
+
});
|
|
978
1198
|
const init = {
|
|
979
1199
|
method,
|
|
980
1200
|
headers,
|
|
981
|
-
body:
|
|
1201
|
+
body: serializeRequestBody(body, headers),
|
|
982
1202
|
signal
|
|
983
1203
|
};
|
|
984
|
-
|
|
985
|
-
url
|
|
1204
|
+
const authCtx = {
|
|
1205
|
+
url: `${base}${path}${toQueryString(options?.query)}`,
|
|
986
1206
|
init,
|
|
987
1207
|
options
|
|
988
|
-
}
|
|
989
|
-
if (
|
|
990
|
-
await this.runBeforeHooks(url, init);
|
|
1208
|
+
};
|
|
1209
|
+
if (!options?.skipAuth) await this.auth.apply(authCtx);
|
|
1210
|
+
await this.runBeforeHooks(authCtx.url, authCtx.init);
|
|
991
1211
|
let refreshAttempted = false;
|
|
992
1212
|
let retryAttempt = 0;
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
if (
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
controller.abort(timeoutError);
|
|
1000
|
-
}, this.timeoutMs);
|
|
1213
|
+
while (true) {
|
|
1214
|
+
const timeoutId = startRequestTimeout(this.timeoutMs, !!options?.signal, controller);
|
|
1215
|
+
try {
|
|
1216
|
+
if (refreshAttempted && !options?.skipAuth) await reapplyAuth(this.auth, authCtx);
|
|
1217
|
+
const req = new Request(authCtx.url, authCtx.init);
|
|
1218
|
+
let res;
|
|
1001
1219
|
try {
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
url,
|
|
1009
|
-
init: freshInit,
|
|
1010
|
-
options
|
|
1011
|
-
});
|
|
1012
|
-
init.headers = freshInit.headers;
|
|
1013
|
-
}
|
|
1014
|
-
const req = new Request(url, init);
|
|
1015
|
-
const res = await this.fetchImpl(req);
|
|
1016
|
-
if (res.status === 401 && this.onUnauthenticated && !refreshAttempted) {
|
|
1017
|
-
if (await this.onUnauthenticated(res.clone())) {
|
|
1018
|
-
refreshAttempted = true;
|
|
1019
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
1020
|
-
continue;
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
const status = res.status;
|
|
1024
|
-
const contentType = res.headers.get("content-type") || "";
|
|
1025
|
-
if (!res.ok) {
|
|
1026
|
-
if (!options?.skipRetry && this.retryPolicy.maxAttempts > 0 && retryAttempt < this.retryPolicy.maxAttempts && this.retryPolicy.retryStatusCodes?.includes(status) && this.retryPolicy.retryMethods?.includes(method)) {
|
|
1027
|
-
let shouldRetry = true;
|
|
1028
|
-
if (this.retryPolicy.shouldRetry) shouldRetry = await this.retryPolicy.shouldRetry({
|
|
1029
|
-
url,
|
|
1030
|
-
method,
|
|
1031
|
-
status,
|
|
1032
|
-
attempt: retryAttempt,
|
|
1033
|
-
response: res.clone()
|
|
1034
|
-
});
|
|
1035
|
-
if (shouldRetry) {
|
|
1036
|
-
retryAttempt++;
|
|
1037
|
-
let delay = this.retryPolicy.baseDelayMs * 2 ** (retryAttempt - 1);
|
|
1038
|
-
if (this.retryPolicy.respectRetryAfter) {
|
|
1039
|
-
const retryAfter = res.headers.get("Retry-After") || res.headers.get("retry-after");
|
|
1040
|
-
if (retryAfter) {
|
|
1041
|
-
delay = parseInt(retryAfter, 10) * 1e3;
|
|
1042
|
-
this.logger.warn(`Request failed with status ${status}. Retrying after ${delay}ms due to Retry-After header...`, {
|
|
1043
|
-
method,
|
|
1044
|
-
url,
|
|
1045
|
-
status,
|
|
1046
|
-
retryAttempt: retryAttempt + 1
|
|
1047
|
-
});
|
|
1048
|
-
}
|
|
1049
|
-
} else this.logger.warn(`Request failed with status ${status}. Retrying attempt ${retryAttempt} after ${delay}ms...`, {
|
|
1050
|
-
method,
|
|
1051
|
-
url,
|
|
1052
|
-
status,
|
|
1053
|
-
retryAttempt
|
|
1054
|
-
});
|
|
1055
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1056
|
-
continue;
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
let data;
|
|
1061
|
-
if (contentType.includes("json")) data = await res.json();
|
|
1062
|
-
else if (contentType.includes("application/octet-stream") || contentType.includes("application/pdf") || contentType.includes("image/") || contentType.includes("video/") || contentType.includes("audio/") || contentType.startsWith("application/zip") || contentType.startsWith("application/x-")) data = await res.blob();
|
|
1063
|
-
else data = await res.text();
|
|
1064
|
-
await this.runAfterHooks(new Request(url, init), res, data);
|
|
1065
|
-
const duration = Date.now() - startTime;
|
|
1066
|
-
this.logger.info("HTTP request successful", {
|
|
1067
|
-
method,
|
|
1068
|
-
url,
|
|
1069
|
-
status: res.status,
|
|
1070
|
-
durationMs: duration
|
|
1071
|
-
});
|
|
1072
|
-
this.metrics.collect({
|
|
1073
|
-
method,
|
|
1074
|
-
path,
|
|
1075
|
-
status: res.status,
|
|
1076
|
-
durationMs: duration,
|
|
1077
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1078
|
-
success: true
|
|
1079
|
-
});
|
|
1080
|
-
return {
|
|
1081
|
-
data,
|
|
1082
|
-
status
|
|
1083
|
-
};
|
|
1084
|
-
} catch (error) {
|
|
1085
|
-
const duration = Date.now() - startTime;
|
|
1086
|
-
this.logger.error("HTTP request failed", error, {
|
|
1087
|
-
method,
|
|
1088
|
-
url,
|
|
1089
|
-
durationMs: duration
|
|
1090
|
-
});
|
|
1091
|
-
this.metrics.collect({
|
|
1092
|
-
method,
|
|
1093
|
-
path,
|
|
1094
|
-
status: error instanceof ApiError ? error.status : void 0,
|
|
1095
|
-
durationMs: duration,
|
|
1096
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1097
|
-
success: false,
|
|
1098
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1099
|
-
});
|
|
1100
|
-
throw error;
|
|
1101
|
-
} finally {
|
|
1220
|
+
res = await this.fetchImpl(req);
|
|
1221
|
+
} catch (err) {
|
|
1222
|
+
throw createNetworkError(method, authCtx.url, err);
|
|
1223
|
+
}
|
|
1224
|
+
if (await this.shouldRefresh(res, refreshAttempted)) {
|
|
1225
|
+
refreshAttempted = true;
|
|
1102
1226
|
if (timeoutId) clearTimeout(timeoutId);
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
const status = res.status;
|
|
1230
|
+
if (!res.ok) {
|
|
1231
|
+
const retryInfo = await this.getRetryInfo(res, authCtx.url, method, status, retryAttempt, options?.skipRetry);
|
|
1232
|
+
if (retryInfo) {
|
|
1233
|
+
retryAttempt++;
|
|
1234
|
+
this.logRetry(method, authCtx.url, status, retryAttempt, retryInfo);
|
|
1235
|
+
await new Promise((resolve) => setTimeout(resolve, retryInfo.delay));
|
|
1236
|
+
continue;
|
|
1237
|
+
}
|
|
1103
1238
|
}
|
|
1239
|
+
const data = await parseResponseData(res, method, authCtx.url);
|
|
1240
|
+
await this.runAfterHooks(new Request(authCtx.url, authCtx.init), res, data);
|
|
1241
|
+
if (!res.ok) throw createStatusError(method, authCtx.url, res, data);
|
|
1242
|
+
recordSuccessfulRequest(this.logger, this.metrics, method, path, authCtx.url, res.status, Date.now() - startTime);
|
|
1243
|
+
return {
|
|
1244
|
+
data,
|
|
1245
|
+
status
|
|
1246
|
+
};
|
|
1247
|
+
} catch (error) {
|
|
1248
|
+
recordFailedRequest(this.logger, this.metrics, method, path, authCtx.url, Date.now() - startTime, error);
|
|
1249
|
+
throw error;
|
|
1250
|
+
} finally {
|
|
1251
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1104
1252
|
}
|
|
1105
|
-
}
|
|
1106
|
-
return doFetch();
|
|
1253
|
+
}
|
|
1107
1254
|
}
|
|
1108
|
-
/**
|
|
1109
|
-
* Convenience method for GET requests.
|
|
1110
|
-
*
|
|
1111
|
-
* @example
|
|
1112
|
-
* ```ts
|
|
1113
|
-
* const { data } = await client.get('/users', { query: { page: 1 } });
|
|
1114
|
-
* ```
|
|
1115
|
-
*/
|
|
1116
1255
|
async get(path, options) {
|
|
1117
1256
|
return this.request("GET", path, void 0, options);
|
|
1118
1257
|
}
|
|
1119
|
-
/**
|
|
1120
|
-
* Convenience method for POST requests.
|
|
1121
|
-
*
|
|
1122
|
-
* @example
|
|
1123
|
-
* ```ts
|
|
1124
|
-
* const { data } = await client.post('/users', { name: 'John' });
|
|
1125
|
-
* ```
|
|
1126
|
-
*/
|
|
1127
1258
|
async post(path, body, options) {
|
|
1128
1259
|
return this.request("POST", path, body, options);
|
|
1129
1260
|
}
|
|
1130
|
-
/**
|
|
1131
|
-
* Convenience method for PUT requests.
|
|
1132
|
-
*
|
|
1133
|
-
* @example
|
|
1134
|
-
* ```ts
|
|
1135
|
-
* const { data } = await client.put('/users/1', { name: 'John Updated' });
|
|
1136
|
-
* ```
|
|
1137
|
-
*/
|
|
1138
1261
|
async put(path, body, options) {
|
|
1139
1262
|
return this.request("PUT", path, body, options);
|
|
1140
1263
|
}
|
|
1141
|
-
/**
|
|
1142
|
-
* Convenience method for PATCH requests.
|
|
1143
|
-
*
|
|
1144
|
-
* @example
|
|
1145
|
-
* ```ts
|
|
1146
|
-
* const { data } = await client.patch('/users/1', { name: 'John' });
|
|
1147
|
-
* ```
|
|
1148
|
-
*/
|
|
1149
1264
|
async patch(path, body, options) {
|
|
1150
1265
|
return this.request("PATCH", path, body, options);
|
|
1151
1266
|
}
|
|
1152
|
-
/**
|
|
1153
|
-
* Convenience method for DELETE requests.
|
|
1154
|
-
*
|
|
1155
|
-
* @example
|
|
1156
|
-
* ```ts
|
|
1157
|
-
* const { data } = await client.delete('/users/1');
|
|
1158
|
-
* ```
|
|
1159
|
-
*/
|
|
1160
1267
|
async delete(path, options) {
|
|
1161
1268
|
return this.request("DELETE", path, void 0, options);
|
|
1162
1269
|
}
|
|
1163
1270
|
/**
|
|
1164
|
-
* Create a strongly-typed endpoint
|
|
1271
|
+
* Create a strongly-typed HTTP endpoint.
|
|
1165
1272
|
* Works with any Standard Schema-compatible library (Zod, Valibot, ArkType, etc.)
|
|
1166
1273
|
*
|
|
1167
|
-
* @param config - Endpoint configuration with schemas
|
|
1168
|
-
* @returns Endpoint call function
|
|
1169
|
-
*
|
|
1170
1274
|
* @example
|
|
1171
1275
|
* ```ts
|
|
1172
|
-
* // With Zod
|
|
1173
|
-
* import { z } from 'zod';
|
|
1174
1276
|
* const getUser = client.createEndpoint({
|
|
1175
1277
|
* method: 'GET',
|
|
1176
1278
|
* path: '/users/:id',
|
|
1177
1279
|
* response: z.object({ id: z.string(), name: z.string() }),
|
|
1178
1280
|
* pathParams: z.object({ id: z.string() }),
|
|
1179
1281
|
* });
|
|
1180
|
-
*
|
|
1181
|
-
* // With Valibot
|
|
1182
|
-
* import * as v from 'valibot';
|
|
1183
|
-
* const getUser = client.createEndpoint({
|
|
1184
|
-
* method: 'GET',
|
|
1185
|
-
* path: '/users/:id',
|
|
1186
|
-
* response: v.object({ id: v.string(), name: v.string() }),
|
|
1187
|
-
* pathParams: v.object({ id: v.string() }),
|
|
1188
|
-
* });
|
|
1189
1282
|
* ```
|
|
1190
1283
|
*/
|
|
1191
1284
|
createEndpoint(config) {
|
|
@@ -1193,25 +1286,40 @@ var HttpClient = class {
|
|
|
1193
1286
|
return (params) => endpoint.call(params);
|
|
1194
1287
|
}
|
|
1195
1288
|
/**
|
|
1196
|
-
* Create a strongly-typed WebSocket endpoint
|
|
1289
|
+
* Create a strongly-typed WebSocket endpoint.
|
|
1197
1290
|
*
|
|
1198
|
-
* @
|
|
1199
|
-
*
|
|
1291
|
+
* @example
|
|
1292
|
+
* ```ts
|
|
1293
|
+
* const chat = client.createWebSocket({
|
|
1294
|
+
* path: '/ws/chat',
|
|
1295
|
+
* send: z.object({ text: z.string() }),
|
|
1296
|
+
* receive: z.object({ text: z.string(), user: z.string() }),
|
|
1297
|
+
* });
|
|
1298
|
+
* const conn = await chat();
|
|
1299
|
+
* ```
|
|
1200
1300
|
*/
|
|
1201
1301
|
createWebSocket(config) {
|
|
1202
1302
|
return new WSEndpointImpl(this, config).createCall();
|
|
1203
1303
|
}
|
|
1204
1304
|
/**
|
|
1205
|
-
* Create a strongly-typed Server-Sent Events
|
|
1305
|
+
* Create a strongly-typed Server-Sent Events endpoint.
|
|
1206
1306
|
*
|
|
1207
|
-
* @
|
|
1208
|
-
*
|
|
1307
|
+
* @example
|
|
1308
|
+
* ```ts
|
|
1309
|
+
* const stream = client.createSSE({
|
|
1310
|
+
* method: 'GET',
|
|
1311
|
+
* path: '/events',
|
|
1312
|
+
* response: z.object({ message: z.string() }),
|
|
1313
|
+
* });
|
|
1314
|
+
* const conn = await stream();
|
|
1315
|
+
* conn.on('message', (data) => console.log(data));
|
|
1316
|
+
* ```
|
|
1209
1317
|
*/
|
|
1210
1318
|
createSSE(config) {
|
|
1211
1319
|
return new SSEEndpointImpl(this, config).createCall();
|
|
1212
1320
|
}
|
|
1213
1321
|
};
|
|
1214
1322
|
//#endregion
|
|
1215
|
-
export { ApiError, ApiKeyAuth, BearerTokenAuth, ConsoleLogger, ConsoleMetricsCollector, EndpointImpl, HTTPMethod, HTTPStatusCode, HttpClient, InMemoryMetricsCollector, LogLevel, LoggerUtil, NoAuth, NoOpLogger, NoOpMetricsCollector, SSEConnectionImpl, SSEEndpointImpl, SchemaDefinitionError, WSConnectionImpl, WSEndpointImpl, isStandardSchema, parseOrThrow, safeParse, toQueryString };
|
|
1323
|
+
export { ApiError, ApiKeyAuth, BearerTokenAuth, ConsoleLogger, ConsoleMetricsCollector, EndpointImpl, HTTPMethod, HTTPStatusCode, HttpClient, InMemoryMetricsCollector, LogLevel, LoggerUtil, NoAuth, NoOpLogger, NoOpMetricsCollector, SSEConnectionImpl, SSEEndpointImpl, SchemaDefinitionError, WSConnectionImpl, WSEndpointImpl, formatValidationIssues, isStandardSchema, parseOrThrow, safeParse, toQueryString, toRequestQuery };
|
|
1216
1324
|
|
|
1217
1325
|
//# sourceMappingURL=index.js.map
|