Skip to content

Instantly share code, notes, and snippets.

@mattiamanzati
Created April 23, 2023 20:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattiamanzati/0d5c7170ce2fd88a730f46226359e53a to your computer and use it in GitHub Desktop.
Save mattiamanzati/0d5c7170ce2fd88a730f46226359e53a to your computer and use it in GitHub Desktop.
import * as ENV from "@effect/data/Context"
import { Tag } from "@effect/data/Context"
import * as DU from "@effect/data/Duration"
import { pipe } from "@effect/data/Function"
import * as O from "@effect/data/Option"
import * as D from "@effect/io/Deferred"
import * as T from "@effect/io/Effect"
import type * as EX from "@effect/io/Exit"
import * as FID from "@effect/io/Fiber/Id"
import * as HUB from "@effect/io/Hub"
import * as Q from "@effect/io/Queue"
import * as RU from "@effect/io/Runtime"
import * as S from "@effect/stream/Stream"
import * as C from "@zuffellato/fw/Case"
import * as React from "react"
import * as RN from "react-native"
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-var-requires
const { unstable_batchedUpdates } =
RN.Platform.OS === "web" ? require("react-dom") : require("react-native")
export const RuntimeContext = React.createContext<() => RU.Runtime<never>>(() => {
throw new Error("Missing top level UI RuntimeProvider!")
})
const unitEffect = T.unit()
export function effectOrNoop<R, E>(
effect?: T.Effect<R, E, void>
): T.Effect<R, E, void> {
return effect || unitEffect
}
export function RuntimeProvider(props: React.PropsWithChildren<{}>) {
const [runtime, setRuntime] = React.useState<O.Option<RU.Runtime<never>>>(O.none())
React.useEffect(() => {
const cancel = T.runCallback(
pipe(
T.runtime<never>(),
T.flatMap((value) => T.sync(() => setRuntime(O.some(value)))),
T.zipRight(T.never())
),
() => undefined
)
return () => cancel(FID.none, () => undefined)
}, [])
const value = React.useCallback(() => O.getOrUndefined(runtime)!, [runtime])
if (O.isNone(runtime)) return null
return React.createElement(RuntimeContext.Provider, { value }, props.children)
}
export const RetryPolicyContext = React.createContext(T.orDie)
export function useEffect<R, E, A>(eff: T.Effect<R, E, A>) {
const retryPolicy = React.useContext(RetryPolicyContext)
const runtime = React.useContext(RuntimeContext)
return React.useCallback(
(args: ENV.Context<R>, cb?: (ex: EX.Exit<E, A>) => void) => {
const cancel = RU.runCallback(runtime())(
pipe(retryPolicy(eff), T.provideContext(args)),
cb ? cb : () => undefined
)
return () => cancel(FID.none, () => undefined)
},
[eff, retryPolicy]
)
}
const _countReducer = (state: number, action: 1 | -1) => state + action
export function useEffectSingleton<R, E, A>(eff: T.Effect<R, E, A>) {
const [state, dispatch] = React.useReducer(_countReducer, 0)
const _run = useEffect(eff)
const run = React.useCallback(
(args: ENV.Context<R>, cb?: (ex: EX.Exit<E, A>) => void) => {
dispatch(1)
return _run(args, (exit) => {
if (cb) cb(exit)
dispatch(-1)
})
},
[_run]
)
return [run, state > 0] as const
}
const _reducer = <A>(_: O.Option<A>, action: A) => O.some(action)
export function useStreamLatestValue_old<A>(
stream: S.Stream<never, never, A>
): O.Option<A> {
const [state, dispatch] = React.useReducer(_reducer, O.none())
const eff = React.useMemo(
() =>
pipe(
stream,
S.changesWith((a, b) => a === b || JSON.stringify(a) === JSON.stringify(b)),
S.map(dispatch),
S.runDrain
),
[stream]
)
const runInterrupt = useEffect(eff)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
// eslint-disable-next-line @typescript-eslint/no-empty-function
React.useEffect(() => runInterrupt(ENV.empty(), () => {}), [eff, runInterrupt])
return state as O.Option<A>
}
type BatchedStream = S.Stream<never, never, () => void>
const StreamRunnerContext = React.createContext(T.runSync(Q.unbounded<BatchedStream>()))
let pending: (() => void)[] = []
export function StreamRunnerProvider(props: React.PropsWithChildren<{}>) {
const queue = React.useContext(StreamRunnerContext)
const eff = React.useMemo(
() =>
pipe(
S.fromQueue(queue, 100),
S.flattenParUnbounded,
S.map((fn) => pending.push(fn)),
S.debounce(DU.millis(0)),
S.mapEffect(() =>
T.sync(() => {
unstable_batchedUpdates(() => {
console.log("items", pending.length)
pending.forEach((_) => _())
pending = []
})
})
),
S.runDrain
),
[]
)
const runInterrupt = useEffect(eff)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
// eslint-disable-next-line @typescript-eslint/no-empty-function
React.useEffect(() => runInterrupt(ENV.empty(), () => {}), [eff, runInterrupt])
return React.createElement(React.Fragment, { children: props.children })
}
export function useStreamLatestValue<A>(
stream: S.Stream<never, never, A>
): O.Option<A> {
const [state, dispatch] = React.useReducer(_reducer, O.none())
const queue = React.useContext(StreamRunnerContext)
React.useEffect(() => {
const def = T.runSync(D.make<never, void>())
const eff = pipe(
stream,
S.changesWith((a, b) => a === b || JSON.stringify(a) === JSON.stringify(b)),
S.map((v) => () => dispatch(v)),
S.interruptWhen(D.await(def))
)
T.runSync(Q.offer(queue, eff))
return () => T.runSync(T.asUnit(D.interrupt(def)))
}, [stream])
return state as O.Option<A>
}
export class RefetchAllEvent extends C.Tagged("RefetchAllEvent")<{}> {}
export type Event = RefetchAllEvent
export interface EventHub extends HUB.Hub<Event> {}
export const EventHub = Tag<EventHub>()
export const EventHubContext = React.createContext<EventHub>(T.runSync(HUB.unbounded()))
export function EventHubProvider(props: React.PropsWithChildren<{}>) {
const hub = React.useMemo(() => T.runSync(HUB.unbounded<Event>()), [])
return React.createElement(EventHubContext.Provider, { value: hub }, props.children)
}
export function invalidate<R, E, A>(
eff: T.Effect<R, E, A>
): T.Effect<R | EventHub, E, A> {
return pipe(
eff,
T.zipLeft(
T.flatMap(EventHub, (eventHub) => eventHub.publish(new RefetchAllEvent()))
)
)
}
export function useQuery<R, E, A>(
fn: () => T.Effect<R, E, A>,
deps: any[]
): S.Stream<R, E, A> {
const eff = React.useMemo(fn, deps)
const eventHub = React.useContext(EventHubContext)
return React.useMemo(
() =>
pipe(
S.succeed(new RefetchAllEvent()),
S.concat(S.fromHub(eventHub)),
S.flatMapParSwitch(1, () => S.fromEffect(eff))
),
[eff, eventHub]
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment