UNPKG

@fedify/fedify

Version:

An ActivityPub server framework

159 lines (154 loc) • 4.95 kB
import { Temporal } from "@js-temporal/polyfill"; import { URLPattern } from "urlpattern-polyfill"; globalThis.addEventListener = () => {}; import { assertEquals } from "../assert_equals-CTYbeopb.js"; import "../assert-DmFG7ppO.js"; import "../assert_instance_of-CF09JHYM.js"; import { test } from "../testing-BZ0dJ4qn.js"; import "../std__assert-vp0TKMS1.js"; import "../assert_rejects-C-sxEMM5.js"; import "../assert_is_error-nrwA1GeT.js"; import "../assert_not_equals-Dc7y-V5Q.js"; import "../assert_throws-Cn9C6Jur.js"; import { delay } from "@es-toolkit/es-toolkit"; //#region x/cfworkers.ts /** * Implementation of the {@link KvStore} interface for Cloudflare Workers KV * binding. This class provides a wrapper around Cloudflare's KV namespace to * store and retrieve JSON-serializable values using structured keys. * @since 1.6.0 */ var WorkersKvStore = class { #namespace; constructor(namespace) { this.#namespace = namespace; } #encodeKey(key) { return JSON.stringify(key); } async get(key) { const encodedKey = this.#encodeKey(key); const { value, metadata } = await this.#namespace.getWithMetadata(encodedKey, "json"); return metadata == null || metadata.expires < Date.now() ? void 0 : value; } async set(key, value, options) { const encodedKey = this.#encodeKey(key); const metadata = options?.ttl == null ? {} : { expires: Date.now() + options.ttl.total("milliseconds") }; await this.#namespace.put(encodedKey, JSON.stringify(value), options?.ttl == null ? { metadata } : { expirationTtl: Math.max(options.ttl.total("seconds"), 60), metadata }); } delete(key) { return this.#namespace.delete(this.#encodeKey(key)); } }; /** * Implementation of the {@link MessageQueue} interface for Cloudflare * Workers Queues binding. This class provides a wrapper around Cloudflare's * Queues to send messages to a queue. * * Note that this implementation does not support the `listen()` method, * as Cloudflare Workers Queues do not support message consumption in the same * way as other message queue systems. Instead, you should use * the {@link Federation.processQueuedTask} method to process messages * passed to the queue. * @since 1.6.0 */ var WorkersMessageQueue = class { #queue; /** * Cloudflare Queues provide automatic retry with exponential backoff * and Dead Letter Queues. * @since 1.7.0 */ nativeRetrial = true; constructor(queue) { this.#queue = queue; } enqueue(message, options) { return this.#queue.send(message, { contentType: "json", delaySeconds: options?.delay?.total("seconds") ?? 0 }); } enqueueMany(messages, options) { const requests = messages.map((msg) => ({ body: msg, contentType: "json" })); return this.#queue.sendBatch(requests, { delaySeconds: options?.delay?.total("seconds") ?? 0 }); } listen(_handler, _options) { throw new TypeError("WorkersMessageQueue does not support listen(). Use Federation.processQueuedTask() method instead."); } }; //#endregion //#region x/cfworkers.test.ts test({ name: "WorkersKvStore", ignore: !("navigator" in globalThis && navigator.userAgent === "Cloudflare-Workers"), async fn(t) { const { env } = t; const store = new WorkersKvStore(env.KV1); await t.step("set() & get()", async () => { await store.set(["foo", "bar"], { foo: 1, bar: 2 }); assertEquals(await store.get(["foo", "bar"]), { foo: 1, bar: 2 }); assertEquals(await store.get(["foo"]), void 0); await store.set(["foo", "baz"], "baz", { ttl: Temporal.Duration.from({ seconds: 0 }) }); assertEquals(await store.get(["foo", "baz"]), void 0); }); await t.step("delete()", async () => { await store.delete(["foo", "bar"]); assertEquals(await store.get(["foo", "bar"]), void 0); }); } }); test({ name: "WorkersMessageQueue", ignore: !("navigator" in globalThis && navigator.userAgent === "Cloudflare-Workers"), async fn(t) { const { env, messageBatches } = t; const queue = new WorkersMessageQueue(env.Q1); await queue.enqueue({ foo: 1, bar: 2 }); await waitFor(() => messageBatches.length > 0, 5e3); assertEquals(messageBatches.length, 1); assertEquals(messageBatches[0].queue, "Q1"); assertEquals(messageBatches[0].messages.length, 1); assertEquals(messageBatches[0].messages[0].body, { foo: 1, bar: 2 }); await queue.enqueue({ baz: 3, qux: 4 }, { delay: Temporal.Duration.from({ seconds: 3 }) }); await delay(2e3); assertEquals(messageBatches.length, 1); await waitFor(() => messageBatches.length > 1, 6e3); assertEquals(messageBatches[1].queue, "Q1"); assertEquals(messageBatches[1].messages.length, 1); assertEquals(messageBatches[1].messages[0].body, { baz: 3, qux: 4 }); } }); async function waitFor(predicate, timeoutMs) { let delayed = 0; while (!predicate()) { await delay(500); delayed += 500; if (delayed > timeoutMs) throw new Error("Timeout"); } } //#endregion