Last active
May 7, 2024 09:48
-
-
Save borispoehland/9169e7b4e59b4bf4fce77db101e06e4b to your computer and use it in GitHub Desktop.
3 fetch effects for effect-ts. One uses cache: no-store, one cache: force-cache and one uses the default cache / revalidate
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as S from '@effect/schema/Schema' | |
import { Context, Effect } from 'effect' | |
export interface IFetcher { | |
fetch: typeof fetch | |
} | |
export const TFetcher = Context.Tag<IFetcher>('IFetcher') | |
class FreshFetcher implements IFetcher { | |
fetch(url: URL | RequestInfo, options: RequestInit | undefined) { | |
return fetch(url, { ...options, cache: 'no-store' }) | |
} | |
} | |
class CacheFetcher implements IFetcher { | |
fetch(url: URL | RequestInfo, options: RequestInit | undefined) { | |
return fetch(url, { ...options, cache: 'force-cache' }) | |
} | |
} | |
class RevalidateFetcher implements IFetcher { | |
fetch(url: URL | RequestInfo, options: RequestInit | undefined) { | |
return fetch(url, options) | |
} | |
} | |
function getGenericFetcher<R, E, A>( | |
program: Effect.Effect<R, E, A>, | |
fetcher: IFetcher | |
) { | |
return Effect.provideService(program, TFetcher, TFetcher.of(fetcher)) | |
} | |
function getFreshFetcher<R, E, A>(program: Effect.Effect<R, E, A>) { | |
return getGenericFetcher(program, { | |
fetch: new FreshFetcher().fetch, | |
}) | |
} | |
function getCacheFetcher<R, E, A>(program: Effect.Effect<R, E, A>) { | |
return getGenericFetcher(program, { | |
fetch: new CacheFetcher().fetch, | |
}) | |
} | |
function getRevalidateFetcher<R, E, A>(program: Effect.Effect<R, E, A>) { | |
return getGenericFetcher(program, { | |
fetch: new RevalidateFetcher().fetch, | |
}) | |
} | |
class FetchError { | |
readonly _tag = 'FetchError' | |
constructor(readonly error: unknown) {} | |
} | |
class JSONError { | |
readonly _tag = 'JSONError' | |
constructor(readonly error: unknown) {} | |
} | |
type IFetchArgs = Parameters<typeof fetch> | |
function fetchEffect(...args: IFetchArgs) { | |
return TFetcher.pipe( | |
Effect.flatMap((fetcher) => { | |
return Effect.tryPromise({ | |
try: () => fetcher.fetch(...args), | |
catch: (error) => new FetchError(error), | |
}) | |
}) | |
) | |
} | |
function jsonEffect(response: Response) { | |
return Effect.tryPromise({ | |
try: () => response.json(), | |
catch: (error) => new JSONError(error), | |
}) | |
} | |
function parseEffect<T>(schema: S.Schema<T>) { | |
return S.parse(schema) | |
} | |
function fetchGeneric<T>(schema: S.Schema<T> | null, ...args: IFetchArgs) { | |
return fetchEffect(...args).pipe( | |
Effect.flatMap(jsonEffect), | |
schema ? Effect.flatMap(parseEffect(schema)) : Effect.map((x) => x), | |
) | |
} | |
export function fetchFresh<T>(schema: S.Schema<T> | null, ...args: IFetchArgs) { | |
return getFreshFetcher(fetchGeneric<T>(schema, ...args)) | |
} | |
export function fetchCache<T>(schema: S.Schema<T> | null, ...args: IFetchArgs) { | |
return getCacheFetcher(fetchGeneric<T>(schema, ...args)) | |
} | |
export function fetchRevalidate<T>( | |
schema: S.Schema<T> | null, | |
...args: IFetchArgs | |
) { | |
return getRevalidateFetcher(fetchGeneric<T>(schema, ...args)) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as S from '@effect/schema/Schema' | |
import { Effect } from 'effect' | |
// fetch with cache: no-store | |
import { fetchFresh } from '#/lib' | |
// fetch with cache: force-cache | |
import { fetchCache } from '#/lib' | |
// fetch with revalidation | |
import { fetchRevalidate } from '#/lib' | |
const schema = S.struct({ | |
foo: S.number, | |
}) | |
/* FETCH FRESH DATA */ | |
// Effect<never, FetchError | JSONError | ParseError, unknown> | |
const program1 = fetchFresh(null, 'http://api.com/time') | |
// Effect<never, FetchError | JSONError | ParseError, string> | |
const program2 = fetchFresh<string>(null, 'http://api.com/time') | |
// Effect<never, FetchError | JSONError | ParseError, string> | |
const program3 = fetchFresh(S.string, 'http://api.com/time') | |
// typeof program2 === typeof program3, but program3 additionally ENSURES that the value is string and throws otherwise (like zod) | |
/* FETCH DATA ONLY ONCE */ | |
const program4 = fetchCache(null, 'http://api.com/time') | |
const program5 = fetchCache<string>(null, 'http://api.com/time') | |
const program6 = fetchCache(S.string, 'http://api.com/time') | |
/* FETCH DATA EVERY 60 SECONDS */ | |
const program4 = fetchRevalidate(null, 'http://api.com/time', { next: { revalidate: 60 } }) | |
const program5 = fetchRevalidate<string>(null, 'http://api.com/time', { next: { revalidate: 60 } }) | |
const program6 = fetchRevalidate(S.string, 'http://api.com/time', { next: { revalidate: 60 } }) | |
Effect.runPromise(program) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment