Compare commits
132 Commits
master
...
b04860aa25
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b04860aa25 | ||
|
|
e9e17ac211 | ||
|
|
1f0ff725ff | ||
|
|
447d89982c | ||
|
|
778ee27795 | ||
|
|
077816efb6 | ||
|
|
e4bacd1ca7 | ||
|
|
0e2c0db28f | ||
|
|
c943d81702 | ||
|
|
c2bc406a5f | ||
|
|
4e778b6c95 | ||
|
|
0437fa5dcc | ||
|
|
5614b8df38 | ||
|
|
70b6c4434e | ||
|
|
2e8dfbc988 | ||
|
|
abc47c4647 | ||
|
|
eedd2a7f2a | ||
|
|
f4ab575a8d | ||
|
|
747e2c6056 | ||
|
|
68c68417d8 | ||
|
|
ed384a62a8 | ||
|
|
3a1748bb39 | ||
|
|
66b8fd2c2e | ||
|
|
bc81c443ab | ||
|
|
ee5dbe3766 | ||
|
|
825de84cef | ||
|
|
d6011f7897 | ||
|
|
8d4bce9e53 | ||
|
|
f7dd4e51f5 | ||
|
|
8772e25ff5 | ||
|
|
94a0864132 | ||
|
|
be8098fb7d | ||
|
|
7021e604ed | ||
|
|
1fd2a9ffbe | ||
|
|
1ed73dc3ac | ||
|
|
c689778cea | ||
|
|
da2a32001c | ||
|
|
5ac3a932d9 | ||
|
|
7935293bc3 | ||
|
|
cabceaffcd | ||
|
|
d239a11cdc | ||
|
|
fad61afce7 | ||
|
|
11fd4941c0 | ||
|
|
7bebc39a87 | ||
|
|
3bc0cc6586 | ||
|
|
f99d18b846 | ||
|
|
d61339ea6a | ||
|
|
3659d3f342 | ||
|
|
1e8a5d412f | ||
|
|
86539f33f0 | ||
|
|
8fa24b1791 | ||
|
|
adaadf13b2 | ||
|
|
3af7c3bf7a | ||
|
|
00b7228073 | ||
|
|
c2b2b1b96e | ||
|
|
74cf37e3a3 | ||
|
|
98091d4598 | ||
|
|
b2f1626268 | ||
|
|
40e8bf6a1f | ||
|
|
9c96741c8e | ||
|
|
3fa9b7d821 | ||
|
|
6b0f2f33cb | ||
|
|
2e00db5778 | ||
|
|
660f32a171 | ||
|
|
3f2639fda1 | ||
|
|
f76b3f333a | ||
|
|
3b407c6b4f | ||
|
|
b01b95a9d5 | ||
|
|
91b95ea6af | ||
|
|
7c99d1ff3d | ||
|
|
ae815553f2 | ||
|
|
86a96cbcce | ||
|
|
538b3a415d | ||
|
|
5b023678f4 | ||
|
|
9266697aa4 | ||
|
|
ad81bf9ed8 | ||
|
|
e92087e593 | ||
|
|
e182e6ab5c | ||
|
|
89175be558 | ||
|
|
4df90a0f1c | ||
|
|
693c7b2db8 | ||
|
|
5f60d03d83 | ||
|
|
ea768218a0 | ||
|
|
3b4eb750ed | ||
|
|
47aa130486 | ||
|
|
02da3df8eb | ||
|
|
8d276d2fbf | ||
|
|
af077d34aa | ||
|
|
618cee4028 | ||
|
|
8244c34d2a | ||
|
|
523d835d00 | ||
|
|
15e96b8fa9 | ||
|
|
44de864713 | ||
|
|
8e1f0a27cf | ||
|
|
8754020323 | ||
|
|
d9a01dae0f | ||
|
|
8873e81f7c | ||
|
|
38fcafb15c | ||
|
|
411397c7de | ||
|
|
85e7b54962 | ||
|
|
ce3989ab77 | ||
|
|
da0f6168f0 | ||
|
|
690dec1f1a | ||
|
|
60274266da | ||
|
|
28424b63cb | ||
|
|
e063eb06f7 | ||
|
|
fb5bb7fcef | ||
|
|
1f57f7d127 | ||
|
|
e8742e5aa6 | ||
|
|
be79d24d6e | ||
|
|
e1349e5e03 | ||
|
|
837dcbb1cb | ||
|
|
8252b6cbdf | ||
|
|
256638bc06 | ||
|
|
c0097bbe81 | ||
|
|
febeaa05d0 | ||
|
|
a71640d493 | ||
|
|
b636a709f3 | ||
|
|
fffbd01b5e | ||
|
|
36d5414d10 | ||
|
|
65810a6d79 | ||
|
|
9e7b30fbb4 | ||
|
|
6c843562ab | ||
|
|
809f512d11 | ||
|
|
e71239b903 | ||
|
|
bfcc097882 | ||
|
|
933b061b5d | ||
|
|
734c84824c | ||
|
|
e83e86f8f1 | ||
|
|
bebbc1d7de | ||
|
|
a7a0951b61 | ||
|
|
1b1a1961bc |
50
packages/example/src/QueryErrorHandler.tsx
Normal file
50
packages/example/src/QueryErrorHandler.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { AlertDialog, Button, Flex, Text } from "@radix-ui/themes"
|
||||
import { ErrorHandler } from "@reffuse/extension-query"
|
||||
import { Cause, Chunk, Context, Effect, Match, Option, Stream } from "effect"
|
||||
import { useState } from "react"
|
||||
import { AppQueryErrorHandler } from "./query"
|
||||
import { R } from "./reffuse"
|
||||
|
||||
|
||||
export function VQueryErrorHandler() {
|
||||
const [failure, setFailure] = useState(Option.none<Cause.Cause<
|
||||
ErrorHandler.Error<Context.Tag.Service<AppQueryErrorHandler>>
|
||||
>>())
|
||||
|
||||
R.useFork(() => AppQueryErrorHandler.pipe(Effect.flatMap(handler =>
|
||||
Stream.runForEach(handler.errors, v => Effect.sync(() =>
|
||||
setFailure(Option.some(v))
|
||||
))
|
||||
)), [])
|
||||
|
||||
return Option.match(failure, {
|
||||
onSome: v => (
|
||||
<AlertDialog.Root open>
|
||||
<AlertDialog.Content maxWidth="450px">
|
||||
<AlertDialog.Title>Error</AlertDialog.Title>
|
||||
<AlertDialog.Description size="2">
|
||||
{Cause.failures(v).pipe(
|
||||
Chunk.head,
|
||||
Option.getOrThrow,
|
||||
|
||||
Match.value,
|
||||
Match.tag("RequestError", () => <Text>HTTP request error</Text>),
|
||||
Match.tag("ResponseError", () => <Text>HTTP response error</Text>),
|
||||
Match.exhaustive,
|
||||
)}
|
||||
</AlertDialog.Description>
|
||||
|
||||
<Flex gap="3" mt="4" justify="end">
|
||||
<AlertDialog.Action>
|
||||
<Button variant="solid" color="red" onClick={() => setFailure(Option.none())}>
|
||||
Ok
|
||||
</Button>
|
||||
</AlertDialog.Action>
|
||||
</Flex>
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Root>
|
||||
),
|
||||
|
||||
onNone: () => <></>,
|
||||
})
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { Layer } from "effect"
|
||||
import { StrictMode } from "react"
|
||||
import { createRoot } from "react-dom/client"
|
||||
import { ReffuseRuntime } from "reffuse"
|
||||
import { AppQueryClient, AppQueryErrorHandler } from "./query"
|
||||
import { GlobalContext } from "./reffuse"
|
||||
import { routeTree } from "./routeTree.gen"
|
||||
|
||||
@@ -14,6 +15,8 @@ const layer = Layer.empty.pipe(
|
||||
Layer.provideMerge(Geolocation.layer),
|
||||
Layer.provideMerge(Permissions.layer),
|
||||
Layer.provideMerge(FetchHttpClient.layer),
|
||||
Layer.provideMerge(AppQueryClient.Live),
|
||||
Layer.provideMerge(AppQueryErrorHandler.Live),
|
||||
)
|
||||
|
||||
const router = createRouter({ routeTree })
|
||||
|
||||
9
packages/example/src/query.ts
Normal file
9
packages/example/src/query.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { HttpClientError } from "@effect/platform"
|
||||
import { ErrorHandler, QueryClient } from "@reffuse/extension-query"
|
||||
|
||||
|
||||
export class AppQueryErrorHandler extends ErrorHandler.Service("AppQueryErrorHandler")<AppQueryErrorHandler,
|
||||
HttpClientError.HttpClientError
|
||||
>() {}
|
||||
|
||||
export class AppQueryClient extends QueryClient.Service({ ErrorHandler: AppQueryErrorHandler })<AppQueryClient>() {}
|
||||
@@ -1,4 +1,3 @@
|
||||
import { HttpClientError } from "@effect/platform"
|
||||
import { QueryService } from "@reffuse/extension-query"
|
||||
import { ParseResult, Schema } from "effect"
|
||||
|
||||
@@ -8,5 +7,5 @@ export const Result = Schema.Array(Schema.String)
|
||||
export class Uuid4Query extends QueryService.Tag("Uuid4Query")<Uuid4Query,
|
||||
readonly ["uuid4", number],
|
||||
typeof Result.Type,
|
||||
HttpClientError.HttpClientError | ParseResult.ParseError
|
||||
ParseResult.ParseError
|
||||
>() {}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Clipboard, Geolocation, Permissions } from "@effect/platform-browser"
|
||||
import { LazyRefExtension } from "@reffuse/extension-lazyref"
|
||||
import { QueryExtension } from "@reffuse/extension-query"
|
||||
import { Reffuse, ReffuseContext } from "reffuse"
|
||||
import { AppQueryClient, AppQueryErrorHandler } from "./query"
|
||||
|
||||
|
||||
export const GlobalContext = ReffuseContext.make<
|
||||
@@ -10,6 +11,8 @@ export const GlobalContext = ReffuseContext.make<
|
||||
| Geolocation.Geolocation
|
||||
| Permissions.Permissions
|
||||
| HttpClient.HttpClient
|
||||
| AppQueryClient
|
||||
| AppQueryErrorHandler
|
||||
>()
|
||||
|
||||
export class GlobalReffuse extends Reffuse.Reffuse.pipe(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { VQueryErrorHandler } from "@/QueryErrorHandler"
|
||||
import { Container, Flex, Theme } from "@radix-ui/themes"
|
||||
import { createRootRoute, Link, Outlet } from "@tanstack/react-router"
|
||||
import { TanStackRouterDevtools } from "@tanstack/router-devtools"
|
||||
@@ -26,6 +27,8 @@ function Root() {
|
||||
</Container>
|
||||
|
||||
<Outlet />
|
||||
|
||||
<VQueryErrorHandler />
|
||||
<TanStackRouterDevtools />
|
||||
</Theme>
|
||||
)
|
||||
|
||||
37
packages/extension-query/src/ErrorHandler.ts
Normal file
37
packages/extension-query/src/ErrorHandler.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { type Cause, Context, Effect, Layer, Queue, Stream } from "effect"
|
||||
import type { Mutable } from "effect/Types"
|
||||
|
||||
|
||||
export interface ErrorHandler<E> {
|
||||
readonly errors: Stream.Stream<Cause.Cause<E>>
|
||||
readonly handle: <A, SelfE, R>(self: Effect.Effect<A, SelfE, R>) => Effect.Effect<A, Exclude<SelfE, E>, R>
|
||||
}
|
||||
|
||||
export type Error<T> = T extends ErrorHandler<infer E> ? E : never
|
||||
|
||||
|
||||
export interface ServiceResult<Self, Id extends string, E> extends Context.TagClass<Self, Id, ErrorHandler<E>> {
|
||||
readonly Live: Layer.Layer<Self>
|
||||
}
|
||||
|
||||
export const Service = <const Id extends string>(id: Id) => (
|
||||
<Self, E = never>(): ServiceResult<Self, Id, E> => {
|
||||
const TagClass = Context.Tag(id)() as ServiceResult<Self, Id, E>
|
||||
(TagClass as Mutable<typeof TagClass>).Live = Layer.effect(TagClass, Effect.gen(function*() {
|
||||
const queue = yield* Queue.unbounded<Cause.Cause<E>>()
|
||||
const errors = Stream.fromQueue(queue)
|
||||
|
||||
const handle = <A, SelfE, R>(
|
||||
self: Effect.Effect<A, SelfE, R>
|
||||
) => Effect.tapErrorCause(self, cause =>
|
||||
Queue.offer(queue, cause as Cause.Cause<E>)
|
||||
) as Effect.Effect<A, Exclude<SelfE, E>, R>
|
||||
|
||||
return { errors, handle }
|
||||
}))
|
||||
return TagClass
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
export class DefaultErrorHandler extends Service("@reffuse/extension-query/DefaultErrorHandler")<DefaultErrorHandler>() {}
|
||||
134
packages/extension-query/src/MutationRunner.ts
Normal file
134
packages/extension-query/src/MutationRunner.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
// import { BrowserStream } from "@effect/platform-browser"
|
||||
// import * as AsyncData from "@typed/async-data"
|
||||
// import { type Cause, Effect, Fiber, identity, Option, Ref, type Scope, Stream, SubscriptionRef } from "effect"
|
||||
|
||||
|
||||
// export interface MutationRunner<K extends readonly unknown[], A, E, R> {
|
||||
// readonly mutation: (...args: K) => Effect.Effect<A, E, R>
|
||||
|
||||
// readonly stateRef: SubscriptionRef.SubscriptionRef<AsyncData.AsyncData<A, E>>
|
||||
|
||||
// readonly forkMutate: Effect.Effect<Fiber.RuntimeFiber<void>>
|
||||
// }
|
||||
|
||||
|
||||
// export interface MakeProps<K extends readonly unknown[], A, E, R> {
|
||||
// readonly mutation: (...args: K) => Effect.Effect<A, E, R>
|
||||
// }
|
||||
|
||||
// export const make = <K extends readonly unknown[], A, E, R>(
|
||||
// { key, query }: MakeProps<K, A, E, R>
|
||||
// ): Effect.Effect<MutationRunner<K, A, E, R>, never, R> => Effect.gen(function*() {
|
||||
// const context = yield* Effect.context<R>()
|
||||
|
||||
// const stateRef = yield* SubscriptionRef.make(AsyncData.noData<A, E>())
|
||||
|
||||
// const interrupt = fiberRef.pipe(
|
||||
// Effect.flatMap(Option.match({
|
||||
// onSome: fiber => Ref.set(fiberRef, Option.none()).pipe(
|
||||
// Effect.andThen(Fiber.interrupt(fiber))
|
||||
// ),
|
||||
// onNone: () => Effect.void,
|
||||
// }))
|
||||
// )
|
||||
|
||||
// const forkInterrupt = fiberRef.pipe(
|
||||
// Effect.flatMap(Option.match({
|
||||
// onSome: fiber => Ref.set(fiberRef, Option.none()).pipe(
|
||||
// Effect.andThen(Fiber.interrupt(fiber).pipe(
|
||||
// Effect.asVoid,
|
||||
// Effect.forkDaemon,
|
||||
// ))
|
||||
// ),
|
||||
// onNone: () => Effect.forkDaemon(Effect.void),
|
||||
// }))
|
||||
// )
|
||||
|
||||
// const forkFetch = interrupt.pipe(
|
||||
// Effect.andThen(
|
||||
// Ref.set(stateRef, AsyncData.loading()).pipe(
|
||||
// Effect.andThen(latestKeyRef),
|
||||
// Effect.flatMap(identity),
|
||||
// Effect.flatMap(key => query(key).pipe(
|
||||
// Effect.matchCauseEffect({
|
||||
// onSuccess: v => Ref.set(stateRef, AsyncData.success(v)),
|
||||
// onFailure: c => Ref.set(stateRef, AsyncData.failure(c)),
|
||||
// })
|
||||
// )),
|
||||
|
||||
// Effect.provide(context),
|
||||
// Effect.fork,
|
||||
// )
|
||||
// ),
|
||||
|
||||
// Effect.flatMap(fiber =>
|
||||
// Ref.set(fiberRef, Option.some(fiber)).pipe(
|
||||
// Effect.andThen(Fiber.join(fiber)),
|
||||
// Effect.andThen(Ref.set(fiberRef, Option.none())),
|
||||
// )
|
||||
// ),
|
||||
|
||||
// Effect.forkDaemon,
|
||||
// )
|
||||
|
||||
// const forkRefresh = interrupt.pipe(
|
||||
// Effect.andThen(
|
||||
// Ref.update(stateRef, previous => {
|
||||
// if (AsyncData.isSuccess(previous) || AsyncData.isFailure(previous))
|
||||
// return AsyncData.refreshing(previous)
|
||||
// if (AsyncData.isRefreshing(previous))
|
||||
// return AsyncData.refreshing(previous.previous)
|
||||
// return AsyncData.loading()
|
||||
// }).pipe(
|
||||
// Effect.andThen(latestKeyRef),
|
||||
// Effect.flatMap(identity),
|
||||
// Effect.flatMap(key => query(key).pipe(
|
||||
// Effect.matchCauseEffect({
|
||||
// onSuccess: v => Ref.set(stateRef, AsyncData.success(v)),
|
||||
// onFailure: c => Ref.set(stateRef, AsyncData.failure(c)),
|
||||
// })
|
||||
// )),
|
||||
|
||||
// Effect.provide(context),
|
||||
// Effect.fork,
|
||||
// )
|
||||
// ),
|
||||
|
||||
// Effect.flatMap(fiber =>
|
||||
// Ref.set(fiberRef, Option.some(fiber)).pipe(
|
||||
// Effect.andThen(Fiber.join(fiber)),
|
||||
// Effect.andThen(Ref.set(fiberRef, Option.none())),
|
||||
// )
|
||||
// ),
|
||||
|
||||
// Effect.forkDaemon,
|
||||
// )
|
||||
|
||||
// const fetchOnKeyChange = Effect.addFinalizer(() => interrupt).pipe(
|
||||
// Effect.andThen(Stream.runForEach(key, latestKey =>
|
||||
// Ref.set(latestKeyRef, Option.some(latestKey)).pipe(
|
||||
// Effect.andThen(forkFetch)
|
||||
// )
|
||||
// ))
|
||||
// )
|
||||
|
||||
// const refreshOnWindowFocus = Stream.runForEach(
|
||||
// BrowserStream.fromEventListenerWindow("focus"),
|
||||
// () => forkRefresh,
|
||||
// )
|
||||
|
||||
// return {
|
||||
// query,
|
||||
|
||||
// latestKeyRef,
|
||||
// stateRef,
|
||||
// fiberRef,
|
||||
|
||||
// forkInterrupt,
|
||||
// forkFetch,
|
||||
// forkRefresh,
|
||||
|
||||
// fetchOnKeyChange,
|
||||
// refreshOnWindowFocus,
|
||||
// }
|
||||
// })
|
||||
39
packages/extension-query/src/QueryClient.ts
Normal file
39
packages/extension-query/src/QueryClient.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Context, Layer } from "effect"
|
||||
import type { Mutable } from "effect/Types"
|
||||
import * as ErrorHandler from "./ErrorHandler.js"
|
||||
|
||||
|
||||
export interface QueryClient<EH, HandledE> {
|
||||
readonly ErrorHandler: Context.Tag<EH, ErrorHandler.ErrorHandler<HandledE>>
|
||||
}
|
||||
|
||||
|
||||
const id = "@reffuse/extension-query/QueryClient"
|
||||
|
||||
export type TagClassShape<EH, HandledE> = Context.TagClassShape<typeof id, QueryClient<EH, HandledE>>
|
||||
export type GenericTagClass<EH, HandledE> = Context.TagClass<TagClassShape<EH, HandledE>, typeof id, QueryClient<EH, HandledE>>
|
||||
export const makeGenericTagClass = <EH = never, HandledE = never>(): GenericTagClass<EH, HandledE> => Context.Tag(id)()
|
||||
|
||||
|
||||
export interface ServiceProps<EH, HandledE> {
|
||||
readonly ErrorHandler?: Context.Tag<EH, ErrorHandler.ErrorHandler<HandledE>>
|
||||
}
|
||||
|
||||
export interface ServiceResult<Self, EH, HandledE> extends Context.TagClass<Self, typeof id, QueryClient<EH, HandledE>> {
|
||||
readonly Live: Layer.Layer<Self>
|
||||
}
|
||||
|
||||
export const Service = <
|
||||
EH = ErrorHandler.DefaultErrorHandler,
|
||||
HandledE = ErrorHandler.Error<Context.Tag.Service<ErrorHandler.DefaultErrorHandler>>,
|
||||
>(
|
||||
props?: ServiceProps<EH, HandledE>
|
||||
) => (
|
||||
<Self>(): ServiceResult<Self, EH, HandledE> => {
|
||||
const TagClass = Context.Tag(id)() as ServiceResult<Self, EH, HandledE>
|
||||
(TagClass as Mutable<typeof TagClass>).Live = Layer.succeed(TagClass, {
|
||||
ErrorHandler: (props?.ErrorHandler ?? ErrorHandler.DefaultErrorHandler) as Context.Tag<EH, ErrorHandler.ErrorHandler<HandledE>>
|
||||
})
|
||||
return TagClass
|
||||
}
|
||||
)
|
||||
@@ -2,6 +2,7 @@ import type * as AsyncData from "@typed/async-data"
|
||||
import { type Cause, type Context, Effect, type Fiber, Layer, type Option, type Stream, type SubscriptionRef } from "effect"
|
||||
import * as React from "react"
|
||||
import { ReffuseExtension, type ReffuseHelpers } from "reffuse"
|
||||
import * as QueryClient from "./QueryClient.js"
|
||||
import * as QueryRunner from "./QueryRunner.js"
|
||||
import type * as QueryService from "./QueryService.js"
|
||||
|
||||
@@ -24,11 +25,20 @@ export interface UseQueryResult<K extends readonly unknown[], A, E> {
|
||||
|
||||
|
||||
export const QueryExtension = ReffuseExtension.make(() => ({
|
||||
useQuery<K extends readonly unknown[], A, E, R>(
|
||||
this: ReffuseHelpers.ReffuseHelpers<R>,
|
||||
props: UseQueryProps<K, A, E, R>,
|
||||
): UseQueryResult<K, A, E> {
|
||||
useQuery<
|
||||
EH,
|
||||
QK extends readonly unknown[],
|
||||
QA,
|
||||
QE,
|
||||
HandledE,
|
||||
QR extends R,
|
||||
R,
|
||||
>(
|
||||
this: ReffuseHelpers.ReffuseHelpers<R | QueryClient.TagClassShape<EH, HandledE> | EH>,
|
||||
props: UseQueryProps<QK, QA, QE, QR>,
|
||||
): UseQueryResult<QK, QA, Exclude<QE, HandledE>> {
|
||||
const runner = this.useMemo(() => QueryRunner.make({
|
||||
QueryClient: QueryClient.makeGenericTagClass<EH, HandledE>(),
|
||||
key: props.key,
|
||||
query: props.query,
|
||||
}), [props.key])
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { BrowserStream } from "@effect/platform-browser"
|
||||
import * as AsyncData from "@typed/async-data"
|
||||
import { type Cause, Effect, Fiber, identity, Option, Ref, type Scope, Stream, SubscriptionRef } from "effect"
|
||||
import { type Cause, type Context, Effect, Fiber, identity, Option, Ref, type Scope, Stream, SubscriptionRef } from "effect"
|
||||
import type * as QueryClient from "./QueryClient.js"
|
||||
|
||||
|
||||
export interface QueryRunner<K extends readonly unknown[], A, E, R> {
|
||||
readonly query: (key: K) => Effect.Effect<A, E, R>
|
||||
readonly context: Context.Context<R>
|
||||
|
||||
readonly latestKeyRef: SubscriptionRef.SubscriptionRef<Option.Option<K>>
|
||||
readonly stateRef: SubscriptionRef.SubscriptionRef<AsyncData.AsyncData<A, E>>
|
||||
@@ -19,18 +20,27 @@ export interface QueryRunner<K extends readonly unknown[], A, E, R> {
|
||||
}
|
||||
|
||||
|
||||
export interface MakeProps<K extends readonly unknown[], A, E, R> {
|
||||
export interface MakeProps<EH, K extends readonly unknown[], A, E, HandledE, R> {
|
||||
readonly QueryClient: QueryClient.GenericTagClass<EH, HandledE>
|
||||
readonly key: Stream.Stream<K>
|
||||
readonly query: (key: K) => Effect.Effect<A, E, R>
|
||||
}
|
||||
|
||||
export const make = <K extends readonly unknown[], A, E, R>(
|
||||
{ key, query }: MakeProps<K, A, E, R>
|
||||
): Effect.Effect<QueryRunner<K, A, E, R>, never, R> => Effect.gen(function*() {
|
||||
const context = yield* Effect.context<R>()
|
||||
export const make = <EH, K extends readonly unknown[], A, E, HandledE, R>(
|
||||
{
|
||||
QueryClient,
|
||||
key,
|
||||
query,
|
||||
}: MakeProps<EH, K, A, E, HandledE, R>
|
||||
): Effect.Effect<
|
||||
QueryRunner<K, A, Exclude<E, HandledE>, R>,
|
||||
never,
|
||||
R | QueryClient.TagClassShape<EH, HandledE> | EH
|
||||
> => Effect.gen(function*() {
|
||||
const context = yield* Effect.context<R | QueryClient.TagClassShape<EH, HandledE> | EH>()
|
||||
|
||||
const latestKeyRef = yield* SubscriptionRef.make(Option.none<K>())
|
||||
const stateRef = yield* SubscriptionRef.make(AsyncData.noData<A, E>())
|
||||
const stateRef = yield* SubscriptionRef.make(AsyncData.noData<A, Exclude<E, HandledE>>())
|
||||
const fiberRef = yield* SubscriptionRef.make(Option.none<Fiber.RuntimeFiber<void, Cause.NoSuchElementException>>())
|
||||
|
||||
const interrupt = fiberRef.pipe(
|
||||
@@ -54,22 +64,27 @@ export const make = <K extends readonly unknown[], A, E, R>(
|
||||
}))
|
||||
)
|
||||
|
||||
const forkFetch = interrupt.pipe(
|
||||
Effect.andThen(
|
||||
Ref.set(stateRef, AsyncData.loading()).pipe(
|
||||
Effect.andThen(latestKeyRef),
|
||||
const run = QueryClient.pipe(
|
||||
Effect.flatMap(client => client.ErrorHandler),
|
||||
Effect.flatMap(errorHandler => latestKeyRef.pipe(
|
||||
Effect.flatMap(identity),
|
||||
Effect.flatMap(key => query(key).pipe(
|
||||
errorHandler.handle,
|
||||
Effect.matchCauseEffect({
|
||||
onSuccess: v => Ref.set(stateRef, AsyncData.success(v)),
|
||||
onFailure: c => Ref.set(stateRef, AsyncData.failure(c)),
|
||||
})
|
||||
}),
|
||||
)),
|
||||
)),
|
||||
|
||||
Effect.provide(context),
|
||||
Effect.fork,
|
||||
)
|
||||
),
|
||||
|
||||
const forkFetch = interrupt.pipe(
|
||||
Effect.andThen(Ref.set(stateRef, AsyncData.loading()).pipe(
|
||||
Effect.andThen(run),
|
||||
Effect.fork,
|
||||
)),
|
||||
|
||||
Effect.flatMap(fiber =>
|
||||
Ref.set(fiberRef, Option.some(fiber)).pipe(
|
||||
@@ -82,27 +97,16 @@ export const make = <K extends readonly unknown[], A, E, R>(
|
||||
)
|
||||
|
||||
const forkRefresh = interrupt.pipe(
|
||||
Effect.andThen(
|
||||
Ref.update(stateRef, previous => {
|
||||
Effect.andThen(Ref.update(stateRef, previous => {
|
||||
if (AsyncData.isSuccess(previous) || AsyncData.isFailure(previous))
|
||||
return AsyncData.refreshing(previous)
|
||||
if (AsyncData.isRefreshing(previous))
|
||||
return AsyncData.refreshing(previous.previous)
|
||||
return AsyncData.loading()
|
||||
}).pipe(
|
||||
Effect.andThen(latestKeyRef),
|
||||
Effect.flatMap(identity),
|
||||
Effect.flatMap(key => query(key).pipe(
|
||||
Effect.matchCauseEffect({
|
||||
onSuccess: v => Ref.set(stateRef, AsyncData.success(v)),
|
||||
onFailure: c => Ref.set(stateRef, AsyncData.failure(c)),
|
||||
})
|
||||
)),
|
||||
|
||||
Effect.provide(context),
|
||||
Effect.andThen(run),
|
||||
Effect.fork,
|
||||
)
|
||||
),
|
||||
)),
|
||||
|
||||
Effect.flatMap(fiber =>
|
||||
Ref.set(fiberRef, Option.some(fiber)).pipe(
|
||||
@@ -128,7 +132,7 @@ export const make = <K extends readonly unknown[], A, E, R>(
|
||||
)
|
||||
|
||||
return {
|
||||
query,
|
||||
context,
|
||||
|
||||
latestKeyRef,
|
||||
stateRef,
|
||||
|
||||
@@ -11,22 +11,3 @@ export interface QueryService<K extends readonly unknown[], A, E> {
|
||||
export const Tag = <const Id extends string>(id: Id) => <
|
||||
Self, K extends readonly unknown[], A, E = never,
|
||||
>() => Effect.Tag(id)<Self, QueryService<K, A, E>>()
|
||||
|
||||
|
||||
// export interface LayerProps<A, E, R> {
|
||||
// readonly query: Effect.Effect<A, E, R>
|
||||
// }
|
||||
|
||||
// export const layer = <Self, Id extends string, A, E, R>(
|
||||
// tag: Context.TagClass<Self, Id, QueryService<A, E>>,
|
||||
// props: LayerProps<A, E, R>,
|
||||
// ): Layer.Layer<Self, never, R> => Layer.effect(tag, Effect.gen(function*() {
|
||||
// const runner = yield* QueryRunner.make({
|
||||
// query: props.query
|
||||
// })
|
||||
|
||||
// return {
|
||||
// state: runner.stateRef,
|
||||
// refresh: runner.forkRefresh,
|
||||
// }
|
||||
// }))
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
export * as ErrorHandler from "./ErrorHandler.js"
|
||||
export * as QueryClient from "./QueryClient.js"
|
||||
export * from "./QueryExtension.js"
|
||||
export * as QueryRunner from "./QueryRunner.js"
|
||||
export * as QueryService from "./QueryService.js"
|
||||
|
||||
Reference in New Issue
Block a user