INTEGRITY Dokumentace

Začínáme

Miniflare API vám umožňuje odesílat události workerům bez skutečných HTTP požadavků, simulovat spojení mezi Workery a pracovat s lokálními emulacemi úložných produktů, jako je KV, R2, a Durable Objects. Díky tomu se skvěle hodí pro psaní testů nebo jiné pokročilé případy použití, kde potřebujete jemnější kontrolu.

Instalace

Miniflare se instaluje pomocí npm jako vývojovou závislost:

npm i -D miniflare

Použití

Ve všech dalších příkladech budeme předpokládat, že Node.js běží v režimu ES modulů. Toho dosáhnete nastavením type pole ve vašem package.json:

{
	...
	"type": "module"
	...
}

Chcete-li inicializovat Miniflare, importujte Miniflare třída z miniflare:

import { Miniflare } from "miniflare";

const mf = new Miniflare({
	modules: true,
	script: `
  export default {
    async fetch(request, env, ctx) {
      return new Response("Hello Miniflare!");
    }
  }
  `,
});

const res = await mf.dispatchFetch("http://localhost:8787/");
console.log(await res.text()); // Hello Miniflare!
await mf.dispose();

zbytek této dokumentace se podrobněji věnuje konfiguraci konkrétních funkcí.

Skripty pro práci s řetězci a soubory

Všimněte si, že ve výše uvedeném příkladu nastavujeme script jako řetězec. Skript jsme stejně dobře mohli umístit do souboru, jako je například worker.js, poté jste použili scriptPath vlastnost místo toho:

const mf = new Miniflare({
	scriptPath: "worker.js",
});

Sledování, opětovné načítání a uvolňování

API Miniflare je určeno především pro testovací scénáře, kde sledování souborů obvykle není potřeba. Pokud potřebujete sledovat soubory, zvažte použití samostatného sledovače souborů, například fs.watch() nebo chokidar, a volání setOptions() s vaší původní konfigurací při změně.

Chcete-li uklidit prostředky a přestat naslouchat požadavkům, měli byste dispose() vaše instance:

await mf.dispose();

Skripty (hlavní i Durable Objects) a možnosti můžete také ručně znovu načíst voláním setOptions() původním konfiguračním objektem.

Aktualizace možností a globálního rozsahu

Můžete použít setOptions metoda pro aktualizaci možností existujícího Miniflare instance. Přijímá stejný objekt options jako new Miniflare konstruktor, použije tato nastavení a poté worker znovu načte.

const mf = new Miniflare({
	script: "...",
	kvNamespaces: ["TEST_NAMESPACE"],
	bindings: { KEY: "value1" },
});

await mf.setOptions({
	script: "...",
	kvNamespaces: ["TEST_NAMESPACE"],
	bindings: { KEY: "value2" },
});

Odesílání událostí

getWorker odesílá fetch, queues, a scheduled události postupně jednotlivým workerům:

import { Miniflare } from "miniflare";

const mf = new Miniflare({
	modules: true,
	script: `
	let lastScheduledController;
	let lastQueueBatch;
	export default {
		async fetch(request, env, ctx) {
			const { pathname } = new URL(request.url);
			if (pathname === "/scheduled") {
				return Response.json({
					scheduledTime: lastScheduledController?.scheduledTime,
					cron: lastScheduledController?.cron,
				});
			} else if (pathname === "/queue") {
				return Response.json({
					queue: lastQueueBatch.queue,
					messages: lastQueueBatch.messages.map((message) => ({
					id: message.id,
					timestamp: message.timestamp.getTime(),
					body: message.body,
					bodyType: message.body.constructor.name,
					})),
				});
			} else if (pathname === "/get-url") {
				return new Response(request.url);
			} else {
				return new Response(null, { status: 404 });
			}
		},
		async scheduled(controller, env, ctx) {
			lastScheduledController = controller;
			if (controller.cron === "* * * * *") controller.noRetry();
		},
		async queue(batch, env, ctx) {
			lastQueueBatch = batch;
			if (batch.queue === "needy") batch.retryAll();
			for (const message of batch.messages) {
				if (message.id === "perfect") message.ack();
			}
		}
	}`,
});

const res = await mf.dispatchFetch("http://localhost:8787/", {
	headers: { "X-Message": "Hello Miniflare!" },
});
console.log(await res.text()); // Hello Miniflare!

