0.2.2 #31
@@ -17,7 +17,7 @@ extends Pipeable.Pipeable {
|
|||||||
readonly fiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, E>>>
|
readonly fiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, E>>>
|
||||||
readonly result: Subscribable.Subscribable<Result.Result<A, E, P>>
|
readonly result: Subscribable.Subscribable<Result.Result<A, E, P>>
|
||||||
|
|
||||||
mutate(key: K): Effect.Effect<Result.Result<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>>>
|
mutateSubscribable(key: K): Effect.Effect<Subscribable.Subscribable<Result.Result<A, E, P>>>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,13 +37,12 @@ extends Pipeable.Class() implements Mutation<K, A, E, R, P> {
|
|||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
mutate(key: K): Effect.Effect<Result.Result<A, E, P>> {
|
mutate(key: K): Effect.Effect<Result.Final<A, E, P>> {
|
||||||
return SubscriptionRef.set(this.latestKey, Option.some(key)).pipe(
|
return SubscriptionRef.set(this.latestKey, Option.some(key)).pipe(
|
||||||
Effect.andThen(Effect.provide(this.start(key), this.context)),
|
Effect.andThen(Effect.provide(this.start(key), this.context)),
|
||||||
Effect.andThen(sub => this.watch(sub)),
|
Effect.andThen(sub => this.watch(sub)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
mutateSubscribable(key: K): Effect.Effect<Subscribable.Subscribable<Result.Result<A, E, P>>> {
|
mutateSubscribable(key: K): Effect.Effect<Subscribable.Subscribable<Result.Result<A, E, P>>> {
|
||||||
return Effect.andThen(
|
return Effect.andThen(
|
||||||
SubscriptionRef.set(this.latestKey, Option.some(key)),
|
SubscriptionRef.set(this.latestKey, Option.some(key)),
|
||||||
@@ -57,7 +56,7 @@ extends Pipeable.Class() implements Mutation<K, A, E, R, P> {
|
|||||||
Scope.Scope | R
|
Scope.Scope | R
|
||||||
> {
|
> {
|
||||||
return this.result.pipe(
|
return this.result.pipe(
|
||||||
Effect.map(previous => (Result.isSuccess(previous) || Result.isFailure(previous))
|
Effect.map(previous => Result.isFinal(previous)
|
||||||
? previous
|
? previous
|
||||||
: undefined
|
: undefined
|
||||||
),
|
),
|
||||||
@@ -84,7 +83,7 @@ extends Pipeable.Class() implements Mutation<K, A, E, R, P> {
|
|||||||
|
|
||||||
watch(
|
watch(
|
||||||
sub: Subscribable.Subscribable<Result.Result<A, E, P>>
|
sub: Subscribable.Subscribable<Result.Result<A, E, P>>
|
||||||
): Effect.Effect<Result.Result<A, E, P>> {
|
): Effect.Effect<Result.Final<A, E, P>> {
|
||||||
return Effect.andThen(
|
return Effect.andThen(
|
||||||
sub.get,
|
sub.get,
|
||||||
initial => Stream.runFoldEffect(
|
initial => Stream.runFoldEffect(
|
||||||
@@ -92,20 +91,20 @@ extends Pipeable.Class() implements Mutation<K, A, E, R, P> {
|
|||||||
initial,
|
initial,
|
||||||
(_, result) => Effect.as(SubscriptionRef.set(this.result, result), result),
|
(_, result) => Effect.as(SubscriptionRef.set(this.result, result), result),
|
||||||
),
|
),
|
||||||
)
|
) as Effect.Effect<Result.Final<A, E, P>>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isMutation = (u: unknown): u is Mutation<unknown[], unknown, unknown, unknown, unknown> => Predicate.hasProperty(u, MutationTypeId)
|
export const isMutation = (u: unknown): u is Mutation<unknown[], unknown, unknown, unknown, unknown> => Predicate.hasProperty(u, MutationTypeId)
|
||||||
|
|
||||||
export declare namespace make {
|
export declare namespace make {
|
||||||
export interface Options<K extends readonly any[], A, E = never, R = never, P = never> {
|
export interface Options<K extends readonly any[] = never, A = void, E = never, R = never, P = never> {
|
||||||
readonly f: (key: NoInfer<K>) => Effect.Effect<A, E, Result.forkEffect.InputContext<R, NoInfer<P>>>
|
readonly f: (key: K) => Effect.Effect<A, E, Result.forkEffect.InputContext<R, NoInfer<P>>>
|
||||||
readonly initialProgress?: P
|
readonly initialProgress?: P
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const make = Effect.fnUntraced(function* <K extends readonly any[], A, E = never, R = never, P = never>(
|
export const make = Effect.fnUntraced(function* <const K extends readonly any[] = never, A = void, E = never, R = never, P = never>(
|
||||||
options: make.Options<K, A, E, R, P>
|
options: make.Options<K, A, E, R, P>
|
||||||
): Effect.fn.Return<
|
): Effect.fn.Return<
|
||||||
Mutation<K, A, E, Result.forkEffect.OutputContext<A, E, R, P>, P>,
|
Mutation<K, A, E, Result.forkEffect.OutputContext<A, E, R, P>, P>,
|
||||||
|
|||||||
@@ -18,9 +18,12 @@ extends Pipeable.Pipeable {
|
|||||||
readonly fiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, E>>>
|
readonly fiber: Subscribable.Subscribable<Option.Option<Fiber.Fiber<A, E>>>
|
||||||
readonly result: Subscribable.Subscribable<Result.Result<A, E, P>>
|
readonly result: Subscribable.Subscribable<Result.Result<A, E, P>>
|
||||||
|
|
||||||
fetch(key: K): Effect.Effect<Result.Result<A, E, P>>
|
fetch(key: K): Effect.Effect<Result.Final<A, E, P>>
|
||||||
readonly refetch: Effect.Effect<Result.Result<A, E, P>, Cause.NoSuchElementException>
|
fetchSubscribable(key: K): Effect.Effect<Subscribable.Subscribable<Result.Result<A, E, P>>>
|
||||||
readonly refresh: Effect.Effect<Result.Result<A, E, P>, Cause.NoSuchElementException>
|
readonly refetch: Effect.Effect<Result.Final<A, E, P>, Cause.NoSuchElementException>
|
||||||
|
readonly refetchSubscribable: Effect.Effect<Subscribable.Subscribable<Result.Result<A, E, P>>, Cause.NoSuchElementException>
|
||||||
|
readonly refresh: Effect.Effect<Result.Final<A, E, P>, Cause.NoSuchElementException>
|
||||||
|
readonly refreshSubscribable: Effect.Effect<Subscribable.Subscribable<Result.Result<A, E, P>>, Cause.NoSuchElementException>
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QueryImpl<in out K extends readonly any[], in out A, in out E = never, in out R = never, in out P = never>
|
export class QueryImpl<in out K extends readonly any[], in out A, in out E = never, in out R = never, in out P = never>
|
||||||
@@ -47,15 +50,20 @@ extends Pipeable.Class() implements Query<K, A, E, R, P> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch(key: K): Effect.Effect<Result.Result<A, E, P>> {
|
fetch(key: K): Effect.Effect<Result.Final<A, E, P>> {
|
||||||
return this.interrupt.pipe(
|
return this.interrupt.pipe(
|
||||||
Effect.andThen(SubscriptionRef.set(this.latestKey, Option.some(key))),
|
Effect.andThen(SubscriptionRef.set(this.latestKey, Option.some(key))),
|
||||||
Effect.andThen(Effect.provide(this.start(key), this.context)),
|
Effect.andThen(Effect.provide(this.start(key), this.context)),
|
||||||
Effect.andThen(sub => this.watch(sub)),
|
Effect.andThen(sub => this.watch(sub)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
fetchSubscribable(key: K): Effect.Effect<Subscribable.Subscribable<Result.Result<A, E, P>>> {
|
||||||
get refetch(): Effect.Effect<Result.Result<A, E, P>, Cause.NoSuchElementException> {
|
return this.interrupt.pipe(
|
||||||
|
Effect.andThen(SubscriptionRef.set(this.latestKey, Option.some(key))),
|
||||||
|
Effect.andThen(Effect.provide(this.start(key), this.context)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
get refetch(): Effect.Effect<Result.Final<A, E, P>, Cause.NoSuchElementException> {
|
||||||
return this.interrupt.pipe(
|
return this.interrupt.pipe(
|
||||||
Effect.andThen(this.latestKey),
|
Effect.andThen(this.latestKey),
|
||||||
Effect.andThen(identity),
|
Effect.andThen(identity),
|
||||||
@@ -63,8 +71,14 @@ extends Pipeable.Class() implements Query<K, A, E, R, P> {
|
|||||||
Effect.andThen(sub => this.watch(sub)),
|
Effect.andThen(sub => this.watch(sub)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
get refetchSubscribable(): Effect.Effect<Subscribable.Subscribable<Result.Result<A, E, P>>, Cause.NoSuchElementException> {
|
||||||
get refresh(): Effect.Effect<Result.Result<A, E, P>, Cause.NoSuchElementException> {
|
return this.interrupt.pipe(
|
||||||
|
Effect.andThen(this.latestKey),
|
||||||
|
Effect.andThen(identity),
|
||||||
|
Effect.andThen(key => Effect.provide(this.start(key), this.context)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
get refresh(): Effect.Effect<Result.Final<A, E, P>, Cause.NoSuchElementException> {
|
||||||
return this.interrupt.pipe(
|
return this.interrupt.pipe(
|
||||||
Effect.andThen(this.latestKey),
|
Effect.andThen(this.latestKey),
|
||||||
Effect.andThen(identity),
|
Effect.andThen(identity),
|
||||||
@@ -72,6 +86,13 @@ extends Pipeable.Class() implements Query<K, A, E, R, P> {
|
|||||||
Effect.andThen(sub => this.watch(sub)),
|
Effect.andThen(sub => this.watch(sub)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
get refreshSubscribable(): Effect.Effect<Subscribable.Subscribable<Result.Result<A, E, P>>, Cause.NoSuchElementException> {
|
||||||
|
return this.interrupt.pipe(
|
||||||
|
Effect.andThen(this.latestKey),
|
||||||
|
Effect.andThen(identity),
|
||||||
|
Effect.andThen(key => Effect.provide(this.start(key, true), this.context)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
start(
|
start(
|
||||||
key: K,
|
key: K,
|
||||||
@@ -82,7 +103,7 @@ extends Pipeable.Class() implements Query<K, A, E, R, P> {
|
|||||||
Scope.Scope | R
|
Scope.Scope | R
|
||||||
> {
|
> {
|
||||||
return this.result.pipe(
|
return this.result.pipe(
|
||||||
Effect.map(previous => (Result.isSuccess(previous) || Result.isFailure(previous))
|
Effect.map(previous => Result.isFinal(previous)
|
||||||
? previous
|
? previous
|
||||||
: undefined
|
: undefined
|
||||||
),
|
),
|
||||||
@@ -101,7 +122,7 @@ extends Pipeable.Class() implements Query<K, A, E, R, P> {
|
|||||||
|
|
||||||
watch(
|
watch(
|
||||||
sub: Subscribable.Subscribable<Result.Result<A, E, P>>
|
sub: Subscribable.Subscribable<Result.Result<A, E, P>>
|
||||||
): Effect.Effect<Result.Result<A, E, P>> {
|
): Effect.Effect<Result.Final<A, E, P>> {
|
||||||
return Effect.andThen(
|
return Effect.andThen(
|
||||||
sub.get,
|
sub.get,
|
||||||
initial => Stream.runFoldEffect(
|
initial => Stream.runFoldEffect(
|
||||||
@@ -109,7 +130,7 @@ extends Pipeable.Class() implements Query<K, A, E, R, P> {
|
|||||||
initial,
|
initial,
|
||||||
(_, result) => Effect.as(SubscriptionRef.set(this.result, result), result),
|
(_, result) => Effect.as(SubscriptionRef.set(this.result, result), result),
|
||||||
),
|
),
|
||||||
)
|
) as Effect.Effect<Result.Final<A, E, P>>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ const ResultPrototype = Object.freeze({
|
|||||||
|
|
||||||
|
|
||||||
export const isResult = (u: unknown): u is Result<unknown, unknown, unknown> => Predicate.hasProperty(u, ResultTypeId)
|
export const isResult = (u: unknown): u is Result<unknown, unknown, unknown> => Predicate.hasProperty(u, ResultTypeId)
|
||||||
|
export const isFinal = (u: unknown): u is Final<unknown, unknown, unknown> => isResult(u) && (isSuccess(u) || isFailure(u))
|
||||||
export const isInitial = (u: unknown): u is Initial => isResult(u) && u._tag === "Initial"
|
export const isInitial = (u: unknown): u is Initial => isResult(u) && u._tag === "Initial"
|
||||||
export const isRunning = (u: unknown): u is Running<unknown> => isResult(u) && u._tag === "Running"
|
export const isRunning = (u: unknown): u is Running<unknown> => isResult(u) && u._tag === "Running"
|
||||||
export const isSuccess = (u: unknown): u is Success<unknown> => isResult(u) && u._tag === "Success"
|
export const isSuccess = (u: unknown): u is Success<unknown> => isResult(u) && u._tag === "Success"
|
||||||
@@ -121,6 +122,7 @@ export const fail = <E, A = never>(
|
|||||||
cause,
|
cause,
|
||||||
previousSuccess: Option.fromNullable(previousSuccess),
|
previousSuccess: Option.fromNullable(previousSuccess),
|
||||||
}, ResultPrototype)
|
}, ResultPrototype)
|
||||||
|
|
||||||
export const refreshing = <R extends Success<any> | Failure<any, any>, P = never>(
|
export const refreshing = <R extends Success<any> | Failure<any, any>, P = never>(
|
||||||
result: R,
|
result: R,
|
||||||
progress?: P,
|
progress?: P,
|
||||||
@@ -199,11 +201,11 @@ export namespace unsafeForkEffect {
|
|||||||
|
|
||||||
export type Options<A, E, P> = {
|
export type Options<A, E, P> = {
|
||||||
readonly initialProgress?: P
|
readonly initialProgress?: P
|
||||||
readonly previous?: Success<A> | Failure<A, E>
|
readonly previous?: Final<A, E, P>
|
||||||
} & (
|
} & (
|
||||||
| {
|
| {
|
||||||
readonly refresh: true
|
readonly refresh: true
|
||||||
readonly previous: Success<A> | Failure<A, E>
|
readonly previous: Final<A, E, P>
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
readonly refresh?: false
|
readonly refresh?: false
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { HttpClient, type HttpClientError } from "@effect/platform"
|
|||||||
import { Button, Container, Flex, Heading, Slider, Text } from "@radix-ui/themes"
|
import { Button, Container, Flex, Heading, Slider, Text } from "@radix-ui/themes"
|
||||||
import { createFileRoute } from "@tanstack/react-router"
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
import { Array, Cause, Chunk, Console, Effect, flow, Match, Option, Schema, Stream } from "effect"
|
import { Array, Cause, Chunk, Console, Effect, flow, Match, Option, Schema, Stream } from "effect"
|
||||||
import { Component, ErrorObserver, Query, Result, Subscribable, SubscriptionRef } from "effect-fc"
|
import { Component, ErrorObserver, Mutation, Query, Result, Subscribable, SubscriptionRef } from "effect-fc"
|
||||||
import { runtime } from "@/runtime"
|
import { runtime } from "@/runtime"
|
||||||
|
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ const Post = Schema.Struct({
|
|||||||
const ResultView = Component.makeUntraced("Result")(function*() {
|
const ResultView = Component.makeUntraced("Result")(function*() {
|
||||||
const runPromise = yield* Component.useRunPromise()
|
const runPromise = yield* Component.useRunPromise()
|
||||||
|
|
||||||
const [idRef, query] = yield* Component.useOnMount(() => Effect.gen(function*() {
|
const [idRef, query, mutation] = yield* Component.useOnMount(() => Effect.gen(function*() {
|
||||||
const idRef = yield* SubscriptionRef.make(1)
|
const idRef = yield* SubscriptionRef.make(1)
|
||||||
const key = Stream.zipLatest(Stream.make("posts" as const), idRef.changes)
|
const key = Stream.zipLatest(Stream.make("posts" as const), idRef.changes)
|
||||||
|
|
||||||
@@ -30,11 +30,20 @@ const ResultView = Component.makeUntraced("Result")(function*() {
|
|||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
return [idRef, query] as const
|
const mutation = yield* Mutation.make({
|
||||||
|
f: ([id]: readonly [id: number]) => HttpClient.HttpClient.pipe(
|
||||||
|
Effect.tap(Effect.sleep("500 millis")),
|
||||||
|
Effect.andThen(client => client.get(`https://jsonplaceholder.typicode.com/posts/${ id }`)),
|
||||||
|
Effect.andThen(response => response.json),
|
||||||
|
Effect.andThen(Schema.decodeUnknown(Post)),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
return [idRef, query, mutation] as const
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const [id, setId] = yield* SubscriptionRef.useSubscriptionRefState(idRef)
|
const [id, setId] = yield* SubscriptionRef.useSubscriptionRefState(idRef)
|
||||||
const [result] = yield* Subscribable.useSubscribables([query.result])
|
const [queryResult, mutationResult] = yield* Subscribable.useSubscribables([query.result, mutation.result])
|
||||||
|
|
||||||
yield* Component.useOnMount(() => ErrorObserver.ErrorObserver<HttpClientError.HttpClientError>().pipe(
|
yield* Component.useOnMount(() => ErrorObserver.ErrorObserver<HttpClientError.HttpClientError>().pipe(
|
||||||
Effect.andThen(observer => observer.subscribe),
|
Effect.andThen(observer => observer.subscribe),
|
||||||
@@ -59,7 +68,8 @@ const ResultView = Component.makeUntraced("Result")(function*() {
|
|||||||
onValueChange={flow(Array.head, Option.getOrThrow, setId)}
|
onValueChange={flow(Array.head, Option.getOrThrow, setId)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{Match.value(result).pipe(
|
<div>
|
||||||
|
{Match.value(queryResult).pipe(
|
||||||
Match.tag("Running", () => <Text>Loading...</Text>),
|
Match.tag("Running", () => <Text>Loading...</Text>),
|
||||||
Match.tag("Success", result => <>
|
Match.tag("Success", result => <>
|
||||||
<Heading>{result.value.title}</Heading>
|
<Heading>{result.value.title}</Heading>
|
||||||
@@ -71,11 +81,31 @@ const ResultView = Component.makeUntraced("Result")(function*() {
|
|||||||
),
|
),
|
||||||
Match.orElse(() => <></>),
|
Match.orElse(() => <></>),
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<Flex direction="row" justify="center" align="center" gap="1">
|
<Flex direction="row" justify="center" align="center" gap="1">
|
||||||
<Button onClick={() => runPromise(query.refresh)}>Refresh</Button>
|
<Button onClick={() => runPromise(query.refresh)}>Refresh</Button>
|
||||||
<Button onClick={() => runPromise(query.refetch)}>Refetch</Button>
|
<Button onClick={() => runPromise(query.refetch)}>Refetch</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{Match.value(mutationResult).pipe(
|
||||||
|
Match.tag("Running", () => <Text>Loading...</Text>),
|
||||||
|
Match.tag("Success", result => <>
|
||||||
|
<Heading>{result.value.title}</Heading>
|
||||||
|
<Text>{result.value.body}</Text>
|
||||||
|
{Result.isRefreshing(result) && <Text>Refreshing...</Text>}
|
||||||
|
</>),
|
||||||
|
Match.tag("Failure", result =>
|
||||||
|
<Text>An error has occured: {result.cause.toString()}</Text>
|
||||||
|
),
|
||||||
|
Match.orElse(() => <></>),
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Flex direction="row" justify="center" align="center" gap="1">
|
||||||
|
<Button onClick={() => runPromise(Effect.andThen(idRef, id => mutation.mutate([id])))}>Mutate</Button>
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user