Skip to content

Commit

Permalink
Update docs and demo
Browse files Browse the repository at this point in the history
  • Loading branch information
dahlia committed Mar 31, 2024
1 parent 1bd3154 commit c2c879a
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 53 deletions.
29 changes: 23 additions & 6 deletions docs/manual/federation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand All @@ -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`

Expand Down
58 changes: 31 additions & 27 deletions docs/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,34 +145,23 @@ 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<void>({
kv: await Deno.openKv(),
kv: new MemoryKvStore(),
});
~~~~

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:

Expand Down Expand Up @@ -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<void>({
kv: await Deno.openKv(),
kv: new MemoryKvStore(),
});

federation.setActorDispatcher("/users/{handle}", async (ctx, handle) => {
Expand Down Expand Up @@ -371,7 +360,7 @@ set the `treatHttps` property to `true` in the `Federation` object:

~~~~ typescript
const federation = new Federation<void>({
kv: await Deno.openKv(),
kv: new MemoryKvStore(),
treatHttps: true, // Treat HTTP requests as HTTPS
});
~~~~
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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";
Expand All @@ -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<void>({
kv,
treatHttps: true,
});

federation
.setActorDispatcher("/users/{handle}", async (ctx, handle, key) => {
if (handle !== "me") return null;
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions examples/blog/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"activitypub",
"codegen",
"deno",
"denokv",
"docloader",
"fedify",
"fediverse",
Expand Down
12 changes: 5 additions & 7 deletions examples/blog/federation/mod.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 {
Expand All @@ -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<TContextData>` object is a registry that registers
// federation-related callbacks:
export const federation = new Federation<void>({
// 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:
Expand Down
2 changes: 2 additions & 0 deletions examples/blog/import_map.g.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -37,6 +38,7 @@
"@preact/signals": "https://esm.sh/*@preact/[email protected]",
"@preact/signals-core": "https://esm.sh/*@preact/[email protected]",
"@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/[email protected]",
"preact/": "https://esm.sh/[email protected]/",
Expand Down
1 change: 1 addition & 0 deletions examples/blog/import_map.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/[email protected]",
"preact/": "https://esm.sh/[email protected]/",
Expand Down
13 changes: 0 additions & 13 deletions federation/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,19 +168,6 @@ export class Federation<TContextData> {
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,
Expand Down

0 comments on commit c2c879a

Please sign in to comment.