0.1.0 #1

Merged
Thilawyn merged 81 commits from next into master 2025-07-17 21:17:57 +02:00
3 changed files with 88 additions and 107 deletions
Showing only changes of commit 1f541b4234 - Show all commits

View File

@@ -1,5 +1,6 @@
import { Context, Effect, ExecutionStrategy, Exit, Function, Pipeable, Ref, Runtime, Scope, String, Tracer, type Types, type Utils } from "effect" import { Context, Effect, ExecutionStrategy, Function, Pipeable, Runtime, Scope, String, Tracer, type Types, type Utils } from "effect"
import * as React from "react" import * as React from "react"
import * as Hook from "./Hook.js"
export interface Component<E, R, P extends {}> extends Pipeable.Pipeable { export interface Component<E, R, P extends {}> extends Pipeable.Pipeable {
@@ -68,7 +69,7 @@ export const useFC: {
runtimeRef.current = yield* Effect.runtime<Exclude<R, Scope.Scope>>() runtimeRef.current = yield* Effect.runtime<Exclude<R, Scope.Scope>>()
return React.useMemo(() => function ScopeProvider(props: P) { return React.useMemo(() => function ScopeProvider(props: P) {
const scope = useScope(runtimeRef.current, self.options) const scope = Runtime.runSync(runtimeRef.current)(Hook.useScope([], self.options))
const FC = React.useMemo(() => { const FC = React.useMemo(() => {
const f = (props: P) => Runtime.runSync(runtimeRef.current)( const f = (props: P) => Runtime.runSync(runtimeRef.current)(
@@ -84,48 +85,6 @@ export const useFC: {
)) ))
}) })
const useScope = (
runtime: Runtime.Runtime<never>,
options: Options,
) => {
const [isInitialRun, initialScope] = React.useMemo(() => Runtime.runSync(runtime)(
Effect.all([Ref.make(true), Scope.make(options.finalizerExecutionStrategy)])
), [])
const [scope, setScope] = React.useState(initialScope)
React.useEffect(() => Runtime.runSync(runtime)(
Effect.if(isInitialRun, {
onTrue: () => Effect.as(
Ref.set(isInitialRun, false),
() => closeScope(scope, runtime, options),
),
onFalse: () => Scope.make(options.finalizerExecutionStrategy).pipe(
Effect.tap(scope => Effect.sync(() => setScope(scope))),
Effect.map(scope => () => closeScope(scope, runtime, options)),
),
})
), [])
return scope
}
const closeScope = (
scope: Scope.CloseableScope,
runtime: Runtime.Runtime<never>,
options: Options,
) => {
switch (options.finalizerExecutionMode) {
case "sync":
Runtime.runSync(runtime)(Scope.close(scope, Exit.void))
break
case "fork":
Runtime.runFork(runtime)(Scope.close(scope, Exit.void))
break
}
}
export const use: { export const use: {
<E, R, P extends {}>( <E, R, P extends {}>(
self: Component<E, R, P>, self: Component<E, R, P>,

View File

@@ -9,6 +9,53 @@ export interface ScopeOptions {
} }
export const useScope: {
(
deps: React.DependencyList,
options?: ScopeOptions,
): Effect.Effect<Scope.Scope>
} = Effect.fn("useScope")(function*(deps, options) {
const runtime = yield* Effect.runtime()
const [isInitialRun, initialScope] = React.useMemo(() => Runtime.runSync(runtime)(Effect.all([
Ref.make(true),
Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.parallel),
])), [])
const [scope, setScope] = React.useState(initialScope)
React.useEffect(() => Runtime.runSync(runtime)(
Effect.if(isInitialRun, {
onTrue: () => Effect.as(
Ref.set(isInitialRun, false),
() => closeScope(scope, runtime, options),
),
onFalse: () => Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.parallel).pipe(
Effect.tap(scope => Effect.sync(() => setScope(scope))),
Effect.map(scope => () => closeScope(scope, runtime, options)),
),
})
), deps)
return scope
})
const closeScope = (
scope: Scope.CloseableScope,
runtime: Runtime.Runtime<never>,
options?: ScopeOptions,
) => {
switch (options?.finalizerExecutionMode ?? "sync") {
case "sync":
Runtime.runSync(runtime)(Scope.close(scope, Exit.void))
break
case "fork":
Runtime.runFork(runtime)(Scope.close(scope, Exit.void))
break
}
}
export const useCallbackSync: { export const useCallbackSync: {
<Args extends unknown[], A, E, R>( <Args extends unknown[], A, E, R>(
callback: (...args: Args) => Effect.Effect<A, E, R>, callback: (...args: Args) => Effect.Effect<A, E, R>,
@@ -57,16 +104,6 @@ export const useOnce: {
return yield* useMemo(factory, []) return yield* useMemo(factory, [])
}) })
export const useMemoLayer: {
<ROut, E, RIn>(
layer: Layer.Layer<ROut, E, RIn>
): Effect.Effect<Context.Context<ROut>, E, RIn>
} = Effect.fn("useMemoLayer")(function* <ROut, E, RIn>(
layer: Layer.Layer<ROut, E, RIn>
) {
return yield* useMemo(() => Effect.provide(Effect.context<ROut>(), layer), [layer])
})
export const useEffect: { export const useEffect: {
<E, R>( <E, R>(
@@ -81,24 +118,14 @@ export const useEffect: {
) { ) {
const runtime = yield* Effect.runtime<Exclude<R, Scope.Scope>>() const runtime = yield* Effect.runtime<Exclude<R, Scope.Scope>>()
React.useEffect(() => { React.useEffect(() => Effect.Do.pipe(
const { scope, exit } = Effect.Do.pipe( Effect.bind("scope", () => Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential)),
Effect.bind("scope", () => Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential)), Effect.bind("exit", ({ scope }) => Effect.exit(Effect.provideService(effect(), Scope.Scope, scope))),
Effect.bind("exit", ({ scope }) => Effect.exit(Effect.provideService(effect(), Scope.Scope, scope))), Effect.map(({ scope }) =>
Runtime.runSync(runtime), () => closeScope(scope, runtime, options)
) ),
Runtime.runSync(runtime),
return () => { ), deps)
switch (options?.finalizerExecutionMode ?? "sync") {
case "sync":
Runtime.runSync(runtime)(Scope.close(scope, exit))
break
case "fork":
Runtime.runFork(runtime)(Scope.close(scope, exit))
break
}
}
}, deps)
}) })
export const useLayoutEffect: { export const useLayoutEffect: {
@@ -114,24 +141,14 @@ export const useLayoutEffect: {
) { ) {
const runtime = yield* Effect.runtime<Exclude<R, Scope.Scope>>() const runtime = yield* Effect.runtime<Exclude<R, Scope.Scope>>()
React.useLayoutEffect(() => { React.useLayoutEffect(() => Effect.Do.pipe(
const { scope, exit } = Effect.Do.pipe( Effect.bind("scope", () => Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential)),
Effect.bind("scope", () => Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential)), Effect.bind("exit", ({ scope }) => Effect.exit(Effect.provideService(effect(), Scope.Scope, scope))),
Effect.bind("exit", ({ scope }) => Effect.exit(Effect.provideService(effect(), Scope.Scope, scope))), Effect.map(({ scope }) =>
Runtime.runSync(runtime), () => closeScope(scope, runtime, options)
) ),
Runtime.runSync(runtime),
return () => { ), deps)
switch (options?.finalizerExecutionMode ?? "sync") {
case "sync":
Runtime.runSync(runtime)(Scope.close(scope, exit))
break
case "fork":
Runtime.runFork(runtime)(Scope.close(scope, exit))
break
}
}
}, deps)
}) })
export const useFork: { export const useFork: {
@@ -153,21 +170,30 @@ export const useFork: {
: Scope.make(options?.finalizerExecutionStrategy) : Scope.make(options?.finalizerExecutionStrategy)
) )
Runtime.runFork(runtime)(Effect.provideService(effect(), Scope.Scope, scope), { ...options, scope }) Runtime.runFork(runtime)(Effect.provideService(effect(), Scope.Scope, scope), { ...options, scope })
return () => closeScope(scope, runtime, options)
return () => {
switch (options?.finalizerExecutionMode ?? "fork") {
case "sync":
Runtime.runSync(runtime)(Scope.close(scope, Exit.void))
break
case "fork":
Runtime.runFork(runtime)(Scope.close(scope, Exit.void))
break
}
}
}, deps) }, deps)
}) })
export const useContextSync: {
<ROut, E, RIn>(
layer: Layer.Layer<ROut, E, RIn>,
options?: ScopeOptions,
): Effect.Effect<Context.Context<ROut>, E, Exclude<RIn, Scope.Scope>>
} = Effect.fn("useContextSync")(function* <ROut, E, RIn>(
layer: Layer.Layer<ROut, E, RIn>,
options?: ScopeOptions,
) {
const scope = yield* useScope([layer], options)
return yield* useMemo(() => Effect.provideService(
Effect.provide(Effect.context<ROut>(), layer),
Scope.Scope,
scope,
), [scope])
})
export const useRefFromReactiveValue: { export const useRefFromReactiveValue: {
<A>(value: A): Effect.Effect<SubscriptionRef.SubscriptionRef<A>> <A>(value: A): Effect.Effect<SubscriptionRef.SubscriptionRef<A>>
} = Effect.fn("useRefFromReactiveValue")(function*(value) { } = Effect.fn("useRefFromReactiveValue")(function*(value) {

View File

@@ -10,12 +10,8 @@ const TodosStateLive = TodosState.Default("todos")
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/")({
component: Component.make(function* Index() { component: Component.make(function* Index() {
return yield* Effect.provide( const context = yield* Hook.useContextSync(TodosStateLive, { finalizerExecutionMode: "fork" })
Component.use(Todos, Todos => <Todos />), return yield* Effect.provide(Component.use(Todos, Todos => <Todos />), context)
yield* Hook.useMemoLayer(TodosStateLive),
)
}, {
finalizerExecutionMode: "fork"
}).pipe( }).pipe(
Component.withRuntime(runtime.context), Component.withRuntime(runtime.context),
) )