@@ -2,6 +2,7 @@
|
||||
/** biome-ignore-all lint/complexity/useArrowFunction: necessary for class prototypes */
|
||||
import { Cause, Context, type Duration, Effect, Equal, Equivalence, Exit, Fiber, Function, HashMap, identity, Layer, Option, Pipeable, Predicate, Ref, Scheduler, Scope, Tracer } from "effect"
|
||||
import * as React from "react"
|
||||
import * as ScopeRegistry from "./ScopeRegistry.js"
|
||||
|
||||
|
||||
export const ComponentTypeId: unique symbol = Symbol.for("@effect-fc/Component/Component")
|
||||
@@ -636,31 +637,6 @@ export const withContext: {
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* Internal Effect service that maintains a registry of scopes associated with React component instances.
|
||||
*
|
||||
* This service is used internally by the `useScope` hook to manage the lifecycle of component scopes,
|
||||
* including tracking active scopes and coordinating their cleanup when components unmount or dependencies change.
|
||||
*/
|
||||
export class ScopeMap extends Context.Service<ScopeMap, {
|
||||
readonly ref: Ref.Ref<HashMap.HashMap<object, ScopeMap.Entry>>
|
||||
}>()(
|
||||
"@effect-fc/Component/ScopeMap"
|
||||
) {
|
||||
static readonly layer = Layer.effect(ScopeMap, Effect.map(
|
||||
Ref.make(HashMap.empty<object, ScopeMap.Entry>()),
|
||||
ref => ({ ref }),
|
||||
))
|
||||
}
|
||||
|
||||
export declare namespace ScopeMap {
|
||||
export interface Entry {
|
||||
readonly scope: Scope.Closeable
|
||||
readonly closeFiber: Option.Option<Fiber.Fiber<void>>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export declare namespace useScope {
|
||||
export interface Options {
|
||||
readonly finalizerExecutionStrategy?: "sequential" | "parallel"
|
||||
@@ -694,14 +670,14 @@ export const useScope = Effect.fnUntraced(function*(
|
||||
contextRef.current = yield* Effect.context()
|
||||
|
||||
const { key, scope } = React.useMemo(() => Effect.runSyncWith(contextRef.current)(Effect.Do.pipe(
|
||||
Effect.bind("scopeMapRef", () => Effect.map(
|
||||
ScopeMap as unknown as Effect.Effect<ScopeMap["Service"]>,
|
||||
scopeMap => scopeMap.ref,
|
||||
Effect.bind("scopeRegistryRef", () => Effect.map(
|
||||
ScopeRegistry.ScopeRegistry as unknown as Effect.Effect<ScopeRegistry.ScopeRegistry["Service"]>,
|
||||
scopeRegistry => scopeRegistry.ref,
|
||||
)),
|
||||
Effect.let("key", () => Equal.byReference({})),
|
||||
Effect.bind("scope", () => Scope.make(options?.finalizerExecutionStrategy ?? defaultOptions.finalizerExecutionStrategy)),
|
||||
Effect.tap(({ scopeMapRef, key, scope }) =>
|
||||
Ref.update(scopeMapRef, HashMap.set(key, Equal.byReference({
|
||||
Effect.tap(({ scopeRegistryRef, key, scope }) =>
|
||||
Ref.update(scopeRegistryRef, HashMap.set(key, Equal.byReference({
|
||||
scope,
|
||||
closeFiber: Option.none(),
|
||||
})))
|
||||
@@ -711,8 +687,8 @@ export const useScope = Effect.fnUntraced(function*(
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: only reactive on "key"
|
||||
React.useEffect(() => Effect.runSyncWith(contextRef.current)(
|
||||
(ScopeMap as unknown as Effect.Effect<ScopeMap["Service"]>).pipe(
|
||||
Effect.map(scopeMap => scopeMap.ref),
|
||||
(ScopeRegistry.ScopeRegistry as unknown as Effect.Effect<ScopeRegistry.ScopeRegistry["Service"]>).pipe(
|
||||
Effect.map(scopeRegistry => scopeRegistry.ref),
|
||||
Effect.tap(ref => Ref.get(ref).pipe(
|
||||
Effect.flatMap(map => Effect.fromOption(HashMap.get(map, key))),
|
||||
Effect.flatMap(entry => Option.match(entry.closeFiber, {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { type Context, Effect, Layer, ManagedRuntime, Predicate } from "effect"
|
||||
import * as React from "react"
|
||||
import * as Component from "./Component.js"
|
||||
import * as ScopeRegistry from "./ScopeRegistry.js"
|
||||
|
||||
|
||||
export const ReactRuntimeTypeId: unique symbol = Symbol.for("@effect-fc/ReactRuntime/ReactRuntime")
|
||||
@@ -16,7 +17,7 @@ export interface ReactRuntime<R, ER> {
|
||||
|
||||
const ReactRuntimePrototype = Object.freeze({ [ReactRuntimeTypeId]: ReactRuntimeTypeId } as const)
|
||||
|
||||
export const preludeLayer: Layer.Layer<Component.ScopeMap> = Component.ScopeMap.layer
|
||||
export const preludeLayer: Layer.Layer<ScopeRegistry.ScopeRegistry> = ScopeRegistry.ScopeRegistry.layer
|
||||
|
||||
export const isReactRuntime = (u: unknown): u is ReactRuntime<unknown, unknown> => Predicate.hasProperty(u, ReactRuntimeTypeId)
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Context, Effect, type Fiber, HashMap, Layer, type Option, Ref, type Scope } from "effect"
|
||||
|
||||
|
||||
/**
|
||||
* Internal Effect service that maintains a registry of scopes associated with React component instances.
|
||||
*
|
||||
* This service is used internally by the `Component.useScope` hook to manage the lifecycle of component scopes,
|
||||
* including tracking active scopes and coordinating their cleanup when components unmount or dependencies change.
|
||||
*/
|
||||
export class ScopeRegistry extends Context.Service<ScopeRegistry, {
|
||||
readonly ref: Ref.Ref<HashMap.HashMap<object, ScopeRegistry.Entry>>
|
||||
}>()(
|
||||
"@effect-fc/ScopeRegistry/ScopeRegistry"
|
||||
) {
|
||||
static readonly layer = Layer.effect(ScopeRegistry, Effect.map(
|
||||
Ref.make(HashMap.empty<object, ScopeRegistry.Entry>()),
|
||||
ref => ({ ref }),
|
||||
))
|
||||
}
|
||||
|
||||
export declare namespace ScopeRegistry {
|
||||
export interface Entry {
|
||||
readonly scope: Scope.Closeable
|
||||
readonly closeFiber: Option.Option<Fiber.Fiber<void>>
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ export * as PubSub from "./PubSub.js"
|
||||
export * as Query from "./Query.js"
|
||||
export * as QueryClient from "./QueryClient.js"
|
||||
export * as ReactRuntime from "./ReactRuntime.js"
|
||||
export * as ScopeRegistry from "./ScopeRegistry.js"
|
||||
export * as SetStateAction from "./SetStateAction.js"
|
||||
export * as Stream from "./Stream.js"
|
||||
export * as View from "./View.js"
|
||||
|
||||
Reference in New Issue
Block a user