INTEGRITY Dokumentace

Durable Object Facets

Durable Object Facets vám umožňují načíst Durable Object třídu z Dynamický Worker a spusťte ji jako podřízený prvek vlastního Durable Object. Podřízený prvek (facet) získá vlastní izolovanou databázi SQLite, zatímco vaše třída funguje jako supervizor, který řídí přístup.

To se hodí, pokud chcete, aby dynamicky generovaný kód, například kód napsaný AI agentem, měl trvalé úložiště, aniž byste mu poskytli přímý přístup k prostoru názvů Durable Object. Váš supervisor kód načte, vytvoří facet a přesměruje do něj požadavky. Kontrolu nad tím, co dynamický kód smí dělat, si tak ponecháváte vy.

Pochopte model

Uspořádání založené na facetech má tři vrstvy:

Databáze supervisoru a databáze facetu jsou uloženy společně jako součást téhož objektu Durable Object. Dynamický kód nemůže číst databázi supervisoru: má přístup pouze ke své vlastní.

Diagram znázorňující architekturu facetů: požadavek prochází vstupním bodem Workeru do instance Durable Object obsahující Supervisor s vlastní databází SQLite, který přes ctx.facets.get() vytvoří izolovaný Facet se samostatnou databází SQLite a přeposílá mu požadavky přes facet.fetch()

Konfigurace vašeho Workeru

Váš Worker potřebuje dvě věci: třídu Durable Object s úložištěm SQLite a binding Worker Loader.

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  // Set this to today's date
  "compatibility_date": "2026-08-28",
  "main": "src/index.ts",
  "migrations": [
    {
      "tag": "v1",
      "new_sqlite_classes": [
        "AppRunner"
      ]
    }
  ],
  "worker_loaders": [
    {
      "binding": "LOADER"
    }
  ]
}
# Set this to today's date
compatibility_date = "2026-08-28"
main = "src/index.ts"

[[migrations]]
tag = "v1"
new_sqlite_classes = ["AppRunner"]

[[worker_loaders]]
binding = "LOADER"

Načtení a spuštění dynamické třídy

Následující příklad ukazuje supervisor Durable Object (AppRunner) která načte dynamický kód, vytvoří z něj facet a předává mu HTTP požadavky.

Dynamický kód je jednoduchá počítadlová aplikace, která sleduje, kolik požadavků obdržela, pomocí vlastního úložiště podporovaného SQLite. V reálné aplikaci by tento kód pocházel od AI agenta nebo z nahrání uživatelem, nikoli ze statického řetězce.

import { DurableObject } from "cloudflare:workers";

// In production, this code would come from an AI agent, a database,
// or user input — not a static string.
const AGENT_CODE = `
  import { DurableObject } from "cloudflare:workers";

  export class App extends DurableObject {
    fetch(request) {
      // Note: storage.kv provides simple KV storage backed by SQLite,
			// but you can also use SQL directly via storage.sql. See:
			// https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/

			let counter = this.ctx.storage.kv.get("counter") || 0;
      ++counter;
      this.ctx.storage.kv.put("counter", counter);

      return new Response("You have made " + counter + " requests.\\n");
    }
  }
`;

// AppRunner is your supervisor. Each instance manages one
// dynamically-loaded application.
export class AppRunner extends DurableObject {
	async fetch(request) {
		// Get a stub pointing to the "app" facet. If the facet has not
		// started yet (or has hibernated), the callback runs to tell the
		// runtime what code to load.
		const facet = this.ctx.facets.get("app", async () => {
			const worker = this.#loadDynamicWorker();

			// Extract the Durable Object class named "App" from the
			// dynamic Worker's exports.
			const appClass = worker.getDurableObjectClass("App");

			return { class: appClass };
		});

		// Forward the request to the facet.
		// You can also call RPC methods on the stub.
		return await facet.fetch(request);
	}

	#loadDynamicWorker() {
		// Use get() so the Worker stays warm across requests.
		// Each unique code version needs a unique ID.
		const codeId = "agent-code-v1";

		return this.env.LOADER.get(codeId, async () => {
			return {
				compatibilityDate: "2026-04-01",
				mainModule: "worker.js",
				modules: { "worker.js": AGENT_CODE },
				globalOutbound: null, // block network access
			};
		});
	}
}

export default {
	async fetch(request, env, ctx) {
		// Look up the AppRunner instance named "my-app".
		const obj = ctx.exports.AppRunner.getByName("my-app");

		// Forward the request to it.
		return await obj.fetch(request);
	},
};
import { DurableObject } from "cloudflare:workers";

