@fedify/fedify
Version:
An ActivityPub server framework
159 lines (154 loc) • 4.95 kB
JavaScript
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