@autometa/http 1.4.20 → 2.0.0-rc.1

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