diff --git a/.changeset/wet-hotels-end.md b/.changeset/wet-hotels-end.md new file mode 100644 index 0000000..52d5a05 --- /dev/null +++ b/.changeset/wet-hotels-end.md @@ -0,0 +1,6 @@ +--- +'@callstack/byorg-utils': minor +'@callstack/byorg-slack': patch +--- + +utils: debouncePartialUpdate function for smart debounce handling diff --git a/packages/slack/package.json b/packages/slack/package.json index c3cc40c..56cc096 100644 --- a/packages/slack/package.json +++ b/packages/slack/package.json @@ -34,7 +34,6 @@ "@callstack/slack-rich-text": "workspace:*", "@slack/bolt": "^4.1.1", "@slack/web-api": "^7.8.0", - "p-debounce": "^4.0.0", "ts-regex-builder": "^1.8.2" }, "devDependencies": { diff --git a/packages/slack/src/app.ts b/packages/slack/src/app.ts index eabe40e..f5711b8 100644 --- a/packages/slack/src/app.ts +++ b/packages/slack/src/app.ts @@ -1,9 +1,8 @@ import Slack, { LogLevel, SayFn } from '@slack/bolt'; import { ContextBlock, RichTextBlock, WebClient } from '@slack/web-api'; import { Application } from '@callstack/byorg-core'; -import { logger } from '@callstack/byorg-utils'; +import { debouncePartialUpdate, logger } from '@callstack/byorg-utils'; import { parseMarkdownToRichTextBlock } from '@callstack/slack-rich-text'; -import pDebounce from 'p-debounce'; import { SlackMessage, SlackApplicationConfig as SlackApplicationConfig, @@ -11,12 +10,12 @@ import { } from './types.js'; import { fetchFileInfo, getThread } from './slack-api.js'; import { formatGroupMessage, toCoreMessage } from './messages.js'; -import { wait } from './utils.js'; import { getBotId } from './bot-id.js'; type MessageBlock = RichTextBlock | ContextBlock; -const UPDATE_THROTTLE_TIME = 1000; +const MIN_UPDATE_TIME = 3000; +const MIN_RESPONSE_LENGTH = 250; const NOTIFICATION_TEXT_LIMIT = 200; const RESPONDING_BLOCK: MessageBlock = { @@ -91,8 +90,6 @@ function configureSlackApp(app: Application, slack: Slack.App): void { blocks, }); responseMessageTs = responseMessage.ts as string; - - await wait(UPDATE_THROTTLE_TIME); return; } @@ -104,15 +101,17 @@ function configureSlackApp(app: Application, slack: Slack.App): void { parse: 'none', }); - await wait(UPDATE_THROTTLE_TIME); return; }; let updatePartialResponsePromise: Promise = Promise.resolve(); - const updateResponseMessageWithThrottle = pDebounce.promise(updateResponseMessage); + const updateResponseMessageWithDebounce = debouncePartialUpdate(updateResponseMessage, { + minUpdateTime: MIN_UPDATE_TIME, + minResponseLength: MIN_RESPONSE_LENGTH, + }); const handlePartialResponse = (response: string): void => { - updatePartialResponsePromise = updateResponseMessageWithThrottle(response); + updatePartialResponsePromise = updateResponseMessageWithDebounce(response); }; const startTime = performance.now(); diff --git a/packages/slack/src/utils.ts b/packages/slack/src/utils.ts deleted file mode 100644 index 7b18fbe..0000000 --- a/packages/slack/src/utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function wait(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} diff --git a/packages/utils/src/debounce.ts b/packages/utils/src/debounce.ts new file mode 100644 index 0000000..6ed615d --- /dev/null +++ b/packages/utils/src/debounce.ts @@ -0,0 +1,38 @@ +export type PartialUpdateFn = (text: string) => Promise; +export type DebouncePartialUpdateOptions = { + minUpdateTime: number; + minResponseLength: number; +}; + +export function debouncePartialUpdate( + partialUpdate: PartialUpdateFn, + { minResponseLength, minUpdateTime }: DebouncePartialUpdateOptions, +) { + let pendingPromise: Promise | undefined; + let lastUpdate: number | undefined; + + return async function (text: string) { + // Wait for the last update to finish + if (pendingPromise) { + return pendingPromise; + } + + // Time-based debounce + if (lastUpdate !== undefined && performance.now() - lastUpdate < minUpdateTime) { + return; + } + + // Skip too short initial messages + if (text.length < minResponseLength) { + return; + } + + try { + lastUpdate = performance.now(); + pendingPromise = partialUpdate(text); + return await pendingPromise; + } finally { + pendingPromise = undefined; + } + }; +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 65898b9..267c125 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -5,3 +5,6 @@ export type { Logger, LogLevel } from './logger/types.js'; export { requireEnv } from './env.js'; export { withCache } from './with-cache.js'; + +export { debouncePartialUpdate } from './debounce.js'; +export type { PartialUpdateFn, DebouncePartialUpdateOptions } from './debounce.js'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 68353d2..13bb9ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -294,9 +294,6 @@ importers: '@slack/web-api': specifier: ^7.8.0 version: 7.8.0 - p-debounce: - specifier: ^4.0.0 - version: 4.0.0 ts-regex-builder: specifier: ^1.8.2 version: 1.8.2