npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fluojs/platform-cloudflare-workers

v2.1.2

Published

Cloudflare Workers HTTP adapter for the Fluo runtime built on the shared Web Request/Response core.

Readme

@fluojs/platform-cloudflare-workers

Cloudflare Workers HTTP adapter for the fluo runtime, optimized for the edge.

Table of Contents

Installation

npm install @fluojs/platform-cloudflare-workers

This package is intended to run on Cloudflare Workers. The published manifest intentionally does not declare engines.node, so npm metadata stays aligned with the Workers runtime contract; the repository's Node.js >=24.11.0 <27 requirement only applies to the maintainer build/test toolchain.

When to Use

Use this package when deploying fluo applications to Cloudflare Workers. It is designed for the serverless edge environment, providing a lightweight fetch-based adapter that respects Worker isolate constraints and native Web APIs.

The adapter binds each request lifecycle to executionContext.waitUntil(...) after the dispatcher is bound and keeps in-flight dispatches, WebSocket upgrades through the upgraded server socket's terminal close, and SSE (text/event-stream) response bodies alive during close() so Worker shutdown does not drop active work mid-request.

During application shutdown, the adapter stops accepting new ingress immediately and gives active HTTP handlers a bounded 10-second drain window before close() fails with a timeout instead of hanging indefinitely. While that drain is still in progress, a concurrent listen() call rejects with Cloudflare Workers adapter cannot listen while shutdown is still draining. instead of reopening the Worker. Once closed, follow-up HTTP and WebSocket upgrade requests receive the same JSON 503 shutdown response until the adapter is explicitly listened again. Lazy hosts keep returning shutdown responses while a timed-out close is still draining, but they clear that temporary gate once the underlying close eventually settles so a later request can bootstrap a fresh Worker application.

Quick Start

These entrypoint fragments belong in an existing Workers project. They require AppModule from the application-owned ./app.module, the @fluojs/runtime dependency, decorator build configuration, and Worker deployment configuration. Retain the module composition in Application Bootstrap Protocol and the host ownership contracts below.

Direct Application Creation

Create the concrete adapter first, then let FluoFactory.create own application creation and call app.listen() to bind its dispatcher without opening a socket.

import { FluoFactory } from '@fluojs/runtime';
import { CloudflareWorkerHttpApplicationAdapter } from '@fluojs/platform-cloudflare-workers';
import { AppModule } from './app.module';

const adapter = CloudflareWorkerHttpApplicationAdapter.create();
const app = await FluoFactory.create(AppModule, { adapter });

await app.listen();

export default {
  fetch: (req, env, ctx) => adapter.fetch(req, env, ctx),
};

Host-Owned Lazy Application

Use a CloudflareWorkerApplicationHost only when this isolate must own lazy bootstrap, generation replacement, retry, and close/drain recovery. It is a different capability from direct application creation, not a second Factory.

import { CloudflareWorkerApplicationHost } from '@fluojs/platform-cloudflare-workers';
import { AppModule } from './app.module';

const worker = CloudflareWorkerApplicationHost.create(AppModule);

export default {
  fetch: worker.fetch,
};

Close Ownership and Lazy Restart

Cloudflare Workers does not provide a host-invoked shutdown callback to the exported fetch handler. When migrating NestJS shutdown hooks, choose an application-owned close trigger. An out-of-band lifecycle trigger, running outside any worker.fetch invocation, may call await worker.close() directly. A management route handled inside the same worker.fetch invocation must return its current response without awaiting close(), then observe it with executionContext.waitUntil(worker.close()) or an equivalent non-self-awaiting mechanism; otherwise close() waits for that active request to drain and reaches the shutdown timeout. Exporting worker.fetch alone does not arrange a close call.

A successful worker.close() is intentionally restartable. It releases the current host generation; a later worker.fetch(...) creates a fresh generation in the isolate, rerunning bootstrap lifecycle hooks and reconstructing application singleton providers. For an env-aware host, that generation uses the cached configuration from the first environment without calling its factory again. Do not treat close() as a terminal Worker shutdown signal. If an application needs terminal behavior, it must own and enforce that state explicitly.

Env-Aware Lazy Host

