@smuzi/http-client 0.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 smuzi-ts
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # @smuzi/http-client
2
+
3
+ TODO
4
+ ---
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ npm install @smuzi/http-client
10
+ ```
11
+
12
+ ---
13
+ > See more examples in the [tests folder](./tests).
14
+
15
+ ## Example
16
+
17
+ ```ts
18
+ ```
@@ -0,0 +1,32 @@
1
+ import { Option, Result, HttpMethod, HttpResponse, RequestHttpHeaders, StdError } from "@smuzi/std";
2
+ import { Connector } from "./connection.js";
3
+ export type BaseRequestConfig = {
4
+ method: HttpMethod;
5
+ headers: RequestHttpHeaders;
6
+ query: Record<string, string | number | boolean>;
7
+ body: Option;
8
+ rawResponse: boolean;
9
+ };
10
+ export type GetRequestConfig = {
11
+ headers?: RequestHttpHeaders;
12
+ query?: Record<string, string | number | boolean>;
13
+ rawResponse?: boolean;
14
+ };
15
+ export type PostRequestConfig = {
16
+ headers?: RequestHttpHeaders;
17
+ query?: Record<string, string | number | boolean>;
18
+ body?: Option;
19
+ rawResponse?: boolean;
20
+ };
21
+ export type HttpClientConfig = {
22
+ baseUrl?: string;
23
+ baseHeaders?: Record<string, string>;
24
+ connector?: Option<Connector>;
25
+ };
26
+ export type HttpClient = {
27
+ get<Body = unknown, E = unknown>(url: any, config?: GetRequestConfig): Promise<Result<HttpResponse<Body>, HttpResponse<E> | StdError>>;
28
+ post<Body = unknown, E = unknown>(url: any, config?: PostRequestConfig): Promise<Result<HttpResponse<Body>, HttpResponse<E> | StdError>>;
29
+ put<Body = unknown, E = unknown>(url: any, config?: PostRequestConfig): Promise<Result<HttpResponse<Body>, HttpResponse<E> | StdError>>;
30
+ delete<Body = unknown, E = unknown>(url: any, config?: GetRequestConfig): Promise<Result<HttpResponse<Body>, HttpResponse<E> | StdError>>;
31
+ };
32
+ export declare function buildHttpClient({ baseUrl, baseHeaders, connector }: HttpClientConfig): HttpClient;
package/build/base.js ADDED
@@ -0,0 +1,100 @@
1
+ import { Err, None, Ok, OptionFromNullable, HttpMethod, HttpResponse, RequestHttpHeaders, ResponseHttpHeaders, isObject, asNull, StdJson, StdFormData } from "@smuzi/std";
2
+ function buildUrl(baseUrl = "", url, query) {
3
+ const fullUrl = baseUrl + url;
4
+ let qs = "";
5
+ for (const [key, value] of Object.entries(query)) {
6
+ qs += key + "=" + value + "&";
7
+ }
8
+ return qs ? `${fullUrl}?${qs.slice(0, -1)}` : fullUrl;
9
+ }
10
+ export function buildHttpClient({ baseUrl = "", baseHeaders = {}, connector = None() }) {
11
+ async function request(url, config) {
12
+ await connector.asyncSomeThen(async (connector_fn) => {
13
+ config = (await connector_fn(config)).unwrap();
14
+ });
15
+ const finalUrl = buildUrl(baseUrl, url, config.query);
16
+ const requestInit = {
17
+ method: config.method,
18
+ };
19
+ if (config.method !== HttpMethod.GET && config.method !== HttpMethod.DELETE) {
20
+ let bodyParsed;
21
+ bodyParsed = config.body.mapSome((bodyValue) => {
22
+ //TODO: Handle bodyValue instanceof FormData
23
+ if (bodyValue instanceof StdFormData) {
24
+ return {
25
+ body: bodyValue.toString().unwrap(),
26
+ contentType: "application/x-www-form-urlencoded"
27
+ };
28
+ }
29
+ if (isObject(bodyValue) && !(bodyValue instanceof FormData)) {
30
+ return {
31
+ body: StdJson.toString(bodyValue).unwrap(),
32
+ contentType: "application/json; charset=utf-8"
33
+ };
34
+ }
35
+ //TODO SAFE: "body as string", need to added any chekers
36
+ return {
37
+ body: asNull(bodyValue) ? "" : bodyValue,
38
+ contentType: "text/plain; charset=utf-8"
39
+ };
40
+ });
41
+ if (!bodyParsed.isNone()) {
42
+ requestInit.body = bodyParsed.unwrapByKey("body");
43
+ config.headers.set("content-type", bodyParsed.unwrapByKey("contentType"));
44
+ }
45
+ }
46
+ //TODO SAFE: researching problem with types and make this more type safe without "as any"
47
+ requestInit.headers = config.headers.unsafeSource();
48
+ try {
49
+ const response = await fetch(finalUrl, requestInit);
50
+ const responseContentType = response.headers.get("content-type") ?? "";
51
+ try {
52
+ const text = await response.text();
53
+ const body = OptionFromNullable(text)
54
+ .mapSome((rawData) => {
55
+ if (config.rawResponse) {
56
+ return rawData;
57
+ }
58
+ if (!responseContentType.includes("application/json")) {
59
+ return rawData;
60
+ }
61
+ return StdJson.fromString(rawData).unwrap();
62
+ }).flat();
63
+ if (!response.ok) {
64
+ return Err(new HttpResponse({
65
+ status: response.status,
66
+ statusText: response.statusText,
67
+ body: body,
68
+ headers: ResponseHttpHeaders.fromHeaders(response.headers)
69
+ }));
70
+ }
71
+ return Ok(new HttpResponse({
72
+ status: response.status,
73
+ statusText: response.statusText,
74
+ body: body,
75
+ headers: ResponseHttpHeaders.fromHeaders(response.headers)
76
+ }));
77
+ }
78
+ catch (e) {
79
+ return Err(e);
80
+ }
81
+ }
82
+ catch (err) {
83
+ return Err(err);
84
+ }
85
+ }
86
+ return {
87
+ get(url, { query = {}, headers = new RequestHttpHeaders, rawResponse = false } = {}) {
88
+ return request(url, { query, headers, rawResponse, method: HttpMethod.GET, body: None() });
89
+ },
90
+ post(url, { query = {}, headers = new RequestHttpHeaders, body = None(), rawResponse = false } = {}) {
91
+ return request(url, { query, headers, rawResponse, method: HttpMethod.POST, body });
92
+ },
93
+ put(url, { query = {}, headers = new RequestHttpHeaders, body = None(), rawResponse = false } = {}) {
94
+ return request(url, { query, headers, rawResponse, method: HttpMethod.PUT, body });
95
+ },
96
+ delete(url, { query = {}, headers = new RequestHttpHeaders, rawResponse = false } = {}) {
97
+ return request(url, { query, headers, rawResponse, method: HttpMethod.GET, body: None() });
98
+ },
99
+ };
100
+ }
@@ -0,0 +1,3 @@
1
+ import { Result, StdError } from "@smuzi/std";
2
+ import { BaseRequestConfig } from "./index.js";
3
+ export type Connector = (request: BaseRequestConfig) => Promise<Result<BaseRequestConfig, StdError>>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export * from "./base.js";
2
+ export * from "./connection.js";
package/build/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./base.js";
2
+ export * from "./connection.js";
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@smuzi/http-client",
3
+ "version": "0.0.1",
4
+ "description": "HTTP client for JavaScript and TypeScript",
5
+ "type": "module",
6
+ "types": "./build/index.d.ts",
7
+ "keywords": [
8
+ "http",
9
+ "api",
10
+ "rest",
11
+ "json",
12
+ "request"
13
+ ],
14
+ "author": "Denis Ratushniak <dinisimys2018@gmail.com>",
15
+ "license": "ISC",
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "files": [
20
+ "./src",
21
+ "./build",
22
+ "./tests"
23
+ ],
24
+ "exports": {
25
+ "./package.json": "./package.json",
26
+ ".": "./src/index.ts",
27
+ "./*": "./src/*.ts"
28
+ },
29
+ "imports": {
30
+ "#lib/*": "./src/*"
31
+ },
32
+ "dependencies": {
33
+ "@smuzi/std": "0.2.4"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^22.15.21",
37
+ "tsx": "^4.20.6",
38
+ "typescript": "^7.0.2",
39
+ "@smuzi/faker": "0.0.2",
40
+ "@smuzi/tests": "0.0.1",
41
+ "@smuzi/schema": "0.0.2",
42
+ "@smuzi/http-server": "0.0.2"
43
+ },
44
+ "scripts": {
45
+ "test": "tsx --env-file=./tests/.env tests/index.ts",
46
+ "build": "tsc --project tsconfig.build.json"
47
+ }
48
+ }
package/src/base.ts ADDED
@@ -0,0 +1,186 @@
1
+ import {
2
+ dump,
3
+ Err,
4
+ None,
5
+ Ok,
6
+ Option,
7
+ OptionFromNullable,
8
+ Result,
9
+ HttpMethod,
10
+ HttpResponse,
11
+ RequestHttpHeaders,
12
+ ResponseHttpHeaders,
13
+ isObject,
14
+ isNull,
15
+ asNull,
16
+ StdError,
17
+ isNone,
18
+ asString,
19
+ StdJson,
20
+ querystring,
21
+ StdFormData
22
+ } from "@smuzi/std";
23
+ import { Connector } from "./connection.js";
24
+
25
+ export type BaseRequestConfig = {
26
+ method: HttpMethod;
27
+ headers: RequestHttpHeaders;
28
+ query: Record<string, string | number | boolean>;
29
+ body: Option;
30
+ rawResponse: boolean
31
+ };
32
+
33
+ export type GetRequestConfig = {
34
+ headers?: RequestHttpHeaders;
35
+ query?: Record<string, string | number | boolean>;
36
+ rawResponse?: boolean
37
+ };
38
+
39
+ export type PostRequestConfig = {
40
+ headers?: RequestHttpHeaders;
41
+ query?: Record<string, string | number | boolean>;
42
+ body?: Option,
43
+ rawResponse?: boolean
44
+ };
45
+
46
+
47
+ function buildUrl(baseUrl: string = "", url: string, query: Record<string, string | number | boolean>) {
48
+ const fullUrl = baseUrl + url;
49
+ let qs = "";
50
+
51
+ for (const [key, value] of Object.entries(query)) {
52
+ qs += key + "=" + value + "&";
53
+ }
54
+
55
+ return qs ? `${fullUrl}?${qs.slice(0, -1)}` : fullUrl;
56
+ }
57
+
58
+ export type HttpClientConfig = {
59
+ baseUrl?: string
60
+ baseHeaders?: Record<string, string>,
61
+ connector?: Option<Connector>,
62
+ }
63
+
64
+ export type HttpClient = {
65
+ get<Body = unknown, E = unknown>(url, config?: GetRequestConfig): Promise<Result<HttpResponse<Body>, HttpResponse<E> | StdError>>,
66
+ post<Body = unknown, E = unknown>(url, config?: PostRequestConfig): Promise<Result<HttpResponse<Body>, HttpResponse<E> | StdError>>
67
+ put<Body = unknown, E = unknown>(url, config?: PostRequestConfig): Promise<Result<HttpResponse<Body>, HttpResponse<E> | StdError>>
68
+ delete<Body = unknown, E = unknown>(url, config?: GetRequestConfig): Promise<Result<HttpResponse<Body>, HttpResponse<E> | StdError>>,
69
+ };
70
+
71
+ export function buildHttpClient({ baseUrl = "", baseHeaders = {}, connector = None() }: HttpClientConfig): HttpClient {
72
+
73
+ async function request<B = unknown, E = unknown>(
74
+ url: string, config: BaseRequestConfig
75
+ ): Promise<Result<HttpResponse<B>, HttpResponse<E> | StdError>> {
76
+ await connector.asyncSomeThen(async (connector_fn) => {
77
+ config = (await connector_fn(config)).unwrap();
78
+ })
79
+
80
+ const finalUrl = buildUrl(baseUrl, url, config.query);
81
+
82
+ const requestInit: RequestInit = {
83
+ method: config.method,
84
+ };
85
+
86
+ if (config.method !== HttpMethod.GET && config.method !== HttpMethod.DELETE) {
87
+ let bodyParsed: Option<{
88
+ body: string,
89
+ contentType: string
90
+ }>;
91
+
92
+ bodyParsed = config.body.mapSome((bodyValue) => {
93
+ //TODO: Handle bodyValue instanceof FormData
94
+
95
+ if (bodyValue instanceof StdFormData) {
96
+ return {
97
+ body: bodyValue.toString().unwrap(),
98
+ contentType: "application/x-www-form-urlencoded"
99
+ }
100
+ }
101
+
102
+ if (isObject(bodyValue) && !(bodyValue instanceof FormData)) {
103
+ return {
104
+ body: StdJson.toString(bodyValue).unwrap(),
105
+ contentType: "application/json; charset=utf-8"
106
+ }
107
+ }
108
+
109
+ //TODO SAFE: "body as string", need to added any chekers
110
+ return {
111
+ body: asNull(bodyValue) ? "" : bodyValue as string,
112
+ contentType: "text/plain; charset=utf-8"
113
+ }
114
+ });
115
+
116
+
117
+ if (! bodyParsed.isNone()) {
118
+ requestInit.body = bodyParsed.unwrapByKey("body");
119
+ config.headers.set("content-type", bodyParsed.unwrapByKey("contentType"));
120
+ }
121
+
122
+ }
123
+
124
+ //TODO SAFE: researching problem with types and make this more type safe without "as any"
125
+ requestInit.headers = config.headers.unsafeSource() as any;
126
+
127
+ try {
128
+ const response = await fetch(finalUrl, requestInit);
129
+ const responseContentType = response.headers.get("content-type") ?? "";
130
+
131
+ try {
132
+ const text = await response.text();
133
+
134
+ const body = OptionFromNullable(text)
135
+ .mapSome((rawData) => {
136
+ if (config.rawResponse) {
137
+ return rawData;
138
+ }
139
+
140
+ if (! responseContentType.includes("application/json")) {
141
+ return rawData;
142
+ }
143
+
144
+ return StdJson.fromString(rawData).unwrap();
145
+ }).flat();
146
+
147
+ if (!response.ok) {
148
+ return Err(new HttpResponse({
149
+ status: response.status,
150
+ statusText: response.statusText,
151
+ body: body as Option<E>,
152
+ headers: ResponseHttpHeaders.fromHeaders(response.headers)
153
+ }))
154
+ }
155
+
156
+ return Ok(new HttpResponse({
157
+ status: response.status,
158
+ statusText: response.statusText,
159
+ body: body as Option<B>,
160
+ headers: ResponseHttpHeaders.fromHeaders(response.headers)
161
+ }));
162
+
163
+ } catch (e) {
164
+ return Err(e);
165
+ }
166
+
167
+ } catch (err) {
168
+ return Err(err);
169
+ }
170
+ }
171
+
172
+ return {
173
+ get(url, { query = {}, headers = new RequestHttpHeaders, rawResponse = false }: GetRequestConfig = {}) {
174
+ return request(url, { query, headers, rawResponse, method: HttpMethod.GET, body: None() });
175
+ },
176
+ post(url, { query = {}, headers = new RequestHttpHeaders, body = None(), rawResponse = false }: PostRequestConfig = {}) {
177
+ return request(url, { query, headers, rawResponse, method: HttpMethod.POST, body });
178
+ },
179
+ put(url, { query = {}, headers = new RequestHttpHeaders, body = None(), rawResponse = false }: PostRequestConfig = {}) {
180
+ return request(url, { query, headers, rawResponse, method: HttpMethod.PUT, body });
181
+ },
182
+ delete(url, { query = {}, headers = new RequestHttpHeaders, rawResponse = false }: GetRequestConfig = {}) {
183
+ return request(url, { query, headers, rawResponse, method: HttpMethod.GET, body: None() });
184
+ },
185
+ }
186
+ }
@@ -0,0 +1,4 @@
1
+ import { Result, StdError } from "@smuzi/std";
2
+ import { BaseRequestConfig } from "./index.js";
3
+
4
+ export type Connector = (request: BaseRequestConfig) => Promise<Result<BaseRequestConfig,StdError>>
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./base.js"
2
+ export * from "./connection.js"
package/tests/.env ADDED
@@ -0,0 +1,3 @@
1
+ APP_HOST=localhost
2
+ APP_PORT=8445
3
+ API_KEY=123
@@ -0,0 +1,20 @@
1
+ import { env, Some, http } from "@smuzi/std";
2
+ import { buildHttp1ServerConfig } from "@smuzi/http-server";
3
+ import { buildHttpClient } from "#lib/index.js";
4
+ import router from "./router.js";
5
+
6
+
7
+ export const serverConfig = buildHttp1ServerConfig({
8
+ host: env("APP_HOST", Some("localhost")),
9
+ port: parseInt(env("APP_PORT", Some('81'))),
10
+ router,
11
+ });
12
+
13
+ export const apiConfig = {
14
+ key: env("API_KEY")
15
+ }
16
+
17
+ export const httpClient = buildHttpClient({
18
+ baseUrl: http.buildUrl(serverConfig.protocol, serverConfig.host, Some(serverConfig.port)),
19
+ });
20
+
@@ -0,0 +1,77 @@
1
+ import { dump, HttpResponse, Option, RequestHttpHeaders } from "@smuzi/std";
2
+ import { CreateHttp1Router } from "@smuzi/http-server";
3
+ import { faker } from "@smuzi/faker";
4
+ import { apiConfig } from "./config.js";
5
+
6
+
7
+ const router = CreateHttp1Router({ path: '' });
8
+
9
+ const usersRouter = CreateHttp1Router({ path: 'users/' });
10
+ usersRouter.get("list", () => {
11
+ return faker.repeat.asArray(5, () => ({
12
+ id: faker.integer(),
13
+ name: faker.string(),
14
+ email: faker.string(),
15
+ }))
16
+ })
17
+
18
+ usersRouter.get("auth", (context) => {
19
+ const token = context.request.query().get("token").someOr("");
20
+
21
+ if (token != apiConfig.key) {
22
+ context.response.writeHead(401, "Unauthorized");
23
+ context.response.end();
24
+ return;
25
+ }
26
+
27
+ context.response.writeHead(200, "Authorized");
28
+ context.response.end();
29
+ })
30
+
31
+ usersRouter.get("authHeader", (context) => {
32
+ const token = context.request.headers.getOther("x-token").someOr("");
33
+
34
+ if (token != apiConfig.key) {
35
+ context.response.writeHead(401, "Unauthorized");
36
+ context.response.end();
37
+ return;
38
+ }
39
+
40
+ context.response.writeHead(200, "Authorized");
41
+ context.response.end();
42
+ })
43
+
44
+ router.get("echoQuery", (context) => {
45
+ const resp = {};
46
+ for(const [key, val] of context.request.query()) {
47
+ resp[key] = val.someOr("");
48
+ }
49
+ return resp;
50
+ })
51
+
52
+ router.get("echoHeaders", (context) => {
53
+ const resp = new HttpResponse();
54
+ for(const [key, val] of context.request.headers) {
55
+ if (key.endsWith("custom")) {
56
+ resp.headers.setOther(key, val.someOr(""));
57
+ }
58
+ }
59
+ return resp;
60
+ })
61
+
62
+ router.post("echoBodyString", async (context) => {
63
+ const body = (await context.request.json());
64
+
65
+ return body.match({
66
+ Err(err) {
67
+ return new HttpResponse({status: 400, statusText: err.message});
68
+ },
69
+ Ok(json) {
70
+ return json.unwrap() as any;
71
+ }
72
+ })
73
+ })
74
+
75
+ router.group(usersRouter);
76
+
77
+ export default router;
@@ -0,0 +1,105 @@
1
+ import {assert, it, okMsg} from "@smuzi/tests";
2
+ import {Some, RequestHttpHeaders, StdError, StdList, StdRecord} from "@smuzi/std";
3
+ import {apiConfig, httpClient} from "./config/config.js";
4
+ import {faker} from "@smuzi/faker";
5
+ import {http1TestRunner} from "./index.js";
6
+
7
+ http1TestRunner.describe("http-client-http1-GET-", [
8
+ it("without options", async () => {
9
+ type User = StdRecord<{ email: string, name: string }>;
10
+ type UsersList = StdList<User>
11
+
12
+ const response = await httpClient.get<UsersList>('/users/list');
13
+
14
+ const body = response.unwrap().body.unwrap();
15
+ const user = body.get(0).unwrap();
16
+
17
+ assert.isString(user.get("email").unwrap())
18
+ assert.isString(user.get("name").unwrap())
19
+
20
+ }),
21
+
22
+ it("not found", async () => {
23
+ const response = (await httpClient.get('/users/list/notFound'));
24
+ assert.result.equalErr(response);
25
+
26
+ response.runThenErr((err) => {
27
+ if (err instanceof StdError) {
28
+ assert.fail(err);
29
+ }
30
+ assert.equal(err.status, 404);
31
+ assert.equal(err.statusText, "Not Found");
32
+ })
33
+ }),
34
+
35
+ it(okMsg("unauthorized"), async () => {
36
+ const response = await httpClient.get('/users/auth');
37
+
38
+ assert.result.equalErr(response);
39
+
40
+ response.runThenErr((err) => {
41
+ if (err instanceof StdError) {
42
+ assert.fail(err);
43
+ }
44
+ assert.equal(err.status, 401);
45
+ assert.equal(err.statusText, "Unauthorized");
46
+ })
47
+ }),
48
+
49
+ it(okMsg("success authorized"), async () => {
50
+
51
+ const response = await httpClient.get('/users/auth', {query: {token: apiConfig.key}});
52
+
53
+ assert.result.equalOk(response);
54
+
55
+ response.runThenOk((resp) => {
56
+ assert.equal(resp.status, 200);
57
+ assert.equal(resp.statusText, "Authorized");
58
+ })
59
+ }),
60
+ it("echo query", async () => {
61
+
62
+ const query = {
63
+ a: faker.string(),
64
+ b: faker.string(),
65
+ c: faker.string(),
66
+ };
67
+
68
+ type ResponseJson = StdRecord<typeof query>
69
+
70
+ const response = await httpClient.get<ResponseJson>('/echoQuery', {query});
71
+
72
+ assert.result.equalOk(response);
73
+
74
+ response.runThenOk((resp) => {
75
+ assert.equal(resp.status, 200);
76
+ assert.equal(resp.statusText, "OK");
77
+ const body = resp.body.unwrap();
78
+ assert.equal(body.get("a").unwrap(), query.a);
79
+ assert.equal(body.get("b").unwrap(), query.b);
80
+ assert.equal(body.get("c").unwrap(), query.c);
81
+ })
82
+ }),
83
+
84
+ it(okMsg("echo custom headers"), async () => {
85
+
86
+ const headers = new RequestHttpHeaders();
87
+
88
+ headers.setOther("x-custom", "xxx");
89
+ headers.setOther("y-custom", "yyy");
90
+ headers.setOther("z-custom", "zzz");
91
+
92
+ const response = await httpClient.get('/echoHeaders', {headers});
93
+
94
+ assert.result.equalOk(response);
95
+
96
+ response.runThenOk((resp) => {
97
+ assert.equal(resp.status, 200);
98
+ assert.equal(resp.statusText, "OK");
99
+ assert.deepEqual(resp.headers.getOther("x-custom"), Some("xxx"));
100
+ assert.deepEqual(resp.headers.getOther("y-custom"), Some("yyy"));
101
+ assert.deepEqual(resp.headers.getOther("z-custom"), Some("zzz"));
102
+ })
103
+ }),
104
+ ]
105
+ )
@@ -0,0 +1,23 @@
1
+ import {TestRunner} from "@smuzi/tests";
2
+ import { http1ServerRun, StdHttp1Server } from "@smuzi/http-server";
3
+ import {Option, Some} from "@smuzi/std";
4
+ import { serverConfig } from "./config/config.js";
5
+
6
+ type GlobalSetup = Option<{
7
+ server: StdHttp1Server
8
+ }>
9
+
10
+ export const http1TestRunner = new TestRunner<GlobalSetup>({
11
+ folder: './tests/http1',
12
+ beforeGlobal: Some(async () => {
13
+ return Some({
14
+ server: (await http1ServerRun(serverConfig)).unwrap()
15
+ });
16
+ }
17
+ ),
18
+ afterGlobal: Some(async (globalSetup) => {
19
+ await globalSetup.unwrap().server.close();
20
+ }),
21
+ });
22
+
23
+ export default async () => http1TestRunner.run();
@@ -0,0 +1,36 @@
1
+ import { it, assert } from "@smuzi/tests";
2
+ import { httpClient } from "./config/config.js";
3
+ import {dump, Some, StdRecord} from "@smuzi/std";
4
+ import { faker } from "@smuzi/faker";
5
+ import {schema} from "@smuzi/schema";
6
+ import {http1TestRunner} from "./index.js";
7
+
8
+ http1TestRunner.describe("http-client-http1-POST-", [
9
+ it("echo json", async () => {
10
+ const userSchema = schema.record({
11
+ name: schema.string(),
12
+ age: schema.number(),
13
+ posts: schema.list(schema.record({
14
+ id: schema.number(),
15
+ subject: schema.string(),
16
+ }))
17
+ })
18
+
19
+ const createUser = faker.schema.make(userSchema);
20
+
21
+ const response = await httpClient.post<typeof createUser>('/echoBodyString', {
22
+ body: Some(createUser)
23
+ });
24
+
25
+
26
+ const responseBody = response
27
+ .unwrap()
28
+ .body
29
+ .unwrap();
30
+
31
+
32
+ assert.result.equalOk(userSchema.validate(responseBody))
33
+
34
+ }),
35
+ ]
36
+ )
package/tests/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import http1 from "./http1/index.js"
2
+
3
+ await http1();