Unverified Commit ca12a15a authored by Hong Minhee (洪 民憙)'s avatar Hong Minhee (洪 民憙) Committed by GitHub
Browse files

Merge pull request #242 from dahlia/cloudflare-workers

parents 94f077ee d7cb9ef3
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -41,13 +41,24 @@ To be released. Note that 1.6.0 was skipped due to a mistake in the versioning.
     -  Added `HttpMessageSignaturesSpecDeterminer` interface.
     -  Added `--first-knock` option to `fedify lookup` command.

 -  Fedify now supports [Cloudflare Workers].  [[#233]]

     -  Added `Federation.processQueuedTask()` method.  [[#242]]
     -  Added `Message` type.  [[#242]]
     -  Added `WorkersKvStore` class.  [[#241], [#242]]
     -  Added `WorkersMessageQueue` class.  [[#241], [#242]]

 -  The minimum supported version of Node.js is now 22.0.0.

[RFC 9421]: https://www.rfc-editor.org/rfc/rfc9421
[Cloudflare Workers]: https://workers.cloudflare.com/
[#208]: https://github.com/fedify-dev/fedify/issues/208
[#227]: https://github.com/fedify-dev/fedify/issues/227
[#233]: https://github.com/fedify-dev/fedify/issues/233
[#235]: https://github.com/fedify-dev/fedify/pull/235
[#237]: https://github.com/fedify-dev/fedify/pull/237
[#241]: https://github.com/fedify-dev/fedify/issues/241
[#242]: https://github.com/fedify-dev/fedify/pull/242


Version 1.5.3
+5 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ import deflist from "markdown-it-deflist";
import footnote from "markdown-it-footnote";
import { jsrRef } from "markdown-it-jsr-ref";
import process from "node:process";
import { ModuleKind, ModuleResolutionKind, ScriptTarget } from "typescript";
import { defineConfig } from "vitepress";
import {
  groupIconMdPlugin,
@@ -210,12 +211,16 @@ export default withMermaid(defineConfig({
      transformerTwoslash({
        twoslashOptions: {
          compilerOptions: {
            moduleResolution: ModuleResolutionKind.Bundler,
            module: ModuleKind.ESNext,
            target: ScriptTarget.ESNext,
            lib: ["dom", "dom.iterable", "esnext"],
            types: [
              "dom",
              "dom.iterable",
              "esnext",
              "@teidesu/deno-types/full",
              "@cloudflare/workers-types/experimental",
            ],
            // @ts-ignore: Although it's typed as string, it's actually an array
            jsx: ["react-jsx"],
+50 −0
Original line number Diff line number Diff line
@@ -142,6 +142,56 @@ const federation = createFederation<void>({
[`PostgresKvStore`]: https://jsr.io/@fedify/postgres/doc/kv/~/PostgresKvStore
[@fedify/postgres]: https://github.com/fedify-dev/postgres

### `WorkersKvStore` (Cloudflare Workers only)

*This API is available since Fedify 1.6.0.*

`WorkersKvStore` is a key–value store implementation for [Cloudflare Workers]
that uses Cloudflare's built-in [Cloudflare Workers KV] API.  It provides
persistent storage and good performance for Cloudflare Workers environments.
It's suitable for production use in Cloudflare Workers applications.

Best for
:   Production use in Cloudflare Workers environments.

Pros
:   Persistent storage, good performance, easy to set up.

Cons
:   Only available in Cloudflare Workers runtime.

~~~~ typescript twoslash
// @noErrors: 2345
import type { FederationBuilder } from "@fedify/fedify";
const builder = undefined as unknown as FederationBuilder<void>;
// ---cut-before---
import type { Federation } from "@fedify/fedify";
import { WorkersKvStore } from "@fedify/fedify/x/cfworkers";

export default {
  async fetch(request, env, ctx) {
    const federation: Federation<void> = await builder.build({
      kv: new WorkersKvStore(env.KV_BINDING),
    });
    return await federation.fetch(request, { contextData: undefined });
  }
} satisfies ExportedHandler<{ KV_BINDING: KVNamespace<string> }>;
~~~~

> [!NOTE]
> Since your `KVNamespace` is not bound to a global variable, but rather
> passed as an argument to the `fetch()` method, you need to instantiate
> your `Federation` object inside the `fetch()` method, rather than the top
> level.
>
> For better organization, you probably want to use a builder pattern to
> register your dispatchers and listeners before instantiating the `Federation`
> object.  See the [*Builder pattern for structuring*
> section](./federation.md#builder-pattern-for-structuring) for details.

[Cloudflare Workers]: https://workers.cloudflare.com/
[Cloudflare Workers KV]: https://developers.cloudflare.com/kv/


Implementing a custom `KvStore`
-------------------------------
+82 −0
Original line number Diff line number Diff line
@@ -213,6 +213,88 @@ const federation = createFederation({
[@fedify/amqp]: https://github.com/fedify-dev/amqp
[RabbitMQ]: https://www.rabbitmq.com/

### `WorkersMessageQueue` (Cloudflare Workers only)

*This API is available since Fedify 1.6.0.*

`WorkersMessageQueue` is a message queue implementation for [Cloudflare Workers]
that uses Cloudflare's built-in [Cloudflare Queues] API.  It provides
scalability and high performance, making it suitable for production use in
Cloudflare Workers environments.  It requires a Cloudflare Queues setup and
management.

Best for
:   Production use in Cloudflare Workers environments.

Pros
:   Persistent, reliable, scalable, easy to set up.

Cons
:   Only available in Cloudflare Workers runtime.

~~~~ typescript twoslash
// @noErrors: 2322 2345
import type { FederationBuilder, KvStore } from "@fedify/fedify";
const builder = undefined as unknown as FederationBuilder<void>;
// ---cut-before---
import type { Federation, Message } from "@fedify/fedify";
import { WorkersMessageQueue } from "@fedify/fedify/x/cfworkers";

export default {
  async fetch(request, env, ctx) {
    const federation: Federation<void> = await builder.build({
// ---cut-start---
      kv: undefined as unknown as KvStore,
// ---cut-end---
      queue: new WorkersMessageQueue(env.QUEUE_BINDING),
    });
    // Omit the rest of the code for brevity
  },

  // Since defining a `queue()` method is the only way to consume messages
  // from the queue in Cloudflare Workers, we need to define it so that
  // the messages can be manually processed by `Federation.processQueuedTask()`
  // method:
  async queue(batch, env, ctx) {
    const federation: Federation<void> = await builder.build({
// ---cut-start---
      kv: undefined as unknown as KvStore,
// ---cut-end---
      queue: new WorkersMessageQueue(env.QUEUE_BINDING),
    });
    for (const msg of batch.messages) {
      await federation.processQueuedTask(
        undefined,  // You need to pass your context data here
        msg.body as Message,  // You need to cast the message body to `Message`
      );
    }
  }
} satisfies ExportedHandler<{ QUEUE_BINDING: Queue }>;
~~~~

> [!NOTE]
> Since your `Queue` is not bound to a global variable, but rather passed as
> an argument to the `fetch()` and `queue()` methods, you need to instantiate
> your `Federation` object inside these methods, rather than at the top level.
>
> For better organization, you probably want to use a builder pattern to
> register your dispatchers and listeners before instantiating the `Federation`
> object.  See the [*Builder pattern for structuring*
> section](./federation.md#builder-pattern-for-structuring) for details.

> [!NOTE]
> The [Cloudflare Queues] API does not provide a way to poll messages from
> the queue, so `WorkersMessageQueue.listen()` method always throws
> a `TypeError` when invoked.  Instead, you should define a `queue()` method
> in your Cloudflare worker, which will be called by the Cloudflare Queues
> API when new messages are available in the queue.  Inside the `queue()`
> method, you need to call `Federation.processQueuedTask()` method to manually
> process the messages.  The `queue()` method is the only way to consume
> messages from the queue in Cloudflare Workers.

[Cloudflare Workers]: https://workers.cloudflare.com/
[Cloudflare Queues]: https://developers.cloudflare.com/queues/


Implementing a custom `MessageQueue`
------------------------------------
+3 −1
Original line number Diff line number Diff line
{
  "devDependencies": {
    "@braintree/sanitize-url": "^7.1.1",
    "@cloudflare/workers-types": "4.20250529.0",
    "@deno/kv": "^0.8.4",
    "@fedify/amqp": "^0.2.0",
    "@fedify/fedify": "^1.6.1-dev.828",
    "@fedify/fedify": "1.6.1-pr.242.863",
    "@fedify/postgres": "^0.3.0",
    "@fedify/redis": "^0.4.0",
    "@hono/node-server": "^1.13.7",
@@ -30,6 +31,7 @@
    "mermaid": "^11.4.1",
    "postgres": "^3.4.5",
    "stringify-entities": "^4.0.4",
    "typescript": "^5.8.3",
    "vitepress": "^1.5.0",
    "vitepress-plugin-group-icons": "^1.3.5",
    "vitepress-plugin-llms": "^1.1.0",
Loading