Use the { fromEnv } overload of the same CloudflareWorkerApplicationHost.create method when the first Worker env must select the root module or bootstrap options. The factory runs once per host instance before module registration; its configuration remains cached while each running application generation reuses it.

import { CloudflareWorkerApplicationHost } from '@fluojs/platform-cloudflare-workers';
import { createAppModule } from './app.module';

interface WorkerEnv {
  API_PREFIX: string;
  DB: D1Database;
}

const worker = CloudflareWorkerApplicationHost.create<WorkerEnv>({
  fromEnv: (env) => ({
    rootModule: createAppModule({ database: env.DB }),
    options: {
      globalPrefix: env.API_PREFIX,
    },
  }),
});

export default {
  fetch: worker.fetch,
};

worker.ready(env) requires an explicit env for the same reason. The first supplied environment determines the singleton bootstrap configuration; later request environments still attach to request.cloudflare.env, but do not reconfigure it. A successful worker.close() releases only the current application generation: the next ready(env) or fetch(...) creates a fresh application from the retained first-environment module and options without calling the factory again. Use CloudflareWorkerApplicationHost.create(AppModule, options) when bootstrap configuration is already available before the first Worker request.

For the fixed-module host's request-bound env path, fetch-time bindings cannot supply ConfigModule.forRoot(...) or singleton bootstrap providers. Read, validate, and narrow request-varying bindings, then pass application-shaped values to provider methods. Choose the { fromEnv } overload only when the first environment must configure the application before module registration.

Once the factory returns a configuration, that host instance caches it. If the factory itself throws, no configuration is cached and a later call may retry. Separate host instances do not share configuration or generations.

Common Patterns

Early Hints are unsupported

The Workers Response API does not provide a request-handler write primitive for an informational response before the final response, so context.response.earlyHints is absent. Check for capability presence before use. Cloudflare deployment/cache features that may generate Early Hints are host configuration and are not exposed as a Fluo response writer.

Streaming multipart consumption

Set multipart: { strategy: 'stream' } at application bootstrap to receive multipart data incrementally. For multipart routes, RequestContext.request.body is an AsyncIterableIterator<MultipartPart>: field parts expose kind: 'field', name, value, and headers; file parts expose kind: 'file', name, filename, contentType, headers, and a single-consumer ReadableStream<Uint8Array> at stream. Finish or cancel each file stream before requesting the next part.

Runtime route dispatch owns an iterator created for a route and automatically calls return() after the handler finishes, cancelling and releasing an active source. Standalone parseMultipartStream(...) consumers own that responsibility: consume the iterator to completion or call return() when ending early.

Byte Ranges and Cache Validation

Workers preserves the shared @fluojs/http single-byte-range and If-Range contract through fetch dispatch. After conditional-request evaluation selects cache validators, a valid Range: bytes= request yields the portable 206 identity-byte response; If-Range reuses those selected validators, while malformed or multi-range fields retain the full response and an unsatisfiable range yields bodyless 416. HEAD mirrors GET metadata without consuming a stream.

Working with WebSocketPairs

The adapter supports Cloudflare's native WebSocketPair for real-time communication via the @fluojs/websockets/cloudflare-workers binding. Upgrade handling is opt-in through that binding, and createWebSocketPair can be injected for non-hosted runtime tests. Configure the binding before listen() starts the Worker dispatch boundary; once listen() has run, the binding identity is frozen for that adapter instance. Replacing or clearing it is rejected even after close(), so upgrade ownership cannot change underneath an isolate that has already crossed the public listen boundary.

import { Module } from '@fluojs/core';
import { WebSocketGateway } from '@fluojs/websockets';
import { CloudflareWorkersWebSocketModule } from '@fluojs/websockets/cloudflare-workers';

@WebSocketGateway({ path: '/ws' })
export class EdgeGateway {}

@Module({
  imports: [CloudflareWorkersWebSocketModule.forRoot()],
  providers: [EdgeGateway],
})
export class RealtimeModule {}

Import RealtimeModule into the application module graph before bootstrap. During application bootstrap, CloudflareWorkersWebSocketModule discovers the gateway and installs the Worker adapter binding through the version-1 realtime capability before app.listen() freezes it. Direct binding configuration is removed; do not add or replace the binding after the listen boundary.

Edge-Native Middleware

