@smuzi/http-client 0.0.3 → 0.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smuzi/http-client",
3
- "version": "0.0.3",
3
+ "version": "0.0.6",
4
4
  "description": "HTTP client for JavaScript and TypeScript",
5
5
  "type": "module",
6
6
  "types": "./build/index.d.ts",
@@ -17,9 +17,8 @@
17
17
  "access": "public"
18
18
  },
19
19
  "files": [
20
- "./src",
21
- "./build",
22
- "./tests"
20
+ "build/**/*.js",
21
+ "build/**/*.d.ts"
23
22
  ],
24
23
  "exports": {
25
24
  "./package.json": "./package.json",
@@ -36,19 +35,20 @@
36
35
  "#lib/*": "./src/*"
37
36
  },
38
37
  "dependencies": {
39
- "@smuzi/std": "0.2.6"
38
+ "@smuzi/std": "0.2.9"
40
39
  },
41
40
  "devDependencies": {
42
41
  "@types/node": "^22.15.21",
43
42
  "tsx": "^4.20.6",
44
43
  "typescript": "^7.0.2",
45
- "@smuzi/faker": "0.0.5",
46
- "@smuzi/tests": "0.0.3",
47
- "@smuzi/schema": "0.0.5",
48
- "@smuzi/http-server": "0.0.5"
44
+ "@smuzi/faker": "0.0.7",
45
+ "@smuzi/tests": "0.0.6",
46
+ "@smuzi/schema": "0.0.8",
47
+ "@smuzi/http-server": "0.0.7"
49
48
  },
50
49
  "scripts": {
51
50
  "test": "tsx --env-file=./tests/.env tests/index.ts",
52
51
  "build": "tsc --project tsconfig.build.json"
53
- }
52
+ },
53
+ "main": "./build/index.js"
54
54
  }
package/src/base.ts DELETED
@@ -1,186 +0,0 @@
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
- }
package/src/connection.ts DELETED
@@ -1,4 +0,0 @@
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 DELETED
@@ -1,2 +0,0 @@
1
- export * from "./base.js"
2
- export * from "./connection.js"
package/tests/.env DELETED
@@ -1,3 +0,0 @@
1
- APP_HOST=localhost
2
- APP_PORT=8445
3
- API_KEY=123
@@ -1,20 +0,0 @@
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
-
@@ -1,77 +0,0 @@
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;
@@ -1,105 +0,0 @@
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
- )
@@ -1,23 +0,0 @@
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();
@@ -1,35 +0,0 @@
1
- import { it, assert } from "@smuzi/tests";
2
- import { httpClient } from "./config/config.js";
3
- import {dump, Some, StdRecord} from "@smuzi/std";
4
- import {schema} from "@smuzi/schema";
5
- import {http1TestRunner} from "./index.js";
6
-
7
- http1TestRunner.describe("http-client-http1-POST-", [
8
- it("echo json", async () => {
9
- const userSchema = schema.record({
10
- name: schema.string(),
11
- age: schema.number(),
12
- posts: schema.list(schema.record({
13
- id: schema.number(),
14
- subject: schema.string(),
15
- }))
16
- })
17
-
18
- const createUser = userSchema.fake();
19
-
20
- const response = await httpClient.post<typeof createUser>('/echoBodyString', {
21
- body: Some(createUser)
22
- });
23
-
24
-
25
- const responseBody = response
26
- .unwrap()
27
- .body
28
- .unwrap();
29
-
30
-
31
- assert.result.equalOk(userSchema.validate(responseBody))
32
-
33
- }),
34
- ]
35
- )
package/tests/index.ts DELETED
@@ -1,3 +0,0 @@
1
- import http1 from "./http1/index.js"
2
-
3
- await http1();