refactor: eliminate remaining duplicate blocks across draft streams and tests

This commit is contained in:
Peter Steinberger
2026-02-21 23:56:58 +00:00
parent abf3dfc375
commit ad1c07e7c0
16 changed files with 316 additions and 199 deletions

View File

@@ -148,6 +148,19 @@ function resolveCaseInsensitiveAccount<T>(
]
);
}
function resolveDefaultToCaseInsensitiveAccount(params: {
channel?:
| {
accounts?: Record<string, { defaultTo?: string }>;
defaultTo?: string;
}
| undefined;
accountId?: string | null;
}): string | undefined {
const account = resolveCaseInsensitiveAccount(params.channel?.accounts, params.accountId);
return (account?.defaultTo ?? params.channel?.defaultTo)?.trim() || undefined;
}
// Channel docks: lightweight channel metadata/behavior for shared code paths.
//
// Rules:
@@ -331,15 +344,7 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
const channel = cfg.channels?.irc as
| { accounts?: Record<string, { defaultTo?: string }>; defaultTo?: string }
| undefined;
const normalized = normalizeAccountId(accountId);
const account =
channel?.accounts?.[normalized] ??
channel?.accounts?.[
Object.keys(channel?.accounts ?? {}).find(
(key) => key.toLowerCase() === normalized.toLowerCase(),
) ?? ""
];
return (account?.defaultTo ?? channel?.defaultTo)?.trim() || undefined;
return resolveDefaultToCaseInsensitiveAccount({ channel, accountId });
},
},
groups: {
@@ -412,15 +417,7 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
const channel = cfg.channels?.googlechat as
| { accounts?: Record<string, { defaultTo?: string }>; defaultTo?: string }
| undefined;
const normalized = normalizeAccountId(accountId);
const account =
channel?.accounts?.[normalized] ??
channel?.accounts?.[
Object.keys(channel?.accounts ?? {}).find(
(key) => key.toLowerCase() === normalized.toLowerCase(),
) ?? ""
];
return (account?.defaultTo ?? channel?.defaultTo)?.trim() || undefined;
return resolveDefaultToCaseInsensitiveAccount({ channel, accountId });
},
},
groups: {

View File

@@ -0,0 +1,139 @@
import { createDraftStreamLoop } from "./draft-stream-loop.js";
export type FinalizableDraftStreamState = {
stopped: boolean;
final: boolean;
};
export function createFinalizableDraftStreamControls(params: {
throttleMs: number;
isStopped: () => boolean;
isFinal: () => boolean;
markStopped: () => void;
markFinal: () => void;
sendOrEditStreamMessage: (text: string) => Promise<boolean>;
}) {
const loop = createDraftStreamLoop({
throttleMs: params.throttleMs,
isStopped: params.isStopped,
sendOrEditStreamMessage: params.sendOrEditStreamMessage,
});
const update = (text: string) => {
if (params.isStopped() || params.isFinal()) {
return;
}
loop.update(text);
};
const stop = async (): Promise<void> => {
params.markFinal();
await loop.flush();
};
const stopForClear = async (): Promise<void> => {
params.markStopped();
loop.stop();
await loop.waitForInFlight();
};
return {
loop,
update,
stop,
stopForClear,
};
}
export function createFinalizableDraftStreamControlsForState(params: {
throttleMs: number;
state: FinalizableDraftStreamState;
sendOrEditStreamMessage: (text: string) => Promise<boolean>;
}) {
return createFinalizableDraftStreamControls({
throttleMs: params.throttleMs,
isStopped: () => params.state.stopped,
isFinal: () => params.state.final,
markStopped: () => {
params.state.stopped = true;
},
markFinal: () => {
params.state.final = true;
},
sendOrEditStreamMessage: params.sendOrEditStreamMessage,
});
}
export async function takeMessageIdAfterStop<T>(params: {
stopForClear: () => Promise<void>;
readMessageId: () => T | undefined;
clearMessageId: () => void;
}): Promise<T | undefined> {
await params.stopForClear();
const messageId = params.readMessageId();
params.clearMessageId();
return messageId;
}
export async function clearFinalizableDraftMessage<T>(params: {
stopForClear: () => Promise<void>;
readMessageId: () => T | undefined;
clearMessageId: () => void;
isValidMessageId: (value: unknown) => value is T;
deleteMessage: (messageId: T) => Promise<void>;
onDeleteSuccess?: (messageId: T) => void;
warn?: (message: string) => void;
warnPrefix: string;
}): Promise<void> {
const messageId = await takeMessageIdAfterStop({
stopForClear: params.stopForClear,
readMessageId: params.readMessageId,
clearMessageId: params.clearMessageId,
});
if (!params.isValidMessageId(messageId)) {
return;
}
try {
await params.deleteMessage(messageId);
params.onDeleteSuccess?.(messageId);
} catch (err) {
params.warn?.(`${params.warnPrefix}: ${err instanceof Error ? err.message : String(err)}`);
}
}
export function createFinalizableDraftLifecycle<T>(params: {
throttleMs: number;
state: FinalizableDraftStreamState;
sendOrEditStreamMessage: (text: string) => Promise<boolean>;
readMessageId: () => T | undefined;
clearMessageId: () => void;
isValidMessageId: (value: unknown) => value is T;
deleteMessage: (messageId: T) => Promise<void>;
onDeleteSuccess?: (messageId: T) => void;
warn?: (message: string) => void;
warnPrefix: string;
}) {
const controls = createFinalizableDraftStreamControlsForState({
throttleMs: params.throttleMs,
state: params.state,
sendOrEditStreamMessage: params.sendOrEditStreamMessage,
});
const clear = async () => {
await clearFinalizableDraftMessage({
stopForClear: controls.stopForClear,
readMessageId: params.readMessageId,
clearMessageId: params.clearMessageId,
isValidMessageId: params.isValidMessageId,
deleteMessage: params.deleteMessage,
onDeleteSuccess: params.onDeleteSuccess,
warn: params.warn,
warnPrefix: params.warnPrefix,
});
};
return {
...controls,
clear,
};
}