Standard fluo middleware (CORS, Global Prefix, etc.) is fully supported through FluoFactory.create(...) and optimized for the Cloudflare environment. CloudflareWorkerHttpApplicationAdapter.create(...) accepts adapter-owned parsing and websocket-pair options; pass routing and middleware options to FluoFactory.create(...) directly, or to the fixed-module host options when the host-owned lazy capability is required.

const worker = CloudflareWorkerApplicationHost.create(AppModule, {
  globalPrefix: 'api/v1',
  cors: { origin: '*' },
});

Behavior Notes

  • The public concrete CloudflareWorkerHttpApplicationAdapter.fetch(request, env, executionContext) contract requires the Worker executionContext; direct callers must pass the real third ctx argument so every HTTP, SSE, and WebSocket ingress registers active work with executionContext.waitUntil(...). Migration: replace direct two-argument adapter calls with adapter.fetch(request, env, ctx).
  • fetch() registers active work with executionContext.waitUntil(...) after listen() or the lazy host binds the dispatcher; upgraded server WebSockets keep that lifecycle and the close drain open until their terminal close event, while SSE (text/event-stream) responses keep them open until the body finishes or is canceled. Synchronous SSE reader or tracked-stream setup failures release the lifecycle before propagating. Before that lifecycle boundary, upgrade requests and HTTP dispatch do not reach application handlers.
  • Adapter options such as maxBodySize are validated by CloudflareWorkerHttpApplicationAdapter.create(...); application options such as globalPrefix, cors, middleware, and securityHeaders belong to FluoFactory.create(...) or the fixed-module host configuration.
  • WebSocket upgrades are owned by the same listen boundary as HTTP dispatch; upgrade requests before listen() do not reach the configured binding, and attempts to replace or clear a defined binding after the adapter has ever listened fail fast instead of mutating Worker upgrade ownership. Create a new adapter when a host needs a different websocket binding.
  • close() returns JSON 503 responses for new HTTP and WebSocket upgrade requests during and after shutdown and times out after 10 seconds if active requests never settle. Calling listen() while that close drain is still active rejects with the Cloudflare Workers adapter shutdown-draining error. Lazy hosts do not permanently cache that timeout once the adapter's underlying drain later finishes.
  • The Worker fetch(...) dispatch path preserves body-bearing RFC QUERY routes and uppercase extension methods such as PURGE; their method token and parsed body reach the registered route through the same fetch dispatch seam.
  • Multipart requests do not preserve rawBody.
  • The Worker env object is attached to each FrameworkRequest as request.cloudflare.env, with the Worker execution context available as request.cloudflare.executionContext. Direct FluoFactory.create(...) completes module registration before its exported fetch(...) handles traffic. CloudflareWorkerApplicationHost.create(AppModule, options) keeps its predeclared root module and options, so its fetch-time env attaches only during request dispatch. The { fromEnv } overload is the opt-in alternative when the first explicit Worker environment must select the root module or final bootstrap options before module registration. Its ready(env) method requires that environment, caches the first environment's module and options once per host instance, and builds one application per application generation from that configuration. A successful close restarts the application without rerunning the factory or accepting later environments as bootstrap configuration. In either path, use request-bound request.cloudflare.env for bindings that are intentionally per-request.

Lifecycle and Public Seam Notes

The root @fluojs/platform-cloudflare-workers export owns the Worker public seam for application code and first-party Worker websocket integrations. Worker-specific public types such as CloudflareWorkerExecutionContext, CloudflareWorkerRequestContext, CloudflareWorkerWebSocketBinding, CloudflareWorkerWebSocketPair, CloudflareWorkerWebSocketPairFactory, CloudflareWorkerWebSocketUpgradeHost, and CloudflareWorkerWebSocketUpgradeResult are exported from this package instead of asking consumers to import @fluojs/http/internal or @fluojs/runtime/internal* subpaths.

The listen, shutdown, SSE drain, and websocket binding rules above are public lifecycle behavior. Changes to those public seam types or lifecycle semantics are release-governed for @fluojs/platform-cloudflare-workers; user-impacting updates must be tracked with Changesets alongside the implementation, docs, and tests.