const worker = await mf.getWorker();

const scheduledResult = await worker.scheduled({
	cron: "* * * * *",
});
console.log(scheduledResult); // { outcome: "ok", noRetry: true });

const queueResult = await worker.queue("needy", [
	{ id: "a", timestamp: new Date(1000), body: "a", attempts: 1 },
	{ id: "b", timestamp: new Date(2000), body: { b: 1 }, attempts: 1 },
]);
console.log(queueResult); // { outcome: "ok", retryAll: true, ackAll: false, explicitRetries: [], explicitAcks: []}

Viz 📨 Události fetch a ⏰ Naplánované události pro další podrobnosti.

HTTP server

Miniflare automaticky spustí server HTTP. Chcete-li počkat, až bude připravený, použijte await ready vlastnost:

import { Miniflare } from "miniflare";

const mf = new Miniflare({
	modules: true,
	script: `
  export default {
    async fetch(request, env, ctx) {
      return new Response("Hello Miniflare!");
    })
  }
  `,
	port: 5000,
});
await mf.ready;
console.log("Listening on :5000");

Request#cf Object

Ve výchozím nastavení Miniflare stáhne Request#cf objekt z důvěryhodného endpointu Cloudflare a uložit jej do mezipaměti node_modules/.mf/cf.json. Toto chování můžete vypnout pomocí cf možnost:

const mf = new Miniflare({
	cf: false,
});

Vlastní objekt cf můžete také zadat prostřednictvím cesty k souboru:

const mf = new Miniflare({
	cf: "cf.json",
});

Toto chování můžete také ovládat pomocí systémové proměnné prostředí, což je užitečné, pokud nepoužíváte přímo Miniflare API (například při spouštění wrangler dev):

# Disable cf fetching entirely (uses fallback data)
export CLOUDFLARE_CF_FETCH_ENABLED=false
npx wrangler dev

# Use a custom cache location for cf.json
export CLOUDFLARE_CF_FETCH_PATH=/tmp/.cf-cache.json
npx wrangler dev

Explicitní cf možnost v API Miniflare má přednost před oběma proměnnými prostředí.

HTTPS server

Chcete-li místo toho spustit server HTTPS, nastavte https možnost. Pro použití výchozí sdílený self-signed certifikát, nastavte https na true:

const mf = new Miniflare({
	https: true,
});

Chcete-li načíst existující certifikát ze souborového systému:

const mf = new Miniflare({
	// These are all optional, you don't need to include them all
	httpsKeyPath: "./key.pem",
	httpsCertPath: "./cert.pem",
});

Chcete-li místo toho načíst existující certifikát z řetězců:

const mf = new Miniflare({
	// These are all optional, you don't need to include them all
	httpsKey: "-----BEGIN RSA PRIVATE KEY-----...",
	httpsCert: "-----BEGIN CERTIFICATE-----...",
});

Pokud je pro volbu zadán jak řetězec, tak cesta (např. httpsKey a httpsKeyPath), bude upřednostněn řetězec.

Protokolování

Standardně [mf:*] logy jsou při použití API vypnuté. Chcete-li je zapnout, nastavte log vlastnost na instanci Log třída. Jejím jediným parametrem je úroveň logování udávající, které zprávy se mají zaznamenávat:

import { Miniflare, Log, LogLevel } from "miniflare";

const mf = new Miniflare({
	scriptPath: "worker.js",
	log: new Log(LogLevel.DEBUG), // Enable debug messages
});

Referenční informace

import { Miniflare, Log, LogLevel } from "miniflare";

