@@ -153,12 +153,20 @@ export interface ComponentOptions {
|
|||||||
* @default "100 millis"
|
* @default "100 millis"
|
||||||
*/
|
*/
|
||||||
readonly finalizerExecutionDebounce: Duration.Input
|
readonly finalizerExecutionDebounce: Duration.Input
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum duration an uncommitted scope may remain registered before it is considered abandoned.
|
||||||
|
*
|
||||||
|
* @default "30 seconds"
|
||||||
|
*/
|
||||||
|
readonly scopeCommitTimeout: Duration.Input
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultOptions: ComponentOptions = {
|
export const defaultOptions: ComponentOptions = {
|
||||||
nonReactiveTags: [Tracer.ParentSpan, Scheduler.Scheduler, Layer.CurrentMemoMap],
|
nonReactiveTags: [Tracer.ParentSpan, Scheduler.Scheduler, Layer.CurrentMemoMap],
|
||||||
finalizerExecutionStrategy: "sequential",
|
finalizerExecutionStrategy: "sequential",
|
||||||
finalizerExecutionDebounce: "100 millis",
|
finalizerExecutionDebounce: "100 millis",
|
||||||
|
scopeCommitTimeout: "1 minute",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -641,6 +649,7 @@ export declare namespace useScope {
|
|||||||
export interface Options {
|
export interface Options {
|
||||||
readonly finalizerExecutionStrategy?: "sequential" | "parallel"
|
readonly finalizerExecutionStrategy?: "sequential" | "parallel"
|
||||||
readonly finalizerExecutionDebounce?: Duration.Input
|
readonly finalizerExecutionDebounce?: Duration.Input
|
||||||
|
readonly scopeCommitTimeout?: Duration.Input
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -675,6 +684,7 @@ export const useScope = Effect.fnUntraced(function*(
|
|||||||
const entry = yield* registry.register(key, {
|
const entry = yield* registry.register(key, {
|
||||||
finalizerExecutionStrategy: options?.finalizerExecutionStrategy ?? defaultOptions.finalizerExecutionStrategy,
|
finalizerExecutionStrategy: options?.finalizerExecutionStrategy ?? defaultOptions.finalizerExecutionStrategy,
|
||||||
finalizerExecutionDebounce: options?.finalizerExecutionDebounce ?? defaultOptions.finalizerExecutionDebounce,
|
finalizerExecutionDebounce: options?.finalizerExecutionDebounce ?? defaultOptions.finalizerExecutionDebounce,
|
||||||
|
scopeCommitTimeout: options?.scopeCommitTimeout ?? defaultOptions.scopeCommitTimeout,
|
||||||
})
|
})
|
||||||
|
|
||||||
return [key, entry.scope]
|
return [key, entry.scope]
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export declare namespace ScopeRegistryService {
|
|||||||
export interface RegisterOptions {
|
export interface RegisterOptions {
|
||||||
readonly finalizerExecutionStrategy: "sequential" | "parallel"
|
readonly finalizerExecutionStrategy: "sequential" | "parallel"
|
||||||
readonly finalizerExecutionDebounce: Duration.Input
|
readonly finalizerExecutionDebounce: Duration.Input
|
||||||
|
readonly scopeCommitTimeout: Duration.Input
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Entry {
|
export interface Entry {
|
||||||
@@ -45,7 +46,7 @@ export class ScopeRegistryServiceImpl implements ScopeRegistryService {
|
|||||||
return Effect.gen({ self: this }, function*() {
|
return Effect.gen({ self: this }, function*() {
|
||||||
const entry = Equal.byReference({
|
const entry = Equal.byReference({
|
||||||
scope: yield* Scope.make(options.finalizerExecutionStrategy),
|
scope: yield* Scope.make(options.finalizerExecutionStrategy),
|
||||||
expiresAt: Option.some(DateTime.addDuration(yield* DateTime.now, options.finalizerExecutionDebounce)),
|
expiresAt: Option.some(DateTime.addDuration(yield* DateTime.now, options.scopeCommitTimeout)),
|
||||||
finalizerExecutionDebounce: options.finalizerExecutionDebounce,
|
finalizerExecutionDebounce: options.finalizerExecutionDebounce,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -374,6 +374,7 @@ describe("Component", () => {
|
|||||||
React.use(pending)
|
React.use(pending)
|
||||||
return <div>committed</div>
|
return <div>committed</div>
|
||||||
}).pipe(
|
}).pipe(
|
||||||
|
Component.withOptions({ scopeCommitTimeout: "10 millis" }),
|
||||||
Component.withContext(runtime.context)
|
Component.withContext(runtime.context)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -414,7 +415,10 @@ describe("Component", () => {
|
|||||||
|
|
||||||
return <div>committed</div>
|
return <div>committed</div>
|
||||||
}).pipe(
|
}).pipe(
|
||||||
Component.withOptions({ finalizerExecutionDebounce: "10 millis" }),
|
Component.withOptions({
|
||||||
|
finalizerExecutionDebounce: "10 millis",
|
||||||
|
scopeCommitTimeout: "10 millis",
|
||||||
|
}),
|
||||||
Component.withContext(runtime.context),
|
Component.withContext(runtime.context),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -437,6 +441,42 @@ describe("Component", () => {
|
|||||||
await runtime.runtime.dispose()
|
await runtime.runtime.dispose()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("does not expire a scope while a descendant is suspended past the finalizer debounce", async () => {
|
||||||
|
const runtime = ReactRuntime.make(Layer.empty)
|
||||||
|
let resolve!: () => void
|
||||||
|
const pending = new Promise<void>(complete => {
|
||||||
|
resolve = complete
|
||||||
|
})
|
||||||
|
|
||||||
|
const Suspended = () => {
|
||||||
|
React.use(pending)
|
||||||
|
return <div>committed</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
let view!: ReturnType<typeof render>
|
||||||
|
await act(async () => {
|
||||||
|
view = render(
|
||||||
|
<ReactRuntime.Provider runtime={runtime} fallback={<div>fallback</div>}>
|
||||||
|
<Suspended />
|
||||||
|
</ReactRuntime.Provider>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
await screen.findByText("fallback")
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 150))
|
||||||
|
await act(async () => {
|
||||||
|
resolve()
|
||||||
|
await pending
|
||||||
|
})
|
||||||
|
await screen.findByText("committed")
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
view.unmount()
|
||||||
|
await runtime.runtime.dispose()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it("clears ScopeRegistry after a suspended render retries, commits, and unmounts", async () => {
|
it("clears ScopeRegistry after a suspended render retries, commits, and unmounts", async () => {
|
||||||
const lifecycle = vi.fn<(message: string) => void>()
|
const lifecycle = vi.fn<(message: string) => void>()
|
||||||
const runtime = ReactRuntime.make(Layer.empty)
|
const runtime = ReactRuntime.make(Layer.empty)
|
||||||
@@ -455,6 +495,7 @@ describe("Component", () => {
|
|||||||
}), [])
|
}), [])
|
||||||
return <div>committed</div>
|
return <div>committed</div>
|
||||||
}).pipe(
|
}).pipe(
|
||||||
|
Component.withOptions({ scopeCommitTimeout: "10 millis" }),
|
||||||
Component.withContext(runtime.context)
|
Component.withContext(runtime.context)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user