@autometa/http 1.4.19 → 2.0.0-rc.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/README.md +107 -2
- package/dist/assertions/http-adapters.d.ts +35 -0
- package/dist/assertions/http-assertions-plugin.d.ts +16 -0
- package/dist/assertions/http-ensure.d.ts +42 -0
- package/dist/axios-transport.d.ts +22 -0
- package/dist/default-schema.d.ts +8 -0
- package/dist/fetch-transport.d.ts +21 -0
- package/dist/http-request.d.ts +109 -0
- package/dist/http-response.d.ts +77 -0
- package/dist/http.d.ts +300 -0
- package/dist/index.cjs +2076 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +15 -1116
- package/dist/index.js +1727 -845
- package/dist/index.js.map +1 -1
- package/dist/plugins.d.ts +43 -0
- package/dist/request-meta.config.d.ts +56 -0
- package/dist/schema.map.d.ts +11 -0
- package/dist/transform-response.d.ts +1 -0
- package/dist/transport.d.ts +11 -0
- package/dist/types.d.ts +39 -0
- package/package.json +31 -31
- package/.eslintignore +0 -3
- package/.eslintrc.cjs +0 -4
- package/.turbo/turbo-lint$colon$fix.log +0 -4
- package/.turbo/turbo-prettify.log +0 -5
- package/.turbo/turbo-test.log +0 -120
- package/CHANGELOG.md +0 -326
- package/dist/esm/index.js +0 -1117
- package/dist/esm/index.js.map +0 -1
- package/dist/index.d.cts +0 -1116
- package/src/axios-client.ts +0 -35
- package/src/default-client-factory.axios.spec.ts +0 -9
- package/src/default-client-factory.other.spec.ts +0 -13
- package/src/default-client-factory.ts +0 -7
- package/src/default-schema.spec.ts +0 -74
- package/src/default-schema.ts +0 -127
- package/src/http-client.ts +0 -20
- package/src/http-request.spec.ts +0 -172
- package/src/http-request.ts +0 -201
- package/src/http-response.ts +0 -107
- package/src/http.spec.ts +0 -189
- package/src/http.ts +0 -907
- package/src/index.ts +0 -13
- package/src/node_modules/.vitest/deps/_metadata.json +0 -8
- package/src/node_modules/.vitest/deps/package.json +0 -3
- package/src/node_modules/.vitest/results.json +0 -1
- package/src/request-meta.config.spec.ts +0 -81
- package/src/request-meta.config.ts +0 -134
- package/src/request.config.ts +0 -34
- package/src/schema.map.spec.ts +0 -50
- package/src/schema.map.ts +0 -68
- package/src/transform-response.ts +0 -43
- package/src/types.ts +0 -37
- package/tsup.config.ts +0 -14
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2076 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var errors = require('@autometa/errors');
|
|
4
|
+
var cliHighlight = require('cli-highlight');
|
|
5
|
+
var assertions = require('@autometa/assertions');
|
|
6
|
+
|
|
7
|
+
// src/http.ts
|
|
8
|
+
|
|
9
|
+
// src/fetch-transport.ts
|
|
10
|
+
function createFetchTransport(fetchImpl = globalThis.fetch) {
|
|
11
|
+
if (!fetchImpl) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
"No fetch implementation available. Provide one via createFetchTransport(fetchImpl)."
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
async send(request, options = {}) {
|
|
18
|
+
const {
|
|
19
|
+
headers: optionHeaders,
|
|
20
|
+
body: optionBody,
|
|
21
|
+
signal: optionSignal,
|
|
22
|
+
streamResponse,
|
|
23
|
+
...restOptions
|
|
24
|
+
} = options;
|
|
25
|
+
const headers = mergeHeaders(request.headers, optionHeaders);
|
|
26
|
+
const body = optionBody ?? createBody(request, headers);
|
|
27
|
+
const url = request.fullUrl ?? "";
|
|
28
|
+
const fetchOptions = {
|
|
29
|
+
...restOptions,
|
|
30
|
+
method: request.method ?? "GET",
|
|
31
|
+
headers,
|
|
32
|
+
body
|
|
33
|
+
};
|
|
34
|
+
if (optionSignal !== void 0) {
|
|
35
|
+
fetchOptions.signal = optionSignal;
|
|
36
|
+
}
|
|
37
|
+
const response = await fetchImpl(url, fetchOptions);
|
|
38
|
+
const data = streamResponse ? response.body ?? null : await readBody(response);
|
|
39
|
+
return {
|
|
40
|
+
status: response.status,
|
|
41
|
+
statusText: response.statusText,
|
|
42
|
+
headers: headersToRecord(response.headers),
|
|
43
|
+
data
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function mergeHeaders(base, additional) {
|
|
49
|
+
const next = { ...base };
|
|
50
|
+
if (!additional) {
|
|
51
|
+
return next;
|
|
52
|
+
}
|
|
53
|
+
for (const [key, value] of Object.entries(additional)) {
|
|
54
|
+
if (value === void 0 || value === null) {
|
|
55
|
+
delete next[key];
|
|
56
|
+
} else {
|
|
57
|
+
next[key] = String(value);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return next;
|
|
61
|
+
}
|
|
62
|
+
function createBody(request, headers) {
|
|
63
|
+
const { data } = request;
|
|
64
|
+
if (data === void 0 || data === null) {
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
if (isBodyInit(data)) {
|
|
68
|
+
return data;
|
|
69
|
+
}
|
|
70
|
+
if (typeof data === "string") {
|
|
71
|
+
return data;
|
|
72
|
+
}
|
|
73
|
+
if (typeof data === "object") {
|
|
74
|
+
if (!hasHeader(headers, "content-type")) {
|
|
75
|
+
headers["content-type"] = "application/json";
|
|
76
|
+
}
|
|
77
|
+
return JSON.stringify(data);
|
|
78
|
+
}
|
|
79
|
+
return String(data);
|
|
80
|
+
}
|
|
81
|
+
async function readBody(response) {
|
|
82
|
+
if (response.status === 204 || response.status === 205) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const text = await response.text();
|
|
86
|
+
return text.length === 0 ? null : text;
|
|
87
|
+
}
|
|
88
|
+
function headersToRecord(headers) {
|
|
89
|
+
const record = {};
|
|
90
|
+
headers.forEach((value, key) => {
|
|
91
|
+
record[key] = value;
|
|
92
|
+
});
|
|
93
|
+
return record;
|
|
94
|
+
}
|
|
95
|
+
function hasHeader(headers, name) {
|
|
96
|
+
const lower = name.toLowerCase();
|
|
97
|
+
return Object.keys(headers).some((key) => key.toLowerCase() === lower);
|
|
98
|
+
}
|
|
99
|
+
function isBodyInit(value) {
|
|
100
|
+
if (typeof value === "string") {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
if (typeof ArrayBuffer !== "undefined" && value instanceof ArrayBuffer) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
if (typeof ArrayBuffer !== "undefined" && ArrayBuffer.isView(value)) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/http-request.ts
|
|
113
|
+
var HTTPRequest = class _HTTPRequest {
|
|
114
|
+
constructor(config) {
|
|
115
|
+
/**
|
|
116
|
+
* Normalised header collection that will be sent with the request.
|
|
117
|
+
*/
|
|
118
|
+
this.headers = {};
|
|
119
|
+
this.params = {};
|
|
120
|
+
this.route = [];
|
|
121
|
+
this.queryOptions = {};
|
|
122
|
+
if (config) {
|
|
123
|
+
Object.assign(this, config);
|
|
124
|
+
if (config.queryOptions) {
|
|
125
|
+
this.queryOptions = { ...config.queryOptions };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Full request URL derived from {@link baseUrl}, {@link route} and {@link params}.
|
|
131
|
+
*/
|
|
132
|
+
get fullUrl() {
|
|
133
|
+
return buildFullUrl(
|
|
134
|
+
this.baseUrl,
|
|
135
|
+
this.route,
|
|
136
|
+
this.params,
|
|
137
|
+
this.queryOptions
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Creates a deep copy of an existing request instance.
|
|
142
|
+
*/
|
|
143
|
+
static derive(original) {
|
|
144
|
+
const request = new _HTTPRequest();
|
|
145
|
+
request.headers = { ...original.headers };
|
|
146
|
+
request.params = cloneParams(original.params);
|
|
147
|
+
request.baseUrl = original.baseUrl;
|
|
148
|
+
request.route = [...original.route];
|
|
149
|
+
request.method = original.method;
|
|
150
|
+
request.data = original.data;
|
|
151
|
+
request.queryOptions = { ...original.queryOptions };
|
|
152
|
+
return request;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
var HTTPRequestBuilder = class _HTTPRequestBuilder {
|
|
156
|
+
constructor(request = () => new HTTPRequest()) {
|
|
157
|
+
this.dynamicHeaders = /* @__PURE__ */ new Map();
|
|
158
|
+
this.requestInstance = typeof request === "function" ? request() : request;
|
|
159
|
+
this.queryOptions = { ...this.requestInstance.queryOptions };
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Initializes a new builder for the default {@link HTTPRequest} type.
|
|
163
|
+
*/
|
|
164
|
+
static create() {
|
|
165
|
+
return new _HTTPRequestBuilder();
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Exposes the underlying request without defensive cloning.
|
|
169
|
+
*/
|
|
170
|
+
get request() {
|
|
171
|
+
return this.requestInstance;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Resolves asynchronous header factories into concrete header values on demand.
|
|
175
|
+
*/
|
|
176
|
+
async resolveDynamicHeaders(request = this.requestInstance) {
|
|
177
|
+
for (const [name, factory] of this.dynamicHeaders) {
|
|
178
|
+
if (!factory) {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
const result = await factory();
|
|
183
|
+
if (result === void 0 || result === null) {
|
|
184
|
+
delete request.headers[name];
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
request.headers[name] = String(result);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
const cause = error;
|
|
190
|
+
throw new Error(
|
|
191
|
+
`Failed to resolve dynamic header "${name}": ${cause.message}`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Sets the root URL (protocol, host and optional base path).
|
|
199
|
+
*/
|
|
200
|
+
url(url) {
|
|
201
|
+
this.requestInstance.baseUrl = url;
|
|
202
|
+
return this;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Appends one or more path segments to the current request route.
|
|
206
|
+
*/
|
|
207
|
+
route(...segments) {
|
|
208
|
+
const mapped = segments.map((segment) => String(segment)).filter((segment) => segment.length > 0);
|
|
209
|
+
this.requestInstance.route.push(...mapped);
|
|
210
|
+
return this;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Adds or removes a query parameter value.
|
|
214
|
+
*/
|
|
215
|
+
param(name, value) {
|
|
216
|
+
if (value === void 0) {
|
|
217
|
+
delete this.requestInstance.params[name];
|
|
218
|
+
return this;
|
|
219
|
+
}
|
|
220
|
+
if (Array.isArray(value)) {
|
|
221
|
+
const list = value.filter(
|
|
222
|
+
(item) => item !== void 0 && item !== null
|
|
223
|
+
);
|
|
224
|
+
const existing = this.requestInstance.params[name];
|
|
225
|
+
if (Array.isArray(existing)) {
|
|
226
|
+
this.requestInstance.params[name] = [
|
|
227
|
+
...existing,
|
|
228
|
+
...list
|
|
229
|
+
];
|
|
230
|
+
} else {
|
|
231
|
+
this.requestInstance.params[name] = list;
|
|
232
|
+
}
|
|
233
|
+
return this;
|
|
234
|
+
}
|
|
235
|
+
if (value && typeof value === "object") {
|
|
236
|
+
const dictEntries = Object.entries(
|
|
237
|
+
value
|
|
238
|
+
).reduce((acc, [key, paramValue]) => {
|
|
239
|
+
if (paramValue !== void 0) {
|
|
240
|
+
acc[key] = paramValue;
|
|
241
|
+
}
|
|
242
|
+
return acc;
|
|
243
|
+
}, {});
|
|
244
|
+
const existing = this.requestInstance.params[name];
|
|
245
|
+
if (isPlainObject(existing)) {
|
|
246
|
+
this.requestInstance.params[name] = {
|
|
247
|
+
...existing,
|
|
248
|
+
...dictEntries
|
|
249
|
+
};
|
|
250
|
+
} else {
|
|
251
|
+
this.requestInstance.params[name] = dictEntries;
|
|
252
|
+
}
|
|
253
|
+
return this;
|
|
254
|
+
}
|
|
255
|
+
this.requestInstance.params[name] = value;
|
|
256
|
+
return this;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Merges a dictionary of query parameters into the request.
|
|
260
|
+
*/
|
|
261
|
+
params(dict) {
|
|
262
|
+
for (const [key, value] of Object.entries(dict)) {
|
|
263
|
+
this.param(key, value);
|
|
264
|
+
}
|
|
265
|
+
return this;
|
|
266
|
+
}
|
|
267
|
+
queryFormat(options) {
|
|
268
|
+
if (options.arrayFormat !== void 0) {
|
|
269
|
+
this.queryOptions.arrayFormat = options.arrayFormat;
|
|
270
|
+
}
|
|
271
|
+
if (options.objectFormat !== void 0) {
|
|
272
|
+
this.queryOptions.objectFormat = options.objectFormat;
|
|
273
|
+
}
|
|
274
|
+
if (Object.prototype.hasOwnProperty.call(options, "serializer")) {
|
|
275
|
+
const serializer = options.serializer ?? void 0;
|
|
276
|
+
if (serializer) {
|
|
277
|
+
this.queryOptions.serializer = serializer;
|
|
278
|
+
} else {
|
|
279
|
+
delete this.queryOptions.serializer;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
this.requestInstance.queryOptions = { ...this.queryOptions };
|
|
283
|
+
return this;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Sets the request body payload. Passing `undefined` removes the body.
|
|
287
|
+
*/
|
|
288
|
+
data(data) {
|
|
289
|
+
if (data === void 0) {
|
|
290
|
+
delete this.requestInstance.data;
|
|
291
|
+
return this;
|
|
292
|
+
}
|
|
293
|
+
this.requestInstance.data = data;
|
|
294
|
+
return this;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Sets a single header using direct values or a lazy factory.
|
|
298
|
+
*/
|
|
299
|
+
header(name, value) {
|
|
300
|
+
if (typeof value === "function") {
|
|
301
|
+
this.dynamicHeaders.set(name, value);
|
|
302
|
+
delete this.requestInstance.headers[name];
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
305
|
+
this.dynamicHeaders.delete(name);
|
|
306
|
+
if (value === void 0 || value === null) {
|
|
307
|
+
delete this.requestInstance.headers[name];
|
|
308
|
+
return this;
|
|
309
|
+
}
|
|
310
|
+
if (Array.isArray(value)) {
|
|
311
|
+
const filtered = value.filter(
|
|
312
|
+
(item) => item !== void 0 && item !== null
|
|
313
|
+
);
|
|
314
|
+
this.requestInstance.headers[name] = filtered.map(String).join(",");
|
|
315
|
+
return this;
|
|
316
|
+
}
|
|
317
|
+
this.requestInstance.headers[name] = String(value);
|
|
318
|
+
return this;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Replaces or merges multiple headers in one call.
|
|
322
|
+
*/
|
|
323
|
+
headers(dict) {
|
|
324
|
+
for (const [key, value] of Object.entries(dict)) {
|
|
325
|
+
this.header(key, value);
|
|
326
|
+
}
|
|
327
|
+
return this;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Stores the HTTP verb ensuring consistent casing.
|
|
331
|
+
*/
|
|
332
|
+
method(method) {
|
|
333
|
+
this.requestInstance.method = method.toUpperCase();
|
|
334
|
+
return this;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Returns a copy-on-write builder pointing at the same request state.
|
|
338
|
+
*/
|
|
339
|
+
derive() {
|
|
340
|
+
return this.clone();
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Produces a deep copy of the builder and the underlying request.
|
|
344
|
+
*/
|
|
345
|
+
clone() {
|
|
346
|
+
const request = HTTPRequest.derive(this.requestInstance);
|
|
347
|
+
const builder = new _HTTPRequestBuilder(request);
|
|
348
|
+
builder.dynamicHeaders = new Map(this.dynamicHeaders);
|
|
349
|
+
builder.queryOptions = { ...this.queryOptions };
|
|
350
|
+
builder.requestInstance.queryOptions = { ...this.queryOptions };
|
|
351
|
+
return builder;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Returns the current request without resolving header factories.
|
|
355
|
+
*/
|
|
356
|
+
build() {
|
|
357
|
+
this.requestInstance.queryOptions = { ...this.queryOptions };
|
|
358
|
+
return this.requestInstance;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Resolves lazy headers before returning the request.
|
|
362
|
+
*/
|
|
363
|
+
async buildAsync() {
|
|
364
|
+
await this.resolveDynamicHeaders();
|
|
365
|
+
this.requestInstance.queryOptions = { ...this.queryOptions };
|
|
366
|
+
return this.requestInstance;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
function cloneParams(params) {
|
|
370
|
+
return Object.entries(params).reduce((acc, [key, value]) => {
|
|
371
|
+
if (Array.isArray(value)) {
|
|
372
|
+
acc[key] = [...value];
|
|
373
|
+
return acc;
|
|
374
|
+
}
|
|
375
|
+
if (isPlainObject(value)) {
|
|
376
|
+
acc[key] = { ...value };
|
|
377
|
+
return acc;
|
|
378
|
+
}
|
|
379
|
+
acc[key] = value;
|
|
380
|
+
return acc;
|
|
381
|
+
}, {});
|
|
382
|
+
}
|
|
383
|
+
function buildFullUrl(baseUrl, route, params, options) {
|
|
384
|
+
const pathSegments = route.map((segment) => String(segment)).filter((segment) => segment.length > 0).map((segment) => segment.replace(/^\/+|\/+$/g, ""));
|
|
385
|
+
const query = buildQueryString(params, options);
|
|
386
|
+
if (baseUrl && /^https?:\/\//i.test(baseUrl)) {
|
|
387
|
+
const url = new URL(baseUrl);
|
|
388
|
+
if (pathSegments.length > 0) {
|
|
389
|
+
const basePath = url.pathname.replace(/^\/+|\/+$/g, "");
|
|
390
|
+
const combined = [basePath, ...pathSegments].filter(Boolean).join("/");
|
|
391
|
+
url.pathname = `/${combined}`;
|
|
392
|
+
}
|
|
393
|
+
url.search = query ? `?${query}` : "";
|
|
394
|
+
return url.toString();
|
|
395
|
+
}
|
|
396
|
+
const trimmedBase = (baseUrl ?? "").replace(/\/+$/g, "");
|
|
397
|
+
const joinedPath = pathSegments.join("/");
|
|
398
|
+
const path = [trimmedBase, joinedPath].filter((segment) => segment && segment.length > 0).join(trimmedBase && joinedPath ? "/" : "");
|
|
399
|
+
if (!path && !query) {
|
|
400
|
+
return "";
|
|
401
|
+
}
|
|
402
|
+
if (!path) {
|
|
403
|
+
return query ? `?${query}` : "";
|
|
404
|
+
}
|
|
405
|
+
return query ? `${path}?${query}` : path;
|
|
406
|
+
}
|
|
407
|
+
function buildQueryString(params, options) {
|
|
408
|
+
if (!params || Object.keys(params).length === 0) {
|
|
409
|
+
return "";
|
|
410
|
+
}
|
|
411
|
+
const serializer = options?.serializer ?? void 0;
|
|
412
|
+
if (serializer) {
|
|
413
|
+
return serializer(params);
|
|
414
|
+
}
|
|
415
|
+
const arrayFormat = options?.arrayFormat ?? "repeat";
|
|
416
|
+
const objectFormat = options?.objectFormat ?? "brackets";
|
|
417
|
+
const search = new URLSearchParams();
|
|
418
|
+
const append = (key, value) => {
|
|
419
|
+
if (value === void 0 || value === null) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
search.append(key, String(value));
|
|
423
|
+
};
|
|
424
|
+
const encode = (key, value) => {
|
|
425
|
+
if (value === void 0 || value === null) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
if (Array.isArray(value)) {
|
|
429
|
+
const filtered = value.filter(
|
|
430
|
+
(item) => item !== void 0 && item !== null
|
|
431
|
+
);
|
|
432
|
+
if (filtered.length === 0) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
switch (arrayFormat) {
|
|
436
|
+
case "json": {
|
|
437
|
+
append(key, JSON.stringify(filtered));
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
case "comma": {
|
|
441
|
+
const joined = filtered.map((item) => String(item)).join(",");
|
|
442
|
+
append(key, joined);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
case "indices": {
|
|
446
|
+
filtered.forEach((item, index) => {
|
|
447
|
+
encode(`${key}[${index}]`, item);
|
|
448
|
+
});
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
case "brackets": {
|
|
452
|
+
filtered.forEach((item) => {
|
|
453
|
+
encode(`${key}[]`, item);
|
|
454
|
+
});
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
default: {
|
|
458
|
+
filtered.forEach((item) => {
|
|
459
|
+
encode(key, item);
|
|
460
|
+
});
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (isPlainObject(value)) {
|
|
466
|
+
if (objectFormat === "json") {
|
|
467
|
+
append(key, JSON.stringify(value));
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
for (const [childKey, childValue] of Object.entries(
|
|
471
|
+
value
|
|
472
|
+
)) {
|
|
473
|
+
const nextKey = objectFormat === "dot" ? `${key}.${childKey}` : `${key}[${childKey}]`;
|
|
474
|
+
encode(nextKey, childValue);
|
|
475
|
+
}
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
append(key, value);
|
|
479
|
+
};
|
|
480
|
+
for (const [key, value] of Object.entries(params)) {
|
|
481
|
+
encode(key, value);
|
|
482
|
+
}
|
|
483
|
+
return search.toString();
|
|
484
|
+
}
|
|
485
|
+
function isPlainObject(value) {
|
|
486
|
+
if (value === null || typeof value !== "object") {
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
const proto = Object.getPrototypeOf(value);
|
|
490
|
+
return proto === Object.prototype || proto === null;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// src/http-response.ts
|
|
494
|
+
var HTTPResponse = class _HTTPResponse {
|
|
495
|
+
constructor() {
|
|
496
|
+
/**
|
|
497
|
+
* Human-readable text accompanying {@link status}.
|
|
498
|
+
*/
|
|
499
|
+
this.statusText = "";
|
|
500
|
+
/**
|
|
501
|
+
* Normalised response headers keyed by lowercase header names.
|
|
502
|
+
*/
|
|
503
|
+
this.headers = {};
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Creates a shallow clone from an existing response instance.
|
|
507
|
+
*/
|
|
508
|
+
static fromRaw(response) {
|
|
509
|
+
const next = new _HTTPResponse();
|
|
510
|
+
next.status = response.status;
|
|
511
|
+
next.statusText = response.statusText;
|
|
512
|
+
next.data = response.data;
|
|
513
|
+
next.headers = { ...response.headers };
|
|
514
|
+
next.request = response.request;
|
|
515
|
+
return next;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Returns a new response with identical metadata but a transformed payload.
|
|
519
|
+
*
|
|
520
|
+
* @example
|
|
521
|
+
* const productResponse = await http.route("products", 1).get<Product>()
|
|
522
|
+
* const simplified = productResponse.mapData((product) => product.name)
|
|
523
|
+
*/
|
|
524
|
+
mapData(transform) {
|
|
525
|
+
const nextValue = typeof transform === "function" ? transform(this.data) : transform;
|
|
526
|
+
return new HTTPResponseBuilder().status(this.status).statusText(this.statusText).headers(this.headers).request(this.request).data(nextValue).build();
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
var HTTPResponseBuilder = class _HTTPResponseBuilder {
|
|
530
|
+
constructor() {
|
|
531
|
+
this.response = new HTTPResponse();
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Creates a new empty builder.
|
|
535
|
+
*/
|
|
536
|
+
static create() {
|
|
537
|
+
return new _HTTPResponseBuilder();
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Produces a new builder seeded with the current response state.
|
|
541
|
+
*/
|
|
542
|
+
derive() {
|
|
543
|
+
return _HTTPResponseBuilder.create().status(this.response.status).statusText(this.response.statusText).headers({ ...this.response.headers }).request(this.response.request).data(this.response.data);
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Sets the response status code.
|
|
547
|
+
*/
|
|
548
|
+
status(code) {
|
|
549
|
+
this.response.status = code;
|
|
550
|
+
return this;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Sets the textual status message.
|
|
554
|
+
*/
|
|
555
|
+
statusText(text) {
|
|
556
|
+
this.response.statusText = text;
|
|
557
|
+
return this;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Attaches the response payload.
|
|
561
|
+
*/
|
|
562
|
+
data(data) {
|
|
563
|
+
this.response.data = data;
|
|
564
|
+
return this;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Replaces the entire headers collection.
|
|
568
|
+
*/
|
|
569
|
+
headers(dict) {
|
|
570
|
+
this.response.headers = { ...dict };
|
|
571
|
+
return this;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Sets or overrides a single response header.
|
|
575
|
+
*/
|
|
576
|
+
header(name, value) {
|
|
577
|
+
this.response.headers[name] = value;
|
|
578
|
+
return this;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* References the originating request.
|
|
582
|
+
*/
|
|
583
|
+
request(request) {
|
|
584
|
+
this.response.request = request;
|
|
585
|
+
return this;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Builds the immutable {@link HTTPResponse} instance.
|
|
589
|
+
*/
|
|
590
|
+
build() {
|
|
591
|
+
return this.response;
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
// src/schema.map.ts
|
|
596
|
+
var SchemaMap = class _SchemaMap {
|
|
597
|
+
constructor(map) {
|
|
598
|
+
if (map instanceof _SchemaMap) {
|
|
599
|
+
this.map = new Map(map.map);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
this.map = map ? new Map(map) : /* @__PURE__ */ new Map();
|
|
603
|
+
}
|
|
604
|
+
derive() {
|
|
605
|
+
return new _SchemaMap(this.map);
|
|
606
|
+
}
|
|
607
|
+
registerStatus(parser, ...codes) {
|
|
608
|
+
codes.forEach((code) => {
|
|
609
|
+
this.map.set(code, parser);
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
registerRange(parser, from, to) {
|
|
613
|
+
for (let code = from; code <= to; code++) {
|
|
614
|
+
this.map.set(code, parser);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
validate(status, data, requireSchema) {
|
|
618
|
+
const parser = this.getParser(status, requireSchema);
|
|
619
|
+
if (!parser) {
|
|
620
|
+
return data;
|
|
621
|
+
}
|
|
622
|
+
if (typeof parser === "function") {
|
|
623
|
+
return parser(data);
|
|
624
|
+
}
|
|
625
|
+
if ("parse" in parser) {
|
|
626
|
+
return parser.parse(data);
|
|
627
|
+
}
|
|
628
|
+
if ("validate" in parser) {
|
|
629
|
+
return parser.validate(data);
|
|
630
|
+
}
|
|
631
|
+
return data;
|
|
632
|
+
}
|
|
633
|
+
getParser(status, requireSchema) {
|
|
634
|
+
const parser = this.map.get(status);
|
|
635
|
+
if (!parser && requireSchema) {
|
|
636
|
+
throw new Error(
|
|
637
|
+
`No schema parser registered for status code ${status} while requireSchema is true.`
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
return parser ?? null;
|
|
641
|
+
}
|
|
642
|
+
toObject() {
|
|
643
|
+
return Object.fromEntries(this.map);
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
// src/request-meta.config.ts
|
|
648
|
+
var MetaConfig = class {
|
|
649
|
+
constructor(init) {
|
|
650
|
+
this.schemas = new SchemaMap();
|
|
651
|
+
this.requireSchema = false;
|
|
652
|
+
this.allowPlainText = false;
|
|
653
|
+
this.onSend = [];
|
|
654
|
+
this.onReceive = [];
|
|
655
|
+
this.throwOnServerError = false;
|
|
656
|
+
this.options = {};
|
|
657
|
+
this.streamResponse = false;
|
|
658
|
+
this.timeoutMs = void 0;
|
|
659
|
+
Object.assign(this, init);
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
var MetaConfigBuilder = class _MetaConfigBuilder {
|
|
663
|
+
constructor() {
|
|
664
|
+
this.schemaMapValue = new SchemaMap();
|
|
665
|
+
this.requireSchemaValue = false;
|
|
666
|
+
this.allowPlainTextValue = false;
|
|
667
|
+
this.onBeforeSendHooks = [];
|
|
668
|
+
this.onAfterReceiveHooks = [];
|
|
669
|
+
this.throwOnServerErrorValue = false;
|
|
670
|
+
this.optionsValue = {};
|
|
671
|
+
this.streamResponseValue = false;
|
|
672
|
+
}
|
|
673
|
+
merge(builder) {
|
|
674
|
+
this.schemaMapValue = builder.schemaMapValue.derive();
|
|
675
|
+
this.requireSchemaValue = builder.requireSchemaValue;
|
|
676
|
+
this.allowPlainTextValue = builder.allowPlainTextValue;
|
|
677
|
+
this.onBeforeSendHooks = [...builder.onBeforeSendHooks];
|
|
678
|
+
this.onAfterReceiveHooks = [...builder.onAfterReceiveHooks];
|
|
679
|
+
this.throwOnServerErrorValue = builder.throwOnServerErrorValue;
|
|
680
|
+
this.optionsValue = { ...builder.optionsValue };
|
|
681
|
+
this.retryOptionsValue = builder.retryOptionsValue ? { ...builder.retryOptionsValue } : void 0;
|
|
682
|
+
this.streamResponseValue = builder.streamResponseValue;
|
|
683
|
+
this.timeoutMsValue = builder.timeoutMsValue;
|
|
684
|
+
return this;
|
|
685
|
+
}
|
|
686
|
+
schemaMap(map) {
|
|
687
|
+
this.schemaMapValue = map;
|
|
688
|
+
return this;
|
|
689
|
+
}
|
|
690
|
+
schema(parser, ...args) {
|
|
691
|
+
args.forEach((arg) => {
|
|
692
|
+
if (typeof arg === "number") {
|
|
693
|
+
this.schemaMapValue.registerStatus(parser, arg);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
if (Array.isArray(arg)) {
|
|
697
|
+
this.schemaMapValue.registerStatus(parser, ...arg);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
this.schemaMapValue.registerRange(parser, arg.from, arg.to);
|
|
701
|
+
});
|
|
702
|
+
return this;
|
|
703
|
+
}
|
|
704
|
+
requireSchema(value) {
|
|
705
|
+
this.requireSchemaValue = value;
|
|
706
|
+
return this;
|
|
707
|
+
}
|
|
708
|
+
allowPlainText(value) {
|
|
709
|
+
this.allowPlainTextValue = value;
|
|
710
|
+
return this;
|
|
711
|
+
}
|
|
712
|
+
onBeforeSend(description, hook) {
|
|
713
|
+
this.onBeforeSendHooks.push([description, hook]);
|
|
714
|
+
return this;
|
|
715
|
+
}
|
|
716
|
+
onReceiveResponse(description, hook) {
|
|
717
|
+
this.onAfterReceiveHooks.push([description, hook]);
|
|
718
|
+
return this;
|
|
719
|
+
}
|
|
720
|
+
throwOnServerError(value) {
|
|
721
|
+
this.throwOnServerErrorValue = value;
|
|
722
|
+
return this;
|
|
723
|
+
}
|
|
724
|
+
options(options) {
|
|
725
|
+
this.optionsValue = mergeOptions(this.optionsValue, options);
|
|
726
|
+
return this;
|
|
727
|
+
}
|
|
728
|
+
retry(options) {
|
|
729
|
+
this.retryOptionsValue = options ? { ...options } : void 0;
|
|
730
|
+
return this;
|
|
731
|
+
}
|
|
732
|
+
streamResponse(value) {
|
|
733
|
+
this.streamResponseValue = value;
|
|
734
|
+
return this;
|
|
735
|
+
}
|
|
736
|
+
timeout(duration) {
|
|
737
|
+
if (typeof duration === "number" && duration > 0) {
|
|
738
|
+
this.timeoutMsValue = duration;
|
|
739
|
+
} else {
|
|
740
|
+
this.timeoutMsValue = void 0;
|
|
741
|
+
}
|
|
742
|
+
return this;
|
|
743
|
+
}
|
|
744
|
+
build() {
|
|
745
|
+
const retry = this.retryOptionsValue ? { ...this.retryOptionsValue } : void 0;
|
|
746
|
+
return new MetaConfig({
|
|
747
|
+
schemas: this.schemaMapValue.derive(),
|
|
748
|
+
requireSchema: this.requireSchemaValue,
|
|
749
|
+
allowPlainText: this.allowPlainTextValue,
|
|
750
|
+
onSend: [...this.onBeforeSendHooks],
|
|
751
|
+
onReceive: [...this.onAfterReceiveHooks],
|
|
752
|
+
options: { ...this.optionsValue },
|
|
753
|
+
throwOnServerError: this.throwOnServerErrorValue,
|
|
754
|
+
...retry ? { retry } : {},
|
|
755
|
+
streamResponse: this.streamResponseValue,
|
|
756
|
+
timeoutMs: this.timeoutMsValue
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
derive() {
|
|
760
|
+
return new _MetaConfigBuilder().schemaMap(this.schemaMapValue.derive()).requireSchema(this.requireSchemaValue).allowPlainText(this.allowPlainTextValue).throwOnServerError(this.throwOnServerErrorValue).options(this.optionsValue).retry(this.retryOptionsValue ?? null).streamResponse(this.streamResponseValue).timeout(this.timeoutMsValue).setOnBeforeSend(this.onBeforeSendHooks).setOnAfterReceive(this.onAfterReceiveHooks);
|
|
761
|
+
}
|
|
762
|
+
setOnBeforeSend(hooks) {
|
|
763
|
+
this.onBeforeSendHooks = [...hooks];
|
|
764
|
+
return this;
|
|
765
|
+
}
|
|
766
|
+
setOnAfterReceive(hooks) {
|
|
767
|
+
this.onAfterReceiveHooks = [...hooks];
|
|
768
|
+
return this;
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
function mergeOptions(target, updates) {
|
|
772
|
+
const next = { ...target };
|
|
773
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
774
|
+
if (value === void 0) {
|
|
775
|
+
delete next[key];
|
|
776
|
+
} else {
|
|
777
|
+
next[key] = value;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
return next;
|
|
781
|
+
}
|
|
782
|
+
function transformResponse(allowPlainText, data) {
|
|
783
|
+
if (data === null || data === void 0) {
|
|
784
|
+
return data;
|
|
785
|
+
}
|
|
786
|
+
if (typeof data === "string") {
|
|
787
|
+
const trimmed = data.trim();
|
|
788
|
+
if (trimmed.length === 0) {
|
|
789
|
+
return void 0;
|
|
790
|
+
}
|
|
791
|
+
if (trimmed.toLowerCase() === "undefined") {
|
|
792
|
+
return void 0;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
const primitive = normalizePrimitive(data);
|
|
796
|
+
if (primitive !== void 0) {
|
|
797
|
+
return primitive;
|
|
798
|
+
}
|
|
799
|
+
if (typeof data === "object") {
|
|
800
|
+
return data;
|
|
801
|
+
}
|
|
802
|
+
if (allowPlainText) {
|
|
803
|
+
return String(data);
|
|
804
|
+
}
|
|
805
|
+
const rendered = typeof data === "string" ? data : String(data);
|
|
806
|
+
const message = [
|
|
807
|
+
"Could not parse response as JSON and plain text responses are disabled.",
|
|
808
|
+
"Call 'allowPlainText(true)' or 'sharedAllowPlainText(true)' to permit plain text responses.",
|
|
809
|
+
"",
|
|
810
|
+
cliHighlight.highlight(rendered, { language: "html" })
|
|
811
|
+
].join("\n");
|
|
812
|
+
throw new errors.AutomationError(message);
|
|
813
|
+
}
|
|
814
|
+
function normalizePrimitive(data) {
|
|
815
|
+
if (typeof data === "string") {
|
|
816
|
+
const parsed = tryParseJson(data);
|
|
817
|
+
if (parsed !== void 0) {
|
|
818
|
+
return parsed;
|
|
819
|
+
}
|
|
820
|
+
const lowered = data.toLowerCase();
|
|
821
|
+
if (lowered === "true" || lowered === "false") {
|
|
822
|
+
return lowered === "true";
|
|
823
|
+
}
|
|
824
|
+
if (/^(?:\d+|\d*\.\d+)$/.test(data)) {
|
|
825
|
+
return Number(data);
|
|
826
|
+
}
|
|
827
|
+
return void 0;
|
|
828
|
+
}
|
|
829
|
+
if (isArrayBufferLike(data)) {
|
|
830
|
+
const text = bufferToString(data);
|
|
831
|
+
return normalizePrimitive(text) ?? text;
|
|
832
|
+
}
|
|
833
|
+
if (typeof data === "boolean" || typeof data === "number") {
|
|
834
|
+
return data;
|
|
835
|
+
}
|
|
836
|
+
return void 0;
|
|
837
|
+
}
|
|
838
|
+
function tryParseJson(value) {
|
|
839
|
+
try {
|
|
840
|
+
return JSON.parse(value);
|
|
841
|
+
} catch {
|
|
842
|
+
return void 0;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
function isArrayBufferLike(value) {
|
|
846
|
+
return typeof ArrayBuffer !== "undefined" && value instanceof ArrayBuffer || typeof ArrayBuffer !== "undefined" && ArrayBuffer.isView(value);
|
|
847
|
+
}
|
|
848
|
+
function bufferToString(value) {
|
|
849
|
+
const view = value instanceof ArrayBuffer ? new Uint8Array(value) : new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
850
|
+
if (typeof TextDecoder !== "undefined") {
|
|
851
|
+
return new TextDecoder().decode(view);
|
|
852
|
+
}
|
|
853
|
+
let output = "";
|
|
854
|
+
for (let i = 0; i < view.length; i++) {
|
|
855
|
+
const code = view[i];
|
|
856
|
+
if (code === void 0) {
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
output += String.fromCharCode(code);
|
|
860
|
+
}
|
|
861
|
+
return output;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// src/http.ts
|
|
865
|
+
var HTTPError = class extends errors.AutomationError {
|
|
866
|
+
constructor(message, request, response, cause) {
|
|
867
|
+
super(message, {
|
|
868
|
+
cause: cause instanceof Error ? cause : void 0
|
|
869
|
+
});
|
|
870
|
+
this.request = request;
|
|
871
|
+
this.response = response;
|
|
872
|
+
this.originalError = cause;
|
|
873
|
+
this.name = this.constructor.name;
|
|
874
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
var HTTPTransportError = class extends HTTPError {
|
|
878
|
+
constructor(request, cause) {
|
|
879
|
+
super("Failed to execute HTTP request", request, void 0, cause);
|
|
880
|
+
this.name = "HTTPTransportError";
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
var HTTPSchemaValidationError = class extends HTTPError {
|
|
884
|
+
constructor(request, response, cause) {
|
|
885
|
+
super("Response schema validation failed", request, response, cause);
|
|
886
|
+
this.name = "HTTPSchemaValidationError";
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
var HTTPServerError = class extends HTTPError {
|
|
890
|
+
constructor(request, response) {
|
|
891
|
+
super(`Server responded with status ${response.status}`, request, response);
|
|
892
|
+
this.name = "HTTPServerError";
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
var _HTTP = class _HTTP {
|
|
896
|
+
constructor(transport, builder, meta, sharedPlugins, scopedPlugins) {
|
|
897
|
+
this.transport = transport;
|
|
898
|
+
this.builder = builder;
|
|
899
|
+
this.meta = meta;
|
|
900
|
+
this.sharedPlugins = sharedPlugins;
|
|
901
|
+
this.scopedPlugins = scopedPlugins;
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Factory helper that prepares an {@link HTTP} instance with shared state.
|
|
905
|
+
*/
|
|
906
|
+
static create(options = {}) {
|
|
907
|
+
const transport = options.transport ?? createFetchTransport();
|
|
908
|
+
const plugins = [..._HTTP.sharedPlugins, ...options.plugins ?? []];
|
|
909
|
+
return new _HTTP(
|
|
910
|
+
transport,
|
|
911
|
+
HTTPRequestBuilder.create(),
|
|
912
|
+
new MetaConfigBuilder(),
|
|
913
|
+
plugins,
|
|
914
|
+
[]
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Registers a plugin applied to every client created via {@link HTTP.create}.
|
|
919
|
+
*/
|
|
920
|
+
static registerSharedPlugin(plugin) {
|
|
921
|
+
this.sharedPlugins = [...this.sharedPlugins, plugin];
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Replaces the shared plugin registry used by {@link HTTP.create}.
|
|
925
|
+
*/
|
|
926
|
+
static setSharedPlugins(plugins) {
|
|
927
|
+
this.sharedPlugins = [...plugins];
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Returns a copy of the currently registered shared plugins.
|
|
931
|
+
*/
|
|
932
|
+
static getSharedPlugins() {
|
|
933
|
+
return [...this.sharedPlugins];
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Registers a plugin that runs for every request executed by this instance and its clones.
|
|
937
|
+
*/
|
|
938
|
+
use(plugin) {
|
|
939
|
+
this.sharedPlugins.push(plugin);
|
|
940
|
+
return this;
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Returns a scoped clone with an additional plugin applied only to that clone.
|
|
944
|
+
*/
|
|
945
|
+
plugin(plugin) {
|
|
946
|
+
return this.derive(({ plugins }) => {
|
|
947
|
+
plugins.push(plugin);
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* Mutates the current instance to use a different transport implementation.
|
|
952
|
+
*/
|
|
953
|
+
useTransport(transport) {
|
|
954
|
+
this.transport = transport;
|
|
955
|
+
return this;
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Produces a new client with an alternate transport without changing the original instance.
|
|
959
|
+
*/
|
|
960
|
+
withTransport(transport) {
|
|
961
|
+
return this.derive(void 0, { transport });
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Sets the base URL shared by subsequent requests.
|
|
965
|
+
*/
|
|
966
|
+
url(url) {
|
|
967
|
+
this.builder.url(url);
|
|
968
|
+
return this;
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Applies additional transport specific options to every request executed by this instance.
|
|
972
|
+
*/
|
|
973
|
+
sharedOptions(options) {
|
|
974
|
+
this.meta.options(options);
|
|
975
|
+
return this;
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Returns a derived client with extra transport options applied only to that clone.
|
|
979
|
+
*/
|
|
980
|
+
withOptions(options) {
|
|
981
|
+
return this.derive(({ meta }) => {
|
|
982
|
+
meta.options(options);
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Registers an {@link AbortSignal} that will be forwarded to every request issued by this instance.
|
|
987
|
+
*/
|
|
988
|
+
sharedAbortSignal(signal) {
|
|
989
|
+
this.meta.options({ signal: signal ?? void 0 });
|
|
990
|
+
return this;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Returns a derived client configured with the provided {@link AbortSignal}.
|
|
994
|
+
*/
|
|
995
|
+
abortSignal(signal) {
|
|
996
|
+
return this.derive(({ meta }) => {
|
|
997
|
+
meta.options({ signal: signal ?? void 0 });
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Configures automatic retries for this instance and all derived clients.
|
|
1002
|
+
*/
|
|
1003
|
+
sharedRetry(options) {
|
|
1004
|
+
this.meta.retry(options);
|
|
1005
|
+
return this;
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Returns a derived client with custom retry behaviour.
|
|
1009
|
+
*/
|
|
1010
|
+
retry(options) {
|
|
1011
|
+
return this.derive(({ meta }) => {
|
|
1012
|
+
meta.retry(options);
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Forces subsequent requests to return raw response streams without parsing.
|
|
1017
|
+
*/
|
|
1018
|
+
sharedStreamResponse(enabled) {
|
|
1019
|
+
this.meta.streamResponse(enabled);
|
|
1020
|
+
return this;
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Returns a derived client configured for streaming responses.
|
|
1024
|
+
*/
|
|
1025
|
+
streamResponse(enabled) {
|
|
1026
|
+
return this.derive(({ meta }) => {
|
|
1027
|
+
meta.streamResponse(enabled);
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Convenience helper that returns a clone configured for streaming responses.
|
|
1032
|
+
*/
|
|
1033
|
+
asStream() {
|
|
1034
|
+
return this.streamResponse(true);
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* Executes a GET request while preserving the raw response stream.
|
|
1038
|
+
*/
|
|
1039
|
+
stream(options) {
|
|
1040
|
+
return this.streamResponse(true).get(options);
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Sets a shared timeout (in milliseconds) applied to every request from this instance.
|
|
1044
|
+
*/
|
|
1045
|
+
sharedTimeout(duration) {
|
|
1046
|
+
this.meta.timeout(duration);
|
|
1047
|
+
return this;
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Returns a derived client with a per-request timeout in milliseconds.
|
|
1051
|
+
*/
|
|
1052
|
+
timeout(duration) {
|
|
1053
|
+
return this.derive(({ meta }) => {
|
|
1054
|
+
meta.timeout(duration);
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Configures whether schema validation is required before resolving a response.
|
|
1059
|
+
*/
|
|
1060
|
+
requireSchema(required) {
|
|
1061
|
+
this.meta.requireSchema(required);
|
|
1062
|
+
return this;
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Returns a clone with overridden plain text handling mode.
|
|
1066
|
+
*/
|
|
1067
|
+
allowPlainText(allow) {
|
|
1068
|
+
return this.derive(({ meta }) => {
|
|
1069
|
+
meta.allowPlainText(allow);
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Sets plain text handling for the current instance and all future requests.
|
|
1074
|
+
*/
|
|
1075
|
+
sharedAllowPlainText(allow) {
|
|
1076
|
+
this.meta.allowPlainText(allow);
|
|
1077
|
+
return this;
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Adds path segments that will be included in every request.
|
|
1081
|
+
*/
|
|
1082
|
+
sharedRoute(...segments) {
|
|
1083
|
+
this.builder.route(...segments);
|
|
1084
|
+
return this;
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Executes a request using the provided method.
|
|
1088
|
+
*
|
|
1089
|
+
* Use this when the verb is dynamic (e.g. provided by a parameter). It
|
|
1090
|
+
* behaves like calling {@link get}/{@link post}/{@link patch}, respecting any
|
|
1091
|
+
* route/headers/body configured earlier in the chain.
|
|
1092
|
+
*/
|
|
1093
|
+
fetchWith(method, options) {
|
|
1094
|
+
const normalized = String(method).trim().toUpperCase();
|
|
1095
|
+
return this.execute(normalized, options);
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Returns a clone with additional path segments.
|
|
1099
|
+
*/
|
|
1100
|
+
route(...segments) {
|
|
1101
|
+
return this.derive(({ builder }) => {
|
|
1102
|
+
builder.route(...segments);
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Applies query serialization preferences to all future requests originating from this instance.
|
|
1107
|
+
*/
|
|
1108
|
+
sharedQueryFormat(options) {
|
|
1109
|
+
this.builder.queryFormat(options);
|
|
1110
|
+
return this;
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Returns a derived client with custom query serialization that does not affect the source instance.
|
|
1114
|
+
*/
|
|
1115
|
+
queryFormat(options) {
|
|
1116
|
+
return this.derive(({ builder }) => {
|
|
1117
|
+
builder.queryFormat(options);
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
sharedSchema(parser, ...args) {
|
|
1121
|
+
this.meta.schema(parser, ...args);
|
|
1122
|
+
return this;
|
|
1123
|
+
}
|
|
1124
|
+
schema(parser, ...args) {
|
|
1125
|
+
return this.derive(({ meta }) => {
|
|
1126
|
+
meta.schema(parser, ...args);
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
sharedParam(name, value, ...rest) {
|
|
1130
|
+
this.builder.param(name, toParamValue(value, rest));
|
|
1131
|
+
return this;
|
|
1132
|
+
}
|
|
1133
|
+
param(name, value, ...rest) {
|
|
1134
|
+
return this.derive(({ builder }) => {
|
|
1135
|
+
builder.param(name, toParamValue(value, rest));
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Merges multiple shared query parameters into the instance.
|
|
1140
|
+
*/
|
|
1141
|
+
sharedParams(dict) {
|
|
1142
|
+
this.builder.params(dict);
|
|
1143
|
+
return this;
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Derives a client with additional query parameters applied together.
|
|
1147
|
+
*/
|
|
1148
|
+
params(dict) {
|
|
1149
|
+
return this.derive(({ builder }) => {
|
|
1150
|
+
builder.params(dict);
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* Sets a shared request body used by every request from this instance.
|
|
1155
|
+
*/
|
|
1156
|
+
sharedData(data) {
|
|
1157
|
+
this.builder.data(data);
|
|
1158
|
+
return this;
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Derives a client with a one-off request body.
|
|
1162
|
+
*/
|
|
1163
|
+
data(data) {
|
|
1164
|
+
return this.derive(({ builder }) => {
|
|
1165
|
+
builder.data(data);
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Registers a header that will be resolved for every request on this instance.
|
|
1170
|
+
*/
|
|
1171
|
+
sharedHeader(name, value) {
|
|
1172
|
+
this.builder.header(name, value);
|
|
1173
|
+
return this;
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Returns a clone with a header applied only to the resulting client.
|
|
1177
|
+
*/
|
|
1178
|
+
header(name, value) {
|
|
1179
|
+
return this.derive(({ builder }) => {
|
|
1180
|
+
builder.header(name, value);
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Registers multiple shared headers for every downstream request.
|
|
1185
|
+
*/
|
|
1186
|
+
sharedHeaders(dict) {
|
|
1187
|
+
this.builder.headers(dict);
|
|
1188
|
+
return this;
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Returns a derived client with additional headers.
|
|
1192
|
+
*/
|
|
1193
|
+
headers(dict) {
|
|
1194
|
+
return this.derive(({ builder }) => {
|
|
1195
|
+
builder.headers(dict);
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Registers a request hook that runs before every execution on this instance.
|
|
1200
|
+
*/
|
|
1201
|
+
sharedOnSend(description, hook) {
|
|
1202
|
+
this.meta.onBeforeSend(description, hook);
|
|
1203
|
+
return this;
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Returns a clone with a request hook used only for that clone.
|
|
1207
|
+
*/
|
|
1208
|
+
onSend(description, hook) {
|
|
1209
|
+
return this.derive(({ meta }) => {
|
|
1210
|
+
meta.onBeforeSend(description, hook);
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Registers a response hook executed after every transport response.
|
|
1215
|
+
*/
|
|
1216
|
+
sharedOnReceive(description, hook) {
|
|
1217
|
+
this.meta.onReceiveResponse(description, hook);
|
|
1218
|
+
return this;
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Returns a derived client with a response hook limited to that client.
|
|
1222
|
+
*/
|
|
1223
|
+
onReceive(description, hook) {
|
|
1224
|
+
return this.derive(({ meta }) => {
|
|
1225
|
+
meta.onReceiveResponse(description, hook);
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Configures whether server errors (>=500) throw by default for every request.
|
|
1230
|
+
*/
|
|
1231
|
+
sharedThrowOnServerError(value) {
|
|
1232
|
+
this.meta.throwOnServerError(value);
|
|
1233
|
+
return this;
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Returns a derived client with custom server error behaviour.
|
|
1237
|
+
*/
|
|
1238
|
+
throwOnServerError(value) {
|
|
1239
|
+
return this.derive(({ meta }) => {
|
|
1240
|
+
meta.throwOnServerError(value);
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Executes a GET request using the current configuration.
|
|
1245
|
+
*/
|
|
1246
|
+
get(options) {
|
|
1247
|
+
return this.execute("GET", options);
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Executes a POST request using the current configuration.
|
|
1251
|
+
*/
|
|
1252
|
+
post(options) {
|
|
1253
|
+
return this.execute("POST", options);
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Executes a PUT request using the current configuration.
|
|
1257
|
+
*/
|
|
1258
|
+
put(options) {
|
|
1259
|
+
return this.execute("PUT", options);
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Executes a PATCH request using the current configuration.
|
|
1263
|
+
*/
|
|
1264
|
+
patch(options) {
|
|
1265
|
+
return this.execute("PATCH", options);
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Executes a DELETE request using the current configuration.
|
|
1269
|
+
*/
|
|
1270
|
+
delete(options) {
|
|
1271
|
+
return this.execute("DELETE", options);
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Executes a HEAD request using the current configuration.
|
|
1275
|
+
*/
|
|
1276
|
+
head(options) {
|
|
1277
|
+
return this.execute("HEAD", options);
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Executes an OPTIONS request using the current configuration.
|
|
1281
|
+
*/
|
|
1282
|
+
options(options) {
|
|
1283
|
+
return this.execute("OPTIONS", options);
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Executes a TRACE request using the current configuration.
|
|
1287
|
+
*/
|
|
1288
|
+
trace(options) {
|
|
1289
|
+
return this.execute("TRACE", options);
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Executes a CONNECT request using the current configuration.
|
|
1293
|
+
*/
|
|
1294
|
+
connect(options) {
|
|
1295
|
+
return this.execute("CONNECT", options);
|
|
1296
|
+
}
|
|
1297
|
+
async execute(method, options) {
|
|
1298
|
+
const baseBuilder = this.builder.clone().method(method);
|
|
1299
|
+
const meta = this.meta.derive().build();
|
|
1300
|
+
const baseOptions = mergeOptions2(meta.options, options);
|
|
1301
|
+
if (meta.streamResponse) {
|
|
1302
|
+
baseOptions.streamResponse = true;
|
|
1303
|
+
}
|
|
1304
|
+
const retryPolicy = meta.retry;
|
|
1305
|
+
const maxRetries = retryPolicy?.attempts ?? 0;
|
|
1306
|
+
for (let retriesUsed = 0; ; ) {
|
|
1307
|
+
const attemptBuilder = baseBuilder.clone();
|
|
1308
|
+
await attemptBuilder.resolveDynamicHeaders();
|
|
1309
|
+
const request = attemptBuilder.build();
|
|
1310
|
+
request.method = method;
|
|
1311
|
+
const attemptOptions = {
|
|
1312
|
+
...baseOptions
|
|
1313
|
+
};
|
|
1314
|
+
let timeoutController;
|
|
1315
|
+
let timeoutHandle;
|
|
1316
|
+
let combinedSignal;
|
|
1317
|
+
if (typeof meta.timeoutMs === "number" && meta.timeoutMs > 0) {
|
|
1318
|
+
const setup = createTimeoutController(meta.timeoutMs);
|
|
1319
|
+
timeoutController = setup.controller;
|
|
1320
|
+
timeoutHandle = setup.timer;
|
|
1321
|
+
}
|
|
1322
|
+
const existingSignal = attemptOptions.signal;
|
|
1323
|
+
const signals = [];
|
|
1324
|
+
if (existingSignal) {
|
|
1325
|
+
signals.push(existingSignal);
|
|
1326
|
+
}
|
|
1327
|
+
if (timeoutController) {
|
|
1328
|
+
signals.push(timeoutController.signal);
|
|
1329
|
+
}
|
|
1330
|
+
if (signals.length === 1) {
|
|
1331
|
+
const singleSignal = signals[0];
|
|
1332
|
+
if (singleSignal) {
|
|
1333
|
+
attemptOptions.signal = singleSignal;
|
|
1334
|
+
} else if ("signal" in attemptOptions) {
|
|
1335
|
+
delete attemptOptions.signal;
|
|
1336
|
+
}
|
|
1337
|
+
} else if (signals.length > 1) {
|
|
1338
|
+
combinedSignal = combineAbortSignals(signals);
|
|
1339
|
+
attemptOptions.signal = combinedSignal.signal;
|
|
1340
|
+
} else if ("signal" in attemptOptions) {
|
|
1341
|
+
delete attemptOptions.signal;
|
|
1342
|
+
}
|
|
1343
|
+
const activeSignal = attemptOptions.signal;
|
|
1344
|
+
if (activeSignal?.aborted) {
|
|
1345
|
+
const reason = activeSignal.reason ?? createAbortError();
|
|
1346
|
+
throw new HTTPTransportError(request, reason);
|
|
1347
|
+
}
|
|
1348
|
+
const requestContext = {
|
|
1349
|
+
request,
|
|
1350
|
+
options: attemptOptions
|
|
1351
|
+
};
|
|
1352
|
+
let response;
|
|
1353
|
+
try {
|
|
1354
|
+
await this.runRequestPlugins(requestContext);
|
|
1355
|
+
await this.runOnSendHooks(meta, request);
|
|
1356
|
+
const raw = await this.transport.send(request, attemptOptions).catch((cause) => {
|
|
1357
|
+
throw new HTTPTransportError(request, cause);
|
|
1358
|
+
});
|
|
1359
|
+
response = this.buildResponse(raw, request);
|
|
1360
|
+
const isStreaming = meta.streamResponse;
|
|
1361
|
+
if (!isStreaming) {
|
|
1362
|
+
response.data = transformResponse(meta.allowPlainText, response.data);
|
|
1363
|
+
}
|
|
1364
|
+
if (meta.throwOnServerError && response.status >= 500) {
|
|
1365
|
+
throw new HTTPServerError(request, response);
|
|
1366
|
+
}
|
|
1367
|
+
await this.runOnReceiveHooks(meta, response);
|
|
1368
|
+
let validated;
|
|
1369
|
+
if (meta.streamResponse) {
|
|
1370
|
+
validated = response;
|
|
1371
|
+
} else {
|
|
1372
|
+
try {
|
|
1373
|
+
validated = this.validateResponse(response, meta);
|
|
1374
|
+
} catch (cause) {
|
|
1375
|
+
throw new HTTPSchemaValidationError(request, response, cause);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
await this.runResponsePlugins({
|
|
1379
|
+
request,
|
|
1380
|
+
response: validated,
|
|
1381
|
+
options: attemptOptions
|
|
1382
|
+
});
|
|
1383
|
+
return validated;
|
|
1384
|
+
} catch (thrown) {
|
|
1385
|
+
const normalized = thrown instanceof HTTPError ? thrown : thrown;
|
|
1386
|
+
const retryAttempt = retriesUsed + 1;
|
|
1387
|
+
const policy = retryPolicy;
|
|
1388
|
+
const canRetry = policy !== void 0 && retryAttempt <= maxRetries && await this.shouldRetryRequest(
|
|
1389
|
+
normalized,
|
|
1390
|
+
policy,
|
|
1391
|
+
retryAttempt,
|
|
1392
|
+
request
|
|
1393
|
+
);
|
|
1394
|
+
if (!canRetry) {
|
|
1395
|
+
await this.runErrorPlugins({
|
|
1396
|
+
request,
|
|
1397
|
+
options: attemptOptions,
|
|
1398
|
+
error: normalized
|
|
1399
|
+
});
|
|
1400
|
+
throw normalized;
|
|
1401
|
+
}
|
|
1402
|
+
retriesUsed += 1;
|
|
1403
|
+
await this.delayRetry(retryAttempt, policy);
|
|
1404
|
+
} finally {
|
|
1405
|
+
if (timeoutHandle !== void 0) {
|
|
1406
|
+
clearTimeout(timeoutHandle);
|
|
1407
|
+
}
|
|
1408
|
+
combinedSignal?.dispose();
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
async runRequestPlugins(context) {
|
|
1413
|
+
for (const plugin of this.plugins()) {
|
|
1414
|
+
if (plugin.onRequest) {
|
|
1415
|
+
await plugin.onRequest(context);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
async runResponsePlugins(context) {
|
|
1420
|
+
for (const plugin of this.plugins()) {
|
|
1421
|
+
if (plugin.onResponse) {
|
|
1422
|
+
await plugin.onResponse(context);
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
async runErrorPlugins(context) {
|
|
1427
|
+
for (const plugin of this.plugins()) {
|
|
1428
|
+
if (plugin.onError) {
|
|
1429
|
+
await plugin.onError(context);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
async runOnSendHooks(meta, request) {
|
|
1434
|
+
for (const [description, hook] of meta.onSend) {
|
|
1435
|
+
try {
|
|
1436
|
+
await hook(request);
|
|
1437
|
+
} catch (error) {
|
|
1438
|
+
throw new errors.AutomationError(
|
|
1439
|
+
`An error occurred in onSend hook "${description}"`,
|
|
1440
|
+
{ cause: error }
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
async runOnReceiveHooks(meta, response) {
|
|
1446
|
+
for (const [description, hook] of meta.onReceive) {
|
|
1447
|
+
try {
|
|
1448
|
+
await hook(response);
|
|
1449
|
+
} catch (error) {
|
|
1450
|
+
throw new errors.AutomationError(
|
|
1451
|
+
`An error occurred in onReceive hook "${description}"`,
|
|
1452
|
+
{ cause: error }
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
validateResponse(response, meta) {
|
|
1458
|
+
const validated = meta.schemas.validate(
|
|
1459
|
+
response.status,
|
|
1460
|
+
response.data,
|
|
1461
|
+
meta.requireSchema
|
|
1462
|
+
);
|
|
1463
|
+
response.data = validated;
|
|
1464
|
+
return response;
|
|
1465
|
+
}
|
|
1466
|
+
buildResponse(raw, request) {
|
|
1467
|
+
return HTTPResponseBuilder.create().status(raw.status).statusText(raw.statusText).headers(normalizeHeaders(raw.headers)).data(raw.data).request(request).build();
|
|
1468
|
+
}
|
|
1469
|
+
plugins() {
|
|
1470
|
+
return [...this.sharedPlugins, ...this.scopedPlugins];
|
|
1471
|
+
}
|
|
1472
|
+
async shouldRetryRequest(error, policy, attempt, request) {
|
|
1473
|
+
if (attempt > policy.attempts) {
|
|
1474
|
+
return false;
|
|
1475
|
+
}
|
|
1476
|
+
const response = error instanceof HTTPError ? error.response : void 0;
|
|
1477
|
+
if (policy.retryOn) {
|
|
1478
|
+
const retryContext = response ? { error, attempt, request, response } : { error, attempt, request };
|
|
1479
|
+
return await policy.retryOn(retryContext);
|
|
1480
|
+
}
|
|
1481
|
+
if (error instanceof HTTPTransportError) {
|
|
1482
|
+
return true;
|
|
1483
|
+
}
|
|
1484
|
+
if (response && response.status >= 500) {
|
|
1485
|
+
return true;
|
|
1486
|
+
}
|
|
1487
|
+
return false;
|
|
1488
|
+
}
|
|
1489
|
+
async delayRetry(attempt, policy) {
|
|
1490
|
+
const { delay } = policy;
|
|
1491
|
+
let duration;
|
|
1492
|
+
if (typeof delay === "function") {
|
|
1493
|
+
duration = await delay(attempt);
|
|
1494
|
+
} else if (typeof delay === "number") {
|
|
1495
|
+
duration = delay * attempt;
|
|
1496
|
+
} else {
|
|
1497
|
+
duration = attempt * 100;
|
|
1498
|
+
}
|
|
1499
|
+
if (!duration || duration <= 0) {
|
|
1500
|
+
return;
|
|
1501
|
+
}
|
|
1502
|
+
await new Promise((resolve) => setTimeout(resolve, duration));
|
|
1503
|
+
}
|
|
1504
|
+
derive(mutate, overrides) {
|
|
1505
|
+
const builder = this.builder.clone();
|
|
1506
|
+
const meta = this.meta.derive();
|
|
1507
|
+
const plugins = [...this.scopedPlugins];
|
|
1508
|
+
if (mutate) {
|
|
1509
|
+
mutate({ builder, meta, plugins });
|
|
1510
|
+
}
|
|
1511
|
+
return new _HTTP(
|
|
1512
|
+
overrides?.transport ?? this.transport,
|
|
1513
|
+
builder,
|
|
1514
|
+
meta,
|
|
1515
|
+
this.sharedPlugins,
|
|
1516
|
+
plugins
|
|
1517
|
+
);
|
|
1518
|
+
}
|
|
1519
|
+
};
|
|
1520
|
+
_HTTP.sharedPlugins = [];
|
|
1521
|
+
var HTTP = _HTTP;
|
|
1522
|
+
function mergeOptions2(base, overrides) {
|
|
1523
|
+
if (!overrides) {
|
|
1524
|
+
return { ...base };
|
|
1525
|
+
}
|
|
1526
|
+
const merged = { ...base };
|
|
1527
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
1528
|
+
if (value === void 0) {
|
|
1529
|
+
delete merged[key];
|
|
1530
|
+
} else {
|
|
1531
|
+
merged[key] = value;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
return merged;
|
|
1535
|
+
}
|
|
1536
|
+
function normalizeHeaders(headers) {
|
|
1537
|
+
const next = {};
|
|
1538
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
1539
|
+
next[key] = Array.isArray(value) ? value.join(",") : String(value);
|
|
1540
|
+
}
|
|
1541
|
+
return next;
|
|
1542
|
+
}
|
|
1543
|
+
function toParamValue(value, rest) {
|
|
1544
|
+
if (rest.length > 0) {
|
|
1545
|
+
return [value, ...rest];
|
|
1546
|
+
}
|
|
1547
|
+
return value;
|
|
1548
|
+
}
|
|
1549
|
+
function createTimeoutController(timeoutMs) {
|
|
1550
|
+
const controller = new AbortController();
|
|
1551
|
+
const timer = setTimeout(() => {
|
|
1552
|
+
controller.abort(createAbortError(`Request timed out after ${timeoutMs} ms`));
|
|
1553
|
+
}, timeoutMs);
|
|
1554
|
+
maybeUnrefTimer(timer);
|
|
1555
|
+
return { controller, timer };
|
|
1556
|
+
}
|
|
1557
|
+
function combineAbortSignals(signals) {
|
|
1558
|
+
const controller = new AbortController();
|
|
1559
|
+
const aborted = signals.find((signal) => signal.aborted);
|
|
1560
|
+
if (aborted) {
|
|
1561
|
+
controller.abort(aborted.reason ?? createAbortError());
|
|
1562
|
+
return { signal: controller.signal, dispose: () => void 0 };
|
|
1563
|
+
}
|
|
1564
|
+
const listeners = [];
|
|
1565
|
+
for (const signal of signals) {
|
|
1566
|
+
const listener = () => {
|
|
1567
|
+
controller.abort(signal.reason ?? createAbortError());
|
|
1568
|
+
};
|
|
1569
|
+
signal.addEventListener("abort", listener, { once: true });
|
|
1570
|
+
listeners.push({ signal, listener });
|
|
1571
|
+
}
|
|
1572
|
+
const dispose = () => {
|
|
1573
|
+
for (const { signal, listener } of listeners) {
|
|
1574
|
+
signal.removeEventListener("abort", listener);
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
controller.signal.addEventListener("abort", dispose, { once: true });
|
|
1578
|
+
return { signal: controller.signal, dispose };
|
|
1579
|
+
}
|
|
1580
|
+
function maybeUnrefTimer(timer) {
|
|
1581
|
+
if (typeof timer === "object" && timer !== null) {
|
|
1582
|
+
const maybeTimer = timer;
|
|
1583
|
+
if (typeof maybeTimer.unref === "function") {
|
|
1584
|
+
maybeTimer.unref();
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
function createAbortError(message = "The operation was aborted.") {
|
|
1589
|
+
const error = new Error(message);
|
|
1590
|
+
error.name = "AbortError";
|
|
1591
|
+
return error;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
// src/default-schema.ts
|
|
1595
|
+
function AnySchema(data) {
|
|
1596
|
+
return data;
|
|
1597
|
+
}
|
|
1598
|
+
function EmptySchema(data) {
|
|
1599
|
+
if (data !== null && data !== void 0 && data !== "null") {
|
|
1600
|
+
throw new Error(`Expected null but received ${describeValue(data)}`);
|
|
1601
|
+
}
|
|
1602
|
+
return data === "null" ? null : data;
|
|
1603
|
+
}
|
|
1604
|
+
function NullSchema(data) {
|
|
1605
|
+
if (data !== null && data !== "null") {
|
|
1606
|
+
throw new Error(`Expected null but received ${describeValue(data)}`);
|
|
1607
|
+
}
|
|
1608
|
+
return null;
|
|
1609
|
+
}
|
|
1610
|
+
function UndefinedSchema(data) {
|
|
1611
|
+
if (data !== void 0) {
|
|
1612
|
+
throw new Error(`Expected undefined but received ${describeValue(data)}`);
|
|
1613
|
+
}
|
|
1614
|
+
return void 0;
|
|
1615
|
+
}
|
|
1616
|
+
function BooleanSchema(data) {
|
|
1617
|
+
if (typeof data === "boolean" || typeof data === "string" && ["true", "false"].includes(data)) {
|
|
1618
|
+
return typeof data === "boolean" ? data : data === "true";
|
|
1619
|
+
}
|
|
1620
|
+
throw new Error(`Expected boolean but received ${describeValue(data)}`);
|
|
1621
|
+
}
|
|
1622
|
+
function NumberSchema(data) {
|
|
1623
|
+
if (typeof data === "number") {
|
|
1624
|
+
return data;
|
|
1625
|
+
}
|
|
1626
|
+
if (typeof data === "string" && /^(?:\d+|\d*\.\d+)$/.test(data)) {
|
|
1627
|
+
return Number(data);
|
|
1628
|
+
}
|
|
1629
|
+
throw new Error(`Expected number but received ${describeValue(data)}`);
|
|
1630
|
+
}
|
|
1631
|
+
function StringSchema(data) {
|
|
1632
|
+
if (typeof data === "string") {
|
|
1633
|
+
return data;
|
|
1634
|
+
}
|
|
1635
|
+
throw new Error(`Expected string but received ${describeValue(data)}`);
|
|
1636
|
+
}
|
|
1637
|
+
function JSONSchema(data) {
|
|
1638
|
+
if (typeof data === "object" && data !== null) {
|
|
1639
|
+
return data;
|
|
1640
|
+
}
|
|
1641
|
+
if (typeof data === "string") {
|
|
1642
|
+
const parsed = tryParseJson2(data);
|
|
1643
|
+
if (parsed !== void 0) {
|
|
1644
|
+
return parsed;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
throw new Error(`Expected JSON but received ${describeValue(data)}`);
|
|
1648
|
+
}
|
|
1649
|
+
function describeValue(value) {
|
|
1650
|
+
return `<${typeof value}> ${String(value)}`;
|
|
1651
|
+
}
|
|
1652
|
+
function tryParseJson2(value) {
|
|
1653
|
+
try {
|
|
1654
|
+
return JSON.parse(value);
|
|
1655
|
+
} catch {
|
|
1656
|
+
return void 0;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
// src/plugins.ts
|
|
1661
|
+
function createLoggingPlugin(sink) {
|
|
1662
|
+
return {
|
|
1663
|
+
name: "http-logging",
|
|
1664
|
+
async onRequest(context) {
|
|
1665
|
+
await sink({
|
|
1666
|
+
type: "request",
|
|
1667
|
+
timestamp: Date.now(),
|
|
1668
|
+
request: context.request,
|
|
1669
|
+
options: context.options
|
|
1670
|
+
});
|
|
1671
|
+
},
|
|
1672
|
+
async onResponse(context) {
|
|
1673
|
+
await sink({
|
|
1674
|
+
type: "response",
|
|
1675
|
+
timestamp: Date.now(),
|
|
1676
|
+
request: context.request,
|
|
1677
|
+
response: context.response,
|
|
1678
|
+
options: context.options
|
|
1679
|
+
});
|
|
1680
|
+
},
|
|
1681
|
+
async onError(context) {
|
|
1682
|
+
await sink({
|
|
1683
|
+
type: "error",
|
|
1684
|
+
timestamp: Date.now(),
|
|
1685
|
+
request: context.request,
|
|
1686
|
+
error: context.error,
|
|
1687
|
+
options: context.options
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// src/axios-transport.ts
|
|
1694
|
+
function createAxiosTransport(axios) {
|
|
1695
|
+
if (!axios || typeof axios.request !== "function") {
|
|
1696
|
+
throw new Error("Axios transport requires an axios-like client instance.");
|
|
1697
|
+
}
|
|
1698
|
+
return {
|
|
1699
|
+
async send(request, options = {}) {
|
|
1700
|
+
const {
|
|
1701
|
+
headers: optionHeaders,
|
|
1702
|
+
streamResponse,
|
|
1703
|
+
...restOptions
|
|
1704
|
+
} = options;
|
|
1705
|
+
const config = {
|
|
1706
|
+
url: request.fullUrl ?? "",
|
|
1707
|
+
method: request.method ?? "GET",
|
|
1708
|
+
headers: { ...request.headers },
|
|
1709
|
+
data: request.data,
|
|
1710
|
+
validateStatus: () => true,
|
|
1711
|
+
...restOptions
|
|
1712
|
+
};
|
|
1713
|
+
if (optionHeaders) {
|
|
1714
|
+
config.headers = mergeHeaders2(config.headers ?? {}, optionHeaders);
|
|
1715
|
+
}
|
|
1716
|
+
if (streamResponse) {
|
|
1717
|
+
config.responseType = config.responseType ?? "stream";
|
|
1718
|
+
}
|
|
1719
|
+
const response = await axios.request(config);
|
|
1720
|
+
return {
|
|
1721
|
+
status: response.status,
|
|
1722
|
+
statusText: response.statusText,
|
|
1723
|
+
headers: response.headers ?? {},
|
|
1724
|
+
data: response.data
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
function mergeHeaders2(base, overrides) {
|
|
1730
|
+
const next = {};
|
|
1731
|
+
for (const [key, value] of Object.entries(base)) {
|
|
1732
|
+
if (value === void 0 || value === null) {
|
|
1733
|
+
continue;
|
|
1734
|
+
}
|
|
1735
|
+
next[key] = String(value);
|
|
1736
|
+
}
|
|
1737
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
1738
|
+
if (value === void 0 || value === null) {
|
|
1739
|
+
delete next[key];
|
|
1740
|
+
continue;
|
|
1741
|
+
}
|
|
1742
|
+
next[key] = String(value);
|
|
1743
|
+
}
|
|
1744
|
+
return next;
|
|
1745
|
+
}
|
|
1746
|
+
function ensureHttp(response, options = {}) {
|
|
1747
|
+
return new HttpEnsureChainImpl(response, {
|
|
1748
|
+
...options.label ? { label: options.label } : {},
|
|
1749
|
+
negated: Boolean(options.negated)
|
|
1750
|
+
});
|
|
1751
|
+
}
|
|
1752
|
+
var HttpEnsureChainImpl = class _HttpEnsureChainImpl {
|
|
1753
|
+
constructor(value, state) {
|
|
1754
|
+
this.value = value;
|
|
1755
|
+
this.label = state.label;
|
|
1756
|
+
this.negated = state.negated;
|
|
1757
|
+
this.normalized = normalizeResponse(value);
|
|
1758
|
+
}
|
|
1759
|
+
get not() {
|
|
1760
|
+
return new _HttpEnsureChainImpl(this.value, {
|
|
1761
|
+
...this.label ? { label: this.label } : {},
|
|
1762
|
+
negated: !this.negated
|
|
1763
|
+
});
|
|
1764
|
+
}
|
|
1765
|
+
toHaveStatus(expectation) {
|
|
1766
|
+
const { pass, description } = matchesStatus(
|
|
1767
|
+
this.normalized.status,
|
|
1768
|
+
expectation
|
|
1769
|
+
);
|
|
1770
|
+
if (shouldFail(pass, this.negated)) {
|
|
1771
|
+
const baseMessage = this.negated ? `Expected response status not to be ${description}` : `Expected response status to be ${description}`;
|
|
1772
|
+
this.fail({
|
|
1773
|
+
matcher: "toHaveStatus",
|
|
1774
|
+
message: baseMessage,
|
|
1775
|
+
actual: this.normalized.status,
|
|
1776
|
+
expected: description
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
return this;
|
|
1780
|
+
}
|
|
1781
|
+
toHaveHeader(name, expectation) {
|
|
1782
|
+
const key = name.toLowerCase();
|
|
1783
|
+
const actual = this.normalized.headers[key];
|
|
1784
|
+
if (expectation === void 0) {
|
|
1785
|
+
const pass2 = actual !== void 0;
|
|
1786
|
+
if (shouldFail(pass2, this.negated)) {
|
|
1787
|
+
const baseMessage = this.negated ? `Expected response not to include header ${name}` : `Expected response to include header ${name}`;
|
|
1788
|
+
this.fail({
|
|
1789
|
+
matcher: "toHaveHeader",
|
|
1790
|
+
message: baseMessage,
|
|
1791
|
+
actual: actual ?? "<missing>",
|
|
1792
|
+
expected: name
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
return this;
|
|
1796
|
+
}
|
|
1797
|
+
const pass = matchHeaderValue(actual, expectation);
|
|
1798
|
+
if (shouldFail(pass, this.negated)) {
|
|
1799
|
+
const baseMessage = this.negated ? `Expected header ${name} not to match` : `Expected header ${name} to match`;
|
|
1800
|
+
this.fail({
|
|
1801
|
+
matcher: "toHaveHeader",
|
|
1802
|
+
message: baseMessage,
|
|
1803
|
+
actual: actual ?? "<missing>",
|
|
1804
|
+
expected: expectation
|
|
1805
|
+
});
|
|
1806
|
+
}
|
|
1807
|
+
return this;
|
|
1808
|
+
}
|
|
1809
|
+
toBeCacheable(expectation = {}) {
|
|
1810
|
+
const cacheControl = this.normalized.headers["cache-control"];
|
|
1811
|
+
if (!cacheControl) {
|
|
1812
|
+
if (!this.negated) {
|
|
1813
|
+
this.fail({
|
|
1814
|
+
matcher: "toBeCacheable",
|
|
1815
|
+
message: "Expected Cache-Control header to be present",
|
|
1816
|
+
actual: "<missing>"
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
return this;
|
|
1820
|
+
}
|
|
1821
|
+
const directives = parseCacheControl(cacheControl);
|
|
1822
|
+
const impliedCacheable = !("no-store" in directives || "no-cache" in directives);
|
|
1823
|
+
const expectationPass = evaluateCacheExpectations(directives, expectation);
|
|
1824
|
+
const pass = impliedCacheable && expectationPass;
|
|
1825
|
+
if (shouldFail(pass, this.negated)) {
|
|
1826
|
+
const baseMessage = this.negated ? "Expected response not to advertise cacheable directives" : "Expected response to advertise cacheable directives";
|
|
1827
|
+
this.fail({
|
|
1828
|
+
matcher: "toBeCacheable",
|
|
1829
|
+
message: baseMessage,
|
|
1830
|
+
actual: directives,
|
|
1831
|
+
expected: expectation
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
return this;
|
|
1835
|
+
}
|
|
1836
|
+
toHaveCorrelationId(headerName = "x-correlation-id") {
|
|
1837
|
+
const key = headerName.toLowerCase();
|
|
1838
|
+
const value = this.normalized.headers[key];
|
|
1839
|
+
const pass = typeof value === "string" && value.trim().length > 0;
|
|
1840
|
+
if (shouldFail(pass, this.negated)) {
|
|
1841
|
+
const baseMessage = this.negated ? `Expected header ${headerName} to be missing or empty` : `Expected header ${headerName} to be present`;
|
|
1842
|
+
this.fail({
|
|
1843
|
+
matcher: "toHaveCorrelationId",
|
|
1844
|
+
message: baseMessage,
|
|
1845
|
+
actual: value ?? "<missing>",
|
|
1846
|
+
expected: headerName
|
|
1847
|
+
});
|
|
1848
|
+
}
|
|
1849
|
+
return this;
|
|
1850
|
+
}
|
|
1851
|
+
fail(details) {
|
|
1852
|
+
const merged = {
|
|
1853
|
+
...details,
|
|
1854
|
+
...this.label ? { receivedLabel: this.label } : {}
|
|
1855
|
+
};
|
|
1856
|
+
throw new assertions.EnsureError(merged);
|
|
1857
|
+
}
|
|
1858
|
+
};
|
|
1859
|
+
function shouldFail(pass, negated) {
|
|
1860
|
+
return negated ? pass : !pass;
|
|
1861
|
+
}
|
|
1862
|
+
function normalizeResponse(response) {
|
|
1863
|
+
return {
|
|
1864
|
+
status: response.status,
|
|
1865
|
+
statusText: response.statusText ?? "",
|
|
1866
|
+
headers: normalizeHeaders2(response.headers),
|
|
1867
|
+
original: response.raw ?? response
|
|
1868
|
+
};
|
|
1869
|
+
}
|
|
1870
|
+
function normalizeHeaders2(source) {
|
|
1871
|
+
const result = {};
|
|
1872
|
+
if (typeof source.get === "function") {
|
|
1873
|
+
const entries = source.entries;
|
|
1874
|
+
const iterator = entries ? entries.call(source) : source[Symbol.iterator]?.call(source);
|
|
1875
|
+
if (iterator) {
|
|
1876
|
+
for (const [name, value] of iterator) {
|
|
1877
|
+
result[String(name).toLowerCase()] = String(value);
|
|
1878
|
+
}
|
|
1879
|
+
return result;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
for (const [key, value] of Object.entries(
|
|
1883
|
+
source
|
|
1884
|
+
)) {
|
|
1885
|
+
result[String(key).toLowerCase()] = String(value);
|
|
1886
|
+
}
|
|
1887
|
+
return result;
|
|
1888
|
+
}
|
|
1889
|
+
function matchesStatus(status, expectation) {
|
|
1890
|
+
if (typeof expectation === "number") {
|
|
1891
|
+
return {
|
|
1892
|
+
pass: status === expectation,
|
|
1893
|
+
description: expectation.toString()
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
if (typeof expectation === "string") {
|
|
1897
|
+
const digit = Number.parseInt(expectation.charAt(0), 10);
|
|
1898
|
+
if (!Number.isNaN(digit) && expectation.endsWith("xx")) {
|
|
1899
|
+
const min = digit * 100;
|
|
1900
|
+
return {
|
|
1901
|
+
pass: status >= min && status < min + 100,
|
|
1902
|
+
description: `${digit}xx`
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
return {
|
|
1906
|
+
pass: status.toString() === expectation,
|
|
1907
|
+
description: expectation
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
if (Array.isArray(expectation)) {
|
|
1911
|
+
const [min, max] = expectation;
|
|
1912
|
+
return {
|
|
1913
|
+
pass: status >= min && status <= max,
|
|
1914
|
+
description: `[${min}, ${max}]`
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
if (typeof expectation === "function") {
|
|
1918
|
+
return {
|
|
1919
|
+
pass: expectation(status),
|
|
1920
|
+
description: "predicate(status)"
|
|
1921
|
+
};
|
|
1922
|
+
}
|
|
1923
|
+
const range = expectation;
|
|
1924
|
+
return {
|
|
1925
|
+
pass: status >= range.min && status <= range.max,
|
|
1926
|
+
description: `[${range.min}, ${range.max}]`
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
function matchHeaderValue(actual, expected) {
|
|
1930
|
+
if (actual === void 0) {
|
|
1931
|
+
return false;
|
|
1932
|
+
}
|
|
1933
|
+
if (typeof expected === "string") {
|
|
1934
|
+
return actual === expected;
|
|
1935
|
+
}
|
|
1936
|
+
if (expected instanceof RegExp) {
|
|
1937
|
+
return expected.test(actual);
|
|
1938
|
+
}
|
|
1939
|
+
if (Array.isArray(expected)) {
|
|
1940
|
+
const list = expected;
|
|
1941
|
+
const actualParts = actual.split(",").map((part) => part.trim()).filter(Boolean);
|
|
1942
|
+
return list.every((value) => actualParts.includes(value));
|
|
1943
|
+
}
|
|
1944
|
+
return expected(actual);
|
|
1945
|
+
}
|
|
1946
|
+
function parseCacheControl(value) {
|
|
1947
|
+
const directives = {};
|
|
1948
|
+
for (const segment of value.split(",")) {
|
|
1949
|
+
const trimmed = segment.trim();
|
|
1950
|
+
if (!trimmed) {
|
|
1951
|
+
continue;
|
|
1952
|
+
}
|
|
1953
|
+
const [rawName, rawParameter] = trimmed.split("=", 2);
|
|
1954
|
+
const name = rawName?.trim();
|
|
1955
|
+
if (!name) {
|
|
1956
|
+
continue;
|
|
1957
|
+
}
|
|
1958
|
+
if (rawParameter === void 0) {
|
|
1959
|
+
directives[name.toLowerCase()] = true;
|
|
1960
|
+
continue;
|
|
1961
|
+
}
|
|
1962
|
+
const parameter = rawParameter.trim().replace(/^"|"$/g, "");
|
|
1963
|
+
directives[name.toLowerCase()] = parameter;
|
|
1964
|
+
}
|
|
1965
|
+
return directives;
|
|
1966
|
+
}
|
|
1967
|
+
function evaluateCacheExpectations(directives, expectation) {
|
|
1968
|
+
if (expectation.cacheability) {
|
|
1969
|
+
if (!(expectation.cacheability in directives)) {
|
|
1970
|
+
return false;
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
if (expectation.maxAge !== void 0) {
|
|
1974
|
+
const raw = directives["max-age"];
|
|
1975
|
+
if (typeof raw !== "string") {
|
|
1976
|
+
return false;
|
|
1977
|
+
}
|
|
1978
|
+
const parsed = Number.parseInt(raw, 10);
|
|
1979
|
+
if (Number.isNaN(parsed)) {
|
|
1980
|
+
return false;
|
|
1981
|
+
}
|
|
1982
|
+
if (typeof expectation.maxAge === "number") {
|
|
1983
|
+
if (parsed !== expectation.maxAge) {
|
|
1984
|
+
return false;
|
|
1985
|
+
}
|
|
1986
|
+
} else {
|
|
1987
|
+
const { min, max } = expectation.maxAge;
|
|
1988
|
+
if (min !== void 0 && parsed < min) {
|
|
1989
|
+
return false;
|
|
1990
|
+
}
|
|
1991
|
+
if (max !== void 0 && parsed > max) {
|
|
1992
|
+
return false;
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
if (expectation.sMaxAge !== void 0) {
|
|
1997
|
+
const raw = directives["s-maxage"];
|
|
1998
|
+
if (typeof raw !== "string") {
|
|
1999
|
+
return false;
|
|
2000
|
+
}
|
|
2001
|
+
const parsed = Number.parseInt(raw, 10);
|
|
2002
|
+
if (Number.isNaN(parsed) || parsed !== expectation.sMaxAge) {
|
|
2003
|
+
return false;
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
if (expectation.revalidate) {
|
|
2007
|
+
if (!("must-revalidate" in directives || "proxy-revalidate" in directives)) {
|
|
2008
|
+
return false;
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
if (expectation.immutable && !("immutable" in directives)) {
|
|
2012
|
+
return false;
|
|
2013
|
+
}
|
|
2014
|
+
return true;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
// src/assertions/http-assertions-plugin.ts
|
|
2018
|
+
var httpAssertionsPlugin = () => ({ isNot }) => (_world) => {
|
|
2019
|
+
const facet = (response, options) => {
|
|
2020
|
+
return ensureHttp(response, {
|
|
2021
|
+
...options?.label ? { label: options.label } : {},
|
|
2022
|
+
negated: isNot
|
|
2023
|
+
});
|
|
2024
|
+
};
|
|
2025
|
+
return facet;
|
|
2026
|
+
};
|
|
2027
|
+
|
|
2028
|
+
// src/assertions/http-adapters.ts
|
|
2029
|
+
function fromHttpResponse(response) {
|
|
2030
|
+
return {
|
|
2031
|
+
status: response.status,
|
|
2032
|
+
statusText: response.statusText ?? "",
|
|
2033
|
+
headers: response.headers,
|
|
2034
|
+
data: response.data,
|
|
2035
|
+
raw: response
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
function fromFetchResponse(response, data) {
|
|
2039
|
+
return {
|
|
2040
|
+
status: response.status,
|
|
2041
|
+
statusText: response.statusText ?? "",
|
|
2042
|
+
headers: response.headers,
|
|
2043
|
+
data,
|
|
2044
|
+
raw: response
|
|
2045
|
+
};
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
exports.AnySchema = AnySchema;
|
|
2049
|
+
exports.BooleanSchema = BooleanSchema;
|
|
2050
|
+
exports.EmptySchema = EmptySchema;
|
|
2051
|
+
exports.HTTP = HTTP;
|
|
2052
|
+
exports.HTTPError = HTTPError;
|
|
2053
|
+
exports.HTTPRequest = HTTPRequest;
|
|
2054
|
+
exports.HTTPRequestBuilder = HTTPRequestBuilder;
|
|
2055
|
+
exports.HTTPResponse = HTTPResponse;
|
|
2056
|
+
exports.HTTPResponseBuilder = HTTPResponseBuilder;
|
|
2057
|
+
exports.HTTPSchemaValidationError = HTTPSchemaValidationError;
|
|
2058
|
+
exports.HTTPTransportError = HTTPTransportError;
|
|
2059
|
+
exports.JSONSchema = JSONSchema;
|
|
2060
|
+
exports.MetaConfig = MetaConfig;
|
|
2061
|
+
exports.MetaConfigBuilder = MetaConfigBuilder;
|
|
2062
|
+
exports.NullSchema = NullSchema;
|
|
2063
|
+
exports.NumberSchema = NumberSchema;
|
|
2064
|
+
exports.SchemaMap = SchemaMap;
|
|
2065
|
+
exports.StringSchema = StringSchema;
|
|
2066
|
+
exports.UndefinedSchema = UndefinedSchema;
|
|
2067
|
+
exports.createAxiosTransport = createAxiosTransport;
|
|
2068
|
+
exports.createFetchTransport = createFetchTransport;
|
|
2069
|
+
exports.createLoggingPlugin = createLoggingPlugin;
|
|
2070
|
+
exports.ensureHttp = ensureHttp;
|
|
2071
|
+
exports.fromFetchResponse = fromFetchResponse;
|
|
2072
|
+
exports.fromHttpResponse = fromHttpResponse;
|
|
2073
|
+
exports.httpAssertionsPlugin = httpAssertionsPlugin;
|
|
2074
|
+
exports.transformResponse = transformResponse;
|
|
2075
|
+
//# sourceMappingURL=out.js.map
|
|
2076
|
+
//# sourceMappingURL=index.cjs.map
|