const mf = new Miniflare({
  // All options are optional, but one of script or scriptPath is required

  log: new Log(LogLevel.INFO), // Logger Miniflare uses for debugging

  script: `
    export default {
      async fetch(request, env, ctx) {
        return new Response("Hello Miniflare!");
      }
    }
  `,
  scriptPath: "./index.js",

  modules: true, // Enable modules
  modulesRules: [
    // Modules import rule
    { type: "ESModule", include: ["**/*.js"], fallthrough: true },
    { type: "Text", include: ["**/*.text"] },
  ],
  compatibilityDate: "2021-11-23", // Opt into backwards-incompatible changes from
  compatibilityFlags: ["formdata_parser_supports_files"], // Control specific backwards-incompatible changes
  upstream: "https://miniflare.dev", // URL of upstream origin
  workers: [{
    // reference additional named workers
    name: "worker2",
    kvNamespaces: { COUNTS: "counts" },
    serviceBindings: {
      INCREMENTER: "incrementer",
      // Service bindings can also be defined as custom functions, with access
      // to anything defined outside Miniflare.
      async CUSTOM(request) {
        // `request` is the incoming `Request` object.
        return new Response(message);
      },
    },
    modules: true,
    script: `export default {
        async fetch(request, env, ctx) {
          // Get the message defined outside
          const response = await env.CUSTOM.fetch("http://host/");
          const message = await response.text();

          // Increment the count 3 times
          await env.INCREMENTER.fetch("http://host/");
          await env.INCREMENTER.fetch("http://host/");
          await env.INCREMENTER.fetch("http://host/");
          const count = await env.COUNTS.get("count");

          return new Response(message + count);
        }
      }`,
    },
  }],
  name: "worker", // Name of service
  routes: ["*site.mf/worker"],


  host: "127.0.0.1", // Host for HTTP(S) server to listen on
  port: 8787, // Port for HTTP(S) server to listen on
  https: true, // Enable self-signed HTTPS (with optional cert path)
  httpsKey: "-----BEGIN RSA PRIVATE KEY-----...",
  httpsKeyPath: "./key.pem", // Path to PEM SSL key
  httpsCert: "-----BEGIN CERTIFICATE-----...",
  httpsCertPath: "./cert.pem", // Path to PEM SSL cert chain
  cf: "./node_modules/.mf/cf.json", // Path for cached Request cf object from Cloudflare
  liveReload: true, // Reload HTML pages whenever worker is reloaded



  kvNamespaces: ["TEST_NAMESPACE"], // KV namespace to bind
  kvPersist: "./kv-data", // Persist KV data (to optional path)

  r2Buckets: ["BUCKET"], // R2 bucket to bind
  r2Persist: "./r2-data", // Persist R2 data (to optional path)

  durableObjects: {
    // Durable Object to bind
    TEST_OBJECT: "TestObject", // className
    API_OBJECT: { className: "ApiObject", scriptName: "api" },
  },
  durableObjectsPersist: "./durable-objects-data", // Persist Durable Object data (to optional path)

  cache: false, // Enable default/named caches (enabled by default)
  cachePersist: "./cache-data", // Persist cached data (to optional path)
  cacheWarnUsage: true, // Warn on cache usage, for workers.dev subdomains

  sitePath: "./site", // Path to serve Workers Site files from
  siteInclude: ["**/*.html", "**/*.css", "**/*.js"], // Glob pattern of site files to serve
  siteExclude: ["node_modules"], // Glob pattern of site files not to serve


  bindings: { SECRET: "sssh" }, // Binds variable/secret to environment
  wasmBindings: { ADD_MODULE: "./add.wasm" }, // WASM module to bind
  textBlobBindings: { TEXT: "./text.txt" }, // Text blob to bind
  dataBlobBindings: { DATA: "./data.bin" }, // Data blob to bind
});

await mf.setOptions({ kvNamespaces: ["TEST_NAMESPACE2"] }); // Apply options and reload

const bindings = await mf.getBindings(); // Get bindings (KV/Durable Object namespaces, variables, etc)

// Dispatch "fetch" event to worker
const res = await mf.dispatchFetch("http://localhost:8787/", {
  headers: { Authorization: "Bearer ..." },
});
const text = await res.text();

const worker = await mf.getWorker();

// Dispatch "scheduled" event to worker
const scheduledResult = await worker.scheduled({ cron: "30 * * * *" })

const TEST_NAMESPACE = await mf.getKVNamespace("TEST_NAMESPACE");

const BUCKET = await mf.getR2Bucket("BUCKET");

const caches = await mf.getCaches(); // Get global `CacheStorage` instance
const defaultCache = caches.default;
const namedCache = await caches.open("name");

// Get Durable Object namespace and storage for ID
const TEST_OBJECT = await mf.getDurableObjectNamespace("TEST_OBJECT");
const id = TEST_OBJECT.newUniqueId();
const storage = await mf.getDurableObjectStorage(id);

// Get Queue Producer
const producer = await mf.getQueueProducer("QUEUE_BINDING");

// Get D1 Database
const db = await mf.getD1Database("D1_BINDING")

await mf.dispose(); // Cleanup storage database connections and watcher