@fluojs/platform-deno
v2.0.1
Published
Deno-based HTTP adapter for the Fluo runtime.
Downloads
440
Maintainers
Readme
@fluojs/platform-deno
Deno-backed HTTP adapter for the fluo runtime, built on native Deno.serve.
Table of Contents
- Installation
- When to Use
- Quick Start
- Common Patterns
- HTTPS and Runtime Portability
- Conformance Coverage
- Public API Overview
- Related Packages
- Example Sources
Installation
deno add npm:@fluojs/platform-deno npm:@fluojs/runtime npm:@fluojs/httpThis package is intended to run on Deno. The published manifest intentionally does not declare engines.node, so npm metadata stays aligned with the Deno runtime contract; the repository's Node.js 20+ requirement only applies to the maintainer build/test toolchain.
When to Use
Use this package when running fluo applications on the Deno runtime. This adapter leverages Deno's native fetch-standard Request and Response objects, providing a secure and high-performance environment for TypeScript backend development.
During application shutdown, the adapter stops new ingress and gives active HTTP handlers a bounded drain window before the Deno server lifecycle completes.
Factory registers createDenoShutdownSignalRegistration() after listen when supplied as shutdownRegistration, and removes those SIGINT/SIGTERM listeners when close begins.
Quick Start
import {
DenoHttpApplicationAdapter,
createDenoShutdownSignalRegistration,
} from '@fluojs/platform-deno';
import { FluoFactory } from '@fluojs/runtime';
import { AppModule } from './app.module.ts';
const app = await FluoFactory.create(AppModule, {
adapter: DenoHttpApplicationAdapter.create({ port: 3000 }),
shutdownRegistration: createDenoShutdownSignalRegistration(),
});
await app.listen();The application-owned startup path opens a network listener. The host may register SIGINT/SIGTERM with createDenoShutdownSignalRegistration() and owns callback failure handling. Run the entrypoint with network access:
deno run --allow-net main.tsSignal listener registration does not require a separate Deno permission. The adapter does not read environment variables. Add a scoped grant such as --allow-env=PORT,DATABASE_URL only when application code reads those keys. Hosts coordinate signal callback failure status and forced termination themselves.
Common Patterns
Early Hints are unsupported
Deno's Fetch Response cannot represent an informational response before the final response, so context.response.earlyHints is absent. Check for capability presence before use. The adapter never turns a requested 103 into a silent no-op or a final-response header.
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.
Host-Owned Deno.serve
If your application owns Deno.serve(...), bootstrap the fluo application without calling app.listen() and create a request handler from its public dispatcher. createDenoFetchHandler(...) only translates and dispatches requests; it never starts a server or owns shutdown, signals, or websocket upgrades.
import { DenoHttpApplicationAdapter, createDenoFetchHandler } from '@fluojs/platform-deno';
import { FluoFactory } from '@fluojs/runtime';
const adapter = DenoHttpApplicationAdapter.create();
const app = await FluoFactory.create(AppModule, { adapter });
const handler = createDenoFetchHandler({
dispatcher: app.dispatcher,
rawBody: true,
});
const server = Deno.serve({ port: 3000 }, handler);
try {
await server.finished;
} finally {
await app.close();
}The surrounding host must stop server, coordinate process signals, and decide whether websocket upgrades are handled separately. Use app.listen() when fluo should own the managed server lifecycle; supply createDenoShutdownSignalRegistration() to Factory when the host wants Deno signal registration. adapter.handle(...) remains available only after the managed adapter's listen(dispatcher) binding has completed.
Direct Adapter Construction
Application code should usually prefer DenoHttpApplicationAdapter.create(options) so adapter setup stays explicit. Custom orchestration and tests may use new DenoHttpApplicationAdapter(options?) directly; the constructor accepts the same optional public DenoAdapterOptions as the factory, applies the default port when options are omitted, preserves hostname over the portable host alias, and rejects invalid port or maxBodySize values during setup.
Opt-in Deno WebSocket Binding
The adapter supports Deno's native Deno.upgradeWebSocket after the application imports and configures the @fluojs/websockets/deno binding. Without that binding, websocket upgrade requests continue through normal HTTP dispatch instead of being upgraded implicitly.
import { Module } from '@fluojs/core';
import { OnMessage, WebSocketGateway } from '@fluojs/websockets';
import { DenoWebSocketModule } from '@fluojs/websockets/deno';
@WebSocketGateway({ path: '/ws' })
export class MyGateway {
@OnMessage('ping')
handlePing(_payload: unknown, socket) {
socket.send(JSON.stringify({ event: 'pong', data: 'hello from deno' }));
}
}
@Module({
imports: [DenoWebSocketModule.forRoot()],
providers: [MyGateway],
})
export class RealtimeModule {}Deno websocket inbound payloads
DenoWebSocketMessage represents every payload form the Deno binding can deliver:
ArrayBuffer | ArrayBufferView | Blob | string. Existing handlers that narrowed this
public union exhaustively for Blob | string must add ArrayBuffer and
ArrayBufferView branches. The @fluojs/websockets/deno binding already accepts and
normalizes these binary payloads before it dispatches gateway handlers.
HTTPS and Runtime Portability
Pass Deno TLS certificate material through the https option to start Deno.serve in HTTPS mode. The adapter forwards https.cert and https.key to Deno as cert and key, and startup logging reports an https:// listen URL so the Deno package stays aligned with the shared HTTP adapter portability contract.
import { DenoHttpApplicationAdapter, createDenoShutdownSignalRegistration } from '@fluojs/platform-deno';
import { FluoFactory } from '@fluojs/runtime';
const app = await FluoFactory.create(AppModule, {
adapter: DenoHttpApplicationAdapter.create({
hostname: '127.0.0.1',
https: {
cert: await Deno.readTextFile('./cert.pem'),
key: await Deno.readTextFile('./key.pem'),
},
port: 3443,
}),
shutdownRegistration: createDenoShutdownSignalRegistration(),
});
await app.listen();hostname remains the Deno-native option name. The adapter also accepts host as a portability alias for shared HTTP adapter tests and cross-runtime configuration helpers; when both are provided, hostname wins for the Deno.serve(...) bind target and reported listen URL.
Advanced options include injectable serve and upgradeWebSocket seams for tests or non-hosted runtimes, rawBody, maxBodySize, and multipart. createDenoFetchHandler(...) accepts the same request parsing options and preserves byte-exact JSON/text raw bodies while excluding rawBody for multipart requests. When a seam is not injected, the managed adapter falls back to globalThis.Deno.serve and globalThis.Deno.upgradeWebSocket at listen/upgrade time. createDenoShutdownSignalRegistration() selects SIGINT/SIGTERM by default; omitting Factory shutdownRegistration leaves signals to the host, and failed multi-signal registration rolls back listeners that were already attached. Duplicate listen(...) calls on an already-running adapter are no-ops that preserve the original dispatcher pipeline. Close waits up to 10 seconds for active requests to drain before aborting the Deno serve signal. If graceful shutdown rejects, the adapter aborts ingress but retains the active server controller until server.finished settles; only then does it release the managed lifecycle and rethrow the original shutdown error. handle(...) returns a JSON 500 before listen() binds the dispatcher, including websocket upgrade requests, and a JSON 503 while shutdown is in progress.
Conformance Coverage
packages/platform-deno/src/adapter.test.ts is the package-local regression target for the managed Deno contract. It covers shared Web dispatch delegation, direct adapter.handle(...) success-path dispatch after listen(dispatcher), direct constructor/factory option normalization, HTTPS startup forwarding, host alias and hostname precedence for the Deno.serve(...) bind target and startup log, duplicate listen(...) no-op dispatcher preservation, default SIGINT/SIGTERM signal listener registration, omitting shutdownRegistration, listener rollback after partial signal-registration failure, websocket upgrade binding and no-binding HTTP fallback, websocket pre-listen bootstrap gating, global Deno serve/upgrade fallback seams, pre-listen 500 handling, shutdown 503 handling, in-flight request drain before serve-signal abort, shutdown-failure ownership until server.finished, and the bounded 10-second close timeout. packages/platform-deno/src/fetch-handler.test.ts applies the shared web-runtime portability harness to the host-owned handler, covering cookies/query decoding, JSON/text and byte-exact raw bodies, multipart exclusion, SSE framing, and proof that dispatch does not call Deno.serve(...). packages/platform-deno/src/declaration-surface.test.ts rebuilds the package and verifies the manifest-exported declarations.
The shared edge portability suite in packages/testing/src/portability/web-runtime-adapter-portability.test.ts exercises Deno beside Bun and Cloudflare Workers for malformed cookie preservation, query decoding, JSON/text raw-body capture, single byte-range status/header/body semantics, multipart raw-body exclusion, and SSE framing. The README parity assertion in the package test keeps these documented edge-runtime coverage claims synchronized with the Korean mirror.
The Deno 2 smoke lane checks the public npm:@fluojs/platform-deno root import and executes the built adapter closure natively. It dispatches a host-owned createDenoFetchHandler(...) request without a listener, then creates a static Deno adapter on port 0, calls FluoFactory.create(...) without a shutdown callback and awaits app.listen(), fetches a real route, and closes the application. Signal ownership remains disabled for that managed-listener test; signal registration is covered separately by the package-local contract suite.
Public API Overview
DenoHttpApplicationAdapter.create(options): Factory for the Deno HTTP adapter; it shares validation and normalization with direct construction.createDenoFetchHandler(options): Synchronously creates aRequesthandler from an already bootstrappedapp.dispatcherwithout starting or owningDeno.serve(...).DenoHttpApplicationAdapter(options?): Core adapter implementation. Directnew DenoHttpApplicationAdapter()ornew DenoHttpApplicationAdapter(options)applies the same default port,hostalias handling,hostnameprecedence, and numeric option validation asDenoHttpApplicationAdapter.create(options).createDenoShutdownSignalRegistration(signals?): Host signal callback supplied to Factory; rolls back partial registration and attempts every listener removal on close.listen(dispatcher): Binds the fluo HTTP dispatcher and startsDeno.serve; duplicate calls are no-ops while preserving the original dispatcher.close(): Stops ingress, drains active requests for up to 10 seconds, and aborts the Deno serve signal if shutdown does not settle. A rejected graceful shutdown retainsgetServer()ownership untilserver.finishedsettles, then releases the managed lifecycle and rethrows the original shutdown error.handle(request): ManualRequesttoResponsedispatcher. It succeeds afterlisten(dispatcher)binds the runtime dispatcher, returns JSON500before binding, and returns JSON503while shutdown is in progress.getListenTarget(): Reports the bind target and public URL using Denohostnameor the portablehostalias.getRealtimeCapability(): Reports the fetch-style Deno websocket upgrade capability for runtime integration.getServer(): Returns the activeDeno.servecontroller while the adapter is listening.- Deno realtime modules install their binding through the version-1 capability during module registration, before
listen(dispatcher)starts the server; direct configure/setter APIs are removed. DenoWebSocketMessage: The full inbound websocket payload union:ArrayBuffer | ArrayBufferView | Blob | string.https: { cert, key }: HTTPS startup options forwarded toDeno.serveand reflected in the reported listen URL.- Option and seam types:
CreateDenoFetchHandlerOptions,DenoServeOptions,DenoServeController,DenoServerWebSocket, websocket binding interfaces,DenoAdapterOptions,DenoShutdownSignal, andDenoServeOnListenInfo.
Related Packages
@fluojs/runtime: Core framework runtime.@fluojs/websockets: Includes specific subpath@fluojs/websockets/deno.@fluojs/http: HTTP decorators and abstractions.
Example Sources
packages/platform-deno/src/adapter.test.tspackages/platform-deno/src/fetch-handler.test.tspackages/websockets/src/deno/deno.test.ts