// In production, this code would come from an AI agent, a database,
// or user input — not a static string.
const AGENT_CODE = `
  import { DurableObject } from "cloudflare:workers";

  export class App extends DurableObject {
    fetch(request) {
      // Note: storage.kv provides simple KV storage backed by SQLite,
			// but you can also use SQL directly via storage.sql. See:
			// https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/

			let counter = this.ctx.storage.kv.get("counter") || 0;
      ++counter;
      this.ctx.storage.kv.put("counter", counter);

      return new Response("You have made " + counter + " requests.\\n");
    }
  }
`;

// AppRunner is your supervisor. Each instance manages one
// dynamically-loaded application.
export class AppRunner extends DurableObject<Env> {
	async fetch(request: Request): Promise<Response> {
		// Get a stub pointing to the "app" facet. If the facet has not
		// started yet (or has hibernated), the callback runs to tell the
		// runtime what code to load.
		const facet = this.ctx.facets.get("app", async () => {
			const worker = this.#loadDynamicWorker();

			// Extract the Durable Object class named "App" from the
			// dynamic Worker's exports.
			const appClass = worker.getDurableObjectClass("App");

			return { class: appClass };
		});

		// Forward the request to the facet.
		// You can also call RPC methods on the stub.
		return await facet.fetch(request);
	}

	#loadDynamicWorker() {
		// Use get() so the Worker stays warm across requests.
		// Each unique code version needs a unique ID.
		const codeId = "agent-code-v1";

		return this.env.LOADER.get(codeId, async () => {
			return {
				compatibilityDate: "2026-04-01",
				mainModule: "worker.js",
				modules: { "worker.js": AGENT_CODE },
				globalOutbound: null, // block network access
			};
		});
	}
}

export default {
	async fetch(
		request: Request,
		env: Env,
		ctx: ExecutionContext,
	): Promise<Response> {
		// Look up the AppRunner instance named "my-app".
		const obj = ctx.exports.AppRunner.getByName("my-app");

		// Forward the request to it.
		return await obj.fetch(request);
	},
};

V tomto příkladu:

this.ctx.facets reference

this.ctx.facets objekt je dostupný uvnitř každé třídy Durable Object. Poskytuje metody pro vytváření, ukončování a mazání facetů. Jeden Durable Object může mít libovolný počet facetů s různými názvy, přičemž každý má svou vlastní nezávislou databázi SQLite.

get

this.ctx.facets.get(name string, callback () => FacetStartupOptions) Fetcher

Vytvoří nebo obnoví facet se zadaným názvem a vrátí stub, pomocí kterého mu můžete odesílat požadavky.

Pokud facet ještě nebyl spuštěn nebo byl hibernován, runtime zavolá getStartupOptions k určení, jaký kód se má načíst. V opačném případě se použije existující facet a callback se nevyvolá. callback lze volitelně async (tj. návratem Promise<FacetStartupOptions>).

Vrácený stub se chová jako Stub Durable Object. Můžete zavolat .fetch() k odesílání HTTP požadavků, nebo přímo volejte metody RPC.

abort

this.ctx.facets.abort(name string, reason any) void

Vypne běžící facet a znehodnotí všechny existující stuby. Jakékoli následné volání na znehodnoceném stubu vyvolá reason. Úložiště facetu se zachová.

Po ukončení můžete zavolat get() znovu, abyste restartovali facet, a to i s jinou třídou. Díky tomu abort() užitečné pro aktualizace kódu: ukončete facet se starou verzí a poté zavolejte get() s callbackem, který vrátí novou třídu.

delete

this.ctx.facets.delete(name string) void

Ukončí facet (pokud běží) a natrvalo odstraní jeho databázi SQLite. Pokud zavoláte get() se stejným názvem, začíná facet s prázdnou databází.

Použijte delete() k vyčištění úložiště pro facety, které už nejsou potřeba.

FacetStartupOptions

Objekt vrácený getStartupOptions callback.

class DurableObjectClass

Třída Durable Object, kterou se má pro facet instancovat. Získáte ji zavoláním worker.getDurableObjectClass("ClassName") na stubu Dynamic Workeru.

id DurableObjectId | string Optional

ID, které facet vidí jako své vlastní ctx.id. Pokud se vynechá, facet zdědí ID nadřazeného Durable Object.

Úložiště izolátu

Supervisor a každý facet mají oddělené databáze SQLite. Dynamický kód používá standardní API úložiště Durable Object a všechny operace cílí na vlastní databázi facetu.

Díky této izolaci nemusíte dynamickému kódu svěřovat data svého supervisoru. Metadata, počítadla pro fakturaci nebo stav řízení přístupu můžete ukládat v databázi supervisoru a facet k nim nemá přístup ani je nemůže upravovat.

V produkčním prostředí byste dynamický kód obvykle ukládali přímo v databázi supervizoru a načítali jej v #loadDynamicWorker() metodu. Díky tomu zůstává kód spojen s instancí Durable Object, která ho spravuje.