openapi-ff
v0.1.3
Published
openapi-ff is a type-safe tiny wrapper around [effector](https://effector.dev/) and [farfetched](https://ff.effector.dev/) to work with OpenAPI schema.
Downloads
252
Readme
openapi-ff
openapi-ff is a type-safe tiny wrapper around effector and farfetched to work with OpenAPI schema.
It works by using openapi-fetch and openapi-typescript.
Setup
pnpm i openapi-ff @farfetched/core effector openapi-fetch
pnpm i -D openapi-typescript typescript
Next, generate TypeScript types from your OpenAPI schema using openapi-typescript:
npx openapi-typescript ./path/to/api/v1.yaml -o ./src/shared/api/schema.d.ts
Usage
import createFetchClient from "openapi-fetch";
import { createClient } from "openapi-ff";
import { createQuery } from "@farfetched/core";
import type { paths } from "./schema"; // generated by openapi-typescript
export const client = createFetchClient<paths>({
baseUrl: "https://myapi.dev/v1/",
});
export const { createApiEffect } = createClient(client);
const blogpostQuery = createQuery({
effect: createApiEffect("get", "/blogposts/{post_id}"),
});
import { useUnit } from "effector-react";
function Post() {
const { data: post, pending } = useUnit(blogpostQuery);
if (pending) {
return <Loader />;
}
return (
<section>
<p>{post.title}</p>
</section>
);
}
Advanced Usage:
import { chainRoute } from "atomic-router";
import { startChain } from "@farfetched/atomic-router";
import { isApiError } from "openapi-ff";
const getBlogpostFx = createApiEffect("get", "/blogposts/{post_id}", {
mapParams: (args: { postId: string }) => ({ params: { path: { post_id: args.postId } } }),
});
const blogpostQuery = createQuery({
effect: getBlogpostFx,
mapData: ({ result, params }) => ({ ...result, ...params }),
initialData: { body: "-", title: "-", postId: "0" },
});
export const blogpostRoute = chainRoute({
route: routes.blogpost.item,
...startChain(blogpostQuery),
});
const apiError = sample({
clock: softwareQuery.finished.failure,
filter: isApiError,
});
Runtime Validation
openapi-ff
does not handle runtime validation, as openapi-typescript
does not support it.
openapi-typescript by its design generates runtime-free static types, and only static types.
However, openapi-ff
allows adding a contract factory when creating a client and provides a corresponding method, createApiEffectWithContract
:
const { createApiEffectWithContract } = createClient(fetchClient, {
createContract(method, path) {
// ... create your own contract
return contract; // Contract<unknown, unknown>
},
});
const query = createQuery({
...createApiEffectWithContract("get", "/blogposts"),
});
typed-openapi example
npx typed-openapi path/to/api.yaml -o src/zod.ts -r zod # Generate zod schemas
pnpm install zod @farfetched/zod
import { EndpointByMethod } from "./zod";
import { zodContract } from "@farfetched/zod";
const { createApiEffectWithContract } = createClient(fetchClient, {
createContract(method, path) {
const endpoints = EndpointByMethod[method] as any;
const response = endpoints[path]?.response;
if (!response) {
throw new Error(`Response schema for route "${method} ${path}" doesn't exist`);
}
return zodContract(response);
},
});
const query = createQuery({
...createApiEffectWithContract("get", "/blogposts"),
});
orval example
Alternatively, you can simply add any contract to a query:
pnpm install zod @farfetched/zod
npx orval --input path/to/api.yaml --output src/zod.ts --client zod --mode single
import { zodContract } from "@farfetched/zod";
import { getBlogpostsResponseItem } from "./zod";
const blogpostQuery = createQuery({
effect: createApiEffect("get", "/blogposts/{post_id}"),
contract: zodContract(getBlogpostsResponseItem),
});
TODO
Add createApiQuery
:
createApiQuery({
method: "get",
path: "/blogposts/{post_id}",
mapParams: (args: { postId: string }) => ({ params: { path: { post_id: args.postId } } }),
mapData: ({ result, params }) => ({ ...result, ...params }),
initialData: { body: "-", title: "-", postId: "0" },
});