feat(companion): add waitForIdle runtime drain helper
This commit is contained in:
@@ -41,6 +41,12 @@ export interface CompanionRuntimeClientOptions {
|
||||
websocketFactory?: (url: string) => WebSocket;
|
||||
}
|
||||
|
||||
export interface WaitForIdleOptions {
|
||||
timeoutMs?: number;
|
||||
pollIntervalMs?: number;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export type CompanionEventHandler = (event: string, data: unknown) => void;
|
||||
export type CompanionTypedEventHandler<TData = unknown> = (data: TData) => void;
|
||||
export type CompanionEventPredicate<TData = unknown> = (data: TData) => boolean;
|
||||
@@ -607,6 +613,74 @@ export class CompanionRuntimeClient {
|
||||
return this.waitForEvent<TData>(COMPANION_EVENT_NAMES.contextWarning, options);
|
||||
}
|
||||
|
||||
waitForIdle(options?: WaitForIdleOptions): Promise<void> {
|
||||
const pollIntervalMs = options?.pollIntervalMs ?? 25;
|
||||
if (!Number.isFinite(pollIntervalMs) || pollIntervalMs <= 0) {
|
||||
throw new Error('pollIntervalMs must be a positive number');
|
||||
}
|
||||
if (!this.hasPendingWork) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const timeoutMs = options?.timeoutMs ?? this.requestTimeoutMs;
|
||||
const signal = options?.signal;
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let settled = false;
|
||||
let timeout: NodeJS.Timeout | null = null;
|
||||
let poll: NodeJS.Timeout | null = null;
|
||||
let abortCleanup: (() => void) | null = null;
|
||||
|
||||
const cleanup = () => {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
if (poll) {
|
||||
clearInterval(poll);
|
||||
poll = null;
|
||||
}
|
||||
if (abortCleanup) {
|
||||
abortCleanup();
|
||||
abortCleanup = null;
|
||||
}
|
||||
};
|
||||
|
||||
const finish = (fn: () => void) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
cleanup();
|
||||
fn();
|
||||
};
|
||||
|
||||
const check = () => {
|
||||
if (!this.hasPendingWork) {
|
||||
finish(() => resolve());
|
||||
}
|
||||
};
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
finish(() => reject(new Error('Timed out waiting for runtime idle state')));
|
||||
}, timeoutMs);
|
||||
poll = setInterval(check, pollIntervalMs);
|
||||
check();
|
||||
|
||||
if (signal) {
|
||||
const onAbort = () => {
|
||||
finish(() => reject(new Error('Aborted while waiting for runtime idle state')));
|
||||
};
|
||||
signal.addEventListener('abort', onAbort, { once: true });
|
||||
abortCleanup = () => {
|
||||
signal.removeEventListener('abort', onAbort);
|
||||
};
|
||||
if (signal.aborted) {
|
||||
onAbort();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
listKnownEventNames(): CompanionEventName[] {
|
||||
return Object.values(COMPANION_EVENT_NAMES);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user