INTEGRITY Dokumentace

Bindings

Bindingy vám umožňují řídit, k čemu má Dynamic Worker přístup. Při vytváření Dynamic Workeru přesně určujete, jaké zdroje a operace může používat.

To vám umožní:

Vlastní bindingy s Dynamic Workery

S vlastními bindingy můžete:

Jak to funguje

Krok 1: Definice bindingu

Chcete-li vytvořit vlastní binding, musí váš loader Worker implementovat WorkerEntrypoint třída a exportujte ji. Metody, které v této třídě definujete, jsou metody, které bude moct Dynamic Worker volat.

import { WorkerEntrypoint } from "cloudflare:workers";

export class ChatRoom extends WorkerEntrypoint {
  async post(text: string): Promise<void> {
    // Your implementation here
  }
}

Krok 2: Předání Dynamic Workeru

Váš loader Worker poté vytvoří instanci exportované třídy, takzvaný stub, a předá ji do env.

let chatRoom = ctx.exports.ChatRoom({ props: { roomName: "#bot-chat" } });

let worker = env.LOADER.load({
  env: { CHAT_ROOM: chatRoom },
  // ...
});

Z pohledu Dynamic Workeru, CHAT_ROOM vypadá jednoduše jako běžný binding s metodami, které lze volat:

// Inside the Dynamic Worker
await this.env.CHAT_ROOM.post("Hello!");

Krok 3: Přizpůsobení pro každého uživatele pomocí props

Jedna třída může obsluhovat mnoho různých Dynamic Workers. Místo toho, abyste pro každého uživatele definovali samostatnou třídu, předáte props při vytváření stubu, který obsahuje informace specifické pro daného uživatele.

// Same class, different props per user
let aliceRoom = ctx.exports.ChatRoom({ props: { roomName: "#alice", apiKey: ALICE_KEY } });
let bobRoom   = ctx.exports.ChatRoom({ props: { roomName: "#bob", apiKey: BOB_KEY } });

Když Dynamic Worker zavolá metodu na bindingu, ve skutečnosti tím volá zpět váš loader Worker, protože právě tam se metoda spouští. Uvnitř této metody můžete číst props prostřednictvím this.ctx.props. Přístup k props má pouze loader Worker, Dynamic Worker je nikdy nevidí.

export class ChatRoom extends WorkerEntrypoint<Cloudflare.Env, ChatRoomProps> {
  async post(text: string): Promise<void> {
    // Props are set when the stub is created — the Dynamic Worker never sees them
    let roomName = this.ctx.props.roomName;
    await postToChat(roomName, text);
  }
}

Příklad: agent chatovací místnosti

Následuje kompletní příklad, který vše spojuje dohromady. Představte si, že vytváříte platformu, na které mohou AI agenti přispívat do chatovacích místností. Každý agent by měl mít možnost přispívat pouze do místnosti, která mu byla přiřazena, a nikdy by neměl vidět API klíč použitý k ověření.

Definujete ChatRoom třídu v nadřazeném Workeru. Tato třída má post metodu, jedinou metodu, kterou může Dynamic Worker na tomto bindingu volat. Uvnitř této třídy určujete, do jaké místnosti zpráva půjde, jaký API klíč se použije a jaké jméno bude ke zprávě přiřazeno.

import { WorkerEntrypoint } from "cloudflare:workers";

export class ChatRoom extends WorkerEntrypoint<Cloudflare.Env, ChatRoomProps> {
  async post(text: string): Promise<void> {
    let { apiKey, botName, roomName } = this.ctx.props;

    // Prefix the message with the bot's name.
    text = `[${botName}]: ${text}`;

    // Send it to the chat service.
    await postToChat(apiKey, roomName, text);
  }
}

type ChatRoomProps = {
  apiKey: string;
  roomName: string;
  botName: string;
};

Exportujete jeden ChatRoom třídy, ale každý stub, který vytvoříte, může mít jiné props: jiný název místnosti, jiný API klíč, jiné jméno bota. props se nastavují při vytváření stubu a Dynamic Worker je nikdy neuvidí.

Nyní ho předejte Dynamic Workeru:

// Create a stub scoped to a specific room.
let chatRoom = ctx.exports.ChatRoom({
  props: {
    apiKey,
    roomName: "#bot-chat",
    botName: "Robo",
  },
});

