π File detail
utils/QueryGuard.ts
π― Use case
This file lives under βutils/β, which covers cross-cutting helpers (shell, tempfiles, settings, messages, process input, β¦). On the API surface it exposes QueryGuard β mainly types, interfaces, or factory objects. It composes internal code from signal (relative imports). What the file header says: Synchronous state machine for the query lifecycle, compatible with React's `useSyncExternalStore`. Three states: idle β no query, safe to dequeue and process dispatching β an item was dequeued, async chain hasn't reached onQuery yet running β onQuery called tryStart(), query is e.
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
Synchronous state machine for the query lifecycle, compatible with React's `useSyncExternalStore`. Three states: idle β no query, safe to dequeue and process dispatching β an item was dequeued, async chain hasn't reached onQuery yet running β onQuery called tryStart(), query is executing Transitions: idle β dispatching (reserve) dispatching β running (tryStart) idle β running (tryStart, for direct user submissions) running β idle (end / forceEnd) dispatching β idle (cancelReservation, when processQueueIfReady fails) `isActive` returns true for both dispatching and running, preventing re-entry from the queue processor during the async gap. Usage with React: const queryGuard = useRef(new QueryGuard()).current const isQueryActive = useSyncExternalStore( queryGuard.subscribe, queryGuard.getSnapshot, )
π€ Exports (heuristic)
QueryGuard
π₯οΈ Source preview
/**
* Synchronous state machine for the query lifecycle, compatible with
* React's `useSyncExternalStore`.
*
* Three states:
* idle β no query, safe to dequeue and process
* dispatching β an item was dequeued, async chain hasn't reached onQuery yet
* running β onQuery called tryStart(), query is executing
*
* Transitions:
* idle β dispatching (reserve)
* dispatching β running (tryStart)
* idle β running (tryStart, for direct user submissions)
* running β idle (end / forceEnd)
* dispatching β idle (cancelReservation, when processQueueIfReady fails)
*
* `isActive` returns true for both dispatching and running, preventing
* re-entry from the queue processor during the async gap.
*
* Usage with React:
* const queryGuard = useRef(new QueryGuard()).current
* const isQueryActive = useSyncExternalStore(
* queryGuard.subscribe,
* queryGuard.getSnapshot,
* )
*/
import { createSignal } from './signal.js'
export class QueryGuard {
private _status: 'idle' | 'dispatching' | 'running' = 'idle'
private _generation = 0
private _changed = createSignal()
/**
* Reserve the guard for queue processing. Transitions idle β dispatching.
* Returns false if not idle (another query or dispatch in progress).
*/
reserve(): boolean {
if (this._status !== 'idle') return false
this._status = 'dispatching'
this._notify()
return true
}
/**
* Cancel a reservation when processQueueIfReady had nothing to process.
* Transitions dispatching β idle.
*/
cancelReservation(): void {
if (this._status !== 'dispatching') return
this._status = 'idle'
this._notify()
}
/**
* Start a query. Returns the generation number on success,
* or null if a query is already running (concurrent guard).
* Accepts transitions from both idle (direct user submit)
* and dispatching (queue processor path).
*/
tryStart(): number | null {
if (this._status === 'running') return null
this._status = 'running'
++this._generation
this._notify()
return this._generation
}
/**
* End a query. Returns true if this generation is still current
* (meaning the caller should perform cleanup). Returns false if a
* newer query has started (stale finally block from a cancelled query).
*/
end(generation: number): boolean {
if (this._generation !== generation) return false
if (this._status !== 'running') return false
this._status = 'idle'
this._notify()
return true
}
/**
* Force-end the current query regardless of generation.
* Used by onCancel where any running query should be terminated.
* Increments generation so stale finally blocks from the cancelled
* query's promise rejection will see a mismatch and skip cleanup.
*/
forceEnd(): void {
if (this._status === 'idle') return
this._status = 'idle'
++this._generation
this._notify()
}
/**
* Is the guard active (dispatching or running)?
* Always synchronous β not subject to React state batching delays.
*/
get isActive(): boolean {
return this._status !== 'idle'
}
get generation(): number {
return this._generation
}
// --
// useSyncExternalStore interface
/** Subscribe to state changes. Stable reference β safe as useEffect dep. */
subscribe = this._changed.subscribe
/** Snapshot for useSyncExternalStore. Returns `isActive`. */
getSnapshot = (): boolean => {
return this._status !== 'idle'
}
private _notify(): void {
this._changed.emit()
}
}