chore: initialize recovered claude workspace
This commit is contained in:
121
src/utils/QueryGuard.ts
Normal file
121
src/utils/QueryGuard.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user