let worker = env.LOADER.load({
  env: {
    CHAT_ROOM: chatRoom,
  },
  compatibilityDate: "$today",
  mainModule: "index.js",
  modules: {
    "index.js": `
      export class Agent extends WorkerEntrypoint {
        async run() {
          // This is all the Dynamic Worker sees.
          await this.env.CHAT_ROOM.post("Hello!");
        }
      }
    `,
  },
  globalOutbound: null,
});

return worker.getEntrypoint("Agent").run();

Agent jen zavolá this.env.CHAT_ROOM.post("Hello!"). Nemá žádnou možnost odesílat zprávy do jiné místnosti, zobrazit nebo použít API klíč ani změnit jméno bota připojené ke svým zprávám.

Tip: Informujte svého agenta o typech

Aby mohl AI agent psát kód pro vaše bindingy, potřebuje znát jejich rozhraní. Poskytněte mu deklarace typů TypeScript s dokumentačními komentáři popisujícími jednotlivé metody. Moderní LLM rozumí TypeScriptu velmi dobře, takže jde o nejstručnější způsob popisu API v JavaScriptu. Funguje to i v případě, že agent píše čistý JavaScript.

Ujistěte se, že váš WorkerEntrypoint třída rozšiřuje typ TypeScript, aby deklarace zůstaly v souladu s implementací.

Předávání běžných Workers bindings

Chcete-li předat prostředky, jako je KV namespace nebo R2 bucketu k Dynamic Workeru musíte prostředek přivázat k loader Workeru a vytvořit vlastní binding, který ho obaluje. Přístup můžete omezit podle jednotlivých zákazníků tak, že klíčům přidáte prefix a definujete jen metody, které chcete zpřístupnit.

Příklad: vymezení KV namespace pro jednotlivé zákazníky

Nejprve k načítacímu Workeru připojte KV namespace jako binding. Poté v načítacím Workeru exportujte třídu, která tento binding využívá a definuje metody, které mohou Dynamic Workers volat:

import { WorkerEntrypoint } from "cloudflare:workers";

export class MyStorage extends WorkerEntrypoint<Cloudflare.Env, MyStorageProps> {
  // Export this class from your loader Worker
  // The Dynamic Worker will be able to call get() and put()
  async get(key: string): Promise<string | null> {
    // Prefix the key so this customer can only access their own data
    return this.env.MY_KV.get(`${this.ctx.props.prefix}:${key}`);
  }

  async put(key: string, value: string): Promise<void> {
    await this.env.MY_KV.put(`${this.ctx.props.prefix}:${key}`, value);
  }
}

type MyStorageProps = {
  prefix: string;
};

Poté ji předejte Dynamic Workeru s prefixem specifickým pro daného zákazníka:

// Create a stub scoped to this customer's prefix
let storage = ctx.exports.MyStorage({
  props: { prefix: `customer-${customerId}` },
});

let worker = env.LOADER.load({
  env: { STORAGE: storage },
  // ...
});

Dynamic Worker ho jednoduše použije jako jakýkoli jiný binding:

// Inside the Dynamic Worker, it just sees STORAGE with get and put
let value = await this.env.STORAGE.get("settings");
await this.env.STORAGE.put("settings", "dark-mode");

Stejný vzor funguje pro jakýkoli prostředek, ke kterému má váš loader Worker přístup: R2 buckety nebo D1 databáze. Přivažte prostředek k loader Workeru, exportujte třídu, která ho používá, a předejte stub Dynamic Workeru.

Trvalé úložiště vázané na každý Dynamic Worker najdete v Durable Object Facets.

Sandboxing založený na capabilities

Vlastní bindingy se řídí bezpečnostním modelem založeným na capabilities: Dynamic Worker má přístup pouze k tomu, co mu výslovně poskytnete. Pokud pro něco nedostal stub, nemá k tomu přístup.

Toto je poháněno technologií Workers RPC, známou také jako Cap'n Web ↗, RPC systém navržený k přenosu odkazů na objekty přes bezpečnostní hranice. Když Dynamic Worker obdrží stub, může volat metody daného objektu a každé volání je RPC zpět na původní objekt ve vašem loader Workeru. Stuby nemají žádný globální identifikátor a nelze je podvrhnout, jediný způsob, jak stub získat, je ho obdržet.

Zabezpečení založené na capabilities je klíčové pro návrh většiny úspěšných sandboxů, i když bývá obvykle skryté jako implementační detail: Android má Binder, Chrome má Mojo a Cloudflare Workers má Cap'n Web. Dynamic Workery vám jako vývojáři tuto sílu zpřístupňují přímo, takže si můžete postavit vlastní silný sandbox.