Lightweight TypeScript anti-spam guard primitives for content moderation, chat moderation, UGC moderation, rate limiting, duplicate detection, burst detection, and actor-based message checks.
Use @textfilters/spam to add stateful spam checks beside censoring and
redaction filters in a composable TypeScript text filtering library.
Add the GitHub Packages registry for the @textfilters scope:
@textfilters:registry=https://npm.pkg.github.comInstall with GitHub npm authentication configured. GitHub Packages requires authentication for npm installs, including public packages.
npm install @textfilters/core @textfilters/spam- Block repeated, too-fast, or bursty messages in chat moderation workflows.
- Add actor-based anti-spam checks before running heavier content moderation.
- Return stable spam decision reasons for application-specific moderation policy.
- Keep rate limiting and duplicate detection separate from text censoring filters.
import {
createInMemorySpamStateStore,
createSpamFilter,
} from "@textfilters/spam";
const spam = createSpamFilter({
minIntervalMs: 700,
duplicateWindowMs: 12_000,
burstWindowMs: 10_000,
burstMaxMessages: 6,
actorKeyPolicy: "reject_missing",
clockPolicy: "system",
trackRejectedAttempts: true,
stateStore: createInMemorySpamStateStore(),
});
const decision = spam.check({
actorKey: "user:123",
text: "hello",
});createSpamFilter(config?) returns a stateful guard with stable name: "spam" and a reset() method. The spamFilter(config?) export is a backwards-compatible alias for createSpamFilter(config?).
The package provides an in-memory, actor-based spam guard for interval, duplicate, and burst checks. Each blocked decision returns a stable reason so callers can apply their own moderation policy.
Actor state is bounded by pruning stale entries as messages are checked and by
trimming each actor's record lists after state updates. Each actor keeps at most
burstMaxMessages burst timestamps and 256 recent normalized text entries for
duplicate detection. Use one guard instance for a shared moderation scope, or
create isolated instances for separate tenants or test cases. stateStore can
supply a custom SpamStateStore; omitting it creates an isolated in-memory store
for that filter instance. Filters with matching state policy settings can share
a store, while filters with different policy windows keep isolated actor buckets
inside the same store. reset() clears the configured store scope for the
filter.
Recommended server-side usage is to pass a stable authenticated actor key, use
clockPolicy: "system" so client-provided timestamps cannot weaken checks, and
enable trackRejectedAttempts when repeated rejected attempts should keep
pressure on the actor's interval, duplicate, and burst windows.
By default, missing actor keys share one stable unknown actor bucket for
backward compatibility. Set actorKeyPolicy: "reject_missing" to reject
non-empty messages without a normalized actor key with reason missing_actor.
By default, finite nowMs values are accepted for deterministic tests and
server-controlled callers. Set clockPolicy: "system" to ignore nowMs and
always use the process clock.
Supported clock policies:
input_or_systemis the default. It accepts finitenowMsvalues and falls back toDate.now()whennowMsis missing or non-finite. This preserves deterministic tests and trusted server-controlled timestamps.systemignores caller-providednowMsand always usesDate.now(). Prefer this policy for server-side moderation endpoints that receive untrusted client input.
Finite negative and backward-moving nowMs values are preserved for
compatibility under input_or_system. They are useful for deterministic tests
but should not be accepted directly from untrusted clients.
By default, rejected messages do not update actor state. Set
trackRejectedAttempts: true to count rejected interval, duplicate, and burst
attempts as pressure for future checks.
See docs/architecture.md for the decision flow, module map, and change guide.
@textfilters/corefor shared pipeline, normalization, and range masking primitives.@textfilters/urlfor URL detection, obfuscated links, and safe link censoring.@textfilters/emailfor email detection and contact redaction.@textfilters/phonefor phone number detection and contact redaction.@textfilters/profanityfor Russian profanity filtering and taxonomy-backed moderation.
Releases are managed by Release Please from Conventional Commit history on main. When a Release Please release is created, the workflow runs npm run check and publishes the package to GitHub Packages. Release tags keep the v* pattern.
The package is prepared for publication to GitHub Packages, not the public npm registry.
MIT