events
Normalize provider bucket notifications (S3, MinIO, R2, GCS, Azure, B2, Tigris, Supabase, Cloudinary, Appwrite, Box, Storj) into one FileEvent and route them to handlers by type and key glob.
The built-in events() plugin adds files.events: one place to react when a file is created or deleted, whether the write came through the SDK, a gateway upload, or straight to the bucket. The Events docs cover the concepts and per-provider setup; this page is the reference.
import { createFiles } from "files-sdk";
import { events } from "files-sdk/events";
import { r2 } from "files-sdk/r2";
const files = createFiles({
adapter: r2({ bucket: "uploads" }),
plugins: [events()],
});
files.events.on("created", "avatars/**", async (event) => {
await resizeAvatar(event.key);
});
Options
events({
format?: "s3" | "r2" | "gcs" | "azure" | "b2" | "tigris" | "supabase"
| "cloudinary" | "appwrite" | "box" | "memory",
bucket?: string,
sdk?: boolean,
dedupe?: { has(id): boolean | Promise<boolean>; add(id, ttl): void | Promise<void> },
dedupeTtl?: number,
onError?: (error: unknown, event: FileEvent) => void,
});
| Option | Default | |
|---|---|---|
format |
files.capabilities.events |
The notification format to read. Each adapter declares the one its provider sends: s3() and s3Fetch() against AWS (no endpoint, or one under amazonaws.com), minio, rustfs, wasabi and storj read "s3"; r2 reads "r2"; gcs and firebase-storage read "gcs"; azure reads "azure"; backblaze-b2 reads "b2"; tigris, supabase, cloudinary, appwrite, box and memory read their own. Every other adapter declares none. Set it for a provider that sends one of these formats under an adapter that doesn’t declare it (a non-AWS s3() endpoint, bun-s3). |
bucket |
The adapter’s bucket | Keep only provider events for this bucket (container, on Azure), so a queue, topic, or endpoint that also carries other buckets’ events (an EventBridge rule, an Azure system topic, a Supabase or Appwrite project webhook) delivers only yours. Defaults to the adapter’s own bucket when it exposes one, and filters only events that name a bucket. false accepts every bucket. |
sdk |
false |
Also raise events for successful upload / delete / copy / move calls through this instance, with source: "sdk". Delivered after the call settles and not awaited. With sdk: true, list events() first in plugins: construction throws Invalid if a plugin before it maps keys or sizes (versioning, softDelete, dedup, encryption, compression, tiering, …), since the sdk source would report its internal keys and stored sizes. |
dedupe |
None | Skip event ids this store has seen. See Delivery. |
dedupeTtl |
24 hours | How long a handled id is remembered, in ms. |
onError |
console.error |
Called for every handler that throws, whatever the source. Unexpected webhook failures go to webhook({ onError }) instead. |
files.events
on
on(type: "created" | "deleted" | "*", handler): () => void;
on(type: "created" | "deleted" | "*", pattern: string, handler): () => void;
Runs handler for events of type whose key matches pattern, a glob matched against the caller-facing key (** when omitted; the same glob syntax as search()). Handlers run one at a time, in registration order. Returns a function that removes the handler. When the instance has a notification format, on() runs the same startup check as webhook(), so a plugin that can’t map provider events throws here rather than on the first delivery.
dispatch
dispatch(input: unknown): Promise<FileEvent[]>;
Parses a delivery and runs the matching handlers. input can be a webhook Request, a message body (an SQS record body, a Queue message body, a Pub/Sub message), a whole consumer batch (a Lambda SQS event), a JSON string, or an array of any of those. Rejects when a handler throws, after every event has been tried, so a queue consumer can leave the message for redelivery. Resolves with the events it handled.
parse
parse(input: unknown): Promise<FileEvent[]>;
dispatch() without running handlers. Events outside the instance prefix or the bucket option are dropped, and installed plugins map the rest through their event hook. It doesn’t authenticate a Request; use webhook() for that.
webhook
webhook(opts: {
verify:
| false
| { token: string }
| { google: GoogleOidcOptions }
| { secret: string; secondarySecret?: string; url?: string; now?: () => number }
| { sns: SnsVerifyOptions };
onError?: (cause: unknown, req: Request) => void;
}): { handle(req: Request): Promise<Response> };
An HTTP endpoint for providers that push, mountable with any gateway binding. onError receives failures the response doesn’t spell out (answered with a generic 500), and defaults to console.error; a handler’s error still goes to events({ onError }). See Webhooks for verification, handshakes and status codes.
emit
emit(events: FileEvent | FileEvent[]): Promise<void>;
Runs the matching handlers for events you already have, with no parsing or prefix mapping. Gateway uploads arrive this way. Rejects like dispatch().
settled
settled(): Promise<void>;
Resolves once every event delivered without an awaiting caller (memory adapter changes, sdk writes) has been handled. For tests.
format
The format this instance reads, or undefined when its adapter has none.
Composition
Plugins that store keys or bytes their callers never addressed map provider events through their event hook:
| Plugin | Provider events |
|---|---|
dedup() |
Blob-store keys are dropped; a pointer’s size and ETag are cleared. |
encryption(), compression() |
size is cleared: it’s the stored size. |
versioning(), softDelete() |
Snapshot and trash keys are dropped, so a soft delete reads as one deleted. |
tiering() |
Events for keys routed to the hot tier pass. With fallback: true, provider events throw: a hot-tier delete may be a move to cold. Use the gateway or sdk sources there. |
Gateway and sdk events come from the instance’s own API and are already caller-facing, so they skip these hooks.