@speechall/sdk 1.0.0 → 2.0.4
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/.beads/README.md +81 -0
- package/.beads/config.yaml +62 -0
- package/.beads/issues.jsonl +46 -0
- package/.beads/metadata.json +4 -0
- package/.env.example +5 -0
- package/.fernignore +45 -0
- package/.gitattributes +3 -0
- package/.github/copilot-instructions.md +78 -0
- package/.github/workflows/auto-release-simple.yml.deprecated +106 -0
- package/.github/workflows/auto-release.yml +67 -0
- package/.github/workflows/ci.yml +41 -0
- package/.github/workflows/release.yml +57 -0
- package/AGENTS.md +94 -0
- package/CHANGELOG.md +58 -0
- package/CLAUDE.md +75 -0
- package/README.md +294 -155
- package/examples/CLAUDE.md +136 -0
- package/examples/advanced-options.ts +213 -0
- package/examples/basic-transcription.ts +66 -0
- package/examples/error-handling.ts +251 -0
- package/examples/list-models.ts +112 -0
- package/examples/remote-transcription.ts +60 -0
- package/fern/fern.config.json +4 -0
- package/fern/generators.yml +43 -0
- package/jest.config.js +11 -0
- package/package.json +26 -46
- package/regenerate.sh +45 -0
- package/scripts/fix-generated-code.sh +25 -0
- package/src/BaseClient.ts +82 -0
- package/src/Client.ts +30 -0
- package/src/api/errors/BadRequestError.ts +22 -0
- package/src/api/errors/GatewayTimeoutError.ts +22 -0
- package/src/api/errors/InternalServerError.ts +22 -0
- package/src/api/errors/NotFoundError.ts +22 -0
- package/src/api/errors/PaymentRequiredError.ts +22 -0
- package/src/api/errors/ServiceUnavailableError.ts +22 -0
- package/src/api/errors/TooManyRequestsError.ts +22 -0
- package/src/api/errors/UnauthorizedError.ts +22 -0
- package/src/api/errors/index.ts +8 -0
- package/src/api/index.ts +3 -0
- package/src/api/resources/index.ts +5 -0
- package/src/api/resources/replacementRules/client/Client.ts +148 -0
- package/src/api/resources/replacementRules/client/index.ts +1 -0
- package/src/api/resources/replacementRules/client/requests/CreateReplacementRulesetRequest.ts +25 -0
- package/src/api/resources/replacementRules/client/requests/index.ts +1 -0
- package/src/api/resources/replacementRules/index.ts +2 -0
- package/src/api/resources/replacementRules/types/CreateReplacementRulesetResponse.ts +6 -0
- package/src/api/resources/replacementRules/types/index.ts +1 -0
- package/src/api/resources/speechToText/client/Client.ts +275 -0
- package/src/api/resources/speechToText/client/index.ts +1 -0
- package/src/api/resources/speechToText/client/requests/RemoteTranscriptionConfiguration.ts +20 -0
- package/src/api/resources/speechToText/client/requests/TranscribeRequest.ts +26 -0
- package/src/api/resources/speechToText/client/requests/index.ts +2 -0
- package/src/api/resources/speechToText/index.ts +1 -0
- package/src/api/types/BaseTranscriptionConfiguration.ts +29 -0
- package/src/api/types/ErrorResponse.ts +11 -0
- package/src/api/types/ExactRule.ts +13 -0
- package/src/api/types/RegexGroupRule.ts +28 -0
- package/src/api/types/RegexRule.ts +28 -0
- package/src/api/types/ReplacementRule.ts +25 -0
- package/src/api/types/SpeechToTextModel.ts +90 -0
- package/src/api/types/TranscriptLanguageCode.ts +114 -0
- package/src/api/types/TranscriptOutputFormat.ts +18 -0
- package/src/api/types/TranscriptionDetailed.ts +19 -0
- package/src/api/types/TranscriptionModelIdentifier.ts +80 -0
- package/src/api/types/TranscriptionOnlyText.ts +11 -0
- package/src/api/types/TranscriptionProvider.ts +23 -0
- package/src/api/types/TranscriptionResponse.ts +8 -0
- package/src/api/types/TranscriptionSegment.ts +17 -0
- package/src/api/types/TranscriptionWord.ts +17 -0
- package/src/api/types/index.ts +16 -0
- package/src/auth/BearerAuthProvider.ts +37 -0
- package/src/auth/index.ts +1 -0
- package/src/core/auth/AuthProvider.ts +6 -0
- package/src/core/auth/AuthRequest.ts +9 -0
- package/src/core/auth/BasicAuth.ts +32 -0
- package/src/core/auth/BearerToken.ts +20 -0
- package/src/core/auth/NoOpAuthProvider.ts +8 -0
- package/src/core/auth/index.ts +5 -0
- package/src/core/base64.ts +27 -0
- package/src/core/exports.ts +2 -0
- package/src/core/fetcher/APIResponse.ts +23 -0
- package/src/core/fetcher/BinaryResponse.ts +34 -0
- package/src/core/fetcher/EndpointMetadata.ts +13 -0
- package/src/core/fetcher/EndpointSupplier.ts +14 -0
- package/src/core/fetcher/Fetcher.ts +391 -0
- package/src/core/fetcher/Headers.ts +93 -0
- package/src/core/fetcher/HttpResponsePromise.ts +116 -0
- package/src/core/fetcher/RawResponse.ts +61 -0
- package/src/core/fetcher/Supplier.ts +11 -0
- package/src/core/fetcher/createRequestUrl.ts +6 -0
- package/src/core/fetcher/getErrorResponseBody.ts +33 -0
- package/src/core/fetcher/getFetchFn.ts +3 -0
- package/src/core/fetcher/getHeader.ts +8 -0
- package/src/core/fetcher/getRequestBody.ts +20 -0
- package/src/core/fetcher/getResponseBody.ts +58 -0
- package/src/core/fetcher/index.ts +11 -0
- package/src/core/fetcher/makeRequest.ts +42 -0
- package/src/core/fetcher/requestWithRetries.ts +64 -0
- package/src/core/fetcher/signals.ts +26 -0
- package/src/core/file/exports.ts +1 -0
- package/src/core/file/file.ts +217 -0
- package/src/core/file/index.ts +2 -0
- package/src/core/file/types.ts +81 -0
- package/src/core/headers.ts +35 -0
- package/src/core/index.ts +7 -0
- package/src/core/json.ts +27 -0
- package/src/core/logging/exports.ts +19 -0
- package/src/core/logging/index.ts +1 -0
- package/src/core/logging/logger.ts +203 -0
- package/src/core/runtime/index.ts +1 -0
- package/src/core/runtime/runtime.ts +134 -0
- package/src/core/url/encodePathParam.ts +18 -0
- package/src/core/url/index.ts +3 -0
- package/src/core/url/join.ts +79 -0
- package/src/core/url/qs.ts +74 -0
- package/src/environments.ts +7 -0
- package/src/errors/SpeechallError.ts +58 -0
- package/src/errors/SpeechallTimeoutError.ts +13 -0
- package/src/errors/handleNonStatusCodeError.ts +37 -0
- package/src/errors/index.ts +2 -0
- package/src/exports.ts +1 -0
- package/src/index.ts +6 -0
- package/test-import.ts +17 -0
- package/tests/integration/api.test.ts +93 -0
- package/tests/unit/client.test.ts +91 -0
- package/tsconfig.json +20 -0
- package/dist/api.d.ts +0 -501
- package/dist/api.d.ts.map +0 -1
- package/dist/api.js +0 -610
- package/dist/base.d.ts +0 -32
- package/dist/base.d.ts.map +0 -1
- package/dist/base.js +0 -35
- package/dist/common.d.ts +0 -14
- package/dist/common.d.ts.map +0 -1
- package/dist/common.js +0 -91
- package/dist/configuration.d.ts +0 -23
- package/dist/configuration.d.ts.map +0 -1
- package/dist/configuration.js +0 -25
- package/dist/esm/api.js +0 -592
- package/dist/esm/base.js +0 -27
- package/dist/esm/common.js +0 -79
- package/dist/esm/configuration.js +0 -21
- package/dist/esm/example.js +0 -131
- package/dist/esm/index.js +0 -2
- package/dist/example.d.ts +0 -3
- package/dist/example.d.ts.map +0 -1
- package/dist/example.js +0 -133
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -18
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type BinaryResponse = {
|
|
2
|
+
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */
|
|
3
|
+
bodyUsed: Response["bodyUsed"];
|
|
4
|
+
/**
|
|
5
|
+
* Returns a ReadableStream of the response body.
|
|
6
|
+
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body)
|
|
7
|
+
*/
|
|
8
|
+
stream: () => Response["body"];
|
|
9
|
+
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/arrayBuffer) */
|
|
10
|
+
arrayBuffer: () => ReturnType<Response["arrayBuffer"]>;
|
|
11
|
+
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/blob) */
|
|
12
|
+
blob: () => ReturnType<Response["blob"]>;
|
|
13
|
+
/**
|
|
14
|
+
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bytes)
|
|
15
|
+
* Some versions of the Fetch API may not support this method.
|
|
16
|
+
*/
|
|
17
|
+
bytes?(): ReturnType<Response["bytes"]>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function getBinaryResponse(response: Response): BinaryResponse {
|
|
21
|
+
const binaryResponse: BinaryResponse = {
|
|
22
|
+
get bodyUsed() {
|
|
23
|
+
return response.bodyUsed;
|
|
24
|
+
},
|
|
25
|
+
stream: () => response.body,
|
|
26
|
+
arrayBuffer: response.arrayBuffer.bind(response),
|
|
27
|
+
blob: response.blob.bind(response),
|
|
28
|
+
};
|
|
29
|
+
if ("bytes" in response && typeof response.bytes === "function") {
|
|
30
|
+
binaryResponse.bytes = response.bytes.bind(response);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return binaryResponse;
|
|
34
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type SecuritySchemeKey = string;
|
|
2
|
+
/**
|
|
3
|
+
* A collection of security schemes, where the key is the name of the security scheme and the value is the list of scopes required for that scheme.
|
|
4
|
+
* All schemes in the collection must be satisfied for authentication to be successful.
|
|
5
|
+
*/
|
|
6
|
+
export type SecuritySchemeCollection = Record<SecuritySchemeKey, AuthScope[]>;
|
|
7
|
+
export type AuthScope = string;
|
|
8
|
+
export type EndpointMetadata = {
|
|
9
|
+
/**
|
|
10
|
+
* An array of security scheme collections. Each collection represents an alternative way to authenticate.
|
|
11
|
+
*/
|
|
12
|
+
security?: SecuritySchemeCollection[];
|
|
13
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { EndpointMetadata } from "./EndpointMetadata.js";
|
|
2
|
+
import type { Supplier } from "./Supplier.js";
|
|
3
|
+
|
|
4
|
+
type EndpointSupplierFn<T> = (arg: { endpointMetadata: EndpointMetadata }) => T | Promise<T>;
|
|
5
|
+
export type EndpointSupplier<T> = Supplier<T> | EndpointSupplierFn<T>;
|
|
6
|
+
export const EndpointSupplier = {
|
|
7
|
+
get: async <T>(supplier: EndpointSupplier<T>, arg: { endpointMetadata: EndpointMetadata }): Promise<T> => {
|
|
8
|
+
if (typeof supplier === "function") {
|
|
9
|
+
return (supplier as EndpointSupplierFn<T>)(arg);
|
|
10
|
+
} else {
|
|
11
|
+
return supplier;
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
};
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import { toJson } from "../json.js";
|
|
2
|
+
import { createLogger, type LogConfig, type Logger } from "../logging/logger.js";
|
|
3
|
+
import type { APIResponse } from "./APIResponse.js";
|
|
4
|
+
import { createRequestUrl } from "./createRequestUrl.js";
|
|
5
|
+
import type { EndpointMetadata } from "./EndpointMetadata.js";
|
|
6
|
+
import { EndpointSupplier } from "./EndpointSupplier.js";
|
|
7
|
+
import { getErrorResponseBody } from "./getErrorResponseBody.js";
|
|
8
|
+
import { getFetchFn } from "./getFetchFn.js";
|
|
9
|
+
import { getRequestBody } from "./getRequestBody.js";
|
|
10
|
+
import { getResponseBody } from "./getResponseBody.js";
|
|
11
|
+
import { Headers } from "./Headers.js";
|
|
12
|
+
import { makeRequest } from "./makeRequest.js";
|
|
13
|
+
import { abortRawResponse, toRawResponse, unknownRawResponse } from "./RawResponse.js";
|
|
14
|
+
import { requestWithRetries } from "./requestWithRetries.js";
|
|
15
|
+
|
|
16
|
+
export type FetchFunction = <R = unknown>(args: Fetcher.Args) => Promise<APIResponse<R, Fetcher.Error>>;
|
|
17
|
+
|
|
18
|
+
export declare namespace Fetcher {
|
|
19
|
+
export interface Args {
|
|
20
|
+
url: string;
|
|
21
|
+
method: string;
|
|
22
|
+
contentType?: string;
|
|
23
|
+
headers?: Record<string, string | EndpointSupplier<string | null | undefined> | null | undefined>;
|
|
24
|
+
queryParameters?: Record<string, unknown>;
|
|
25
|
+
body?: unknown;
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
maxRetries?: number;
|
|
28
|
+
withCredentials?: boolean;
|
|
29
|
+
abortSignal?: AbortSignal;
|
|
30
|
+
requestType?: "json" | "file" | "bytes" | "form" | "other";
|
|
31
|
+
responseType?: "json" | "blob" | "sse" | "streaming" | "text" | "arrayBuffer" | "binary-response";
|
|
32
|
+
duplex?: "half";
|
|
33
|
+
endpointMetadata?: EndpointMetadata;
|
|
34
|
+
fetchFn?: typeof fetch;
|
|
35
|
+
logging?: LogConfig | Logger;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type Error = FailedStatusCodeError | NonJsonError | BodyIsNullError | TimeoutError | UnknownError;
|
|
39
|
+
|
|
40
|
+
export interface FailedStatusCodeError {
|
|
41
|
+
reason: "status-code";
|
|
42
|
+
statusCode: number;
|
|
43
|
+
body: unknown;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface NonJsonError {
|
|
47
|
+
reason: "non-json";
|
|
48
|
+
statusCode: number;
|
|
49
|
+
rawBody: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface BodyIsNullError {
|
|
53
|
+
reason: "body-is-null";
|
|
54
|
+
statusCode: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface TimeoutError {
|
|
58
|
+
reason: "timeout";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface UnknownError {
|
|
62
|
+
reason: "unknown";
|
|
63
|
+
errorMessage: string;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const SENSITIVE_HEADERS = new Set([
|
|
68
|
+
"authorization",
|
|
69
|
+
"www-authenticate",
|
|
70
|
+
"x-api-key",
|
|
71
|
+
"api-key",
|
|
72
|
+
"apikey",
|
|
73
|
+
"x-api-token",
|
|
74
|
+
"x-auth-token",
|
|
75
|
+
"auth-token",
|
|
76
|
+
"cookie",
|
|
77
|
+
"set-cookie",
|
|
78
|
+
"proxy-authorization",
|
|
79
|
+
"proxy-authenticate",
|
|
80
|
+
"x-csrf-token",
|
|
81
|
+
"x-xsrf-token",
|
|
82
|
+
"x-session-token",
|
|
83
|
+
"x-access-token",
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
function redactHeaders(headers: Headers | Record<string, string>): Record<string, string> {
|
|
87
|
+
const filtered: Record<string, string> = {};
|
|
88
|
+
for (const [key, value] of headers instanceof Headers ? headers.entries() : Object.entries(headers)) {
|
|
89
|
+
if (SENSITIVE_HEADERS.has(key.toLowerCase())) {
|
|
90
|
+
filtered[key] = "[REDACTED]";
|
|
91
|
+
} else {
|
|
92
|
+
filtered[key] = value;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return filtered;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const SENSITIVE_QUERY_PARAMS = new Set([
|
|
99
|
+
"api_key",
|
|
100
|
+
"api-key",
|
|
101
|
+
"apikey",
|
|
102
|
+
"token",
|
|
103
|
+
"access_token",
|
|
104
|
+
"access-token",
|
|
105
|
+
"auth_token",
|
|
106
|
+
"auth-token",
|
|
107
|
+
"password",
|
|
108
|
+
"passwd",
|
|
109
|
+
"secret",
|
|
110
|
+
"api_secret",
|
|
111
|
+
"api-secret",
|
|
112
|
+
"apisecret",
|
|
113
|
+
"key",
|
|
114
|
+
"session",
|
|
115
|
+
"session_id",
|
|
116
|
+
"session-id",
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
function redactQueryParameters(queryParameters?: Record<string, unknown>): Record<string, unknown> | undefined {
|
|
120
|
+
if (queryParameters == null) {
|
|
121
|
+
return queryParameters;
|
|
122
|
+
}
|
|
123
|
+
const redacted: Record<string, unknown> = {};
|
|
124
|
+
for (const [key, value] of Object.entries(queryParameters)) {
|
|
125
|
+
if (SENSITIVE_QUERY_PARAMS.has(key.toLowerCase())) {
|
|
126
|
+
redacted[key] = "[REDACTED]";
|
|
127
|
+
} else {
|
|
128
|
+
redacted[key] = value;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return redacted;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function redactUrl(url: string): string {
|
|
135
|
+
const protocolIndex = url.indexOf("://");
|
|
136
|
+
if (protocolIndex === -1) return url;
|
|
137
|
+
|
|
138
|
+
const afterProtocol = protocolIndex + 3;
|
|
139
|
+
|
|
140
|
+
// Find the first delimiter that marks the end of the authority section
|
|
141
|
+
const pathStart = url.indexOf("/", afterProtocol);
|
|
142
|
+
let queryStart = url.indexOf("?", afterProtocol);
|
|
143
|
+
let fragmentStart = url.indexOf("#", afterProtocol);
|
|
144
|
+
|
|
145
|
+
const firstDelimiter = Math.min(
|
|
146
|
+
pathStart === -1 ? url.length : pathStart,
|
|
147
|
+
queryStart === -1 ? url.length : queryStart,
|
|
148
|
+
fragmentStart === -1 ? url.length : fragmentStart,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Find the LAST @ before the delimiter (handles multiple @ in credentials)
|
|
152
|
+
let atIndex = -1;
|
|
153
|
+
for (let i = afterProtocol; i < firstDelimiter; i++) {
|
|
154
|
+
if (url[i] === "@") {
|
|
155
|
+
atIndex = i;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (atIndex !== -1) {
|
|
160
|
+
url = `${url.slice(0, afterProtocol)}[REDACTED]@${url.slice(atIndex + 1)}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Recalculate queryStart since url might have changed
|
|
164
|
+
queryStart = url.indexOf("?");
|
|
165
|
+
if (queryStart === -1) return url;
|
|
166
|
+
|
|
167
|
+
fragmentStart = url.indexOf("#", queryStart);
|
|
168
|
+
const queryEnd = fragmentStart !== -1 ? fragmentStart : url.length;
|
|
169
|
+
const queryString = url.slice(queryStart + 1, queryEnd);
|
|
170
|
+
|
|
171
|
+
if (queryString.length === 0) return url;
|
|
172
|
+
|
|
173
|
+
// FAST PATH: Quick check if any sensitive keywords present
|
|
174
|
+
// Using indexOf is faster than regex for simple substring matching
|
|
175
|
+
const lower = queryString.toLowerCase();
|
|
176
|
+
const hasSensitive =
|
|
177
|
+
lower.includes("token") ||
|
|
178
|
+
lower.includes("key") ||
|
|
179
|
+
lower.includes("password") ||
|
|
180
|
+
lower.includes("passwd") ||
|
|
181
|
+
lower.includes("secret") ||
|
|
182
|
+
lower.includes("session") ||
|
|
183
|
+
lower.includes("auth");
|
|
184
|
+
|
|
185
|
+
if (!hasSensitive) {
|
|
186
|
+
return url;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// SLOW PATH: Parse and redact
|
|
190
|
+
const redactedParams: string[] = [];
|
|
191
|
+
const params = queryString.split("&");
|
|
192
|
+
|
|
193
|
+
for (const param of params) {
|
|
194
|
+
const equalIndex = param.indexOf("=");
|
|
195
|
+
if (equalIndex === -1) {
|
|
196
|
+
redactedParams.push(param);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const key = param.slice(0, equalIndex);
|
|
201
|
+
let shouldRedact = SENSITIVE_QUERY_PARAMS.has(key.toLowerCase());
|
|
202
|
+
|
|
203
|
+
if (!shouldRedact && key.includes("%")) {
|
|
204
|
+
try {
|
|
205
|
+
const decodedKey = decodeURIComponent(key);
|
|
206
|
+
shouldRedact = SENSITIVE_QUERY_PARAMS.has(decodedKey.toLowerCase());
|
|
207
|
+
} catch {}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
redactedParams.push(shouldRedact ? `${key}=[REDACTED]` : param);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return url.slice(0, queryStart + 1) + redactedParams.join("&") + url.slice(queryEnd);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function getHeaders(args: Fetcher.Args): Promise<Headers> {
|
|
217
|
+
const newHeaders: Headers = new Headers();
|
|
218
|
+
|
|
219
|
+
newHeaders.set(
|
|
220
|
+
"Accept",
|
|
221
|
+
args.responseType === "json" ? "application/json" : args.responseType === "text" ? "text/plain" : "*/*",
|
|
222
|
+
);
|
|
223
|
+
if (args.body !== undefined && args.contentType != null) {
|
|
224
|
+
newHeaders.set("Content-Type", args.contentType);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (args.headers == null) {
|
|
228
|
+
return newHeaders;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
for (const [key, value] of Object.entries(args.headers)) {
|
|
232
|
+
const result = await EndpointSupplier.get(value, { endpointMetadata: args.endpointMetadata ?? {} });
|
|
233
|
+
if (typeof result === "string") {
|
|
234
|
+
newHeaders.set(key, result);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (result == null) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
newHeaders.set(key, `${result}`);
|
|
241
|
+
}
|
|
242
|
+
return newHeaders;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export async function fetcherImpl<R = unknown>(args: Fetcher.Args): Promise<APIResponse<R, Fetcher.Error>> {
|
|
246
|
+
const url = createRequestUrl(args.url, args.queryParameters);
|
|
247
|
+
const requestBody: BodyInit | undefined = await getRequestBody({
|
|
248
|
+
body: args.body,
|
|
249
|
+
type: args.requestType ?? "other",
|
|
250
|
+
});
|
|
251
|
+
const fetchFn = args.fetchFn ?? (await getFetchFn());
|
|
252
|
+
const headers = await getHeaders(args);
|
|
253
|
+
const logger = createLogger(args.logging);
|
|
254
|
+
|
|
255
|
+
if (logger.isDebug()) {
|
|
256
|
+
const metadata = {
|
|
257
|
+
method: args.method,
|
|
258
|
+
url: redactUrl(url),
|
|
259
|
+
headers: redactHeaders(headers),
|
|
260
|
+
queryParameters: redactQueryParameters(args.queryParameters),
|
|
261
|
+
hasBody: requestBody != null,
|
|
262
|
+
};
|
|
263
|
+
logger.debug("Making HTTP request", metadata);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const response = await requestWithRetries(
|
|
268
|
+
async () =>
|
|
269
|
+
makeRequest(
|
|
270
|
+
fetchFn,
|
|
271
|
+
url,
|
|
272
|
+
args.method,
|
|
273
|
+
headers,
|
|
274
|
+
requestBody,
|
|
275
|
+
args.timeoutMs,
|
|
276
|
+
args.abortSignal,
|
|
277
|
+
args.withCredentials,
|
|
278
|
+
args.duplex,
|
|
279
|
+
),
|
|
280
|
+
args.maxRetries,
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
if (response.status >= 200 && response.status < 400) {
|
|
284
|
+
if (logger.isDebug()) {
|
|
285
|
+
const metadata = {
|
|
286
|
+
method: args.method,
|
|
287
|
+
url: redactUrl(url),
|
|
288
|
+
statusCode: response.status,
|
|
289
|
+
responseHeaders: redactHeaders(response.headers),
|
|
290
|
+
};
|
|
291
|
+
logger.debug("HTTP request succeeded", metadata);
|
|
292
|
+
}
|
|
293
|
+
const body = await getResponseBody(response, args.responseType);
|
|
294
|
+
return {
|
|
295
|
+
ok: true,
|
|
296
|
+
body: body as R,
|
|
297
|
+
headers: response.headers,
|
|
298
|
+
rawResponse: toRawResponse(response),
|
|
299
|
+
};
|
|
300
|
+
} else {
|
|
301
|
+
if (logger.isError()) {
|
|
302
|
+
const metadata = {
|
|
303
|
+
method: args.method,
|
|
304
|
+
url: redactUrl(url),
|
|
305
|
+
statusCode: response.status,
|
|
306
|
+
responseHeaders: redactHeaders(Object.fromEntries(response.headers.entries())),
|
|
307
|
+
};
|
|
308
|
+
logger.error("HTTP request failed with error status", metadata);
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
ok: false,
|
|
312
|
+
error: {
|
|
313
|
+
reason: "status-code",
|
|
314
|
+
statusCode: response.status,
|
|
315
|
+
body: await getErrorResponseBody(response),
|
|
316
|
+
},
|
|
317
|
+
rawResponse: toRawResponse(response),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
} catch (error) {
|
|
321
|
+
if (args.abortSignal?.aborted) {
|
|
322
|
+
if (logger.isError()) {
|
|
323
|
+
const metadata = {
|
|
324
|
+
method: args.method,
|
|
325
|
+
url: redactUrl(url),
|
|
326
|
+
};
|
|
327
|
+
logger.error("HTTP request was aborted", metadata);
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
ok: false,
|
|
331
|
+
error: {
|
|
332
|
+
reason: "unknown",
|
|
333
|
+
errorMessage: "The user aborted a request",
|
|
334
|
+
},
|
|
335
|
+
rawResponse: abortRawResponse,
|
|
336
|
+
};
|
|
337
|
+
} else if (error instanceof Error && error.name === "AbortError") {
|
|
338
|
+
if (logger.isError()) {
|
|
339
|
+
const metadata = {
|
|
340
|
+
method: args.method,
|
|
341
|
+
url: redactUrl(url),
|
|
342
|
+
timeoutMs: args.timeoutMs,
|
|
343
|
+
};
|
|
344
|
+
logger.error("HTTP request timed out", metadata);
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
ok: false,
|
|
348
|
+
error: {
|
|
349
|
+
reason: "timeout",
|
|
350
|
+
},
|
|
351
|
+
rawResponse: abortRawResponse,
|
|
352
|
+
};
|
|
353
|
+
} else if (error instanceof Error) {
|
|
354
|
+
if (logger.isError()) {
|
|
355
|
+
const metadata = {
|
|
356
|
+
method: args.method,
|
|
357
|
+
url: redactUrl(url),
|
|
358
|
+
errorMessage: error.message,
|
|
359
|
+
};
|
|
360
|
+
logger.error("HTTP request failed with error", metadata);
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
ok: false,
|
|
364
|
+
error: {
|
|
365
|
+
reason: "unknown",
|
|
366
|
+
errorMessage: error.message,
|
|
367
|
+
},
|
|
368
|
+
rawResponse: unknownRawResponse,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (logger.isError()) {
|
|
373
|
+
const metadata = {
|
|
374
|
+
method: args.method,
|
|
375
|
+
url: redactUrl(url),
|
|
376
|
+
error: toJson(error),
|
|
377
|
+
};
|
|
378
|
+
logger.error("HTTP request failed with unknown error", metadata);
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
ok: false,
|
|
382
|
+
error: {
|
|
383
|
+
reason: "unknown",
|
|
384
|
+
errorMessage: toJson(error),
|
|
385
|
+
},
|
|
386
|
+
rawResponse: unknownRawResponse,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export const fetcher: FetchFunction = fetcherImpl;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
let Headers: typeof globalThis.Headers;
|
|
2
|
+
|
|
3
|
+
if (typeof globalThis.Headers !== "undefined") {
|
|
4
|
+
Headers = globalThis.Headers;
|
|
5
|
+
} else {
|
|
6
|
+
Headers = class Headers implements Headers {
|
|
7
|
+
private headers: Map<string, string[]>;
|
|
8
|
+
|
|
9
|
+
constructor(init?: HeadersInit) {
|
|
10
|
+
this.headers = new Map();
|
|
11
|
+
|
|
12
|
+
if (init) {
|
|
13
|
+
if (init instanceof Headers) {
|
|
14
|
+
init.forEach((value, key) => this.append(key, value));
|
|
15
|
+
} else if (Array.isArray(init)) {
|
|
16
|
+
for (const [key, value] of init) {
|
|
17
|
+
if (typeof key === "string" && typeof value === "string") {
|
|
18
|
+
this.append(key, value);
|
|
19
|
+
} else {
|
|
20
|
+
throw new TypeError("Each header entry must be a [string, string] tuple");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
} else {
|
|
24
|
+
for (const [key, value] of Object.entries(init)) {
|
|
25
|
+
if (typeof value === "string") {
|
|
26
|
+
this.append(key, value);
|
|
27
|
+
} else {
|
|
28
|
+
throw new TypeError("Header values must be strings");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
append(name: string, value: string): void {
|
|
36
|
+
const key = name.toLowerCase();
|
|
37
|
+
const existing = this.headers.get(key) || [];
|
|
38
|
+
this.headers.set(key, [...existing, value]);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
delete(name: string): void {
|
|
42
|
+
const key = name.toLowerCase();
|
|
43
|
+
this.headers.delete(key);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get(name: string): string | null {
|
|
47
|
+
const key = name.toLowerCase();
|
|
48
|
+
const values = this.headers.get(key);
|
|
49
|
+
return values ? values.join(", ") : null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
has(name: string): boolean {
|
|
53
|
+
const key = name.toLowerCase();
|
|
54
|
+
return this.headers.has(key);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
set(name: string, value: string): void {
|
|
58
|
+
const key = name.toLowerCase();
|
|
59
|
+
this.headers.set(key, [value]);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
forEach(callbackfn: (value: string, key: string, parent: Headers) => void, thisArg?: unknown): void {
|
|
63
|
+
const boundCallback = thisArg ? callbackfn.bind(thisArg) : callbackfn;
|
|
64
|
+
this.headers.forEach((values, key) => boundCallback(values.join(", "), key, this));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getSetCookie(): string[] {
|
|
68
|
+
return this.headers.get("set-cookie") || [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
*entries(): HeadersIterator<[string, string]> {
|
|
72
|
+
for (const [key, values] of this.headers.entries()) {
|
|
73
|
+
yield [key, values.join(", ")];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
*keys(): HeadersIterator<string> {
|
|
78
|
+
yield* this.headers.keys();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
*values(): HeadersIterator<string> {
|
|
82
|
+
for (const values of this.headers.values()) {
|
|
83
|
+
yield values.join(", ");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
[Symbol.iterator](): HeadersIterator<[string, string]> {
|
|
88
|
+
return this.entries();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { Headers };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { WithRawResponse } from "./RawResponse.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A promise that returns the parsed response and lets you retrieve the raw response too.
|
|
5
|
+
*/
|
|
6
|
+
export class HttpResponsePromise<T> extends Promise<T> {
|
|
7
|
+
private innerPromise: Promise<WithRawResponse<T>>;
|
|
8
|
+
private unwrappedPromise: Promise<T> | undefined;
|
|
9
|
+
|
|
10
|
+
private constructor(promise: Promise<WithRawResponse<T>>) {
|
|
11
|
+
// Initialize with a no-op to avoid premature parsing
|
|
12
|
+
super((resolve) => {
|
|
13
|
+
resolve(undefined as unknown as T);
|
|
14
|
+
});
|
|
15
|
+
this.innerPromise = promise;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates an `HttpResponsePromise` from a function that returns a promise.
|
|
20
|
+
*
|
|
21
|
+
* @param fn - A function that returns a promise resolving to a `WithRawResponse` object.
|
|
22
|
+
* @param args - Arguments to pass to the function.
|
|
23
|
+
* @returns An `HttpResponsePromise` instance.
|
|
24
|
+
*/
|
|
25
|
+
public static fromFunction<F extends (...args: never[]) => Promise<WithRawResponse<T>>, T>(
|
|
26
|
+
fn: F,
|
|
27
|
+
...args: Parameters<F>
|
|
28
|
+
): HttpResponsePromise<T> {
|
|
29
|
+
return new HttpResponsePromise<T>(fn(...args));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Creates a function that returns an `HttpResponsePromise` from a function that returns a promise.
|
|
34
|
+
*
|
|
35
|
+
* @param fn - A function that returns a promise resolving to a `WithRawResponse` object.
|
|
36
|
+
* @returns A function that returns an `HttpResponsePromise` instance.
|
|
37
|
+
*/
|
|
38
|
+
public static interceptFunction<
|
|
39
|
+
F extends (...args: never[]) => Promise<WithRawResponse<T>>,
|
|
40
|
+
T = Awaited<ReturnType<F>>["data"],
|
|
41
|
+
>(fn: F): (...args: Parameters<F>) => HttpResponsePromise<T> {
|
|
42
|
+
return (...args: Parameters<F>): HttpResponsePromise<T> => {
|
|
43
|
+
return HttpResponsePromise.fromPromise<T>(fn(...args));
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates an `HttpResponsePromise` from an existing promise.
|
|
49
|
+
*
|
|
50
|
+
* @param promise - A promise resolving to a `WithRawResponse` object.
|
|
51
|
+
* @returns An `HttpResponsePromise` instance.
|
|
52
|
+
*/
|
|
53
|
+
public static fromPromise<T>(promise: Promise<WithRawResponse<T>>): HttpResponsePromise<T> {
|
|
54
|
+
return new HttpResponsePromise<T>(promise);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Creates an `HttpResponsePromise` from an executor function.
|
|
59
|
+
*
|
|
60
|
+
* @param executor - A function that takes resolve and reject callbacks to create a promise.
|
|
61
|
+
* @returns An `HttpResponsePromise` instance.
|
|
62
|
+
*/
|
|
63
|
+
public static fromExecutor<T>(
|
|
64
|
+
executor: (resolve: (value: WithRawResponse<T>) => void, reject: (reason?: unknown) => void) => void,
|
|
65
|
+
): HttpResponsePromise<T> {
|
|
66
|
+
const promise = new Promise<WithRawResponse<T>>(executor);
|
|
67
|
+
return new HttpResponsePromise<T>(promise);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Creates an `HttpResponsePromise` from a resolved result.
|
|
72
|
+
*
|
|
73
|
+
* @param result - A `WithRawResponse` object to resolve immediately.
|
|
74
|
+
* @returns An `HttpResponsePromise` instance.
|
|
75
|
+
*/
|
|
76
|
+
public static fromResult<T>(result: WithRawResponse<T>): HttpResponsePromise<T> {
|
|
77
|
+
const promise = Promise.resolve(result);
|
|
78
|
+
return new HttpResponsePromise<T>(promise);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private unwrap(): Promise<T> {
|
|
82
|
+
if (!this.unwrappedPromise) {
|
|
83
|
+
this.unwrappedPromise = this.innerPromise.then(({ data }) => data);
|
|
84
|
+
}
|
|
85
|
+
return this.unwrappedPromise;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** @inheritdoc */
|
|
89
|
+
public override then<TResult1 = T, TResult2 = never>(
|
|
90
|
+
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
|
91
|
+
onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,
|
|
92
|
+
): Promise<TResult1 | TResult2> {
|
|
93
|
+
return this.unwrap().then(onfulfilled, onrejected);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** @inheritdoc */
|
|
97
|
+
public override catch<TResult = never>(
|
|
98
|
+
onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null,
|
|
99
|
+
): Promise<T | TResult> {
|
|
100
|
+
return this.unwrap().catch(onrejected);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** @inheritdoc */
|
|
104
|
+
public override finally(onfinally?: (() => void) | null): Promise<T> {
|
|
105
|
+
return this.unwrap().finally(onfinally);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Retrieves the data and raw response.
|
|
110
|
+
*
|
|
111
|
+
* @returns A promise resolving to a `WithRawResponse` object.
|
|
112
|
+
*/
|
|
113
|
+
public async withRawResponse(): Promise<WithRawResponse<T>> {
|
|
114
|
+
return await this.innerPromise;
|
|
115
|
+
}
|
|
116
|
+
}
|