0.1.3 (#5)
All checks were successful
Publish / publish (push) Successful in 24s
Lint / lint (push) Successful in 16s

Co-authored-by: Julien Valverdé <julien.valverde@mailo.com>
Reviewed-on: https://gitea:3000/Thilawyn/reffuse/pulls/5
This commit was merged in pull request #5.
This commit is contained in:
Julien Valverdé
2025-03-11 01:44:37 +01:00
parent 2aa0c64a7c
commit a7b5a32071
28 changed files with 707 additions and 109 deletions

View File

@@ -0,0 +1,10 @@
import { GlobalReffuse } from "@/reffuse"
import { Reffuse, ReffuseContext } from "reffuse"
import { Uuid4Query } from "./services"
export const QueryContext = ReffuseContext.make<Uuid4Query.Uuid4Query>()
export const R = new class QueryReffuse extends GlobalReffuse.pipe(
Reffuse.withContexts(QueryContext)
) {}

View File

@@ -0,0 +1,12 @@
import { HttpClientError } from "@effect/platform"
import { QueryService } from "@reffuse/extension-query"
import { ParseResult, Schema } from "effect"
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
>() {}

View File

@@ -0,0 +1 @@
export * as Uuid4Query from "./Uuid4Query"

View File

@@ -0,0 +1,32 @@
import { Button, Container, Flex, Text } from "@radix-ui/themes"
import * as AsyncData from "@typed/async-data"
import { R } from "../reffuse"
import { Uuid4Query } from "../services"
export function Uuid4QueryService() {
const runSync = R.useRunSync()
const query = R.useMemo(() => Uuid4Query.Uuid4Query, [])
const [state] = R.useRefState(query.state)
return (
<Container>
<Flex direction="column" align="center" gap="2">
<Text>
{AsyncData.match(state, {
NoData: () => "No data yet",
Loading: () => "Loading...",
Success: (value, { isRefreshing, isOptimistic }) =>
`Value: ${value} ${isRefreshing ? "(refreshing)" : ""} ${isOptimistic ? "(optimistic)" : ""}`,
Failure: (cause, { isRefreshing }) =>
`Error: ${cause} ${isRefreshing ? "(refreshing)" : ""}`,
})}
</Text>
<Button onClick={() => runSync(query.refresh)}>Refresh</Button>
</Flex>
</Container>
)
}

View File

@@ -1,6 +1,7 @@
import { HttpClient } from "@effect/platform"
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"
@@ -13,6 +14,7 @@ export const GlobalContext = ReffuseContext.make<
export class GlobalReffuse extends Reffuse.Reffuse.pipe(
Reffuse.withExtension(LazyRefExtension),
Reffuse.withExtension(QueryExtension),
Reffuse.withContexts(GlobalContext),
) {}

View File

@@ -18,6 +18,8 @@ import { Route as LazyrefImport } from './routes/lazyref'
import { Route as CountImport } from './routes/count'
import { Route as BlankImport } from './routes/blank'
import { Route as IndexImport } from './routes/index'
import { Route as QueryUsequeryImport } from './routes/query/usequery'
import { Route as QueryServiceImport } from './routes/query/service'
// Create/Update Routes
@@ -63,6 +65,18 @@ const IndexRoute = IndexImport.update({
getParentRoute: () => rootRoute,
} as any)
const QueryUsequeryRoute = QueryUsequeryImport.update({
id: '/query/usequery',
path: '/query/usequery',
getParentRoute: () => rootRoute,
} as any)
const QueryServiceRoute = QueryServiceImport.update({
id: '/query/service',
path: '/query/service',
getParentRoute: () => rootRoute,
} as any)
// Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' {
@@ -116,6 +130,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof TimeImport
parentRoute: typeof rootRoute
}
'/query/service': {
id: '/query/service'
path: '/query/service'
fullPath: '/query/service'
preLoaderRoute: typeof QueryServiceImport
parentRoute: typeof rootRoute
}
'/query/usequery': {
id: '/query/usequery'
path: '/query/usequery'
fullPath: '/query/usequery'
preLoaderRoute: typeof QueryUsequeryImport
parentRoute: typeof rootRoute
}
}
}
@@ -129,6 +157,8 @@ export interface FileRoutesByFullPath {
'/promise': typeof PromiseRoute
'/tests': typeof TestsRoute
'/time': typeof TimeRoute
'/query/service': typeof QueryServiceRoute
'/query/usequery': typeof QueryUsequeryRoute
}
export interface FileRoutesByTo {
@@ -139,6 +169,8 @@ export interface FileRoutesByTo {
'/promise': typeof PromiseRoute
'/tests': typeof TestsRoute
'/time': typeof TimeRoute
'/query/service': typeof QueryServiceRoute
'/query/usequery': typeof QueryUsequeryRoute
}
export interface FileRoutesById {
@@ -150,6 +182,8 @@ export interface FileRoutesById {
'/promise': typeof PromiseRoute
'/tests': typeof TestsRoute
'/time': typeof TimeRoute
'/query/service': typeof QueryServiceRoute
'/query/usequery': typeof QueryUsequeryRoute
}
export interface FileRouteTypes {
@@ -162,8 +196,19 @@ export interface FileRouteTypes {
| '/promise'
| '/tests'
| '/time'
| '/query/service'
| '/query/usequery'
fileRoutesByTo: FileRoutesByTo
to: '/' | '/blank' | '/count' | '/lazyref' | '/promise' | '/tests' | '/time'
to:
| '/'
| '/blank'
| '/count'
| '/lazyref'
| '/promise'
| '/tests'
| '/time'
| '/query/service'
| '/query/usequery'
id:
| '__root__'
| '/'
@@ -173,6 +218,8 @@ export interface FileRouteTypes {
| '/promise'
| '/tests'
| '/time'
| '/query/service'
| '/query/usequery'
fileRoutesById: FileRoutesById
}
@@ -184,6 +231,8 @@ export interface RootRouteChildren {
PromiseRoute: typeof PromiseRoute
TestsRoute: typeof TestsRoute
TimeRoute: typeof TimeRoute
QueryServiceRoute: typeof QueryServiceRoute
QueryUsequeryRoute: typeof QueryUsequeryRoute
}
const rootRouteChildren: RootRouteChildren = {
@@ -194,6 +243,8 @@ const rootRouteChildren: RootRouteChildren = {
PromiseRoute: PromiseRoute,
TestsRoute: TestsRoute,
TimeRoute: TimeRoute,
QueryServiceRoute: QueryServiceRoute,
QueryUsequeryRoute: QueryUsequeryRoute,
}
export const routeTree = rootRoute
@@ -212,7 +263,9 @@ export const routeTree = rootRoute
"/lazyref",
"/promise",
"/tests",
"/time"
"/time",
"/query/service",
"/query/usequery"
]
},
"/": {
@@ -235,6 +288,12 @@ export const routeTree = rootRoute
},
"/time": {
"filePath": "time.tsx"
},
"/query/service": {
"filePath": "query/service.tsx"
},
"/query/usequery": {
"filePath": "query/usequery.tsx"
}
}
}

View File

@@ -20,6 +20,7 @@ function Root() {
<Link to="/count">Count</Link>
<Link to="/tests">Tests</Link>
<Link to="/promise">Promise</Link>
<Link to="/query/usequery">Query</Link>
<Link to="/blank">Blank</Link>
</Flex>
</Container>

View File

@@ -0,0 +1,35 @@
import { QueryContext } from "@/query/reffuse"
import { Uuid4Query } from "@/query/services"
import { Uuid4QueryService } from "@/query/views/Uuid4QueryService"
import { R } from "@/reffuse"
import { HttpClient } from "@effect/platform"
import { createFileRoute } from "@tanstack/react-router"
import { Console, Effect, Schema } from "effect"
import { useMemo } from "react"
export const Route = createFileRoute("/query/service")({
component: RouteComponent
})
function RouteComponent() {
const query = R.useQuery({
key: R.useStreamFromValues(["uuid4", 10 as number]),
query: ([, count]) => Console.log(`Querying ${ count } IDs...`).pipe(
Effect.andThen(Effect.sleep("500 millis")),
Effect.andThen(HttpClient.get(`https://www.uuidtools.com/api/generate/v4/count/${ count }`)),
HttpClient.withTracerPropagation(false),
Effect.flatMap(res => res.json),
Effect.flatMap(Schema.decodeUnknown(Uuid4Query.Result)),
Effect.scoped,
),
})
const layer = useMemo(() => query.layer(Uuid4Query.Uuid4Query), [query])
return (
<QueryContext.Provider layer={layer}>
<Uuid4QueryService />
</QueryContext.Provider>
)
}

View File

@@ -0,0 +1,66 @@
import { R } from "@/reffuse"
import { HttpClient } from "@effect/platform"
import { Button, Container, Flex, Slider, Text } from "@radix-ui/themes"
import { createFileRoute } from "@tanstack/react-router"
import * as AsyncData from "@typed/async-data"
import { Array, Console, Effect, flow, Option, Schema } from "effect"
import { useState } from "react"
export const Route = createFileRoute("/query/usequery")({
component: RouteComponent
})
const Result = Schema.Array(Schema.String)
function RouteComponent() {
const runSync = R.useRunSync()
const [count, setCount] = useState(1)
const query = R.useQuery({
key: R.useStreamFromValues(["uuid4", count]),
query: ([, count]) => Console.log(`Querying ${ count } IDs...`).pipe(
Effect.andThen(Effect.sleep("500 millis")),
Effect.andThen(HttpClient.get(`https://www.uuidtools.com/api/generate/v4/count/${ count }`)),
HttpClient.withTracerPropagation(false),
Effect.flatMap(res => res.json),
Effect.flatMap(Schema.decodeUnknown(Result)),
Effect.scoped,
),
})
const [state] = R.useRefState(query.state)
return (
<Container>
<Flex direction="column" align="center" gap="2">
<Slider
min={1}
max={100}
value={[count]}
onValueChange={flow(
Array.head,
Option.getOrThrow,
setCount,
)}
/>
<Text>
{AsyncData.match(state, {
NoData: () => "No data yet",
Loading: () => "Loading...",
Success: (value, { isRefreshing, isOptimistic }) =>
`Value: ${value} ${isRefreshing ? "(refreshing)" : ""} ${isOptimistic ? "(optimistic)" : ""}`,
Failure: (cause, { isRefreshing }) =>
`Error: ${cause} ${isRefreshing ? "(refreshing)" : ""}`,
})}
</Text>
<Button onClick={() => runSync(query.refresh)}>Refresh</Button>
</Flex>
</Container>
)
}

View File

@@ -1,7 +1,9 @@
import { R } from "@/reffuse"
import { Button } from "@radix-ui/themes"
import { Button, Flex } from "@radix-ui/themes"
import { createFileRoute } from "@tanstack/react-router"
import { Console, Effect } from "effect"
import { GetRandomValues, makeUuid4 } from "@typed/id"
import { Console, Effect, Stream } from "effect"
import { useState } from "react"
export const Route = createFileRoute("/tests")({
@@ -20,12 +22,25 @@ function RouteComponent() {
Effect.delay("1 second"),
), [])
const [reactValue, setReactValue] = useState("initial")
const reactValueStream = R.useStreamFromValues([reactValue])
R.useFork(() => Stream.runForEach(reactValueStream, Console.log), [reactValueStream])
const logValue = R.useCallbackSync(Effect.fn(function*(value: string) {
yield* Effect.log(value)
}), [])
const generateUuid = R.useCallbackSync(() => makeUuid4.pipe(
Effect.provide(GetRandomValues.CryptoRandom),
Effect.map(setReactValue),
), [])
return (
<Button onClick={() => logValue("test")}>Log value</Button>
<Flex direction="row" justify="center" align="center" gap="2">
<Button onClick={() => logValue("test")}>Log value</Button>
<Button onClick={() => generateUuid()}>Generate UUID</Button>
</Flex>
)
}