mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-24 07:01:49 +00:00
Runtime: dedupe typing lease logic
This commit is contained in:
@@ -1,29 +1,39 @@
|
||||
import { afterEach, describe, vi } from "vitest";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
createDiscordTypingLease,
|
||||
type CreateDiscordTypingLeaseParams,
|
||||
} from "./runtime-discord-typing.js";
|
||||
import { registerSharedTypingLeaseTests } from "./typing-lease.test-support.js";
|
||||
|
||||
const DISCORD_TYPING_INTERVAL_MS = 2_000;
|
||||
|
||||
function buildDiscordTypingParams(
|
||||
pulse: CreateDiscordTypingLeaseParams["pulse"],
|
||||
): CreateDiscordTypingLeaseParams {
|
||||
return {
|
||||
channelId: "123",
|
||||
intervalMs: DISCORD_TYPING_INTERVAL_MS,
|
||||
pulse,
|
||||
};
|
||||
}
|
||||
|
||||
describe("createDiscordTypingLease", () => {
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
registerSharedTypingLeaseTests({
|
||||
createLease: createDiscordTypingLease,
|
||||
buildParams: buildDiscordTypingParams,
|
||||
it("uses the Discord default interval and forwards pulse params", async () => {
|
||||
vi.useFakeTimers();
|
||||
const pulse: CreateDiscordTypingLeaseParams["pulse"] = vi.fn(async () => undefined);
|
||||
const cfg = { channels: { discord: { token: "x" } } };
|
||||
|
||||
const lease = await createDiscordTypingLease({
|
||||
channelId: "123",
|
||||
accountId: "work",
|
||||
cfg,
|
||||
intervalMs: Number.NaN,
|
||||
pulse,
|
||||
});
|
||||
|
||||
expect(pulse).toHaveBeenCalledTimes(1);
|
||||
expect(pulse).toHaveBeenCalledWith({
|
||||
channelId: "123",
|
||||
accountId: "work",
|
||||
cfg,
|
||||
});
|
||||
|
||||
await vi.advanceTimersByTimeAsync(7_999);
|
||||
expect(pulse).toHaveBeenCalledTimes(1);
|
||||
await vi.advanceTimersByTimeAsync(1);
|
||||
expect(pulse).toHaveBeenCalledTimes(2);
|
||||
|
||||
lease.stop();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { logWarn } from "../../logger.js";
|
||||
import { createTypingLease } from "./typing-lease.js";
|
||||
|
||||
export type CreateDiscordTypingLeaseParams = {
|
||||
channelId: string;
|
||||
@@ -18,45 +18,15 @@ export async function createDiscordTypingLease(params: CreateDiscordTypingLeaseP
|
||||
refresh: () => Promise<void>;
|
||||
stop: () => void;
|
||||
}> {
|
||||
const intervalMs =
|
||||
typeof params.intervalMs === "number" && Number.isFinite(params.intervalMs)
|
||||
? Math.max(1_000, Math.floor(params.intervalMs))
|
||||
: DEFAULT_DISCORD_TYPING_INTERVAL_MS;
|
||||
|
||||
let stopped = false;
|
||||
let timer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
const pulse = async () => {
|
||||
if (stopped) {
|
||||
return;
|
||||
}
|
||||
await params.pulse({
|
||||
return await createTypingLease({
|
||||
defaultIntervalMs: DEFAULT_DISCORD_TYPING_INTERVAL_MS,
|
||||
errorLabel: "discord",
|
||||
intervalMs: params.intervalMs,
|
||||
pulse: params.pulse,
|
||||
pulseArgs: {
|
||||
channelId: params.channelId,
|
||||
accountId: params.accountId,
|
||||
cfg: params.cfg,
|
||||
});
|
||||
};
|
||||
|
||||
await pulse();
|
||||
|
||||
timer = setInterval(() => {
|
||||
// Background lease refreshes must never escape as unhandled rejections.
|
||||
void pulse().catch((err) => {
|
||||
logWarn(`plugins: discord typing pulse failed: ${String(err)}`);
|
||||
});
|
||||
}, intervalMs);
|
||||
timer.unref?.();
|
||||
|
||||
return {
|
||||
refresh: async () => {
|
||||
await pulse();
|
||||
},
|
||||
stop: () => {
|
||||
stopped = true;
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
43
src/plugins/runtime/typing-lease.test.ts
Normal file
43
src/plugins/runtime/typing-lease.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { afterEach, describe, it, vi } from "vitest";
|
||||
import { createTypingLease } from "./typing-lease.js";
|
||||
import {
|
||||
expectDefaultTypingLeaseInterval,
|
||||
registerSharedTypingLeaseTests,
|
||||
} from "./typing-lease.test-support.js";
|
||||
|
||||
const TEST_TYPING_INTERVAL_MS = 2_000;
|
||||
const TEST_TYPING_DEFAULT_INTERVAL_MS = 4_000;
|
||||
|
||||
function buildTypingLeaseParams(
|
||||
pulse: (params: { target: string; lane?: string }) => Promise<unknown>,
|
||||
) {
|
||||
return {
|
||||
defaultIntervalMs: TEST_TYPING_DEFAULT_INTERVAL_MS,
|
||||
errorLabel: "test",
|
||||
intervalMs: TEST_TYPING_INTERVAL_MS,
|
||||
pulse,
|
||||
pulseArgs: {
|
||||
target: "target-1",
|
||||
lane: "answer",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("createTypingLease", () => {
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
registerSharedTypingLeaseTests({
|
||||
createLease: createTypingLease,
|
||||
buildParams: buildTypingLeaseParams,
|
||||
});
|
||||
|
||||
it("falls back to the default interval for non-finite values", async () => {
|
||||
await expectDefaultTypingLeaseInterval({
|
||||
createLease: createTypingLease,
|
||||
buildParams: buildTypingLeaseParams,
|
||||
defaultIntervalMs: TEST_TYPING_DEFAULT_INTERVAL_MS,
|
||||
});
|
||||
});
|
||||
});
|
||||
56
src/plugins/runtime/typing-lease.ts
Normal file
56
src/plugins/runtime/typing-lease.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { logWarn } from "../../logger.js";
|
||||
|
||||
export type TypingLease = {
|
||||
refresh: () => Promise<void>;
|
||||
stop: () => void;
|
||||
};
|
||||
|
||||
type CreateTypingLeaseParams<TPulseArgs> = {
|
||||
defaultIntervalMs: number;
|
||||
errorLabel: string;
|
||||
intervalMs?: number;
|
||||
pulse: (params: TPulseArgs) => Promise<unknown>;
|
||||
pulseArgs: TPulseArgs;
|
||||
};
|
||||
|
||||
export async function createTypingLease<TPulseArgs>(
|
||||
params: CreateTypingLeaseParams<TPulseArgs>,
|
||||
): Promise<TypingLease> {
|
||||
const intervalMs =
|
||||
typeof params.intervalMs === "number" && Number.isFinite(params.intervalMs)
|
||||
? Math.max(1_000, Math.floor(params.intervalMs))
|
||||
: params.defaultIntervalMs;
|
||||
|
||||
let stopped = false;
|
||||
let timer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
const pulse = async () => {
|
||||
if (stopped) {
|
||||
return;
|
||||
}
|
||||
await params.pulse(params.pulseArgs);
|
||||
};
|
||||
|
||||
await pulse();
|
||||
|
||||
timer = setInterval(() => {
|
||||
// Background lease refreshes must never escape as unhandled rejections.
|
||||
void pulse().catch((err) => {
|
||||
logWarn(`plugins: ${params.errorLabel} typing pulse failed: ${String(err)}`);
|
||||
});
|
||||
}, intervalMs);
|
||||
timer.unref?.();
|
||||
|
||||
return {
|
||||
refresh: async () => {
|
||||
await pulse();
|
||||
},
|
||||
stop: () => {
|
||||
stopped = true;
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user