akiomik / akiomik/nosvelte

useReq() has no teardown path once its query has settled

Open
#61 3 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
TypeScript
Stars
29
Forks
3
Avg merge
3h 5m
Merged PRs (30d)
11

Description

#60 closes a request's REQ when the query is cancelled mid-fetch, which is the
only teardown `useReq()` has. Once the query settles there is no longer anything
to cancel, so a subscription that outlives its consumer stays open.

This is reachable with a caller-supplied `req`. The oneshot req `useReq()` builds
for itself completes on EOSE and RxJS tears it down, but a forward req never
completes:

1. A component passes its own `RxReqBase` and the query resolves on the first event.
2. The component is destroyed. The query is already settled, so `removeObserver()`
has no in-flight retryer to cancel and the abort never fires.
3. The subscription stays live. Further `req.emit()` calls keep pushing events
through it and into `queryClient.setQueryData()` for a component that is gone.

Reactive re-invocation compounds it: components call the hooks from a reactive
statement (`$: result = useUniqueEventList(...)`), so each re-run builds a fresh
observable, and with the default `staleTime: 0` a remount refetches and subscribes
again — while the previous subscription is still running.

Fixing this needs a lifecycle hook `useReq()` does not have today. Rough options:

- Return an unsubscriber from `ReqResult` and have the components call it from
`onDestroy`. Explicit, but an API change for direct hook users.
- Tear down when the query's observer count drops to zero, via the query cache's
`observerRemoved` event. No API change, but Svelte's subscribe/unsubscribe
ordering across a reactive re-run can transiently hit zero observers, so it
needs care to avoid killing a subscription that is about to be re-adopted.

Worth noting while looking at this: `useReq()` calls `req.emit(filters)` before
anything subscribes to the query, so for a forward req that first emission is
dropped and no REQ is sent until the caller emits again. That may deserve its own
issue.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by reading the useReq() and ReqResult lifecycle, then inspect how query observers are removed and how onDestroy or the query cache's observerRemoved event could participate. Reproduce the forward-req case where the query settles before the component is destroyed. Done means subscriptions are torn down when consumers leave and later req.emit() calls no longer update the removed component's query.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.