Compare commits
186 Commits
master
...
46211638f5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -11,6 +11,7 @@
|
|||||||
// Import Routes
|
// Import Routes
|
||||||
|
|
||||||
import { Route as rootRoute } from './routes/__root'
|
import { Route as rootRoute } from './routes/__root'
|
||||||
|
import { Route as TodosImport } from './routes/todos'
|
||||||
import { Route as TimeImport } from './routes/time'
|
import { Route as TimeImport } from './routes/time'
|
||||||
import { Route as TestsImport } from './routes/tests'
|
import { Route as TestsImport } from './routes/tests'
|
||||||
import { Route as PromiseImport } from './routes/promise'
|
import { Route as PromiseImport } from './routes/promise'
|
||||||
@@ -24,6 +25,12 @@ import { Route as QueryServiceImport } from './routes/query/service'
|
|||||||
|
|
||||||
// Create/Update Routes
|
// Create/Update Routes
|
||||||
|
|
||||||
|
const TodosRoute = TodosImport.update({
|
||||||
|
id: '/todos',
|
||||||
|
path: '/todos',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const TimeRoute = TimeImport.update({
|
const TimeRoute = TimeImport.update({
|
||||||
id: '/time',
|
id: '/time',
|
||||||
path: '/time',
|
path: '/time',
|
||||||
@@ -137,6 +144,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof TimeImport
|
preLoaderRoute: typeof TimeImport
|
||||||
parentRoute: typeof rootRoute
|
parentRoute: typeof rootRoute
|
||||||
}
|
}
|
||||||
|
'/todos': {
|
||||||
|
id: '/todos'
|
||||||
|
path: '/todos'
|
||||||
|
fullPath: '/todos'
|
||||||
|
preLoaderRoute: typeof TodosImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
'/query/service': {
|
'/query/service': {
|
||||||
id: '/query/service'
|
id: '/query/service'
|
||||||
path: '/query/service'
|
path: '/query/service'
|
||||||
@@ -171,6 +185,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/promise': typeof PromiseRoute
|
'/promise': typeof PromiseRoute
|
||||||
'/tests': typeof TestsRoute
|
'/tests': typeof TestsRoute
|
||||||
'/time': typeof TimeRoute
|
'/time': typeof TimeRoute
|
||||||
|
'/todos': typeof TodosRoute
|
||||||
'/query/service': typeof QueryServiceRoute
|
'/query/service': typeof QueryServiceRoute
|
||||||
'/query/usemutation': typeof QueryUsemutationRoute
|
'/query/usemutation': typeof QueryUsemutationRoute
|
||||||
'/query/usequery': typeof QueryUsequeryRoute
|
'/query/usequery': typeof QueryUsequeryRoute
|
||||||
@@ -184,6 +199,7 @@ export interface FileRoutesByTo {
|
|||||||
'/promise': typeof PromiseRoute
|
'/promise': typeof PromiseRoute
|
||||||
'/tests': typeof TestsRoute
|
'/tests': typeof TestsRoute
|
||||||
'/time': typeof TimeRoute
|
'/time': typeof TimeRoute
|
||||||
|
'/todos': typeof TodosRoute
|
||||||
'/query/service': typeof QueryServiceRoute
|
'/query/service': typeof QueryServiceRoute
|
||||||
'/query/usemutation': typeof QueryUsemutationRoute
|
'/query/usemutation': typeof QueryUsemutationRoute
|
||||||
'/query/usequery': typeof QueryUsequeryRoute
|
'/query/usequery': typeof QueryUsequeryRoute
|
||||||
@@ -198,6 +214,7 @@ export interface FileRoutesById {
|
|||||||
'/promise': typeof PromiseRoute
|
'/promise': typeof PromiseRoute
|
||||||
'/tests': typeof TestsRoute
|
'/tests': typeof TestsRoute
|
||||||
'/time': typeof TimeRoute
|
'/time': typeof TimeRoute
|
||||||
|
'/todos': typeof TodosRoute
|
||||||
'/query/service': typeof QueryServiceRoute
|
'/query/service': typeof QueryServiceRoute
|
||||||
'/query/usemutation': typeof QueryUsemutationRoute
|
'/query/usemutation': typeof QueryUsemutationRoute
|
||||||
'/query/usequery': typeof QueryUsequeryRoute
|
'/query/usequery': typeof QueryUsequeryRoute
|
||||||
@@ -213,6 +230,7 @@ export interface FileRouteTypes {
|
|||||||
| '/promise'
|
| '/promise'
|
||||||
| '/tests'
|
| '/tests'
|
||||||
| '/time'
|
| '/time'
|
||||||
|
| '/todos'
|
||||||
| '/query/service'
|
| '/query/service'
|
||||||
| '/query/usemutation'
|
| '/query/usemutation'
|
||||||
| '/query/usequery'
|
| '/query/usequery'
|
||||||
@@ -225,6 +243,7 @@ export interface FileRouteTypes {
|
|||||||
| '/promise'
|
| '/promise'
|
||||||
| '/tests'
|
| '/tests'
|
||||||
| '/time'
|
| '/time'
|
||||||
|
| '/todos'
|
||||||
| '/query/service'
|
| '/query/service'
|
||||||
| '/query/usemutation'
|
| '/query/usemutation'
|
||||||
| '/query/usequery'
|
| '/query/usequery'
|
||||||
@@ -237,6 +256,7 @@ export interface FileRouteTypes {
|
|||||||
| '/promise'
|
| '/promise'
|
||||||
| '/tests'
|
| '/tests'
|
||||||
| '/time'
|
| '/time'
|
||||||
|
| '/todos'
|
||||||
| '/query/service'
|
| '/query/service'
|
||||||
| '/query/usemutation'
|
| '/query/usemutation'
|
||||||
| '/query/usequery'
|
| '/query/usequery'
|
||||||
@@ -251,6 +271,7 @@ export interface RootRouteChildren {
|
|||||||
PromiseRoute: typeof PromiseRoute
|
PromiseRoute: typeof PromiseRoute
|
||||||
TestsRoute: typeof TestsRoute
|
TestsRoute: typeof TestsRoute
|
||||||
TimeRoute: typeof TimeRoute
|
TimeRoute: typeof TimeRoute
|
||||||
|
TodosRoute: typeof TodosRoute
|
||||||
QueryServiceRoute: typeof QueryServiceRoute
|
QueryServiceRoute: typeof QueryServiceRoute
|
||||||
QueryUsemutationRoute: typeof QueryUsemutationRoute
|
QueryUsemutationRoute: typeof QueryUsemutationRoute
|
||||||
QueryUsequeryRoute: typeof QueryUsequeryRoute
|
QueryUsequeryRoute: typeof QueryUsequeryRoute
|
||||||
@@ -264,6 +285,7 @@ const rootRouteChildren: RootRouteChildren = {
|
|||||||
PromiseRoute: PromiseRoute,
|
PromiseRoute: PromiseRoute,
|
||||||
TestsRoute: TestsRoute,
|
TestsRoute: TestsRoute,
|
||||||
TimeRoute: TimeRoute,
|
TimeRoute: TimeRoute,
|
||||||
|
TodosRoute: TodosRoute,
|
||||||
QueryServiceRoute: QueryServiceRoute,
|
QueryServiceRoute: QueryServiceRoute,
|
||||||
QueryUsemutationRoute: QueryUsemutationRoute,
|
QueryUsemutationRoute: QueryUsemutationRoute,
|
||||||
QueryUsequeryRoute: QueryUsequeryRoute,
|
QueryUsequeryRoute: QueryUsequeryRoute,
|
||||||
@@ -286,6 +308,7 @@ export const routeTree = rootRoute
|
|||||||
"/promise",
|
"/promise",
|
||||||
"/tests",
|
"/tests",
|
||||||
"/time",
|
"/time",
|
||||||
|
"/todos",
|
||||||
"/query/service",
|
"/query/service",
|
||||||
"/query/usemutation",
|
"/query/usemutation",
|
||||||
"/query/usequery"
|
"/query/usequery"
|
||||||
@@ -312,6 +335,9 @@ export const routeTree = rootRoute
|
|||||||
"/time": {
|
"/time": {
|
||||||
"filePath": "time.tsx"
|
"filePath": "time.tsx"
|
||||||
},
|
},
|
||||||
|
"/todos": {
|
||||||
|
"filePath": "todos.tsx"
|
||||||
|
},
|
||||||
"/query/service": {
|
"/query/service": {
|
||||||
"filePath": "query/service.tsx"
|
"filePath": "query/service.tsx"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,29 +1,10 @@
|
|||||||
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 { createFileRoute } from "@tanstack/react-router"
|
||||||
import { Layer } from "effect"
|
|
||||||
import { useMemo } from "react"
|
|
||||||
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/")({
|
export const Route = createFileRoute('/')({
|
||||||
component: Index
|
component: RouteComponent
|
||||||
})
|
})
|
||||||
|
|
||||||
function Index() {
|
function RouteComponent() {
|
||||||
|
return <div>Hello "/"!</div>
|
||||||
const todosLayer = useMemo(() => Layer.empty.pipe(
|
|
||||||
Layer.provideMerge(TodosState.make("todos"))
|
|
||||||
), [])
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Container>
|
|
||||||
<TodosContext.Provider layer={todosLayer}>
|
|
||||||
<VTodos />
|
|
||||||
</TodosContext.Provider>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
35
packages/example/src/routes/todos.tsx
Normal file
35
packages/example/src/routes/todos.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
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 (
|
||||||
|
<Container>
|
||||||
|
<TodosContext.Provider layer={todosLayer}>
|
||||||
|
<VTodos />
|
||||||
|
</TodosContext.Provider>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ export class Reffuse extends ReffuseHelpers.make() {}
|
|||||||
|
|
||||||
export const withContexts = <R2 extends Array<unknown>>(
|
export const withContexts = <R2 extends Array<unknown>>(
|
||||||
...contexts: [...{ [K in keyof R2]: ReffuseContext.ReffuseContext<R2[K]> }]
|
...contexts: [...{ [K in keyof R2]: ReffuseContext.ReffuseContext<R2[K]> }]
|
||||||
) =>
|
) => (
|
||||||
<
|
<
|
||||||
BaseClass extends ReffuseHelpers.ReffuseHelpersClass<R1>,
|
BaseClass extends ReffuseHelpers.ReffuseHelpersClass<R1>,
|
||||||
R1
|
R1
|
||||||
@@ -29,9 +29,9 @@ export const withContexts = <R2 extends Array<unknown>>(
|
|||||||
) => class extends self {
|
) => class extends self {
|
||||||
static readonly contexts = [...self.contexts, ...contexts]
|
static readonly contexts = [...self.contexts, ...contexts]
|
||||||
} as any
|
} as any
|
||||||
|
)
|
||||||
|
|
||||||
|
export const withExtension = <A extends object>(extension: ReffuseExtension.ReffuseExtension<A>) => (
|
||||||
export const withExtension = <A extends object>(extension: ReffuseExtension.ReffuseExtension<A>) =>
|
|
||||||
<
|
<
|
||||||
BaseClass extends ReffuseHelpers.ReffuseHelpersClass<R>,
|
BaseClass extends ReffuseHelpers.ReffuseHelpersClass<R>,
|
||||||
R
|
R
|
||||||
@@ -45,3 +45,4 @@ export const withExtension = <A extends object>(extension: ReffuseExtension.Reff
|
|||||||
Object.assign(class_.prototype, extension())
|
Object.assign(class_.prototype, extension())
|
||||||
return class_ as any
|
return class_ as any
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|||||||
160
packages/reffuse/src/ReffuseContext.ts
Normal file
160
packages/reffuse/src/ReffuseContext.ts
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import { Array, Context, Effect, ExecutionStrategy, Exit, Layer, Ref, Runtime, Scope } from "effect"
|
||||||
|
import * as React from "react"
|
||||||
|
import * as ReffuseRuntime from "./ReffuseRuntime.js"
|
||||||
|
|
||||||
|
|
||||||
|
export class ReffuseContext<R> {
|
||||||
|
readonly Context = React.createContext<Context.Context<R>>(null!)
|
||||||
|
readonly Provider = makeProvider(this.Context)
|
||||||
|
readonly AsyncProvider = makeAsyncProvider(this.Context)
|
||||||
|
|
||||||
|
|
||||||
|
useContext(): Context.Context<R> {
|
||||||
|
return React.useContext(this.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
useLayer(): Layer.Layer<R> {
|
||||||
|
const context = this.useContext()
|
||||||
|
return React.useMemo(() => Layer.effectContext(Effect.succeed(context)), [context])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type R<T> = T extends ReffuseContext<infer R> ? R : never
|
||||||
|
|
||||||
|
|
||||||
|
export type ReactProvider<R> = React.FC<{
|
||||||
|
readonly layer: Layer.Layer<R, unknown, Scope.Scope>
|
||||||
|
readonly scope?: Scope.Scope
|
||||||
|
readonly children?: React.ReactNode
|
||||||
|
}>
|
||||||
|
|
||||||
|
const makeProvider = <R>(Context: React.Context<Context.Context<R>>): ReactProvider<R> => {
|
||||||
|
return function ReffuseContextReactProvider(props) {
|
||||||
|
const runtime = ReffuseRuntime.useRuntime()
|
||||||
|
const runSync = React.useMemo(() => Runtime.runSync(runtime), [runtime])
|
||||||
|
|
||||||
|
const makeScope = React.useMemo(() => props.scope
|
||||||
|
? Scope.fork(props.scope, ExecutionStrategy.sequential)
|
||||||
|
: Scope.make(),
|
||||||
|
[props.scope])
|
||||||
|
|
||||||
|
const makeContext = React.useCallback((scope: Scope.CloseableScope) => Effect.context<R>().pipe(
|
||||||
|
Effect.provide(props.layer),
|
||||||
|
Effect.provideService(Scope.Scope, scope),
|
||||||
|
), [props.layer])
|
||||||
|
|
||||||
|
const [isInitialRun, initialScope, initialValue] = React.useMemo(() => Effect.Do.pipe(
|
||||||
|
Effect.bind("isInitialRun", () => Ref.make(true)),
|
||||||
|
Effect.bind("scope", () => makeScope),
|
||||||
|
Effect.bind("context", ({ scope }) => makeContext(scope)),
|
||||||
|
Effect.map(({ isInitialRun, scope, context }) => [isInitialRun, scope, context] as const),
|
||||||
|
runSync,
|
||||||
|
), [])
|
||||||
|
|
||||||
|
const [value, setValue] = React.useState(initialValue)
|
||||||
|
|
||||||
|
React.useEffect(() => isInitialRun.pipe(
|
||||||
|
Effect.if({
|
||||||
|
onTrue: () => Ref.set(isInitialRun, false).pipe(
|
||||||
|
Effect.map(() =>
|
||||||
|
() => runSync(Scope.close(initialScope, Exit.void))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
onFalse: () => Effect.Do.pipe(
|
||||||
|
Effect.bind("scope", () => makeScope),
|
||||||
|
Effect.bind("context", ({ scope }) => makeContext(scope)),
|
||||||
|
Effect.tap(({ context }) =>
|
||||||
|
Effect.sync(() => setValue(context))
|
||||||
|
),
|
||||||
|
Effect.map(({ scope }) =>
|
||||||
|
() => runSync(Scope.close(scope, Exit.void))
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
|
||||||
|
runSync,
|
||||||
|
), [makeScope, makeContext, runSync])
|
||||||
|
|
||||||
|
return React.createElement(Context, { ...props, value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AsyncReactProvider<R> = React.FC<{
|
||||||
|
readonly layer: Layer.Layer<R, unknown, Scope.Scope>
|
||||||
|
readonly scope?: Scope.Scope
|
||||||
|
readonly finalizerExecutionStrategy?: ExecutionStrategy.ExecutionStrategy
|
||||||
|
readonly fallback?: React.ReactNode
|
||||||
|
readonly children?: React.ReactNode
|
||||||
|
}>
|
||||||
|
|
||||||
|
const makeAsyncProvider = <R>(Context: React.Context<Context.Context<R>>): AsyncReactProvider<R> => {
|
||||||
|
function ReffuseContextAsyncReactProviderInner({ promise, children }: {
|
||||||
|
readonly promise: Promise<Context.Context<R>>
|
||||||
|
readonly children?: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return React.createElement(Context, {
|
||||||
|
value: React.use(promise),
|
||||||
|
children,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return function ReffuseContextAsyncReactProvider(props) {
|
||||||
|
const runtime = ReffuseRuntime.useRuntime()
|
||||||
|
const runSync = React.useMemo(() => Runtime.runSync(runtime), [runtime])
|
||||||
|
const runFork = React.useMemo(() => Runtime.runFork(runtime), [runtime])
|
||||||
|
|
||||||
|
const [promise, setPromise] = React.useState(Promise.withResolvers<Context.Context<R>>().promise)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const { promise, resolve, reject } = Promise.withResolvers<Context.Context<R>>()
|
||||||
|
setPromise(promise)
|
||||||
|
|
||||||
|
const scope = runSync(props.scope
|
||||||
|
? Scope.fork(props.scope, props.finalizerExecutionStrategy ?? ExecutionStrategy.sequential)
|
||||||
|
: Scope.make(props.finalizerExecutionStrategy)
|
||||||
|
)
|
||||||
|
|
||||||
|
Effect.context<R>().pipe(
|
||||||
|
Effect.match({
|
||||||
|
onSuccess: resolve,
|
||||||
|
onFailure: reject,
|
||||||
|
}),
|
||||||
|
|
||||||
|
Effect.provide(props.layer),
|
||||||
|
Effect.provideService(Scope.Scope, scope),
|
||||||
|
effect => runFork(effect, { ...props, scope }),
|
||||||
|
)
|
||||||
|
|
||||||
|
return () => { runFork(Scope.close(scope, Exit.void)) }
|
||||||
|
}, [props.layer, runSync, runFork])
|
||||||
|
|
||||||
|
return React.createElement(React.Suspense, {
|
||||||
|
children: React.createElement(ReffuseContextAsyncReactProviderInner, { ...props, promise }),
|
||||||
|
fallback: props.fallback,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const make = <R = never>() => new ReffuseContext<R>()
|
||||||
|
|
||||||
|
export const useMergeAll = <T extends Array<unknown>>(
|
||||||
|
...contexts: [...{ [K in keyof T]: ReffuseContext<T[K]> }]
|
||||||
|
): Context.Context<T[number]> => {
|
||||||
|
const values = contexts.map(v => React.use(v.Context))
|
||||||
|
return React.useMemo(() => Context.mergeAll(...values), values)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useMergeAllLayers = <T extends Array<unknown>>(
|
||||||
|
...contexts: [...{ [K in keyof T]: ReffuseContext<T[K]> }]
|
||||||
|
): Layer.Layer<T[number]> => {
|
||||||
|
const values = contexts.map(v => React.use(v.Context))
|
||||||
|
|
||||||
|
return React.useMemo(() => Array.isNonEmptyArray(values)
|
||||||
|
? Layer.mergeAll(
|
||||||
|
...Array.map(values, context => Layer.effectContext(Effect.succeed(context)))
|
||||||
|
)
|
||||||
|
: Layer.empty as Layer.Layer<T[number]>,
|
||||||
|
values)
|
||||||
|
}
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
import { Array, Context, Effect, Layer, Runtime } from "effect"
|
|
||||||
import * as React from "react"
|
|
||||||
import * as ReffuseRuntime from "./ReffuseRuntime.js"
|
|
||||||
|
|
||||||
|
|
||||||
export class ReffuseContext<R> {
|
|
||||||
readonly Context = React.createContext<Context.Context<R>>(null!)
|
|
||||||
readonly Provider = makeProvider(this.Context)
|
|
||||||
readonly AsyncProvider = makeAsyncProvider(this.Context)
|
|
||||||
|
|
||||||
|
|
||||||
useContext(): Context.Context<R> {
|
|
||||||
return React.useContext(this.Context)
|
|
||||||
}
|
|
||||||
|
|
||||||
useLayer(): Layer.Layer<R> {
|
|
||||||
const context = this.useContext()
|
|
||||||
return React.useMemo(() => Layer.effectContext(Effect.succeed(context)), [context])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type R<T> = T extends ReffuseContext<infer R> ? R : never
|
|
||||||
|
|
||||||
|
|
||||||
export type ReactProvider<R> = React.FC<{
|
|
||||||
readonly layer: Layer.Layer<R, unknown>
|
|
||||||
readonly children?: React.ReactNode
|
|
||||||
}>
|
|
||||||
|
|
||||||
function makeProvider<R>(Context: React.Context<Context.Context<R>>): ReactProvider<R> {
|
|
||||||
return function ReffuseContextReactProvider(props) {
|
|
||||||
const runtime = ReffuseRuntime.useRuntime()
|
|
||||||
|
|
||||||
const value = React.useMemo(() => Effect.context<R>().pipe(
|
|
||||||
Effect.provide(props.layer),
|
|
||||||
Runtime.runSync(runtime),
|
|
||||||
), [props.layer, runtime])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Context
|
|
||||||
{...props}
|
|
||||||
value={value}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AsyncReactProvider<R> = React.FC<{
|
|
||||||
readonly layer: Layer.Layer<R, unknown>
|
|
||||||
readonly fallback?: React.ReactNode
|
|
||||||
readonly children?: React.ReactNode
|
|
||||||
}>
|
|
||||||
|
|
||||||
function makeAsyncProvider<R>(Context: React.Context<Context.Context<R>>): AsyncReactProvider<R> {
|
|
||||||
function Inner({ promise, children }: {
|
|
||||||
readonly promise: Promise<Context.Context<R>>
|
|
||||||
readonly children?: React.ReactNode
|
|
||||||
}) {
|
|
||||||
const value = React.use(promise)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Context
|
|
||||||
value={value}
|
|
||||||
children={children}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return function ReffuseContextAsyncReactProvider(props) {
|
|
||||||
const runtime = ReffuseRuntime.useRuntime()
|
|
||||||
|
|
||||||
const promise = React.useMemo(() => Effect.context<R>().pipe(
|
|
||||||
Effect.provide(props.layer),
|
|
||||||
Runtime.runPromise(runtime),
|
|
||||||
), [props.layer, runtime])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Suspense fallback={props.fallback}>
|
|
||||||
<Inner
|
|
||||||
{...props}
|
|
||||||
promise={promise}
|
|
||||||
/>
|
|
||||||
</React.Suspense>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function make<R = never>() {
|
|
||||||
return new ReffuseContext<R>()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useMergeAll<T extends Array<unknown>>(
|
|
||||||
...contexts: [...{ [K in keyof T]: ReffuseContext<T[K]> }]
|
|
||||||
): Context.Context<T[number]> {
|
|
||||||
const values = contexts.map(v => React.use(v.Context))
|
|
||||||
return React.useMemo(() => Context.mergeAll(...values), values)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useMergeAllLayers<T extends Array<unknown>>(
|
|
||||||
...contexts: [...{ [K in keyof T]: ReffuseContext<T[K]> }]
|
|
||||||
): Layer.Layer<T[number]> {
|
|
||||||
const values = contexts.map(v => React.use(v.Context))
|
|
||||||
|
|
||||||
return React.useMemo(() => Array.isNonEmptyArray(values)
|
|
||||||
? Layer.mergeAll(
|
|
||||||
...Array.map(values, context => Layer.effectContext(Effect.succeed(context)))
|
|
||||||
)
|
|
||||||
: Layer.empty as Layer.Layer<T[number]>,
|
|
||||||
values)
|
|
||||||
}
|
|
||||||
@@ -108,41 +108,38 @@ export abstract class ReffuseHelpers<R> {
|
|||||||
): A {
|
): A {
|
||||||
const runSync = this.useRunSync()
|
const runSync = this.useRunSync()
|
||||||
|
|
||||||
// Calculate an initial version of the value so that it can be accessed during the first render
|
const [isInitialRun, initialScope, initialValue] = React.useMemo(() => Effect.Do.pipe(
|
||||||
const [initialScope, initialValue] = React.useMemo(() => Scope.make(options?.finalizerExecutionStrategy).pipe(
|
Effect.bind("isInitialRun", () => Ref.make(true)),
|
||||||
Effect.flatMap(scope => effect().pipe(
|
Effect.bind("scope", () => Scope.make(options?.finalizerExecutionStrategy)),
|
||||||
Effect.provideService(Scope.Scope, scope),
|
Effect.bind("value", ({ scope }) => Effect.provideService(effect(), Scope.Scope, scope)),
|
||||||
Effect.map(value => [scope, value] as const),
|
Effect.map(({ isInitialRun, scope, value }) => [isInitialRun, scope, value] as const),
|
||||||
)),
|
|
||||||
|
|
||||||
runSync,
|
runSync,
|
||||||
), [])
|
), [])
|
||||||
|
|
||||||
// Keep track of the state of the initial scope
|
|
||||||
const initialScopeClosed = React.useRef(false)
|
|
||||||
|
|
||||||
const [value, setValue] = React.useState(initialValue)
|
const [value, setValue] = React.useState(initialValue)
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => isInitialRun.pipe(
|
||||||
const closeInitialScopeIfNeeded = Scope.close(initialScope, Exit.void).pipe(
|
Effect.if({
|
||||||
Effect.andThen(Effect.sync(() => { initialScopeClosed.current = true })),
|
onTrue: () => Ref.set(isInitialRun, false).pipe(
|
||||||
Effect.when(() => !initialScopeClosed.current),
|
Effect.map(() =>
|
||||||
)
|
() => runSync(Scope.close(initialScope, Exit.void))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
const [scope, value] = closeInitialScopeIfNeeded.pipe(
|
onFalse: () => Effect.Do.pipe(
|
||||||
Effect.andThen(Scope.make(options?.finalizerExecutionStrategy).pipe(
|
Effect.bind("scope", () => Scope.make(options?.finalizerExecutionStrategy)),
|
||||||
Effect.flatMap(scope => effect().pipe(
|
Effect.bind("value", ({ scope }) => Effect.provideService(effect(), Scope.Scope, scope)),
|
||||||
Effect.provideService(Scope.Scope, scope),
|
Effect.tap(({ value }) =>
|
||||||
Effect.map(value => [scope, value] as const),
|
Effect.sync(() => setValue(value))
|
||||||
))
|
),
|
||||||
)),
|
Effect.map(({ scope }) =>
|
||||||
|
() => runSync(Scope.close(scope, Exit.void))
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
|
||||||
runSync,
|
runSync,
|
||||||
)
|
), [
|
||||||
|
|
||||||
setValue(value)
|
|
||||||
return () => { runSync(Scope.close(scope, Exit.void)) }
|
|
||||||
}, [
|
|
||||||
...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync],
|
...options?.doNotReExecuteOnRuntimeOrContextChange ? [] : [runSync],
|
||||||
...deps,
|
...deps,
|
||||||
])
|
])
|
||||||
@@ -428,20 +425,26 @@ export interface ReffuseHelpers<R> extends Pipeable.Pipeable {}
|
|||||||
|
|
||||||
ReffuseHelpers.prototype.pipe = function pipe() {
|
ReffuseHelpers.prototype.pipe = function pipe() {
|
||||||
return Pipeable.pipeArguments(this, arguments)
|
return Pipeable.pipeArguments(this, arguments)
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export interface ReffuseHelpersClass<R> extends Pipeable.Pipeable {
|
export interface ReffuseHelpersClass<R> extends Pipeable.Pipeable {
|
||||||
new(): ReffuseHelpers<R>
|
new(): ReffuseHelpers<R>
|
||||||
|
make<Self>(this: new () => Self): Self
|
||||||
readonly contexts: readonly ReffuseContext.ReffuseContext<R>[]
|
readonly contexts: readonly ReffuseContext.ReffuseContext<R>[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(ReffuseHelpers as ReffuseHelpersClass<any>).make = function make() {
|
||||||
|
return new this()
|
||||||
|
};
|
||||||
|
|
||||||
(ReffuseHelpers as ReffuseHelpersClass<any>).pipe = function pipe() {
|
(ReffuseHelpers as ReffuseHelpersClass<any>).pipe = function pipe() {
|
||||||
return Pipeable.pipeArguments(this, arguments)
|
return Pipeable.pipeArguments(this, arguments)
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
export const make = (): ReffuseHelpersClass<never> =>
|
export const make = (): ReffuseHelpersClass<never> => (
|
||||||
class extends (ReffuseHelpers<never> as ReffuseHelpersClass<never>) {
|
class extends (ReffuseHelpers<never> as ReffuseHelpersClass<never>) {
|
||||||
static readonly contexts = []
|
static readonly contexts = []
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|||||||
16
packages/reffuse/src/ReffuseRuntime.ts
Normal file
16
packages/reffuse/src/ReffuseRuntime.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Runtime } from "effect"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
|
||||||
|
export const Context = React.createContext<Runtime.Runtime<never>>(null!)
|
||||||
|
|
||||||
|
export const Provider = function ReffuseRuntimeReactProvider(props: {
|
||||||
|
readonly children?: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return React.createElement(Context, {
|
||||||
|
...props,
|
||||||
|
value: Runtime.defaultRuntime,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useRuntime = () => React.useContext(Context)
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { Runtime } from "effect"
|
|
||||||
import * as React from "react"
|
|
||||||
|
|
||||||
|
|
||||||
export const Context = React.createContext<Runtime.Runtime<never>>(null!)
|
|
||||||
|
|
||||||
export const Provider = (props: { readonly children?: React.ReactNode }) => (
|
|
||||||
<Context
|
|
||||||
{...props}
|
|
||||||
value={Runtime.defaultRuntime}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
Provider.displayName = "ReffuseRuntimeReactProvider"
|
|
||||||
|
|
||||||
export const useRuntime = () => React.useContext(Context)
|
|
||||||
Reference in New Issue
Block a user