Skip to content

Instantly share code, notes, and snippets.

@ilfey
Last active March 24, 2025 06:27
Show Gist options
  • Select an option

  • Save ilfey/686f7dc44bc2f7b1578452c488d11931 to your computer and use it in GitHub Desktop.

Select an option

Save ilfey/686f7dc44bc2f7b1578452c488d11931 to your computer and use it in GitHub Desktop.
import {Contract, createMutation} from "@farfetched/core";
import {createRequestInstance, Payload} from "./createRequestInstance.ts";
import {API_URL} from "./config.ts";
import {sleep} from "./sleep.ts";
const MOCK_ENABLED = false;
interface MutationParameters<
Params,
Response,
> {
request: Payload<Params>
response: {
contract: Contract<any, Response>
mockedData?: Response,
}
}
export const createApiMutation = <
Params,
Response,
>({
request,
response,
}: MutationParameters<
Params,
Response
>) => {
const fx = createRequestInstance<Params, Response>({
baseUrl: API_URL,
payload: request,
})
const mutation = createMutation({
effect: fx,
contract: response.contract,
})
mutation.finished.failure.watch(({error}) => {
console.error(error);
if (error?.errorType === 'NETWORK') {
console.log("@NETWORK")
// TODO: error
}
if (error?.errorType === 'HTTP' && error.status !== 401) {
console.log("@HTTP !== 401")
// TODO: error
}
});
if (response.mockedData && MOCK_ENABLED) {
mutation.__.executeFx.use(async (params: Params) => {
const config = typeof request === "function" ? request(params) : request;
console.log(`Request ${config.url} mocked`);
await sleep(500);
return response.mockedData;
});
}
return mutation
}
import {Contract, createQuery, DynamicallySourcedField,} from "@farfetched/core";
import {createRequestInstance, Payload} from "./createRequestInstance.ts";
import {API_URL} from "./config.ts";
import {sleep} from "./sleep.ts";
const MOCK_ENABLED = false;
interface QueryParameters<
Params,
Response,
MappedData,
InitialData,
> {
request: Payload<Params>
response: {
contract: Contract<any, Response>
mapData?: DynamicallySourcedField<{
result: Response;
params: Params;
}, MappedData | InitialData, void>
mockedData?: Response,
}
initialData?: InitialData | null;
}
export const createApiQuery = <
Params,
Response,
MappedData,
InitialData,
>({
request,
response,
initialData = null,
}: QueryParameters<
Params,
Response,
MappedData,
InitialData
>) => {
const fx = createRequestInstance<Params, Response>({
baseUrl: API_URL,
payload: request,
})
const query = createQuery({
effect: fx,
contract: response.contract,
mapData: response.mapData!,
initialData: initialData,
})
query.finished.failure.watch(({error}) => {
console.error(error);
if (error?.errorType === 'NETWORK') {
console.log("@NETWORK")
// TODO: error
}
if (error?.errorType === 'HTTP' && error.status !== 401) {
console.log("@HTTP !== 401")
// TODO: error
}
if (error?.errorType === 'INVALID_DATA') {
console.log("@INVALID_DATA")
// TODO: error
}
});
if (response.mockedData && MOCK_ENABLED) {
query.__.executeFx.use(async (params: Params) => {
const config = typeof request === "function" ? request(params) : request;
console.log(`Request ${config.url} mocked`);
await sleep(500);
return response.mockedData;
});
}
return query
}
import {attach, createEffect} from "effector";
import {$token} from "../api/authorization.ts";
import {HttpError, NetworkError} from "@farfetched/core"
import {requestFx} from "./request-fx.ts";
const getTokenFx = attach({
source: $token,
effect: async (token) => token,
})
export const createRequestInstance = <P = RequestInit, R = void>({
baseUrl,
payload,
}: Omit<CreateRequestInstanceParams<P>, "url">) =>
createEffect<P, R, HttpError | NetworkError>(async (params) => {
const {url, query, headers, ...fetchOptions} = getConfig(payload, params)
const fullUrl = new URL(url, baseUrl)
if (query) {
fullUrl.search = new URLSearchParams(
Object.entries(query)
.map(kv => [kv[0], kv[1].toString()]) as unknown as string[][],
).toString()
}
const newHeaders = new Headers(headers);
newHeaders.set("Authorization", "Bearer " + await getTokenFx());
const request = new Request(fullUrl, {
...fetchOptions,
headers: newHeaders,
})
const response = await requestFx(request)
return await response.json() as R
})
const getConfig = <P>(payload: Payload<P>, params: P): CreateRequestParams =>
typeof payload === "function" ? payload(params) : payload
type CreateRequestParams = RequestInit & {
/** The presence of baseUrl is mandatory, otherwise the requests will not work.*/
baseUrl?: string;
headers?: Record<string, string>;
url: `/${string}`;
query?: Record<string, any>;
};
export type Payload<P> = CreateRequestParams | ((params: P) => CreateRequestParams);
type CreateRequestInstanceParams<P> = CreateRequestParams & {
payload: Payload<P>;
};
import {createApiQuery} from "shared/lib/createApiQuery.ts";
import {createListContract} from "shared/api/types.ts";
import {AnimeListItemContract} from "../model/AnimeItemDto.ts";
interface Params {
limit: number,
page: number
}
export const createGetAnimeListQuery = () =>
createApiQuery({
request: (params: Params) => ({
url: "/api/v1/animes",
method: "GET",
query: params,
}),
response: {
contract: createListContract(AnimeListItemContract),
mapData: ({result}) => result.data
}
})
import {createEffect} from "effector/compat";
import {fetchFx, httpError, HttpError, networkError, NetworkError} from "@farfetched/core";
export const requestFx = createEffect<
Request,
Response,
NetworkError | HttpError
>(async (request) => {
const response = await fetchFx(request)
.catch((cause) => {
throw networkError({
reason: cause?.message ?? null,
cause,
});
});
if (!response.ok) {
const resp = await response.text().catch(() => null)
throw httpError({
status: response.status,
statusText: response.statusText,
response: resp ?? null,
});
}
return response;
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment