diff --git a/packages/effect-fc/src/Component.ts b/packages/effect-fc/src/Component.ts index 8942f57..3fdb717 100644 --- a/packages/effect-fc/src/Component.ts +++ b/packages/effect-fc/src/Component.ts @@ -39,16 +39,10 @@ export namespace Component { export interface Options { readonly displayName?: string - readonly finalizerExecutionMode: "sync" | "fork" readonly finalizerExecutionStrategy: ExecutionStrategy.ExecutionStrategy } } -export interface ScopeOptions { - readonly finalizerExecutionMode?: "sync" | "fork" - readonly finalizerExecutionStrategy?: ExecutionStrategy.ExecutionStrategy -} - const ComponentProto = Object.freeze({ ...Effectable.CommitPrototype, @@ -88,10 +82,9 @@ const ComponentProto = Object.freeze({ }, } as const) -const defaultOptions = { - finalizerExecutionMode: "fork", +const defaultOptions: Component.Options = { finalizerExecutionStrategy: ExecutionStrategy.sequential, -} as const +} const nonReactiveTags = [Tracer.ParentSpan] as const @@ -419,10 +412,16 @@ export namespace ScopeMap { } +export namespace useScope { + export interface Options { + readonly finalizerExecutionStrategy?: ExecutionStrategy.ExecutionStrategy + } +} + export const useScope: { ( deps: React.DependencyList, - options?: ScopeOptions, + options?: useScope.Options, ): Effect.Effect } = Effect.fnUntraced(function*(deps, options) { // biome-ignore lint/style/noNonNullAssertion: context initialization @@ -441,7 +440,7 @@ export const useScope: { scope => Ref.update(scopeMap.ref, HashMap.set(key, { scope, closeFiber: Option.none(), - })) + })), ), }), scope => [key, scope] as const, @@ -476,21 +475,6 @@ export const useScope: { return scope }) -const closeScope = ( - scope: Scope.CloseableScope, - runtime: Runtime.Runtime, - 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 useOnMount: { ( f: () => Effect.Effect @@ -516,52 +500,69 @@ export const useOnChange: { return yield* React.useMemo(() => Runtime.runSync(runtime)(Effect.cached(f())), deps) }) +export namespace useReactEffect { + export interface Options { + readonly finalizerExecutionMode?: "sync" | "fork" + readonly finalizerExecutionStrategy?: ExecutionStrategy.ExecutionStrategy + } +} + export const useReactEffect: { ( f: () => Effect.Effect, deps?: React.DependencyList, - options?: ScopeOptions, + options?: useReactEffect.Options, ): Effect.Effect> } = Effect.fnUntraced(function* ( f: () => Effect.Effect, deps?: React.DependencyList, - options?: ScopeOptions, + options?: useReactEffect.Options, ) { const runtime = yield* Effect.runtime>() - - React.useEffect(() => Effect.Do.pipe( - Effect.bind("scope", () => Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential)), - Effect.bind("exit", ({ scope }) => Effect.exit(Effect.provideService(f(), Scope.Scope, scope))), - Effect.map(({ scope }) => - () => closeScope(scope, runtime, options) - ), - Runtime.runSync(runtime), // biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList - ), deps) + React.useEffect(() => runReactEffect(runtime, f, options), deps) }) +const runReactEffect = ( + runtime: Runtime.Runtime>, + f: () => Effect.Effect, + options?: useReactEffect.Options, +) => Effect.Do.pipe( + Effect.bind("scope", () => Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential)), + Effect.bind("exit", ({ scope }) => Effect.exit(Effect.provideService(f(), Scope.Scope, scope))), + Effect.map(({ scope }) => + () => { + 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 + } + } + ), + Runtime.runSync(runtime), +) + +export namespace useReactLayoutEffect { + export type Options = useReactEffect.Options +} + export const useReactLayoutEffect: { ( f: () => Effect.Effect, deps?: React.DependencyList, - options?: ScopeOptions, + options?: useReactLayoutEffect.Options, ): Effect.Effect> } = Effect.fnUntraced(function* ( f: () => Effect.Effect, deps?: React.DependencyList, - options?: ScopeOptions, + options?: useReactLayoutEffect.Options, ) { const runtime = yield* Effect.runtime>() - - React.useLayoutEffect(() => Effect.Do.pipe( - Effect.bind("scope", () => Scope.make(options?.finalizerExecutionStrategy ?? ExecutionStrategy.sequential)), - Effect.bind("exit", ({ scope }) => Effect.exit(Effect.provideService(f(), Scope.Scope, scope))), - Effect.map(({ scope }) => - () => closeScope(scope, runtime, options) - ), - Runtime.runSync(runtime), // biome-ignore lint/correctness/useExhaustiveDependencies: use of React.DependencyList - ), deps) + React.useLayoutEffect(() => runReactEffect(runtime, f, options), deps) }) export const useCallbackSync: { @@ -598,14 +599,18 @@ export const useCallbackPromise: { return React.useCallback((...args: Args) => Runtime.runPromise(runtimeRef.current)(f(...args)), deps) }) +export namespace useContext { + export type Options = useScope.Options +} + export const useContext: { ( layer: Layer.Layer, - options?: ScopeOptions, + options?: useContext.Options, ): Effect.Effect, E, RIn> } = Effect.fnUntraced(function* ( layer: Layer.Layer, - options?: ScopeOptions, + options?: useContext.Options, ) { const scope = yield* useScope([layer], options)