Skip to content

Commit

Permalink
feat: readme changes
Browse files Browse the repository at this point in the history
  • Loading branch information
Bekacru committed May 3, 2024
1 parent 63fbd45 commit 6fc2d3a
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 184 deletions.
104 changes: 65 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
# Better Fetch

A fetch wrapper for typescript that returns data and error object. Works on the browser, node (version 18+), workers, deno and bun. Some of the APIs are inspired by [ofetch](https://github.com/unjs/ofetch).
A fetch wrapper for typescript that returns data and error object, supports defined route schemas, plugins and more. Works on the browser, node (version 18+), workers, deno and bun.

## Installation

```bash
pnpm install @better-tools/fetch
```

## Usage
## Basic Usage

```typescript
import fetch from "@better-tools/fetch"
import betterFetch from "@better-tools/fetch"

const { data, error } = await fetch<{
const { data, error } = await betterFetch<{
userId: number;
id: number;
title: string;
Expand Down Expand Up @@ -47,12 +47,52 @@ const { data, error } = await $fetch<{
}>("/todos/1");
```

### ♯ Typed Fetch

Better fetch allows you to define schema that will be used to infer request body, query parameters, response data and error types.

```typescript
import { createFetch } from "@better-tools/fetch";
import { T, FetchSchema } from "@better-tools/fetch/typed";

const routes = {
"/": {
output: T.Object({
message: T.String(),
}),
},
"/signin": {
input: T.Object({
username: T.String(),
password: T.String(),
}),
output: T.Object({
token: T.String(),
}),
},
"/signup": {
input: T.Object({
username: T.String(),
password: T.String(),
optional: T.Optional(T.String()),
}),
output: T.Object({
message: T.String(),
}),
},
} satisfies FetchSchema;

const $fetch = createFetch<typeof routes>()
```


You can also pass default response and error types. Which will be used if you don't pass the types in the fetch call.

```typescript
import { createFetch } from "@better-tools/fetch";
import { DefaultSchema } from "@better-tools/fetch/typed";

const $fetch = createFetch<{
const $fetch = createFetch<DefaultSchema,{
userId: number;
id: number;
title: string;
Expand Down Expand Up @@ -91,18 +131,26 @@ const { useFetch, useMutate } = createReactFetch({
});

function App() {
const { data, error } = useFetch<{
type Todo = {
userId: number;
id: number;
title: string;
completed: boolean;
}>("/todos/1");
};
const { data, error, isPending } = useFetch<Todo>("/todos/1");
if (error) {
// handle the error
}
if (data) {
// handle the data
}
const { mutate, isPending } = useMutate<Todo>("/todos")
await mutate({
userId: 1,
id: 1,
title: "delectus aut autem",
completed: false
})
}
```

Expand All @@ -127,45 +175,23 @@ function App() {
```


### ♯ Typed Fetch
Better fetch allows you to define schema that will be used to infer request body, query parameters, response data and error types.
### ♯ Plugins

Plugins are functions that can be used to modify the request, response, error and other parts of the request lifecycle.

Example:
```typescript
import { createFetch } from "@better-tools/fetch";
import { T, FetchSchema } from "@better-tools/fetch/typed";
import { csrfProtection } from "./plugins/csrfProtection"

const routes = {
"/": {
output: T.Object({
message: T.String(),
}),
},
"/signin": {
input: T.Object({
username: T.String(),
password: T.String(),
}),
output: T.Object({
token: T.String(),
}),
},
"/signup": {
input: T.Object({
username: T.String(),
password: T.String(),
optional: T.Optional(T.String()),
}),
output: T.Object({
message: T.String(),
}),
},
} satisfies FetchSchema;

const $fetch = createFetch<typeof routes>()
const $fetch = createFetch({
baseUrl: "https://jsonplaceholder.typicode.com",
retry: 2,
plugins: [csrfProtection()]
});
```



### ♯ Parsing the response

Better fetch will smartly parse JSON using JSON.parse and if it fails it will return the response as text.
Expand Down
43 changes: 28 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ import {
isJSONSerializable,
jsonParse,
} from "./utils";
import { FetchSchema, Static, T, TNever } from "./typed";
import { TObject, Type } from "@sinclair/typebox";
// import { Static, TNever, TObject, Type } from "@sinclair/typebox";
import { FetchSchema, Static } from "./typed";
import { TNever, TObject } from "@sinclair/typebox";

interface RequestContext {
request: Request;
Expand All @@ -22,7 +21,7 @@ interface ResponseContext {
response: Response;
}

export interface BetterFetchOptions extends Omit<RequestInit, "body"> {
export type BetterFetchOptions<B extends Record<string, any> = any> = {
/**
* a base url that will be prepended to the url
*/
Expand Down Expand Up @@ -86,9 +85,9 @@ export interface BetterFetchOptions extends Omit<RequestInit, "body"> {
*/
duplex?: "full" | "half";
/**
* Query parameters
* HTTP method
*/
query?: Record<string, string | number | boolean | undefined>;
method?: PayloadMethod | NonPayloadMethod;
/**
* Custom fetch implementation
*/
Expand All @@ -97,7 +96,7 @@ export interface BetterFetchOptions extends Omit<RequestInit, "body"> {
* Plugins
*/
plugins?: Plugin[];
}
} & Omit<RequestInit, "body">;

/**
* A plugin that can be used to modify the url and options.
Expand All @@ -116,10 +115,15 @@ export interface CreateFetchOption extends BetterFetchOptions {}
export type PayloadMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
export type NonPayloadMethod = "GET" | "HEAD" | "OPTIONS";

export type FetchOption<T extends Record<string, unknown> = any> =
T extends Record<string, any>
? BetterFetchOptions & { body: T }
: BetterFetchOptions & { body?: T };
export type FetchOption<
T extends Record<string, unknown> = any,
Q extends Record<string, unknown> = any,
> = InferBody<T> & InferQuery<Q> & BetterFetchOptions;

type InferBody<T> = T extends Record<string, any> ? { body: T } : { body?: T };
type InferQuery<Q> = Q extends Record<string, any>
? { query: Q }
: { query?: Q };

export type BetterFetchResponse<
T,
Expand Down Expand Up @@ -294,10 +298,10 @@ export const betterFetch: BetterFetch = async (url, options) => {
export const createFetch = <
Routes extends FetchSchema = any,
R = unknown,
F = unknown,
E = unknown,
>(
config?: CreateFetchOption,
): BetterFetch<Routes, R, F> => {
): BetterFetch<Routes, R, E> => {
const $fetch: BetterFetch = async (url, options) => {
return await betterFetch(url, {
...config,
Expand All @@ -313,7 +317,7 @@ betterFetch.native = fetch;
export interface BetterFetch<
Routes extends FetchSchema = {
[key in string]: {
output: any;
output: TNever;
};
},
BaseT = any,
Expand All @@ -322,7 +326,16 @@ export interface BetterFetch<
<T = BaseT, E = BaseE, K extends keyof Routes = keyof Routes>(
url: K | URL | Omit<string, keyof Routes>,
...options: Routes[K]["input"] extends TObject
? [FetchOption<Static<Routes[K]["input"]>>]
? [
FetchOption<
Static<Routes[K]["input"]>,
Routes[K]["query"] extends TObject
? Static<Routes[K]["query"]>
: any
>,
]
: Routes[K]["query"] extends TObject
? [FetchOption<any, Static<Routes[K]["query"]>>]
: [FetchOption?]
): Promise<
BetterFetchResponse<
Expand Down
52 changes: 0 additions & 52 deletions src/plugins/csrf-protection.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/plugins/index.ts

This file was deleted.

12 changes: 8 additions & 4 deletions src/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,11 @@ export const createReactFetch = <
} as any;

const [res, setRes] =
useState<BetterFetchResponse<Static<NonNullable<Routes[K]["output"]>>>>(
initial,
);
useState<
Routes[K]["output"] extends TObject
? BetterFetchResponse<Static<Routes[K]["output"]>>
: BetterFetchResponse<R>
>(initial);
const [isLoading, setIsLoading] = useState(false);
const fetchData = async () => {
setIsLoading(true);
Expand Down Expand Up @@ -134,7 +136,9 @@ export const createReactFetch = <
};
}, []);
return {
data: res?.data,
data: res?.data as Routes[K]["output"] extends TObject
? Static<Routes[K]["output"]>
: R,
error: isLoading ? null : res?.error,
isError: res?.error && !isLoading,
isLoading,
Expand Down
13 changes: 9 additions & 4 deletions src/typed.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { TObject, Type } from "@sinclair/typebox";
import { TNever, TObject, Type } from "@sinclair/typebox";

export const T = Type;

export * from "@sinclair/typebox";

export type DefaultSchema = {
[key: string]: any;
[key: string]: {
input?: TNever;
output?: TNever;
query?: TNever;
};
};

export type FetchSchema = Record<
string,
{
input?: TObject;
output?: TObject;
input?: TObject | TNever;
output?: TObject | TNever;
query?: TObject | TNever;
}
>;
Loading

0 comments on commit 6fc2d3a

Please sign in to comment.