@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
@@ -1,35 +0,0 @@
1
- import { HTTPRequest } from "./http-request";
2
- import { HTTPResponse, HTTPResponseBuilder } from "./http-response";
3
- import { HTTPAdditionalOptions, StatusCode } from "./types";
4
- import axios, { AxiosRequestConfig } from "axios";
5
- import { HTTPClient } from "./http-client";
6
-
7
- @HTTPClient.Use()
8
- export class AxiosClient extends HTTPClient {
9
- async request<TRequestType, TResponseType>(
10
- request: HTTPRequest<TRequestType>,
11
- options: HTTPAdditionalOptions<AxiosRequestConfig>
12
- ): Promise<HTTPResponse<TResponseType>> {
13
- const { baseUrl, route, params, headers, method, data } = request;
14
- const url = [baseUrl, ...route].join("/");
15
- const axiosRequest: AxiosRequestConfig = {
16
- url,
17
- params,
18
- headers,
19
- method,
20
- data,
21
- validateStatus: function (status) {
22
- return status >= 0 && status < 600;
23
- },
24
- ...options,
25
- };
26
- const response = await axios(axiosRequest);
27
- return HTTPResponseBuilder.create()
28
- .status(response.status as StatusCode)
29
- .statusText(response.statusText)
30
- .data(response.data)
31
- .headers(response.headers as Record<string, string>)
32
- .request(request)
33
- .build() as HTTPResponse<TResponseType>;
34
- }
35
- }
@@ -1,9 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { defaultClientFactory } from "./default-client-factory";
3
- import { AxiosClient } from "./axios-client";
4
- describe("default client as Axios", () => {
5
- it("should be an Axios client", () => {
6
- const client = defaultClientFactory();
7
- expect(client).instanceOf(AxiosClient);
8
- });
9
- });
@@ -1,13 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { defaultClientFactory } from "./default-client-factory";
3
- import { HTTPClient } from "./http-client";
4
- describe("default client as Axios", () => {
5
- @HTTPClient.Use()
6
- class TestHTTPClient extends HTTPClient {
7
- request = vi.fn();
8
- }
9
- it("should be an Axios client", () => {
10
- const client = defaultClientFactory();
11
- expect(client).instanceOf(TestHTTPClient);
12
- });
13
- });
@@ -1,7 +0,0 @@
1
- import { Class } from "@autometa/types";
2
- import { defaultClient, type HTTPClient } from "./http-client";
3
-
4
- export function defaultClientFactory() {
5
- const type = defaultClient as Class<HTTPClient>;
6
- return new type();
7
- }
@@ -1,74 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import {
3
- AnySchema,
4
- BooleanSchema,
5
- NullSchema,
6
- NumberSchema,
7
- StringSchema,
8
- UndefinedSchema,
9
- } from "./default-schema";
10
- describe("Default Schema Functions", () => {
11
- describe("AnySchema", () => {
12
- it("should return the data", () => {
13
- expect(AnySchema(1)).toBe(1);
14
- });
15
- });
16
-
17
- describe("StringSchema", () => {
18
- it("should return the data", () => {
19
- expect(StringSchema("1")).toBe("1");
20
- });
21
- });
22
-
23
- describe("NumberSchema", () => {
24
- it("should return the data", () => {
25
- expect(NumberSchema(1)).toBe(1);
26
- });
27
-
28
- it("should return stringified data", () => {
29
- expect(NumberSchema("1")).toBe(1);
30
- });
31
- });
32
-
33
- describe("BooleanSchema", () => {
34
- it("should return the data", () => {
35
- expect(BooleanSchema(true)).toBe(true);
36
- });
37
-
38
- it("should return stringified data", () => {
39
- expect(BooleanSchema("true")).toBe(true);
40
- });
41
- });
42
-
43
- describe("NullSchema", () => {
44
- it("should return the data", () => {
45
- expect(NullSchema(null)).toBe(null);
46
- });
47
-
48
- it("should return the stringified data", () => {
49
- expect(NullSchema("null")).toBe(null);
50
- });
51
-
52
- it("should throw if the data is not null", () => {
53
- expect(() => NullSchema(1)).toThrow();
54
- });
55
-
56
- it("should throw if the data is not null", () => {
57
- expect(() => NullSchema(undefined)).toThrow();
58
- });
59
- });
60
-
61
- describe("UndefinedSchema", () => {
62
- it("should return the data", () => {
63
- expect(UndefinedSchema(undefined)).toBe(undefined);
64
- });
65
-
66
- it("should throw if the data is not undefined", () => {
67
- expect(() => UndefinedSchema(1)).toThrow();
68
- });
69
-
70
- it("should throw if the data is not undefined", () => {
71
- expect(() => UndefinedSchema(null)).toThrow();
72
- });
73
- });
74
- });
@@ -1,127 +0,0 @@
1
- import isJson from "@stdlib/assert-is-json";
2
-
3
- /**
4
- * Schema which does not care about data validation.
5
- *
6
- * Useful if `requireSchema` is set to true, but a specific
7
- * endpoints response does not matter.
8
- * @param data
9
- * @returns
10
- */
11
- export function AnySchema(data: unknown) {
12
- return data;
13
- }
14
-
15
- /**
16
- * Schema which validates that a response is empty. This can mean
17
- * the data payload was `null`, `undefined` or the string `'null'`.
18
- *
19
- * Useful if `requireSchema` is set to true, but a specific
20
- * endpoints response should be null or not defined.
21
- * @param data
22
- * @returns
23
- */
24
- export function EmptySchema(data: unknown) {
25
- if (data !== null && data !== undefined && data !== "null") {
26
- throw new Error(`Expected null but got <${typeof data}> for ${data}`);
27
- }
28
- return data === "null" ? null : data;
29
- }
30
-
31
- /**
32
- * Schema which validates a response was null.
33
- *
34
- * Useful if `requireSchema` is set to true, but a specific
35
- * endpoints response should be null.
36
- * @param data
37
- * @returns
38
- */
39
- export function NullSchema(data: unknown) {
40
- if (data !== null && data !== "null") {
41
- throw new Error(`Expected null but got <${typeof data}> for ${data}`);
42
- }
43
- return null;
44
- }
45
-
46
- /**
47
- * Schema which validates a response was undefined.
48
- *
49
- * Useful if `requireSchema` is set to true, but a specific
50
- * endpoints response should be undefined.
51
- *
52
- * @param data
53
- * @returns
54
- */
55
- export function UndefinedSchema(data: unknown) {
56
- if (data !== undefined) {
57
- throw new Error(`Expected undefined but got <${typeof data}> for ${data}`);
58
- }
59
- return undefined;
60
- }
61
-
62
- /**
63
- * Schema which validates a response was a boolean, or a string of value
64
- * `'true'` or `'false'`.
65
- *
66
- * Useful if `requireSchema` is set to true, but a specific
67
- * endpoints response should be a boolean.
68
- *
69
- * @param data
70
- * @returns
71
- */
72
- export function BooleanSchema(data: unknown) {
73
- if (
74
- !(typeof data === "boolean") &&
75
- ["true", "false"].includes(String(data)) === false
76
- ) {
77
- throw new Error(`Expected boolean but got <${typeof data}> for ${data}`);
78
- }
79
- return JSON.parse(data as string);
80
- }
81
-
82
- /**
83
- * Schema which validates a response was a number, or a string of value
84
- * of a number.
85
- *
86
- * Useful if `requireSchema` is set to true, but a specific
87
- * endpoints response should be a number.
88
- *
89
- * @param data
90
- * @returns
91
- */
92
- export function NumberSchema(data: unknown) {
93
- if (
94
- !(typeof data === "number") &&
95
- /^\d*\.?\d+$/.test(String(data)) === false
96
- ) {
97
- throw new Error(`Expected number but got <${typeof data}> for ${data}`);
98
- }
99
- return JSON.parse(data as string);
100
- }
101
-
102
- /**
103
- * Schema which validates a response was a string.
104
- *
105
- * Useful if `requireSchema` is set to true, but a specific
106
- * endpoints response should be a string.
107
- *
108
- * @param data
109
- * @returns
110
- */
111
- export function StringSchema(data: unknown) {
112
- if (typeof data !== "string") {
113
- throw new Error(`Expected string but got <${typeof data}> for ${data}`);
114
- }
115
- return data;
116
- }
117
-
118
- export function JSONSchema<T = unknown>(data: unknown) {
119
- if (typeof data === "object") {
120
- return data as T;
121
- }
122
- if (!isJson(data)) {
123
- throw new Error(`Expected JSON but got <${typeof data}> for ${data}`);
124
- }
125
- const result = JSON.parse(data);
126
- return result;
127
- }
@@ -1,20 +0,0 @@
1
- import { HTTPRequest } from "./http-request";
2
- import { HTTPResponse } from "./http-response";
3
- import { HTTPAdditionalOptions } from "./types";
4
- import { Class } from "@autometa/types";
5
- export let defaultClient: Class<HTTPClient>;
6
- export abstract class HTTPClient {
7
- static Use(): (target: Class<HTTPClient>) => void;
8
- static Use(client?: Class<HTTPClient>) {
9
- if (client) {
10
- defaultClient = client;
11
- }
12
- return function (target: Class<HTTPClient>) {
13
- defaultClient = target;
14
- };
15
- }
16
- abstract request<TRequestType, TResponseType>(
17
- request: HTTPRequest<TRequestType>,
18
- options?: HTTPAdditionalOptions<unknown>
19
- ): Promise<HTTPResponse<TResponseType>>;
20
- }
@@ -1,172 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { HTTPRequest, HTTPRequestBuilder } from "./http-request";
3
-
4
- describe("HTTP Request", () => {
5
- it("should derive a detailed request", () => {
6
- const request = new HTTPRequest();
7
- request.baseUrl = "https://example.com";
8
- request.route.push("foo", "bar");
9
- request.params = { foo: "bar" };
10
- request.data = { foo: "bar" };
11
- request.headers = { foo: "bar" };
12
- request.method = "GET";
13
-
14
- const derived = HTTPRequest.derive(request);
15
- expect(derived).toEqual({
16
- baseUrl: "https://example.com",
17
- route: ["foo", "bar"],
18
- params: { foo: "bar" },
19
- data: { foo: "bar" },
20
- headers: { foo: "bar" },
21
- method: "GET",
22
- });
23
- });
24
-
25
- describe("HTTPRequestBuilder", () => {
26
- it("should build a request from a base", () => {
27
- const request = new HTTPRequest();
28
- request.baseUrl = "https://example.com";
29
- request.route.push("foo", "bar");
30
- request.params = { foo: "bar" };
31
- request.data = { foo: "bar" };
32
- request.headers = { foo: "bar" };
33
- request.method = "GET";
34
- const builder = new HTTPRequestBuilder(request);
35
- expect(builder.request).toEqual(request);
36
- });
37
-
38
- it("should build a request from scratch", () => {
39
- const builder = new HTTPRequestBuilder()
40
- .url("https://example.com")
41
- .route("foo", "bar")
42
- .param("foo", "bar")
43
- .data({ foo: "bar" })
44
- .header("foo", "bar")
45
- .method("GET");
46
- expect(builder.request).toEqual({
47
- baseUrl: "https://example.com",
48
- route: ["foo", "bar"],
49
- params: { foo: "bar" },
50
- data: { foo: "bar" },
51
- headers: { foo: "bar" },
52
- method: "GET",
53
- });
54
- });
55
-
56
- it("should derive a request builder", () => {
57
- const builder = new HTTPRequestBuilder()
58
- .url("https://example.com")
59
- .route("foo", "bar")
60
- .param("foo", "bar")
61
- .data({ foo: "bar" })
62
- .header("foo", "bar")
63
- .method("GET");
64
- const derived = builder.derive();
65
- expect(derived.request).toEqual(builder.request);
66
- expect(derived.request).not.toBe(builder.request);
67
- });
68
-
69
- describe("build", () => {
70
- it("should build a request", () => {
71
- const request = new HTTPRequestBuilder()
72
- .url("https://example.com")
73
- .route("foo", "bar")
74
- .param("foo", "bar")
75
- .data({ foo: "bar" })
76
- .header("foo", "bar")
77
- .method("GET")
78
- .build();
79
- expect(request).toEqual({
80
- baseUrl: "https://example.com",
81
- route: ["foo", "bar"],
82
- params: { foo: "bar" },
83
- data: { foo: "bar" },
84
- headers: { foo: "bar" },
85
- method: "GET",
86
- });
87
- });
88
- });
89
-
90
- describe("paramList", () => {
91
- it("should build a request with a list of params", () => {
92
- const request = new HTTPRequestBuilder()
93
- .url("https://example.com")
94
- .route("foo", "bar")
95
- .param("foo", ["bar", "baz"])
96
- .data({ foo: "bar" })
97
- .header("foo", "bar")
98
- .method("GET")
99
- .build();
100
- expect(request).toEqual({
101
- baseUrl: "https://example.com",
102
- route: ["foo", "bar"],
103
- params: { foo: ["bar", "baz"] },
104
- data: { foo: "bar" },
105
- headers: { foo: "bar" },
106
- method: "GET",
107
- });
108
- });
109
- });
110
-
111
- describe("fullUrl", () => {
112
- it("should have a full url with routes", () => {
113
- const request = new HTTPRequestBuilder()
114
- .url("https://example.com")
115
- .route("foo")
116
- .build().fullUrl;
117
- expect(request).toEqual("https://example.com/foo");
118
- });
119
- it("should have a full url with routes and params", () => {
120
- const request = new HTTPRequestBuilder()
121
- .url("https://example.com")
122
- .route("foo")
123
- .param("gru", "bar")
124
- .build().fullUrl;
125
- expect(request).toEqual("https://example.com/foo?gru=bar");
126
- });
127
-
128
- it("should have a full url with a route and a param list", () => {
129
- const request = new HTTPRequestBuilder()
130
- .url("https://example.com")
131
- .route("foo")
132
- .param("bob", ["bar", "baz"])
133
- .build().fullUrl;
134
- expect(request).toEqual("https://example.com/foo?bob=bar%2Cbaz");
135
- });
136
- });
137
- });
138
-
139
- describe("dynamic header", () => {
140
- it("should resolve a synchronous dynamic header", async () => {
141
- const builder = new HTTPRequestBuilder()
142
- .url("https://example.com")
143
- .route("foo", "bar")
144
- .param("foo", "bar")
145
- .data({ foo: "bar" })
146
- .header("foo", "bar")
147
- .header("dynamic", () => "foo")
148
- .method("GET");
149
- await builder.resolveDynamicHeaders();
150
- expect(builder.request.headers).toEqual({
151
- foo: "bar",
152
- dynamic: "foo",
153
- });
154
- });
155
-
156
- it("should resolve an asynchronous dynamic header", async () => {
157
- const builder = new HTTPRequestBuilder()
158
- .url("https://example.com")
159
- .route("foo", "bar")
160
- .param("foo", "bar")
161
- .data({ foo: "bar" })
162
- .header("foo", "bar")
163
- .header("dynamic", async () => "foo")
164
- .method("GET");
165
- await builder.resolveDynamicHeaders();
166
- expect(builder.request.headers).toEqual({
167
- foo: "bar",
168
- dynamic: "foo",
169
- });
170
- });
171
- });
172
- });
@@ -1,201 +0,0 @@
1
- import { RequestConfig, RequestConfigBasic } from "./request.config";
2
- import { HTTPMethod } from "./types";
3
- import { urlJoinP } from "url-join-ts";
4
- export class HTTPRequest<T = unknown> implements RequestConfig<T> {
5
- headers: Record<string, string> = {};
6
- params: Record<string, string | string[] | Record<string, unknown>> = {};
7
- baseUrl?: string;
8
- route: string[] = [];
9
- method: HTTPMethod;
10
- data: T;
11
-
12
- constructor(config?: RequestConfigBasic) {
13
- Object.assign(this, config);
14
- }
15
-
16
- /**
17
- * Returns the full URL of the request, including the base url,
18
- * routes, and query parameters.
19
- *
20
- * ```ts
21
- * console.log(request.fullUrl())// https://example.com/foo?bar=baz?array=1,2,3
22
- * ```
23
- *
24
- * Note characters may be converted to escape codes. I.e (space => %20) and (comma => %2C)
25
- *
26
- * N.B this getter estimates what the url will be. The actual value
27
- * might be different depending on your underlying HTTPClient and
28
- * configuration. For example, query parameters might
29
- * use different array formats.
30
- */
31
- get fullUrl() {
32
- return urlJoinP(this.baseUrl, this.route, this.params);
33
- }
34
-
35
- /**
36
- * Returns a new independent copy of the request.
37
- */
38
- static derive(original: HTTPRequest<unknown>) {
39
- const request = new HTTPRequest();
40
- request.headers = { ...original.headers };
41
- request.params = { ...original.params };
42
- request.baseUrl = original.baseUrl;
43
- request.route = [...original.route];
44
- request.method = original.method;
45
- request.data = original.data;
46
- return request;
47
- }
48
- }
49
-
50
- export class HTTPRequestBuilder<T extends HTTPRequest<unknown>> {
51
- #request: T;
52
- #dynamicHeaders = new Map<
53
- string,
54
- | (() => string | number | boolean | null)
55
- | (() => Promise<string | number | boolean | null>)
56
- >();
57
-
58
- constructor(request: T | (() => T) = () => new HTTPRequest() as T) {
59
- if (typeof request === "function") {
60
- this.#request = request();
61
- return;
62
- }
63
- this.#request = request;
64
- }
65
-
66
- static create<T extends HTTPRequest<unknown>>() {
67
- return new HTTPRequestBuilder<T>();
68
- }
69
-
70
- get request() {
71
- return this.#request;
72
- }
73
-
74
- async resolveDynamicHeaders(
75
- request: HTTPRequest<T> = this.#request as HTTPRequest<T>
76
- ) {
77
- for (const [name, value] of this.#dynamicHeaders) {
78
- try {
79
- if (!request.headers) request.headers = {};
80
- request.headers[name] = String(await value());
81
- } catch (e) {
82
- const cause = e as Error;
83
- const msg = `Failed to resolve dynamic header "${name}":
84
- ${cause}`;
85
- throw new Error(msg);
86
- }
87
- }
88
- return this;
89
- }
90
-
91
- url(url: string) {
92
- this.#request.baseUrl = url;
93
- return this;
94
- }
95
-
96
- route(...route: string[]) {
97
- this.#request.route.push(...route);
98
- return this;
99
- }
100
-
101
- param(
102
- name: string,
103
- value:
104
- | string
105
- | number
106
- | boolean
107
- | (string | number | boolean)[]
108
- | Record<string, string | number | boolean>
109
- ) {
110
- if (Array.isArray(value)) {
111
- const asStr = value.flatMap(String);
112
- this.#request.params[name] = asStr;
113
- return this;
114
- }
115
- if (!Array.isArray(value) && typeof value === "object") {
116
- this.#request.params[name] = value;
117
- return this;
118
- }
119
- this.#request.params[name] = String(value);
120
- return this;
121
- }
122
-
123
- params(dict: Record<string, unknown>) {
124
- Object.assign(this.#request.params, dict);
125
- return this;
126
- }
127
-
128
- data<T>(data: T) {
129
- this.#request.data = data;
130
- return this;
131
- }
132
-
133
- header(
134
- name: string,
135
- value:
136
- | string
137
- | number
138
- | boolean
139
- | null
140
- | (string | number | boolean)[]
141
- | (() => string | number | boolean | null)
142
- | (() => Promise<string | number | boolean | null>),
143
- onArray: (value: (string | number | boolean)[]) => string = (value) =>
144
- value.join(",")
145
- ) {
146
- if (typeof value === "function") {
147
- if (this.#request.headers[name]) {
148
- delete this.#request.headers[name];
149
- }
150
- this.#dynamicHeaders.set(name, value);
151
- return this;
152
- }
153
- if (this.#dynamicHeaders.has(name)) {
154
- this.#dynamicHeaders.delete(name);
155
- }
156
- const val = Array.isArray(value) ? onArray(value) : String(value);
157
- this.#request.headers[name] = val;
158
- return this;
159
- }
160
-
161
- headers(dict: Record<string, string>) {
162
- Object.assign(this.#request.headers, dict);
163
- return this;
164
- }
165
-
166
- get() {
167
- return this.#request;
168
- }
169
-
170
- method(method: HTTPMethod) {
171
- this.#request.method = method;
172
- return this;
173
- }
174
-
175
- #setDynamicHeaders(
176
- headers: Map<
177
- string,
178
- | (() => string | number | boolean | null)
179
- | (() => Promise<string | number | boolean | null>)
180
- >
181
- ) {
182
- this.#dynamicHeaders = new Map(headers);
183
- return this;
184
- }
185
-
186
- derive(): HTTPRequestBuilder<T> {
187
- const request = HTTPRequest.derive(this.#request);
188
- return new HTTPRequestBuilder(request).#setDynamicHeaders(
189
- this.#dynamicHeaders
190
- ) as HTTPRequestBuilder<T>;
191
- }
192
-
193
- build(): HTTPRequest<T> {
194
- return this.#request as HTTPRequest<T>;
195
- }
196
-
197
- async buildAsync(): Promise<HTTPRequest<T>> {
198
- await this.resolveDynamicHeaders();
199
- return this.#request as HTTPRequest<T>;
200
- }
201
- }