Read recent messages with fetchHistory and iterateHistory, search the index and use locally cached copies
After the first reply bot works, read earlier messages from a channel, search them or keep recent ones in a local cache. Reading history is a request to Fluxer and does not need the gateway connection
Scan for One Message
Find the first recent message that matches a rule. Add this helper to a module that already has a bot Client
export async function findRecentMessage(client, channelId, matches) {
for await (const item of client.messages.iterateHistory(channelId, { maxItems: 200 })) {
if (item.isErr())
return item
if (matches(item.value))
return item
}
return undefined
}
import type { Client, Message } from "@neontechspace/fluxerly"
export async function findRecentMessage(
client: Client,
channelId: string,
matches: (message: Message) => boolean,
) {
for await (const item of client.messages.iterateHistory(channelId, { maxItems: 200 })) {
if (item.isErr()) return item
if (matches(item.value)) return item
}
return undefined
}
The helper returns a Result with the matching message or the failure that stopped the read. It returns undefined if none of the 200 newest messages match
The iterate method reads newest first and requests the next page only when the loop needs it, so stopping early saves requests. The required max bounds the scan, so older messages can remain unread. To read a single page of up to 100 messages instead, use fetch, as the last section shows
Read Pinned Messages
Use messages for one page or messages with max for a bounded scan. Pins are ordered by pin time, not message creation time. Page ordering and cursor progress compare every fractional timestamp digit and normalize timezone offsets, so distinct pins less than a millisecond apart can advance the scan. Equal instants remain equal and can stall a multi-page scan. Preserve next unchanged in a manual loop and skip duplicate message IDs
Search the Message Index
When the text to look for is known, ask Fluxer's search index for one page of results. The index can lag behind the newest messages
export async function searchChannel(client, channelId, text) {
const result = await client.messages.search({ channelId }, { content: text, limit: 10 })
if (result.isErr())
return result
if (result.value.indexing)
return undefined
return result.value.messages
}
import type { Client } from "@neontechspace/fluxerly"
export async function searchChannel(client: Client, channelId: string, text: string) {
const result = await client.messages.search({ channelId }, { content: text, limit: 10 })
if (result.isErr()) return result
if (result.value.indexing) return undefined
return result.value.messages
}
The helper returns the matching messages, a failed Result, or undefined while Fluxer is still preparing its search index. It does not wait for the index or add results to the cache. To read the next page, increase page and keep limit unchanged
Search pages also include channel snapshots. An unnamed group DM has name: null, while another private channel may omit the name. The include filter requests age-restricted channel results. In an age-restricted community, guild search includes those results for eligible callers, including bots, even when the filter is omitted or false. Channel access checks still apply
To search several pages, use iterate with max as the limit. Results can shift while the index changes during the scan
Limit the Message Cache
The message cache keeps recent messages in memory so they can be read without a request. It is off until configured. Create a client with a token supplied by the application
import { createClient } from "@neontechspace/fluxerly"
export function createCachedClient(token) {
return createClient({
token,
cache: {
messages: {
maxEntries: 500,
maxBytes: 2_000_000,
maxAgeMs: 60_000,
},
},
})
}
import { createClient } from "@neontechspace/fluxerly"
export function createCachedClient(token: string) {
return createClient({
token,
cache: {
messages: {
maxEntries: 500,
maxBytes: 2_000_000,
maxAgeMs: 60_000,
},
},
})
}
This client keeps up to 500 messages from sends, reads, history pages and gateway events, within about two megabytes and for at most one minute each. Without these settings, an enabled message cache keeps up to 1,000 messages in 8 MiB with no age limit. The same cache option works in run
A function as max chooses the age limit per message, such as max to keep only messages from people. Zero skips keeping a message, and null keeps it without an age limit. Every cache category accepts the same kind of function
To release cached data while the bot runs, call client, or client for every category. The client method removes one entry, with keys such as channel for messages. Neither changes anything on Fluxer
Why Can a Message Be Missing from the Cache?
The cache stores data only in memory. A message may be missing because it was removed to make room, expired or conflicted with another observation of the same message. A lost gateway connection also clears the messages cached before it, even when the connection recovers, because changes during the gap may have been missed.
The entry and byte limits do not count runtime overhead or copies held elsewhere in the application. To react to cache changes, register a listener with client, which reports each stored, deleted or cleared entry. A listener that returns a promise has at most 256 unfinished calls, and up to 256 more changes wait for them. Beyond that, the oldest waiting change is dropped and logged as cache. Shutdown does not wait for unfinished calls, so changes still waiting then are dropped and logged the same way. Pass { concurrency, max as the second argument to change these limits
Deleting a channel or a thread removes its cached messages. Deleting a text, announcement, forum or media channel also deletes its threads, and Fluxer sends no thread deletion for them. A message does not record which channel holds its thread, so the cache then also removes the messages of every channel in that community that the channel cache does not hold. Messages read through REST carry no community, so they count as possibly in that community. Without cache, all of these messages go. With it, which also fills from each community's startup snapshot, messages in the channels and threads it still holds stay
See Client and Message for the complete cache contract
Use the Cache, Then Fetch if Needed
Use a cached message when a recent copy is good enough, and fetch it from Fluxer otherwise. Pass a Message with both channel and id
The received message is a different type, Message, whose id is optional. Check id before building the Message. An announcement-channel follow notice identifies only a source channel, not a message to fetch
import { orThrow } from "@neontechspace/fluxerly"
export async function readCachedOrFetch(client, message) {
const cached = client.messages.get(message)
if (cached !== undefined)
return cached
return orThrow(await client.messages.fetch(message, { timeoutMs: 5_000 }))
}
import { orThrow, type Client, type MessageReference } from "@neontechspace/fluxerly"
export async function readCachedOrFetch(client: Client, message: MessageReference) {
const cached = client.messages.get(message)
if (cached !== undefined) return cached
return orThrow(await client.messages.fetch(message, { timeoutMs: 5_000 }))
}
A cached copy returns immediately without a request. Otherwise the helper fetches the message with a five-second deadline, and or throws if that fails
The get method returns undefined when the client has no usable copy. A cached copy is a frozen snapshot of the message as it was when observed, so use fetch when the task needs its current state
Check Whether the Limits Fit
Each category in client keeps running totals since the client was created. The hits and misses values count lookups such as client, including lookups the SDK makes itself. The evictions value counts entries the cache removed on its own, with capacity for room within max or max and expiry for entries older than max
export function messageCacheReport(client) {
const { hits, misses, evictions } = client.diagnostics().caches.messages
const lookups = hits + misses
return {
hitRate: lookups === 0 ? null : hits / lookups,
removedForRoom: evictions.capacity,
expired: evictions.expiry,
}
}
import type { Client } from "@neontechspace/fluxerly"
export function messageCacheReport(client: Client) {
const { hits, misses, evictions } = client.diagnostics().caches.messages
const lookups = hits + misses
return {
hitRate: lookups === 0 ? null : hits / lookups,
removedForRoom: evictions.capacity,
expired: evictions.expiry,
}
}
Frequent misses together with many capacity evictions suggest the limits are too small for the messages the bot reads again. Frequent misses with few evictions mean the bot looks up messages it never kept, so larger limits will not help. Counting costs one increment per lookup or eviction, so the totals are always on. A disabled category reports zero
Wire It Up
In a run bot, cache settings sit beside the token, and commands read history through the client in their context. This bot answers !recent with the people who spoke in the channel's 20 newest messages
import { runBot } from "@neontechspace/fluxerly"
await runBot({
token: process.env.FLUXER_BOT_TOKEN,
cache: { messages: { maxEntries: 500, maxAgeMs: 600_000 } },
commands: {
prefix: "!",
commands: {
recent: {
description: "List recent speakers",
execute: async ({ client, message, reply, signal }) => {
const page = await client.messages.fetchHistory(message.channelId, { limit: 20 }, { signal })
if (page.isErr())
return page
const people = page.value.filter((item) => !item.author.isBot)
const names = new Set(people.map((item) => item.author.username))
return reply(`Recent speakers: ${[...names].join(", ") || "none"}`)
},
},
},
},
})
import { runBot } from "@neontechspace/fluxerly"
await runBot({
token: process.env.FLUXER_BOT_TOKEN,
cache: { messages: { maxEntries: 500, maxAgeMs: 600_000 } },
commands: {
prefix: "!",
commands: {
recent: {
description: "List recent speakers",
execute: async ({ client, message, reply, signal }) => {
const page = await client.messages.fetchHistory(message.channelId, { limit: 20 }, { signal })
if (page.isErr()) return page
const people = page.value.filter((item) => !item.author.isBot)
const names = new Set(people.map((item) => item.author.username))
return reply(`Recent speakers: ${[...names].join(", ") || "none"}`)
},
},
},
},
})
Passing the command's signal stops the read when the bot shuts down. A failed read is returned from execute, so the router reports it with the command name. With the cache enabled, the page read can also store those messages for later client lookups