Add Effect v4 support, Fast Refresh tooling, and revamped docs #56

Merged
Thilawyn merged 133 commits from next into master 2026-07-26 02:32:59 +02:00
Showing only changes of commit d21ff53c26 - Show all commits
+76 -33
View File
@@ -1,4 +1,4 @@
import { type Cause, type Context, Duration, Effect, Equal, type Equivalence, Exit, Fiber, Option, Pipeable, Predicate, type Scope, Semaphore, Stream, SubscriptionRef } from "effect" import { type Cause, type Context, Duration, Effect, Equal, type Equivalence, Exit, Fiber, Option, Pipeable, Predicate, PubSub, Ref, type Scope, Semaphore, Stream, SubscriptionRef } from "effect"
import { AsyncResult } from "effect/unstable/reactivity" import { AsyncResult } from "effect/unstable/reactivity"
import * as Lens from "./Lens.js" import * as Lens from "./Lens.js"
import * as QueryClient from "./QueryClient.js" import * as QueryClient from "./QueryClient.js"
@@ -231,40 +231,40 @@ extends Pipeable.Class implements Query<K, A, E, R> {
Scope.Scope | R Scope.Scope | R
> { > {
return Effect.gen({ self: this }, function*() { return Effect.gen({ self: this }, function*() {
const state = Lens.fromSubscriptionRef(yield* SubscriptionRef.make(previous)) const state = yield* makeQueryStateLens(previous)
const fiber = yield* Effect.forkScoped(Effect.andThen( const fiber = yield* Effect.forkScoped(Effect.andThen(
Lens.update(state, previous => AsyncResult.match(previous.result, { Lens.update<QueryState<K, A, E>, never, never, never, never>(
onInitial: () => ({ state,
key: previous.key, previous => AsyncResult.match(previous.result, {
result: AsyncResult.initial(true), onInitial: () => ({
}), key: previous.key,
onSuccess: result => ({ result: AsyncResult.initial(true),
key: previous.key,
result: AsyncResult.success(result.value, {
waiting: true,
}), }),
}), onSuccess: result => ({
onFailure: result => ({ key: previous.key,
key: previous.key, result: AsyncResult.success(result.value, {
result: AsyncResult.failure(result.cause, { waiting: true,
waiting: true, }),
previousSuccess: result.previousSuccess,
}), }),
}), onFailure: result => ({
})), key: previous.key,
result: AsyncResult.failure(result.cause, {
waiting: true,
previousSuccess: result.previousSuccess,
}),
}),
}
)),
Effect.onExit(this.f(previous.key), exit => Effect.all([ Effect.onExit(this.f(previous.key), exit => Effect.gen({ self: this }, function*() {
Effect.fiberId, const fiberId = yield* Effect.fiberId
Lens.get(this.fiber), const fiber = yield* Lens.get(this.fiber)
]).pipe(
Effect.flatMap(([fiberId, fiber]) => Option.match(fiber, { if (Option.isSome(fiber) && fiberId === fiber.value.id)
onSome: v => fiberId === v.id yield* Lens.set(this.fiber, Option.none())
? Lens.set(this.fiber, Option.none())
: Effect.void, const finalState = (yield* Lens.updateAndGet<QueryState<K, A, E>, never, never, never, never>(
onNone: () => Effect.void,
})),
Effect.andThen(Lens.updateAndGet(
state, state,
previous => Exit.match(exit, { previous => Exit.match(exit, {
onSuccess: v => ({ onSuccess: v => ({
@@ -290,9 +290,11 @@ extends Pipeable.Class implements Query<K, A, E, R> {
}), }),
}), }),
}), }),
)), )) as FinalQueryState<K, A, E>
Effect.flatMap(finalState => Lens.set(this.latestFinalState, Option.some(finalState as FinalQueryState<K, A, E>))),
)), yield* Lens.set(this.latestFinalState, Option.some(finalState))
yield* PubSub.shutdown(state.pubsub)
}))
)) ))
yield* Lens.set(this.fiber, Option.some(fiber)) yield* Lens.set(this.fiber, Option.some(fiber))
@@ -416,3 +418,44 @@ export const service = <K, A, E = never, R = never>(
make(options), make(options),
query => Effect.forkScoped(query.run), query => Effect.forkScoped(query.run),
) )
export class QueryStateLens<in out K, in out A, in out E = never>
extends Lens.LensImpl<QueryState<K, A, E>, never, never, never, never> {
constructor(
readonly ref: Ref.Ref<QueryState<K, A, E>>,
readonly pubsub: PubSub.PubSub<QueryState<K, A, E>>,
readonly semaphore: Semaphore.Semaphore,
) {
super()
}
get resolve(): Effect.Effect<Lens.LensImpl.Resolved<QueryState<K, A, E>>, never, never> {
return Effect.map(
Ref.get(this.ref),
value => ({
value,
commit: next => Effect.flatMap(
next,
value => Effect.andThen(
Ref.set(this.ref, value),
PubSub.publish(this.pubsub, value),
),
),
}),
)
}
get changes() { return Stream.fromPubSub(this.pubsub) }
get lock() { return Effect.succeed(this.semaphore.withPermit) }
}
export const makeQueryStateLens = <K, A, E = never>(
initial: QueryState<K, A, E>,
) => Effect.all([
Ref.make(initial),
PubSub.unbounded<QueryState<K, A, E>>({ replay: 1 }),
Semaphore.make(1),
]).pipe(
Effect.tap(([, pubsub]) => PubSub.publish(pubsub, initial)),
Effect.map(([ref, pubsub, semaphore]) => new QueryStateLens(ref, pubsub, semaphore)),
)