0.1.11 #14

Merged
Thilawyn merged 318 commits from next into master 2025-05-19 14:01:41 +02:00
9 changed files with 164 additions and 7 deletions
Showing only changes of commit 3af7c3bf7a - Show all commits

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,20 @@
import { HttpClient, HttpClientError } from "@effect/platform"
import { QueryService } from "@reffuse/extension-query"
import { Console, Effect, ParseResult, Schema } from "effect"
export const Result = Schema.Tuple(Schema.String)
export class Uuid4Query extends QueryService.Tag("Uuid4Query")<Uuid4Query,
typeof Result.Type,
HttpClientError.HttpClientError | ParseResult.ParseError
>() {}
export const Uuid4QueryLive = QueryService.layer(Uuid4Query, Console.log("Querying...").pipe(
Effect.andThen(Effect.sleep("500 millis")),
Effect.andThen(HttpClient.get("https://www.uuidtools.com/api/generate/v4")),
HttpClient.withTracerPropagation(false),
Effect.flatMap(res => res.json),
Effect.flatMap(Schema.decodeUnknown(Result)),
Effect.scoped,
))

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 { state, refresh } = R.useMemo(() => Uuid4Query.Uuid4Query, [])
const [queryState] = R.useRefState(state)
return (
<Container>
<Flex direction="column" align="center" gap="2">
<Text>
{AsyncData.match(queryState, {
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(refresh)}>Refresh</Button>
</Flex>
</Container>
)
}

View File

@@ -19,6 +19,7 @@ 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 QueryServiceImport } from './routes/query/service'
// Create/Update Routes
@@ -70,6 +71,12 @@ const IndexRoute = IndexImport.update({
getParentRoute: () => rootRoute,
} as any)
const QueryServiceRoute = QueryServiceImport.update({
id: '/service',
path: '/service',
getParentRoute: () => QueryRoute,
} as any)
// Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' {
@@ -130,20 +137,38 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof TimeImport
parentRoute: typeof rootRoute
}
'/query/service': {
id: '/query/service'
path: '/service'
fullPath: '/query/service'
preLoaderRoute: typeof QueryServiceImport
parentRoute: typeof QueryImport
}
}
}
// Create and export the route tree
interface QueryRouteChildren {
QueryServiceRoute: typeof QueryServiceRoute
}
const QueryRouteChildren: QueryRouteChildren = {
QueryServiceRoute: QueryServiceRoute,
}
const QueryRouteWithChildren = QueryRoute._addFileChildren(QueryRouteChildren)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/blank': typeof BlankRoute
'/count': typeof CountRoute
'/lazyref': typeof LazyrefRoute
'/promise': typeof PromiseRoute
'/query': typeof QueryRoute
'/query': typeof QueryRouteWithChildren
'/tests': typeof TestsRoute
'/time': typeof TimeRoute
'/query/service': typeof QueryServiceRoute
}
export interface FileRoutesByTo {
@@ -152,9 +177,10 @@ export interface FileRoutesByTo {
'/count': typeof CountRoute
'/lazyref': typeof LazyrefRoute
'/promise': typeof PromiseRoute
'/query': typeof QueryRoute
'/query': typeof QueryRouteWithChildren
'/tests': typeof TestsRoute
'/time': typeof TimeRoute
'/query/service': typeof QueryServiceRoute
}
export interface FileRoutesById {
@@ -164,9 +190,10 @@ export interface FileRoutesById {
'/count': typeof CountRoute
'/lazyref': typeof LazyrefRoute
'/promise': typeof PromiseRoute
'/query': typeof QueryRoute
'/query': typeof QueryRouteWithChildren
'/tests': typeof TestsRoute
'/time': typeof TimeRoute
'/query/service': typeof QueryServiceRoute
}
export interface FileRouteTypes {
@@ -180,6 +207,7 @@ export interface FileRouteTypes {
| '/query'
| '/tests'
| '/time'
| '/query/service'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
@@ -190,6 +218,7 @@ export interface FileRouteTypes {
| '/query'
| '/tests'
| '/time'
| '/query/service'
id:
| '__root__'
| '/'
@@ -200,6 +229,7 @@ export interface FileRouteTypes {
| '/query'
| '/tests'
| '/time'
| '/query/service'
fileRoutesById: FileRoutesById
}
@@ -209,7 +239,7 @@ export interface RootRouteChildren {
CountRoute: typeof CountRoute
LazyrefRoute: typeof LazyrefRoute
PromiseRoute: typeof PromiseRoute
QueryRoute: typeof QueryRoute
QueryRoute: typeof QueryRouteWithChildren
TestsRoute: typeof TestsRoute
TimeRoute: typeof TimeRoute
}
@@ -220,7 +250,7 @@ const rootRouteChildren: RootRouteChildren = {
CountRoute: CountRoute,
LazyrefRoute: LazyrefRoute,
PromiseRoute: PromiseRoute,
QueryRoute: QueryRoute,
QueryRoute: QueryRouteWithChildren,
TestsRoute: TestsRoute,
TimeRoute: TimeRoute,
}
@@ -261,13 +291,20 @@ export const routeTree = rootRoute
"filePath": "promise.tsx"
},
"/query": {
"filePath": "query.tsx"
"filePath": "query.tsx",
"children": [
"/query/service"
]
},
"/tests": {
"filePath": "tests.tsx"
},
"/time": {
"filePath": "time.tsx"
},
"/query/service": {
"filePath": "query/service.tsx",
"parent": "/query"
}
}
}

View File

@@ -0,0 +1,26 @@
import { QueryContext } from "@/query/reffuse"
import { Uuid4Query } from "@/query/services"
import { R } from "@/reffuse"
import { createFileRoute } from "@tanstack/react-router"
import { Effect, Layer } from "effect"
import { useMemo } from "react"
export const Route = createFileRoute("/query/service")({
component: RouteComponent
})
function RouteComponent() {
const context = R.useContext()
const layer = useMemo(() => Layer.empty.pipe(
Layer.provideMerge(Uuid4Query.Uuid4QueryLive),
Layer.provide(context)
), [])
return (
<QueryContext.Provider layer={layer}>
</QueryContext.Provider>
)
}

View File

@@ -0,0 +1,26 @@
import * as AsyncData from "@typed/async-data"
import { Context, Effect, Fiber, Layer, SubscriptionRef } from "effect"
import * as QueryRunner from "./QueryRunner.js"
export interface QueryService<A, E> {
readonly state: SubscriptionRef.SubscriptionRef<AsyncData.AsyncData<A, E>>
readonly refresh: Effect.Effect<Fiber.RuntimeFiber<void>>
}
export const Tag = <const Id extends string>(id: Id) => <
Self, A, E = never,
>() => Effect.Tag(id)<Self, QueryService<A, E>>()
export const layer = <Self, Id extends string, A, E, R>(
tag: Context.TagClass<Self, Id, QueryService<A, E>>,
query: Effect.Effect<A, E, R>,
): Layer.Layer<Self, never, R> => Layer.effect(tag, Effect.gen(function*() {
const runner = yield* QueryRunner.make(query)
return {
state: runner.stateRef,
refresh: runner.forkRefresh,
}
}))

View File

@@ -1,2 +1,3 @@
export * from "./QueryExtension.js"
export * as QueryRunner from "./QueryRunner.js"
export * as QueryService from "./QueryService.js"

View File

@@ -1,4 +1,4 @@
import { type Context, Effect, ExecutionStrategy, Exit, type Fiber, Pipeable, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect"
import { type Context, Effect, ExecutionStrategy, Exit, type Fiber, Layer, Pipeable, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect"
import * as React from "react"
import * as ReffuseContext from "./ReffuseContext.js"
import * as ReffuseRuntime from "./ReffuseRuntime.js"
@@ -23,6 +23,10 @@ export abstract class ReffuseHelpers<R> {
return ReffuseContext.useMergeAll(...this.constructor.contexts)
}
useLayer<R>(this: ReffuseHelpers<R>): Layer.Layer<R> {
return ReffuseContext.useMergeAllLayers(...this.constructor.contexts)
}
useRunSync<R>(this: ReffuseHelpers<R>): <A, E>(effect: Effect.Effect<A, E, R>) => A {
const runtime = ReffuseRuntime.useRuntime()