Heximon Logo
Deploy

Deno Deploy

DenoDeployStrategy, dist/deno-worker.js, Deno.cron registrations, deployctl deploy, platform limits.

Deno Deploy is the simplest artifact of any Heximon platform: one self-contained script, no config file, no deploy tree — just dist/deno-worker.js and a deployctl deploy call.

Configure the strategy

heximon.config.ts
import { defineHeximonConfig } from "@heximon/build";
import { DenoDeployStrategy } from "@heximon/deno";
import { HttpPlugin } from "@heximon/http/compiler";

export default defineHeximonConfig({
  platform: new DenoDeployStrategy(),
  plugins: [new HttpPlugin()],
});

DenoDeployStrategy is an edge-class strategy, platform name "deno".

What vp build emits

terminal
vp build                                              # → dist/deno-worker.js
deployctl deploy --project=<your-project> dist/deno-worker.js

The strategy resolves { noExternal: true } — every dependency (@heximon/*, npm deps, your app source) is bundled into the one file, because Deno Deploy doesn't load node_modules. Only node:*-prefixed builtins stay external, resolved against Deno's own Node-compat layer.

The generated deno-entry.js

deno-entry.js (generated, simplified)
import { createServer, H3FetchClient } from "@heximon/http";
import { routes } from "./router.js";
import { Platform } from "@heximon/runtime";
import { createApp } from "./apps/main.wiring.js";

let serverPromise;

async function boot() {
  Platform.capture(Deno.env.toObject());
  let server;
  const internalClient = new H3FetchClient((input, init) =>
    server.fetch(new Request(new URL(input, "http://localhost"), init)));
  const app = await createApp({ InternalClient: internalClient });
  server = createServer(routes, app);
  return server;
}

export default {
  async fetch(request) {
    serverPromise ??= boot();
    const server = await serverPromise;
    return server.fetch(request);
  },
};

Two details worth noting: there's no env argument threaded per request the way Cloudflare Workers gets one — Deno env is ambient via Deno.env, captured once at isolate boot — and the entry does not export a scheduled key. Crons register a different way entirely (next section).

Scheduled work

List SchedulePlugin and DenoWorkerPlugin reads the discovered cron expressions, baking one top-level Deno.cron(name, expr, cb) registration per handler directly into deno-entry.js:

import type { ScheduledHandler } from "@heximon/schedule";
import { SessionRepository } from "./session-repository";

export class NightlyCleanup implements ScheduledHandler<"0 3 * * *", { name: "Nightly cleanup" }> {
  public constructor(private readonly sessions: SessionRepository) {}

  public handle(scheduledAt: Date): void {
    this.sessions.purgeExpired(scheduledAt);
  }
}
deno-entry.js fragment (generated)
import { scheduled as heximonScheduled } from "./schedule-cron.js";

Deno.cron("Nightly cleanup", "0 3 * * *", async () => {
  await heximonScheduled({ cron: "0 3 * * *", scheduledTime: Date.now() });
});

The DenoScheduleDriver adds no clock of its own — the platform fires the Deno.cron callback, which forwards into the generated consumer and dispatches inside a fresh Context.run frame. scheduledAt is Date.now() here, since Deno's cron callback receives no native scheduled-time argument the way Cloudflare's controller.scheduledTime does.

Deno awaits the async callback before the invocation exits, so deferred work inside the handler completes with no explicit waitUntil needed.

Platform limits

Deno Deploy is a V8-isolate edge runtime with none of Cloudflare's stateful primitives:

FeatureSupported
HTTP controllersYes
Scheduled cron (@heximon/schedule)Yes — via Deno.cron()
Queues (@heximon/queue)No
Durable Objects (@heximon/durable)No
Workflows (@heximon/workflow)No
Durable WebSocket (@heximon/websocket)No

Compiling a Durable Object or workflow against DenoDeployStrategy emits a PlatformEdgeUnsupported diagnostic naming "deno" and the offending class — the build fails before any code is emitted.

A queue handler gets its own diagnostic, PlatformFeatureUnsupported, for the same reason: the in-memory drain is ephemeral on an edge isolate and the polling consumers are Node-only, so produce to an external queue from the edge and consume it on a Node or Lambda worker instead.

Nitro path

On @heximon/nitro with a deno-deploy preset, the Nitro module selects DenoDeployStrategy automatically — no heximon.platform entry needed on that path. The same DenoWorkerPlugin assembles the entry either way.

Dev

Deno Deploy has no Miniflare equivalent, so vp dev runs your app as a standard Node HTTP server — the strategy's built-in NodeStrategy dev default — with the full build-error page, JSON preview, recompile-on-change, and auto-reload preserved.

Ship it

deployctl deploy --project=<your-project> dist/deno-worker.js   # add --prod for the production domain

DenoDeployStrategy implements the @heximon/deploy capability, so heximon deploy runs that same command for you (the project name resolves from DENO_DEPLOY_PROJECT when set, and a "production" environment adds --prod). Project teardown is dashboard/API-only — deployctl has no delete command, so there's no heximon deploy --destroy for Deno.

See also

  • ScheduleScheduledHandler, cron discovery, and the driver abstraction.
  • Deploy overview — the supportedFeatures gate shared across every platform.
  • heximon deploy — the CLI's Deno-specific preflight and --project resolution.
Copyright © 2026