{
  "closeOwnership": {
    "inFetchManagement": "wait-until",
    "outOfBand": "await",
    "restart": "restartable"
  },
  "realtimeCapability": {
    "bindingInstallationVersion": 1,
    "contract": "raw-websocket-expansion",
    "kind": "fetch-style",
    "mode": "request-upgrade",
    "support": "supported",
    "version": 1
  }
}

Conformance Coverage

packages/platform-cloudflare-workers/src/adapter.test.ts and packages/platform-cloudflare-workers/src/adapter-lifecycle.test.ts are the package-local regression targets for the documented Worker contract. They cover shared Web dispatch delegation, Worker env request attachment, executionContext.waitUntil(...) SSE (text/event-stream) body tracking, body-cancellation and synchronous setup-failure drains, websocket upgrade binding, upgraded server-socket close tracking, pre-listen HTTP and websocket lifecycle guards, websocket binding freeze after the listen boundary, fixed-module and env-aware lazy host reuse, explicit env-aware readiness, first-environment configuration retention across successful host restarts, timeout recovery, shutdown gating, drain-time listen() rejection, JSON 503 responses while closing and after close for both HTTP and websocket upgrades, reliable fake-timer cleanup, public seam source imports, the structured realtime capability contract, and the bounded 10-second close timeout.

The shared edge portability suite in packages/testing/src/portability/web-runtime-adapter-portability.test.ts exercises Cloudflare Workers beside Bun and Deno for conditional requests, single-byte ranges and If-Range, body-bearing QUERY and PURGE fetch dispatch, malformed cookie preservation, query decoding, JSON/text raw-body capture, multipart raw-body exclusion, and SSE framing. The package test parses the structured realtime capability contract in both README locales and compares its machine-consumed values with the adapter capability.

Public API Overview

  • CloudflareWorkerHttpApplicationAdapter.create(options): Creates the concrete Worker HTTP adapter.
  • CloudflareWorkerApplicationHost.create(AppModule, options): Creates a lazy fixed-module Worker host.
  • CloudflareWorkerApplicationHost.create({ fromEnv }): Creates an env-aware lazy Worker host from its first explicit Worker environment.
  • CloudflareWorkerApplicationGeneration<Env>: The host ready(...) result with app, concrete adapter, extractable fetch, and close(...).
  • Options and types: CloudflareWorkerAdapterOptions, CloudflareWorkerApplicationHostOptions, CloudflareWorkerHostConfiguration, CloudflareWorkerHostFactory<Env>, CloudflareWorkerExecutionContext, CloudflareWorkerRequestContext, CloudflareWorkerWebSocketBinding, CloudflareWorkerWebSocket, CloudflareWorkerWebSocketMessage, CloudflareWorkerWebSocketPair, CloudflareWorkerWebSocketPairFactory, CloudflareWorkerWebSocketUpgradeHost, and CloudflareWorkerWebSocketUpgradeResult.

Related Packages

  • @fluojs/runtime: Core framework runtime.
  • @fluojs/websockets: Includes specific subpath @fluojs/websockets/cloudflare-workers.
  • @fluojs/http: Shared HTTP decorators.

Example Sources

  • packages/platform-cloudflare-workers/src/adapter.test.ts
  • packages/websockets/src/cloudflare-workers/cloudflare-workers.test.ts

Bounded Body Parsing

CloudflareWorkerAdapterOptions inherits the runtime Web factory's HTTP-owned bodyParser policy. CloudflareWorkerHttpApplicationAdapter.create(...) and host configuration accept 'text', 'default', or a custom callback. Omission retains MIME-based parsing and existing limits.

import { CloudflareWorkerHttpApplicationAdapter } from '@fluojs/platform-cloudflare-workers';

const adapter = CloudflareWorkerHttpApplicationAdapter.create({
  bodyParser: 'text', maxBodySize: 1_048_576, rawBody: true,
});

Headers and native Request identity are preserved. The runtime clone path keeps the original body readable. Multipart remains independent. Parsing happens before HTTP middleware/guards, so text alone does not guarantee auth order. See the HTTP contract and matrix for callback delegation, errors, empty bodies, and cancellation cooperation. src/body-parser.test.ts runs the actual adapter with the shared wire fixture and observes its waitUntil lifecycle. This is local Web conformance, not a claim of a deployed Cloudflare isolate test.