Compare commits
319 Commits
master
...
16a7dec3fd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16a7dec3fd | ||
|
|
ffba43c259 | ||
|
|
45ab641262 | ||
|
|
c7c2f8de62 | ||
|
|
dda868d444 | ||
|
|
b9e787f42b | ||
|
|
1af2a14b52 | ||
|
|
861e462ebd | ||
|
|
b6a127c8a7 | ||
|
|
497e9a34f2 | ||
|
|
9d0daaa87f | ||
|
|
557c4a1b97 | ||
|
|
b395644798 | ||
|
|
099a28ca0d | ||
|
|
27ca5e643a | ||
|
|
a616e84079 | ||
|
|
c832c3f79a | ||
|
|
6f65574ebd | ||
|
|
9ccabbb627 | ||
|
|
5f4087aa40 | ||
|
|
85b41bda9f | ||
|
|
e5a7fe8ad6 | ||
|
|
59b7115d19 | ||
|
|
08af31f0b9 | ||
|
|
44fc6bbbc4 | ||
|
|
904b725753 | ||
|
|
73dd7bc160 | ||
|
|
6bc07d5b2a | ||
|
|
70e9b9218d | ||
|
|
31b07f842b | ||
|
|
10f23d4cb4 | ||
|
|
39765102db | ||
|
|
04e78e1ea3 | ||
|
|
606dd2c00f | ||
|
|
c13a8d549f | ||
|
|
4b9bfd0637 | ||
|
|
53fc1ef505 | ||
|
|
c8b675d93e | ||
|
|
882ec9591c | ||
|
|
5b3637afd8 | ||
|
|
d6256a7cfd | ||
|
|
cf6c84ff8e | ||
|
|
198a7cee03 | ||
|
|
032f283ac8 | ||
|
|
c34629e20d | ||
|
|
284a080f19 | ||
|
|
87d27dd48d | ||
|
|
24853561f1 | ||
|
|
1902ad373f | ||
|
|
aa6c4a8008 | ||
|
|
d5ac84b2cc | ||
|
|
3c604abcef | ||
|
|
ba99309877 | ||
|
|
db3cd05851 | ||
|
|
dce81be269 | ||
|
|
3980c10747 | ||
|
|
43a3793dbf | ||
|
|
da7044ee9f | ||
|
|
ff5503cfd1 | ||
|
|
dc2cfb35e0 | ||
|
|
1228c51694 | ||
|
|
076007ec67 | ||
|
|
dd524e1aa5 | ||
|
|
1c7cef703b | ||
|
|
fa0f8c6b24 | ||
|
|
357e5aa56b | ||
|
|
ea374d7e0f | ||
|
|
148c98acbd | ||
|
|
39d2176c61 | ||
|
|
107ff1e794 | ||
|
|
a70ef27f75 | ||
|
|
04b2fad038 | ||
|
|
691b28427d | ||
|
|
1de976aaa8 | ||
|
|
df851cf9ee | ||
|
|
459f548c10 | ||
|
|
6156baec4d | ||
|
|
1163b83929 | ||
|
|
8917f84952 | ||
|
|
58752253b3 | ||
|
|
ba362baf04 | ||
|
|
33cf4fbcbd | ||
|
|
e8f92c88b8 | ||
|
|
6ae155de34 | ||
|
|
db783f174e | ||
|
|
2b48695e54 | ||
|
|
ab441fe982 | ||
|
|
eabcf9085b | ||
|
|
926482b154 | ||
|
|
110b0813f8 | ||
|
|
974af95a22 | ||
|
|
d6e1d445e8 | ||
|
|
d8d6e87a12 | ||
|
|
682e473bf7 | ||
|
|
31dd7b5fdb | ||
|
|
17686e68c3 | ||
|
|
49d4bd4d43 | ||
|
|
be88035936 | ||
|
|
3497d17046 | ||
|
|
1ca832e69d | ||
|
|
98bd72d1d7 | ||
|
|
f594f47793 | ||
|
|
4f9827720c | ||
|
|
0f761524fd | ||
|
|
574136e161 | ||
|
|
7a12abdbdf | ||
|
|
8fecb94292 | ||
|
|
26a2111705 | ||
|
|
1cb02407c8 | ||
|
|
6e8ce84851 | ||
|
|
570fb93876 | ||
|
|
821fd18f8f | ||
|
|
b7ef95341b | ||
|
|
5f5ef5614b | ||
|
|
cbd39f893e | ||
|
|
529e3d3f9d | ||
|
|
9d47418a69 | ||
|
|
c1b6e73231 | ||
|
|
d1ba4148f2 | ||
|
|
ef13e87d12 | ||
|
|
8b141b907f | ||
|
|
52a36cb882 | ||
|
|
3b844f071b | ||
|
|
4e422a1901 | ||
|
|
a5c6b34dfe | ||
|
|
ab1f851428 | ||
|
|
3f091d55c2 | ||
|
|
76a33fccca | ||
|
|
c75bb10e6b | ||
|
|
3da4b2a318 | ||
|
|
9a24ecaf84 | ||
|
|
7b20df6c71 | ||
|
|
f40dae90fb | ||
|
|
46211638f5 | ||
|
|
a28d6c3d30 | ||
|
|
6b74b9a3b2 | ||
|
|
e17f945666 | ||
|
|
aa46ecc82d | ||
|
|
8ea9146dd9 | ||
|
|
0a4bb2856d | ||
|
|
b4cd7daa81 | ||
|
|
b5712d5433 | ||
|
|
57b7eac05c | ||
|
|
9a9bd78ec6 | ||
|
|
ddcd681ca4 | ||
|
|
66de517ab5 | ||
|
|
b50255ded2 | ||
|
|
03f0b623ed | ||
|
|
fb6d803723 | ||
|
|
972986241c | ||
|
|
9eb0904600 | ||
|
|
fc86c818e0 | ||
|
|
5a12139602 | ||
|
|
a0928c718f | ||
|
|
49d9edd4b1 | ||
|
|
3552c25b5c | ||
|
|
516e0a465d | ||
|
|
7cf5367409 | ||
|
|
3b237c0588 | ||
|
|
d9aa42d23a | ||
|
|
fd3213c53f | ||
|
|
baa8c92221 | ||
|
|
d55b432846 | ||
|
|
6266c7506e | ||
|
|
043e966e45 | ||
|
|
88fab2c7d7 | ||
|
|
224ccd8e32 | ||
|
|
4cf70ada0b | ||
|
|
f9bd5d4d6b | ||
|
|
1ec1db0658 | ||
|
|
2d94e84941 | ||
|
|
aab83907ba | ||
|
|
8c0d6b4c8a | ||
|
|
d82d1d1c29 | ||
|
|
0f09573948 | ||
|
|
2b6b36713e | ||
|
|
5d0aecc9d5 | ||
|
|
f21d8b2d8a | ||
|
|
f85173fa68 | ||
|
|
65a124de1f | ||
|
|
16893761c6 | ||
|
|
3fdc2e31eb | ||
|
|
8636a28f2f | ||
|
|
d56578da8f | ||
|
|
299109d421 | ||
|
|
4995b2949f | ||
|
|
6e6e675709 | ||
|
|
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 |
@@ -1,16 +1,16 @@
|
|||||||
import { AlertDialog, Button, Flex, Text } from "@radix-ui/themes"
|
import { AlertDialog, Button, Flex, Text } from "@radix-ui/themes"
|
||||||
import { Cause, Console, Effect, Either, flow, Match, Option, Stream } from "effect"
|
import { Cause, Console, Effect, Either, flow, Match, Option, Stream } from "effect"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
|
import { AppQueryClient } from "./query"
|
||||||
import { R } from "./reffuse"
|
import { R } from "./reffuse"
|
||||||
import { AppQueryErrorHandler } from "./services"
|
|
||||||
|
|
||||||
|
|
||||||
export function VQueryErrorHandler() {
|
export function VQueryErrorHandler() {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
|
||||||
const error = R.useSubscribeStream(
|
const error = R.useSubscribeStream(
|
||||||
R.useMemo(() => AppQueryErrorHandler.AppQueryErrorHandler.pipe(
|
R.useMemo(() => AppQueryClient.pipe(
|
||||||
Effect.map(handler => handler.errors.pipe(
|
Effect.map(client => client.errorHandler.errors.pipe(
|
||||||
Stream.changes,
|
Stream.changes,
|
||||||
Stream.tap(Console.error),
|
Stream.tap(Console.error),
|
||||||
Stream.tap(() => Effect.sync(() => setOpen(true))),
|
Stream.tap(() => Effect.sync(() => setOpen(true))),
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ import { Layer } from "effect"
|
|||||||
import { StrictMode } from "react"
|
import { StrictMode } from "react"
|
||||||
import { createRoot } from "react-dom/client"
|
import { createRoot } from "react-dom/client"
|
||||||
import { ReffuseRuntime } from "reffuse"
|
import { ReffuseRuntime } from "reffuse"
|
||||||
|
import { AppQueryClient, AppQueryErrorHandler } from "./query"
|
||||||
import { RootContext } from "./reffuse"
|
import { RootContext } from "./reffuse"
|
||||||
import { routeTree } from "./routeTree.gen"
|
import { routeTree } from "./routeTree.gen"
|
||||||
import { AppQueryClient, AppQueryErrorHandler } from "./services"
|
|
||||||
|
|
||||||
|
|
||||||
const layer = Layer.empty.pipe(
|
const layer = Layer.empty.pipe(
|
||||||
Layer.provideMerge(AppQueryClient.AppQueryClient.Default),
|
Layer.provideMerge(AppQueryClient.Live),
|
||||||
Layer.provideMerge(AppQueryErrorHandler.AppQueryErrorHandler.Default),
|
Layer.provideMerge(AppQueryErrorHandler.Live),
|
||||||
Layer.provideMerge(Clipboard.layer),
|
Layer.provideMerge(Clipboard.layer),
|
||||||
Layer.provideMerge(Geolocation.layer),
|
Layer.provideMerge(Geolocation.layer),
|
||||||
Layer.provideMerge(Permissions.layer),
|
Layer.provideMerge(Permissions.layer),
|
||||||
|
|||||||
17
packages/example/src/query.ts
Normal file
17
packages/example/src/query.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { HttpClientError } from "@effect/platform"
|
||||||
|
import { QueryClient, QueryErrorHandler } from "@reffuse/extension-query"
|
||||||
|
import { Effect } from "effect"
|
||||||
|
|
||||||
|
|
||||||
|
export class AppQueryErrorHandler extends QueryErrorHandler.Service<AppQueryErrorHandler,
|
||||||
|
HttpClientError.HttpClientError
|
||||||
|
>()(
|
||||||
|
"AppQueryErrorHandler",
|
||||||
|
|
||||||
|
(self, failure, defect) => self.pipe(
|
||||||
|
Effect.catchTag("RequestError", "ResponseError", failure),
|
||||||
|
Effect.catchAllDefect(defect),
|
||||||
|
),
|
||||||
|
) {}
|
||||||
|
|
||||||
|
export class AppQueryClient extends QueryClient.Service<AppQueryClient>()({ ErrorHandler: AppQueryErrorHandler }) {}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { QueryRunner } from "@reffuse/extension-query"
|
import { QueryService } from "@reffuse/extension-query"
|
||||||
import { ParseResult, Schema } from "effect"
|
import { ParseResult, Schema } from "effect"
|
||||||
|
|
||||||
|
|
||||||
export const Result = Schema.Array(Schema.String)
|
export const Result = Schema.Array(Schema.String)
|
||||||
|
|
||||||
export class Uuid4Query extends QueryRunner.Tag("Uuid4Query")<Uuid4Query,
|
export class Uuid4Query extends QueryService.Tag("Uuid4Query")<Uuid4Query,
|
||||||
readonly ["uuid4", number],
|
readonly ["uuid4", number],
|
||||||
typeof Result.Type,
|
typeof Result.Type,
|
||||||
ParseResult.ParseError
|
ParseResult.ParseError
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export function Uuid4QueryService() {
|
|||||||
const runFork = R.useRunFork()
|
const runFork = R.useRunFork()
|
||||||
|
|
||||||
const query = R.useMemo(() => Uuid4Query.Uuid4Query, [])
|
const query = R.useMemo(() => Uuid4Query.Uuid4Query, [])
|
||||||
const [state] = R.useSubscribeRefs(query.stateRef)
|
const [state] = R.useRefState(query.state)
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ import { Clipboard, Geolocation, Permissions } from "@effect/platform-browser"
|
|||||||
import { LazyRefExtension } from "@reffuse/extension-lazyref"
|
import { LazyRefExtension } from "@reffuse/extension-lazyref"
|
||||||
import { QueryExtension } from "@reffuse/extension-query"
|
import { QueryExtension } from "@reffuse/extension-query"
|
||||||
import { Reffuse, ReffuseContext } from "reffuse"
|
import { Reffuse, ReffuseContext } from "reffuse"
|
||||||
import { AppQueryClient, AppQueryErrorHandler } from "./services"
|
import { AppQueryClient } from "./query"
|
||||||
|
|
||||||
|
|
||||||
export const RootContext = ReffuseContext.make<
|
export const RootContext = ReffuseContext.make<
|
||||||
| AppQueryClient.AppQueryClient
|
| AppQueryClient
|
||||||
| AppQueryErrorHandler.AppQueryErrorHandler
|
|
||||||
| Clipboard.Clipboard
|
| Clipboard.Clipboard
|
||||||
| Geolocation.Geolocation
|
| Geolocation.Geolocation
|
||||||
| Permissions.Permissions
|
| Permissions.Permissions
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Uuid4QueryService } from "@/query/views/Uuid4QueryService"
|
|||||||
import { R } from "@/reffuse"
|
import { R } from "@/reffuse"
|
||||||
import { HttpClient } from "@effect/platform"
|
import { HttpClient } from "@effect/platform"
|
||||||
import { createFileRoute } from "@tanstack/react-router"
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
import { Console, Effect, Layer, Schema } from "effect"
|
import { Console, Effect, Schema } from "effect"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
|
|
||||||
|
|
||||||
@@ -17,18 +17,15 @@ function RouteComponent() {
|
|||||||
key: R.useStreamFromReactiveValues(["uuid4", 10 as number]),
|
key: R.useStreamFromReactiveValues(["uuid4", 10 as number]),
|
||||||
query: ([, count]) => Console.log(`Querying ${ count } IDs...`).pipe(
|
query: ([, count]) => Console.log(`Querying ${ count } IDs...`).pipe(
|
||||||
Effect.andThen(Effect.sleep("500 millis")),
|
Effect.andThen(Effect.sleep("500 millis")),
|
||||||
Effect.andThen(Effect.map(
|
Effect.andThen(HttpClient.get(`https://www.uuidtools.com/api/generate/v4/count/${ count }`)),
|
||||||
HttpClient.HttpClient,
|
|
||||||
HttpClient.withTracerPropagation(false),
|
HttpClient.withTracerPropagation(false),
|
||||||
)),
|
|
||||||
Effect.flatMap(client => client.get(`https://www.uuidtools.com/api/generate/v4/count/${ count }`)),
|
|
||||||
Effect.flatMap(res => res.json),
|
Effect.flatMap(res => res.json),
|
||||||
Effect.flatMap(Schema.decodeUnknown(Uuid4Query.Result)),
|
Effect.flatMap(Schema.decodeUnknown(Uuid4Query.Result)),
|
||||||
Effect.scoped,
|
Effect.scoped,
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
const layer = useMemo(() => Layer.succeed(Uuid4Query.Uuid4Query, query), [query])
|
const layer = useMemo(() => query.layer(Uuid4Query.Uuid4Query), [query])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryContext.Provider layer={layer}>
|
<QueryContext.Provider layer={layer}>
|
||||||
|
|||||||
@@ -29,18 +29,15 @@ function RouteComponent() {
|
|||||||
Effect.tap(() => QueryProgress.QueryProgress.update(() =>
|
Effect.tap(() => QueryProgress.QueryProgress.update(() =>
|
||||||
AsyncData.Progress.make({ loaded: 50, total: Option.some(100) })
|
AsyncData.Progress.make({ loaded: 50, total: Option.some(100) })
|
||||||
)),
|
)),
|
||||||
Effect.andThen(Effect.map(
|
Effect.andThen(HttpClient.get(`https://www.uuidtools.com/api/generate/v4/count/${ count }`)),
|
||||||
HttpClient.HttpClient,
|
|
||||||
HttpClient.withTracerPropagation(false),
|
HttpClient.withTracerPropagation(false),
|
||||||
)),
|
|
||||||
Effect.flatMap(client => client.get(`https://www.uuidtools.com/api/generate/v4/count/${ count }`)),
|
|
||||||
Effect.flatMap(res => res.json),
|
Effect.flatMap(res => res.json),
|
||||||
Effect.flatMap(Schema.decodeUnknown(Result)),
|
Effect.flatMap(Schema.decodeUnknown(Result)),
|
||||||
Effect.scoped,
|
Effect.scoped,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const [state] = R.useSubscribeRefs(mutation.stateRef)
|
const [state] = R.useSubscribeRefs(mutation.state)
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -23,18 +23,15 @@ function RouteComponent() {
|
|||||||
key: R.useStreamFromReactiveValues(["uuid4", count]),
|
key: R.useStreamFromReactiveValues(["uuid4", count]),
|
||||||
query: ([, count]) => Console.log(`Querying ${ count } IDs...`).pipe(
|
query: ([, count]) => Console.log(`Querying ${ count } IDs...`).pipe(
|
||||||
Effect.andThen(Effect.sleep("500 millis")),
|
Effect.andThen(Effect.sleep("500 millis")),
|
||||||
Effect.andThen(Effect.map(
|
Effect.andThen(HttpClient.get(`https://www.uuidtools.com/api/generate/v4/count/${ count }`)),
|
||||||
HttpClient.HttpClient,
|
|
||||||
HttpClient.withTracerPropagation(false),
|
HttpClient.withTracerPropagation(false),
|
||||||
)),
|
|
||||||
Effect.flatMap(client => client.get(`https://www.uuidtools.com/api/generate/v4/count/${ count }`)),
|
|
||||||
Effect.flatMap(res => res.json),
|
Effect.flatMap(res => res.json),
|
||||||
Effect.flatMap(Schema.decodeUnknown(Result)),
|
Effect.flatMap(Schema.decodeUnknown(Result)),
|
||||||
Effect.scoped,
|
Effect.scoped,
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
const [state] = R.useSubscribeRefs(query.stateRef)
|
const [state] = R.useSubscribeRefs(query.state)
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -6,16 +6,6 @@ import { Console, Effect, Option } from "effect"
|
|||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
|
|
||||||
|
|
||||||
interface Node {
|
|
||||||
value: string
|
|
||||||
left?: Leaf
|
|
||||||
right?: Leaf
|
|
||||||
}
|
|
||||||
interface Leaf {
|
|
||||||
node: Node
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const makeUuid = Effect.provide(makeUuid4, GetRandomValues.CryptoRandom)
|
const makeUuid = Effect.provide(makeUuid4, GetRandomValues.CryptoRandom)
|
||||||
|
|
||||||
|
|
||||||
@@ -42,11 +32,6 @@ function RouteComponent() {
|
|||||||
runSync,
|
runSync,
|
||||||
), [scopeLayer, runSync])
|
), [scopeLayer, runSync])
|
||||||
|
|
||||||
|
|
||||||
const nodeRef = R.useRef(() => Effect.succeed<Node>({ value: "prout" }))
|
|
||||||
const nodeValueRef = R.useSubRefFromPath(nodeRef, ["value"])
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex direction="column" justify="center" align="center" gap="2">
|
<Flex direction="column" justify="center" align="center" gap="2">
|
||||||
<Text>{uuid}</Text>
|
<Text>{uuid}</Text>
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import { QueryClient } from "@reffuse/extension-query"
|
|
||||||
import * as AppQueryErrorHandler from "./AppQueryErrorHandler"
|
|
||||||
|
|
||||||
|
|
||||||
export class AppQueryClient extends QueryClient.Service<AppQueryClient>()({
|
|
||||||
errorHandler: AppQueryErrorHandler.AppQueryErrorHandler
|
|
||||||
}) {}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { HttpClientError } from "@effect/platform"
|
|
||||||
import { QueryErrorHandler } from "@reffuse/extension-query"
|
|
||||||
import { Effect } from "effect"
|
|
||||||
|
|
||||||
|
|
||||||
export class AppQueryErrorHandler extends Effect.Service<AppQueryErrorHandler>()("AppQueryErrorHandler", {
|
|
||||||
effect: QueryErrorHandler.make<HttpClientError.HttpClientError>()(
|
|
||||||
(self, failure, defect) => self.pipe(
|
|
||||||
Effect.catchTag("RequestError", "ResponseError", failure),
|
|
||||||
Effect.catchAllDefect(defect),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}) {}
|
|
||||||
@@ -1,2 +1 @@
|
|||||||
export * as AppQueryClient from "./AppQueryClient"
|
export {}
|
||||||
export * as AppQueryErrorHandler from "./AppQueryErrorHandler"
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@reffuse/extension-query",
|
"name": "@reffuse/extension-query",
|
||||||
"version": "0.1.5",
|
"version": "0.1.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"./README.md",
|
"./README.md",
|
||||||
@@ -39,6 +39,6 @@
|
|||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
"effect": "^3.15.0",
|
"effect": "^3.15.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"reffuse": "^0.1.11"
|
"reffuse": "^0.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
packages/extension-query/src/MutationService.ts
Normal file
16
packages/extension-query/src/MutationService.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import type * as AsyncData from "@typed/async-data"
|
||||||
|
import { Effect, type Fiber, type Stream, type SubscriptionRef } from "effect"
|
||||||
|
|
||||||
|
|
||||||
|
export interface MutationService<K extends readonly unknown[], A, E> {
|
||||||
|
readonly state: SubscriptionRef.SubscriptionRef<AsyncData.AsyncData<A, E>>
|
||||||
|
readonly mutate: (...key: K) => Effect.Effect<AsyncData.Success<A> | AsyncData.Failure<E>>
|
||||||
|
readonly forkMutate: (...key: K) => Effect.Effect<readonly [
|
||||||
|
fiber: Fiber.RuntimeFiber<AsyncData.Success<A> | AsyncData.Failure<E>>,
|
||||||
|
state: Stream.Stream<AsyncData.AsyncData<A, E>>,
|
||||||
|
]>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Tag = <const Id extends string>(id: Id) => <
|
||||||
|
Self, K extends readonly unknown[], A, E = never,
|
||||||
|
>() => Effect.Tag(id)<Self, MutationService<K, A, E>>()
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Context, Effect, identity, Layer } from "effect"
|
import { Context, Effect, Layer } from "effect"
|
||||||
import type { Mutable } from "effect/Types"
|
import type { Mutable } from "effect/Types"
|
||||||
import * as QueryErrorHandler from "./QueryErrorHandler.js"
|
import * as QueryErrorHandler from "./QueryErrorHandler.js"
|
||||||
|
|
||||||
@@ -8,17 +8,6 @@ export interface QueryClient<FallbackA, HandledE> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface MakeProps<FallbackA, HandledE> {
|
|
||||||
readonly errorHandler: QueryErrorHandler.QueryErrorHandler<FallbackA, HandledE>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const make = <FallbackA, HandledE>(
|
|
||||||
{ errorHandler }: MakeProps<FallbackA, HandledE>
|
|
||||||
): Effect.Effect<QueryClient<FallbackA, HandledE>> => Effect.Do.pipe(
|
|
||||||
Effect.let("errorHandler", () => errorHandler)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
const id = "@reffuse/extension-query/QueryClient"
|
const id = "@reffuse/extension-query/QueryClient"
|
||||||
|
|
||||||
export type TagClassShape<FallbackA, HandledE> = Context.TagClassShape<typeof id, QueryClient<FallbackA, HandledE>>
|
export type TagClassShape<FallbackA, HandledE> = Context.TagClassShape<typeof id, QueryClient<FallbackA, HandledE>>
|
||||||
@@ -30,28 +19,46 @@ export type GenericTagClass<FallbackA, HandledE> = Context.TagClass<
|
|||||||
export const makeGenericTagClass = <FallbackA = never, HandledE = never>(): GenericTagClass<FallbackA, HandledE> => Context.Tag(id)()
|
export const makeGenericTagClass = <FallbackA = never, HandledE = never>(): GenericTagClass<FallbackA, HandledE> => Context.Tag(id)()
|
||||||
|
|
||||||
|
|
||||||
export interface ServiceProps<FallbackA = never, HandledE = never, E = never, R = never> {
|
export interface ServiceProps<EH, FallbackA, HandledE> {
|
||||||
readonly errorHandler?: Effect.Effect<QueryErrorHandler.QueryErrorHandler<FallbackA, HandledE>, E, R>
|
readonly ErrorHandler?: Context.Tag<EH, QueryErrorHandler.QueryErrorHandler<FallbackA, HandledE>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServiceResult<Self, FallbackA, HandledE, E, R> extends Context.TagClass<
|
export interface ServiceResult<Self, EH, FallbackA, HandledE> extends Context.TagClass<
|
||||||
Self,
|
Self,
|
||||||
typeof id,
|
typeof id,
|
||||||
QueryClient<FallbackA, HandledE>
|
QueryClient<FallbackA, HandledE>
|
||||||
> {
|
> {
|
||||||
readonly Default: Layer.Layer<Self, E, R>
|
readonly Live: Layer.Layer<
|
||||||
|
Self | (EH extends QueryErrorHandler.DefaultQueryErrorHandler ? EH : never),
|
||||||
|
never,
|
||||||
|
EH extends QueryErrorHandler.DefaultQueryErrorHandler ? never : EH
|
||||||
|
>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Service = <Self>() => (
|
export const Service = <Self>() => (
|
||||||
<FallbackA = never, HandledE = never, E = never, R = never>(
|
<
|
||||||
props?: ServiceProps<FallbackA, HandledE, E, R>
|
EH = QueryErrorHandler.DefaultQueryErrorHandler,
|
||||||
): ServiceResult<Self, FallbackA, HandledE, E, R> => {
|
FallbackA = QueryErrorHandler.Fallback<Context.Tag.Service<QueryErrorHandler.DefaultQueryErrorHandler>>,
|
||||||
const TagClass = Context.Tag(id)() as ServiceResult<Self, FallbackA, HandledE, E, R>
|
HandledE = QueryErrorHandler.Error<Context.Tag.Service<QueryErrorHandler.DefaultQueryErrorHandler>>,
|
||||||
|
>(
|
||||||
|
props?: ServiceProps<EH, FallbackA, HandledE>
|
||||||
|
): ServiceResult<Self, EH, FallbackA, HandledE> => {
|
||||||
|
const TagClass = Context.Tag(id)() as ServiceResult<Self, EH, FallbackA, HandledE>
|
||||||
|
|
||||||
(TagClass as Mutable<typeof TagClass>).Default = Layer.effect(TagClass, Effect.flatMap(
|
(TagClass as Mutable<typeof TagClass>).Live = Layer.effect(TagClass, Effect.Do.pipe(
|
||||||
props?.errorHandler ?? QueryErrorHandler.make<never>()(identity),
|
Effect.bind("errorHandler", () =>
|
||||||
errorHandler => make({ errorHandler }),
|
(props?.ErrorHandler ?? QueryErrorHandler.DefaultQueryErrorHandler) as Effect.Effect<
|
||||||
))
|
QueryErrorHandler.QueryErrorHandler<FallbackA, HandledE>,
|
||||||
|
never,
|
||||||
|
EH extends QueryErrorHandler.DefaultQueryErrorHandler ? never : EH
|
||||||
|
>
|
||||||
|
)
|
||||||
|
)).pipe(
|
||||||
|
Layer.provideMerge((props?.ErrorHandler
|
||||||
|
? Layer.empty
|
||||||
|
: QueryErrorHandler.DefaultQueryErrorHandler.Live
|
||||||
|
) as Layer.Layer<EH>)
|
||||||
|
)
|
||||||
|
|
||||||
return TagClass
|
return TagClass
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Cause, Effect, PubSub, Stream } from "effect"
|
import { Cause, Context, Effect, identity, Layer, PubSub, Stream } from "effect"
|
||||||
|
import type { Mutable } from "effect/Types"
|
||||||
|
|
||||||
|
|
||||||
export interface QueryErrorHandler<FallbackA, HandledE> {
|
export interface QueryErrorHandler<FallbackA, HandledE> {
|
||||||
@@ -10,14 +11,31 @@ export type Fallback<T> = T extends QueryErrorHandler<infer A, any> ? A : never
|
|||||||
export type Error<T> = T extends QueryErrorHandler<any, infer E> ? E : never
|
export type Error<T> = T extends QueryErrorHandler<any, infer E> ? E : never
|
||||||
|
|
||||||
|
|
||||||
export const make = <HandledE = never>() => (
|
export interface ServiceResult<
|
||||||
<FallbackA>(
|
Self,
|
||||||
|
Id extends string,
|
||||||
|
FallbackA,
|
||||||
|
HandledE,
|
||||||
|
> extends Context.TagClass<
|
||||||
|
Self,
|
||||||
|
Id,
|
||||||
|
QueryErrorHandler<FallbackA, HandledE>
|
||||||
|
> {
|
||||||
|
readonly Live: Layer.Layer<Self>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Service = <Self, HandledE = never>() => (
|
||||||
|
<const Id extends string, FallbackA>(
|
||||||
|
id: Id,
|
||||||
f: (
|
f: (
|
||||||
self: Effect.Effect<never, HandledE>,
|
self: Effect.Effect<never, HandledE>,
|
||||||
failure: (failure: HandledE) => Effect.Effect<never>,
|
failure: (failure: HandledE) => Effect.Effect<never>,
|
||||||
defect: (defect: unknown) => Effect.Effect<never>,
|
defect: (defect: unknown) => Effect.Effect<never>,
|
||||||
) => Effect.Effect<FallbackA>
|
) => Effect.Effect<FallbackA>,
|
||||||
): Effect.Effect<QueryErrorHandler<FallbackA, HandledE>> => Effect.gen(function*() {
|
): ServiceResult<Self, Id, FallbackA, HandledE> => {
|
||||||
|
const TagClass = Context.Tag(id)() as ServiceResult<Self, Id, FallbackA, HandledE>
|
||||||
|
|
||||||
|
(TagClass as Mutable<typeof TagClass>).Live = Layer.effect(TagClass, Effect.gen(function*() {
|
||||||
const pubsub = yield* PubSub.unbounded<Cause.Cause<HandledE>>()
|
const pubsub = yield* PubSub.unbounded<Cause.Cause<HandledE>>()
|
||||||
const errors = Stream.fromPubSub(pubsub)
|
const errors = Stream.fromPubSub(pubsub)
|
||||||
|
|
||||||
@@ -25,16 +43,23 @@ export const make = <HandledE = never>() => (
|
|||||||
self: Effect.Effect<A, E, R>
|
self: Effect.Effect<A, E, R>
|
||||||
): Effect.Effect<A | FallbackA, Exclude<E, HandledE>, R> => f(
|
): Effect.Effect<A | FallbackA, Exclude<E, HandledE>, R> => f(
|
||||||
self as unknown as Effect.Effect<never, HandledE, never>,
|
self as unknown as Effect.Effect<never, HandledE, never>,
|
||||||
(failure: HandledE) => Effect.andThen(
|
(failure: HandledE) => PubSub.publish(pubsub, Cause.fail(failure)).pipe(
|
||||||
PubSub.publish(pubsub, Cause.fail(failure)),
|
Effect.andThen(Effect.failCause(Cause.empty))
|
||||||
Effect.failCause(Cause.empty),
|
|
||||||
),
|
),
|
||||||
(defect: unknown) => Effect.andThen(
|
(defect: unknown) => PubSub.publish(pubsub, Cause.die(defect)).pipe(
|
||||||
PubSub.publish(pubsub, Cause.die(defect)),
|
Effect.andThen(Effect.failCause(Cause.empty))
|
||||||
Effect.failCause(Cause.empty),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
return { errors, handle }
|
return { errors, handle }
|
||||||
})
|
}))
|
||||||
|
|
||||||
|
return TagClass
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
export class DefaultQueryErrorHandler extends Service<DefaultQueryErrorHandler>()(
|
||||||
|
"@reffuse/extension-query/DefaultQueryErrorHandler",
|
||||||
|
identity,
|
||||||
|
) {}
|
||||||
|
|||||||
@@ -1,21 +1,53 @@
|
|||||||
import type { Effect, Stream } from "effect"
|
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 ReffuseNamespace } from "reffuse"
|
import { ReffuseExtension, type ReffuseNamespace } from "reffuse"
|
||||||
import * as MutationRunner from "./MutationRunner.js"
|
import type * as MutationService from "./MutationService.js"
|
||||||
import * as QueryClient from "./QueryClient.js"
|
import * as QueryClient from "./QueryClient.js"
|
||||||
import type * as QueryProgress from "./QueryProgress.js"
|
import type * as QueryProgress from "./QueryProgress.js"
|
||||||
import * as QueryRunner from "./QueryRunner.js"
|
import type * as QueryService from "./QueryService.js"
|
||||||
|
import { MutationRunner, QueryRunner } from "./internal/index.js"
|
||||||
|
|
||||||
|
|
||||||
export interface UseQueryProps<K extends readonly unknown[], A, E, R> {
|
export interface UseQueryProps<K extends readonly unknown[], A, E, R> {
|
||||||
readonly key: Stream.Stream<K>
|
readonly key: Stream.Stream<K>
|
||||||
readonly query: (key: K) => Effect.Effect<A, E, R | QueryProgress.QueryProgress>
|
readonly query: (key: K) => Effect.Effect<A, E, R | QueryProgress.QueryProgress>
|
||||||
readonly options?: QueryRunner.RunOptions
|
readonly refreshOnWindowFocus?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UseQueryResult<K extends readonly unknown[], A, E> {
|
||||||
|
readonly latestKey: SubscriptionRef.SubscriptionRef<Option.Option<K>>
|
||||||
|
readonly state: SubscriptionRef.SubscriptionRef<AsyncData.AsyncData<A, E>>
|
||||||
|
|
||||||
|
readonly forkRefresh: Effect.Effect<readonly [
|
||||||
|
fiber: Fiber.RuntimeFiber<AsyncData.Success<A> | AsyncData.Failure<E>, Cause.NoSuchElementException>,
|
||||||
|
state: Stream.Stream<AsyncData.AsyncData<A, E>>,
|
||||||
|
]>
|
||||||
|
|
||||||
|
readonly layer: <Self, Id extends string>(
|
||||||
|
tag: Context.TagClass<Self, Id, QueryService.QueryService<K, A, E>>
|
||||||
|
) => Layer.Layer<Self>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface UseMutationProps<K extends readonly unknown[], A, E, R> {
|
export interface UseMutationProps<K extends readonly unknown[], A, E, R> {
|
||||||
readonly mutation: (key: K) => Effect.Effect<A, E, R | QueryProgress.QueryProgress>
|
readonly mutation: (key: K) => Effect.Effect<A, E, R | QueryProgress.QueryProgress>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UseMutationResult<K extends readonly unknown[], A, E> {
|
||||||
|
readonly state: SubscriptionRef.SubscriptionRef<AsyncData.AsyncData<A, E>>
|
||||||
|
|
||||||
|
readonly mutate: (...key: K) => Effect.Effect<AsyncData.Success<A> | AsyncData.Failure<E>>
|
||||||
|
readonly forkMutate: (...key: K) => Effect.Effect<readonly [
|
||||||
|
fiber: Fiber.RuntimeFiber<AsyncData.Success<A> | AsyncData.Failure<E>>,
|
||||||
|
state: Stream.Stream<AsyncData.AsyncData<A, E>>,
|
||||||
|
]>
|
||||||
|
|
||||||
|
readonly layer: <Self, Id extends string>(
|
||||||
|
tag: Context.TagClass<Self, Id, MutationService.MutationService<K, A, E>>
|
||||||
|
) => Layer.Layer<Self>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export const QueryExtension = ReffuseExtension.make(() => ({
|
export const QueryExtension = ReffuseExtension.make(() => ({
|
||||||
useQuery<
|
useQuery<
|
||||||
@@ -29,16 +61,32 @@ export const QueryExtension = ReffuseExtension.make(() => ({
|
|||||||
>(
|
>(
|
||||||
this: ReffuseNamespace.ReffuseNamespace<R | QueryClient.TagClassShape<FallbackA, HandledE>>,
|
this: ReffuseNamespace.ReffuseNamespace<R | QueryClient.TagClassShape<FallbackA, HandledE>>,
|
||||||
props: UseQueryProps<QK, QA, QE, QR>,
|
props: UseQueryProps<QK, QA, QE, QR>,
|
||||||
): QueryRunner.QueryRunner<QK, QA | FallbackA, Exclude<QE, HandledE>> {
|
): UseQueryResult<QK, QA | FallbackA, Exclude<QE, HandledE>> {
|
||||||
const runner = this.useMemo(() => QueryRunner.make({
|
const runner = this.useMemo(() => QueryRunner.make({
|
||||||
QueryClient: QueryClient.makeGenericTagClass<FallbackA, HandledE>(),
|
QueryClient: QueryClient.makeGenericTagClass<FallbackA, HandledE>(),
|
||||||
key: props.key,
|
key: props.key,
|
||||||
query: props.query,
|
query: props.query,
|
||||||
}), [props.key])
|
}), [props.key])
|
||||||
|
|
||||||
this.useFork(() => QueryRunner.run(runner, props.options), [runner])
|
this.useFork(() => runner.fetchOnKeyChange, [runner])
|
||||||
|
|
||||||
return runner
|
this.useFork(() => (props.refreshOnWindowFocus ?? true)
|
||||||
|
? runner.refreshOnWindowFocus
|
||||||
|
: Effect.void,
|
||||||
|
[props.refreshOnWindowFocus, runner])
|
||||||
|
|
||||||
|
return React.useMemo(() => ({
|
||||||
|
latestKey: runner.latestKeyRef,
|
||||||
|
state: runner.stateRef,
|
||||||
|
|
||||||
|
forkRefresh: runner.forkRefresh,
|
||||||
|
|
||||||
|
layer: tag => Layer.succeed(tag, {
|
||||||
|
latestKey: runner.latestKeyRef,
|
||||||
|
state: runner.stateRef,
|
||||||
|
forkRefresh: runner.forkRefresh,
|
||||||
|
}),
|
||||||
|
}), [runner])
|
||||||
},
|
},
|
||||||
|
|
||||||
useMutation<
|
useMutation<
|
||||||
@@ -52,10 +100,23 @@ export const QueryExtension = ReffuseExtension.make(() => ({
|
|||||||
>(
|
>(
|
||||||
this: ReffuseNamespace.ReffuseNamespace<R | QueryClient.TagClassShape<FallbackA, HandledE>>,
|
this: ReffuseNamespace.ReffuseNamespace<R | QueryClient.TagClassShape<FallbackA, HandledE>>,
|
||||||
props: UseMutationProps<QK, QA, QE, QR>,
|
props: UseMutationProps<QK, QA, QE, QR>,
|
||||||
): MutationRunner.MutationRunner<QK, QA | FallbackA, Exclude<QE, HandledE>> {
|
): UseMutationResult<QK, QA | FallbackA, Exclude<QE, HandledE>> {
|
||||||
return this.useMemo(() => MutationRunner.make({
|
const runner = this.useMemo(() => MutationRunner.make({
|
||||||
QueryClient: QueryClient.makeGenericTagClass<FallbackA, HandledE>(),
|
QueryClient: QueryClient.makeGenericTagClass<FallbackA, HandledE>(),
|
||||||
mutation: props.mutation,
|
mutation: props.mutation,
|
||||||
}), [])
|
}), [])
|
||||||
|
|
||||||
|
return React.useMemo(() => ({
|
||||||
|
state: runner.stateRef,
|
||||||
|
|
||||||
|
mutate: runner.mutate,
|
||||||
|
forkMutate: runner.forkMutate,
|
||||||
|
|
||||||
|
layer: tag => Layer.succeed(tag, {
|
||||||
|
state: runner.stateRef,
|
||||||
|
mutate: runner.mutate,
|
||||||
|
forkMutate: runner.forkMutate,
|
||||||
|
}),
|
||||||
|
}), [runner])
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export class QueryProgress extends Effect.Tag("@reffuse/extension-query/QueryPro
|
|||||||
f: (previous: Option.Option<AsyncData.Progress>) => AsyncData.Progress
|
f: (previous: Option.Option<AsyncData.Progress>) => AsyncData.Progress
|
||||||
) => Effect.Effect<void>
|
) => Effect.Effect<void>
|
||||||
}>() {
|
}>() {
|
||||||
static readonly Default: Layer.Layer<
|
static readonly Live: Layer.Layer<
|
||||||
QueryProgress,
|
QueryProgress,
|
||||||
never,
|
never,
|
||||||
QueryState.QueryState<any, any>
|
QueryState.QueryState<any, any>
|
||||||
|
|||||||
@@ -1,193 +0,0 @@
|
|||||||
import { BrowserStream } from "@effect/platform-browser"
|
|
||||||
import * as AsyncData from "@typed/async-data"
|
|
||||||
import { type Cause, Effect, Fiber, identity, Option, Queue, Ref, type Scope, Stream, SubscriptionRef } from "effect"
|
|
||||||
import type * as QueryClient from "./QueryClient.js"
|
|
||||||
import * as QueryProgress from "./QueryProgress.js"
|
|
||||||
import { QueryState } from "./internal/index.js"
|
|
||||||
|
|
||||||
|
|
||||||
export interface QueryRunner<K extends readonly unknown[], A, E> {
|
|
||||||
readonly queryKey: Stream.Stream<K>
|
|
||||||
readonly latestKeyValueRef: SubscriptionRef.SubscriptionRef<Option.Option<K>>
|
|
||||||
readonly stateRef: SubscriptionRef.SubscriptionRef<AsyncData.AsyncData<A, E>>
|
|
||||||
readonly fiberRef: SubscriptionRef.SubscriptionRef<Option.Option<Fiber.RuntimeFiber<
|
|
||||||
AsyncData.Success<A> | AsyncData.Failure<E>,
|
|
||||||
Cause.NoSuchElementException
|
|
||||||
>>>
|
|
||||||
|
|
||||||
readonly interrupt: Effect.Effect<void>
|
|
||||||
readonly forkInterrupt: Effect.Effect<Fiber.RuntimeFiber<void>>
|
|
||||||
readonly forkFetch: (keyValue: K) => Effect.Effect<readonly [
|
|
||||||
fiber: Fiber.RuntimeFiber<AsyncData.Success<A> | AsyncData.Failure<E>>,
|
|
||||||
state: Stream.Stream<AsyncData.AsyncData<A, E>>,
|
|
||||||
]>
|
|
||||||
readonly forkRefresh: Effect.Effect<readonly [
|
|
||||||
fiber: Fiber.RuntimeFiber<AsyncData.Success<A> | AsyncData.Failure<E>, Cause.NoSuchElementException>,
|
|
||||||
state: Stream.Stream<AsyncData.AsyncData<A, E>>,
|
|
||||||
]>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const Tag = <const Id extends string>(id: Id) => <
|
|
||||||
Self, K extends readonly unknown[], A, E = never
|
|
||||||
>() => Effect.Tag(id)<Self, QueryRunner<K, A, E>>()
|
|
||||||
|
|
||||||
|
|
||||||
export interface MakeProps<K extends readonly unknown[], A, FallbackA, E, HandledE, R> {
|
|
||||||
readonly QueryClient: QueryClient.GenericTagClass<FallbackA, HandledE>
|
|
||||||
readonly key: Stream.Stream<K>
|
|
||||||
readonly query: (key: K) => Effect.Effect<A, E, R | QueryProgress.QueryProgress>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const make = <K extends readonly unknown[], A, FallbackA, E, HandledE, R>(
|
|
||||||
{
|
|
||||||
QueryClient,
|
|
||||||
key,
|
|
||||||
query,
|
|
||||||
}: MakeProps<K, A, FallbackA, E, HandledE, R>
|
|
||||||
): Effect.Effect<
|
|
||||||
QueryRunner<K, A | FallbackA, Exclude<E, HandledE>>,
|
|
||||||
never,
|
|
||||||
R | QueryClient.TagClassShape<FallbackA, HandledE>
|
|
||||||
> => Effect.gen(function*() {
|
|
||||||
const context = yield* Effect.context<R | QueryClient.TagClassShape<FallbackA, HandledE>>()
|
|
||||||
|
|
||||||
const latestKeyValueRef = yield* SubscriptionRef.make(Option.none<K>())
|
|
||||||
const stateRef = yield* SubscriptionRef.make(AsyncData.noData<A | FallbackA, Exclude<E, HandledE>>())
|
|
||||||
const fiberRef = yield* SubscriptionRef.make(Option.none<Fiber.RuntimeFiber<
|
|
||||||
AsyncData.Success<A | FallbackA> | AsyncData.Failure<Exclude<E, HandledE>>,
|
|
||||||
Cause.NoSuchElementException
|
|
||||||
>>())
|
|
||||||
|
|
||||||
const queryStateTag = QueryState.makeTag<A | FallbackA, Exclude<E, HandledE>>()
|
|
||||||
|
|
||||||
const interrupt = Effect.flatMap(fiberRef, Option.match({
|
|
||||||
onSome: fiber => Ref.set(fiberRef, Option.none()).pipe(
|
|
||||||
Effect.andThen(Fiber.interrupt(fiber))
|
|
||||||
),
|
|
||||||
onNone: () => Effect.void,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const forkInterrupt = Effect.flatMap(fiberRef, 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 run = (keyValue: K) => Effect.all([QueryClient, queryStateTag]).pipe(
|
|
||||||
Effect.flatMap(([client, state]) => Ref.set(latestKeyValueRef, Option.some(keyValue)).pipe(
|
|
||||||
Effect.andThen(query(keyValue)),
|
|
||||||
client.errorHandler.handle,
|
|
||||||
Effect.matchCauseEffect({
|
|
||||||
onSuccess: v => Effect.tap(Effect.succeed(AsyncData.success(v)), state.set),
|
|
||||||
onFailure: c => Effect.tap(Effect.succeed(AsyncData.failure(c)), state.set),
|
|
||||||
}),
|
|
||||||
)),
|
|
||||||
|
|
||||||
Effect.provide(context),
|
|
||||||
Effect.provide(QueryProgress.QueryProgress.Default),
|
|
||||||
)
|
|
||||||
|
|
||||||
const forkFetch = (keyValue: K) => Queue.unbounded<AsyncData.AsyncData<A | FallbackA, Exclude<E, HandledE>>>().pipe(
|
|
||||||
Effect.flatMap(stateQueue => queryStateTag.pipe(
|
|
||||||
Effect.flatMap(state => interrupt.pipe(
|
|
||||||
Effect.andThen(
|
|
||||||
Effect.addFinalizer(() => Effect.andThen(
|
|
||||||
Ref.set(fiberRef, Option.none()),
|
|
||||||
Queue.shutdown(stateQueue),
|
|
||||||
)).pipe(
|
|
||||||
Effect.andThen(state.set(AsyncData.loading())),
|
|
||||||
Effect.andThen(run(keyValue)),
|
|
||||||
Effect.scoped,
|
|
||||||
Effect.forkDaemon,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
Effect.tap(fiber => Ref.set(fiberRef, Option.some(fiber))),
|
|
||||||
Effect.map(fiber => [fiber, Stream.fromQueue(stateQueue)] as const),
|
|
||||||
)),
|
|
||||||
|
|
||||||
Effect.provide(QueryState.layer(
|
|
||||||
queryStateTag,
|
|
||||||
stateRef,
|
|
||||||
value => Effect.andThen(
|
|
||||||
Queue.offer(stateQueue, value),
|
|
||||||
Ref.set(stateRef, value),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
))
|
|
||||||
)
|
|
||||||
|
|
||||||
const setInitialRefreshState = Effect.flatMap(queryStateTag, state => state.update(previous => {
|
|
||||||
if (AsyncData.isSuccess(previous) || AsyncData.isFailure(previous))
|
|
||||||
return AsyncData.refreshing(previous)
|
|
||||||
if (AsyncData.isRefreshing(previous))
|
|
||||||
return AsyncData.refreshing(previous.previous)
|
|
||||||
return AsyncData.loading()
|
|
||||||
}))
|
|
||||||
|
|
||||||
const forkRefresh = Queue.unbounded<AsyncData.AsyncData<A | FallbackA, Exclude<E, HandledE>>>().pipe(
|
|
||||||
Effect.flatMap(stateQueue => interrupt.pipe(
|
|
||||||
Effect.andThen(
|
|
||||||
Effect.addFinalizer(() => Effect.andThen(
|
|
||||||
Ref.set(fiberRef, Option.none()),
|
|
||||||
Queue.shutdown(stateQueue),
|
|
||||||
)).pipe(
|
|
||||||
Effect.andThen(setInitialRefreshState),
|
|
||||||
Effect.andThen(latestKeyValueRef.pipe(
|
|
||||||
Effect.flatMap(identity),
|
|
||||||
Effect.flatMap(run),
|
|
||||||
)),
|
|
||||||
Effect.scoped,
|
|
||||||
Effect.forkDaemon,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
|
|
||||||
Effect.tap(fiber => Ref.set(fiberRef, Option.some(fiber))),
|
|
||||||
Effect.map(fiber => [fiber, Stream.fromQueue(stateQueue)] as const),
|
|
||||||
|
|
||||||
Effect.provide(QueryState.layer(
|
|
||||||
queryStateTag,
|
|
||||||
stateRef,
|
|
||||||
value => Effect.andThen(
|
|
||||||
Queue.offer(stateQueue, value),
|
|
||||||
Ref.set(stateRef, value),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
))
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
queryKey: key,
|
|
||||||
latestKeyValueRef,
|
|
||||||
stateRef,
|
|
||||||
fiberRef,
|
|
||||||
|
|
||||||
interrupt,
|
|
||||||
forkInterrupt,
|
|
||||||
forkFetch,
|
|
||||||
forkRefresh,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
export interface RunOptions {
|
|
||||||
readonly refreshOnWindowFocus?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const run = <K extends readonly unknown[], A, E>(
|
|
||||||
self: QueryRunner<K, A, E>,
|
|
||||||
options?: RunOptions,
|
|
||||||
): Effect.Effect<void, never, Scope.Scope> => Effect.gen(function*() {
|
|
||||||
if (typeof window !== "undefined" && (options?.refreshOnWindowFocus ?? true))
|
|
||||||
yield* Effect.forkScoped(
|
|
||||||
Stream.runForEach(BrowserStream.fromEventListenerWindow("focus"), () => self.forkRefresh)
|
|
||||||
)
|
|
||||||
|
|
||||||
yield* Effect.addFinalizer(() => self.interrupt)
|
|
||||||
yield* Stream.runForEach(Stream.changes(self.queryKey), latestKey => self.forkFetch(latestKey))
|
|
||||||
})
|
|
||||||
16
packages/extension-query/src/QueryService.ts
Normal file
16
packages/extension-query/src/QueryService.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import type * as AsyncData from "@typed/async-data"
|
||||||
|
import { type Cause, Effect, type Fiber, type Option, type Stream, type SubscriptionRef } from "effect"
|
||||||
|
|
||||||
|
|
||||||
|
export interface QueryService<K extends readonly unknown[], A, E> {
|
||||||
|
readonly latestKey: SubscriptionRef.SubscriptionRef<Option.Option<K>>
|
||||||
|
readonly state: SubscriptionRef.SubscriptionRef<AsyncData.AsyncData<A, E>>
|
||||||
|
readonly forkRefresh: Effect.Effect<readonly [
|
||||||
|
fiber: Fiber.RuntimeFiber<AsyncData.Success<A> | AsyncData.Failure<E>, Cause.NoSuchElementException>,
|
||||||
|
state: Stream.Stream<AsyncData.AsyncData<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>>()
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
export * as MutationRunner from "./MutationRunner.js"
|
export * as MutationService from "./MutationService.js"
|
||||||
export * as QueryClient from "./QueryClient.js"
|
export * as QueryClient from "./QueryClient.js"
|
||||||
export * as QueryErrorHandler from "./QueryErrorHandler.js"
|
export * as QueryErrorHandler from "./QueryErrorHandler.js"
|
||||||
export * from "./QueryExtension.js"
|
export * from "./QueryExtension.js"
|
||||||
export * as QueryProgress from "./QueryProgress.js"
|
export * as QueryProgress from "./QueryProgress.js"
|
||||||
export * as QueryRunner from "./QueryRunner.js"
|
export * as QueryService from "./QueryService.js"
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import * as AsyncData from "@typed/async-data"
|
import * as AsyncData from "@typed/async-data"
|
||||||
import { Effect, type Fiber, Queue, Ref, Stream, SubscriptionRef } from "effect"
|
import { type Context, Effect, type Fiber, Queue, Ref, Stream, SubscriptionRef } from "effect"
|
||||||
import type * as QueryClient from "./QueryClient.js"
|
import type * as QueryClient from "../QueryClient.js"
|
||||||
import * as QueryProgress from "./QueryProgress.js"
|
import * as QueryProgress from "../QueryProgress.js"
|
||||||
import { QueryState } from "./internal/index.js"
|
import * as QueryState from "./QueryState.js"
|
||||||
|
|
||||||
|
|
||||||
export interface MutationRunner<K extends readonly unknown[], A, E> {
|
export interface MutationRunner<K extends readonly unknown[], A, E, R> {
|
||||||
|
readonly context: Context.Context<R>
|
||||||
readonly stateRef: SubscriptionRef.SubscriptionRef<AsyncData.AsyncData<A, E>>
|
readonly stateRef: SubscriptionRef.SubscriptionRef<AsyncData.AsyncData<A, E>>
|
||||||
|
|
||||||
readonly mutate: (...key: K) => Effect.Effect<AsyncData.Success<A> | AsyncData.Failure<E>>
|
readonly mutate: (...key: K) => Effect.Effect<AsyncData.Success<A> | AsyncData.Failure<E>>
|
||||||
@@ -16,11 +17,6 @@ export interface MutationRunner<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, MutationRunner<K, A, E>>()
|
|
||||||
|
|
||||||
|
|
||||||
export interface MakeProps<K extends readonly unknown[], A, FallbackA, E, HandledE, R> {
|
export interface MakeProps<K extends readonly unknown[], A, FallbackA, E, HandledE, R> {
|
||||||
readonly QueryClient: QueryClient.GenericTagClass<FallbackA, HandledE>
|
readonly QueryClient: QueryClient.GenericTagClass<FallbackA, HandledE>
|
||||||
readonly mutation: (key: K) => Effect.Effect<A, E, R | QueryProgress.QueryProgress>
|
readonly mutation: (key: K) => Effect.Effect<A, E, R | QueryProgress.QueryProgress>
|
||||||
@@ -32,7 +28,7 @@ export const make = <K extends readonly unknown[], A, FallbackA, E, HandledE, R>
|
|||||||
mutation,
|
mutation,
|
||||||
}: MakeProps<K, A, FallbackA, E, HandledE, R>
|
}: MakeProps<K, A, FallbackA, E, HandledE, R>
|
||||||
): Effect.Effect<
|
): Effect.Effect<
|
||||||
MutationRunner<K, A | FallbackA, Exclude<E, HandledE>>,
|
MutationRunner<K, A | FallbackA, Exclude<E, HandledE>, R>,
|
||||||
never,
|
never,
|
||||||
R | QueryClient.TagClassShape<FallbackA, HandledE>
|
R | QueryClient.TagClassShape<FallbackA, HandledE>
|
||||||
> => Effect.gen(function*() {
|
> => Effect.gen(function*() {
|
||||||
@@ -41,18 +37,25 @@ export const make = <K extends readonly unknown[], A, FallbackA, E, HandledE, R>
|
|||||||
|
|
||||||
const queryStateTag = QueryState.makeTag<A | FallbackA, Exclude<E, HandledE>>()
|
const queryStateTag = QueryState.makeTag<A | FallbackA, Exclude<E, HandledE>>()
|
||||||
|
|
||||||
const run = (key: K) => Effect.all([QueryClient, queryStateTag]).pipe(
|
const run = (key: K) => Effect.Do.pipe(
|
||||||
Effect.flatMap(([client, state]) => state.set(AsyncData.loading()).pipe(
|
Effect.bind("state", () => queryStateTag),
|
||||||
|
Effect.bind("client", () => QueryClient),
|
||||||
|
|
||||||
|
Effect.flatMap(({ state, client }) => state.set(AsyncData.loading()).pipe(
|
||||||
Effect.andThen(mutation(key)),
|
Effect.andThen(mutation(key)),
|
||||||
client.errorHandler.handle,
|
client.errorHandler.handle,
|
||||||
Effect.matchCauseEffect({
|
Effect.matchCauseEffect({
|
||||||
onSuccess: v => Effect.tap(Effect.succeed(AsyncData.success(v)), state.set),
|
onSuccess: v => Effect.succeed(AsyncData.success(v)).pipe(
|
||||||
onFailure: c => Effect.tap(Effect.succeed(AsyncData.failure(c)), state.set),
|
Effect.tap(state.set)
|
||||||
|
),
|
||||||
|
onFailure: c => Effect.succeed(AsyncData.failure(c)).pipe(
|
||||||
|
Effect.tap(state.set)
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
)),
|
)),
|
||||||
|
|
||||||
Effect.provide(context),
|
Effect.provide(context),
|
||||||
Effect.provide(QueryProgress.QueryProgress.Default),
|
Effect.provide(QueryProgress.QueryProgress.Live),
|
||||||
)
|
)
|
||||||
|
|
||||||
const mutate = (...key: K) => Effect.provide(run(key), QueryState.layer(
|
const mutate = (...key: K) => Effect.provide(run(key), QueryState.layer(
|
||||||
@@ -61,11 +64,11 @@ export const make = <K extends readonly unknown[], A, FallbackA, E, HandledE, R>
|
|||||||
value => Ref.set(globalStateRef, value),
|
value => Ref.set(globalStateRef, value),
|
||||||
))
|
))
|
||||||
|
|
||||||
const forkMutate = (...key: K) => Effect.all([
|
const forkMutate = (...key: K) => Effect.Do.pipe(
|
||||||
Ref.make(AsyncData.noData<A | FallbackA, Exclude<E, HandledE>>()),
|
Effect.bind("stateRef", () => Ref.make(AsyncData.noData<A | FallbackA, Exclude<E, HandledE>>())),
|
||||||
Queue.unbounded<AsyncData.AsyncData<A | FallbackA, Exclude<E, HandledE>>>(),
|
Effect.bind("stateQueue", () => Queue.unbounded<AsyncData.AsyncData<A | FallbackA, Exclude<E, HandledE>>>()),
|
||||||
]).pipe(
|
|
||||||
Effect.flatMap(([stateRef, stateQueue]) =>
|
Effect.flatMap(({ stateRef, stateQueue }) =>
|
||||||
Effect.addFinalizer(() => Queue.shutdown(stateQueue)).pipe(
|
Effect.addFinalizer(() => Queue.shutdown(stateQueue)).pipe(
|
||||||
Effect.andThen(run(key)),
|
Effect.andThen(run(key)),
|
||||||
Effect.scoped,
|
Effect.scoped,
|
||||||
191
packages/extension-query/src/internal/QueryRunner.ts
Normal file
191
packages/extension-query/src/internal/QueryRunner.ts
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import { BrowserStream } from "@effect/platform-browser"
|
||||||
|
import * as AsyncData from "@typed/async-data"
|
||||||
|
import { type Cause, type Context, Effect, Fiber, identity, Option, Queue, Ref, type Scope, Stream, SubscriptionRef } from "effect"
|
||||||
|
import type * as QueryClient from "../QueryClient.js"
|
||||||
|
import * as QueryProgress from "../QueryProgress.js"
|
||||||
|
import * as QueryState from "./QueryState.js"
|
||||||
|
|
||||||
|
|
||||||
|
export interface QueryRunner<K extends readonly unknown[], A, E, R> {
|
||||||
|
readonly context: Context.Context<R>
|
||||||
|
|
||||||
|
readonly latestKeyRef: SubscriptionRef.SubscriptionRef<Option.Option<K>>
|
||||||
|
readonly stateRef: SubscriptionRef.SubscriptionRef<AsyncData.AsyncData<A, E>>
|
||||||
|
readonly fiberRef: SubscriptionRef.SubscriptionRef<Option.Option<Fiber.RuntimeFiber<
|
||||||
|
AsyncData.Success<A> | AsyncData.Failure<E>,
|
||||||
|
Cause.NoSuchElementException
|
||||||
|
>>>
|
||||||
|
|
||||||
|
readonly forkInterrupt: Effect.Effect<Fiber.RuntimeFiber<void, Cause.NoSuchElementException>>
|
||||||
|
readonly forkFetch: Effect.Effect<readonly [
|
||||||
|
fiber: Fiber.RuntimeFiber<AsyncData.Success<A> | AsyncData.Failure<E>, Cause.NoSuchElementException>,
|
||||||
|
state: Stream.Stream<AsyncData.AsyncData<A, E>>,
|
||||||
|
]>
|
||||||
|
readonly forkRefresh: Effect.Effect<readonly [
|
||||||
|
fiber: Fiber.RuntimeFiber<AsyncData.Success<A> | AsyncData.Failure<E>, Cause.NoSuchElementException>,
|
||||||
|
state: Stream.Stream<AsyncData.AsyncData<A, E>>,
|
||||||
|
]>
|
||||||
|
|
||||||
|
readonly fetchOnKeyChange: Effect.Effect<void, Cause.NoSuchElementException, Scope.Scope>
|
||||||
|
readonly refreshOnWindowFocus: Effect.Effect<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface MakeProps<K extends readonly unknown[], A, FallbackA, E, HandledE, R> {
|
||||||
|
readonly QueryClient: QueryClient.GenericTagClass<FallbackA, HandledE>
|
||||||
|
readonly key: Stream.Stream<K>
|
||||||
|
readonly query: (key: K) => Effect.Effect<A, E, R | QueryProgress.QueryProgress>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const make = <K extends readonly unknown[], A, FallbackA, E, HandledE, R>(
|
||||||
|
{
|
||||||
|
QueryClient,
|
||||||
|
key,
|
||||||
|
query,
|
||||||
|
}: MakeProps<K, A, FallbackA, E, HandledE, R>
|
||||||
|
): Effect.Effect<
|
||||||
|
QueryRunner<K, A | FallbackA, Exclude<E, HandledE>, R>,
|
||||||
|
never,
|
||||||
|
R | QueryClient.TagClassShape<FallbackA, HandledE>
|
||||||
|
> => Effect.gen(function*() {
|
||||||
|
const context = yield* Effect.context<R | QueryClient.TagClassShape<FallbackA, HandledE>>()
|
||||||
|
|
||||||
|
const latestKeyRef = yield* SubscriptionRef.make(Option.none<K>())
|
||||||
|
const stateRef = yield* SubscriptionRef.make(AsyncData.noData<A | FallbackA, Exclude<E, HandledE>>())
|
||||||
|
const fiberRef = yield* SubscriptionRef.make(Option.none<Fiber.RuntimeFiber<
|
||||||
|
AsyncData.Success<A | FallbackA> | AsyncData.Failure<Exclude<E, HandledE>>,
|
||||||
|
Cause.NoSuchElementException
|
||||||
|
>>())
|
||||||
|
|
||||||
|
const queryStateTag = QueryState.makeTag<A | FallbackA, Exclude<E, HandledE>>()
|
||||||
|
|
||||||
|
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 run = Effect.Do.pipe(
|
||||||
|
Effect.bind("state", () => queryStateTag),
|
||||||
|
Effect.bind("client", () => QueryClient),
|
||||||
|
Effect.bind("latestKey", () => latestKeyRef.pipe(Effect.flatMap(identity))),
|
||||||
|
|
||||||
|
Effect.flatMap(({ state, client, latestKey }) => query(latestKey).pipe(
|
||||||
|
client.errorHandler.handle,
|
||||||
|
Effect.matchCauseEffect({
|
||||||
|
onSuccess: v => Effect.succeed(AsyncData.success(v)).pipe(
|
||||||
|
Effect.tap(state.set)
|
||||||
|
),
|
||||||
|
onFailure: c => Effect.succeed(AsyncData.failure(c)).pipe(
|
||||||
|
Effect.tap(state.set)
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
|
||||||
|
Effect.provide(context),
|
||||||
|
Effect.provide(QueryProgress.QueryProgress.Live),
|
||||||
|
)
|
||||||
|
|
||||||
|
const forkFetch = Queue.unbounded<AsyncData.AsyncData<A | FallbackA, Exclude<E, HandledE>>>().pipe(
|
||||||
|
Effect.flatMap(stateQueue => queryStateTag.pipe(
|
||||||
|
Effect.flatMap(state => interrupt.pipe(
|
||||||
|
Effect.andThen(Effect.addFinalizer(() => Ref.set(fiberRef, Option.none()).pipe(
|
||||||
|
Effect.andThen(Queue.shutdown(stateQueue))
|
||||||
|
)).pipe(
|
||||||
|
Effect.andThen(state.set(AsyncData.loading())),
|
||||||
|
Effect.andThen(run),
|
||||||
|
Effect.scoped,
|
||||||
|
Effect.forkDaemon,
|
||||||
|
)),
|
||||||
|
|
||||||
|
Effect.tap(fiber => Ref.set(fiberRef, Option.some(fiber))),
|
||||||
|
Effect.map(fiber => [fiber, Stream.fromQueue(stateQueue)] as const),
|
||||||
|
)),
|
||||||
|
|
||||||
|
Effect.provide(QueryState.layer(
|
||||||
|
queryStateTag,
|
||||||
|
stateRef,
|
||||||
|
value => Queue.offer(stateQueue, value).pipe(
|
||||||
|
Effect.andThen(Ref.set(stateRef, value))
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
const setInitialRefreshState = queryStateTag.pipe(
|
||||||
|
Effect.flatMap(state => state.update(previous => {
|
||||||
|
if (AsyncData.isSuccess(previous) || AsyncData.isFailure(previous))
|
||||||
|
return AsyncData.refreshing(previous)
|
||||||
|
if (AsyncData.isRefreshing(previous))
|
||||||
|
return AsyncData.refreshing(previous.previous)
|
||||||
|
return AsyncData.loading()
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
const forkRefresh = Queue.unbounded<AsyncData.AsyncData<A | FallbackA, Exclude<E, HandledE>>>().pipe(
|
||||||
|
Effect.flatMap(stateQueue => interrupt.pipe(
|
||||||
|
Effect.andThen(Effect.addFinalizer(() => Ref.set(fiberRef, Option.none()).pipe(
|
||||||
|
Effect.andThen(Queue.shutdown(stateQueue))
|
||||||
|
)).pipe(
|
||||||
|
Effect.andThen(setInitialRefreshState),
|
||||||
|
Effect.andThen(run),
|
||||||
|
Effect.scoped,
|
||||||
|
Effect.forkDaemon,
|
||||||
|
)),
|
||||||
|
|
||||||
|
Effect.tap(fiber => Ref.set(fiberRef, Option.some(fiber))),
|
||||||
|
Effect.map(fiber => [fiber, Stream.fromQueue(stateQueue)] as const),
|
||||||
|
|
||||||
|
Effect.provide(QueryState.layer(
|
||||||
|
queryStateTag,
|
||||||
|
stateRef,
|
||||||
|
value => Queue.offer(stateQueue, value).pipe(
|
||||||
|
Effect.andThen(Ref.set(stateRef, value))
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
const fetchOnKeyChange = Effect.addFinalizer(() => interrupt).pipe(
|
||||||
|
Effect.andThen(Stream.runForEach(Stream.changes(key), latestKey =>
|
||||||
|
Ref.set(latestKeyRef, Option.some(latestKey)).pipe(
|
||||||
|
Effect.andThen(forkFetch)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
)
|
||||||
|
|
||||||
|
const refreshOnWindowFocus = Stream.runForEach(
|
||||||
|
BrowserStream.fromEventListenerWindow("focus"),
|
||||||
|
() => forkRefresh,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
context,
|
||||||
|
|
||||||
|
latestKeyRef,
|
||||||
|
stateRef,
|
||||||
|
fiberRef,
|
||||||
|
|
||||||
|
forkInterrupt,
|
||||||
|
forkFetch,
|
||||||
|
forkRefresh,
|
||||||
|
|
||||||
|
fetchOnKeyChange,
|
||||||
|
refreshOnWindowFocus,
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1 +1,3 @@
|
|||||||
|
export * as MutationRunner from "./MutationRunner.js"
|
||||||
|
export * as QueryRunner from "./QueryRunner.js"
|
||||||
export * as QueryState from "./QueryState.js"
|
export * as QueryState from "./QueryState.js"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "reffuse",
|
"name": "reffuse",
|
||||||
"version": "0.1.13",
|
"version": "0.1.11",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"./README.md",
|
"./README.md",
|
||||||
|
|||||||
@@ -1,29 +1,24 @@
|
|||||||
import { Array, Function, Option, Predicate } from "effect"
|
import { Array, Function, Option, Predicate } from "effect"
|
||||||
|
|
||||||
|
|
||||||
type Prev = readonly [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
export type Paths<T> = [] | (
|
||||||
|
T extends readonly any[] ? ArrayPaths<T> :
|
||||||
export type Paths<T, D extends number = 5, Seen = never> = [] | (
|
T extends object ? ObjectPaths<T> :
|
||||||
D extends never ? [] :
|
|
||||||
T extends Seen ? [] :
|
|
||||||
T extends readonly any[] ? ArrayPaths<T, D, Seen | T> :
|
|
||||||
T extends object ? ObjectPaths<T, D, Seen | T> :
|
|
||||||
never
|
never
|
||||||
)
|
)
|
||||||
|
|
||||||
export type ArrayPaths<T extends readonly any[], D extends number, Seen> = {
|
export type ArrayPaths<T extends readonly any[]> = {
|
||||||
[K in keyof T as K extends number ? K : never]:
|
[K in keyof T as K extends number ? K : never]:
|
||||||
| [K]
|
| [K]
|
||||||
| [K, ...Paths<T[K], Prev[D], Seen>]
|
| [K, ...Paths<T[K]>]
|
||||||
} extends infer O
|
} extends infer O
|
||||||
? O[keyof O]
|
? O[keyof O]
|
||||||
: never
|
: never
|
||||||
|
|
||||||
export type ObjectPaths<T extends object, D extends number, Seen> = {
|
export type ObjectPaths<T extends object> = {
|
||||||
[K in keyof T as K extends string | number | symbol ? K : never]-?:
|
[K in keyof T as K extends string | number | symbol ? K : never]:
|
||||||
NonNullable<T[K]> extends infer V
|
| [K]
|
||||||
? [K] | [K, ...Paths<V, Prev[D], Seen>]
|
| [K, ...Paths<T[K]>]
|
||||||
: never
|
|
||||||
} extends infer O
|
} extends infer O
|
||||||
? O[keyof O]
|
? O[keyof O]
|
||||||
: never
|
: never
|
||||||
|
|||||||
Reference in New Issue
Block a user