Add Effect v4 version
Lint / lint (push) Successful in 48s

This commit is contained in:
Julien Valverdé
2026-06-22 02:04:20 +02:00
parent b7ea35006d
commit 091e102b23
41 changed files with 3832 additions and 2 deletions
+146
View File
@@ -0,0 +1,146 @@
import { type Context, Effect, Equal, type Fiber, Option, Pipeable, Predicate, type Scope, Stream, SubscriptionRef } from "effect"
import { Subscribable } from "effect-lens"
import * as Result from "./Result.js"
export const MutationTypeId: unique symbol = Symbol.for("@effect-fc/Mutation/Mutation")
export type MutationTypeId = typeof MutationTypeId
export interface Mutation<in out K extends Mutation.AnyKey, in out A, in out E = never, in out R = never, in out P = never>
extends Pipeable.Pipeable {
readonly [MutationTypeId]: MutationTypeId
readonly context: Context.Context<Scope.Scope | R>
readonly f: (key: K) => Effect.Effect<A, E, R>
readonly initialProgress: P
readonly latestKey: Subscribable.Subscribable<Option.Option<K>>
readonly fiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, E>>>
readonly result: Subscribable.Subscribable<Result.Result<A, E, P>>
readonly latestFinalResult: Subscribable.Subscribable<Option.Option<Result.Final<A, E, P>>>
mutate(key: K): Effect.Effect<Result.Final<A, E, P>>
mutateSubscribable(key: K): Effect.Effect<Subscribable.Subscribable<Result.Result<A, E, P>>>
}
export declare namespace Mutation {
export type AnyKey = readonly any[]
}
export class MutationImpl<in out K extends Mutation.AnyKey, in out A, in out E = never, in out R = never, in out P = never>
extends Pipeable.Class implements Mutation<K, A, E, R, P> {
readonly [MutationTypeId]: MutationTypeId = MutationTypeId
readonly latestKey: Subscribable.Subscribable<Option.Option<K>>
readonly fiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, E>>>
readonly result: Subscribable.Subscribable<Result.Result<A, E, P>>
readonly latestFinalResult: Subscribable.Subscribable<Option.Option<Result.Final<A, E, P>>>
constructor(
readonly context: Context.Context<Scope.Scope | R>,
readonly f: (key: K) => Effect.Effect<A, E, R>,
readonly initialProgress: P,
readonly latestKeyRef: SubscriptionRef.SubscriptionRef<Option.Option<K>>,
readonly fiberRef: SubscriptionRef.SubscriptionRef<Option.Option<Fiber.Fiber<A, E>>>,
readonly resultRef: SubscriptionRef.SubscriptionRef<Result.Result<A, E, P>>,
readonly latestFinalResultRef: SubscriptionRef.SubscriptionRef<Option.Option<Result.Final<A, E, P>>>,
) {
super()
this.latestKey = fromSubscriptionRef(latestKeyRef)
this.fiber = fromSubscriptionRef(fiberRef)
this.result = fromSubscriptionRef(resultRef)
this.latestFinalResult = fromSubscriptionRef(latestFinalResultRef)
}
mutate(key: K): Effect.Effect<Result.Final<A, E, P>> {
return SubscriptionRef.set(this.latestKeyRef, Option.some(key)).pipe(
Effect.andThen(this.start(key)),
Effect.andThen(sub => this.watch(sub)),
Effect.provide(this.context),
)
}
mutateSubscribable(key: K): Effect.Effect<Subscribable.Subscribable<Result.Result<A, E, P>>> {
return SubscriptionRef.set(this.latestKeyRef, Option.some(key)).pipe(
Effect.andThen(this.start(key)),
Effect.tap(sub => Effect.forkScoped(this.watch(sub))),
Effect.provide(this.context),
)
}
start(key: K): Effect.Effect<
Subscribable.Subscribable<Result.Result<A, E, P>>,
never,
Scope.Scope | R
> {
const self = this
return Effect.gen(function*() {
const initial = yield* SubscriptionRef.get(self.latestFinalResultRef)
const [sub, fiber] = yield* Result.unsafeForkEffect<A, E, R, P>(
Effect.onExit(self.f(key), () => Effect.andThen(
Effect.all([Effect.fiberId, SubscriptionRef.get(self.fiberRef)]),
([currentFiberId, fiber]) => Option.match(fiber, {
onSome: v => Equal.equals(currentFiberId, v.id)
? SubscriptionRef.set(self.fiberRef, Option.none())
: Effect.succeed(undefined),
onNone: () => Effect.succeed(undefined),
}),
)),
{
initial: Option.isSome(initial) ? Result.willFetch(initial.value) : Result.initial(),
initialProgress: self.initialProgress,
} as Result.unsafeForkEffect.Options<A, E, P>,
)
yield* SubscriptionRef.set(self.fiberRef, Option.some(fiber))
return sub
})
}
watch(
sub: Subscribable.Subscribable<Result.Result<A, E, P>>
): Effect.Effect<Result.Final<A, E, P>> {
return sub.get.pipe(
Effect.andThen(initial => Stream.runFoldEffect(
Stream.takeUntil(sub.changes, result => Result.isFinal(result) && !Result.hasFlag(result)),
() => initial,
(_, result) => Effect.as(SubscriptionRef.set(this.resultRef, result), result),
) as Effect.Effect<Result.Final<A, E, P>>),
Effect.tap(result => SubscriptionRef.set(this.latestFinalResultRef, Option.some(result))),
)
}
}
export const isMutation = (u: unknown): u is Mutation<readonly unknown[], unknown, unknown, unknown, unknown> => Predicate.hasProperty(u, MutationTypeId)
export declare namespace make {
export interface Options<K extends Mutation.AnyKey = never, A = void, E = never, R = never, P = never> {
readonly f: (key: K) => Effect.Effect<A, E, Result.forkEffect.InputContext<R, NoInfer<P>>>
readonly initialProgress?: P
}
}
export const make = Effect.fnUntraced(function* <const K extends Mutation.AnyKey = never, A = void, E = never, R = never, P = never>(
options: make.Options<K, A, E, R, P>
): Effect.fn.Return<
Mutation<K, A, E, Result.forkEffect.OutputContext<R, P>, P>,
never,
Scope.Scope | Result.forkEffect.OutputContext<R, P>
> {
return new MutationImpl(
yield* Effect.context<Scope.Scope | Result.forkEffect.OutputContext<R, P>>(),
options.f as any,
options.initialProgress as P,
yield* SubscriptionRef.make(Option.none<K>()),
yield* SubscriptionRef.make(Option.none<Fiber.Fiber<A, E>>()),
yield* SubscriptionRef.make(Result.initial<A, E, P>()),
yield* SubscriptionRef.make(Option.none<Result.Final<A, E, P>>()),
)
})
const fromSubscriptionRef = <A>(ref: SubscriptionRef.SubscriptionRef<A>): Subscribable.Subscribable<A> => Subscribable.make({
get: SubscriptionRef.get(ref),
changes: SubscriptionRef.changes(ref),
})