From c2c879a174260b5119ee746aa72cc1e141dafa91 Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Sun, 31 Mar 2024 15:03:00 +0900 Subject: [PATCH] Update docs and demo --- docs/manual/federation.md | 29 ++++++++++++--- docs/tutorial.md | 58 +++++++++++++++-------------- examples/blog/.vscode/settings.json | 1 + examples/blog/federation/mod.ts | 12 +++--- examples/blog/import_map.g.json | 2 + examples/blog/import_map.json | 1 + federation/middleware.ts | 13 ------- 7 files changed, 63 insertions(+), 53 deletions(-) diff --git a/docs/manual/federation.md b/docs/manual/federation.md index 13e5f5cf..111a3ebf 100644 --- a/docs/manual/federation.md +++ b/docs/manual/federation.md @@ -45,9 +45,15 @@ that the `Federation` object uses to store several kinds of cache data and to maintain the queue of outgoing activities. `KvStore` is an abstract interface that represents a key-value store. -Currently, there is only one implementation of `KvStore`, which is the -`MemoryKvStore` class, but you can define your own `KvStore` implementation -if you want to use a different key-value store. +Currently, there are two implementations of `KvStore`, which are the +`MemoryKvStore` and `DenoKvStore` classes. The `MemoryKvStore` class is for +testing and development purposes, and the `DenoKvStore` class is Deno KV-backed +implementation for production use (as you can guess from the name, it is only +available in Deno runtime). However, you can define your own `KvStore` +implementation if you want to use a different key-value store.[^1] + +[^1]: We are welcome to contributions of `KvStore` implementations for other + key-value stores. ### `kvPrefixes` @@ -71,9 +77,20 @@ If you don't provide this option, activities will not be queued and will be sent immediately. `MessageQueue` is an abstract interface that represents a message queue. -Currently, there is only one implementation of `MessageQueue`, which is the -`InProcessMessageQueue` class, but you can define your own `MessageQueue` -implementation if you want to use a different message queue. +Currently, there are only two implementations of `MessageQueue`, which are +the `InProcessMessageQueue` and `DenoKvMessageQueue` classes. +The `InProcessMessageQueue` class is for testing and development purposes, +and the `DenoKvMessageQueue` class is a Deno KV-backed implementation for +production use (as you can guess from the name, it is only available in Deno +runtime). However, you can define your own `MessageQueue` implementation if +you want to use a different message queue.[^1] + +> [!IMPORTANT] +> While the `queue` option is optional, it is highly recommended to provide +> a message queue implementation in production environments. If you don't +> provide a message queue implementation, activities will not be queued and +> will be sent immediately. This can make delivery of activities unreliable +> and can cause performance issues. ### `documentLoader` diff --git a/docs/tutorial.md b/docs/tutorial.md index 15db91e5..f7ef0fd8 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -145,10 +145,10 @@ ActivityPub activities and actors. Let's modify the server script to use the `Federation` object: ~~~~ typescript -import { Federation } from "@fedify/fedify"; +import { Federation, MemoryKvStore } from "@fedify/fedify"; const federation = new Federation({ - kv: await Deno.openKv(), + kv: new MemoryKvStore(), }); ~~~~ @@ -156,23 +156,12 @@ In the above code, we import the `Federation` object from the Fedify framework and create a new `Federation` object. We pass an object to the `new Federation()` constructor, which is the configuration object. The `kv` property is a key-value store that is used to store several internal -data of the `Federation` object. We use the [`Deno.openKv()`] function to open +data of the `Federation` object. We use the `MemoryKvStore` to open a key-value store. -> [!NOTE] -> Since the `Deno.openKv()` function is one of the unstable APIs in Deno as of -> March 2024, you need to specify the `"unstable": ["kv"]` field in -> the *deno.json* file to use the `Deno.openKv()` function. The *deno.json* -> file should look like this: -> -> ~~~~ json -> { -> "imports": { -> "@fedify/fedify": "jsr:@fedify/fedify@^0.3.0" -> }, -> "unstable": ["kv"] -> } -> ~~~~ +> [!IMPORTANT] +> Since `MemoryKvStore` is for testing and development purposes, you should +> use a persistent key-value store like [`DenoKvStore`] for production use. Then, we pass the incoming `Request` to the `federation.handle()` method: @@ -217,10 +206,10 @@ like). Let's create an actor dispatcher for our server: ~~~~ typescript -import { Federation, Person } from "@fedify/fedify"; +import { Federation, MemoryKvStore, Person } from "@fedify/fedify"; const federation = new Federation({ - kv: await Deno.openKv(), + kv: new MemoryKvStore(), }); federation.setActorDispatcher("/users/{handle}", async (ctx, handle) => { @@ -371,7 +360,7 @@ set the `treatHttps` property to `true` in the `Federation` object: ~~~~ typescript const federation = new Federation({ - kv: await Deno.openKv(), + kv: new MemoryKvStore(), treatHttps: true, // Treat HTTP requests as HTTPS }); ~~~~ @@ -417,7 +406,7 @@ represents the `Follow` activity. We will use the `Follow` class to handle incoming follow requests: ~~~~ typescript -import { Federation, Follow, Person } from "@fedify/fedify"; +import { Federation, Follow, Person, MemoryKvStore } from "@fedify/fedify"; ~~~~ Then, we register an inbox listener for the `Follow` activity: @@ -493,7 +482,7 @@ Fedify provides helper functions to generate and export/import keys: ~~~~ typescript import { - Federation, Follow, Person, + Federation, Follow, Person, MemoryKvStore, // Import helper functions: exportJwk, generateCryptoKeyPair, importJwk, } from "@fedify/fedify"; @@ -513,11 +502,6 @@ should be chained after the `Federation.setActorDispatcher()` method: ~~~~ typescript const kv = await Deno.openKv(); // Open the key-value store -const federation = new Federation({ - kv, - treatHttps: true, -}); - federation .setActorDispatcher("/users/{handle}", async (ctx, handle, key) => { if (handle !== "me") return null; @@ -564,6 +548,26 @@ at the first time and store it in the key-value store. When the actor *me* is dispatched again, the key pair dispatcher loads the key pair from the key-value store. +> [!IMPORTANT] +> In the above code, we use the `Deno.openKv()` function to open the key-value +> store, which is persistent. However, Deno KV is an unstable feature as of +> March 2024, so you need to add the `"unstable": ["kv"]` field to the +> *deno.json* file: +> +> ~~~~ json +> { +> "imports": { +> "@fedify/fedify": "jsr:@fedify/fedify@^0.4.0" +> }, +> "unstable": ["kv"] +> } +> ~~~~ + +> [!NOTE] +> Although we use the Deno KV database in this tutorial, you can use any +> other your favorite database to store the key pair. The key-value store +> is just an example. + Restart the server and make an HTTP request to the actor *me* using `curl`. Now you should see the actor *me* with the public key in the response: diff --git a/examples/blog/.vscode/settings.json b/examples/blog/.vscode/settings.json index 2c57c374..623187af 100644 --- a/examples/blog/.vscode/settings.json +++ b/examples/blog/.vscode/settings.json @@ -33,6 +33,7 @@ "activitypub", "codegen", "deno", + "denokv", "docloader", "fedify", "fediverse", diff --git a/examples/blog/federation/mod.ts b/examples/blog/federation/mod.ts index 2774d07e..47d68826 100644 --- a/examples/blog/federation/mod.ts +++ b/examples/blog/federation/mod.ts @@ -1,11 +1,7 @@ import { Temporal } from "@js-temporal/polyfill"; import { parse } from "@std/semver"; import { dirname, join } from "@std/path"; -import { - Federation, - InProcessMessageQueue, - MemoryKvStore, -} from "@fedify/fedify/federation"; +import { Federation } from "@fedify/fedify/federation"; import { Accept, Activity, @@ -19,6 +15,7 @@ import { Person, Undo, } from "@fedify/fedify/vocab"; +import { DenoKvMessageQueue, DenoKvStore } from "@fedify/fedify/x/denokv"; import { getBlog } from "../models/blog.ts"; import { addComment, Comment, getComments } from "../models/comment.ts"; import { @@ -28,15 +25,16 @@ import { removeFollower, } from "../models/follower.ts"; import { countPosts, getPosts, toArticle } from "../models/post.ts"; +import { openKv } from "../models/kv.ts"; // The `Federation` object is a registry that registers // federation-related callbacks: export const federation = new Federation({ // The following key-value storage is used for internal cache: - kv: new MemoryKvStore(), + kv: new DenoKvStore(await openKv()), // The following message queue is used for maintaining outgoing activities: - queue: new InProcessMessageQueue(), + queue: new DenoKvMessageQueue(await openKv()), // The following option is useful for local development, as Fresh's dev // server does not support HTTPS: diff --git a/examples/blog/import_map.g.json b/examples/blog/import_map.g.json index a9900915..80c0c78b 100644 --- a/examples/blog/import_map.g.json +++ b/examples/blog/import_map.g.json @@ -15,6 +15,7 @@ "@js-temporal/polyfill": "npm:@js-temporal/polyfill@^0.4.4", "@phensley/language-tag": "npm:@phensley/language-tag@^1.8.0", "@std/assert": "jsr:@std/assert@^0.220.1", + "@std/async/delay": "jsr:@std/async@^0.220.1/delay", "@std/bytes": "jsr:@std/bytes@^0.220.1", "@std/collections": "jsr:@std/collections@^0.220.1", "@std/encoding": "jsr:@std/encoding@^0.220.1", @@ -37,6 +38,7 @@ "@preact/signals": "https://esm.sh/*@preact/signals@1.2.2", "@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.5.1", "@std/dotenv/load": "jsr:@std/dotenv@^0.220.1/load", + "@fedify/fedify/x/denokv": "../../x/denokv.ts", "markdown-it": "npm:markdown-it@^14.0.0", "preact": "https://esm.sh/preact@10.19.6", "preact/": "https://esm.sh/preact@10.19.6/", diff --git a/examples/blog/import_map.json b/examples/blog/import_map.json index 291dbd01..9ea40edc 100644 --- a/examples/blog/import_map.json +++ b/examples/blog/import_map.json @@ -14,6 +14,7 @@ "@fedify/fedify/vocab": "../../vocab/mod.ts", "@fedify/fedify/webfinger": "../../webfinger/mod.ts", "@fedify/fedify/x/fresh": "../../x/fresh.ts", + "@fedify/fedify/x/denokv": "../../x/denokv.ts", "markdown-it": "npm:markdown-it@^14.0.0", "preact": "https://esm.sh/preact@10.19.6", "preact/": "https://esm.sh/preact@10.19.6/", diff --git a/federation/middleware.ts b/federation/middleware.ts index c361e15a..e3924435 100644 --- a/federation/middleware.ts +++ b/federation/middleware.ts @@ -168,19 +168,6 @@ export class Federation { authenticatedDocumentLoaderFactory ?? getAuthenticatedDocumentLoader; this.#treatHttps = treatHttps ?? false; - if (backoffSchedule != null) { - // TODO: Deno KV Queue's backoff schedule is too limited for our needs. - // We should manually implement our own backoff retrial mechanism. - // Fortunately, Deno KV Queue's delay option allows up to 30 days. - // We can use that to implement our own backoff schedule. - if (backoffSchedule.length > 5) { - throw new Error("Backoff schedule must have at most 5 entries."); - } - const hour = Temporal.Duration.from({ hours: 1 }); - if (backoffSchedule.some((d) => Temporal.Duration.compare(d, hour) > 0)) { - throw new Error("Backoff schedule must not exceed 1 hour."); - } - } this.#backoffSchedule = backoffSchedule ?? [ 3_000, 15_000,