Skip to content

Instantly share code, notes, and snippets.

@borispoehland
Last active May 7, 2024 09:48
Show Gist options
  • Save borispoehland/9169e7b4e59b4bf4fce77db101e06e4b to your computer and use it in GitHub Desktop.
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
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))
}
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