Extending

Typed keys

Every engine accepts free-form names by default. Declare yours once and the whole framework checks them — jobs, events, templates, disks and database rows.

TypeScript declaration merging lets you add your own keys to the interfaces Yatta declares. This is the same mechanism bun-types uses; nothing is generated, so there is no build step and nothing to drift out of date.

Jobs

yatta/func/jobs.tsts
import { createJobs, SQLiteJobStore } from "yatta/jobs"; export interface AppJobs {  "send-email": { to: string; subject: string; body: string };  "transcode-video": { videoId: string; quality: "720p" | "1080p" };  "cleanup-stale-tokens": { maxAgeDays?: number };} declare module "yatta/jobs" {  interface JobRegister extends AppJobs {}} export const jobs = createJobs({  store: new SQLiteJobStore("Database/jobs.db"),});

Every call site is now checked:

checkedts
await jobs.enqueue("send-email", {  to: "ada@example.com",  subject: "Hi",  body: "Hello",});          // ✓ await jobs.enqueue("send-email", { to: "ada@example.com" });          // ✗ Property 'subject' is missing await jobs.enqueue("emial-send", {});   // ✗ not a known job

Events

yatta/func/events.tsts
export interface AppEvents {  "user.registered": { userId: string; email: string };  "order.completed": { orderId: string; amount: number };} declare module "yatta/jobs" {  interface EventRegister extends AppEvents {}}
tsts
events.on("user.registered", (data) => {  data.email;      // string — typed}); await events.emit("order.completed", { orderId: "o_1", amount: 42 });

Mail templates

yatta/func/mail.tsts
export interface AppTemplates {  welcome: { name: string; verifyUrl: string };  receipt: { total: number; currency: string };} declare module "yatta/mail" {  interface MailRegister {    templates: AppTemplates;  }}
tsts
await mailer.send({  to,  template: "welcome",  data: { name: "Ada", verifyUrl: "https://…" },   // ✓}); await mailer.send({  to,  template: "welcome",  data: { name: "Ada" },                           // ✗ verifyUrl missing});

Storage disks

yatta/func/storage.tsts
declare module "yatta/storage" {  interface StorageRegister {    disks: "local" | "s3" | "backups";  }}
tsts
await storage.disk("local").upload("a.png", buffer);await storage.disk("s3").upload("a.png", buffer);await storage.disk("gcs").upload("a.png", buffer);   // ✗ unknown disk

Database rows

yatta/func/db.tsts
export const schema = {  users: {    id: col.uuid(),    email: col.text().unique(),    roles: col.json<string[]>().default(["user"]),    createdAt: col.createdAt(),  },}; declare module "yatta/db" {  interface Register {    schema: typeof schema;  }}
typed rowsts
const user = await db.users.findById("u_1"); user.email;     // stringuser.roles;     // string[]user.nope;      // ✗ not a column

Realtime events

tsts
declare module "yatta/realtime" {  interface RealtimeRegister {    events: {      "chat.message": { user: string; text: string };      "order.status": { orderId: string; status: string };    };  }}

Keeping types tidy

  • Put each augmentation in the file that owns the engine — that keeps the declaration next to the code that uses it.
  • Use a named interface per app and extends it, rather than merging straight into JobRegister.
  • Everything lives in yatta/func/, so the shape of your system is readable from one folder.