From 6c8f6622ddea3824a2cb7dddf773fe6c24d69aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20Valverd=C3=A9?= Date: Tue, 1 Jul 2025 23:16:33 +0200 Subject: [PATCH] Example work --- packages/example/src/VQueryErrorHandler.tsx | 57 --- packages/example/src/main.tsx | 26 +- packages/example/src/query/reffuse.ts | 10 - .../example/src/query/services/Uuid4Query.ts | 11 - packages/example/src/query/services/index.ts | 1 - .../src/query/views/Uuid4QueryService.tsx | 32 -- packages/example/src/reffuse.ts | 24 -- packages/example/src/routeTree.gen.ts | 389 ++---------------- packages/example/src/routes/__root.tsx | 7 - packages/example/src/routes/blank.tsx | 9 +- packages/example/src/routes/count.tsx | 26 -- .../src/routes/effect-component-tests.tsx | 105 ----- packages/example/src/routes/index.tsx | 20 +- packages/example/src/routes/lazyref.tsx | 31 -- packages/example/src/routes/promise.tsx | 35 -- packages/example/src/routes/query/service.tsx | 38 -- .../example/src/routes/query/usemutation.tsx | 84 ---- .../example/src/routes/query/usequery.tsx | 77 ---- packages/example/src/routes/streams/pull.tsx | 34 -- packages/example/src/routes/tests.tsx | 62 --- packages/example/src/routes/time.tsx | 39 -- packages/example/src/routes/todos.tsx | 35 -- packages/example/src/runtime.ts | 14 + .../example/src/services/AppQueryClient.ts | 7 - .../src/services/AppQueryErrorHandler.ts | 13 - packages/example/src/services/index.ts | 2 - packages/example/src/todos/reffuse.ts | 10 - .../example/src/todos/services/TodosState.ts | 44 -- packages/example/src/todos/services/index.ts | 1 - packages/example/src/todos/views/VNewTodo.tsx | 44 -- packages/example/src/todos/views/VTodo.tsx | 53 --- packages/example/src/todos/views/VTodos.tsx | 38 -- 32 files changed, 70 insertions(+), 1308 deletions(-) delete mode 100644 packages/example/src/VQueryErrorHandler.tsx delete mode 100644 packages/example/src/query/reffuse.ts delete mode 100644 packages/example/src/query/services/Uuid4Query.ts delete mode 100644 packages/example/src/query/services/index.ts delete mode 100644 packages/example/src/query/views/Uuid4QueryService.tsx delete mode 100644 packages/example/src/reffuse.ts delete mode 100644 packages/example/src/routes/count.tsx delete mode 100644 packages/example/src/routes/effect-component-tests.tsx delete mode 100644 packages/example/src/routes/lazyref.tsx delete mode 100644 packages/example/src/routes/promise.tsx delete mode 100644 packages/example/src/routes/query/service.tsx delete mode 100644 packages/example/src/routes/query/usemutation.tsx delete mode 100644 packages/example/src/routes/query/usequery.tsx delete mode 100644 packages/example/src/routes/streams/pull.tsx delete mode 100644 packages/example/src/routes/tests.tsx delete mode 100644 packages/example/src/routes/time.tsx delete mode 100644 packages/example/src/routes/todos.tsx create mode 100644 packages/example/src/runtime.ts delete mode 100644 packages/example/src/services/AppQueryClient.ts delete mode 100644 packages/example/src/services/AppQueryErrorHandler.ts delete mode 100644 packages/example/src/services/index.ts delete mode 100644 packages/example/src/todos/reffuse.ts delete mode 100644 packages/example/src/todos/services/TodosState.ts delete mode 100644 packages/example/src/todos/services/index.ts delete mode 100644 packages/example/src/todos/views/VNewTodo.tsx delete mode 100644 packages/example/src/todos/views/VTodo.tsx delete mode 100644 packages/example/src/todos/views/VTodos.tsx diff --git a/packages/example/src/VQueryErrorHandler.tsx b/packages/example/src/VQueryErrorHandler.tsx deleted file mode 100644 index 05b396d..0000000 --- a/packages/example/src/VQueryErrorHandler.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { AlertDialog, Button, Flex, Text } from "@radix-ui/themes" -import { Cause, Console, Effect, Either, flow, Match, Option, Stream } from "effect" -import { useState } from "react" -import { R } from "./reffuse" -import { AppQueryErrorHandler } from "./services" - - -export function VQueryErrorHandler() { - const [open, setOpen] = useState(false) - - const error = R.useSubscribeStream( - R.useMemo(() => AppQueryErrorHandler.AppQueryErrorHandler.pipe( - Effect.map(handler => handler.errors.pipe( - Stream.changes, - Stream.tap(Console.error), - Stream.tap(() => Effect.sync(() => setOpen(true))), - )) - ), []) - ) - - if (Option.isNone(error)) - return <> - - return ( - - - Error - - {Either.match(Cause.failureOrCause(error.value), { - onLeft: flow( - Match.value, - Match.tag("RequestError", () => HTTP request error), - Match.tag("ResponseError", () => HTTP response error), - Match.exhaustive, - ), - - onRight: flow( - Cause.dieOption, - Option.match({ - onSome: () => Unrecoverable defect, - onNone: () => Unknown error, - }), - ), - })} - - - - - - - - - - ) -} diff --git a/packages/example/src/main.tsx b/packages/example/src/main.tsx index 32a603d..78fdf12 100644 --- a/packages/example/src/main.tsx +++ b/packages/example/src/main.tsx @@ -1,24 +1,11 @@ -import { FetchHttpClient } from "@effect/platform" -import { Clipboard, Geolocation, Permissions } from "@effect/platform-browser" import { createRouter, RouterProvider } from "@tanstack/react-router" -import { Layer } from "effect" +import { ReactManagedRuntime } from "effect-fc" import { StrictMode } from "react" import { createRoot } from "react-dom/client" -import { ReffuseRuntime } from "reffuse" -import { RootContext } from "./reffuse" import { routeTree } from "./routeTree.gen" -import { AppQueryClient, AppQueryErrorHandler } from "./services" +import { runtime } from "./runtime" -const layer = Layer.empty.pipe( - Layer.provideMerge(AppQueryClient.AppQueryClient.Default), - Layer.provideMerge(AppQueryErrorHandler.AppQueryErrorHandler.Default), - Layer.provideMerge(Clipboard.layer), - Layer.provideMerge(Geolocation.layer), - Layer.provideMerge(Permissions.layer), - Layer.provideMerge(FetchHttpClient.layer), -) - const router = createRouter({ routeTree }) declare module "@tanstack/react-router" { @@ -27,13 +14,10 @@ declare module "@tanstack/react-router" { } } - createRoot(document.getElementById("root")!).render( - - - - - + + + ) diff --git a/packages/example/src/query/reffuse.ts b/packages/example/src/query/reffuse.ts deleted file mode 100644 index 08cf368..0000000 --- a/packages/example/src/query/reffuse.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { RootReffuse } from "@/reffuse" -import { Reffuse, ReffuseContext } from "reffuse" -import { Uuid4Query } from "./services" - - -export const QueryContext = ReffuseContext.make() - -export const R = new class QueryReffuse extends RootReffuse.pipe( - Reffuse.withContexts(QueryContext) -) {} diff --git a/packages/example/src/query/services/Uuid4Query.ts b/packages/example/src/query/services/Uuid4Query.ts deleted file mode 100644 index 46708bf..0000000 --- a/packages/example/src/query/services/Uuid4Query.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { QueryRunner } from "@reffuse/extension-query" -import { ParseResult, Schema } from "effect" - - -export const Result = Schema.Array(Schema.String) - -export class Uuid4Query extends QueryRunner.Tag("Uuid4Query")() {} diff --git a/packages/example/src/query/services/index.ts b/packages/example/src/query/services/index.ts deleted file mode 100644 index 4f67d41..0000000 --- a/packages/example/src/query/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * as Uuid4Query from "./Uuid4Query" diff --git a/packages/example/src/query/views/Uuid4QueryService.tsx b/packages/example/src/query/views/Uuid4QueryService.tsx deleted file mode 100644 index e5e3499..0000000 --- a/packages/example/src/query/views/Uuid4QueryService.tsx +++ /dev/null @@ -1,32 +0,0 @@ -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 runFork = R.useRunFork() - - const query = R.useMemo(() => Uuid4Query.Uuid4Query, []) - const [state] = R.useSubscribeRefs(query.stateRef) - - - return ( - - - - {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)" : ""}`, - })} - - - - - - ) -} diff --git a/packages/example/src/reffuse.ts b/packages/example/src/reffuse.ts deleted file mode 100644 index 00abc14..0000000 --- a/packages/example/src/reffuse.ts +++ /dev/null @@ -1,24 +0,0 @@ -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" -import { AppQueryClient, AppQueryErrorHandler } from "./services" - - -export const RootContext = ReffuseContext.make< - | AppQueryClient.AppQueryClient - | AppQueryErrorHandler.AppQueryErrorHandler - | Clipboard.Clipboard - | Geolocation.Geolocation - | Permissions.Permissions - | HttpClient.HttpClient ->() - -export class RootReffuse extends Reffuse.Reffuse.pipe( - Reffuse.withExtension(LazyRefExtension), - Reffuse.withExtension(QueryExtension), - Reffuse.withContexts(RootContext), -) {} - -export const R = new RootReffuse() diff --git a/packages/example/src/routeTree.gen.ts b/packages/example/src/routeTree.gen.ts index 201dccb..87e976f 100644 --- a/packages/example/src/routeTree.gen.ts +++ b/packages/example/src/routeTree.gen.ts @@ -8,397 +8,70 @@ // You should NOT make any changes in this file as it will be overwritten. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. -// Import Routes +import { Route as rootRouteImport } from './routes/__root' +import { Route as BlankRouteImport } from './routes/blank' +import { Route as IndexRouteImport } from './routes/index' -import { Route as rootRoute } from './routes/__root' -import { Route as TodosImport } from './routes/todos' -import { Route as TimeImport } from './routes/time' -import { Route as TestsImport } from './routes/tests' -import { Route as PromiseImport } from './routes/promise' -import { Route as LazyrefImport } from './routes/lazyref' -import { Route as EffectComponentTestsImport } from './routes/effect-component-tests' -import { Route as CountImport } from './routes/count' -import { Route as BlankImport } from './routes/blank' -import { Route as IndexImport } from './routes/index' -import { Route as StreamsPullImport } from './routes/streams/pull' -import { Route as QueryUsequeryImport } from './routes/query/usequery' -import { Route as QueryUsemutationImport } from './routes/query/usemutation' -import { Route as QueryServiceImport } from './routes/query/service' - -// Create/Update Routes - -const TodosRoute = TodosImport.update({ - id: '/todos', - path: '/todos', - getParentRoute: () => rootRoute, -} as any) - -const TimeRoute = TimeImport.update({ - id: '/time', - path: '/time', - getParentRoute: () => rootRoute, -} as any) - -const TestsRoute = TestsImport.update({ - id: '/tests', - path: '/tests', - getParentRoute: () => rootRoute, -} as any) - -const PromiseRoute = PromiseImport.update({ - id: '/promise', - path: '/promise', - getParentRoute: () => rootRoute, -} as any) - -const LazyrefRoute = LazyrefImport.update({ - id: '/lazyref', - path: '/lazyref', - getParentRoute: () => rootRoute, -} as any) - -const EffectComponentTestsRoute = EffectComponentTestsImport.update({ - id: '/effect-component-tests', - path: '/effect-component-tests', - getParentRoute: () => rootRoute, -} as any) - -const CountRoute = CountImport.update({ - id: '/count', - path: '/count', - getParentRoute: () => rootRoute, -} as any) - -const BlankRoute = BlankImport.update({ +const BlankRoute = BlankRouteImport.update({ id: '/blank', path: '/blank', - getParentRoute: () => rootRoute, + getParentRoute: () => rootRouteImport, } as any) - -const IndexRoute = IndexImport.update({ +const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', - getParentRoute: () => rootRoute, + getParentRoute: () => rootRouteImport, } as any) -const StreamsPullRoute = StreamsPullImport.update({ - id: '/streams/pull', - path: '/streams/pull', - getParentRoute: () => rootRoute, -} as any) - -const QueryUsequeryRoute = QueryUsequeryImport.update({ - id: '/query/usequery', - path: '/query/usequery', - getParentRoute: () => rootRoute, -} as any) - -const QueryUsemutationRoute = QueryUsemutationImport.update({ - id: '/query/usemutation', - path: '/query/usemutation', - 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' { - interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexImport - parentRoute: typeof rootRoute - } - '/blank': { - id: '/blank' - path: '/blank' - fullPath: '/blank' - preLoaderRoute: typeof BlankImport - parentRoute: typeof rootRoute - } - '/count': { - id: '/count' - path: '/count' - fullPath: '/count' - preLoaderRoute: typeof CountImport - parentRoute: typeof rootRoute - } - '/effect-component-tests': { - id: '/effect-component-tests' - path: '/effect-component-tests' - fullPath: '/effect-component-tests' - preLoaderRoute: typeof EffectComponentTestsImport - parentRoute: typeof rootRoute - } - '/lazyref': { - id: '/lazyref' - path: '/lazyref' - fullPath: '/lazyref' - preLoaderRoute: typeof LazyrefImport - parentRoute: typeof rootRoute - } - '/promise': { - id: '/promise' - path: '/promise' - fullPath: '/promise' - preLoaderRoute: typeof PromiseImport - parentRoute: typeof rootRoute - } - '/tests': { - id: '/tests' - path: '/tests' - fullPath: '/tests' - preLoaderRoute: typeof TestsImport - parentRoute: typeof rootRoute - } - '/time': { - id: '/time' - path: '/time' - fullPath: '/time' - preLoaderRoute: typeof TimeImport - parentRoute: typeof rootRoute - } - '/todos': { - id: '/todos' - path: '/todos' - fullPath: '/todos' - preLoaderRoute: typeof TodosImport - parentRoute: typeof rootRoute - } - '/query/service': { - id: '/query/service' - path: '/query/service' - fullPath: '/query/service' - preLoaderRoute: typeof QueryServiceImport - parentRoute: typeof rootRoute - } - '/query/usemutation': { - id: '/query/usemutation' - path: '/query/usemutation' - fullPath: '/query/usemutation' - preLoaderRoute: typeof QueryUsemutationImport - parentRoute: typeof rootRoute - } - '/query/usequery': { - id: '/query/usequery' - path: '/query/usequery' - fullPath: '/query/usequery' - preLoaderRoute: typeof QueryUsequeryImport - parentRoute: typeof rootRoute - } - '/streams/pull': { - id: '/streams/pull' - path: '/streams/pull' - fullPath: '/streams/pull' - preLoaderRoute: typeof StreamsPullImport - parentRoute: typeof rootRoute - } - } -} - -// Create and export the route tree - export interface FileRoutesByFullPath { '/': typeof IndexRoute '/blank': typeof BlankRoute - '/count': typeof CountRoute - '/effect-component-tests': typeof EffectComponentTestsRoute - '/lazyref': typeof LazyrefRoute - '/promise': typeof PromiseRoute - '/tests': typeof TestsRoute - '/time': typeof TimeRoute - '/todos': typeof TodosRoute - '/query/service': typeof QueryServiceRoute - '/query/usemutation': typeof QueryUsemutationRoute - '/query/usequery': typeof QueryUsequeryRoute - '/streams/pull': typeof StreamsPullRoute } - export interface FileRoutesByTo { '/': typeof IndexRoute '/blank': typeof BlankRoute - '/count': typeof CountRoute - '/effect-component-tests': typeof EffectComponentTestsRoute - '/lazyref': typeof LazyrefRoute - '/promise': typeof PromiseRoute - '/tests': typeof TestsRoute - '/time': typeof TimeRoute - '/todos': typeof TodosRoute - '/query/service': typeof QueryServiceRoute - '/query/usemutation': typeof QueryUsemutationRoute - '/query/usequery': typeof QueryUsequeryRoute - '/streams/pull': typeof StreamsPullRoute } - export interface FileRoutesById { - __root__: typeof rootRoute + __root__: typeof rootRouteImport '/': typeof IndexRoute '/blank': typeof BlankRoute - '/count': typeof CountRoute - '/effect-component-tests': typeof EffectComponentTestsRoute - '/lazyref': typeof LazyrefRoute - '/promise': typeof PromiseRoute - '/tests': typeof TestsRoute - '/time': typeof TimeRoute - '/todos': typeof TodosRoute - '/query/service': typeof QueryServiceRoute - '/query/usemutation': typeof QueryUsemutationRoute - '/query/usequery': typeof QueryUsequeryRoute - '/streams/pull': typeof StreamsPullRoute } - export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: - | '/' - | '/blank' - | '/count' - | '/effect-component-tests' - | '/lazyref' - | '/promise' - | '/tests' - | '/time' - | '/todos' - | '/query/service' - | '/query/usemutation' - | '/query/usequery' - | '/streams/pull' + fullPaths: '/' | '/blank' fileRoutesByTo: FileRoutesByTo - to: - | '/' - | '/blank' - | '/count' - | '/effect-component-tests' - | '/lazyref' - | '/promise' - | '/tests' - | '/time' - | '/todos' - | '/query/service' - | '/query/usemutation' - | '/query/usequery' - | '/streams/pull' - id: - | '__root__' - | '/' - | '/blank' - | '/count' - | '/effect-component-tests' - | '/lazyref' - | '/promise' - | '/tests' - | '/time' - | '/todos' - | '/query/service' - | '/query/usemutation' - | '/query/usequery' - | '/streams/pull' + to: '/' | '/blank' + id: '__root__' | '/' | '/blank' fileRoutesById: FileRoutesById } - export interface RootRouteChildren { IndexRoute: typeof IndexRoute BlankRoute: typeof BlankRoute - CountRoute: typeof CountRoute - EffectComponentTestsRoute: typeof EffectComponentTestsRoute - LazyrefRoute: typeof LazyrefRoute - PromiseRoute: typeof PromiseRoute - TestsRoute: typeof TestsRoute - TimeRoute: typeof TimeRoute - TodosRoute: typeof TodosRoute - QueryServiceRoute: typeof QueryServiceRoute - QueryUsemutationRoute: typeof QueryUsemutationRoute - QueryUsequeryRoute: typeof QueryUsequeryRoute - StreamsPullRoute: typeof StreamsPullRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/blank': { + id: '/blank' + path: '/blank' + fullPath: '/blank' + preLoaderRoute: typeof BlankRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + } } const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, BlankRoute: BlankRoute, - CountRoute: CountRoute, - EffectComponentTestsRoute: EffectComponentTestsRoute, - LazyrefRoute: LazyrefRoute, - PromiseRoute: PromiseRoute, - TestsRoute: TestsRoute, - TimeRoute: TimeRoute, - TodosRoute: TodosRoute, - QueryServiceRoute: QueryServiceRoute, - QueryUsemutationRoute: QueryUsemutationRoute, - QueryUsequeryRoute: QueryUsequeryRoute, - StreamsPullRoute: StreamsPullRoute, } - -export const routeTree = rootRoute +export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() - -/* ROUTE_MANIFEST_START -{ - "routes": { - "__root__": { - "filePath": "__root.tsx", - "children": [ - "/", - "/blank", - "/count", - "/effect-component-tests", - "/lazyref", - "/promise", - "/tests", - "/time", - "/todos", - "/query/service", - "/query/usemutation", - "/query/usequery", - "/streams/pull" - ] - }, - "/": { - "filePath": "index.tsx" - }, - "/blank": { - "filePath": "blank.tsx" - }, - "/count": { - "filePath": "count.tsx" - }, - "/effect-component-tests": { - "filePath": "effect-component-tests.tsx" - }, - "/lazyref": { - "filePath": "lazyref.tsx" - }, - "/promise": { - "filePath": "promise.tsx" - }, - "/tests": { - "filePath": "tests.tsx" - }, - "/time": { - "filePath": "time.tsx" - }, - "/todos": { - "filePath": "todos.tsx" - }, - "/query/service": { - "filePath": "query/service.tsx" - }, - "/query/usemutation": { - "filePath": "query/usemutation.tsx" - }, - "/query/usequery": { - "filePath": "query/usequery.tsx" - }, - "/streams/pull": { - "filePath": "streams/pull.tsx" - } - } -} -ROUTE_MANIFEST_END */ diff --git a/packages/example/src/routes/__root.tsx b/packages/example/src/routes/__root.tsx index 12abc9b..4f94c20 100644 --- a/packages/example/src/routes/__root.tsx +++ b/packages/example/src/routes/__root.tsx @@ -1,4 +1,3 @@ -import { VQueryErrorHandler } from "@/VQueryErrorHandler" import { Container, Flex, Theme } from "@radix-ui/themes" import { createRootRoute, Link, Outlet } from "@tanstack/react-router" import { TanStackRouterDevtools } from "@tanstack/react-router-devtools" @@ -17,18 +16,12 @@ function Root() { Index - Time - Count - Tests - Promise - Query Blank - ) diff --git a/packages/example/src/routes/blank.tsx b/packages/example/src/routes/blank.tsx index 3d1cd68..4f3c7df 100644 --- a/packages/example/src/routes/blank.tsx +++ b/packages/example/src/routes/blank.tsx @@ -1,9 +1,10 @@ -import { createFileRoute } from '@tanstack/react-router' +import { createFileRoute } from "@tanstack/react-router" -export const Route = createFileRoute('/blank')({ - component: RouteComponent, + +export const Route = createFileRoute("/blank")({ + component: RouteComponent }) function RouteComponent() { - return
Hello "/blank"!
+ return
Hello "/blank"!
} diff --git a/packages/example/src/routes/count.tsx b/packages/example/src/routes/count.tsx deleted file mode 100644 index ab6d802..0000000 --- a/packages/example/src/routes/count.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { R } from "@/reffuse" -import { createFileRoute } from "@tanstack/react-router" -import { Effect, Ref } from "effect" - - -export const Route = createFileRoute("/count")({ - component: Count -}) - -function Count() { - - const runSync = R.useRunSync() - - const countRef = R.useRef(() => Effect.succeed(0)) - const [count] = R.useSubscribeRefs(countRef) - - - return ( -
- -
- ) - -} diff --git a/packages/example/src/routes/effect-component-tests.tsx b/packages/example/src/routes/effect-component-tests.tsx deleted file mode 100644 index b4f1fa4..0000000 --- a/packages/example/src/routes/effect-component-tests.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { Box, TextField } from "@radix-ui/themes" -import { createFileRoute } from "@tanstack/react-router" -import { Array, Console, Effect, Layer, pipe, Ref, Runtime, SubscriptionRef } from "effect" -import { ReactComponent, ReactHook, ReactManagedRuntime } from "effect-components" - - -const LogLive = Layer.scopedDiscard(Effect.acquireRelease( - Console.log("Runtime built."), - () => Console.log("Runtime destroyed."), -)) - -class TestService extends Effect.Service()("TestService", { - effect: Effect.bind(Effect.Do, "ref", () => SubscriptionRef.make("value")), -}) {} - -class SubService extends Effect.Service()("SubService", { - effect: Effect.bind(Effect.Do, "ref", () => SubscriptionRef.make("subvalue")), -}) {} - -const runtime = ReactManagedRuntime.make(Layer.empty.pipe( - Layer.provideMerge(LogLive), - Layer.provideMerge(TestService.Default), -)) - - -export const Route = createFileRoute("/effect-component-tests")({ - component: RouteComponent, -}) - -function RouteComponent() { - return ( - - - - ) -} - -const MyRoute = pipe( - Effect.fn(function*() { - const runtime = yield* Effect.runtime() - - const service = yield* TestService - const [value] = yield* ReactHook.useSubscribeRefs(service.ref) - - // const MyTestComponentFC = yield* Effect.provide( - // ReactComponent.useFC(MyTestComponent), - // yield* ReactHook.useMemoLayer(SubService.Default), - // ) - - return <> - - Runtime.runSync(runtime)(Ref.set(service.ref, e.target.value))} - /> - - - {/* {yield* ReactComponent.use(MyTestComponent, C => ).pipe( - Effect.provide(yield* ReactHook.useMemoLayer(SubService.Default)) - )} */} - - {/* {Array.range(0, 3).map(k => - - )} */} - - {yield* pipe( - Array.range(0, 3), - Array.map(k => ReactComponent.use(MyTestComponent, FC => - - )), - Effect.all, - Effect.provide(yield* ReactHook.useMemoLayer(SubService.Default)), - )} - - }), - - ReactComponent.withDisplayName("MyRoute"), - ReactComponent.withRuntime(runtime.context), -) - - -const MyTestComponent = pipe( - Effect.fn(function*() { - const runtime = yield* Effect.runtime() - - const service = yield* SubService - const [value] = yield* ReactHook.useSubscribeRefs(service.ref) - - // yield* ReactHook.useMemo(() => Effect.andThen( - // Effect.addFinalizer(() => Console.log("MyTestComponent umounted")), - // Console.log("MyTestComponent mounted"), - // ), []) - - return <> - - Runtime.runSync(runtime)(Ref.set(service.ref, e.target.value))} - /> - - - }), - - ReactComponent.withDisplayName("MyTestComponent"), -) diff --git a/packages/example/src/routes/index.tsx b/packages/example/src/routes/index.tsx index 58b96d2..6a6620f 100644 --- a/packages/example/src/routes/index.tsx +++ b/packages/example/src/routes/index.tsx @@ -1,10 +1,20 @@ +import { runtime } from "@/runtime" import { createFileRoute } from "@tanstack/react-router" +import { Effect, pipe } from "effect" +import { ReactComponent } from "effect-fc" -export const Route = createFileRoute('/')({ +const RouteComponent = pipe( + Effect.fn(function*() { + yield* Effect.succeed("ouient") + return
Hello "/"!
+ }), + + ReactComponent.withDisplayName("Index"), + ReactComponent.withRuntime(runtime.context), +) + + +export const Route = createFileRoute("/")({ component: RouteComponent }) - -function RouteComponent() { - return
Hello "/"!
-} diff --git a/packages/example/src/routes/lazyref.tsx b/packages/example/src/routes/lazyref.tsx deleted file mode 100644 index 67657a7..0000000 --- a/packages/example/src/routes/lazyref.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { R } from "@/reffuse" -import { Button, Text } from "@radix-ui/themes" -import { createFileRoute } from "@tanstack/react-router" -import * as LazyRef from "@typed/lazy-ref" -import { Suspense, use } from "react" - - -export const Route = createFileRoute("/lazyref")({ - component: RouteComponent -}) - -function RouteComponent() { - const promise = R.usePromise(() => LazyRef.of(0), []) - - return ( - Loading...}> - - - ) -} - -function LazyRefComponent({ promise }: { readonly promise: Promise> }) { - const ref = use(promise) - const [value, setValue] = R.useLazyRefState(ref) - - return ( - - ) -} diff --git a/packages/example/src/routes/promise.tsx b/packages/example/src/routes/promise.tsx deleted file mode 100644 index 4b197cf..0000000 --- a/packages/example/src/routes/promise.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { R } from "@/reffuse" -import { HttpClient } from "@effect/platform" -import { Text } from "@radix-ui/themes" -import { createFileRoute } from "@tanstack/react-router" -import { Console, Effect, Schema } from "effect" -import { Suspense, use } from "react" - - -export const Route = createFileRoute("/promise")({ - component: RouteComponent -}) - - -const Result = Schema.Tuple(Schema.String) -type Result = typeof Result.Type - -function RouteComponent() { - const promise = R.usePromise(() => Effect.addFinalizer(() => Console.log("Cleanup")).pipe( - Effect.andThen(HttpClient.get("https://www.uuidtools.com/api/generate/v4")), - HttpClient.withTracerPropagation(false), - Effect.flatMap(res => res.json), - Effect.flatMap(Schema.decodeUnknown(Result)), - ), []) - - return ( - Loading...}> - - - ) -} - -function AsyncComponent({ promise }: { readonly promise: Promise }) { - const [uuid] = use(promise) - return {uuid} -} diff --git a/packages/example/src/routes/query/service.tsx b/packages/example/src/routes/query/service.tsx deleted file mode 100644 index 8e15e40..0000000 --- a/packages/example/src/routes/query/service.tsx +++ /dev/null @@ -1,38 +0,0 @@ -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, Layer, Schema } from "effect" -import { useMemo } from "react" - - -export const Route = createFileRoute("/query/service")({ - component: RouteComponent -}) - -function RouteComponent() { - const query = R.useQuery({ - key: R.useStreamFromReactiveValues(["uuid4", 10 as number]), - query: ([, count]) => Console.log(`Querying ${ count } IDs...`).pipe( - Effect.andThen(Effect.sleep("500 millis")), - Effect.andThen(Effect.map( - HttpClient.HttpClient, - HttpClient.withTracerPropagation(false), - )), - Effect.flatMap(client => client.get(`https://www.uuidtools.com/api/generate/v4/count/${ count }`)), - Effect.flatMap(res => res.json), - Effect.flatMap(Schema.decodeUnknown(Uuid4Query.Result)), - Effect.scoped, - ), - }) - - const layer = useMemo(() => Layer.succeed(Uuid4Query.Uuid4Query, query), [query]) - - return ( - - - - ) -} diff --git a/packages/example/src/routes/query/usemutation.tsx b/packages/example/src/routes/query/usemutation.tsx deleted file mode 100644 index 6a3ff66..0000000 --- a/packages/example/src/routes/query/usemutation.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { R } from "@/reffuse" -import { HttpClient } from "@effect/platform" -import { Button, Container, Flex, Slider, Text } from "@radix-ui/themes" -import { QueryProgress } from "@reffuse/extension-query" -import { createFileRoute } from "@tanstack/react-router" -import * as AsyncData from "@typed/async-data" -import { Array, Console, Effect, flow, Option, Schema, Stream } from "effect" -import { useState } from "react" - - -export const Route = createFileRoute("/query/usemutation")({ - component: RouteComponent -}) - - -const Result = Schema.Array(Schema.String) - -function RouteComponent() { - const runFork = R.useRunFork() - - const [count, setCount] = useState(1) - - const mutation = R.useMutation({ - mutation: ([count]: readonly [count: number]) => Console.log(`Querying ${ count } IDs...`).pipe( - Effect.andThen(QueryProgress.QueryProgress.update(() => - AsyncData.Progress.make({ loaded: 0, total: Option.some(100) }) - )), - Effect.andThen(Effect.sleep("500 millis")), - Effect.tap(() => QueryProgress.QueryProgress.update(() => - AsyncData.Progress.make({ loaded: 50, total: Option.some(100) }) - )), - Effect.andThen(Effect.map( - HttpClient.HttpClient, - HttpClient.withTracerPropagation(false), - )), - Effect.flatMap(client => client.get(`https://www.uuidtools.com/api/generate/v4/count/${ count }`)), - Effect.flatMap(res => res.json), - Effect.flatMap(Schema.decodeUnknown(Result)), - Effect.scoped, - ) - }) - - const [state] = R.useSubscribeRefs(mutation.stateRef) - - - return ( - - - - - - {AsyncData.match(state, { - NoData: () => "No data yet", - Loading: progress => - `Loading... - ${ Option.match(progress, { - onSome: ({ loaded, total }) => ` (${ loaded }/${ Option.getOrElse(total, () => "unknown") })`, - onNone: () => "", - }) }`, - Success: value => `Value: ${ value }`, - Failure: cause => `Error: ${ cause }`, - })} - - - - - - ) -} diff --git a/packages/example/src/routes/query/usequery.tsx b/packages/example/src/routes/query/usequery.tsx deleted file mode 100644 index 4afc21f..0000000 --- a/packages/example/src/routes/query/usequery.tsx +++ /dev/null @@ -1,77 +0,0 @@ -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, Stream } from "effect" -import { useState } from "react" - - -export const Route = createFileRoute("/query/usequery")({ - component: RouteComponent -}) - - -const Result = Schema.Array(Schema.String) - -function RouteComponent() { - const runFork = R.useRunFork() - - const [count, setCount] = useState(1) - - const query = R.useQuery({ - key: R.useStreamFromReactiveValues(["uuid4", count]), - query: ([, count]) => Console.log(`Querying ${ count } IDs...`).pipe( - Effect.andThen(Effect.sleep("500 millis")), - Effect.andThen(Effect.map( - HttpClient.HttpClient, - HttpClient.withTracerPropagation(false), - )), - Effect.flatMap(client => client.get(`https://www.uuidtools.com/api/generate/v4/count/${ count }`)), - Effect.flatMap(res => res.json), - Effect.flatMap(Schema.decodeUnknown(Result)), - Effect.scoped, - ), - }) - - const [state] = R.useSubscribeRefs(query.stateRef) - - - return ( - - - - - - {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)" : ""}`, - })} - - - - - - ) -} diff --git a/packages/example/src/routes/streams/pull.tsx b/packages/example/src/routes/streams/pull.tsx deleted file mode 100644 index e9683f4..0000000 --- a/packages/example/src/routes/streams/pull.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { R } from "@/reffuse" -import { Button, Flex, Text } from "@radix-ui/themes" -import { createFileRoute } from "@tanstack/react-router" -import { Chunk, Effect, Exit, Option, Queue, Random, Scope, Stream } from "effect" -import { useMemo, useState } from "react" - - -export const Route = createFileRoute("/streams/pull")({ - component: RouteComponent -}) - -function RouteComponent() { - const stream = useMemo(() => Stream.repeatEffect(Random.nextInt), []) - const streamScope = R.useScope([stream], { finalizerExecutionMode: "fork" }) - - const queue = R.useMemo(() => Effect.provideService(Stream.toQueueOfElements(stream), Scope.Scope, streamScope), [streamScope]) - - const [value, setValue] = useState(Option.none()) - const pullLatest = R.useCallbackSync(() => Queue.takeAll(queue).pipe( - Effect.flatMap(Chunk.last), - Effect.flatMap(Exit.matchEffect({ - onSuccess: Effect.succeed, - onFailure: Effect.fail, - })), - Effect.tap(v => Effect.sync(() => setValue(Option.some(v)))), - ), [queue]) - - return ( - - {Option.isSome(value) && {value.value}} - - - ) -} diff --git a/packages/example/src/routes/tests.tsx b/packages/example/src/routes/tests.tsx deleted file mode 100644 index 1bd85eb..0000000 --- a/packages/example/src/routes/tests.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { R } from "@/reffuse" -import { Button, Flex, Text } from "@radix-ui/themes" -import { createFileRoute } from "@tanstack/react-router" -import { GetRandomValues, makeUuid4 } from "@typed/id" -import { Console, Effect, Option } from "effect" -import { useEffect, useState } from "react" - - -interface Node { - value: string - left?: Leaf - right?: Leaf -} -interface Leaf { - node: Node -} - - -const makeUuid = Effect.provide(makeUuid4, GetRandomValues.CryptoRandom) - - -export const Route = createFileRoute("/tests")({ - component: RouteComponent -}) - -function RouteComponent() { - const runSync = R.useRunSync() - - const [uuid, setUuid] = useState(R.useMemo(() => makeUuid, [])) - const generateUuid = R.useCallbackSync(() => makeUuid.pipe( - Effect.tap(v => Effect.sync(() => setUuid(v))) - ), []) - - const uuidStream = R.useStreamFromReactiveValues([uuid]) - const uuidStreamLatestValue = R.useSubscribeStream(uuidStream) - - const [, scopeLayer] = R.useScope([uuid]) - - useEffect(() => Effect.addFinalizer(() => Console.log("Scope cleanup!")).pipe( - Effect.andThen(Console.log("Scope changed")), - Effect.provide(scopeLayer), - runSync, - ), [scopeLayer, runSync]) - - - const nodeRef = R.useRef(() => Effect.succeed({ value: "prout" })) - const nodeValueRef = R.useSubRefFromPath(nodeRef, ["value"]) - - - return ( - - {uuid} - - - {Option.match(uuidStreamLatestValue, { - onSome: ([v]) => v, - onNone: () => <>, - })} - - - ) -} diff --git a/packages/example/src/routes/time.tsx b/packages/example/src/routes/time.tsx deleted file mode 100644 index 99e7e39..0000000 --- a/packages/example/src/routes/time.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { R } from "@/reffuse" -import { createFileRoute } from "@tanstack/react-router" -import { Console, DateTime, Effect, Ref, Schedule, Stream, SubscriptionRef } from "effect" - - -const timeEverySecond = Stream.repeatEffectWithSchedule( - DateTime.now, - Schedule.intersect(Schedule.forever, Schedule.spaced("1 second")), -) - - -export const Route = createFileRoute("/time")({ - component: Time -}) - -function Time() { - - const timeRef = R.useMemo(() => DateTime.now.pipe(Effect.flatMap(SubscriptionRef.make)), []) - - R.useFork(() => Effect.addFinalizer(() => Console.log("Cleanup")).pipe( - Effect.andThen(Stream.runForEach(timeEverySecond, v => Ref.set(timeRef, v))) - ), [timeRef]) - - const [time] = R.useRefState(timeRef) - - - return ( -
-

- {DateTime.format(time, { - hour: "numeric", - minute: "numeric", - second: "numeric", - })} -

-
- ) - -} diff --git a/packages/example/src/routes/todos.tsx b/packages/example/src/routes/todos.tsx deleted file mode 100644 index a681617..0000000 --- a/packages/example/src/routes/todos.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { TodosContext } from "@/todos/reffuse" -import { TodosState } from "@/todos/services" -import { VTodos } from "@/todos/views/VTodos" -import { Container } from "@radix-ui/themes" -import { createFileRoute } from "@tanstack/react-router" -import { Console, Effect, Layer } from "effect" -import { useMemo } from "react" - - -export const Route = createFileRoute("/todos")({ - component: Todos -}) - -function Todos() { - - const todosLayer = useMemo(() => Layer.empty.pipe( - Layer.provideMerge(TodosState.make("todos")), - - Layer.merge(Layer.effectDiscard( - Effect.addFinalizer(() => Console.log("TodosContext cleaned up")).pipe( - Effect.andThen(Console.log("TodosContext constructed")) - ) - )), - ), []) - - - return ( - - - - - - ) - -} diff --git a/packages/example/src/runtime.ts b/packages/example/src/runtime.ts new file mode 100644 index 0000000..67f73ea --- /dev/null +++ b/packages/example/src/runtime.ts @@ -0,0 +1,14 @@ +import { FetchHttpClient } from "@effect/platform" +import { Clipboard, Geolocation, Permissions } from "@effect/platform-browser" +import { Layer } from "effect" +import { ReactManagedRuntime } from "effect-fc" + + +export const AppLive = Layer.empty.pipe( + Layer.provideMerge(Clipboard.layer), + Layer.provideMerge(Geolocation.layer), + Layer.provideMerge(Permissions.layer), + Layer.provideMerge(FetchHttpClient.layer), +) + +export const runtime = ReactManagedRuntime.make(AppLive) diff --git a/packages/example/src/services/AppQueryClient.ts b/packages/example/src/services/AppQueryClient.ts deleted file mode 100644 index bee5014..0000000 --- a/packages/example/src/services/AppQueryClient.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { QueryClient } from "@reffuse/extension-query" -import * as AppQueryErrorHandler from "./AppQueryErrorHandler" - - -export class AppQueryClient extends QueryClient.Service()({ - errorHandler: AppQueryErrorHandler.AppQueryErrorHandler -}) {} diff --git a/packages/example/src/services/AppQueryErrorHandler.ts b/packages/example/src/services/AppQueryErrorHandler.ts deleted file mode 100644 index efff7ec..0000000 --- a/packages/example/src/services/AppQueryErrorHandler.ts +++ /dev/null @@ -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", { - effect: QueryErrorHandler.make()( - (self, failure, defect) => self.pipe( - Effect.catchTag("RequestError", "ResponseError", failure), - Effect.catchAllDefect(defect), - ) - ) -}) {} diff --git a/packages/example/src/services/index.ts b/packages/example/src/services/index.ts deleted file mode 100644 index 691ab08..0000000 --- a/packages/example/src/services/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * as AppQueryClient from "./AppQueryClient" -export * as AppQueryErrorHandler from "./AppQueryErrorHandler" diff --git a/packages/example/src/todos/reffuse.ts b/packages/example/src/todos/reffuse.ts deleted file mode 100644 index 986f562..0000000 --- a/packages/example/src/todos/reffuse.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { RootReffuse } from "@/reffuse" -import { Reffuse, ReffuseContext } from "reffuse" -import { TodosState } from "./services" - - -export const TodosContext = ReffuseContext.make() - -export const R = new class TodosReffuse extends RootReffuse.pipe( - Reffuse.withContexts(TodosContext) -) {} diff --git a/packages/example/src/todos/services/TodosState.ts b/packages/example/src/todos/services/TodosState.ts deleted file mode 100644 index 4475c39..0000000 --- a/packages/example/src/todos/services/TodosState.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Todo } from "@/domain" -import { KeyValueStore } from "@effect/platform" -import { BrowserKeyValueStore } from "@effect/platform-browser" -import { PlatformError } from "@effect/platform/Error" -import { Chunk, Context, Effect, identity, Layer, ParseResult, Ref, Schema, Stream, SubscriptionRef } from "effect" - - -export class TodosState extends Context.Tag("TodosState")> - readonly load: Effect.Effect - readonly save: Effect.Effect -}>() {} - - -export const make = (key: string) => Layer.effect(TodosState, Effect.gen(function*() { - const readFromLocalStorage = KeyValueStore.KeyValueStore.pipe( - Effect.flatMap(kv => kv.get(key)), - Effect.flatMap(identity), - Effect.flatMap(Schema.decode( - Schema.compose(Schema.parseJson(), Schema.Chunk(Todo.TodoFromJson)) - )), - Effect.catchTag("NoSuchElementException", () => Effect.succeed(Chunk.empty())), - Effect.provide(BrowserKeyValueStore.layerLocalStorage), - ) - - const writeToLocalStorage = (values: Chunk.Chunk) => KeyValueStore.KeyValueStore.pipe( - Effect.flatMap(kv => values.pipe( - Schema.encode( - Schema.compose(Schema.parseJson(), Schema.Chunk(Todo.TodoFromJson)) - ), - Effect.flatMap(v => kv.set(key, v)), - )), - Effect.provide(BrowserKeyValueStore.layerLocalStorage), - ) - - const todos = yield* SubscriptionRef.make(yield* readFromLocalStorage) - const load = Effect.flatMap(readFromLocalStorage, v => Ref.set(todos, v)) - const save = Effect.flatMap(todos, writeToLocalStorage) - - // Sync changes with local storage - yield* Effect.forkScoped(Stream.runForEach(todos.changes, writeToLocalStorage)) - - return { todos, load, save } -})) diff --git a/packages/example/src/todos/services/index.ts b/packages/example/src/todos/services/index.ts deleted file mode 100644 index 5d1c39e..0000000 --- a/packages/example/src/todos/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * as TodosState from "./TodosState" diff --git a/packages/example/src/todos/views/VNewTodo.tsx b/packages/example/src/todos/views/VNewTodo.tsx deleted file mode 100644 index 873942a..0000000 --- a/packages/example/src/todos/views/VNewTodo.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { Todo } from "@/domain" -import { Box, Button, Card, Flex, TextArea } from "@radix-ui/themes" -import { GetRandomValues, makeUuid4 } from "@typed/id" -import { Chunk, Effect, Option, Ref } from "effect" -import { R } from "../reffuse" -import { TodosState } from "../services" - - -const createEmptyTodo = makeUuid4.pipe( - Effect.map(id => Todo.Todo.make({ id, content: "", completedAt: Option.none()}, true)), - Effect.provide(GetRandomValues.CryptoRandom), -) - - -export function VNewTodo() { - - const todoRef = R.useRef(() => createEmptyTodo) - const [content, setContent] = R.useRefState(R.useSubRefFromPath(todoRef, ["content"])) - - const add = R.useCallbackSync(() => Effect.all([TodosState.TodosState, todoRef]).pipe( - Effect.flatMap(([state, todo]) => Ref.update(state.todos, Chunk.prepend(todo))), - Effect.andThen(createEmptyTodo), - Effect.flatMap(v => Ref.set(todoRef, v)), - ), [todoRef]) - - - return ( - - - -